coderaft 0.0.16 → 0.0.19
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 +10 -8
- package/code.mjs +1 -1
- package/code.tar.zst +0 -0
- package/dist/_chunks/server.mjs +44 -10
- package/dist/cli.mjs +16 -12
- package/dist/index.d.mts +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,9 +45,11 @@ npx coderaft -o .
|
|
|
45
45
|
### Docker
|
|
46
46
|
|
|
47
47
|
```sh
|
|
48
|
-
docker run -it --rm -p 6063:6063
|
|
48
|
+
docker run -it --rm -p 6063:6063 -v coderaft:/data ghcr.io/pithings/coderaft
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
Based on Alpine with Node.js LTS, bash, corepack (npm/pnpm/yarn) (~65 MB download, ~89 MB final). Data persists in `/data` volume (`/data/workspace` for project files, `/data/home` symlinked to `/root` for configs).
|
|
52
|
+
|
|
51
53
|
## Programmatic
|
|
52
54
|
|
|
53
55
|
```ts
|
|
@@ -137,13 +139,13 @@ interface CodeServerHandle {
|
|
|
137
139
|
|
|
138
140
|
### Server
|
|
139
141
|
|
|
140
|
-
| Option | Description
|
|
141
|
-
| ----------------------------- |
|
|
142
|
-
| `-p, --port <port>` | Port to listen on (default: `$PORT` or `6063`)
|
|
143
|
-
| `-H, --host <host>` | Host/interface to bind
|
|
144
|
-
| `--
|
|
145
|
-
| `--socket-path <path>` | Path to a socket file to listen on
|
|
146
|
-
| `--print-startup-performance` | Print startup timing to stdout
|
|
142
|
+
| Option | Description |
|
|
143
|
+
| ----------------------------- | --------------------------------------------------- |
|
|
144
|
+
| `-p, --port <port>` | Port to listen on (default: `$PORT` or `6063`) |
|
|
145
|
+
| `-H, --host <host>` | Host/interface to bind |
|
|
146
|
+
| `--base-url <path>` | Base URL the server is mounted under (default: `/`) |
|
|
147
|
+
| `--socket-path <path>` | Path to a socket file to listen on |
|
|
148
|
+
| `--print-startup-performance` | Print startup timing to stdout |
|
|
147
149
|
|
|
148
150
|
### Auth
|
|
149
151
|
|
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 = "30c02e4b77df8ee2";
|
|
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/server.mjs
CHANGED
|
@@ -151,6 +151,7 @@ async function createCodeServer(opts = {}) {
|
|
|
151
151
|
const withoutToken = opts.vscode?.["without-connection-token"] === true || !explicitToken && isLocal;
|
|
152
152
|
const connectionToken = withoutToken ? "" : opts.connectionToken ?? randomUUID();
|
|
153
153
|
const defaultFolder = opts.defaultFolder ?? process.cwd();
|
|
154
|
+
const baseURL = normalizeBaseURL(opts.baseURL ?? opts.vscode?.["server-base-path"]);
|
|
154
155
|
const mintKey = randomBytes(32);
|
|
155
156
|
process.env.CODE_SERVER_PARENT_PID ??= String(process.pid);
|
|
156
157
|
cleanupStaleLocks(opts.vscode?.["user-data-dir"] ?? join(_os.homedir(), ".vscode-server-oss", "data"));
|
|
@@ -172,6 +173,7 @@ async function createCodeServer(opts = {}) {
|
|
|
172
173
|
}
|
|
173
174
|
const vscodeServer = await (await mod.loadCodeWithNls()).createServer(null, {
|
|
174
175
|
"default-folder": defaultFolder,
|
|
176
|
+
...baseURL ? { "server-base-path": baseURL } : {},
|
|
175
177
|
...withoutToken ? { "without-connection-token": true } : { "connection-token": connectionToken },
|
|
176
178
|
"reconnection-grace-time": "30",
|
|
177
179
|
"disable-getting-started-override": true,
|
|
@@ -189,7 +191,8 @@ async function createCodeServer(opts = {}) {
|
|
|
189
191
|
connectionToken,
|
|
190
192
|
handleRequest(req, res) {
|
|
191
193
|
const method = req.method ?? "GET";
|
|
192
|
-
const
|
|
194
|
+
const strippedUrl = stripBaseURL(req.url ?? "/", baseURL);
|
|
195
|
+
const url = strippedUrl.split("?")[0];
|
|
193
196
|
if (url === "/manifest.json") {
|
|
194
197
|
res.writeHead(200, {
|
|
195
198
|
"Content-Type": "application/manifest+json",
|
|
@@ -232,7 +235,7 @@ async function createCodeServer(opts = {}) {
|
|
|
232
235
|
return;
|
|
233
236
|
}
|
|
234
237
|
if (url === "/login" || url === "/logout") {
|
|
235
|
-
res.writeHead(302, { Location:
|
|
238
|
+
res.writeHead(302, { Location: `${baseURL}/` });
|
|
236
239
|
res.end();
|
|
237
240
|
return;
|
|
238
241
|
}
|
|
@@ -242,7 +245,7 @@ async function createCodeServer(opts = {}) {
|
|
|
242
245
|
});
|
|
243
246
|
return;
|
|
244
247
|
}
|
|
245
|
-
const proxyMatch = parseProxyPath(
|
|
248
|
+
const proxyMatch = parseProxyPath(strippedUrl);
|
|
246
249
|
if (proxyMatch) {
|
|
247
250
|
const { port: targetPort, path: targetPath } = proxyMatch;
|
|
248
251
|
proxy.web(req, res, {
|
|
@@ -255,7 +258,7 @@ async function createCodeServer(opts = {}) {
|
|
|
255
258
|
vscodeServer.handleRequest(req, res);
|
|
256
259
|
},
|
|
257
260
|
handleUpgrade(req, socket, _head) {
|
|
258
|
-
const proxyMatch = parseProxyPath(req.url ?? "/");
|
|
261
|
+
const proxyMatch = parseProxyPath(stripBaseURL(req.url ?? "/", baseURL));
|
|
259
262
|
if (proxyMatch) {
|
|
260
263
|
const { port: targetPort, path: targetPath } = proxyMatch;
|
|
261
264
|
proxy.ws(req, socket, {
|
|
@@ -273,6 +276,7 @@ async function createCodeServer(opts = {}) {
|
|
|
273
276
|
};
|
|
274
277
|
}
|
|
275
278
|
async function startCodeServer(opts = {}) {
|
|
279
|
+
const socketPath = opts.socketPath;
|
|
276
280
|
const port = opts.port ?? (Number(process.env.PORT) || 6063);
|
|
277
281
|
const handler = await createCodeServer(opts);
|
|
278
282
|
const server = createServer((req, res) => {
|
|
@@ -281,7 +285,7 @@ async function startCodeServer(opts = {}) {
|
|
|
281
285
|
server.on("upgrade", (req, socket, head) => {
|
|
282
286
|
handler.handleUpgrade(req, socket, head);
|
|
283
287
|
});
|
|
284
|
-
const
|
|
288
|
+
const listenTcp = (p) => new Promise((resolve, reject) => {
|
|
285
289
|
server.once("error", reject);
|
|
286
290
|
const cb = () => {
|
|
287
291
|
server.removeListener("error", reject);
|
|
@@ -290,17 +294,31 @@ async function startCodeServer(opts = {}) {
|
|
|
290
294
|
if (opts.host) server.listen(p, opts.host, cb);
|
|
291
295
|
else server.listen(p, cb);
|
|
292
296
|
});
|
|
293
|
-
|
|
294
|
-
|
|
297
|
+
const listenSocket = (path) => new Promise((resolve, reject) => {
|
|
298
|
+
try {
|
|
299
|
+
unlinkSync(path);
|
|
300
|
+
} catch {}
|
|
301
|
+
server.once("error", reject);
|
|
302
|
+
server.listen(path, () => {
|
|
303
|
+
server.removeListener("error", reject);
|
|
304
|
+
resolve();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
if (socketPath) await listenSocket(socketPath);
|
|
308
|
+
else try {
|
|
309
|
+
await listenTcp(port);
|
|
295
310
|
} catch (err) {
|
|
296
|
-
if (err?.code === "EADDRINUSE") await
|
|
311
|
+
if (err?.code === "EADDRINUSE") await listenTcp(0);
|
|
297
312
|
else throw err;
|
|
298
313
|
}
|
|
299
|
-
const
|
|
314
|
+
const address = server.address();
|
|
315
|
+
const actualPort = address && typeof address === "object" && "port" in address ? address.port : void 0;
|
|
316
|
+
const basePath = normalizeBaseURL(opts.baseURL ?? opts.vscode?.["server-base-path"]);
|
|
300
317
|
return {
|
|
301
318
|
server,
|
|
302
319
|
port: actualPort,
|
|
303
|
-
|
|
320
|
+
socketPath,
|
|
321
|
+
url: socketPath ? `unix:${socketPath}` : handler.connectionToken ? `http://localhost:${actualPort}${basePath}/?tkn=${handler.connectionToken}` : `http://localhost:${actualPort}${basePath}/`,
|
|
304
322
|
connectionToken: handler.connectionToken,
|
|
305
323
|
async close() {
|
|
306
324
|
await handler.dispose();
|
|
@@ -308,6 +326,9 @@ async function startCodeServer(opts = {}) {
|
|
|
308
326
|
await new Promise((resolve, reject) => {
|
|
309
327
|
server.close((err) => err ? reject(err) : resolve());
|
|
310
328
|
});
|
|
329
|
+
if (socketPath) try {
|
|
330
|
+
unlinkSync(socketPath);
|
|
331
|
+
} catch {}
|
|
311
332
|
}
|
|
312
333
|
};
|
|
313
334
|
}
|
|
@@ -364,6 +385,19 @@ function parseProxyPath(url) {
|
|
|
364
385
|
path: (match[2] || "/") + query
|
|
365
386
|
};
|
|
366
387
|
}
|
|
388
|
+
function normalizeBaseURL(input) {
|
|
389
|
+
if (!input || input === "/") return "";
|
|
390
|
+
let result = input.trim();
|
|
391
|
+
if (!result.startsWith("/")) result = "/" + result;
|
|
392
|
+
while (result.endsWith("/")) result = result.slice(0, -1);
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
function stripBaseURL(url, baseURL) {
|
|
396
|
+
if (!baseURL) return url;
|
|
397
|
+
if (url === baseURL) return "/";
|
|
398
|
+
if (url.startsWith(baseURL + "/") || url.startsWith(baseURL + "?")) return url.slice(baseURL.length) || "/";
|
|
399
|
+
return url;
|
|
400
|
+
}
|
|
367
401
|
function sendJson(res, status, body) {
|
|
368
402
|
const payload = JSON.stringify(body);
|
|
369
403
|
res.writeHead(status, {
|
package/dist/cli.mjs
CHANGED
|
@@ -12,6 +12,7 @@ const { values, positionals } = parseArgs({
|
|
|
12
12
|
type: "string",
|
|
13
13
|
short: "H"
|
|
14
14
|
},
|
|
15
|
+
"base-url": { type: "string" },
|
|
15
16
|
"server-base-path": { type: "string" },
|
|
16
17
|
"socket-path": { type: "string" },
|
|
17
18
|
"print-startup-performance": { type: "boolean" },
|
|
@@ -92,7 +93,7 @@ if (values.help) {
|
|
|
92
93
|
Server:
|
|
93
94
|
-p, --port <port> Port to listen on (default: $PORT or 6063)
|
|
94
95
|
-H, --host <host> Host/interface to bind
|
|
95
|
-
--
|
|
96
|
+
--base-url <path> Base URL the server is mounted under (default: /)
|
|
96
97
|
--socket-path <path> Path to a socket file to listen on
|
|
97
98
|
--print-startup-performance Print startup timing to stdout
|
|
98
99
|
|
|
@@ -175,7 +176,6 @@ if (values.help) {
|
|
|
175
176
|
const vscode = {};
|
|
176
177
|
for (const key of [
|
|
177
178
|
"server-base-path",
|
|
178
|
-
"socket-path",
|
|
179
179
|
"print-startup-performance",
|
|
180
180
|
"connection-token-file",
|
|
181
181
|
"without-connection-token",
|
|
@@ -224,9 +224,22 @@ for (const key of [
|
|
|
224
224
|
if (values["logs-path"]) vscode.logsPath = values["logs-path"];
|
|
225
225
|
const dir = positionals[0];
|
|
226
226
|
if (dir) vscode["disable-workspace-trust"] = true;
|
|
227
|
-
|
|
227
|
+
let handle;
|
|
228
|
+
let shuttingDown = false;
|
|
229
|
+
const shutdown = () => {
|
|
230
|
+
if (shuttingDown) process.exit(0);
|
|
231
|
+
shuttingDown = true;
|
|
232
|
+
setTimeout(() => process.exit(0), 3e3).unref();
|
|
233
|
+
if (handle) handle.close().finally(() => process.exit(0));
|
|
234
|
+
else process.exit(0);
|
|
235
|
+
};
|
|
236
|
+
process.on("SIGINT", shutdown);
|
|
237
|
+
process.on("SIGTERM", shutdown);
|
|
238
|
+
handle = await startCodeServer({
|
|
228
239
|
port: values.port ? Number(values.port) : void 0,
|
|
229
240
|
host: values.host,
|
|
241
|
+
socketPath: values["socket-path"],
|
|
242
|
+
baseURL: values["base-url"] ?? values["server-base-path"],
|
|
230
243
|
defaultFolder: dir || values["default-folder"],
|
|
231
244
|
connectionToken: values["connection-token"] ?? values.token,
|
|
232
245
|
vscode
|
|
@@ -247,13 +260,4 @@ if (values.open) {
|
|
|
247
260
|
else if (platform === "win32") exec(`start chrome --app="${url}" || start msedge --app="${url}" || start "" "${url}"`);
|
|
248
261
|
else exec(`google-chrome-stable --app="${url}" 2>/dev/null || google-chrome --app="${url}" 2>/dev/null || chromium --app="${url}" 2>/dev/null || xdg-open "${url}"`);
|
|
249
262
|
}
|
|
250
|
-
let shuttingDown = false;
|
|
251
|
-
const shutdown = () => {
|
|
252
|
-
if (shuttingDown) process.exit(0);
|
|
253
|
-
shuttingDown = true;
|
|
254
|
-
setTimeout(() => process.exit(0), 3e3).unref();
|
|
255
|
-
handle.close().finally(() => process.exit(0));
|
|
256
|
-
};
|
|
257
|
-
process.on("SIGINT", shutdown);
|
|
258
|
-
process.on("SIGTERM", shutdown);
|
|
259
263
|
export {};
|
package/dist/index.d.mts
CHANGED
|
@@ -85,12 +85,20 @@ interface CreateCodeServerOptions {
|
|
|
85
85
|
connectionToken?: string;
|
|
86
86
|
/** Host/interface to bind (used to infer local-only access for token default). */
|
|
87
87
|
host?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Base URL the server is mounted under (e.g. `/code`). Defaults to `/`.
|
|
90
|
+
* Forwarded to VS Code as `server-base-path` and honored by coderaft's own
|
|
91
|
+
* routes (`/healthz`, `/_static/*`, `/proxy/*`, `/login`, …).
|
|
92
|
+
*/
|
|
93
|
+
baseURL?: string;
|
|
88
94
|
/** Extra options forwarded to VS Code's `createServer()`. */
|
|
89
95
|
vscode?: VSCodeServerOptions;
|
|
90
96
|
}
|
|
91
97
|
interface StartCodeServerOptions extends CreateCodeServerOptions {
|
|
92
|
-
/** TCP port to listen on. Defaults to `$PORT` or `6063`. */
|
|
98
|
+
/** TCP port to listen on. Defaults to `$PORT` or `6063`. Ignored when `socketPath` is set. */
|
|
93
99
|
port?: number;
|
|
100
|
+
/** Unix socket path to listen on. When set, `port` and `host` are ignored. */
|
|
101
|
+
socketPath?: string;
|
|
94
102
|
}
|
|
95
103
|
interface CodeServerHandler {
|
|
96
104
|
/** Node-style HTTP request handler (middleware). */
|
|
@@ -102,7 +110,10 @@ interface CodeServerHandler {
|
|
|
102
110
|
}
|
|
103
111
|
interface CodeServerHandle {
|
|
104
112
|
server: Server;
|
|
105
|
-
port
|
|
113
|
+
/** TCP port the server is bound to, or `undefined` when listening on a unix socket. */
|
|
114
|
+
port?: number;
|
|
115
|
+
/** Unix socket path the server is bound to, or `undefined` when listening on TCP. */
|
|
116
|
+
socketPath?: string;
|
|
106
117
|
url: string;
|
|
107
118
|
connectionToken: string;
|
|
108
119
|
close(): Promise<void>;
|