@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.
@@ -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-DjerPudu.mjs";
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 code-server child process: detects the
90
- * binary, launches it with a freshly generated password-auth token, probes
91
- * `/healthz` for readiness, and mirrors a secret-free status into shared
92
- * state. The auth cookie is handed back only through `start()` / `status()`
93
- * so the already-authorized client can sign the iframe in automatically.
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
- bin;
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
- cookieValue;
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
- cleanupRegistered = false;
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.bin = options.bin ?? "code-server";
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 ?? "0.0.0.0";
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
- /** Re-probe for the code-server binary and publish the result. */
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
- const result = await detectCodeServer(this.bin);
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
- ...result
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 (+ auth when running) for the launcher UI. */
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
- auth: this.authInfo()
324
+ connect: this.connectInfo()
163
325
  };
164
326
  }
165
327
  /**
166
- * Launch code-server (if not already up) and resolve once it answers its
167
- * readiness probe. Idempotent while starting/running — returns the live
168
- * status instead of spawning a second process.
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 initialPort = this.forcedPort !== void 0 && this.forcedPort !== 0 ? this.forcedPort : this.forcedPort === 0 ? 0 : await getPort({
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 token = randomBytes(32).toString("hex");
179
- this.cookieValue = createHash("sha256").update(token).digest("hex");
180
- const folder = req.folder ?? this.workspace;
181
- const args = [
182
- "--auth",
183
- "password",
184
- "--bind-addr",
185
- `${this.host}:${initialPort}`,
186
- "--disable-telemetry",
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
- if (actualPort === 0 && portResolver) for (const line of text.split("\n")) {
218
- const match = line.match(/HTTP server listening on https?:\/\/(?:[^:]+|\[[^\]]+\]):(\d+)/);
219
- if (match) {
220
- actualPort = Number.parseInt(match[1], 10);
221
- this.server.port = actualPort;
222
- this.publish();
223
- portResolver?.(actualPort);
224
- portResolver = void 0;
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.proc = void 0;
237
- this.cookieValue = void 0;
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.proc = void 0;
246
- this.cookieValue = void 0;
444
+ this.reset();
247
445
  this.server = crashed ? {
248
446
  status: "error",
249
- error: this.lastLog() || `code-server exited with code ${exitCode}`
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
- if (portRejecter) portRejecter?.(/* @__PURE__ */ new Error(`code-server exited before binding (code ${exitCode})`));
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.proc = void 0;
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 code-server process and reset to `stopped`. */
487
+ /** Stop the editor process and reset to `stopped`. */
289
488
  stop() {
290
489
  const child = this.proc;
291
- this.proc = void 0;
292
- this.cookieValue = void 0;
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.proc = void 0;
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
- authInfo() {
308
- if (this.server.status !== "running" || !this.cookieValue) return void 0;
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
- cookieValue: this.cookieValue
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 code-server. In a hub, spawn it through `ctx.terminals` so it shows
344
- * up as a read-only terminal session (proper icon + name) whose output the
345
- * hub streams to its terminals panel; standalone, spawn it directly. Either
346
- * way, return the underlying {@link ChildProcess} so the shared readiness /
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("code-server process handle was unavailable");
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 code-server's unauthenticated `/healthz` endpoint until it responds
432
- * or the timeout elapses. Returns false if the process exits first.
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 probeHealthz(port)) return true;
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.cleanupRegistered) return;
445
- this.cleanupRegistered = true;
446
- process.once("exit", () => this.dispose());
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 probeHealthz(port) {
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: "/healthz",
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 };
@@ -1,2 +1,2 @@
1
- import { t as serverFunctions } from "../rpc-DjerPudu.mjs";
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) => `code-server is not installed (could not run "${p.bin} --version")`,
21
- fix: "Install code-server — e.g. `curl -fsSL https://code-server.dev/install.sh | sh` — or set the `bin` option to its path. See https://coder.com/docs/code-server/latest/install"
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) => `code-server did not become ready on port ${p.port} within ${p.timeout}ms`,
25
- fix: "Check the code-server logs for startup errors, raise `startTimeout`, or free the port."
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 code-server ("${p.bin}"): ${p.reason}` },
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) => `code-server exited unexpectedly with code ${p.code}`,
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
  });