@devframes/plugin-code-server 0.7.4 → 0.7.6

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
@@ -4,28 +4,37 @@
4
4
  > This plugin is experimental and may change without a major version bump until
5
5
  > it stabilizes.
6
6
 
7
- Run [code-server](https://github.com/coder/code-server) (VS Code in the
8
- browser) as a devframe panel. The plugin detects a local `code-server`
9
- install, launches it on demand, and embeds the editor in an
10
- auto-authenticated `<iframe>`.
7
+ Run VS Code in the browser as a devframe panel. The plugin detects a local
8
+ editor binary, launches it on demand, and embeds the editor in an
9
+ auto-authenticated `<iframe>`. The launcher is a **Vue** SPA built on the shared
10
+ `@antfu/design` system.
11
11
 
12
12
  ## How it works
13
13
 
14
- - **Detection** — on startup it runs `code-server --version`. When the binary
15
- is missing, the launcher renders install instructions and links instead of a
14
+ - **Detection** — on startup it probes the resolved binary with `--version`.
15
+ When none is found, the launcher renders install instructions instead of a
16
16
  launch button.
17
- - **Launch** — the launcher's button starts code-server 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
- via code-server's `/healthz` endpoint.
20
- - **Auto-auth** — code-server runs with password auth. The plugin generates a
21
- random token, sets `HASHED_PASSWORD` to its SHA-256, and hands the matching
22
- session cookie back to the already-authorized devframe client. The launcher
23
- applies that cookie for the current host before loading the iframe, so the
24
- editor opens already signed in — no code-server login page.
19
+ before the iframe loads.
20
+ - **Auto-auth** — the plugin generates fresh auth material per launch and hands
21
+ it to the already-authorized devframe client, so the editor opens already
22
+ signed in. `code-server` uses a session cookie (`HASHED_PASSWORD`);
23
+ `code serve-web` uses a connection token on the URL (`?tkn=`).
25
24
 
26
- The editor iframe points at code-server's own origin
27
- (`<protocol>//<host>:<port>/`), so WebSocket traffic flows directly without a
28
- reverse proxy.
25
+ ## Backends & modes
26
+
27
+ `mode: 'local'` (default) embeds a server on this machine; `backend` selects it,
28
+ or leave it unset to auto-detect (prefers `code-server`, then `code serve-web`).
29
+
30
+ | `backend` | Binary | Auth |
31
+ |-----------|--------|------|
32
+ | `'code-server'` | Coder [`code-server`](https://github.com/coder/code-server) | password + session cookie |
33
+ | `'ms-code-serve-web'` | Microsoft [`code serve-web`](https://code.visualstudio.com/docs/remote/vscode-server) | connection token (`?tkn=`) |
34
+
35
+ `mode: 'tunnel'` runs Microsoft's `code tunnel` and embeds the hosted
36
+ `vscode.dev` editor. The first launch surfaces a device-login prompt in the
37
+ launcher; `vscode.dev` handles authentication.
29
38
 
30
39
  ## Usage
31
40
 
@@ -41,9 +50,12 @@ npx @devframes/plugin-code-server # dev server + launcher
41
50
  import { createCodeServerDevframe } from '@devframes/plugin-code-server'
42
51
 
43
52
  export default createCodeServerDevframe({
44
- // bin: 'code-server', // binary to detect/launch (default: PATH)
45
- // serverPort: 8080, // force a port (default: free port near 8080)
46
- // args: ['--disable-getting-started-override'],
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' },
47
59
  })
48
60
  ```
49
61
 
@@ -61,24 +73,25 @@ export default {
61
73
 
62
74
  | Function | Type | Purpose |
63
75
  |----------|------|---------|
64
- | `devframes:plugin:code-server:detect` | query | Re-probe for the binary; returns `{ installed, version, bin }`. |
65
- | `devframes:plugin:code-server:status` | query | Current status + auth cookie when running. |
76
+ | `devframes:plugin:code-server:detect` | query | Re-probe; returns `{ installed, version, bin, backend, mode }`. |
77
+ | `devframes:plugin:code-server:status` | query | Current status + connect descriptor when running. |
66
78
  | `devframes:plugin:code-server:start` | action | Launch and wait for readiness. |
67
79
  | `devframes:plugin:code-server:stop` | action | Stop the process. |
68
80
 
69
- Status (minus the auth cookie) is mirrored into the
81
+ Status (minus the connect descriptor) is mirrored into the
70
82
  `devframes:plugin:code-server:state` shared state for reactive UIs.
71
83
 
72
84
  ## UI
73
85
 
74
- The launcher UI is a pure, state-driven view (`src/client/view.ts`) decoupled
75
- from RPC, so every state renders in isolation. `mountCodeServer` wires the live
76
- connection to it. Each UI state has a Storybook story:
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
88
+ a Storybook story; `App.vue` wires the live connection to it and mounts the
89
+ editor in a full-bleed, auto-authenticated iframe (`EditorFrame.vue`).
77
90
 
78
91
  ```sh
79
92
  pnpm storybook # dev
80
93
  pnpm build-storybook # static build
81
94
  ```
82
95
 
83
- Stories: connecting, not-installed, launch, launch-error, starting, running
84
- (the running story mounts a mock editor instead of a live server).
96
+ Stories: connecting, not-installed, launch, launch (serve-web), launch-error,
97
+ starting, tunnel-login, and a running editor frame (mock editor).
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as createCodeServerDevframe } from "./src-8iDqPfSS.mjs";
1
+ import { n as createCodeServerDevframe } from "./src-EUgoe7om.mjs";
2
2
  import { createCac } from "devframe/adapters/cac";
3
3
  //#region src/cli.ts
4
4
  /**
@@ -1,74 +1,12 @@
1
- import { CodeServerAuth, CodeServerDetection, CodeServerServerInfo, CodeServerSharedState, CodeServerStatus } from "../types.mjs";
2
- import { o as STATE_KEY } from "../constants-DZKOXnxd.mjs";
3
- import { DevframeRpcClient } from "devframe/client";
4
- //#region src/client/view.d.ts
5
- /** The data the view renders from. Pure — no RPC, no process knowledge. */
6
- interface CodeServerViewState {
7
- detection: CodeServerDetection;
8
- server: CodeServerServerInfo;
9
- /** Auth handoff; present only once the editor is reachable. */
10
- auth?: CodeServerAuth;
11
- /** A request (launch / re-check) is in flight. */
12
- busy?: boolean;
13
- }
14
- /** User intents the view surfaces. */
15
- interface CodeServerViewActions {
16
- launch: () => void;
17
- recheck: () => void;
18
- }
19
- interface CodeServerViewOptions {
20
- actions?: Partial<CodeServerViewActions>;
21
- /**
22
- * Build the editor iframe URL from the running port. Defaults to the
23
- * current page host so the cookie set on this origin reaches the iframe.
24
- */
25
- resolveEditorUrl?: (port: number) => string;
26
- /**
27
- * Apply code-server's session cookie before the iframe loads. Defaults to
28
- * `document.cookie`. Storybook overrides this to a no-op.
29
- */
30
- applyAuth?: (auth: CodeServerAuth) => void;
31
- }
32
- /** Discrete UI states, one per Storybook story. */
33
- type CodeServerPhase = 'connecting' | 'not-installed' | 'launch' | 'starting' | 'running';
34
- interface CodeServerViewHandle {
35
- /** Re-render from a new state (cheap; reuses the live iframe when unchanged). */
36
- update: (state: CodeServerViewState) => void;
37
- dispose: () => void;
38
- }
39
- /** Map a state to its discrete UI phase. */
40
- declare function resolvePhase(state: CodeServerViewState): CodeServerPhase;
41
- /**
42
- * Create the code-server launcher view inside `container`. The view is a pure
43
- * function of {@link CodeServerViewState}: callers (the SPA via
44
- * `mountCodeServer`, or Storybook) push state with `update()` and wire intent
45
- * through `options.actions`. It performs no RPC and owns no process state, so
46
- * every UI phase can be rendered in isolation.
47
- */
48
- declare function createCodeServerView(container: HTMLElement, options?: CodeServerViewOptions): CodeServerViewHandle;
49
- //#endregion
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";
3
+ import { DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientOptions } from "devframe/client";
50
4
  //#region src/client/index.d.ts
51
- interface MountCodeServerOptions {
52
- /** Pre-connected client. When omitted, `connectDevframe()` is awaited. */
53
- rpc?: DevframeRpcClient;
54
- }
55
- interface CodeServerHandle {
56
- rpc: DevframeRpcClient;
57
- /** Stop the code-server process (no UI control is rendered over the editor). */
58
- stop: () => Promise<void>;
59
- dispose: () => void;
60
- }
61
5
  /**
62
- * Mount the code-server launcher into `container`. Connects to the devframe
63
- * backend, then drives a {@link createCodeServerView} from the live
64
- * detection/server state: install instructions when the binary is missing, a
65
- * launch screen when stopped, a progress state while starting, and the editor
66
- * in a full-bleed, auto-authenticated iframe once running.
67
- *
68
- * The connection is assumed already authorized with devframe's auth, so the
69
- * server-issued session cookie is applied transparently before the iframe
70
- * loads — the user never sees code-server's login page.
6
+ * Connect to the code-server plugin's devframe backend. A thin, typed wrapper
7
+ * around devframe's {@link connectDevframe}; the SPA derives its base from
8
+ * `document.baseURI`, so no options are required in the common case.
71
9
  */
72
- declare function mountCodeServer(container: HTMLElement, options?: MountCodeServerOptions): Promise<CodeServerHandle>;
10
+ declare function connectCodeServer(options?: DevframeRpcClientOptions): Promise<DevframeRpcClient>;
73
11
  //#endregion
74
- export { CodeServerHandle, type CodeServerPhase, type CodeServerServerInfo, type CodeServerSharedState, type CodeServerStatus, type CodeServerViewActions, type CodeServerViewHandle, type CodeServerViewOptions, type CodeServerViewState, MountCodeServerOptions, STATE_KEY, createCodeServerView, mountCodeServer, resolvePhase };
12
+ export { type CodeServerBackend, type CodeServerConnect, type CodeServerDetection, type CodeServerLogin, type CodeServerMode, type CodeServerServerInfo, type CodeServerSharedState, type CodeServerStartResult, type CodeServerStatus, type CodeServerStatusResult, type DevframeConnectionStatus, type DevframeRpcClient, STATE_KEY, connectCodeServer };
@@ -1,445 +1,21 @@
1
1
  import { connectDevframe } from "devframe/client";
2
- import { createIframePanes } from "iframe-pane";
3
2
  //#region src/constants.ts
4
3
  /**
5
4
  * Shared-state key holding the serializable, secret-free server status and
6
- * detection result. The authentication cookie is never published here — it
7
- * is returned only from the `start` / `status` RPCs to the already-authorized
8
- * client (see {@link CodeServerAuth}).
5
+ * detection result. Authentication material is never published here — it is
6
+ * returned only from the `start` / `status` RPCs to the already-authorized
7
+ * client (see {@link import('./types').CodeServerConnect}).
9
8
  */
10
9
  const STATE_KEY = "devframes:plugin:code-server:state";
11
10
  //#endregion
12
- //#region src/client/design.ts
13
- function cx(...parts) {
14
- return parts.filter(Boolean).join(" ");
15
- }
16
- function button({ variant = "primary", size = "md", class: extra } = {}) {
17
- const variantClass = {
18
- primary: "btn-primary",
19
- secondary: "btn-action",
20
- outline: "btn-action",
21
- ghost: "inline-flex items-center justify-center gap-1.5 rounded px2 py1 op75 hover:op100 hover:bg-active transition disabled:pointer-events-none disabled:op30!",
22
- destructive: "btn-action text-error border-error/30!",
23
- link: "inline-flex items-center gap-1.5 color-active hover:underline underline-offset-2"
24
- };
25
- const sizeClass = size === "sm" ? variant === "primary" ? "text-sm px-2.5! py-1!" : "text-sm" : size === "lg" ? "text-base px-4! py-2!" : "";
26
- return cx(variantClass[variant], sizeClass, extra);
27
- }
28
- function link(extra) {
29
- return cx("color-active hover:underline underline-offset-2", extra);
30
- }
31
- function spinner(extra) {
32
- return cx("inline-block size-4 rounded-full border-2 border-current border-t-transparent animate-spin", extra);
33
- }
34
- //#endregion
35
- //#region src/client/connection-state.ts
36
- const COPY = {
37
- connecting: {
38
- icon: "i-ph-plugs-connected-duotone",
39
- title: "Connecting…",
40
- body: "Establishing a connection to the devframe server."
41
- },
42
- disconnected: {
43
- icon: "i-ph-plugs-duotone",
44
- title: "Disconnected",
45
- body: "Lost the connection to the devframe server. Reload once it is back up."
46
- },
47
- unauthorized: {
48
- icon: "i-ph-lock-key-duotone",
49
- title: "Not authorized",
50
- body: "Reopen the link printed by your dev server, then reload."
51
- },
52
- error: {
53
- icon: "i-ph-warning-octagon-duotone",
54
- title: "Connection failed",
55
- body: "Could not reach the devframe server."
56
- }
57
- };
58
- /**
59
- * A full-container connection state, shown whenever the devframe client isn't
60
- * `connected` so the launcher never sits on an unexplained blank while the
61
- * socket is down. Reload is the recovery path (no auto-reconnect).
62
- */
63
- function createConnectionState(container) {
64
- const root = document.createElement("div");
65
- root.className = "absolute inset-0 flex flex-col items-center justify-center gap-4 bg-base color-base p-8 text-center z-nav";
66
- root.hidden = true;
67
- container.append(root);
68
- function update(status, error) {
69
- if (status === "connected") {
70
- root.hidden = true;
71
- root.replaceChildren();
72
- return false;
73
- }
74
- const copy = COPY[status];
75
- root.replaceChildren();
76
- const glyph = document.createElement("div");
77
- glyph.className = cx(copy.icon, "text-4xl color-active");
78
- root.append(glyph);
79
- const text = document.createElement("div");
80
- text.className = "flex flex-col gap-1";
81
- const title = document.createElement("p");
82
- title.className = "text-lg font-medium";
83
- title.textContent = copy.title;
84
- const body = document.createElement("p");
85
- body.className = "text-sm color-muted max-w-sm";
86
- body.textContent = copy.body;
87
- text.append(title, body);
88
- if (error && status === "error") {
89
- const code = document.createElement("code");
90
- code.className = "mt-1 max-w-sm break-words font-mono text-xs color-faint";
91
- code.textContent = error;
92
- text.append(code);
93
- }
94
- root.append(text);
95
- if (status !== "connecting") {
96
- const reload = document.createElement("button");
97
- reload.type = "button";
98
- reload.className = button({
99
- variant: "primary",
100
- size: "sm"
101
- });
102
- reload.textContent = "Reload";
103
- reload.addEventListener("click", () => location.reload());
104
- root.append(reload);
105
- }
106
- root.hidden = false;
107
- return true;
108
- }
109
- return {
110
- update,
111
- dispose() {
112
- root.remove();
113
- }
114
- };
115
- }
116
- //#endregion
117
- //#region src/client/view.ts
118
- const DOCS_URL = "https://coder.com/docs/code-server/latest/install";
119
- const REPO_URL = "https://github.com/coder/code-server";
120
- const panes = createIframePanes();
121
- let paneCounter = 0;
122
- const INSTALL_COMMANDS = [
123
- {
124
- label: "Install script (Linux / macOS)",
125
- command: "curl -fsSL https://code-server.dev/install.sh | sh"
126
- },
127
- {
128
- label: "npm",
129
- command: "npm install -g code-server"
130
- },
131
- {
132
- label: "Homebrew",
133
- command: "brew install code-server"
134
- }
135
- ];
136
- /** Map a state to its discrete UI phase. */
137
- function resolvePhase(state) {
138
- const { detection, server, auth, busy } = state;
139
- if (server.status === "running" && server.port != null) return auth ? "running" : "starting";
140
- if (server.status === "starting" || busy && server.status !== "error") return "starting";
141
- if (!detection.checked) return "connecting";
142
- if (!detection.installed) return "not-installed";
143
- return "launch";
144
- }
145
- function el(tag, className, text) {
146
- const node = document.createElement(tag);
147
- if (className) node.className = className;
148
- if (text != null) node.textContent = text;
149
- return node;
150
- }
151
- /** A Phosphor icon (UnoCSS presetIcons), matching the other devframe plugins. */
152
- function icon(name, cls = "") {
153
- return el("span", cx(name, "shrink-0", cls));
154
- }
155
- /** The uppercase "code-server" eyebrow, prefixed with the brand icon. */
156
- function eyebrow() {
157
- const p = el("p", "flex items-center gap-1.5 mb-2.5 text-xs tracking-wider uppercase color-muted");
158
- p.append(icon("i-ph-code-duotone", "text-sm"), document.createTextNode("code-server"));
159
- return p;
160
- }
161
- function defaultEditorUrl(port) {
162
- return `${location.protocol}//${location.hostname}:${port}/`;
163
- }
164
- function defaultApplyAuth(auth) {
165
- document.cookie = `${auth.cookieName}=${auth.cookieValue}; path=/; SameSite=Lax`;
166
- }
167
- /**
168
- * Create the code-server launcher view inside `container`. The view is a pure
169
- * function of {@link CodeServerViewState}: callers (the SPA via
170
- * `mountCodeServer`, or Storybook) push state with `update()` and wire intent
171
- * through `options.actions`. It performs no RPC and owns no process state, so
172
- * every UI phase can be rendered in isolation.
173
- */
174
- function createCodeServerView(container, options = {}) {
175
- const resolveEditorUrl = options.resolveEditorUrl ?? defaultEditorUrl;
176
- const applyAuth = options.applyAuth ?? defaultApplyAuth;
177
- const launch = () => options.actions?.launch?.();
178
- const recheck = () => options.actions?.recheck?.();
179
- const root = el("div", "absolute inset-0 flex flex-col of-hidden bg-base color-base font-sans");
180
- container.append(root);
181
- const paneId = `code-server-editor-${++paneCounter}`;
182
- const editorTarget = el("div", "dcs-frame flex-1 w-full");
183
- let editorUrl = null;
184
- function shell(...children) {
185
- panes.get(paneId)?.unmount();
186
- editorUrl = null;
187
- const center = el("div", "flex-1 flex items-center justify-center p-6");
188
- const card = el("div", "w-full max-w-[560px]");
189
- card.append(...children);
190
- center.append(card);
191
- root.replaceChildren(center);
192
- }
193
- function renderConnecting() {
194
- const wrap = el("div", "flex items-center gap-2.5");
195
- wrap.append(el("div", spinner("color-muted")), el("span", "text-sm color-muted", "Connecting to devframe…"));
196
- shell(wrap);
197
- }
198
- function renderNotInstalled(busy) {
199
- const nodes = [];
200
- nodes.push(eyebrow());
201
- nodes.push(el("h1", "mb-2 text-[1.35rem] font-semibold", "code-server is not installed"));
202
- nodes.push(el("p", "mb-5 text-sm leading-relaxed color-muted", "Install code-server (VS Code in the browser) to open the editor here. Pick whichever fits your setup, then re-check."));
203
- const install = el("div", "flex flex-col gap-3 mb-5");
204
- for (const { label, command } of INSTALL_COMMANDS) {
205
- const row = el("div");
206
- row.append(el("label", "block mb-1 text-xs color-muted", label));
207
- const cmd = el("div", "flex items-center gap-2 px-2.5 py-2 rounded-md border border-base bg-secondary");
208
- cmd.append(el("code", "flex-1 of-x-auto whitespace-nowrap font-mono text-xs color-base", command));
209
- const copy = el("button", button({
210
- variant: "outline",
211
- size: "sm",
212
- class: "shrink-0 text-[11px]"
213
- }), "Copy");
214
- copy.onclick = () => {
215
- navigator.clipboard?.writeText(command).then(() => {
216
- copy.textContent = "Copied";
217
- setTimeout(() => {
218
- copy.textContent = "Copy";
219
- }, 1200);
220
- }).catch(() => {});
221
- };
222
- cmd.append(copy);
223
- row.append(cmd);
224
- install.append(row);
225
- }
226
- nodes.push(install);
227
- const actions = el("div", "flex flex-wrap items-center gap-2.5");
228
- const recheckBtn = el("button", button({ variant: "primary" }));
229
- recheckBtn.append(icon("i-ph-arrows-clockwise", cx("size-4", busy && "animate-spin")), document.createTextNode(busy ? "Checking…" : "Re-check"));
230
- recheckBtn.disabled = busy;
231
- recheckBtn.onclick = recheck;
232
- actions.append(recheckBtn);
233
- actions.append(link$1("Installation docs", DOCS_URL));
234
- actions.append(link$1("GitHub", REPO_URL));
235
- nodes.push(actions);
236
- shell(...nodes);
237
- }
238
- function renderLaunch(state) {
239
- const { detection, server, busy } = state;
240
- const nodes = [];
241
- nodes.push(eyebrow());
242
- nodes.push(el("h1", "mb-2 text-[1.35rem] font-semibold", "Launch the editor"));
243
- nodes.push(el("p", "mb-5 text-sm leading-relaxed color-muted", "Start a code-server instance scoped to this workspace and open VS Code right here. The server runs with a generated password and the editor is signed in automatically."));
244
- if (server.status === "error" && server.error) nodes.push(el("div", "mb-4 px-3 py-2.5 rounded-md border border-error/35 bg-error/10 text-error text-sm whitespace-pre-wrap break-words", server.error));
245
- const meta = el("div", "flex flex-wrap gap-x-4 gap-y-1.5 mb-5 text-xs color-muted");
246
- if (detection.version) {
247
- const v = el("span");
248
- v.append(document.createTextNode("version "), el("code", "font-mono color-base", detection.version));
249
- meta.append(v);
250
- }
251
- const b = el("span");
252
- b.append(document.createTextNode("binary "), el("code", "font-mono color-base", detection.bin));
253
- meta.append(b);
254
- nodes.push(meta);
255
- const actions = el("div", "flex flex-wrap items-center gap-2.5");
256
- const launchBtn = el("button", button({ variant: "primary" }));
257
- if (busy) launchBtn.append(el("span", spinner()), document.createTextNode("Starting…"));
258
- else launchBtn.append(icon("i-ph-rocket-launch-duotone", "size-4"), document.createTextNode("Launch code-server"));
259
- launchBtn.disabled = busy ?? false;
260
- launchBtn.onclick = launch;
261
- actions.append(launchBtn);
262
- nodes.push(actions);
263
- shell(...nodes);
264
- }
265
- function renderStarting() {
266
- const wrap = el("div", "flex items-center gap-2.5");
267
- wrap.append(el("div", spinner("color-muted")), el("span", "text-sm color-muted", "Starting code-server…"));
268
- shell(wrap);
269
- }
270
- function renderRunning(state) {
271
- const url = resolveEditorUrl(state.server.port);
272
- if (editorUrl !== url) {
273
- if (state.auth) applyAuth(state.auth);
274
- editorUrl = url;
275
- }
276
- const pane = panes.ensure(paneId, {
277
- src: url,
278
- attrs: { allow: "clipboard-read; clipboard-write; cross-origin-isolated" },
279
- style: { border: "0" }
280
- });
281
- if (pane.iframe.src !== url) pane.iframe.src = url;
282
- if (root.firstElementChild !== editorTarget) root.replaceChildren(editorTarget);
283
- pane.mount(editorTarget);
284
- }
285
- function link$1(text, href) {
286
- const a = el("a", link("inline-flex items-center gap-1 text-sm"));
287
- a.append(document.createTextNode(text), icon("i-ph-arrow-square-out", "size-3.5"));
288
- a.href = href;
289
- a.target = "_blank";
290
- a.rel = "noreferrer";
291
- return a;
292
- }
293
- function update(state) {
294
- switch (resolvePhase(state)) {
295
- case "running":
296
- renderRunning(state);
297
- break;
298
- case "starting":
299
- renderStarting();
300
- break;
301
- case "connecting":
302
- renderConnecting();
303
- break;
304
- case "not-installed":
305
- renderNotInstalled(state.busy ?? false);
306
- break;
307
- case "launch":
308
- renderLaunch(state);
309
- break;
310
- }
311
- }
312
- return {
313
- update,
314
- dispose() {
315
- panes.get(paneId)?.dispose();
316
- root.remove();
317
- }
318
- };
319
- }
320
- //#endregion
321
11
  //#region src/client/index.ts
322
12
  /**
323
- * Mount the code-server launcher into `container`. Connects to the devframe
324
- * backend, then drives a {@link createCodeServerView} from the live
325
- * detection/server state: install instructions when the binary is missing, a
326
- * launch screen when stopped, a progress state while starting, and the editor
327
- * in a full-bleed, auto-authenticated iframe once running.
328
- *
329
- * The connection is assumed already authorized with devframe's auth, so the
330
- * server-issued session cookie is applied transparently before the iframe
331
- * loads — the user never sees code-server's login page.
13
+ * Connect to the code-server plugin's devframe backend. A thin, typed wrapper
14
+ * around devframe's {@link connectDevframe}; the SPA derives its base from
15
+ * `document.baseURI`, so no options are required in the common case.
332
16
  */
333
- async function mountCodeServer(container, options = {}) {
334
- const rpc = options.rpc ?? await connectDevframe();
335
- if (rpc.connectionMeta.backend === "websocket") await rpc.ensureTrusted(5e3).catch(() => {});
336
- if (!container.style.position) container.style.position = "relative";
337
- const connectionState = createConnectionState(container);
338
- const syncConnection = () => {
339
- connectionState.update(rpc.status, rpc.connectionError?.message);
340
- };
341
- syncConnection();
342
- const offConnection = rpc.events.on("connection:status", syncConnection);
343
- let detection = {
344
- checked: false,
345
- installed: false,
346
- bin: "code-server"
347
- };
348
- let server = { status: "stopped" };
349
- let auth;
350
- let busy = false;
351
- let disposed = false;
352
- const view = createCodeServerView(container, { actions: {
353
- launch,
354
- recheck
355
- } });
356
- function sync() {
357
- if (disposed) return;
358
- view.update({
359
- detection,
360
- server,
361
- auth,
362
- busy
363
- });
364
- }
365
- function applyResult(result) {
366
- detection = result.detection;
367
- server = result.server;
368
- if (result.auth) auth = result.auth;
369
- }
370
- async function call(method, ...args) {
371
- try {
372
- return await rpc.call(method, ...args);
373
- } catch (error) {
374
- server = {
375
- status: "error",
376
- error: error instanceof Error ? error.message : String(error)
377
- };
378
- return;
379
- }
380
- }
381
- async function launch() {
382
- if (busy) return;
383
- busy = true;
384
- sync();
385
- const result = await call("devframes:plugin:code-server:start", {});
386
- if (result) applyResult(result);
387
- busy = false;
388
- sync();
389
- }
390
- async function stop() {
391
- if (busy) return;
392
- busy = true;
393
- sync();
394
- const result = await call("devframes:plugin:code-server:stop");
395
- if (result) applyResult(result);
396
- auth = void 0;
397
- busy = false;
398
- sync();
399
- }
400
- async function recheck() {
401
- if (busy) return;
402
- busy = true;
403
- sync();
404
- await call("devframes:plugin:code-server:detect");
405
- busy = false;
406
- sync();
407
- }
408
- sync();
409
- const initial = await call("devframes:plugin:code-server:status");
410
- if (initial) applyResult(initial);
411
- sync();
412
- const state = await rpc.sharedState.get(STATE_KEY, { initialValue: {
413
- detection,
414
- server
415
- } });
416
- const current = state.value();
417
- detection = current.detection ?? detection;
418
- server = current.server ?? server;
419
- const off = state.on("updated", (full) => {
420
- detection = full.detection ?? detection;
421
- server = full.server ?? server;
422
- if (server.status === "running" && !auth) {
423
- call("devframes:plugin:code-server:status").then((result) => {
424
- if (result?.auth) auth = result.auth;
425
- sync();
426
- });
427
- return;
428
- }
429
- sync();
430
- });
431
- sync();
432
- return {
433
- rpc,
434
- stop,
435
- dispose() {
436
- disposed = true;
437
- off?.();
438
- offConnection?.();
439
- connectionState.dispose();
440
- view.dispose();
441
- }
442
- };
17
+ function connectCodeServer(options) {
18
+ return connectDevframe(options);
443
19
  }
444
20
  //#endregion
445
- export { STATE_KEY, createCodeServerView, mountCodeServer, resolvePhase };
21
+ export { STATE_KEY, connectCodeServer };
@@ -3,16 +3,16 @@
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. The authentication cookie is never published here — it
7
- * is returned only from the `start` / `status` RPCs to the already-authorized
8
- * client (see {@link CodeServerAuth}).
6
+ * detection result. Authentication material is never published here — it is
7
+ * returned only from the `start` / `status` RPCs to the already-authorized
8
+ * client (see {@link import('./types').CodeServerConnect}).
9
9
  */
10
10
  declare const STATE_KEY = "devframes:plugin:code-server:state";
11
11
  /** Default dev-server port for the plugin's own launcher SPA (standalone CLI). */
12
12
  declare const DEFAULT_PORT = 9013;
13
- /** Preferred port for the spawned code-server process (falls back if taken). */
13
+ /** Preferred port for the spawned editor process (falls back if taken). */
14
14
  declare const DEFAULT_CODE_SERVER_PORT = 8080;
15
- /** How long to wait for code-server to answer its `/healthz` probe. */
15
+ /** How long to wait for the editor to answer its readiness probe. */
16
16
  declare const DEFAULT_START_TIMEOUT = 30000;
17
17
  /** code-server's session cookie base name (see `getCookieSessionName`). */
18
18
  declare const SESSION_COOKIE_BASE = "code-server-session";