coderaft 0.0.41 → 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 +33 -8
- package/code.mjs +1 -1
- package/code.tar.zst +0 -0
- package/dist/_chunks/_args.mjs +22 -2
- package/dist/_chunks/extensions.mjs +196 -0
- package/dist/_chunks/server.mjs +4 -164
- package/dist/cli.mjs +14 -1
- package/dist/index.d.mts +39 -1
- package/dist/index.mjs +3 -2
- package/install.mjs +20 -6
- package/package.json +15 -15
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 --
|
|
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
|
-
| `--
|
|
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 = "
|
|
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
|
package/dist/_chunks/_args.mjs
CHANGED
|
@@ -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
|
-
--
|
|
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 };
|
package/dist/_chunks/server.mjs
CHANGED
|
@@ -1,169 +1,12 @@
|
|
|
1
|
+
import { __exportAll, ensureExtensions, resolveExtensionDirs } from "./extensions.mjs";
|
|
1
2
|
import { createProxyServer } from "./libs/httpxy.mjs";
|
|
2
|
-
import {
|
|
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
|
|
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,
|
|
354
|
+
export { createCodeServer, server_exports, startCodeServer };
|
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["
|
|
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
|
-
|
|
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 {
|
|
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
|
|
2
|
-
// (`spawnCli`). This runs in a dedicated
|
|
3
|
-
// `process.exit()` once
|
|
4
|
-
// the parent's long-lived server. Config is
|
|
5
|
-
// env var (JSON); progress is logged to
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
57
|
-
"@xterm/addon-image": "^0.10.0-beta.
|
|
58
|
-
"@xterm/addon-ligatures": "^0.11.0-beta.
|
|
59
|
-
"@xterm/addon-progress": "^0.3.0-beta.
|
|
60
|
-
"@xterm/addon-search": "^0.17.0-beta.
|
|
61
|
-
"@xterm/addon-serialize": "^0.15.0-beta.
|
|
62
|
-
"@xterm/addon-unicode11": "^0.10.0-beta.
|
|
63
|
-
"@xterm/addon-webgl": "^0.20.0-beta.
|
|
64
|
-
"@xterm/headless": "^6.1.0-beta.
|
|
65
|
-
"@xterm/xterm": "^6.1.0-beta.
|
|
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.
|
|
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": "^
|
|
87
|
+
"zod": "^4.4.3"
|
|
88
88
|
}
|
|
89
89
|
}
|