coderaft 0.0.31 → 0.0.32
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 +74 -13
- package/code.mjs +1 -1
- package/code.tar.zst +0 -0
- package/dist/_chunks/_args.mjs +7 -0
- package/dist/_chunks/libs/httpxy.mjs +2 -2
- package/dist/_chunks/server.mjs +97 -4
- package/dist/cli.mjs +4 -2
- package/dist/index.d.mts +48 -1
- package/dist/index.mjs +2 -2
- package/install.mjs +33 -0
- package/package.json +3 -1
- package/dist/_chunks/rolldown-runtime.mjs +0 -13
package/README.md
CHANGED
|
@@ -100,6 +100,9 @@ Creates a code-server handler without binding to a port.
|
|
|
100
100
|
| `defaultFolder` | `string` | Workspace folder opened when no input is given in the URL. |
|
|
101
101
|
| `connectionToken` | `string` | Shared auth secret. Disabled for localhost, auto-generated for remote hosts. |
|
|
102
102
|
| `host` | `string` | Host/interface to bind. Used to infer whether to require a token. |
|
|
103
|
+
| `baseURL` | `string` | Base URL the server is mounted under (e.g. `/code`). Defaults to `/`. |
|
|
104
|
+
| `proxyURI` | `string` | Proxy URI template for forwarded ports (see [Port Proxy](#port-proxy)). |
|
|
105
|
+
| `extensions` | `string[]` | Extensions to preinstall before start (see [Extensions](#extensions)). |
|
|
103
106
|
| `vscode` | `VSCodeServerOptions` | Extra options forwarded to VS Code's internal `createServer()`. |
|
|
104
107
|
|
|
105
108
|
Returns a `CodeServerHandler`:
|
|
@@ -163,17 +166,48 @@ await instance.close();
|
|
|
163
166
|
|
|
164
167
|
Options cross an IPC boundary, so nested values (`vscode`, etc.) must be JSON-compatible.
|
|
165
168
|
|
|
169
|
+
## Extensions
|
|
170
|
+
|
|
171
|
+
Preinstall extensions when an instance starts by passing an `extensions` array. Ids resolve from the [Open VSX](https://open-vsx.org) registry by default — the same gallery the in-editor Extensions view uses.
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { startCodeServer } from "coderaft";
|
|
175
|
+
|
|
176
|
+
const instance = await startCodeServer({
|
|
177
|
+
defaultFolder: "/path/to/workspace",
|
|
178
|
+
extensions: [
|
|
179
|
+
"esbenp.prettier-vscode", // gallery id
|
|
180
|
+
"dbaeumer.vscode-eslint@3.0.24", // pin a version
|
|
181
|
+
"/abs/path/to/local.vsix", // local .vsix file
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Works with `createCodeServer`, `startCodeServer`, and `spawnCodeServer`, and on the CLI:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
coderaft --install-extension esbenp.prettier-vscode --install-extension dbaeumer.vscode-eslint
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- **Idempotent** — already-installed extensions are skipped, so warm restarts pay no cost. Installs run only when something is missing.
|
|
193
|
+
- **Non-blocking on failure** — a bad id or a gallery outage is logged as a warning; the server still starts.
|
|
194
|
+
- Installs into the server's extensions dir (`--extensions-dir`, default `~/.vscode-server-oss/extensions`), so they persist across restarts.
|
|
195
|
+
|
|
196
|
+
> [!NOTE]
|
|
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
|
+
|
|
166
199
|
## CLI Options
|
|
167
200
|
|
|
168
201
|
### Server
|
|
169
202
|
|
|
170
|
-
| Option | Description
|
|
171
|
-
| ----------------------------- |
|
|
172
|
-
| `-p, --port <port>` | Port to listen on (default: `$PORT` or `6063`)
|
|
173
|
-
| `-H, --host <host>` | Host/interface to bind
|
|
174
|
-
| `--base-url <path>` | Base URL the server is mounted under (default: `/`)
|
|
175
|
-
| `--
|
|
176
|
-
| `--
|
|
203
|
+
| Option | Description |
|
|
204
|
+
| ----------------------------- | ---------------------------------------------------------------------- |
|
|
205
|
+
| `-p, --port <port>` | Port to listen on (default: `$PORT` or `6063`) |
|
|
206
|
+
| `-H, --host <host>` | Host/interface to bind |
|
|
207
|
+
| `--base-url <path>` | Base URL the server is mounted under (default: `/`) |
|
|
208
|
+
| `--proxy-uri <template>` | Proxy URI template for forwarded ports (see [Port Proxy](#port-proxy)) |
|
|
209
|
+
| `--socket-path <path>` | Path to a socket file to listen on |
|
|
210
|
+
| `--print-startup-performance` | Print startup timing to stdout |
|
|
177
211
|
|
|
178
212
|
### Auth
|
|
179
213
|
|
|
@@ -241,12 +275,13 @@ Options cross an IPC boundary, so nested values (`vscode`, etc.) must be JSON-co
|
|
|
241
275
|
|
|
242
276
|
### Features
|
|
243
277
|
|
|
244
|
-
| Option | Description
|
|
245
|
-
| ------------------------------------ |
|
|
246
|
-
| `--enable-sync` | Enable settings sync
|
|
247
|
-
| `--
|
|
248
|
-
| `--
|
|
249
|
-
| `--disable-
|
|
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 |
|
|
250
285
|
|
|
251
286
|
### Remote
|
|
252
287
|
|
|
@@ -291,6 +326,32 @@ Options cross an IPC boundary, so nested values (`vscode`, etc.) must be JSON-co
|
|
|
291
326
|
| `--crash-reporter-directory <dir>` | Crash reporter directory |
|
|
292
327
|
| `--crash-reporter-id <id>` | Crash reporter ID |
|
|
293
328
|
|
|
329
|
+
## Port Proxy
|
|
330
|
+
|
|
331
|
+
When a VS Code extension or the terminal opens a local URL (e.g. `http://localhost:3000`), coderaft proxies it through the browser. By default, ports are proxied via a path-based scheme:
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
https://example.com/proxy/3000/
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
When using `--base-url`, the base path is automatically prepended:
|
|
338
|
+
|
|
339
|
+
```sh
|
|
340
|
+
coderaft --base-url /code
|
|
341
|
+
# → https://example.com/code/proxy/3000/
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
For subdomain-based proxying, use `--proxy-uri` with a `{{port}}` placeholder:
|
|
345
|
+
|
|
346
|
+
```sh
|
|
347
|
+
coderaft --proxy-uri "https://{{port}}.proxy.example.com/"
|
|
348
|
+
# → https://3000.proxy.example.com/
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Subdomain proxying requires external infrastructure (wildcard DNS + reverse proxy) — coderaft's built-in proxy handler only supports path-based routing.
|
|
352
|
+
|
|
353
|
+
The `VSCODE_PROXY_URI` environment variable can also be used and takes the same template format.
|
|
354
|
+
|
|
294
355
|
## Sponsors
|
|
295
356
|
|
|
296
357
|
<p align="center">
|
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 = "02cc4256d084fb3a";
|
|
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
|
@@ -8,6 +8,7 @@ const cliOptions = {
|
|
|
8
8
|
short: "H"
|
|
9
9
|
},
|
|
10
10
|
"base-url": { type: "string" },
|
|
11
|
+
"proxy-uri": { type: "string" },
|
|
11
12
|
"server-base-path": { type: "string" },
|
|
12
13
|
"socket-path": { type: "string" },
|
|
13
14
|
"print-startup-performance": { type: "boolean" },
|
|
@@ -44,6 +45,10 @@ const cliOptions = {
|
|
|
44
45
|
"disable-update-check": { type: "boolean" },
|
|
45
46
|
"disable-experiments": { type: "boolean" },
|
|
46
47
|
"enable-sync": { type: "boolean" },
|
|
48
|
+
"install-extension": {
|
|
49
|
+
type: "string",
|
|
50
|
+
multiple: true
|
|
51
|
+
},
|
|
47
52
|
"disable-extensions": { type: "boolean" },
|
|
48
53
|
"disable-extension": {
|
|
49
54
|
type: "string",
|
|
@@ -135,6 +140,7 @@ const helpText = `
|
|
|
135
140
|
-p, --port <port> Port to listen on (default: $PORT or 6063)
|
|
136
141
|
-H, --host <host> Host/interface to bind
|
|
137
142
|
--base-url <path> Base URL the server is mounted under (default: /)
|
|
143
|
+
--proxy-uri <template> Proxy URI template ({{port}} is replaced with port number)
|
|
138
144
|
--socket-path <path> Path to a socket file to listen on
|
|
139
145
|
--print-startup-performance Print startup timing to stdout
|
|
140
146
|
|
|
@@ -180,6 +186,7 @@ const helpText = `
|
|
|
180
186
|
|
|
181
187
|
Features:
|
|
182
188
|
--enable-sync Enable settings sync
|
|
189
|
+
--install-extension <ext-id> Preinstall an extension from Open VSX before start (repeatable)
|
|
183
190
|
--disable-extensions Disable all installed extensions
|
|
184
191
|
--disable-extension <ext-id> Disable specific extension (repeatable)
|
|
185
192
|
--enable-proposed-api <ext-id> Enable proposed API for extension (repeatable)
|
|
@@ -80,7 +80,7 @@ function setupOutgoing(outgoing, options, req, forward) {
|
|
|
80
80
|
}
|
|
81
81
|
function joinURL(base, path) {
|
|
82
82
|
if (!base || base === "/") return path || "/";
|
|
83
|
-
if (!path) return base;
|
|
83
|
+
if (!path || path === "/") return base || "/";
|
|
84
84
|
const baseHasTrailing = base[base.length - 1] === "/";
|
|
85
85
|
const pathHasLeading = path[0] === "/";
|
|
86
86
|
if (baseHasTrailing && pathHasLeading) return base + path.slice(1);
|
|
@@ -562,4 +562,4 @@ function _createProxyFn(type, server) {
|
|
|
562
562
|
return callbackPromise;
|
|
563
563
|
};
|
|
564
564
|
}
|
|
565
|
-
export { createProxyServer
|
|
565
|
+
export { createProxyServer };
|
package/dist/_chunks/server.mjs
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createProxyServer } from "./libs/httpxy.mjs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
4
|
import { createReadStream, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
5
|
+
import { fork } from "node:child_process";
|
|
5
6
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
6
7
|
import { createServer } from "node:http";
|
|
7
8
|
import { extname, join, normalize, sep } from "node:path";
|
|
8
9
|
import { stat } from "node:fs/promises";
|
|
9
10
|
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);
|
|
10
22
|
if (process.platform === "android") {
|
|
11
23
|
if (process.execPath.includes("linker64") || process.execPath.startsWith("/apex/")) {
|
|
12
24
|
const resolved = process.env.TERMUX_EXEC__PROC_SELF_EXE || "/data/data/com.termux/files/usr/bin/node";
|
|
@@ -82,6 +94,76 @@ if (process.platform === "android") {
|
|
|
82
94
|
};
|
|
83
95
|
_syncESM();
|
|
84
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
|
+
}
|
|
85
167
|
const STATIC_MIME = {
|
|
86
168
|
".js": "text/javascript",
|
|
87
169
|
".mjs": "text/javascript",
|
|
@@ -158,9 +240,19 @@ async function createCodeServer(opts = {}) {
|
|
|
158
240
|
const mintKey = randomBytes(32);
|
|
159
241
|
process.env.CODE_SERVER_PARENT_PID ??= String(process.pid);
|
|
160
242
|
process.env.NODE_EXEC_PATH ??= process.execPath;
|
|
161
|
-
|
|
243
|
+
if (opts.proxyURI) process.env.VSCODE_PROXY_URI = opts.proxyURI;
|
|
244
|
+
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");
|
|
248
|
+
cleanupStaleLocks(userDataDir);
|
|
162
249
|
watchChildProcessHealth();
|
|
163
250
|
const { modulesDir } = await loadCode();
|
|
251
|
+
if (opts.extensions?.length) await ensureExtensions(opts.extensions, {
|
|
252
|
+
extensionsDir,
|
|
253
|
+
userDataDir,
|
|
254
|
+
serverDataDir
|
|
255
|
+
});
|
|
164
256
|
const vsRootPath = join(modulesDir, "code-server", "lib", "vscode");
|
|
165
257
|
const _log = console.log;
|
|
166
258
|
console.log = (...args) => {
|
|
@@ -177,6 +269,7 @@ async function createCodeServer(opts = {}) {
|
|
|
177
269
|
}
|
|
178
270
|
const vscodeServer = await (await mod.loadCodeWithNls()).createServer(null, {
|
|
179
271
|
"default-folder": defaultFolder,
|
|
272
|
+
"extensions-dir": extensionsDir,
|
|
180
273
|
...baseURL ? { "server-base-path": baseURL } : {},
|
|
181
274
|
...withoutToken ? { "without-connection-token": true } : { "connection-token": connectionToken },
|
|
182
275
|
"reconnection-grace-time": "30",
|
|
@@ -418,4 +511,4 @@ function sendJson(res, status, body) {
|
|
|
418
511
|
});
|
|
419
512
|
res.end(payload);
|
|
420
513
|
}
|
|
421
|
-
export {
|
|
514
|
+
export { createCodeServer, ensureExtensions, readInstalledExtensions, server_exports, startCodeServer };
|
package/dist/cli.mjs
CHANGED
|
@@ -210,7 +210,7 @@ else startMain();
|
|
|
210
210
|
function startWorker() {
|
|
211
211
|
process.on("message", async (msg) => {
|
|
212
212
|
if (msg.type === "start") {
|
|
213
|
-
const { startCodeServer } = await import("./_chunks/server.mjs").then((n) => n.
|
|
213
|
+
const { startCodeServer } = await import("./_chunks/server.mjs").then((n) => n.server_exports);
|
|
214
214
|
const handle = await startCodeServer(msg.opts);
|
|
215
215
|
process.send({
|
|
216
216
|
type: "ready",
|
|
@@ -244,8 +244,10 @@ async function startMain() {
|
|
|
244
244
|
host: values.host,
|
|
245
245
|
socketPath: values["socket-path"],
|
|
246
246
|
baseURL: values["base-url"] ?? values["server-base-path"],
|
|
247
|
+
proxyURI: values["proxy-uri"],
|
|
247
248
|
defaultFolder: dir || values["default-folder"],
|
|
248
249
|
connectionToken: values["connection-token"] ?? values.token,
|
|
250
|
+
extensions: values["install-extension"],
|
|
249
251
|
vscode
|
|
250
252
|
};
|
|
251
253
|
const interactive = process.stdout.isTTY && !values["no-tui"];
|
|
@@ -257,7 +259,7 @@ async function startMain() {
|
|
|
257
259
|
} });
|
|
258
260
|
process.on("exit", () => tui?.destroy());
|
|
259
261
|
if (values["no-fork"]) {
|
|
260
|
-
const { startCodeServer } = await import("./_chunks/server.mjs").then((n) => n.
|
|
262
|
+
const { startCodeServer } = await import("./_chunks/server.mjs").then((n) => n.server_exports);
|
|
261
263
|
let handle;
|
|
262
264
|
let shuttingDown = false;
|
|
263
265
|
const shutdown = () => {
|
package/dist/index.d.mts
CHANGED
|
@@ -99,6 +99,27 @@ interface CreateCodeServerOptions {
|
|
|
99
99
|
* routes (`/healthz`, `/_static/*`, `/proxy/*`, `/login`, …).
|
|
100
100
|
*/
|
|
101
101
|
baseURL?: string;
|
|
102
|
+
/**
|
|
103
|
+
* URI template for proxied ports. `{{port}}` is replaced with the port number.
|
|
104
|
+
*
|
|
105
|
+
* Path-based (default): `baseURL + "/proxy/{{port}}/"` (e.g. `/code/proxy/3000/`)
|
|
106
|
+
* Subdomain-based: `https://{{port}}.proxy.example.com/`
|
|
107
|
+
*
|
|
108
|
+
* Passed to VS Code as `VSCODE_PROXY_URI`.
|
|
109
|
+
*/
|
|
110
|
+
proxyURI?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Extensions to preinstall before the server boots. Each entry is anything
|
|
113
|
+
* VS Code's CLI accepts: a gallery id (`esbenp.prettier-vscode`), an id pinned
|
|
114
|
+
* to a version (`esbenp.prettier-vscode@12.4.0`), or a path to a local
|
|
115
|
+
* `.vsix`. Gallery ids resolve from Open VSX (https://open-vsx.org) by
|
|
116
|
+
* default.
|
|
117
|
+
*
|
|
118
|
+
* Installation is idempotent — already-installed extensions are skipped, so
|
|
119
|
+
* warm restarts pay no cost. Failures (bad id, gallery outage) are logged and
|
|
120
|
+
* never block startup.
|
|
121
|
+
*/
|
|
122
|
+
extensions?: string[];
|
|
102
123
|
/** Extra options forwarded to VS Code's `createServer()`. */
|
|
103
124
|
vscode?: VSCodeServerOptions;
|
|
104
125
|
}
|
|
@@ -183,4 +204,30 @@ declare class SpawnedCodeServer extends EventEmitter {
|
|
|
183
204
|
reload(): Promise<void>;
|
|
184
205
|
}
|
|
185
206
|
declare function spawnCodeServer(opts?: SpawnCodeServerOptions): Promise<SpawnedCodeServer>;
|
|
186
|
-
|
|
207
|
+
interface EnsureExtensionsOptions {
|
|
208
|
+
/** Directory extensions are installed into (must match the running server). */
|
|
209
|
+
extensionsDir: string;
|
|
210
|
+
/** VS Code user data directory. */
|
|
211
|
+
userDataDir: string;
|
|
212
|
+
/** VS Code server data directory. */
|
|
213
|
+
serverDataDir?: string;
|
|
214
|
+
/** Reinstall even if an extension with the same id is already present. */
|
|
215
|
+
force?: boolean;
|
|
216
|
+
/** Install pre-release versions when available. */
|
|
217
|
+
preRelease?: boolean;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Read the ids of already-installed extensions (lowercased) from the
|
|
221
|
+
* `extensions.json` manifest VS Code maintains in the extensions directory.
|
|
222
|
+
* Returns an empty set when the directory or manifest doesn't exist yet.
|
|
223
|
+
*/
|
|
224
|
+
declare function readInstalledExtensions(extensionsDir: string): Set<string>;
|
|
225
|
+
/**
|
|
226
|
+
* Ensure the given extensions are installed before the server boots. Missing
|
|
227
|
+
* extensions are installed from the gallery (Open VSX by default) in a forked
|
|
228
|
+
* child process — `spawnCli` calls `process.exit()` when done, so it cannot run
|
|
229
|
+
* in the server process. Best-effort: install failures are logged, not thrown,
|
|
230
|
+
* so a bad id or a gallery outage never blocks startup.
|
|
231
|
+
*/
|
|
232
|
+
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createCodeServer, ensureExtensions, readInstalledExtensions, startCodeServer } from "./_chunks/server.mjs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { fork } from "node:child_process";
|
|
4
4
|
import { EventEmitter } from "node:events";
|
|
@@ -168,4 +168,4 @@ async function terminateChild(proc) {
|
|
|
168
168
|
proc.kill("SIGTERM");
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
|
-
export { SpawnedCodeServer, createCodeServer, spawnCodeServer, startCodeServer };
|
|
171
|
+
export { SpawnedCodeServer, createCodeServer, ensureExtensions, readInstalledExtensions, spawnCodeServer, startCodeServer };
|
package/install.mjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
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.
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { loadCode } from "#code";
|
|
9
|
+
|
|
10
|
+
const cfg = JSON.parse(process.env.CODERAFT_INSTALL || "{}");
|
|
11
|
+
|
|
12
|
+
// Suppress server-main.js's standalone auto-boot (same trick as server.ts):
|
|
13
|
+
// the module top-level is `process.env.CODE_SERVER_PARENT_PID || <boot>()`, so
|
|
14
|
+
// a truthy value makes the import side-effect-free and lets us drive the CLI.
|
|
15
|
+
process.env.CODE_SERVER_PARENT_PID ??= String(process.pid);
|
|
16
|
+
|
|
17
|
+
const { modulesDir } = await loadCode();
|
|
18
|
+
const vsRoot = join(modulesDir, "code-server", "lib", "vscode");
|
|
19
|
+
const mod = await import(pathToFileURL(join(vsRoot, "out/server-main.js")).href);
|
|
20
|
+
const serverModule = await mod.loadCodeWithNls();
|
|
21
|
+
|
|
22
|
+
// `spawnCli` consumes a VS Code NativeParsedArgs object. Extensions resolve
|
|
23
|
+
// against the gallery baked into the patched server-main.js, which defaults to
|
|
24
|
+
// Open VSX (https://open-vsx.org/vscode/gallery).
|
|
25
|
+
await serverModule.spawnCli({
|
|
26
|
+
_: [],
|
|
27
|
+
"install-extension": cfg.ids,
|
|
28
|
+
"extensions-dir": cfg.extensionsDir,
|
|
29
|
+
"user-data-dir": cfg.userDataDir,
|
|
30
|
+
"server-data-dir": cfg.serverDataDir,
|
|
31
|
+
...(cfg.force ? { force: true } : {}),
|
|
32
|
+
...(cfg.preRelease ? { "pre-release": true } : {}),
|
|
33
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coderaft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"repository": "pithings/coderaft",
|
|
5
5
|
"bin": {
|
|
6
6
|
"coderaft": "./dist/cli.mjs"
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"tar.mjs",
|
|
13
13
|
"code.mjs",
|
|
14
14
|
"worker.mjs",
|
|
15
|
+
"install.mjs",
|
|
15
16
|
"android-preload.cjs",
|
|
16
17
|
"code.tar.zst"
|
|
17
18
|
],
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
"imports": {
|
|
24
25
|
"#code": "./code.mjs",
|
|
25
26
|
"#worker": "./worker.mjs",
|
|
27
|
+
"#install": "./install.mjs",
|
|
26
28
|
"#android-preload": "./android-preload.cjs"
|
|
27
29
|
},
|
|
28
30
|
"scripts": {
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __exportAll = (all, no_symbols) => {
|
|
4
|
-
let target = {};
|
|
5
|
-
for (var name in all) __defProp(target, name, {
|
|
6
|
-
get: all[name],
|
|
7
|
-
enumerable: true
|
|
8
|
-
});
|
|
9
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
-
return target;
|
|
11
|
-
};
|
|
12
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
13
|
-
export { __require as n, __exportAll as t };
|