coderaft 0.0.39 → 0.0.42

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
@@ -186,7 +186,7 @@ const instance = await startCodeServer({
186
186
  Works with `createCodeServer`, `startCodeServer`, and `spawnCodeServer`, and on the CLI:
187
187
 
188
188
  ```sh
189
- coderaft --install-extension esbenp.prettier-vscode --install-extension dbaeumer.vscode-eslint
189
+ coderaft --preinstall-extension esbenp.prettier-vscode --preinstall-extension dbaeumer.vscode-eslint
190
190
  ```
191
191
 
192
192
  - **Idempotent** — already-installed extensions are skipped, so warm restarts pay no cost. Installs run only when something is missing.
@@ -196,6 +196,18 @@ coderaft --install-extension esbenp.prettier-vscode --install-extension dbaeumer
196
196
  > [!NOTE]
197
197
  > Installation runs in a short-lived child process before the server boots (VS Code's extension CLI calls `process.exit()` when done). The first cold start with new extensions takes ~1s longer per extension; subsequent starts are instant.
198
198
 
199
+ ### Managing extensions without starting a server
200
+
201
+ `--install-extension`, `--uninstall-extension`, and `--list-extensions` behave like the `code` CLI: they act on the extensions dir, print the result, and exit. That is what tooling which shells out to `code --install-extension <id>` (IDE extension installers, provisioning scripts) expects, so coderaft can stand in for `code` on the `PATH`.
202
+
203
+ ```sh
204
+ coderaft --install-extension esbenp.prettier-vscode # add --force to reinstall
205
+ coderaft --list-extensions --show-versions
206
+ coderaft --uninstall-extension esbenp.prettier-vscode
207
+ ```
208
+
209
+ The exit code reflects the outcome — a spec that failed to install exits non-zero. Use `--preinstall-extension` when you want to install _and then_ serve.
210
+
199
211
  ## CLI Options
200
212
 
201
213
  ### Server
@@ -273,15 +285,28 @@ coderaft --install-extension esbenp.prettier-vscode --install-extension dbaeumer
273
285
  | `--disable-update-check` | Disable update check |
274
286
  | `--disable-experiments` | Disable experiments |
275
287
 
288
+ ### Extension Management
289
+
290
+ These run against the extensions dir and exit without starting a server (see [Extensions](#extensions)).
291
+
292
+ | Option | Description |
293
+ | -------------------------------- | ----------------------------------------------------- |
294
+ | `--install-extension <ext-id>` | Install an extension from Open VSX (repeatable) |
295
+ | `--uninstall-extension <ext-id>` | Uninstall an extension (repeatable) |
296
+ | `--list-extensions` | List installed extensions |
297
+ | `--show-versions` | Show versions in `--list-extensions` output |
298
+ | `--force` | Reinstall even if the extension is already installed |
299
+ | `--pre-release` | Install the pre-release version when one is available |
300
+
276
301
  ### Features
277
302
 
278
- | Option | Description |
279
- | ------------------------------------ | --------------------------------------------------------------------------------- |
280
- | `--enable-sync` | Enable settings sync |
281
- | `--install-extension <ext-id>` | Preinstall an extension from Open VSX (repeatable, see [Extensions](#extensions)) |
282
- | `--enable-proposed-api <ext-id>` | Enable proposed API for an extension (repeatable) |
283
- | `--disable-workspace-trust` | Disable workspace trust |
284
- | `--disable-getting-started-override` | Disable getting started override |
303
+ | Option | Description |
304
+ | ------------------------------------ | ------------------------------------------------------------------------------------------- |
305
+ | `--enable-sync` | Enable settings sync |
306
+ | `--preinstall-extension <ext-id>` | Install an extension before starting the server (repeatable, see [Extensions](#extensions)) |
307
+ | `--enable-proposed-api <ext-id>` | Enable proposed API for an extension (repeatable) |
308
+ | `--disable-workspace-trust` | Disable workspace trust |
309
+ | `--disable-getting-started-override` | Disable getting started override |
285
310
 
286
311
  ### Remote
287
312
 
package/code.mjs CHANGED
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  // Auto-updated by scripts/pack.ts
7
- const codeArchiveHash = "eb7d3e0f7d18f82e";
7
+ const codeArchiveHash = "1bd91db31c40505f";
8
8
 
9
9
  const archivePath = fileURLToPath(new URL("./code.tar.zst", import.meta.url));
10
10
 
package/code.tar.zst CHANGED
Binary file
@@ -44,11 +44,23 @@ const cliOptions = {
44
44
  "disable-telemetry": { type: "boolean" },
45
45
  "disable-update-check": { type: "boolean" },
46
46
  "disable-experiments": { type: "boolean" },
47
- "enable-sync": { type: "boolean" },
48
47
  "install-extension": {
49
48
  type: "string",
50
49
  multiple: true
51
50
  },
51
+ "uninstall-extension": {
52
+ type: "string",
53
+ multiple: true
54
+ },
55
+ "list-extensions": { type: "boolean" },
56
+ "show-versions": { type: "boolean" },
57
+ force: { type: "boolean" },
58
+ "pre-release": { type: "boolean" },
59
+ "enable-sync": { type: "boolean" },
60
+ "preinstall-extension": {
61
+ type: "string",
62
+ multiple: true
63
+ },
52
64
  "disable-extensions": { type: "boolean" },
53
65
  "disable-extension": {
54
66
  type: "string",
@@ -184,9 +196,17 @@ const helpText = `
184
196
  --disable-update-check Disable update check
185
197
  --disable-experiments Disable experiments
186
198
 
199
+ Extensions (run and exit, no server):
200
+ --install-extension <ext-id> Install an extension from Open VSX (repeatable)
201
+ --uninstall-extension <ext-id> Uninstall an extension (repeatable)
202
+ --list-extensions List installed extensions
203
+ --show-versions Show versions in --list-extensions output
204
+ --force Reinstall even if already installed
205
+ --pre-release Install the pre-release version when available
206
+
187
207
  Features:
188
208
  --enable-sync Enable settings sync
189
- --install-extension <ext-id> Preinstall an extension from Open VSX before start (repeatable)
209
+ --preinstall-extension <ext-id> Install an extension before starting the server (repeatable)
190
210
  --disable-extensions Disable all installed extensions
191
211
  --disable-extension <ext-id> Disable specific extension (repeatable)
192
212
  --enable-proposed-api <ext-id> Enable proposed API for extension (repeatable)
@@ -0,0 +1,196 @@
1
+ import { createRequire } from "node:module";
2
+ import { fileURLToPath } from "node:url";
3
+ import { readFileSync } from "node:fs";
4
+ import { fork } from "node:child_process";
5
+ import { join } from "node:path";
6
+ var __defProp = Object.defineProperty;
7
+ var __exportAll = (all, no_symbols) => {
8
+ let target = {};
9
+ for (var name in all) __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
13
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
+ return target;
15
+ };
16
+ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
17
+ if (process.platform === "android") {
18
+ if (process.execPath.includes("linker64") || process.execPath.startsWith("/apex/")) {
19
+ const resolved = process.env.TERMUX_EXEC__PROC_SELF_EXE || "/data/data/com.termux/files/usr/bin/node";
20
+ Object.defineProperty(process, "execPath", {
21
+ value: resolved,
22
+ writable: true,
23
+ configurable: true,
24
+ enumerable: true
25
+ });
26
+ }
27
+ const preload = `--require "${fileURLToPath(import.meta.resolve("#android-preload"))}"`;
28
+ process.env.NODE_OPTIONS = process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ${preload}` : preload;
29
+ const _fs = process.getBuiltinModule?.("fs") ?? __require("node:fs");
30
+ const { syncBuiltinESMExports: _syncESM } = process.getBuiltinModule?.("module") ?? __require("node:module");
31
+ const TERMUX_EXEC_LIB = "/data/data/com.termux/files/usr/lib/libtermux-exec.so";
32
+ try {
33
+ _fs.accessSync(TERMUX_EXEC_LIB);
34
+ const _cp = process.getBuiltinModule?.("child_process") ?? __require("node:child_process");
35
+ const _injectLdPreload = (opts) => {
36
+ if (opts && typeof opts === "object" && opts.env) {
37
+ const env = opts.env;
38
+ if (!env.LD_PRELOAD) env.LD_PRELOAD = TERMUX_EXEC_LIB;
39
+ }
40
+ };
41
+ const _spawn = _cp.spawn;
42
+ _cp.spawn = function spawn(...spawnArgs) {
43
+ _injectLdPreload(spawnArgs[2]);
44
+ return _spawn.apply(this, spawnArgs);
45
+ };
46
+ const _fork = _cp.fork;
47
+ _cp.fork = function fork(modulePath, ...rest) {
48
+ for (const arg of rest) if (arg && typeof arg === "object" && !Array.isArray(arg)) {
49
+ _injectLdPreload(arg);
50
+ break;
51
+ }
52
+ return _fork.call(this, modulePath, ...rest);
53
+ };
54
+ _syncESM();
55
+ } catch {}
56
+ if (process.env.PATH) {
57
+ const accessible = process.env.PATH.split(":").filter((dir) => {
58
+ try {
59
+ _fs.accessSync(dir, _fs.constants.R_OK);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ });
65
+ if (accessible.length > 0) process.env.PATH = accessible.join(":");
66
+ }
67
+ const _os = process.getBuiltinModule?.("os") ?? __require("node:os");
68
+ const _crypto = process.getBuiltinModule?.("crypto") ?? __require("node:crypto");
69
+ const BLACKLISTED = /* @__PURE__ */ new Set([
70
+ "00:00:00:00:00:00",
71
+ "ff:ff:ff:ff:ff:ff",
72
+ "ac:de:48:00:11:22"
73
+ ]);
74
+ const original = _os.networkInterfaces;
75
+ _os.networkInterfaces = function networkInterfaces() {
76
+ const ifaces = original.call(_os);
77
+ for (const name in ifaces) for (const info of ifaces[name]) if (info.mac && !BLACKLISTED.has(info.mac)) return ifaces;
78
+ const hash = _crypto.createHash("md5").update(_os.hostname()).digest();
79
+ hash[0] = (hash[0] | 2) & 254;
80
+ ifaces._coderaft = [{
81
+ address: "10.0.0.1",
82
+ netmask: "255.255.255.0",
83
+ family: "IPv4",
84
+ mac: [...hash.subarray(0, 6)].map((b) => b.toString(16).padStart(2, "0")).join(":"),
85
+ internal: false,
86
+ cidr: "10.0.0.1/24"
87
+ }];
88
+ return ifaces;
89
+ };
90
+ _syncESM();
91
+ }
92
+ var extensions_exports = /* @__PURE__ */ __exportAll({
93
+ ensureExtensions: () => ensureExtensions,
94
+ readInstalledExtensions: () => readInstalledExtensions,
95
+ resolveExtensionDirs: () => resolveExtensionDirs,
96
+ runExtensionCommand: () => runExtensionCommand
97
+ });
98
+ const _os = process.getBuiltinModule?.("os") ?? __require("node:os");
99
+ function resolveExtensionDirs(vscode = {}) {
100
+ const serverDataDir = vscode["server-data-dir"] ?? join(_os.homedir(), ".vscode-server-oss");
101
+ return {
102
+ serverDataDir,
103
+ userDataDir: vscode["user-data-dir"] ?? join(serverDataDir, "data"),
104
+ extensionsDir: vscode["extensions-dir"] ?? join(serverDataDir, "extensions")
105
+ };
106
+ }
107
+ function readInstalledExtensions(extensionsDir) {
108
+ try {
109
+ const raw = readFileSync(join(extensionsDir, "extensions.json"), "utf8");
110
+ const entries = JSON.parse(raw);
111
+ return new Set(entries.map((e) => e.identifier?.id?.toLowerCase()).filter((id) => typeof id === "string"));
112
+ } catch {
113
+ return /* @__PURE__ */ new Set();
114
+ }
115
+ }
116
+ function specId(spec) {
117
+ const at = spec.indexOf("@", 1);
118
+ return (at === -1 ? spec : spec.slice(0, at)).toLowerCase();
119
+ }
120
+ function pendingExtensions(specs, extensionsDir) {
121
+ const installed = readInstalledExtensions(extensionsDir);
122
+ return specs.filter((spec) => {
123
+ if (spec.toLowerCase().endsWith(".vsix")) return true;
124
+ return !installed.has(specId(spec));
125
+ });
126
+ }
127
+ async function ensureExtensions(specs, opts) {
128
+ const pending = opts.force ? specs : pendingExtensions(specs, opts.extensionsDir);
129
+ if (pending.length === 0) return;
130
+ console.log(`[coderaft] Installing ${pending.length} extension${pending.length === 1 ? "" : "s"}: ${pending.join(", ")}`);
131
+ await forkCli({
132
+ ids: pending,
133
+ ...opts
134
+ });
135
+ const installed = readInstalledExtensions(opts.extensionsDir);
136
+ for (const spec of pending) {
137
+ if (spec.toLowerCase().endsWith(".vsix")) continue;
138
+ if (!installed.has(specId(spec))) console.warn(`[coderaft] Extension failed to install: ${spec}`);
139
+ }
140
+ }
141
+ async function runExtensionCommand(cmd, dirs) {
142
+ const code = await forkCli({
143
+ ids: cmd.install,
144
+ ...cmd,
145
+ ...dirs
146
+ });
147
+ if (code !== 0 || !cmd.install?.length) return code;
148
+ const installed = readInstalledExtensions(dirs.extensionsDir);
149
+ const failed = cmd.install.filter((spec) => !spec.toLowerCase().endsWith(".vsix") && !installed.has(specId(spec)));
150
+ if (failed.length === 0) return 0;
151
+ console.error(`[coderaft] Extension failed to install: ${failed.join(", ")}`);
152
+ return 1;
153
+ }
154
+ function forkCli(cfg) {
155
+ const installPath = fileURLToPath(import.meta.resolve("#install"));
156
+ return new Promise((resolve, reject) => {
157
+ const child = fork(installPath, {
158
+ stdio: [
159
+ "ignore",
160
+ "pipe",
161
+ "inherit",
162
+ "ipc"
163
+ ],
164
+ env: {
165
+ ...process.env,
166
+ CODERAFT_INSTALL: JSON.stringify({
167
+ ids: cfg.ids,
168
+ uninstall: cfg.uninstall,
169
+ list: cfg.list,
170
+ showVersions: cfg.showVersions,
171
+ extensionsDir: cfg.extensionsDir,
172
+ userDataDir: cfg.userDataDir,
173
+ serverDataDir: cfg.serverDataDir,
174
+ force: cfg.force,
175
+ preRelease: cfg.preRelease
176
+ })
177
+ }
178
+ });
179
+ let buf = "";
180
+ child.stdout?.on("data", (chunk) => {
181
+ buf += chunk.toString();
182
+ const lines = buf.split("\n");
183
+ buf = lines.pop() ?? "";
184
+ for (const line of lines) {
185
+ if (/^\s*(info|debug|trace)\s+\[/.test(line)) continue;
186
+ if (line.trim()) console.log(line);
187
+ }
188
+ });
189
+ child.once("exit", (code, signal) => {
190
+ if (buf.trim() && !/^\s*(info|debug|trace)\s+\[/.test(buf)) console.log(buf);
191
+ resolve(code ?? (signal ? 1 : 0));
192
+ });
193
+ child.once("error", reject);
194
+ });
195
+ }
196
+ export { __exportAll, ensureExtensions, extensions_exports, readInstalledExtensions, resolveExtensionDirs, runExtensionCommand };
@@ -1,169 +1,12 @@
1
+ import { __exportAll, ensureExtensions, resolveExtensionDirs } from "./extensions.mjs";
1
2
  import { createProxyServer } from "./libs/httpxy.mjs";
2
- import { createRequire } from "node:module";
3
- import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import { pathToFileURL } from "node:url";
4
4
  import { createReadStream, readFileSync, readdirSync, unlinkSync } from "node:fs";
5
- import { fork } from "node:child_process";
6
5
  import { randomBytes, randomUUID } from "node:crypto";
7
6
  import { createServer } from "node:http";
8
7
  import { extname, join, normalize, sep } from "node:path";
9
8
  import { stat } from "node:fs/promises";
10
9
  import { loadCode } from "#code";
11
- var __defProp = Object.defineProperty;
12
- var __exportAll = (all, no_symbols) => {
13
- let target = {};
14
- for (var name in all) __defProp(target, name, {
15
- get: all[name],
16
- enumerable: true
17
- });
18
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
19
- return target;
20
- };
21
- var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
22
- if (process.platform === "android") {
23
- if (process.execPath.includes("linker64") || process.execPath.startsWith("/apex/")) {
24
- const resolved = process.env.TERMUX_EXEC__PROC_SELF_EXE || "/data/data/com.termux/files/usr/bin/node";
25
- Object.defineProperty(process, "execPath", {
26
- value: resolved,
27
- writable: true,
28
- configurable: true,
29
- enumerable: true
30
- });
31
- }
32
- const preload = `--require "${fileURLToPath(import.meta.resolve("#android-preload"))}"`;
33
- process.env.NODE_OPTIONS = process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ${preload}` : preload;
34
- const _fs = process.getBuiltinModule?.("fs") ?? __require("node:fs");
35
- const { syncBuiltinESMExports: _syncESM } = process.getBuiltinModule?.("module") ?? __require("node:module");
36
- const TERMUX_EXEC_LIB = "/data/data/com.termux/files/usr/lib/libtermux-exec.so";
37
- try {
38
- _fs.accessSync(TERMUX_EXEC_LIB);
39
- const _cp = process.getBuiltinModule?.("child_process") ?? __require("node:child_process");
40
- const _injectLdPreload = (opts) => {
41
- if (opts && typeof opts === "object" && opts.env) {
42
- const env = opts.env;
43
- if (!env.LD_PRELOAD) env.LD_PRELOAD = TERMUX_EXEC_LIB;
44
- }
45
- };
46
- const _spawn = _cp.spawn;
47
- _cp.spawn = function spawn(...spawnArgs) {
48
- _injectLdPreload(spawnArgs[2]);
49
- return _spawn.apply(this, spawnArgs);
50
- };
51
- const _fork = _cp.fork;
52
- _cp.fork = function fork(modulePath, ...rest) {
53
- for (const arg of rest) if (arg && typeof arg === "object" && !Array.isArray(arg)) {
54
- _injectLdPreload(arg);
55
- break;
56
- }
57
- return _fork.call(this, modulePath, ...rest);
58
- };
59
- _syncESM();
60
- } catch {}
61
- if (process.env.PATH) {
62
- const accessible = process.env.PATH.split(":").filter((dir) => {
63
- try {
64
- _fs.accessSync(dir, _fs.constants.R_OK);
65
- return true;
66
- } catch {
67
- return false;
68
- }
69
- });
70
- if (accessible.length > 0) process.env.PATH = accessible.join(":");
71
- }
72
- const _os = process.getBuiltinModule?.("os") ?? __require("node:os");
73
- const _crypto = process.getBuiltinModule?.("crypto") ?? __require("node:crypto");
74
- const BLACKLISTED = /* @__PURE__ */ new Set([
75
- "00:00:00:00:00:00",
76
- "ff:ff:ff:ff:ff:ff",
77
- "ac:de:48:00:11:22"
78
- ]);
79
- const original = _os.networkInterfaces;
80
- _os.networkInterfaces = function networkInterfaces() {
81
- const ifaces = original.call(_os);
82
- for (const name in ifaces) for (const info of ifaces[name]) if (info.mac && !BLACKLISTED.has(info.mac)) return ifaces;
83
- const hash = _crypto.createHash("md5").update(_os.hostname()).digest();
84
- hash[0] = (hash[0] | 2) & 254;
85
- ifaces._coderaft = [{
86
- address: "10.0.0.1",
87
- netmask: "255.255.255.0",
88
- family: "IPv4",
89
- mac: [...hash.subarray(0, 6)].map((b) => b.toString(16).padStart(2, "0")).join(":"),
90
- internal: false,
91
- cidr: "10.0.0.1/24"
92
- }];
93
- return ifaces;
94
- };
95
- _syncESM();
96
- }
97
- function readInstalledExtensions(extensionsDir) {
98
- try {
99
- const raw = readFileSync(join(extensionsDir, "extensions.json"), "utf8");
100
- const entries = JSON.parse(raw);
101
- return new Set(entries.map((e) => e.identifier?.id?.toLowerCase()).filter((id) => typeof id === "string"));
102
- } catch {
103
- return /* @__PURE__ */ new Set();
104
- }
105
- }
106
- function specId(spec) {
107
- const at = spec.indexOf("@", 1);
108
- return (at === -1 ? spec : spec.slice(0, at)).toLowerCase();
109
- }
110
- function pendingExtensions(specs, extensionsDir) {
111
- const installed = readInstalledExtensions(extensionsDir);
112
- return specs.filter((spec) => {
113
- if (spec.toLowerCase().endsWith(".vsix")) return true;
114
- return !installed.has(specId(spec));
115
- });
116
- }
117
- async function ensureExtensions(specs, opts) {
118
- const pending = opts.force ? specs : pendingExtensions(specs, opts.extensionsDir);
119
- if (pending.length === 0) return;
120
- console.log(`[coderaft] Installing ${pending.length} extension${pending.length === 1 ? "" : "s"}: ${pending.join(", ")}`);
121
- await runInstall(pending, opts);
122
- const installed = readInstalledExtensions(opts.extensionsDir);
123
- for (const spec of pending) {
124
- if (spec.toLowerCase().endsWith(".vsix")) continue;
125
- if (!installed.has(specId(spec))) console.warn(`[coderaft] Extension failed to install: ${spec}`);
126
- }
127
- }
128
- function runInstall(specs, opts) {
129
- const installPath = fileURLToPath(import.meta.resolve("#install"));
130
- return new Promise((resolve, reject) => {
131
- const child = fork(installPath, {
132
- stdio: [
133
- "ignore",
134
- "pipe",
135
- "inherit",
136
- "ipc"
137
- ],
138
- env: {
139
- ...process.env,
140
- CODERAFT_INSTALL: JSON.stringify({
141
- ids: specs,
142
- extensionsDir: opts.extensionsDir,
143
- userDataDir: opts.userDataDir,
144
- serverDataDir: opts.serverDataDir,
145
- force: opts.force,
146
- preRelease: opts.preRelease
147
- })
148
- }
149
- });
150
- let buf = "";
151
- child.stdout?.on("data", (chunk) => {
152
- buf += chunk.toString();
153
- const lines = buf.split("\n");
154
- buf = lines.pop() ?? "";
155
- for (const line of lines) {
156
- if (/^\s*(info|debug|trace)\s+\[/.test(line)) continue;
157
- if (line.trim()) console.log(line);
158
- }
159
- });
160
- child.once("exit", () => {
161
- if (buf.trim() && !/^\s*(info|debug|trace)\s+\[/.test(buf)) console.log(buf);
162
- resolve();
163
- });
164
- child.once("error", reject);
165
- });
166
- }
167
10
  const STATIC_MIME = {
168
11
  ".js": "text/javascript",
169
12
  ".mjs": "text/javascript",
@@ -209,7 +52,6 @@ var server_exports = /* @__PURE__ */ __exportAll({
209
52
  createCodeServer: () => createCodeServer,
210
53
  startCodeServer: () => startCodeServer
211
54
  });
212
- const _os = process.getBuiltinModule?.("os") ?? __require("node:os");
213
55
  const MANIFEST_BODY = JSON.stringify({
214
56
  name: "coderaft",
215
57
  short_name: "coderaft",
@@ -242,9 +84,7 @@ async function createCodeServer(opts = {}) {
242
84
  process.env.NODE_EXEC_PATH ??= process.execPath;
243
85
  if (opts.proxyURI) process.env.VSCODE_PROXY_URI = opts.proxyURI;
244
86
  else if (baseURL) process.env.VSCODE_PROXY_URI ??= `${baseURL}/proxy/{{port}}/`;
245
- const serverDataDir = opts.vscode?.["server-data-dir"] ?? join(_os.homedir(), ".vscode-server-oss");
246
- const userDataDir = opts.vscode?.["user-data-dir"] ?? join(serverDataDir, "data");
247
- const extensionsDir = opts.vscode?.["extensions-dir"] ?? join(serverDataDir, "extensions");
87
+ const { serverDataDir, userDataDir, extensionsDir } = resolveExtensionDirs(opts.vscode);
248
88
  cleanupStaleLocks(userDataDir);
249
89
  watchChildProcessHealth();
250
90
  const { modulesDir } = await loadCode();
@@ -511,4 +351,4 @@ function sendJson(res, status, body) {
511
351
  });
512
352
  res.end(payload);
513
353
  }
514
- export { createCodeServer, ensureExtensions, readInstalledExtensions, server_exports, startCodeServer };
354
+ export { createCodeServer, server_exports, startCodeServer };
package/dist/cli.d.mts CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {}
package/dist/cli.mjs CHANGED
@@ -237,6 +237,19 @@ async function startMain() {
237
237
  const vscode = {};
238
238
  for (const key of vsKeys) if (values[key] !== void 0) vscode[key] = values[key];
239
239
  if (values["logs-path"]) vscode.logsPath = values["logs-path"];
240
+ const install = values["install-extension"];
241
+ const uninstall = values["uninstall-extension"];
242
+ if (install?.length || uninstall?.length || values["list-extensions"]) {
243
+ const { resolveExtensionDirs, runExtensionCommand } = await import("./_chunks/extensions.mjs").then((n) => n.extensions_exports);
244
+ process.exit(await runExtensionCommand({
245
+ install,
246
+ uninstall,
247
+ list: values["list-extensions"],
248
+ showVersions: values["show-versions"],
249
+ force: values.force,
250
+ preRelease: values["pre-release"]
251
+ }, resolveExtensionDirs(vscode)));
252
+ }
240
253
  const dir = positionals[0];
241
254
  if (dir) vscode["disable-workspace-trust"] = true;
242
255
  const opts = {
@@ -247,7 +260,7 @@ async function startMain() {
247
260
  proxyURI: values["proxy-uri"],
248
261
  defaultFolder: dir || values["default-folder"],
249
262
  connectionToken: values["connection-token"] ?? values.token,
250
- extensions: values["install-extension"],
263
+ extensions: values["preinstall-extension"],
251
264
  vscode
252
265
  };
253
266
  const interactive = process.stdout.isTTY && !values["no-tui"];
package/dist/index.d.mts CHANGED
@@ -204,6 +204,20 @@ declare class SpawnedCodeServer extends EventEmitter {
204
204
  reload(): Promise<void>;
205
205
  }
206
206
  declare function spawnCodeServer(opts?: SpawnCodeServerOptions): Promise<SpawnedCodeServer>;
207
+ interface ExtensionDirs {
208
+ /** VS Code server data directory. */
209
+ serverDataDir: string;
210
+ /** VS Code user data directory. */
211
+ userDataDir: string;
212
+ /** Directory extensions are installed into. */
213
+ extensionsDir: string;
214
+ }
215
+ /**
216
+ * Resolve the data directories extension management and the running server must
217
+ * agree on, mirroring VS Code's OSS server defaults
218
+ * (`~/.vscode-server-oss/{,data,extensions}`).
219
+ */
220
+ declare function resolveExtensionDirs(vscode?: VSCodeServerOptions): ExtensionDirs;
207
221
  interface EnsureExtensionsOptions {
208
222
  /** Directory extensions are installed into (must match the running server). */
209
223
  extensionsDir: string;
@@ -230,4 +244,28 @@ declare function readInstalledExtensions(extensionsDir: string): Set<string>;
230
244
  * so a bad id or a gallery outage never blocks startup.
231
245
  */
232
246
  declare function ensureExtensions(specs: string[], opts: EnsureExtensionsOptions): Promise<void>;
233
- export { type CodeServerHandle, type CodeServerHandler, type CreateCodeServerOptions, type EnsureExtensionsOptions, type SpawnCodeServerOptions, type SpawnProcessOptions, SpawnedCodeServer, type StartCodeServerOptions, createCodeServer, ensureExtensions, readInstalledExtensions, spawnCodeServer, startCodeServer };
247
+ interface ExtensionCommand {
248
+ /** Specs to install: gallery id, `id@version`, or a path to a local `.vsix`. */
249
+ install?: string[];
250
+ /** Extension ids to uninstall. */
251
+ uninstall?: string[];
252
+ /** Print the installed extensions to stdout. */
253
+ list?: boolean;
254
+ /** Append `@version` to each id in `list` output. */
255
+ showVersions?: boolean;
256
+ /** Reinstall even if the extension is already present. */
257
+ force?: boolean;
258
+ /** Install pre-release versions when available. */
259
+ preRelease?: boolean;
260
+ }
261
+ /**
262
+ * Run VS Code's own extension CLI to completion and resolve with the exit code
263
+ * to hand back to the shell. This backs the `code`-compatible one-shot commands
264
+ * (`--install-extension`, `--uninstall-extension`, `--list-extensions`), which
265
+ * manage extensions and exit instead of booting a server.
266
+ *
267
+ * A zero exit from the CLI is verified against the extensions manifest, so a
268
+ * spec that silently didn't land still reports failure to the caller.
269
+ */
270
+ declare function runExtensionCommand(cmd: ExtensionCommand, dirs: ExtensionDirs): Promise<number>;
271
+ export { type CodeServerHandle, type CodeServerHandler, type CreateCodeServerOptions, type EnsureExtensionsOptions, type ExtensionCommand, type ExtensionDirs, type SpawnCodeServerOptions, type SpawnProcessOptions, SpawnedCodeServer, type StartCodeServerOptions, createCodeServer, ensureExtensions, readInstalledExtensions, resolveExtensionDirs, runExtensionCommand, spawnCodeServer, startCodeServer };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { createCodeServer, ensureExtensions, readInstalledExtensions, startCodeServer } from "./_chunks/server.mjs";
1
+ import { ensureExtensions, readInstalledExtensions, resolveExtensionDirs, runExtensionCommand } from "./_chunks/extensions.mjs";
2
+ import { createCodeServer, startCodeServer } from "./_chunks/server.mjs";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { fork } from "node:child_process";
4
5
  import { EventEmitter } from "node:events";
@@ -168,4 +169,4 @@ async function terminateChild(proc) {
168
169
  proc.kill("SIGTERM");
169
170
  });
170
171
  }
171
- export { SpawnedCodeServer, createCodeServer, ensureExtensions, readInstalledExtensions, spawnCodeServer, startCodeServer };
172
+ export { SpawnedCodeServer, createCodeServer, ensureExtensions, readInstalledExtensions, resolveExtensionDirs, runExtensionCommand, spawnCodeServer, startCodeServer };
package/install.mjs CHANGED
@@ -1,8 +1,9 @@
1
- // Forked child entry used to preinstall extensions through VS Code's own CLI
2
- // (`spawnCli`). This runs in a dedicated process because `spawnCli` calls
3
- // `process.exit()` once installs settle doing it in-process would tear down
4
- // the parent's long-lived server. Config is passed via the `CODERAFT_INSTALL`
5
- // env var (JSON); progress is logged to stdout/stderr by VS Code itself.
1
+ // Forked child entry used to drive extension management through VS Code's own
2
+ // CLI (`spawnCli`) — install, uninstall, and list. This runs in a dedicated
3
+ // process because `spawnCli` calls `process.exit()` once the command settles
4
+ // doing it in-process would tear down the parent's long-lived server. Config is
5
+ // passed via the `CODERAFT_INSTALL` env var (JSON); progress is logged to
6
+ // stdout/stderr by VS Code itself.
6
7
  import { join } from "node:path";
7
8
  import { pathToFileURL } from "node:url";
8
9
  import { loadCode } from "#code";
@@ -19,12 +20,25 @@ const vsRoot = join(modulesDir, "code-server", "lib", "vscode");
19
20
  const mod = await import(pathToFileURL(join(vsRoot, "out/server-main.js")).href);
20
21
  const serverModule = await mod.loadCodeWithNls();
21
22
 
23
+ // The uninstall path compares every candidate against
24
+ // `productService.defaultChatAgent.extensionId` to keep the built-in chat agent
25
+ // from being removed. code-server's product.json has no `defaultChatAgent`, so
26
+ // the comparison throws before anything is uninstalled. An empty id is a valid
27
+ // operand that no real extension id can equal.
28
+ const _product = globalThis._VSCODE_PRODUCT_JSON;
29
+ if (_product) {
30
+ _product.defaultChatAgent ??= { extensionId: "" };
31
+ }
32
+
22
33
  // `spawnCli` consumes a VS Code NativeParsedArgs object. Extensions resolve
23
34
  // against the gallery baked into the patched server-main.js, which defaults to
24
35
  // Open VSX (https://open-vsx.org/vscode/gallery).
25
36
  await serverModule.spawnCli({
26
37
  _: [],
27
- "install-extension": cfg.ids,
38
+ ...(cfg.ids?.length ? { "install-extension": cfg.ids } : {}),
39
+ ...(cfg.uninstall?.length ? { "uninstall-extension": cfg.uninstall } : {}),
40
+ ...(cfg.list ? { "list-extensions": true } : {}),
41
+ ...(cfg.showVersions ? { "show-versions": true } : {}),
28
42
  "extensions-dir": cfg.extensionsDir,
29
43
  "user-data-dir": cfg.userDataDir,
30
44
  "server-data-dir": cfg.serverDataDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderaft",
3
- "version": "0.0.39",
3
+ "version": "0.0.42",
4
4
  "repository": "pithings/coderaft",
5
5
  "bin": {
6
6
  "coderaft": "./dist/cli.mjs"
@@ -31,14 +31,14 @@
31
31
  "build": "obuild"
32
32
  },
33
33
  "devDependencies": {
34
- "code-server": "https://github.com/coder/code-server/releases/download/v4.130.0/package.tar.gz",
34
+ "code-server": "https://github.com/coder/code-server/releases/download/v4.133.0/package.tar.gz",
35
35
  "@github/copilot": "workspace:*",
36
36
  "@github/copilot-sdk": "workspace:*",
37
37
  "@microsoft/1ds-core-js": "workspace:*",
38
38
  "@microsoft/1ds-post-js": "workspace:*",
39
39
  "@microsoft/mxc-sdk": "0.6.1",
40
40
  "@parcel/watcher": "workspace:*",
41
- "@vscode/copilot-api": "^0.4.2",
41
+ "@vscode/copilot-api": "^0.5.2",
42
42
  "@vscode/deviceid": "workspace:*",
43
43
  "@vscode/fs-copyfile": "workspace:*",
44
44
  "@vscode/iconv-lite-umd": "0.7.1",
@@ -53,16 +53,16 @@
53
53
  "@vscode/vscode-languagedetection": "1.0.23",
54
54
  "@vscode/windows-process-tree": "workspace:*",
55
55
  "@vscode/windows-registry": "workspace:*",
56
- "@xterm/addon-clipboard": "^0.3.0-beta.288",
57
- "@xterm/addon-image": "^0.10.0-beta.288",
58
- "@xterm/addon-ligatures": "^0.11.0-beta.288",
59
- "@xterm/addon-progress": "^0.3.0-beta.288",
60
- "@xterm/addon-search": "^0.17.0-beta.288",
61
- "@xterm/addon-serialize": "^0.15.0-beta.288",
62
- "@xterm/addon-unicode11": "^0.10.0-beta.288",
63
- "@xterm/addon-webgl": "^0.20.0-beta.287",
64
- "@xterm/headless": "^6.1.0-beta.288",
65
- "@xterm/xterm": "^6.1.0-beta.288",
56
+ "@xterm/addon-clipboard": "^0.3.0-beta.292",
57
+ "@xterm/addon-image": "^0.10.0-beta.292",
58
+ "@xterm/addon-ligatures": "^0.11.0-beta.292",
59
+ "@xterm/addon-progress": "^0.3.0-beta.292",
60
+ "@xterm/addon-search": "^0.17.0-beta.292",
61
+ "@xterm/addon-serialize": "^0.15.0-beta.292",
62
+ "@xterm/addon-unicode11": "^0.10.0-beta.292",
63
+ "@xterm/addon-webgl": "^0.20.0-beta.291",
64
+ "@xterm/headless": "^6.1.0-beta.292",
65
+ "@xterm/xterm": "^6.1.0-beta.292",
66
66
  "cookie": "^0.7.0",
67
67
  "detect-libc": "^2.1.2",
68
68
  "http-proxy-agent": "^7.0.0",
@@ -75,7 +75,7 @@
75
75
  "node-addon-api": "^6.0.0",
76
76
  "node-pty": "workspace:*",
77
77
  "ssh2": "workspace:*",
78
- "tar": "^7.5.9",
78
+ "tar": "^7.5.20",
79
79
  "tas-client": "0.3.1",
80
80
  "typescript": "^6.0.3",
81
81
  "vscode-oniguruma": "1.7.0",
@@ -84,6 +84,6 @@
84
84
  "ws": "^8.19.0",
85
85
  "yauzl": "^3.3.1",
86
86
  "yazl": "^2.4.3",
87
- "zod": "^3.25.76"
87
+ "zod": "^4.4.3"
88
88
  }
89
89
  }