@devframes/plugin-code-server 0.7.5 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -27
- package/dist/cli.mjs +1 -1
- package/dist/client/index.d.mts +8 -70
- package/dist/client/index.mjs +9 -433
- package/dist/{constants-DZKOXnxd.d.mts → constants-DtQz38ex.d.mts} +5 -5
- package/dist/constants.d.mts +1 -1
- package/dist/constants.mjs +5 -5
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +1 -1
- package/dist/node/index.d.mts +122 -27
- package/dist/node/index.mjs +358 -90
- package/dist/rpc/index.mjs +1 -1
- package/dist/{rpc-DjerPudu.mjs → rpc-BdNzxqDi.mjs} +10 -6
- package/dist/spa/assets/index-CT2PEkxJ.js +2 -0
- package/dist/spa/assets/index-qPxwYWWC.css +1 -0
- package/dist/spa/index.html +2 -2
- package/dist/{src-CjjdHGFH.mjs → src-BrJNMY5c.mjs} +2 -2
- package/dist/types.d.mts +114 -33
- package/dist/vite.mjs +1 -1
- package/package.json +11 -9
- package/dist/spa/assets/index-CkuBbEzE.js +0 -2
- package/dist/spa/assets/index-YTtYo-AL.css +0 -1
package/dist/node/index.mjs
CHANGED
|
@@ -1,10 +1,114 @@
|
|
|
1
1
|
import { DEFAULT_CODE_SERVER_PORT, PLUGIN_ID, STATE_KEY, TERMINAL_SESSION_ICON, TERMINAL_SESSION_TITLE, getCookieSessionName } from "../constants.mjs";
|
|
2
|
-
import { i as diagnostics, n as getCodeServerSupervisor, r as setCodeServerSupervisor, t as serverFunctions } from "../rpc-
|
|
2
|
+
import { i as diagnostics, n as getCodeServerSupervisor, r as setCodeServerSupervisor, t as serverFunctions } from "../rpc-BdNzxqDi.mjs";
|
|
3
3
|
import { execSync, spawn } from "node:child_process";
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
5
|
import { request } from "node:http";
|
|
6
|
+
import { hostname } from "node:os";
|
|
6
7
|
import process from "node:process";
|
|
7
8
|
import { getPort } from "get-port-please";
|
|
9
|
+
//#region src/node/backends.ts
|
|
10
|
+
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
11
|
+
/** Coder's open-source `code-server`, with password auth + session cookie handoff. */
|
|
12
|
+
const codeServerProfile = {
|
|
13
|
+
kind: "code-server",
|
|
14
|
+
backend: "code-server",
|
|
15
|
+
defaultBin: "code-server",
|
|
16
|
+
buildArgs: (c) => [
|
|
17
|
+
"--auth",
|
|
18
|
+
"password",
|
|
19
|
+
"--bind-addr",
|
|
20
|
+
`${c.host}:${c.port}`,
|
|
21
|
+
"--disable-telemetry",
|
|
22
|
+
"--disable-update-check",
|
|
23
|
+
...c.extraArgs,
|
|
24
|
+
c.folder
|
|
25
|
+
],
|
|
26
|
+
buildEnv: (c, base) => {
|
|
27
|
+
const env = { ...base };
|
|
28
|
+
delete env.PASSWORD;
|
|
29
|
+
env.HASHED_PASSWORD = sha256(c.secret);
|
|
30
|
+
return env;
|
|
31
|
+
},
|
|
32
|
+
healthPath: "/healthz",
|
|
33
|
+
matchPort: (line) => {
|
|
34
|
+
const m = line.match(/HTTP server listening on https?:\/\/(?:[^:]+|\[[^\]]+\]):(\d+)/);
|
|
35
|
+
return m ? Number.parseInt(m[1], 10) : void 0;
|
|
36
|
+
},
|
|
37
|
+
connect: (c) => ({
|
|
38
|
+
path: "/",
|
|
39
|
+
cookie: {
|
|
40
|
+
name: c.cookieName,
|
|
41
|
+
value: sha256(c.secret)
|
|
42
|
+
}
|
|
43
|
+
}),
|
|
44
|
+
connectReused: () => ({ path: "/" })
|
|
45
|
+
};
|
|
46
|
+
/** Microsoft's `code serve-web`, with a connection token passed via `?tkn=`. */
|
|
47
|
+
const serveWebProfile = {
|
|
48
|
+
kind: "serve-web",
|
|
49
|
+
backend: "ms-code-serve-web",
|
|
50
|
+
defaultBin: "code",
|
|
51
|
+
buildArgs: (c) => [
|
|
52
|
+
"serve-web",
|
|
53
|
+
"--accept-server-license-terms",
|
|
54
|
+
"--host",
|
|
55
|
+
c.host,
|
|
56
|
+
"--port",
|
|
57
|
+
String(c.port),
|
|
58
|
+
"--connection-token",
|
|
59
|
+
c.secret,
|
|
60
|
+
...c.extraArgs
|
|
61
|
+
],
|
|
62
|
+
buildEnv: (_c, base) => ({ ...base }),
|
|
63
|
+
healthPath: "/",
|
|
64
|
+
matchPort: (line) => {
|
|
65
|
+
const m = line.match(/available at https?:\/\/[^:]+:(\d+)/i);
|
|
66
|
+
return m ? Number.parseInt(m[1], 10) : void 0;
|
|
67
|
+
},
|
|
68
|
+
connect: (c) => ({ path: `/?tkn=${encodeURIComponent(c.secret)}&folder=${encodeURIComponent(c.folder)}` }),
|
|
69
|
+
connectReused: () => ({ path: "/" })
|
|
70
|
+
};
|
|
71
|
+
const TUNNEL_URL_RE = /(https:\/\/vscode\.dev\/tunnel\/\S+)/;
|
|
72
|
+
const DEVICE_LOGIN_RE = /log in(?:to)? (https?:\/\/\S+) and use code ([A-Z0-9-]+)/i;
|
|
73
|
+
/** Microsoft's `code tunnel`, embedding the hosted `vscode.dev` editor. */
|
|
74
|
+
const tunnelProfile = {
|
|
75
|
+
kind: "tunnel",
|
|
76
|
+
backend: "ms-code-serve-web",
|
|
77
|
+
defaultBin: "code",
|
|
78
|
+
buildArgs: (c) => [
|
|
79
|
+
"tunnel",
|
|
80
|
+
"--accept-server-license-terms",
|
|
81
|
+
"--name",
|
|
82
|
+
c.tunnelName,
|
|
83
|
+
...c.extraArgs
|
|
84
|
+
],
|
|
85
|
+
buildEnv: (_c, base) => ({ ...base }),
|
|
86
|
+
healthPath: null,
|
|
87
|
+
matchReadyUrl: (line) => {
|
|
88
|
+
const m = line.match(TUNNEL_URL_RE);
|
|
89
|
+
return m ? m[1].replace(/[)\].,]+$/, "") : void 0;
|
|
90
|
+
},
|
|
91
|
+
matchLogin: (line) => {
|
|
92
|
+
const m = line.match(DEVICE_LOGIN_RE);
|
|
93
|
+
return m ? {
|
|
94
|
+
url: m[1],
|
|
95
|
+
code: m[2]
|
|
96
|
+
} : void 0;
|
|
97
|
+
},
|
|
98
|
+
connect: (c) => ({ url: c.readyUrl ?? `https://vscode.dev/tunnel/${c.tunnelName}${c.folder}` }),
|
|
99
|
+
connectReused: () => ({})
|
|
100
|
+
};
|
|
101
|
+
const LOCAL_PROFILES = {
|
|
102
|
+
"code-server": codeServerProfile,
|
|
103
|
+
"ms-code-serve-web": serveWebProfile
|
|
104
|
+
};
|
|
105
|
+
/** The ordered auto-detection candidates for `mode: 'local'` with no explicit backend. */
|
|
106
|
+
const AUTO_DETECT_ORDER = ["code-server", "ms-code-serve-web"];
|
|
107
|
+
/** Resolve the launch profile for a mode + backend. */
|
|
108
|
+
function resolveProfile(mode, backend) {
|
|
109
|
+
return mode === "tunnel" ? tunnelProfile : LOCAL_PROFILES[backend];
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
8
112
|
//#region src/node/detect.ts
|
|
9
113
|
/**
|
|
10
114
|
* Probe the host for a usable code-server binary by running
|
|
@@ -86,53 +190,77 @@ const LOG_BUFFER_LINES = 200;
|
|
|
86
190
|
/** Interval between readiness probes. */
|
|
87
191
|
const PROBE_INTERVAL = 250;
|
|
88
192
|
/**
|
|
89
|
-
* Owns the lifecycle of a single
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
193
|
+
* Owns the lifecycle of a single editor child process. Resolves a launch
|
|
194
|
+
* {@link CodeServerProfile} (Coder `code-server`, Microsoft `code serve-web`,
|
|
195
|
+
* or `code tunnel`), detects the binary, launches it with freshly generated
|
|
196
|
+
* auth material, waits for readiness, and mirrors a secret-free status into
|
|
197
|
+
* shared state. The connect descriptor (session cookie / connection token /
|
|
198
|
+
* tunnel URL) is handed back only through `start()` / `status()` so the
|
|
199
|
+
* already-authorized client can open the editor without a login page.
|
|
94
200
|
*
|
|
95
201
|
* Depends only on the core devframe context (shared state), not on the hub.
|
|
96
202
|
*/
|
|
97
203
|
var CodeServerSupervisor = class {
|
|
98
204
|
ctx;
|
|
99
|
-
|
|
205
|
+
mode;
|
|
206
|
+
explicitBackend;
|
|
207
|
+
explicitBin;
|
|
100
208
|
workspace;
|
|
101
209
|
host;
|
|
102
210
|
forcedPort;
|
|
103
211
|
extraArgs;
|
|
104
212
|
extraEnv;
|
|
105
|
-
cookieName;
|
|
106
213
|
cookieSuffix;
|
|
214
|
+
cookieName;
|
|
107
215
|
startTimeout;
|
|
216
|
+
reuseExistingServer;
|
|
217
|
+
tunnelName;
|
|
218
|
+
/** Resolved after the first detection. */
|
|
219
|
+
backend;
|
|
220
|
+
bin;
|
|
221
|
+
profile;
|
|
108
222
|
state;
|
|
109
223
|
detection;
|
|
110
224
|
server = { status: "stopped" };
|
|
111
225
|
proc;
|
|
112
|
-
|
|
226
|
+
/** Context of the live launch, used to compute the client connect descriptor. */
|
|
227
|
+
launchCtx;
|
|
228
|
+
/** Whether the running server was adopted (reused) rather than launched. */
|
|
229
|
+
adopted = false;
|
|
230
|
+
/** Captured `vscode.dev` URL for a running tunnel. */
|
|
231
|
+
readyUrl;
|
|
113
232
|
logBuffer = [];
|
|
114
|
-
|
|
233
|
+
exitHandler;
|
|
115
234
|
/** Stable id of the hub terminal session, reused across start/stop. */
|
|
116
235
|
sessionId;
|
|
117
236
|
/** The live hub terminal session when launched through `ctx.terminals`. */
|
|
118
237
|
session;
|
|
119
238
|
constructor(ctx, options = {}) {
|
|
120
239
|
this.ctx = ctx;
|
|
121
|
-
this.
|
|
240
|
+
this.mode = options.mode ?? "local";
|
|
241
|
+
this.explicitBackend = options.backend;
|
|
242
|
+
this.explicitBin = options.bin;
|
|
122
243
|
this.workspace = options.cwd ?? ctx.cwd;
|
|
123
|
-
this.host = options.host ?? "
|
|
244
|
+
this.host = options.host ?? "127.0.0.1";
|
|
124
245
|
this.forcedPort = options.serverPort;
|
|
125
246
|
this.extraArgs = options.args ?? [];
|
|
126
247
|
this.extraEnv = options.env ?? {};
|
|
127
248
|
this.cookieSuffix = options.cookieSuffix;
|
|
128
249
|
this.cookieName = getCookieSessionName(options.cookieSuffix);
|
|
129
250
|
this.startTimeout = options.startTimeout ?? 3e4;
|
|
251
|
+
this.reuseExistingServer = options.reuseExistingServer ?? false;
|
|
252
|
+
this.tunnelName = options.tunnel?.name || hostname().split(".").join("") || "devframe";
|
|
253
|
+
this.sessionId = options.cookieSuffix ? `${PLUGIN_ID}:${options.cookieSuffix}` : PLUGIN_ID;
|
|
254
|
+
this.backend = this.explicitBackend ?? "code-server";
|
|
255
|
+
this.profile = resolveProfile(this.mode, this.backend);
|
|
256
|
+
this.bin = this.explicitBin ?? this.profile.defaultBin;
|
|
130
257
|
this.detection = {
|
|
131
258
|
checked: false,
|
|
132
259
|
installed: false,
|
|
133
|
-
bin: this.bin
|
|
260
|
+
bin: this.bin,
|
|
261
|
+
backend: this.backend,
|
|
262
|
+
mode: this.mode
|
|
134
263
|
};
|
|
135
|
-
this.sessionId = options.cookieSuffix ? `${PLUGIN_ID}:${options.cookieSuffix}` : PLUGIN_ID;
|
|
136
264
|
}
|
|
137
265
|
/** Resolve shared state, register process-exit cleanup, run first detection. */
|
|
138
266
|
async init() {
|
|
@@ -141,72 +269,124 @@ var CodeServerSupervisor = class {
|
|
|
141
269
|
detection: this.detection,
|
|
142
270
|
server: this.server
|
|
143
271
|
} });
|
|
144
|
-
this.registerCleanup();
|
|
145
272
|
await this.detect();
|
|
146
273
|
}
|
|
147
|
-
/**
|
|
274
|
+
/**
|
|
275
|
+
* Probe for a usable editor binary and publish the result. Resolves the
|
|
276
|
+
* backend + binary when the caller left them implicit: tunnel mode always
|
|
277
|
+
* uses `code`; an explicit backend or `bin` is honored as-is; otherwise the
|
|
278
|
+
* plugin tries each {@link AUTO_DETECT_ORDER} candidate and keeps the first
|
|
279
|
+
* that is installed.
|
|
280
|
+
*/
|
|
148
281
|
async detect() {
|
|
149
|
-
|
|
282
|
+
let installed = false;
|
|
283
|
+
let version;
|
|
284
|
+
if (this.mode === "tunnel" || this.explicitBackend || this.explicitBin) {
|
|
285
|
+
const result = await detectCodeServer(this.bin);
|
|
286
|
+
installed = result.installed;
|
|
287
|
+
version = result.version;
|
|
288
|
+
} else {
|
|
289
|
+
let resolved;
|
|
290
|
+
for (const backend of AUTO_DETECT_ORDER) {
|
|
291
|
+
const bin = resolveProfile("local", backend).defaultBin;
|
|
292
|
+
const result = await detectCodeServer(bin);
|
|
293
|
+
if (result.installed) {
|
|
294
|
+
resolved = {
|
|
295
|
+
backend,
|
|
296
|
+
bin,
|
|
297
|
+
version: result.version
|
|
298
|
+
};
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (resolved) {
|
|
303
|
+
this.setResolved(resolved.backend, resolved.bin);
|
|
304
|
+
installed = true;
|
|
305
|
+
version = resolved.version;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
150
308
|
this.detection = {
|
|
151
309
|
checked: true,
|
|
152
|
-
|
|
310
|
+
installed,
|
|
311
|
+
version,
|
|
312
|
+
bin: this.bin,
|
|
313
|
+
backend: this.backend,
|
|
314
|
+
mode: this.mode
|
|
153
315
|
};
|
|
154
316
|
this.publish();
|
|
155
317
|
return this.detection;
|
|
156
318
|
}
|
|
157
|
-
/** Current status (+
|
|
319
|
+
/** Current status (+ connect info when running) for the launcher UI. */
|
|
158
320
|
status() {
|
|
159
321
|
return {
|
|
160
322
|
detection: { ...this.detection },
|
|
161
323
|
server: { ...this.server },
|
|
162
|
-
|
|
324
|
+
connect: this.connectInfo()
|
|
163
325
|
};
|
|
164
326
|
}
|
|
165
327
|
/**
|
|
166
|
-
* Launch
|
|
167
|
-
*
|
|
168
|
-
*
|
|
328
|
+
* Launch the editor (if not already up) and resolve once it is reachable.
|
|
329
|
+
* Idempotent while starting/running — returns the live status instead of
|
|
330
|
+
* spawning a second process. In tunnel mode it resolves as soon as either
|
|
331
|
+
* the `vscode.dev` URL or a device-login prompt is seen, so the action never
|
|
332
|
+
* blocks on interactive authentication.
|
|
169
333
|
*/
|
|
170
334
|
async start(req = {}) {
|
|
171
335
|
if (this.server.status === "running" || this.server.status === "starting") return this.status();
|
|
172
336
|
if (!this.detection.checked) await this.detect();
|
|
173
337
|
if (!this.detection.installed) throw diagnostics.DP_CODE_SERVER_0001({ bin: this.bin });
|
|
174
|
-
const
|
|
338
|
+
const folder = req.folder ?? this.workspace;
|
|
339
|
+
const isLocal = this.profile.kind !== "tunnel";
|
|
340
|
+
if (isLocal && this.reuseExistingServer) {
|
|
341
|
+
const target = this.forcedPort && this.forcedPort !== 0 ? this.forcedPort : DEFAULT_CODE_SERVER_PORT;
|
|
342
|
+
if (this.profile.healthPath && await probeHealth(target, this.profile.healthPath)) {
|
|
343
|
+
this.adopted = true;
|
|
344
|
+
this.launchCtx = this.baseCtx(folder, target, "");
|
|
345
|
+
this.server = {
|
|
346
|
+
status: "running",
|
|
347
|
+
port: target,
|
|
348
|
+
startedAt: Date.now()
|
|
349
|
+
};
|
|
350
|
+
this.publish();
|
|
351
|
+
return this.status();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const secret = randomBytes(32).toString("hex");
|
|
355
|
+
const initialPort = !isLocal ? 0 : this.forcedPort !== void 0 && this.forcedPort !== 0 ? this.forcedPort : this.forcedPort === 0 ? 0 : await getPort({
|
|
175
356
|
host: "127.0.0.1",
|
|
176
357
|
port: DEFAULT_CODE_SERVER_PORT
|
|
177
358
|
});
|
|
178
|
-
const
|
|
179
|
-
this.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"--disable-update-check",
|
|
188
|
-
...this.cookieSuffix ? ["--cookie-suffix", this.cookieSuffix] : [],
|
|
189
|
-
...this.extraArgs,
|
|
190
|
-
folder
|
|
191
|
-
];
|
|
192
|
-
const env = {};
|
|
193
|
-
for (const [k, v] of Object.entries(process.env)) if (v !== void 0) env[k] = v;
|
|
194
|
-
delete env.PASSWORD;
|
|
195
|
-
env.HASHED_PASSWORD = this.cookieValue;
|
|
196
|
-
Object.assign(env, this.extraEnv);
|
|
359
|
+
const ctx = this.baseCtx(folder, initialPort, secret);
|
|
360
|
+
this.launchCtx = ctx;
|
|
361
|
+
this.adopted = false;
|
|
362
|
+
this.readyUrl = void 0;
|
|
363
|
+
const args = this.profile.buildArgs(ctx);
|
|
364
|
+
const baseEnv = {};
|
|
365
|
+
for (const [k, v] of Object.entries(process.env)) if (v !== void 0) baseEnv[k] = v;
|
|
366
|
+
Object.assign(baseEnv, this.extraEnv);
|
|
367
|
+
const env = this.profile.buildEnv(ctx, baseEnv);
|
|
197
368
|
this.logBuffer = [];
|
|
198
369
|
let actualPort = initialPort;
|
|
199
370
|
let portResolver;
|
|
200
371
|
let portRejecter;
|
|
201
|
-
const portPromise = initialPort === 0 ? new Promise((resolve, reject) => {
|
|
372
|
+
const portPromise = isLocal && initialPort === 0 ? new Promise((resolve, reject) => {
|
|
202
373
|
portResolver = resolve;
|
|
203
374
|
portRejecter = reject;
|
|
204
375
|
}) : Promise.resolve(initialPort);
|
|
376
|
+
let readyUrlResolver;
|
|
377
|
+
let loginResolver;
|
|
378
|
+
const readyUrlPromise = new Promise((resolve) => {
|
|
379
|
+
readyUrlResolver = resolve;
|
|
380
|
+
});
|
|
381
|
+
const loginPromise = new Promise((resolve) => {
|
|
382
|
+
loginResolver = resolve;
|
|
383
|
+
});
|
|
205
384
|
const child = await this.launchProcess(args, env, folder);
|
|
385
|
+
this.registerCleanup();
|
|
206
386
|
this.proc = child;
|
|
207
387
|
this.server = {
|
|
208
388
|
status: "starting",
|
|
209
|
-
port: actualPort || void 0,
|
|
389
|
+
port: isLocal ? actualPort || void 0 : void 0,
|
|
210
390
|
pid: child.pid,
|
|
211
391
|
startedAt: Date.now()
|
|
212
392
|
};
|
|
@@ -214,14 +394,34 @@ var CodeServerSupervisor = class {
|
|
|
214
394
|
const capture = (chunk) => {
|
|
215
395
|
const text = chunk.toString("utf8");
|
|
216
396
|
this.appendLog(text);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
397
|
+
for (const line of text.split("\n")) {
|
|
398
|
+
if (isLocal && actualPort === 0 && this.profile.matchPort) {
|
|
399
|
+
const port = this.profile.matchPort(line);
|
|
400
|
+
if (port) {
|
|
401
|
+
actualPort = port;
|
|
402
|
+
this.server.port = port;
|
|
403
|
+
this.publish();
|
|
404
|
+
portResolver?.(port);
|
|
405
|
+
portResolver = void 0;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (this.profile.matchLogin) {
|
|
409
|
+
const login = this.profile.matchLogin(line);
|
|
410
|
+
if (login && this.proc === child) {
|
|
411
|
+
this.server = {
|
|
412
|
+
...this.server,
|
|
413
|
+
login
|
|
414
|
+
};
|
|
415
|
+
this.publish();
|
|
416
|
+
loginResolver?.(login);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (this.profile.matchReadyUrl) {
|
|
420
|
+
const url = this.profile.matchReadyUrl(line);
|
|
421
|
+
if (url && this.proc === child) {
|
|
422
|
+
this.readyUrl = url;
|
|
423
|
+
readyUrlResolver?.(url);
|
|
424
|
+
}
|
|
225
425
|
}
|
|
226
426
|
}
|
|
227
427
|
};
|
|
@@ -233,30 +433,29 @@ var CodeServerSupervisor = class {
|
|
|
233
433
|
status: "error",
|
|
234
434
|
error: error.message
|
|
235
435
|
};
|
|
236
|
-
this.
|
|
237
|
-
|
|
238
|
-
if (portRejecter) portRejecter?.(error);
|
|
436
|
+
this.reset();
|
|
437
|
+
portRejecter?.(error);
|
|
239
438
|
this.publish();
|
|
240
439
|
});
|
|
241
440
|
child.on("exit", (code) => {
|
|
242
441
|
if (this.proc !== child) return;
|
|
243
442
|
const exitCode = code ?? 0;
|
|
244
443
|
const crashed = this.server.status !== "stopped" && exitCode !== 0;
|
|
245
|
-
this.
|
|
246
|
-
this.cookieValue = void 0;
|
|
444
|
+
this.reset();
|
|
247
445
|
this.server = crashed ? {
|
|
248
446
|
status: "error",
|
|
249
|
-
error: this.lastLog() ||
|
|
447
|
+
error: this.lastLog() || `${this.bin} exited with code ${exitCode}`
|
|
250
448
|
} : { status: "stopped" };
|
|
251
449
|
if (crashed) diagnostics.DP_CODE_SERVER_0005({ code: exitCode }, { method: "warn" });
|
|
252
450
|
this.reflectHub(crashed ? "error" : "stopped");
|
|
253
451
|
this.session = void 0;
|
|
254
|
-
|
|
452
|
+
portRejecter?.(/* @__PURE__ */ new Error(`${this.bin} exited before binding (code ${exitCode})`));
|
|
255
453
|
this.publish();
|
|
256
454
|
});
|
|
257
455
|
try {
|
|
456
|
+
if (!isLocal) return await this.awaitTunnel(child, readyUrlPromise, loginPromise);
|
|
258
457
|
const port = await Promise.race([portPromise, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("timeout waiting for dynamic port allocation")), this.startTimeout))]);
|
|
259
|
-
if (!await this.waitForReady(port)) throw new Error(this.lastLog() || "startup timed out");
|
|
458
|
+
if (!await this.waitForReady(port, this.profile.healthPath)) throw new Error(this.lastLog() || "startup timed out");
|
|
260
459
|
if (this.proc !== child) return this.status();
|
|
261
460
|
this.server = {
|
|
262
461
|
status: "running",
|
|
@@ -275,23 +474,23 @@ var CodeServerSupervisor = class {
|
|
|
275
474
|
status: "error",
|
|
276
475
|
error: error instanceof Error ? error.message : String(error)
|
|
277
476
|
};
|
|
278
|
-
this.
|
|
279
|
-
this.cookieValue = void 0;
|
|
477
|
+
this.reset();
|
|
280
478
|
this.publish();
|
|
281
479
|
}
|
|
480
|
+
if (!isLocal) throw diagnostics.DP_CODE_SERVER_0006({ timeout: this.startTimeout });
|
|
282
481
|
throw diagnostics.DP_CODE_SERVER_0002({
|
|
283
482
|
port: actualPort,
|
|
284
483
|
timeout: this.startTimeout
|
|
285
484
|
});
|
|
286
485
|
}
|
|
287
486
|
}
|
|
288
|
-
/** Stop the
|
|
487
|
+
/** Stop the editor process and reset to `stopped`. */
|
|
289
488
|
stop() {
|
|
290
489
|
const child = this.proc;
|
|
291
|
-
|
|
292
|
-
this.
|
|
490
|
+
const wasAdopted = this.adopted;
|
|
491
|
+
this.reset();
|
|
293
492
|
this.server = { status: "stopped" };
|
|
294
|
-
if (child) this.terminate(child);
|
|
493
|
+
if (child && !wasAdopted) this.terminate(child);
|
|
295
494
|
this.reflectHub("stopped");
|
|
296
495
|
this.session = void 0;
|
|
297
496
|
this.publish();
|
|
@@ -299,18 +498,85 @@ var CodeServerSupervisor = class {
|
|
|
299
498
|
}
|
|
300
499
|
/** Kill the process on host shutdown / test teardown. */
|
|
301
500
|
dispose() {
|
|
302
|
-
if (this.proc) this.terminate(this.proc);
|
|
303
|
-
this.
|
|
304
|
-
this.cookieValue = void 0;
|
|
501
|
+
if (this.proc && !this.adopted) this.terminate(this.proc);
|
|
502
|
+
this.reset();
|
|
305
503
|
this.session = void 0;
|
|
504
|
+
if (this.exitHandler) {
|
|
505
|
+
process.off("exit", this.exitHandler);
|
|
506
|
+
this.exitHandler = void 0;
|
|
507
|
+
}
|
|
306
508
|
}
|
|
307
|
-
|
|
308
|
-
|
|
509
|
+
/** Resolved backend for tests / callers. */
|
|
510
|
+
get resolvedBackend() {
|
|
511
|
+
return this.backend;
|
|
512
|
+
}
|
|
513
|
+
setResolved(backend, bin) {
|
|
514
|
+
this.backend = backend;
|
|
515
|
+
this.bin = bin;
|
|
516
|
+
this.profile = resolveProfile(this.mode, backend);
|
|
517
|
+
}
|
|
518
|
+
baseCtx(folder, port, secret) {
|
|
309
519
|
return {
|
|
520
|
+
host: this.host,
|
|
521
|
+
port,
|
|
522
|
+
folder,
|
|
523
|
+
secret,
|
|
310
524
|
cookieName: this.cookieName,
|
|
311
|
-
|
|
525
|
+
extraArgs: this.extraArgs,
|
|
526
|
+
tunnelName: this.tunnelName
|
|
312
527
|
};
|
|
313
528
|
}
|
|
529
|
+
/** Clear per-launch process state (keeps `server`/`detection`). */
|
|
530
|
+
reset() {
|
|
531
|
+
this.proc = void 0;
|
|
532
|
+
this.launchCtx = void 0;
|
|
533
|
+
this.adopted = false;
|
|
534
|
+
this.readyUrl = void 0;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Resolve start() for a tunnel: succeed as soon as the `vscode.dev` URL
|
|
538
|
+
* appears (→ running) or a device-login prompt is seen (→ starting, so the
|
|
539
|
+
* user can authenticate while the log stream continues to a running URL).
|
|
540
|
+
*/
|
|
541
|
+
async awaitTunnel(child, readyUrlPromise, loginPromise) {
|
|
542
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("timed out waiting for the tunnel")), this.startTimeout));
|
|
543
|
+
const outcome = await Promise.race([
|
|
544
|
+
readyUrlPromise.then(() => "ready"),
|
|
545
|
+
loginPromise.then(() => "login"),
|
|
546
|
+
timeout
|
|
547
|
+
]);
|
|
548
|
+
if (this.proc !== child) return this.status();
|
|
549
|
+
if (outcome === "ready") {
|
|
550
|
+
this.server = {
|
|
551
|
+
status: "running",
|
|
552
|
+
pid: child.pid,
|
|
553
|
+
startedAt: this.server.startedAt
|
|
554
|
+
};
|
|
555
|
+
this.publish();
|
|
556
|
+
return this.status();
|
|
557
|
+
}
|
|
558
|
+
readyUrlPromise.then(() => {
|
|
559
|
+
if (this.proc === child && this.server.status !== "running") {
|
|
560
|
+
this.server = {
|
|
561
|
+
status: "running",
|
|
562
|
+
pid: child.pid,
|
|
563
|
+
startedAt: this.server.startedAt,
|
|
564
|
+
login: void 0
|
|
565
|
+
};
|
|
566
|
+
this.publish();
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
return this.status();
|
|
570
|
+
}
|
|
571
|
+
connectInfo() {
|
|
572
|
+
if (this.server.status !== "running" || !this.launchCtx) return void 0;
|
|
573
|
+
if (this.adopted) return this.profile.connectReused({ port: this.launchCtx.port });
|
|
574
|
+
return this.profile.connect({
|
|
575
|
+
...this.launchCtx,
|
|
576
|
+
port: this.server.port ?? this.launchCtx.port,
|
|
577
|
+
readyUrl: this.readyUrl
|
|
578
|
+
});
|
|
579
|
+
}
|
|
314
580
|
terminate(child) {
|
|
315
581
|
if (this.session) {
|
|
316
582
|
this.session.terminate().catch(() => {});
|
|
@@ -340,11 +606,11 @@ var CodeServerSupervisor = class {
|
|
|
340
606
|
});
|
|
341
607
|
}
|
|
342
608
|
/**
|
|
343
|
-
* Launch
|
|
344
|
-
* up as a read-only terminal session (proper icon + name) whose output
|
|
345
|
-
* hub streams to its terminals panel; standalone, spawn it directly.
|
|
346
|
-
* way, return the underlying {@link ChildProcess} so the shared
|
|
347
|
-
* port / log wiring in `start()` is identical.
|
|
609
|
+
* Launch the editor binary. In a hub, spawn it through `ctx.terminals` so it
|
|
610
|
+
* shows up as a read-only terminal session (proper icon + name) whose output
|
|
611
|
+
* the hub streams to its terminals panel; standalone, spawn it directly.
|
|
612
|
+
* Either way, return the underlying {@link ChildProcess} so the shared
|
|
613
|
+
* readiness / port / log wiring in `start()` is identical.
|
|
348
614
|
*/
|
|
349
615
|
async launchProcess(args, env, folder) {
|
|
350
616
|
const hub = this.resolveHubTerminals();
|
|
@@ -364,10 +630,11 @@ var CodeServerSupervisor = class {
|
|
|
364
630
|
id: this.sessionId,
|
|
365
631
|
title: TERMINAL_SESSION_TITLE,
|
|
366
632
|
description: folder,
|
|
367
|
-
icon: TERMINAL_SESSION_ICON
|
|
633
|
+
icon: TERMINAL_SESSION_ICON,
|
|
634
|
+
restartable: false
|
|
368
635
|
});
|
|
369
636
|
const child = session.getChildProcess();
|
|
370
|
-
if (!child) throw new Error("
|
|
637
|
+
if (!child) throw new Error("editor process handle was unavailable");
|
|
371
638
|
this.session = session;
|
|
372
639
|
return child;
|
|
373
640
|
} catch (error) {
|
|
@@ -428,22 +695,22 @@ var CodeServerSupervisor = class {
|
|
|
428
695
|
});
|
|
429
696
|
}
|
|
430
697
|
/**
|
|
431
|
-
* Poll
|
|
432
|
-
*
|
|
698
|
+
* Poll the server's readiness path until it responds or the timeout elapses.
|
|
699
|
+
* Returns false if the process exits first.
|
|
433
700
|
*/
|
|
434
|
-
async waitForReady(port) {
|
|
701
|
+
async waitForReady(port, path) {
|
|
435
702
|
const deadline = Date.now() + this.startTimeout;
|
|
436
703
|
while (Date.now() < deadline) {
|
|
437
704
|
if (!this.proc) return false;
|
|
438
|
-
if (await
|
|
705
|
+
if (await probeHealth(port, path)) return true;
|
|
439
706
|
await delay(PROBE_INTERVAL);
|
|
440
707
|
}
|
|
441
708
|
return false;
|
|
442
709
|
}
|
|
443
710
|
registerCleanup() {
|
|
444
|
-
if (this.
|
|
445
|
-
this.
|
|
446
|
-
process.once("exit",
|
|
711
|
+
if (this.exitHandler) return;
|
|
712
|
+
this.exitHandler = () => this.dispose();
|
|
713
|
+
process.once("exit", this.exitHandler);
|
|
447
714
|
}
|
|
448
715
|
};
|
|
449
716
|
function delay(ms) {
|
|
@@ -451,12 +718,12 @@ function delay(ms) {
|
|
|
451
718
|
setTimeout(resolve, ms).unref?.();
|
|
452
719
|
});
|
|
453
720
|
}
|
|
454
|
-
function
|
|
721
|
+
function probeHealth(port, path) {
|
|
455
722
|
return new Promise((resolve) => {
|
|
456
723
|
const req = request({
|
|
457
724
|
host: "127.0.0.1",
|
|
458
725
|
port,
|
|
459
|
-
path
|
|
726
|
+
path,
|
|
460
727
|
method: "GET",
|
|
461
728
|
timeout: 1500
|
|
462
729
|
}, (res) => {
|
|
@@ -487,7 +754,8 @@ async function setupCodeServer(ctx, options = {}) {
|
|
|
487
754
|
setCodeServerSupervisor(ctx, supervisor);
|
|
488
755
|
await supervisor.init();
|
|
489
756
|
for (const fn of serverFunctions) ctx.rpc.register(fn);
|
|
757
|
+
if (options.startOnBoot) supervisor.start().catch(() => {});
|
|
490
758
|
return supervisor;
|
|
491
759
|
}
|
|
492
760
|
//#endregion
|
|
493
|
-
export { CodeServerSupervisor, detectCodeServer, diagnostics, getCodeServerSupervisor, setCodeServerSupervisor, setupCodeServer };
|
|
761
|
+
export { CodeServerSupervisor, detectCodeServer, diagnostics, getCodeServerSupervisor, resolveProfile, setCodeServerSupervisor, setupCodeServer };
|
package/dist/rpc/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as serverFunctions } from "../rpc-
|
|
1
|
+
import { t as serverFunctions } from "../rpc-BdNzxqDi.mjs";
|
|
2
2
|
export { serverFunctions };
|
|
@@ -17,21 +17,25 @@ const diagnostics = defineDiagnostics({
|
|
|
17
17
|
reporters: [reporter],
|
|
18
18
|
codes: {
|
|
19
19
|
DP_CODE_SERVER_0001: {
|
|
20
|
-
why: (p) => `
|
|
21
|
-
fix: "Install code-server
|
|
20
|
+
why: (p) => `No editor binary found (could not run "${p.bin} --version")`,
|
|
21
|
+
fix: "Install Coder code-server (`curl -fsSL https://code-server.dev/install.sh | sh`) or the Microsoft `code` CLI, or set the `bin` option to its path. See https://coder.com/docs/code-server/latest/install"
|
|
22
22
|
},
|
|
23
23
|
DP_CODE_SERVER_0002: {
|
|
24
|
-
why: (p) => `
|
|
25
|
-
fix: "Check the
|
|
24
|
+
why: (p) => `the editor did not become ready on port ${p.port} within ${p.timeout}ms`,
|
|
25
|
+
fix: "Check the editor logs for startup errors, raise `startTimeout`, or free the port."
|
|
26
26
|
},
|
|
27
|
-
DP_CODE_SERVER_0003: { why: (p) => `Failed to spawn
|
|
27
|
+
DP_CODE_SERVER_0003: { why: (p) => `Failed to spawn the editor ("${p.bin}"): ${p.reason}` },
|
|
28
28
|
DP_CODE_SERVER_0004: {
|
|
29
29
|
why: "code-server supervisor is not initialised on this context",
|
|
30
30
|
fix: "Call setupCodeServer(ctx) (or use createCodeServerDevframe) before invoking the code-server RPCs."
|
|
31
31
|
},
|
|
32
32
|
DP_CODE_SERVER_0005: {
|
|
33
|
-
why: (p) => `
|
|
33
|
+
why: (p) => `the editor process exited unexpectedly with code ${p.code}`,
|
|
34
34
|
fix: "Inspect the captured output in the launcher and re-launch."
|
|
35
|
+
},
|
|
36
|
+
DP_CODE_SERVER_0006: {
|
|
37
|
+
why: (p) => `the tunnel did not open within ${p.timeout}ms`,
|
|
38
|
+
fix: "Check the tunnel logs, ensure the `code` CLI is signed in, or raise `startTimeout`."
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
});
|