@devframes/plugin-code-server 0.9.8 → 0.9.10

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,761 +1,3 @@
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-EXj5vBYf.mjs";
3
- import { execSync, spawn } from "node:child_process";
4
- import { createHash, randomBytes } from "node:crypto";
5
- import { request } from "node:http";
6
- import { hostname } from "node:os";
7
- import process from "node:process";
8
- import { getPort } from "devframe/utils/get-port";
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
112
- //#region src/node/detect.ts
113
- /**
114
- * Probe the host for a usable code-server binary by running
115
- * `<bin> --version`. Resolves to `installed: false` when the binary is
116
- * missing (ENOENT), errors, or exits non-zero — never throws — so the
117
- * launcher can fall back to install instructions.
118
- *
119
- * `code-server --version` prints e.g. `4.96.4 abc123 with Code 1.96.4`; the
120
- * first semver-looking token is taken as the version. Matching a semver
121
- * 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.
124
- */
125
- function detectCodeServer(bin = "code-server", timeoutMs = 5e3) {
126
- return new Promise((resolve) => {
127
- let settled = false;
128
- let stdout = "";
129
- const finish = (result) => {
130
- if (settled) return;
131
- settled = true;
132
- resolve(result);
133
- };
134
- let child;
135
- try {
136
- child = spawn(bin, ["--version"], {
137
- windowsHide: true,
138
- stdio: [
139
- "ignore",
140
- "pipe",
141
- "ignore"
142
- ],
143
- shell: process.platform === "win32" && bin.endsWith(".cmd")
144
- });
145
- } catch {
146
- finish({
147
- installed: false,
148
- bin
149
- });
150
- return;
151
- }
152
- const timer = setTimeout(() => {
153
- child.kill();
154
- finish({
155
- installed: false,
156
- bin
157
- });
158
- }, timeoutMs);
159
- timer.unref?.();
160
- child.stdout?.on("data", (chunk) => {
161
- stdout += chunk.toString("utf8");
162
- });
163
- child.on("error", () => {
164
- clearTimeout(timer);
165
- finish({
166
- installed: false,
167
- bin
168
- });
169
- });
170
- child.on("exit", (code) => {
171
- clearTimeout(timer);
172
- if (code === 0) {
173
- const version = stdout.match(/\d+\.\d+\.\d\S*/)?.[0] ?? (stdout.trim().split(/\s+/)[0] || void 0);
174
- finish({
175
- installed: true,
176
- version,
177
- bin
178
- });
179
- } else finish({
180
- installed: false,
181
- bin
182
- });
183
- });
184
- });
185
- }
186
- //#endregion
187
- //#region src/node/supervisor.ts
188
- /** Recent output lines retained for surfacing startup failures. */
189
- const LOG_BUFFER_LINES = 200;
190
- /** Interval between readiness probes. */
191
- const PROBE_INTERVAL = 250;
192
- /**
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.
200
- *
201
- * Depends only on the core devframe context (shared state), not on the hub.
202
- */
203
- var CodeServerSupervisor = class {
204
- ctx;
205
- mode;
206
- explicitBackend;
207
- explicitBin;
208
- workspace;
209
- host;
210
- forcedPort;
211
- extraArgs;
212
- extraEnv;
213
- cookieSuffix;
214
- cookieName;
215
- startTimeout;
216
- reuseExistingServer;
217
- tunnelName;
218
- /** Resolved after the first detection. */
219
- backend;
220
- bin;
221
- profile;
222
- state;
223
- detection;
224
- server = { status: "stopped" };
225
- proc;
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;
232
- logBuffer = [];
233
- exitHandler;
234
- /** Stable id of the hub terminal session, reused across start/stop. */
235
- sessionId;
236
- /** The live hub terminal session when launched through `ctx.terminals`. */
237
- session;
238
- constructor(ctx, options = {}) {
239
- this.ctx = ctx;
240
- this.mode = options.mode ?? "local";
241
- this.explicitBackend = options.backend;
242
- this.explicitBin = options.bin;
243
- this.workspace = options.cwd ?? ctx.cwd;
244
- this.host = options.host ?? "127.0.0.1";
245
- this.forcedPort = options.serverPort;
246
- this.extraArgs = options.args ?? [];
247
- this.extraEnv = options.env ?? {};
248
- this.cookieSuffix = options.cookieSuffix;
249
- this.cookieName = getCookieSessionName(options.cookieSuffix);
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;
257
- this.detection = {
258
- checked: false,
259
- installed: false,
260
- bin: this.bin,
261
- backend: this.backend,
262
- mode: this.mode
263
- };
264
- }
265
- /** Resolve shared state, register process-exit cleanup, run first detection. */
266
- async init() {
267
- if (this.state) return;
268
- this.state = await this.ctx.rpc.sharedState.get(STATE_KEY, { initialValue: {
269
- detection: this.detection,
270
- server: this.server
271
- } });
272
- await this.detect();
273
- }
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
- */
281
- async detect() {
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
- }
308
- this.detection = {
309
- checked: true,
310
- installed,
311
- version,
312
- bin: this.bin,
313
- backend: this.backend,
314
- mode: this.mode
315
- };
316
- this.publish();
317
- return this.detection;
318
- }
319
- /** Current status (+ connect info when running) for the launcher UI. */
320
- status() {
321
- return {
322
- detection: { ...this.detection },
323
- server: { ...this.server },
324
- connect: this.connectInfo()
325
- };
326
- }
327
- /**
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.
333
- */
334
- async start(req = {}) {
335
- if (this.server.status === "running" || this.server.status === "starting") return this.status();
336
- if (!this.detection.checked) await this.detect();
337
- if (!this.detection.installed) throw diagnostics.DP_CODE_SERVER_0001({ bin: this.bin });
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({
356
- host: "127.0.0.1",
357
- port: DEFAULT_CODE_SERVER_PORT
358
- });
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);
368
- this.logBuffer = [];
369
- let actualPort = initialPort;
370
- let portResolver;
371
- let portRejecter;
372
- const portPromise = isLocal && initialPort === 0 ? new Promise((resolve, reject) => {
373
- portResolver = resolve;
374
- portRejecter = reject;
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
- });
384
- const child = await this.launchProcess(args, env, folder);
385
- this.registerCleanup();
386
- this.proc = child;
387
- this.server = {
388
- status: "starting",
389
- port: isLocal ? actualPort || void 0 : void 0,
390
- pid: child.pid,
391
- startedAt: Date.now()
392
- };
393
- 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
- }
427
- };
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();
439
- });
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");
451
- 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
- this.server = {
461
- status: "running",
462
- port,
463
- pid: child.pid,
464
- startedAt: this.server.startedAt
465
- };
466
- 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
- }
486
- }
487
- /** Stop the editor process and reset to `stopped`. */
488
- stop() {
489
- const child = this.proc;
490
- const wasAdopted = this.adopted;
491
- this.reset();
492
- this.server = { status: "stopped" };
493
- if (child && !wasAdopted) this.terminate(child);
494
- this.reflectHub("stopped");
495
- this.session = void 0;
496
- this.publish();
497
- return this.status();
498
- }
499
- /** Kill the process on host shutdown / test teardown. */
500
- dispose() {
501
- if (this.proc && !this.adopted) this.terminate(this.proc);
502
- this.reset();
503
- this.session = void 0;
504
- if (this.exitHandler) {
505
- process.off("exit", this.exitHandler);
506
- this.exitHandler = void 0;
507
- }
508
- }
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) {
519
- return {
520
- host: this.host,
521
- port,
522
- folder,
523
- secret,
524
- cookieName: this.cookieName,
525
- extraArgs: this.extraArgs,
526
- tunnelName: this.tunnelName
527
- };
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
- }
580
- terminate(child) {
581
- if (this.session) {
582
- this.session.terminate().catch(() => {});
583
- return;
584
- }
585
- try {
586
- if (process.platform === "win32" && this.bin.endsWith(".cmd") && child.pid) execSync(`taskkill /pid ${child.pid} /t /f`, { stdio: "ignore" });
587
- else child.kill("SIGTERM");
588
- } catch {}
589
- }
590
- /**
591
- * Resolve the hub's terminals subsystem when this devframe is mounted in a
592
- * hub. `ctx.terminals` only exists on a `DevframeHubContext`, so it is
593
- * duck-typed — standalone runtimes (CLI / Vite / build) have no such property
594
- * and fall back to a direct child process.
595
- */
596
- resolveHubTerminals() {
597
- const terminals = this.ctx.terminals;
598
- return terminals && typeof terminals.startChildProcess === "function" ? terminals : void 0;
599
- }
600
- /** Update the mirrored hub terminal session's status, when one exists. */
601
- reflectHub(status) {
602
- const hub = this.resolveHubTerminals();
603
- if (hub?.sessions.has(this.sessionId)) hub.update({
604
- id: this.sessionId,
605
- status
606
- });
607
- }
608
- /**
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.
614
- */
615
- async launchProcess(args, env, folder) {
616
- 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
- }
655
- }
656
- try {
657
- return spawn(this.bin, args, {
658
- cwd: folder,
659
- env,
660
- windowsHide: true,
661
- stdio: [
662
- "ignore",
663
- "pipe",
664
- "pipe"
665
- ],
666
- shell: process.platform === "win32" && this.bin.endsWith(".cmd")
667
- });
668
- } catch (error) {
669
- const reason = error instanceof Error ? error.message : String(error);
670
- this.server = {
671
- status: "error",
672
- error: reason
673
- };
674
- this.publish();
675
- throw diagnostics.DP_CODE_SERVER_0003({
676
- bin: this.bin,
677
- reason
678
- });
679
- }
680
- }
681
- appendLog(text) {
682
- for (const line of text.split("\n")) {
683
- if (line.length === 0) continue;
684
- this.logBuffer.push(line);
685
- }
686
- if (this.logBuffer.length > LOG_BUFFER_LINES) this.logBuffer.splice(0, this.logBuffer.length - LOG_BUFFER_LINES);
687
- }
688
- lastLog() {
689
- return this.logBuffer.length ? this.logBuffer[this.logBuffer.length - 1] : void 0;
690
- }
691
- publish() {
692
- this.state?.mutate((draft) => {
693
- draft.detection = { ...this.detection };
694
- draft.server = { ...this.server };
695
- });
696
- }
697
- /**
698
- * Poll the server's readiness path until it responds or the timeout elapses.
699
- * Returns false if the process exits first.
700
- */
701
- async waitForReady(port, path) {
702
- const deadline = Date.now() + this.startTimeout;
703
- while (Date.now() < deadline) {
704
- if (!this.proc) return false;
705
- if (await probeHealth(port, path)) return true;
706
- await delay(PROBE_INTERVAL);
707
- }
708
- return false;
709
- }
710
- registerCleanup() {
711
- if (this.exitHandler) return;
712
- this.exitHandler = () => this.dispose();
713
- process.once("exit", this.exitHandler);
714
- }
715
- };
716
- function delay(ms) {
717
- return new Promise((resolve) => {
718
- setTimeout(resolve, ms).unref?.();
719
- });
720
- }
721
- function probeHealth(port, path) {
722
- return new Promise((resolve) => {
723
- const req = request({
724
- host: "127.0.0.1",
725
- port,
726
- path,
727
- method: "GET",
728
- timeout: 1500
729
- }, (res) => {
730
- res.resume();
731
- resolve((res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 500);
732
- });
733
- req.on("error", () => resolve(false));
734
- req.on("timeout", () => {
735
- req.destroy();
736
- resolve(false);
737
- });
738
- req.end();
739
- });
740
- }
741
- //#endregion
742
- //#region src/node/index.ts
743
- /**
744
- * Wire the code-server subsystem onto a devframe node context: create the
745
- * {@link CodeServerSupervisor}, run the initial binary detection, publish
746
- * status into shared state, and register the control RPC functions. Returns
747
- * the supervisor so callers can launch/stop or dispose it on shutdown.
748
- *
749
- * Works in any devframe runtime (CLI, Vite, embedded, build) — it only relies
750
- * on the core `ctx.rpc` shared-state surface, not on the hub.
751
- */
752
- async function setupCodeServer(ctx, options = {}) {
753
- const supervisor = new CodeServerSupervisor(ctx, options);
754
- setCodeServerSupervisor(ctx, supervisor);
755
- await supervisor.init();
756
- for (const fn of serverFunctions) ctx.rpc.register(fn);
757
- if (options.startOnBoot) supervisor.start().catch(() => {});
758
- return supervisor;
759
- }
760
- //#endregion
761
- export { CodeServerSupervisor, detectCodeServer, diagnostics, getCodeServerSupervisor, resolveProfile, setCodeServerSupervisor, setupCodeServer };
1
+ import { t as createCodeServerDevframe } from "../node-C_wU5QYh.mjs";
2
+ import { DEFAULT_CODE_SERVER_PORT, DEFAULT_PORT, PLUGIN_ID, STATE_KEY, getCookieSessionName } from "./constants.mjs";
3
+ export { DEFAULT_CODE_SERVER_PORT, DEFAULT_PORT, PLUGIN_ID, STATE_KEY, createCodeServerDevframe, createCodeServerDevframe as default, getCookieSessionName };