Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Show Remote SSH Output panel on workspace start#627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
EhabY merged 11 commits intocoder:mainfromEhabY:show-remote-ssh-output-on-start
Nov 11, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletionsCHANGELOG.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,12 @@

## Unreleased

### Changed

- Improved workspace connection progress messages and enhanced the workspace build terminal
with better log streaming. The extension now also waits for blocking startup scripts to
complete before connecting, providing clear progress indicators during the wait.

## [v1.11.3](https://github.com/coder/vscode-coder/releases/tag/v1.11.3) 2025-10-22

### Fixed
Expand Down
31 changes: 28 additions & 3 deletionssrc/api/coderApi.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import {
type ProvisionerJobLog,
type Workspace,
type WorkspaceAgent,
type WorkspaceAgentLog,
} from "coder/site/src/api/typesGenerated";
import * as vscode from "vscode";
import { type ClientOptions, type CloseEvent, type ErrorEvent } from "ws";
Expand DownExpand Up@@ -109,18 +110,42 @@ export class CoderApi extends Api {
logs: ProvisionerJobLog[],
options?: ClientOptions,
) => {
return this.watchLogs<ProvisionerJobLog>(
`/api/v2/workspacebuilds/${buildId}/logs`,
logs,
options,
);
};

watchWorkspaceAgentLogs = async (
agentId: string,
logs: WorkspaceAgentLog[],
options?: ClientOptions,
) => {
return this.watchLogs<WorkspaceAgentLog[]>(
`/api/v2/workspaceagents/${agentId}/logs`,
logs,
options,
);
};

private async watchLogs<TData>(
apiRoute: string,
logs: { id: number }[],
options?: ClientOptions,
) {
const searchParams = new URLSearchParams({ follow: "true" });
const lastLog = logs.at(-1);
if (lastLog) {
searchParams.append("after", lastLog.id.toString());
}

return this.createWebSocket<ProvisionerJobLog>({
apiRoute: `/api/v2/workspacebuilds/${buildId}/logs`,
return this.createWebSocket<TData>({
apiRoute,
searchParams,
options,
});
};
}

private async createWebSocket<TData = unknown>(
configs: Omit<OneWayWebSocketInit, "location">,
Expand Down
125 changes: 75 additions & 50 deletionssrc/api/workspace.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
import { spawn } from "child_process";
import { type Api } from "coder/site/src/api/api";
import { type Workspace } from "coder/site/src/api/typesGenerated";
import {
type WorkspaceAgentLog,
type ProvisionerJobLog,
type Workspace,
type WorkspaceAgent,
} from "coder/site/src/api/typesGenerated";
import { spawn } from "node:child_process";
import * as vscode from "vscode";

import { type FeatureSet } from "../featureSet";
import { getGlobalFlags } from "../globalFlags";
import { escapeCommandArg } from "../util";
import { type OneWayWebSocket } from "../websocket/oneWayWebSocket";

import { errToStr, createWorkspaceIdentifier } from "./api-helper";
import { type CoderApi } from "./coderApi";
Expand DownExpand Up@@ -36,35 +42,33 @@ export async function startWorkspaceIfStoppedOrFailed(
createWorkspaceIdentifier(workspace),
];
if (featureSet.buildReason) {
startArgs.push(...["--reason", "vscode_connection"]);
startArgs.push("--reason", "vscode_connection");
}

// { shell: true } requires one shell-safe command string, otherwise we lose all escaping
const cmd = `${escapeCommandArg(binPath)} ${startArgs.join(" ")}`;
const startProcess = spawn(cmd, { shell: true });

startProcess.stdout.on("data", (data: Buffer) => {
data
const lines =data
.toString()
.split(/\r*\n/)
.forEach((line: string) => {
if (line !== "") {
writeEmitter.fire(line.toString() + "\r\n");
}
});
.filter((line) => line !== "");
for (const line of lines) {
writeEmitter.fire(line.toString() + "\r\n");
}
});

let capturedStderr = "";
startProcess.stderr.on("data", (data: Buffer) => {
data
const lines =data
.toString()
.split(/\r*\n/)
.forEach((line: string) => {
if (line !== "") {
writeEmitter.fire(line.toString() + "\r\n");
capturedStderr += line.toString() + "\n";
}
});
.filter((line) => line !== "");
for (const line of lines) {
writeEmitter.fire(line.toString() + "\r\n");
capturedStderr += line.toString() + "\n";
}
});

startProcess.on("close", (code: number) => {
Expand All@@ -82,51 +86,72 @@ export async function startWorkspaceIfStoppedOrFailed(
}

/**
* Wait for the latest build to finish while streaming logs to the emitter.
*
* Once completed, fetch the workspace again and return it.
* Streams build logs to the emitter in real-time.
* Returns the websocket for lifecycle management.
*/
export async functionwaitForBuild(
export async functionstreamBuildLogs(
client: CoderApi,
writeEmitter: vscode.EventEmitter<string>,
workspace: Workspace,
): Promise<Workspace> {
// This fetches the initial bunch of logs.
const logs = await client.getWorkspaceBuildLogs(workspace.latest_build.id);
logs.forEach((log) => writeEmitter.fire(log.output + "\r\n"));

): Promise<OneWayWebSocket<ProvisionerJobLog>> {
const socket = await client.watchBuildLogsByBuildId(
workspace.latest_build.id,
logs,
[],
);

await new Promise<void>((resolve, reject) => {
socket.addEventListener("message", (data) => {
if (data.parseError) {
writeEmitter.fire(
errToStr(data.parseError, "Failed to parse message") + "\r\n",
);
} else {
writeEmitter.fire(data.parsedMessage.output + "\r\n");
}
});
socket.addEventListener("message", (data) => {
if (data.parseError) {
writeEmitter.fire(
errToStr(data.parseError, "Failed to parse message") + "\r\n",
);
} else {
writeEmitter.fire(data.parsedMessage.output + "\r\n");
}
});

socket.addEventListener("error", (error) => {
const baseUrlRaw = client.getAxiosInstance().defaults.baseURL;
writeEmitter.fire(
`Error watching workspace build logs on ${baseUrlRaw}: ${errToStr(error, "no further details")}\r\n`,
);
});

socket.addEventListener("close", () => {
writeEmitter.fire("Build complete\r\n");
});

return socket;
}

socket.addEventListener("error", (error) => {
const baseUrlRaw = client.getAxiosInstance().defaults.baseURL;
return reject(
new Error(
`Failed to watch workspace build on ${baseUrlRaw}: ${errToStr(error, "no further details")}`,
),
/**
* Streams agent logs to the emitter in real-time.
* Returns the websocket for lifecycle management.
*/
export async function streamAgentLogs(
client: CoderApi,
writeEmitter: vscode.EventEmitter<string>,
agent: WorkspaceAgent,
): Promise<OneWayWebSocket<WorkspaceAgentLog[]>> {
const socket = await client.watchWorkspaceAgentLogs(agent.id, []);

socket.addEventListener("message", (data) => {
if (data.parseError) {
writeEmitter.fire(
errToStr(data.parseError, "Failed to parse message") + "\r\n",
);
});
} else {
for (const log of data.parsedMessage) {
writeEmitter.fire(log.output + "\r\n");
}
}
});

socket.addEventListener("close", () => resolve());
socket.addEventListener("error", (error) => {
const baseUrlRaw = client.getAxiosInstance().defaults.baseURL;
writeEmitter.fire(
`Error watching agent logs on ${baseUrlRaw}: ${errToStr(error, "no further details")}\r\n`,
);
});

writeEmitter.fire("Build complete\r\n");
const updatedWorkspace = await client.getWorkspace(workspace.id);
writeEmitter.fire(
`Workspace is now ${updatedWorkspace.latest_build.status}\r\n`,
);
return updatedWorkspace;
return socket;
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp