@devframes/plugin-code-server 0.9.8 → 0.9.9

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 CHANGED
@@ -11,13 +11,13 @@ auto-authenticated `<iframe>`. The launcher is a **Vue** SPA built on the shared
11
11
 
12
12
  ## How it works
13
13
 
14
- - **Detection** — on startup it probes the resolved binary with `--version`.
14
+ - **Detection**: on startup it probes the resolved binary with `--version`.
15
15
  When none is found, the launcher renders install instructions instead of a
16
16
  launch button.
17
- - **Launch** — the launcher's button starts the editor as a managed child
17
+ - **Launch**: the launcher's button starts the editor as a managed child
18
18
  process bound to a free port, scoped to the workspace. Readiness is probed
19
19
  before the iframe loads.
20
- - **Auto-auth** — the devframe generates fresh auth material per launch and hands
20
+ - **Auto-auth**: the devframe generates fresh auth material per launch and hands
21
21
  it to the already-authorized devframe client, so the editor opens already
22
22
  signed in. `code-server` uses a session cookie (`HASHED_PASSWORD`);
23
23
  `code serve-web` uses a connection token on the URL (`?tkn=`).
@@ -50,12 +50,12 @@ pnpx @devframes/plugin-code-server # dev server + launcher
50
50
  import { createCodeServerDevframe } from '@devframes/plugin-code-server'
51
51
 
52
52
  export default createCodeServerDevframe({
53
- // backend: 'code-server', // 'code-server' | 'ms-code-serve-web' (default: auto)
54
- // mode: 'local', // 'local' | 'tunnel'
55
- // serverPort: 8080, // force a port (default: free port near 8080)
56
- // startOnBoot: true, // launch during setup instead of on demand
57
- // reuseExistingServer: true, // adopt a server already answering on the port
58
- // tunnel: { name: 'my-box' },
53
+ // backend: 'code-server' | 'ms-code-serve-web' (default: auto)
54
+ // mode: 'local' | 'tunnel'
55
+ // serverPort: 8080 (default: free port near 8080)
56
+ // startOnBoot: true (default: on demand)
57
+ // reuseExistingServer: true (adopt a server on the port)
58
+ // tunnel: { name: 'my-box' }
59
59
  })
60
60
  ```
61
61
 
@@ -84,7 +84,7 @@ Status (minus the connect descriptor) is mirrored into the
84
84
  ## UI
85
85
 
86
86
  The launcher is a Vue SPA (`src/spa`). `LauncherView.vue` is a pure,
87
- state-driven view decoupled from RPC — every phase renders in isolation and has
87
+ state-driven view decoupled from RPC, so every phase renders in isolation and has
88
88
  a Storybook story; `App.vue` wires the live connection to it and mounts the
89
89
  editor in a full-bleed, auto-authenticated iframe (`EditorFrame.vue`).
90
90
 
package/dist/cli.d.mts CHANGED
@@ -2,8 +2,8 @@ import { CodeServerOptions } from "./types.mjs";
2
2
  import { CacHandle, CreateCacOptions } from "devframe/adapters/cac";
3
3
  //#region src/cli.d.ts
4
4
  /**
5
- * Build a standalone CLI for the code-server panel — `dev` / `build` / `mcp`
6
- * subcommands, backed by {@link createCodeServerDevframe}. Used by the package
5
+ * Build a standalone CLI for the code-server panel, exposing `dev` / `build` /
6
+ * `mcp` subcommands, backed by {@link createCodeServerDevframe}. Used by the package
7
7
  * `bin` (`devframe-code-server`).
8
8
  */
9
9
  declare function createCodeServerCli(options?: CodeServerOptions, cliOptions?: CreateCacOptions): CacHandle;
package/dist/cli.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { t as createCodeServerDevframe } from "./src-CNJYQ3UQ.mjs";
1
+ import { t as createCodeServerDevframe } from "./src-5dhTkOqj.mjs";
2
2
  import { createCac } from "devframe/adapters/cac";
3
3
  //#region src/cli.ts
4
4
  /**
5
- * Build a standalone CLI for the code-server panel — `dev` / `build` / `mcp`
6
- * subcommands, backed by {@link createCodeServerDevframe}. Used by the package
5
+ * Build a standalone CLI for the code-server panel, exposing `dev` / `build` /
6
+ * `mcp` subcommands, backed by {@link createCodeServerDevframe}. Used by the package
7
7
  * `bin` (`devframe-code-server`).
8
8
  */
9
9
  function createCodeServerCli(options = {}, cliOptions = {}) {
@@ -1,5 +1,5 @@
1
1
  import { CodeServerBackend, CodeServerConnect, CodeServerDetection, CodeServerLogin, CodeServerMode, CodeServerServerInfo, CodeServerSharedState, CodeServerStartResult, CodeServerStatus, CodeServerStatusResult } from "../types.mjs";
2
- import { o as STATE_KEY } from "../constants-DtQz38ex.mjs";
2
+ import { o as STATE_KEY } from "../constants-BbInB7V-.mjs";
3
3
  import { DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientOptions } from "devframe/client";
4
4
  import { ConnectionMeta } from "devframe/types";
5
5
  //#region src/client/index.d.ts
@@ -2,7 +2,7 @@ import { connectDevframe } from "devframe/client";
2
2
  //#region src/constants.ts
3
3
  /**
4
4
  * Shared-state key holding the serializable, secret-free server status and
5
- * detection result. Authentication material is never published here — it is
5
+ * detection result. Authentication material is never published here; it is
6
6
  * returned only from the `start` / `status` RPCs to the already-authorized
7
7
  * client (see {@link import('./types').CodeServerConnect}).
8
8
  */
@@ -3,7 +3,7 @@
3
3
  declare const PLUGIN_ID = "devframes_plugin_code-server";
4
4
  /**
5
5
  * Shared-state key holding the serializable, secret-free server status and
6
- * detection result. Authentication material is never published here — it is
6
+ * detection result. Authentication material is never published here; it is
7
7
  * returned only from the `start` / `status` RPCs to the already-authorized
8
8
  * client (see {@link import('./types').CodeServerConnect}).
9
9
  */
@@ -1,2 +1,2 @@
1
- import { a as SESSION_COOKIE_BASE, c as TERMINAL_SESSION_TITLE, i as PLUGIN_ID, l as getCookieSessionName, n as DEFAULT_PORT, o as STATE_KEY, r as DEFAULT_START_TIMEOUT, s as TERMINAL_SESSION_ICON, t as DEFAULT_CODE_SERVER_PORT } from "./constants-DtQz38ex.mjs";
1
+ import { a as SESSION_COOKIE_BASE, c as TERMINAL_SESSION_TITLE, i as PLUGIN_ID, l as getCookieSessionName, n as DEFAULT_PORT, o as STATE_KEY, r as DEFAULT_START_TIMEOUT, s as TERMINAL_SESSION_ICON, t as DEFAULT_CODE_SERVER_PORT } from "./constants-BbInB7V-.mjs";
2
2
  export { DEFAULT_CODE_SERVER_PORT, DEFAULT_PORT, DEFAULT_START_TIMEOUT, PLUGIN_ID, SESSION_COOKIE_BASE, STATE_KEY, TERMINAL_SESSION_ICON, TERMINAL_SESSION_TITLE, getCookieSessionName };
@@ -3,7 +3,7 @@
3
3
  const PLUGIN_ID = "devframes_plugin_code-server";
4
4
  /**
5
5
  * Shared-state key holding the serializable, secret-free server status and
6
- * detection result. Authentication material is never published here — it is
6
+ * detection result. Authentication material is never published here; it is
7
7
  * returned only from the `start` / `status` RPCs to the already-authorized
8
8
  * client (see {@link import('./types').CodeServerConnect}).
9
9
  */
package/dist/index.d.mts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { CodeServerBackend, CodeServerConnect, CodeServerDetection, CodeServerLogin, CodeServerMode, CodeServerOptions, CodeServerServerInfo, CodeServerSharedState, CodeServerStartRequest, CodeServerStartResult, CodeServerStatus, CodeServerStatusResult, CodeServerTunnelOptions } from "./types.mjs";
2
- import { i as PLUGIN_ID, l as getCookieSessionName, n as DEFAULT_PORT, o as STATE_KEY, t as DEFAULT_CODE_SERVER_PORT } from "./constants-DtQz38ex.mjs";
2
+ import { i as PLUGIN_ID, l as getCookieSessionName, n as DEFAULT_PORT, o as STATE_KEY, t as DEFAULT_CODE_SERVER_PORT } from "./constants-BbInB7V-.mjs";
3
3
  import { DevframeDefinition } from "devframe";
4
4
  //#region src/index.d.ts
5
5
  /**
6
6
  * Build a {@link DevframeDefinition} for the code-server panel. The same
7
7
  * definition runs standalone (`createCac`), mounts into a Vite host
8
- * (`/vite`), or docks inside a hub — its `setup` only relies on the core
8
+ * (`/vite`), or docks inside a hub, since its `setup` only relies on the core
9
9
  * devframe RPC + shared-state surface.
10
10
  *
11
11
  * @experimental This plugin is experimental and may change without a major
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as createCodeServerDevframe } from "./src-CNJYQ3UQ.mjs";
1
+ import { t as createCodeServerDevframe } from "./src-5dhTkOqj.mjs";
2
2
  import { DEFAULT_CODE_SERVER_PORT, DEFAULT_PORT, PLUGIN_ID, STATE_KEY, getCookieSessionName } from "./constants.mjs";
3
3
  export { DEFAULT_CODE_SERVER_PORT, DEFAULT_PORT, PLUGIN_ID, STATE_KEY, createCodeServerDevframe, createCodeServerDevframe as default, getCookieSessionName };
@@ -62,12 +62,28 @@ declare class CodeServerSupervisor {
62
62
  status(): CodeServerStatusResult;
63
63
  /**
64
64
  * Launch the editor (if not already up) and resolve once it is reachable.
65
- * Idempotent while starting/running — returns the live status instead of
65
+ * Idempotent while starting/running; returns the live status instead of
66
66
  * spawning a second process. In tunnel mode it resolves as soon as either
67
67
  * the `vscode.dev` URL or a device-login prompt is seen, so the action never
68
68
  * blocks on interactive authentication.
69
69
  */
70
70
  start(req?: CodeServerStartRequest): Promise<CodeServerStartResult>;
71
+ /** Adopt an already-running local server on the target port, if one answers. */
72
+ private tryAdopt;
73
+ /** Resolve the port to launch on: 0 for tunnels/dynamic, a forced port, or a free one. */
74
+ private resolveInitialPort;
75
+ /** The child's environment: inherited env, caller overrides, then profile shaping. */
76
+ private buildLaunchEnv;
77
+ /** Buffer output and latch the port / login / ready-URL signals from each line. */
78
+ private consumeOutput;
79
+ private latchPort;
80
+ private latchLogin;
81
+ private latchReadyUrl;
82
+ private handleChildError;
83
+ private handleChildExit;
84
+ /** Wait for the dynamic port then readiness, and flip to `running`. */
85
+ private finalizeLocalStart;
86
+ private handleStartFailure;
71
87
  /** Stop the editor process and reset to `stopped`. */
72
88
  stop(): CodeServerStatusResult;
73
89
  /** Kill the process on host shutdown / test teardown. */
@@ -89,7 +105,7 @@ declare class CodeServerSupervisor {
89
105
  /**
90
106
  * Resolve the hub's terminals subsystem when this devframe is mounted in a
91
107
  * hub. `ctx.terminals` only exists on a `DevframeHubContext`, so it is
92
- * duck-typed — standalone runtimes (CLI / Vite / build) have no such property
108
+ * duck-typed: standalone runtimes (CLI / Vite / build) have no such property
93
109
  * and fall back to a direct child process.
94
110
  */
95
111
  private resolveHubTerminals;
@@ -103,6 +119,10 @@ declare class CodeServerSupervisor {
103
119
  * readiness / port / log wiring in `start()` is identical.
104
120
  */
105
121
  private launchProcess;
122
+ /** Spawn through the hub's terminals subsystem so it shows as a terminal session. */
123
+ private launchViaHub;
124
+ /** Spawn the editor binary directly (standalone runtimes with no hub). */
125
+ private launchDirect;
106
126
  private appendLog;
107
127
  private lastLog;
108
128
  private publish;
@@ -119,8 +139,8 @@ declare class CodeServerSupervisor {
119
139
  * The internal launch profile the supervisor drives. One of three "kinds":
120
140
  * the two local {@link CodeServerBackend}s plus the `code tunnel` profile
121
141
  * selected by `mode: 'tunnel'`. Each profile owns the backend-specific pieces
122
- * — binary, arguments, auth env, readiness detection, and how the client
123
- * ultimately reaches the editor — while the supervisor owns the shared
142
+ * (binary, arguments, auth env, readiness detection, and how the client
143
+ * ultimately reaches the editor), while the supervisor owns the shared
124
144
  * spawn / log / publish lifecycle.
125
145
  */
126
146
  type CodeServerProfileKind = 'code-server' | 'serve-web' | 'tunnel';
@@ -181,14 +201,14 @@ interface DetectCodeServerResult {
181
201
  /**
182
202
  * Probe the host for a usable code-server binary by running
183
203
  * `<bin> --version`. Resolves to `installed: false` when the binary is
184
- * missing (ENOENT), errors, or exits non-zero — never throws — so the
204
+ * missing (ENOENT), errors, or exits non-zero (never throws), so the
185
205
  * launcher can fall back to install instructions.
186
206
  *
187
207
  * `code-server --version` prints e.g. `4.96.4 abc123 with Code 1.96.4`; the
188
208
  * first semver-looking token is taken as the version. Matching a semver
189
209
  * pattern (rather than the leading whitespace token) keeps cold-start i18n
190
- * noise — e.g. an `i18next: …` initialization line printed before the version
191
- * — from leaking into the reported version.
210
+ * noise (e.g. an `i18next: …` initialization line printed before the version)
211
+ * from leaking into the reported version.
192
212
  */
193
213
  declare function detectCodeServer(bin?: string, timeoutMs?: number): Promise<DetectCodeServerResult>;
194
214
  //#endregion
@@ -473,7 +493,7 @@ declare const diagnostics: Diagnostics<{
473
493
  * status into shared state, and register the control RPC functions. Returns
474
494
  * the supervisor so callers can launch/stop or dispose it on shutdown.
475
495
  *
476
- * Works in any devframe runtime (CLI, Vite, embedded, build) — it only relies
496
+ * Works in any devframe runtime (CLI, Vite, embedded, build), since it only relies
477
497
  * on the core `ctx.rpc` shared-state surface, not on the hub.
478
498
  */
479
499
  declare function setupCodeServer(ctx: DevframeNodeContext, options?: CodeServerOptions): Promise<CodeServerSupervisor>;
@@ -60,6 +60,7 @@ const serveWebProfile = {
60
60
  ...c.extraArgs
61
61
  ],
62
62
  buildEnv: (_c, base) => ({ ...base }),
63
+ /** serve-web has no health endpoint; the root responds (200/redirect) once up. */
63
64
  healthPath: "/",
64
65
  matchPort: (line) => {
65
66
  const m = line.match(/available at https?:\/\/[^:]+:(\d+)/i);
@@ -113,14 +114,14 @@ function resolveProfile(mode, backend) {
113
114
  /**
114
115
  * Probe the host for a usable code-server binary by running
115
116
  * `<bin> --version`. Resolves to `installed: false` when the binary is
116
- * missing (ENOENT), errors, or exits non-zero — never throws — so the
117
+ * missing (ENOENT), errors, or exits non-zero (never throws), so the
117
118
  * launcher can fall back to install instructions.
118
119
  *
119
120
  * `code-server --version` prints e.g. `4.96.4 abc123 with Code 1.96.4`; the
120
121
  * first semver-looking token is taken as the version. Matching a semver
121
122
  * pattern (rather than the leading whitespace token) keeps cold-start i18n
122
- * noise — e.g. an `i18next: …` initialization line printed before the version
123
- * — from leaking into the reported version.
123
+ * noise (e.g. an `i18next: …` initialization line printed before the version)
124
+ * from leaking into the reported version.
124
125
  */
125
126
  function detectCodeServer(bin = "code-server", timeoutMs = 5e3) {
126
127
  return new Promise((resolve) => {
@@ -326,7 +327,7 @@ var CodeServerSupervisor = class {
326
327
  }
327
328
  /**
328
329
  * 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
+ * Idempotent while starting/running; returns the live status instead of
330
331
  * spawning a second process. In tunnel mode it resolves as soon as either
331
332
  * the `vscode.dev` URL or a device-login prompt is seen, so the action never
332
333
  * blocks on interactive authentication.
@@ -338,151 +339,177 @@ var CodeServerSupervisor = class {
338
339
  const folder = req.folder ?? this.workspace;
339
340
  const isLocal = this.profile.kind !== "tunnel";
340
341
  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
- }
342
+ const adopted = await this.tryAdopt(folder);
343
+ if (adopted) return adopted;
353
344
  }
354
345
  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({
356
- host: "127.0.0.1",
357
- port: DEFAULT_CODE_SERVER_PORT
358
- });
346
+ const initialPort = await this.resolveInitialPort(isLocal);
359
347
  const ctx = this.baseCtx(folder, initialPort, secret);
360
348
  this.launchCtx = ctx;
361
349
  this.adopted = false;
362
350
  this.readyUrl = void 0;
363
351
  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);
352
+ const env = this.buildLaunchEnv(ctx);
368
353
  this.logBuffer = [];
369
- let actualPort = initialPort;
370
- let portResolver;
371
- let portRejecter;
354
+ const signals = {
355
+ isLocal,
356
+ actualPort: initialPort
357
+ };
372
358
  const portPromise = isLocal && initialPort === 0 ? new Promise((resolve, reject) => {
373
- portResolver = resolve;
374
- portRejecter = reject;
359
+ signals.resolvePort = resolve;
360
+ signals.rejectPort = reject;
375
361
  }) : Promise.resolve(initialPort);
376
- let readyUrlResolver;
377
- let loginResolver;
378
362
  const readyUrlPromise = new Promise((resolve) => {
379
- readyUrlResolver = resolve;
363
+ signals.resolveReadyUrl = resolve;
380
364
  });
381
365
  const loginPromise = new Promise((resolve) => {
382
- loginResolver = resolve;
366
+ signals.resolveLogin = resolve;
383
367
  });
384
368
  const child = await this.launchProcess(args, env, folder);
385
369
  this.registerCleanup();
386
370
  this.proc = child;
387
371
  this.server = {
388
372
  status: "starting",
389
- port: isLocal ? actualPort || void 0 : void 0,
373
+ port: isLocal ? signals.actualPort || void 0 : void 0,
390
374
  pid: child.pid,
391
375
  startedAt: Date.now()
392
376
  };
393
377
  this.publish();
394
- const capture = (chunk) => {
395
- const text = chunk.toString("utf8");
396
- this.appendLog(text);
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
- }
425
- }
426
- }
378
+ child.stdout?.on("data", (chunk) => this.consumeOutput(chunk, child, signals));
379
+ child.stderr?.on("data", (chunk) => this.consumeOutput(chunk, child, signals));
380
+ child.on("error", (error) => this.handleChildError(child, error, signals));
381
+ child.on("exit", (code) => this.handleChildExit(child, code, signals));
382
+ try {
383
+ if (!isLocal) return await this.awaitTunnel(child, readyUrlPromise, loginPromise);
384
+ return await this.finalizeLocalStart(child, portPromise);
385
+ } catch (error) {
386
+ const reason = error instanceof Error ? error.message : String(error);
387
+ return this.handleStartFailure(child, reason, isLocal, signals.actualPort);
388
+ }
389
+ }
390
+ /** Adopt an already-running local server on the target port, if one answers. */
391
+ async tryAdopt(folder) {
392
+ const target = this.forcedPort && this.forcedPort !== 0 ? this.forcedPort : DEFAULT_CODE_SERVER_PORT;
393
+ if (!this.profile.healthPath || !await probeHealth(target, this.profile.healthPath)) return void 0;
394
+ this.adopted = true;
395
+ this.launchCtx = this.baseCtx(folder, target, "");
396
+ this.server = {
397
+ status: "running",
398
+ port: target,
399
+ startedAt: Date.now()
427
400
  };
428
- child.stdout?.on("data", capture);
429
- child.stderr?.on("data", capture);
430
- child.on("error", (error) => {
431
- if (this.proc !== child) return;
432
- this.server = {
433
- status: "error",
434
- error: error.message
435
- };
436
- this.reset();
437
- portRejecter?.(error);
438
- this.publish();
401
+ this.publish();
402
+ return this.status();
403
+ }
404
+ /** Resolve the port to launch on: 0 for tunnels/dynamic, a forced port, or a free one. */
405
+ async resolveInitialPort(isLocal) {
406
+ if (!isLocal) return 0;
407
+ if (this.forcedPort !== void 0 && this.forcedPort !== 0) return this.forcedPort;
408
+ if (this.forcedPort === 0) return 0;
409
+ return getPort({
410
+ host: "127.0.0.1",
411
+ port: DEFAULT_CODE_SERVER_PORT
439
412
  });
440
- child.on("exit", (code) => {
441
- if (this.proc !== child) return;
442
- const exitCode = code ?? 0;
443
- const crashed = this.server.status !== "stopped" && exitCode !== 0;
444
- this.reset();
445
- this.server = crashed ? {
446
- status: "error",
447
- error: this.lastLog() || `${this.bin} exited with code ${exitCode}`
448
- } : { status: "stopped" };
449
- if (crashed) diagnostics.DP_CODE_SERVER_0005({ code: exitCode }, { method: "warn" });
450
- this.reflectHub(crashed ? "error" : "stopped");
413
+ }
414
+ /** The child's environment: inherited env, caller overrides, then profile shaping. */
415
+ buildLaunchEnv(ctx) {
416
+ const baseEnv = {};
417
+ for (const [k, v] of Object.entries(process.env)) if (v !== void 0) baseEnv[k] = v;
418
+ Object.assign(baseEnv, this.extraEnv);
419
+ return this.profile.buildEnv(ctx, baseEnv);
420
+ }
421
+ /** Buffer output and latch the port / login / ready-URL signals from each line. */
422
+ consumeOutput(chunk, child, s) {
423
+ const text = chunk.toString("utf8");
424
+ this.appendLog(text);
425
+ for (const line of text.split("\n")) {
426
+ this.latchPort(line, s);
427
+ this.latchLogin(line, child, s);
428
+ this.latchReadyUrl(line, child, s);
429
+ }
430
+ }
431
+ latchPort(line, s) {
432
+ if (!s.isLocal || s.actualPort !== 0 || !this.profile.matchPort) return;
433
+ const port = this.profile.matchPort(line);
434
+ if (!port) return;
435
+ s.actualPort = port;
436
+ this.server.port = port;
437
+ this.publish();
438
+ s.resolvePort?.(port);
439
+ s.resolvePort = void 0;
440
+ }
441
+ latchLogin(line, child, s) {
442
+ const login = this.profile.matchLogin?.(line);
443
+ if (!login || this.proc !== child) return;
444
+ this.server = {
445
+ ...this.server,
446
+ login
447
+ };
448
+ this.publish();
449
+ s.resolveLogin?.(login);
450
+ }
451
+ latchReadyUrl(line, child, s) {
452
+ const url = this.profile.matchReadyUrl?.(line);
453
+ if (!url || this.proc !== child) return;
454
+ this.readyUrl = url;
455
+ s.resolveReadyUrl?.(url);
456
+ }
457
+ handleChildError(child, error, s) {
458
+ if (this.proc !== child) return;
459
+ this.server = {
460
+ status: "error",
461
+ error: error.message
462
+ };
463
+ this.reset();
464
+ s.rejectPort?.(error);
465
+ this.publish();
466
+ }
467
+ handleChildExit(child, code, s) {
468
+ if (this.proc !== child) return;
469
+ const exitCode = code ?? 0;
470
+ const crashed = this.server.status !== "stopped" && exitCode !== 0;
471
+ this.reset();
472
+ this.server = crashed ? {
473
+ status: "error",
474
+ error: this.lastLog() || `${this.bin} exited with code ${exitCode}`
475
+ } : { status: "stopped" };
476
+ if (crashed) diagnostics.DP_CODE_SERVER_0005({ code: exitCode }, { method: "warn" });
477
+ this.reflectHub(crashed ? "error" : "stopped");
478
+ this.session = void 0;
479
+ s.rejectPort?.(/* @__PURE__ */ new Error(`${this.bin} exited before binding (code ${exitCode})`));
480
+ this.publish();
481
+ }
482
+ /** Wait for the dynamic port then readiness, and flip to `running`. */
483
+ async finalizeLocalStart(child, portPromise) {
484
+ const port = await Promise.race([portPromise, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("timeout waiting for dynamic port allocation")), this.startTimeout))]);
485
+ if (!await this.waitForReady(port, this.profile.healthPath)) throw new Error(this.lastLog() || "startup timed out");
486
+ if (this.proc !== child) return this.status();
487
+ this.server = {
488
+ status: "running",
489
+ port,
490
+ pid: child.pid,
491
+ startedAt: this.server.startedAt
492
+ };
493
+ this.publish();
494
+ return this.status();
495
+ }
496
+ handleStartFailure(child, reason, isLocal, actualPort) {
497
+ if (this.proc === child) {
498
+ this.terminate(child);
499
+ this.reflectHub("error");
451
500
  this.session = void 0;
452
- portRejecter?.(/* @__PURE__ */ new Error(`${this.bin} exited before binding (code ${exitCode})`));
453
- this.publish();
454
- });
455
- try {
456
- if (!isLocal) return await this.awaitTunnel(child, readyUrlPromise, loginPromise);
457
- const port = await Promise.race([portPromise, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("timeout waiting for dynamic port allocation")), this.startTimeout))]);
458
- if (!await this.waitForReady(port, this.profile.healthPath)) throw new Error(this.lastLog() || "startup timed out");
459
- if (this.proc !== child) return this.status();
460
501
  this.server = {
461
- status: "running",
462
- port,
463
- pid: child.pid,
464
- startedAt: this.server.startedAt
502
+ status: "error",
503
+ error: reason
465
504
  };
505
+ this.reset();
466
506
  this.publish();
467
- return this.status();
468
- } catch (error) {
469
- if (this.proc === child) {
470
- this.terminate(child);
471
- this.reflectHub("error");
472
- this.session = void 0;
473
- this.server = {
474
- status: "error",
475
- error: error instanceof Error ? error.message : String(error)
476
- };
477
- this.reset();
478
- this.publish();
479
- }
480
- if (!isLocal) throw diagnostics.DP_CODE_SERVER_0006({ timeout: this.startTimeout });
481
- throw diagnostics.DP_CODE_SERVER_0002({
482
- port: actualPort,
483
- timeout: this.startTimeout
484
- });
485
507
  }
508
+ if (!isLocal) throw diagnostics.DP_CODE_SERVER_0006({ timeout: this.startTimeout });
509
+ throw diagnostics.DP_CODE_SERVER_0002({
510
+ port: actualPort,
511
+ timeout: this.startTimeout
512
+ });
486
513
  }
487
514
  /** Stop the editor process and reset to `stopped`. */
488
515
  stop() {
@@ -590,7 +617,7 @@ var CodeServerSupervisor = class {
590
617
  /**
591
618
  * Resolve the hub's terminals subsystem when this devframe is mounted in a
592
619
  * hub. `ctx.terminals` only exists on a `DevframeHubContext`, so it is
593
- * duck-typed — standalone runtimes (CLI / Vite / build) have no such property
620
+ * duck-typed: standalone runtimes (CLI / Vite / build) have no such property
594
621
  * and fall back to a direct child process.
595
622
  */
596
623
  resolveHubTerminals() {
@@ -614,45 +641,58 @@ var CodeServerSupervisor = class {
614
641
  */
615
642
  async launchProcess(args, env, folder) {
616
643
  const hub = this.resolveHubTerminals();
617
- if (hub) {
618
- const stale = hub.sessions.get(this.sessionId);
619
- if (stale) hub.remove?.(stale);
620
- try {
621
- const session = await hub.startChildProcess({
622
- command: this.bin,
623
- args,
624
- cwd: folder,
625
- env: {
626
- ...env,
627
- PASSWORD: ""
628
- }
629
- }, {
630
- id: this.sessionId,
631
- title: TERMINAL_SESSION_TITLE,
632
- description: folder,
633
- icon: TERMINAL_SESSION_ICON,
634
- restartable: false
635
- });
636
- const child = session.getChildProcess();
637
- if (!child) throw new Error("editor process handle was unavailable");
638
- this.session = session;
639
- return child;
640
- } catch (error) {
641
- const reason = error instanceof Error ? error.message : String(error);
642
- const orphan = hub.sessions.get(this.sessionId);
643
- if (orphan) hub.remove?.(orphan);
644
- this.session = void 0;
645
- this.server = {
646
- status: "error",
647
- error: reason
648
- };
649
- this.publish();
650
- throw diagnostics.DP_CODE_SERVER_0003({
651
- bin: this.bin,
652
- reason
653
- });
654
- }
644
+ return hub ? this.launchViaHub(hub, args, env, folder) : this.launchDirect(args, env, folder);
645
+ }
646
+ /** Spawn through the hub's terminals subsystem so it shows as a terminal session. */
647
+ async launchViaHub(hub, args, env, folder) {
648
+ const stale = hub.sessions.get(this.sessionId);
649
+ if (stale) hub.remove?.(stale);
650
+ try {
651
+ const session = await hub.startChildProcess({
652
+ command: this.bin,
653
+ args,
654
+ cwd: folder,
655
+ /**
656
+ * The hub merges `process.env`, so neutralize PASSWORD explicitly
657
+ * (code-server authenticates via HASHED_PASSWORD).
658
+ */
659
+ env: {
660
+ ...env,
661
+ PASSWORD: ""
662
+ }
663
+ }, {
664
+ id: this.sessionId,
665
+ title: TERMINAL_SESSION_TITLE,
666
+ description: folder,
667
+ icon: TERMINAL_SESSION_ICON,
668
+ /**
669
+ * Restart re-runs the supervisor's start flow (fresh port + secret),
670
+ * not this raw process, so hide the panel's generic restart.
671
+ */
672
+ restartable: false
673
+ });
674
+ const child = session.getChildProcess();
675
+ if (!child) throw new Error("editor process handle was unavailable");
676
+ this.session = session;
677
+ return child;
678
+ } catch (error) {
679
+ const reason = error instanceof Error ? error.message : String(error);
680
+ const orphan = hub.sessions.get(this.sessionId);
681
+ if (orphan) hub.remove?.(orphan);
682
+ this.session = void 0;
683
+ this.server = {
684
+ status: "error",
685
+ error: reason
686
+ };
687
+ this.publish();
688
+ throw diagnostics.DP_CODE_SERVER_0003({
689
+ bin: this.bin,
690
+ reason
691
+ });
655
692
  }
693
+ }
694
+ /** Spawn the editor binary directly (standalone runtimes with no hub). */
695
+ launchDirect(args, env, folder) {
656
696
  try {
657
697
  return spawn(this.bin, args, {
658
698
  cwd: folder,
@@ -746,7 +786,7 @@ function probeHealth(port, path) {
746
786
  * status into shared state, and register the control RPC functions. Returns
747
787
  * the supervisor so callers can launch/stop or dispose it on shutdown.
748
788
  *
749
- * Works in any devframe runtime (CLI, Vite, embedded, build) — it only relies
789
+ * Works in any devframe runtime (CLI, Vite, embedded, build), since it only relies
750
790
  * on the core `ctx.rpc` shared-state surface, not on the hub.
751
791
  */
752
792
  async function setupCodeServer(ctx, options = {}) {
@@ -2,7 +2,7 @@ import { PLUGIN_ID } from "./constants.mjs";
2
2
  import { defineDevframe } from "devframe";
3
3
  //#region package.json
4
4
  var name = "@devframes/plugin-code-server";
5
- var version = "0.9.8";
5
+ var version = "0.9.9";
6
6
  var description = "Devframe plugin that runs VS Code in the browser as an embedded editor panel.";
7
7
  var homepage = "https://github.com/devframes/devframe#readme";
8
8
  //#endregion
@@ -14,7 +14,7 @@ const remoteAssets = {
14
14
  /**
15
15
  * Build a {@link DevframeDefinition} for the code-server panel. The same
16
16
  * definition runs standalone (`createCac`), mounts into a Vite host
17
- * (`/vite`), or docks inside a hub — its `setup` only relies on the core
17
+ * (`/vite`), or docks inside a hub, since its `setup` only relies on the core
18
18
  * devframe RPC + shared-state surface.
19
19
  *
20
20
  * @experimental This plugin is experimental and may change without a major
@@ -38,6 +38,10 @@ function createCodeServerDevframe(options = {}) {
38
38
  homepage,
39
39
  description,
40
40
  icon: "ph:code-duotone",
41
+ /**
42
+ * Leave undefined so `resolveBasePath` picks `/` standalone and
43
+ * `/__<id>/` when hosted. Authors override via `options.basePath`.
44
+ */
41
45
  basePath: options.basePath,
42
46
  cli: {
43
47
  command: options.command ?? "devframe-code-server",
@@ -45,6 +49,11 @@ function createCodeServerDevframe(options = {}) {
45
49
  portRange: options.portRange,
46
50
  random: options.random,
47
51
  distDir: resolvedDist,
52
+ /**
53
+ * Gate the standalone launcher by default; `maybeOpenBrowser` folds the
54
+ * current OTP into the `--open` URL so the tab lands already trusted.
55
+ * Hosted adapters supply their own auth layer and ignore this.
56
+ */
48
57
  auth: options.auth ?? true
49
58
  },
50
59
  async setup(ctx) {
package/dist/types.d.mts CHANGED
@@ -4,11 +4,11 @@ type CodeServerStatus = 'stopped' | 'starting' | 'running' | 'error';
4
4
  /**
5
5
  * Which editor server binary the plugin launches in `mode: 'local'`:
6
6
  *
7
- * - `'code-server'` — Coder's open-source
7
+ * - `'code-server'`: Coder's open-source
8
8
  * [code-server](https://github.com/coder/code-server) (`code-server …`). The
9
9
  * plugin runs it with password auth and hands the client a session cookie, so
10
10
  * the embedded editor opens already signed in.
11
- * - `'ms-code-serve-web'` — Microsoft's official
11
+ * - `'ms-code-serve-web'`: Microsoft's official
12
12
  * [`code serve-web`](https://code.visualstudio.com/docs/remote/vscode-server)
13
13
  * (ships with the `code` CLI). The plugin generates a connection token and
14
14
  * hands it to the client as a `?tkn=` query parameter.
@@ -17,9 +17,9 @@ type CodeServerBackend = 'code-server' | 'ms-code-serve-web';
17
17
  /**
18
18
  * How the editor is served:
19
19
  *
20
- * - `'local'` — a local server ({@link CodeServerBackend}) embedded from this
20
+ * - `'local'`: a local server ({@link CodeServerBackend}) embedded from this
21
21
  * machine's origin.
22
- * - `'tunnel'` — Microsoft's `code tunnel`, which registers a remote tunnel and
22
+ * - `'tunnel'`: Microsoft's `code tunnel`, which registers a remote tunnel and
23
23
  * embeds the hosted `vscode.dev` editor. First launch prints a device-login
24
24
  * prompt (surfaced as {@link CodeServerLogin}); authentication is handled by
25
25
  * `vscode.dev` itself, not this plugin.
@@ -70,7 +70,7 @@ interface CodeServerServerInfo {
70
70
  }
71
71
  /**
72
72
  * The full shared-state payload broadcast to subscribed clients. Deliberately
73
- * carries no authentication material — see {@link CodeServerConnect}.
73
+ * carries no authentication material; see {@link CodeServerConnect}.
74
74
  */
75
75
  interface CodeServerSharedState {
76
76
  detection: CodeServerDetection;
@@ -79,7 +79,7 @@ interface CodeServerSharedState {
79
79
  /**
80
80
  * How the client reaches the running editor. May carry a secret (session
81
81
  * cookie or connection token), so it is returned only from the `start` /
82
- * `status` RPCs to the already-authorized client — never published to shared
82
+ * `status` RPCs to the already-authorized client, never published to shared
83
83
  * state.
84
84
  *
85
85
  * - `url` set → embed it verbatim (tunnel mode's `vscode.dev` URL).
@@ -98,11 +98,11 @@ interface CodeServerConnect {
98
98
  value: string;
99
99
  };
100
100
  }
101
- /** Result of the `status` query — shared state plus connect info when running. */
101
+ /** Result of the `status` query: shared state plus connect info when running. */
102
102
  interface CodeServerStatusResult extends CodeServerSharedState {
103
103
  connect?: CodeServerConnect;
104
104
  }
105
- /** Result of the `start` action — identical shape to {@link CodeServerStatusResult}. */
105
+ /** Result of the `start` action, identical shape to {@link CodeServerStatusResult}. */
106
106
  type CodeServerStartResult = CodeServerStatusResult;
107
107
  /** Wire payload for the `start` RPC. */
108
108
  interface CodeServerStartRequest {
@@ -169,7 +169,7 @@ interface CodeServerOptions {
169
169
  random?: boolean;
170
170
  /**
171
171
  * Require the trust handshake on the standalone launcher server. Enabled
172
- * by default — `--open` embeds the current OTP in the opened URL, so the
172
+ * by default; `--open` embeds the current OTP in the opened URL, so the
173
173
  * tab authenticates automatically without extra prompts. Hosted adapters
174
174
  * manage their own auth and ignore this.
175
175
  */
package/dist/vite.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as createCodeServerDevframe } from "./src-CNJYQ3UQ.mjs";
1
+ import { t as createCodeServerDevframe } from "./src-5dhTkOqj.mjs";
2
2
  import { devframeViteBridge, devframeVitePlugin } from "@devframes/vite/single";
3
3
  //#region src/vite.ts
4
4
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devframes/plugin-code-server",
3
3
  "type": "module",
4
- "version": "0.9.8",
4
+ "version": "0.9.9",
5
5
  "description": "Devframe plugin that runs VS Code in the browser as an embedded editor panel.",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -40,8 +40,8 @@
40
40
  "dist"
41
41
  ],
42
42
  "peerDependencies": {
43
- "@devframes/plugin-code-server--assets": "0.9.8",
44
- "devframe": "0.9.8",
43
+ "@devframes/plugin-code-server--assets": "0.9.9",
44
+ "devframe": "0.9.9",
45
45
  "vite": "^8.0.0"
46
46
  },
47
47
  "peerDependenciesMeta": {
@@ -53,19 +53,19 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
- "@devframes/vite": "0.9.8",
56
+ "@devframes/vite": "0.9.9",
57
57
  "cac": "^7.0.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@antfu/design": "^0.4.0",
61
- "@devframes/plugin-code-server--assets": "0.9.8",
61
+ "@devframes/plugin-code-server--assets": "0.9.9",
62
62
  "@iconify-json/ph": "^1.2.2",
63
63
  "@storybook/addon-docs": "^10.5.10",
64
64
  "@storybook/vue3-vite": "^10.5.10",
65
65
  "@types/node": "^26.4.0",
66
66
  "@vitejs/plugin-vue": "^6.0.8",
67
67
  "colorjs.io": "^0.7.1",
68
- "devframe": "0.9.8",
68
+ "devframe": "0.9.9",
69
69
  "storybook": "^10.5.10",
70
70
  "tsdown": "^0.22.14",
71
71
  "unocss": "^66.8.1",