@nimbus-sh/core 0.6.0 → 0.8.0
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 +47 -26
- package/dist/_shared/byte-stream.d.ts +5 -4
- package/dist/_shared/byte-stream.d.ts.map +1 -1
- package/dist/_shared/real-node-imports.d.ts +7 -0
- package/dist/_shared/real-node-imports.d.ts.map +1 -1
- package/dist/_shared/real-node-imports.js +8 -0
- package/dist/_shared/tarball.d.ts +24 -5
- package/dist/_shared/tarball.d.ts.map +1 -1
- package/dist/_shared/tarball.js +42 -5
- package/dist/_shared/vfs-write-ledger.d.ts.map +1 -1
- package/dist/_shared/vfs-write-ledger.js +22 -16
- package/dist/runtime/comment-strip.d.ts +32 -29
- package/dist/runtime/comment-strip.d.ts.map +1 -1
- package/dist/runtime/comment-strip.js +273 -51
- package/dist/runtime/cpython-runner.d.ts +1 -0
- package/dist/runtime/cpython-runner.d.ts.map +1 -1
- package/dist/runtime/cpython-runner.js +1 -1
- package/dist/runtime/esbuild-service.d.ts +38 -42
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +500 -388
- package/dist/runtime/javascript-ast.d.ts.map +1 -1
- package/dist/runtime/javascript-ast.js +45 -7
- package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -1
- package/dist/runtime/opentui-wasm-backend.js +18 -16
- package/dist/runtime/port-registry.d.ts +4 -2
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +60 -8
- package/dist/runtime/ruby-runner.d.ts +1 -0
- package/dist/runtime/ruby-runner.d.ts.map +1 -1
- package/dist/runtime/ruby-runner.js +1 -0
- package/dist/shell/stdin-adapter.d.ts +11 -0
- package/dist/shell/stdin-adapter.d.ts.map +1 -0
- package/dist/shell/stdin-adapter.js +59 -0
- package/dist/shell/unix-commands.d.ts +2 -1
- package/dist/shell/unix-commands.d.ts.map +1 -1
- package/dist/shell/unix-commands.js +1078 -50
- package/dist/substrate/lifo/commands/io/dd.js +1 -1
- package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/net/curl.js +270 -54
- package/dist/substrate/lifo/commands/system/npm.d.ts +18 -1
- package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/system/npm.js +25 -43
- package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/text/head.js +2 -1
- package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/text/sed.js +499 -117
- package/dist/substrate/lifo/commands/types.d.ts +8 -4
- package/dist/substrate/lifo/commands/types.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/zlib.d.ts +37 -18
- package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/zlib.js +122 -23
- package/dist/substrate/lifo/shell/interpreter.d.ts +9 -1
- package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/interpreter.js +79 -29
- package/dist/substrate/lifo/shell/pipe.d.ts +16 -0
- package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/pipe.js +62 -18
- package/dist/substrate/lifo/shell/terminal-stdin.d.ts +20 -3
- package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/terminal-stdin.js +60 -24
- package/dist/vfs/sqlite-vfs.d.ts +19 -0
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +106 -23
- package/dist/workspace/nimbus-workspace.d.ts +22 -1
- package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
- package/dist/workspace/nimbus-workspace.js +19 -3
- package/dist/workspace/supervisor-op.d.ts +107 -0
- package/dist/workspace/supervisor-op.d.ts.map +1 -0
- package/dist/workspace/supervisor-op.js +224 -0
- package/package.json +2 -2
- package/src/_shared/byte-stream.ts +5 -4
- package/src/_shared/real-node-imports.ts +8 -0
- package/src/_shared/tarball.ts +60 -5
- package/src/_shared/vfs-write-ledger.ts +22 -16
- package/src/runtime/comment-strip.ts +218 -52
- package/src/runtime/cpython-runner.ts +2 -1
- package/src/runtime/esbuild-service.ts +572 -372
- package/src/runtime/javascript-ast.ts +40 -9
- package/src/runtime/opentui-wasm-backend.ts +19 -17
- package/src/runtime/port-registry.ts +66 -8
- package/src/runtime/ruby-runner.ts +2 -0
- package/src/shell/stdin-adapter.ts +58 -0
- package/src/shell/unix-commands.ts +1054 -50
- package/src/substrate/lifo/commands/io/dd.ts +2 -2
- package/src/substrate/lifo/commands/net/curl.ts +310 -58
- package/src/substrate/lifo/commands/system/npm.ts +55 -38
- package/src/substrate/lifo/commands/text/head.ts +2 -1
- package/src/substrate/lifo/commands/text/sed.ts +519 -109
- package/src/substrate/lifo/commands/types.ts +8 -5
- package/src/substrate/lifo/node-compat/zlib.ts +155 -26
- package/src/substrate/lifo/shell/interpreter.ts +74 -27
- package/src/substrate/lifo/shell/pipe.ts +65 -24
- package/src/substrate/lifo/shell/terminal-stdin.ts +66 -30
- package/src/vfs/sqlite-vfs.ts +104 -23
- package/src/workspace/nimbus-workspace.ts +30 -3
- package/src/workspace/supervisor-op.ts +309 -0
|
@@ -27,8 +27,11 @@ import type { CommandResult, RunOptions, SandboxFs } from '../substrate/lifo/san
|
|
|
27
27
|
import type { ITerminal } from '../substrate/lifo/terminal/ITerminal.js';
|
|
28
28
|
import { SqliteVFS } from '../vfs/sqlite-vfs.js';
|
|
29
29
|
import type { SqlDatabase, TransactionHost } from '../runtime/os-contracts.js';
|
|
30
|
+
import { SessionProcessSupervisor } from '../runtime/session-process-supervisor.js';
|
|
30
31
|
import type { FacetHost } from '../runtime/facet-host.js';
|
|
31
32
|
import { type RuntimePackage } from '../runtime/runtime-package.js';
|
|
33
|
+
import { type CtxExports, type FabricComposition } from '@nimbus-sh/platform/composition.js';
|
|
34
|
+
import { type SupervisorOpEnvelope, type SupervisorOpHandler } from './supervisor-op.js';
|
|
32
35
|
export interface NimbusWorkspaceOptions {
|
|
33
36
|
/** The host's SQLite. In a Durable Object: `ctx.storage.sql`. */
|
|
34
37
|
readonly sql: SqlDatabase;
|
|
@@ -38,8 +41,14 @@ export interface NimbusWorkspaceOptions {
|
|
|
38
41
|
* Every atomic write in the filesystem rests on this being a real
|
|
39
42
|
* transaction. An implementation that merely calls the callback converts
|
|
40
43
|
* each one into a torn write that reports success.
|
|
44
|
+
*
|
|
45
|
+
* `NimbusWorkspace.create` is a first-write-wins composition root beside
|
|
46
|
+
* `worker/index.ts` and `loom/actor.ts`: when `ctxExports` is absent the
|
|
47
|
+
* host's ctx `exports` bag is adopted as the isolate's.
|
|
41
48
|
*/
|
|
42
|
-
readonly transactions?: TransactionHost
|
|
49
|
+
readonly transactions?: TransactionHost & {
|
|
50
|
+
readonly exports?: CtxExports;
|
|
51
|
+
};
|
|
43
52
|
/**
|
|
44
53
|
* The filesystem already open over `sql`, for a host that has one.
|
|
45
54
|
*
|
|
@@ -106,6 +115,15 @@ export interface NimbusWorkspaceOptions {
|
|
|
106
115
|
* carry REPLs and a resident-process substrate this cannot reach.
|
|
107
116
|
*/
|
|
108
117
|
readonly facets?: FacetHost;
|
|
118
|
+
/** Isolate-wide fabric composition, including the hosting namespace. */
|
|
119
|
+
readonly fabric?: FabricComposition;
|
|
120
|
+
/** Explicit exports override the bag on the transaction host. */
|
|
121
|
+
readonly ctxExports?: CtxExports;
|
|
122
|
+
/** The process table that allocated supervisor-binding pids. */
|
|
123
|
+
readonly processes?: SessionProcessSupervisor;
|
|
124
|
+
readonly processOutput?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
|
|
125
|
+
/** Host operations, including overrides for host-specific accounting. */
|
|
126
|
+
readonly supervisorOps?: Readonly<Record<string, SupervisorOpHandler>>;
|
|
109
127
|
}
|
|
110
128
|
/**
|
|
111
129
|
* A durable filesystem and a shell over it.
|
|
@@ -120,6 +138,7 @@ export interface NimbusWorkspaceOptions {
|
|
|
120
138
|
*/
|
|
121
139
|
export declare class NimbusWorkspace {
|
|
122
140
|
private readonly sql;
|
|
141
|
+
private readonly supervisorOps;
|
|
123
142
|
/**
|
|
124
143
|
* Credentialed and mount-aware. Acts as the session user, never as the
|
|
125
144
|
* kernel: a pid-less caller must not gain more authority than the shell it
|
|
@@ -142,6 +161,8 @@ export declare class NimbusWorkspace {
|
|
|
142
161
|
private constructor();
|
|
143
162
|
static create(options: NimbusWorkspaceOptions): Promise<NimbusWorkspace>;
|
|
144
163
|
exec(command: string, options?: RunOptions): Promise<CommandResult>;
|
|
164
|
+
/** The hosting object forwards its supervisorOp RPC to this method. */
|
|
165
|
+
supervisorOp(envelope: SupervisorOpEnvelope): Promise<unknown>;
|
|
145
166
|
/**
|
|
146
167
|
* Apply the login files, and begin reading the terminal when there is one.
|
|
147
168
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nimbus-workspace.d.ts","sourceRoot":"","sources":["../../src/workspace/nimbus-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,kCAAkC,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAE7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAI9E,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,SAAS,EAAqB,MAAM,sBAAsB,CAAC;AAMpE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"nimbus-workspace.d.ts","sourceRoot":"","sources":["../../src/workspace/nimbus-workspace.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,6 +34,8 @@ import { rehydrateInstalledRuntimesView, } from '../runtime/installed-runtimes.j
|
|
|
34
34
|
import { seedRuntimePackage } from '../runtime/runtime-package.js';
|
|
35
35
|
import { registerUnixCommands } from '../shell/unix-commands.js';
|
|
36
36
|
import { installPathExecResolver } from '../shell/exec-dispatch.js';
|
|
37
|
+
import { adoptCtxExports, composeFabric } from '@nimbus-sh/platform/composition.js';
|
|
38
|
+
import { createSupervisorOpHandler } from './supervisor-op.js';
|
|
37
39
|
/**
|
|
38
40
|
* A durable filesystem and a shell over it.
|
|
39
41
|
*
|
|
@@ -47,6 +49,7 @@ import { installPathExecResolver } from '../shell/exec-dispatch.js';
|
|
|
47
49
|
*/
|
|
48
50
|
export class NimbusWorkspace {
|
|
49
51
|
sql;
|
|
52
|
+
supervisorOps;
|
|
50
53
|
/**
|
|
51
54
|
* Credentialed and mount-aware. Acts as the session user, never as the
|
|
52
55
|
* kernel: a pid-less caller must not gain more authority than the shell it
|
|
@@ -66,8 +69,9 @@ export class NimbusWorkspace {
|
|
|
66
69
|
*/
|
|
67
70
|
env;
|
|
68
71
|
commands;
|
|
69
|
-
constructor(vfs, kernel, shell, registry, env, sql) {
|
|
72
|
+
constructor(vfs, kernel, shell, registry, env, sql, supervisorOps) {
|
|
70
73
|
this.sql = sql;
|
|
74
|
+
this.supervisorOps = supervisorOps;
|
|
71
75
|
this.vfs = vfs;
|
|
72
76
|
this.kernel = kernel;
|
|
73
77
|
this.shell = shell;
|
|
@@ -80,6 +84,11 @@ export class NimbusWorkspace {
|
|
|
80
84
|
this.fs = new SandboxFsImpl(kernel.vfs.as(CRED_SESSION_USER), () => shell.getCwd());
|
|
81
85
|
}
|
|
82
86
|
static async create(options) {
|
|
87
|
+
if (options.fabric)
|
|
88
|
+
composeFabric(options.fabric);
|
|
89
|
+
const exports = options.ctxExports ?? options.transactions?.exports;
|
|
90
|
+
if (exports)
|
|
91
|
+
adoptCtxExports(exports);
|
|
83
92
|
const vfs = options.vfs ?? openFilesystem(options);
|
|
84
93
|
const mounts = options.mounts ?? DEFAULT_MOUNT_POINTS;
|
|
85
94
|
seedBaseFilesystem(vfs, mounts);
|
|
@@ -118,11 +127,18 @@ export class NimbusWorkspace {
|
|
|
118
127
|
home,
|
|
119
128
|
});
|
|
120
129
|
}
|
|
121
|
-
|
|
130
|
+
const supervisorOps = createSupervisorOpHandler({
|
|
131
|
+
vfs, processes: options.processes, output: options.processOutput, extend: options.supervisorOps,
|
|
132
|
+
});
|
|
133
|
+
return new NimbusWorkspace(vfs, kernel, shell, registry, env, options.sql, supervisorOps);
|
|
122
134
|
}
|
|
123
135
|
exec(command, options) {
|
|
124
136
|
return this.commands.run(command, options);
|
|
125
137
|
}
|
|
138
|
+
/** The hosting object forwards its supervisorOp RPC to this method. */
|
|
139
|
+
supervisorOp(envelope) {
|
|
140
|
+
return this.supervisorOps(envelope);
|
|
141
|
+
}
|
|
126
142
|
/**
|
|
127
143
|
* Apply the login files, and begin reading the terminal when there is one.
|
|
128
144
|
*
|
|
@@ -243,7 +259,7 @@ async function registerWasmRuntimes(deps) {
|
|
|
243
259
|
getEsbuild: () => {
|
|
244
260
|
if (!esbuild) {
|
|
245
261
|
esbuild = import('../runtime/esbuild-service.js')
|
|
246
|
-
.then((module) => new module.EsbuildService(deps.vfs));
|
|
262
|
+
.then((module) => new module.EsbuildService(deps.vfs.as(CRED_KERNEL)));
|
|
247
263
|
}
|
|
248
264
|
return esbuild;
|
|
249
265
|
},
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { SqliteVFS } from '../vfs/sqlite-vfs.js';
|
|
2
|
+
import { type VfsCred } from '../runtime/os-contracts.js';
|
|
3
|
+
import { SqliteRuntimeFsBridge } from '../runtime/sqlite-runtime-fs-bridge.js';
|
|
4
|
+
import type { SessionProcessSupervisor } from '../runtime/session-process-supervisor.js';
|
|
5
|
+
/** Identity comes from the supervisor binding, never from facet arguments. */
|
|
6
|
+
export interface SupervisorOpEnvelope {
|
|
7
|
+
readonly op: SupervisorOpName;
|
|
8
|
+
readonly args?: readonly unknown[];
|
|
9
|
+
readonly pid?: number;
|
|
10
|
+
readonly writerId?: string;
|
|
11
|
+
readonly mutationOwner?: string;
|
|
12
|
+
readonly stream?: ReadableStream<Uint8Array>;
|
|
13
|
+
}
|
|
14
|
+
export type SupervisorOpHandler = (envelope: SupervisorOpEnvelope, tools: SupervisorOpTools) => unknown;
|
|
15
|
+
export interface SupervisorOpDeps {
|
|
16
|
+
readonly vfs: SqliteVFS;
|
|
17
|
+
/** Absent a process table, operations use the unprivileged session user. */
|
|
18
|
+
readonly processes?: SessionProcessSupervisor;
|
|
19
|
+
readonly output?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
|
|
20
|
+
/**
|
|
21
|
+
* The host's `_rpc*` surface for ops beyond the native set — an in-process
|
|
22
|
+
* workspace's dispatch record, or the session itself for
|
|
23
|
+
* `sessionSupervisorOps`. A native op never consults it.
|
|
24
|
+
*/
|
|
25
|
+
readonly host?: SupervisorOpHost;
|
|
26
|
+
/**
|
|
27
|
+
* A pid-keyed bridge cache to serve the native ops from. Supplied by the
|
|
28
|
+
* session so `supervisorBridge` hands callers the same bridges the handler
|
|
29
|
+
* uses; in-process workspaces let the handler build its own.
|
|
30
|
+
*/
|
|
31
|
+
readonly bridge?: SupervisorOpBridgeStore;
|
|
32
|
+
readonly extend?: Partial<Record<SupervisorOpName, SupervisorOpHandler>>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One slot in an op's argument plan: a number takes `envelope.args[n]`, a
|
|
36
|
+
* name takes the envelope's identity field (`pid`, `writerId`, `stream`,
|
|
37
|
+
* `mutationOwner`). The envelope is always the shape — a host never
|
|
38
|
+
* re-parses it.
|
|
39
|
+
*/
|
|
40
|
+
export type SupervisorOpArg = number | 'pid' | 'writerId' | 'stream' | 'mutationOwner';
|
|
41
|
+
export interface SupervisorOpRoute {
|
|
42
|
+
/** The host method this op dispatches to. */
|
|
43
|
+
readonly method: string;
|
|
44
|
+
/** Positional plan for the host call — envelope fields, not raw args. */
|
|
45
|
+
readonly args: readonly SupervisorOpArg[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The embedder's dispatch surface — the `_rpc*` methods SUPERVISOR_OP_ROUTES
|
|
49
|
+
* names. The session satisfies it with its own class; an in-process
|
|
50
|
+
* workspace supplies its host object.
|
|
51
|
+
*/
|
|
52
|
+
export interface SupervisorOpHost {
|
|
53
|
+
readonly [method: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The canonical supervisor op set — every operation the supervisor RPC
|
|
57
|
+
* serves. Three consumers key on these names:
|
|
58
|
+
*
|
|
59
|
+
* - `sessionSupervisorOp` (worker): the DO's host — `extend` overrides for
|
|
60
|
+
* hosted accounting plus the non-filesystem ops it answers itself.
|
|
61
|
+
* - `createSupervisorOpHandler`: an in-process workspace — filesystem ops
|
|
62
|
+
* run against the VFS directly; every other op dispatches to
|
|
63
|
+
* `deps.host` through SUPERVISOR_OP_ROUTES, the embedder's `_rpc*`
|
|
64
|
+
* surface.
|
|
65
|
+
* - `supervisor-host-dispatch`: the test — derives every case's delegate
|
|
66
|
+
* and expected arguments from SUPERVISOR_OP_ROUTES, not a copied list.
|
|
67
|
+
*
|
|
68
|
+
* An op absent here is not served, on any host.
|
|
69
|
+
*/
|
|
70
|
+
export declare const SUPERVISOR_OPS: readonly ["readFile", "readFileBytes", "writeFile", "stat", "lstat", "hasLegacySymlinkUnder", "utimes", "chmod", "access", "chown", "setUmask", "readdir", "exists", "mkdir", "rmdir", "rename", "unlink", "readlink", "symlink", "fsAcquire", "fsRevision", "fsList", "wsOpen", "wsPoll", "wsSend", "wsClose", "fsOpen", "fsRead", "fsWrite", "fsClose", "fsReadRange", "fsReadRangeUncached", "fsReadBatch", "fsWriteRange", "fsAppend", "fsAppendAck", "fsTruncate", "writeBatch", "writeBatchStream", "putRegistryEntries", "stdout", "stderr", "prefetch", "registerPort", "unregisterPort", "reportExit", "routeLoopback", "transform", "cpSpawn", "cpStdinWrite", "cpStdinEnd", "cpReadStdin", "cpReadOutput", "cpDrainOutput", "cpKill", "cpWait", "cpDispatchInline"];
|
|
71
|
+
export type SupervisorOpName = (typeof SUPERVISOR_OPS)[number];
|
|
72
|
+
/**
|
|
73
|
+
* What the shared handler hands a host override: the pid-keyed bridge and
|
|
74
|
+
* the deps it was built with, so an override that wraps a filesystem op
|
|
75
|
+
* (read-allocation accounting, stream-drain timing) reuses the same bridge
|
|
76
|
+
* the default handler would have used instead of caching its own.
|
|
77
|
+
*/
|
|
78
|
+
export interface SupervisorOpTools {
|
|
79
|
+
readonly bridge: (pid?: number) => SqliteRuntimeFsBridge;
|
|
80
|
+
readonly vfs: SqliteVFS;
|
|
81
|
+
readonly cred: (pid?: number) => VfsCred;
|
|
82
|
+
readonly output?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
|
|
83
|
+
}
|
|
84
|
+
/** The host-side argument plan per op — how an envelope becomes an _rpc* call. */
|
|
85
|
+
export declare const SUPERVISOR_OP_ROUTES: Readonly<Record<SupervisorOpName, SupervisorOpRoute>>;
|
|
86
|
+
/**
|
|
87
|
+
* The ops `createSupervisorOpHandler` serves natively — one pid-keyed
|
|
88
|
+
* filesystem bridge, plus the output stream. A session's `extend` overrides
|
|
89
|
+
* never cover these by accident: `sessionSupervisorOps` builds its delegate
|
|
90
|
+
* set from this name list, not a hand-copied table.
|
|
91
|
+
*/
|
|
92
|
+
export declare const SUPERVISOR_NATIVE_OPS: ReadonlySet<string>;
|
|
93
|
+
/** The pid-keyed bridge cache behind the native filesystem ops. */
|
|
94
|
+
export interface SupervisorOpBridgeStore {
|
|
95
|
+
readonly bridge: (pid?: number) => SqliteRuntimeFsBridge;
|
|
96
|
+
/** Drop a pid's bridge — a process exit ends its credential's validity. */
|
|
97
|
+
readonly forget: (pid: number) => void;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Exported so the session's `supervisorBridge` — used by RPC bodies the
|
|
101
|
+
* envelope delegates back to (fsOpen, fsAppend, writeBatch, …) — is the
|
|
102
|
+
* same cache the handler's native ops serve from, never a second one.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createSupervisorBridgeStore(deps: Pick<SupervisorOpDeps, 'vfs' | 'processes'>): SupervisorOpBridgeStore;
|
|
105
|
+
/** One dispatch method lets any host serve its workspace to process facets. */
|
|
106
|
+
export declare function createSupervisorOpHandler(deps: SupervisorOpDeps): (envelope: SupervisorOpEnvelope) => Promise<unknown>;
|
|
107
|
+
//# sourceMappingURL=supervisor-op.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor-op.d.ts","sourceRoot":"","sources":["../../src/workspace/supervisor-op.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { CRED_SESSION_USER } from '../runtime/os-contracts.js';
|
|
2
|
+
import { SqliteRuntimeFsBridge } from '../runtime/sqlite-runtime-fs-bridge.js';
|
|
3
|
+
import { getSymlinkRegistry } from '../vfs/symlink-registry.js';
|
|
4
|
+
function stringArg(envelope, index) {
|
|
5
|
+
const value = envelope.args?.[index];
|
|
6
|
+
if (typeof value !== 'string') {
|
|
7
|
+
throw new Error(`supervisor op ${envelope.op}: argument ${index} must be a string`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function numberArg(envelope, index) {
|
|
12
|
+
const value = envelope.args?.[index];
|
|
13
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
14
|
+
throw new Error(`supervisor op ${envelope.op}: argument ${index} must be a number`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function contentArg(envelope, index) {
|
|
19
|
+
const value = envelope.args?.[index];
|
|
20
|
+
if (typeof value === 'string' || value instanceof Uint8Array)
|
|
21
|
+
return value;
|
|
22
|
+
if (value instanceof ArrayBuffer)
|
|
23
|
+
return new Uint8Array(value);
|
|
24
|
+
throw new Error(`supervisor op ${envelope.op}: argument ${index} must be bytes or text`);
|
|
25
|
+
}
|
|
26
|
+
function credFor(deps, pid) {
|
|
27
|
+
if (pid === undefined)
|
|
28
|
+
return CRED_SESSION_USER;
|
|
29
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
30
|
+
throw new Error('supervisor op: filesystem operation requires a valid process pid');
|
|
31
|
+
}
|
|
32
|
+
return deps.processes ? deps.processes.cred(pid) : CRED_SESSION_USER;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The canonical supervisor op set — every operation the supervisor RPC
|
|
36
|
+
* serves. Three consumers key on these names:
|
|
37
|
+
*
|
|
38
|
+
* - `sessionSupervisorOp` (worker): the DO's host — `extend` overrides for
|
|
39
|
+
* hosted accounting plus the non-filesystem ops it answers itself.
|
|
40
|
+
* - `createSupervisorOpHandler`: an in-process workspace — filesystem ops
|
|
41
|
+
* run against the VFS directly; every other op dispatches to
|
|
42
|
+
* `deps.host` through SUPERVISOR_OP_ROUTES, the embedder's `_rpc*`
|
|
43
|
+
* surface.
|
|
44
|
+
* - `supervisor-host-dispatch`: the test — derives every case's delegate
|
|
45
|
+
* and expected arguments from SUPERVISOR_OP_ROUTES, not a copied list.
|
|
46
|
+
*
|
|
47
|
+
* An op absent here is not served, on any host.
|
|
48
|
+
*/
|
|
49
|
+
export const SUPERVISOR_OPS = [
|
|
50
|
+
'readFile', 'readFileBytes', 'writeFile', 'stat', 'lstat',
|
|
51
|
+
'hasLegacySymlinkUnder', 'utimes', 'chmod', 'access', 'chown', 'setUmask',
|
|
52
|
+
'readdir', 'exists', 'mkdir', 'rmdir', 'rename', 'unlink', 'readlink',
|
|
53
|
+
'symlink', 'fsAcquire', 'fsRevision', 'fsList', 'wsOpen', 'wsPoll',
|
|
54
|
+
'wsSend', 'wsClose', 'fsOpen', 'fsRead', 'fsWrite', 'fsClose',
|
|
55
|
+
'fsReadRange', 'fsReadRangeUncached', 'fsReadBatch', 'fsWriteRange',
|
|
56
|
+
'fsAppend', 'fsAppendAck', 'fsTruncate', 'writeBatch', 'writeBatchStream',
|
|
57
|
+
'putRegistryEntries', 'stdout', 'stderr', 'prefetch', 'registerPort',
|
|
58
|
+
'unregisterPort', 'reportExit', 'routeLoopback', 'transform', 'cpSpawn',
|
|
59
|
+
'cpStdinWrite', 'cpStdinEnd', 'cpReadStdin', 'cpReadOutput',
|
|
60
|
+
'cpDrainOutput', 'cpKill', 'cpWait', 'cpDispatchInline',
|
|
61
|
+
];
|
|
62
|
+
/** The host-side argument plan per op — how an envelope becomes an _rpc* call. */
|
|
63
|
+
export const SUPERVISOR_OP_ROUTES = {
|
|
64
|
+
readFile: { method: '_rpcReadFile', args: [0, 'pid'] },
|
|
65
|
+
readFileBytes: { method: '_rpcReadFileBytes', args: [0, 'pid'] },
|
|
66
|
+
writeFile: { method: '_rpcWriteFile', args: [0, 1, 'pid'] },
|
|
67
|
+
stat: { method: '_rpcStat', args: [0, 'pid'] },
|
|
68
|
+
lstat: { method: '_rpcLstat', args: [0, 'pid'] },
|
|
69
|
+
hasLegacySymlinkUnder: { method: '_rpcHasLegacySymlinkUnder', args: [0, 'pid'] },
|
|
70
|
+
utimes: { method: '_rpcUtimes', args: [0, 1, 2, 'pid'] },
|
|
71
|
+
chmod: { method: '_rpcChmod', args: [0, 1, 'pid'] },
|
|
72
|
+
access: { method: '_rpcAccess', args: [0, 1, 'pid'] },
|
|
73
|
+
chown: { method: '_rpcChown', args: [0, 1, 2, 'pid', 3] },
|
|
74
|
+
setUmask: { method: '_rpcSetUmask', args: [0, 'pid'] },
|
|
75
|
+
readdir: { method: '_rpcReaddir', args: [0, 'pid'] },
|
|
76
|
+
exists: { method: '_rpcExists', args: [0, 'pid'] },
|
|
77
|
+
mkdir: { method: '_rpcMkdir', args: [0, 'pid'] },
|
|
78
|
+
rmdir: { method: '_rpcRmdir', args: [0, 'pid'] },
|
|
79
|
+
rename: { method: '_rpcRename', args: [0, 1, 'pid'] },
|
|
80
|
+
unlink: { method: '_rpcUnlink', args: [0, 'pid'] },
|
|
81
|
+
readlink: { method: '_rpcReadlink', args: [0, 'pid'] },
|
|
82
|
+
symlink: { method: '_rpcSymlink', args: [0, 1, 'pid'] },
|
|
83
|
+
fsAcquire: { method: '_rpcFsAcquire', args: [0, 1, 'pid'] },
|
|
84
|
+
fsRevision: { method: '_rpcFsRevision', args: [0, 'pid'] },
|
|
85
|
+
fsList: { method: '_rpcFsList', args: [0, 1, 'pid'] },
|
|
86
|
+
wsOpen: { method: '_rpcWsOpen', args: [0, 1, 'pid'] },
|
|
87
|
+
wsPoll: { method: '_rpcWsPoll', args: [0, 1, 'pid'] },
|
|
88
|
+
wsSend: { method: '_rpcWsSend', args: [0, 1, 2, 'pid'] },
|
|
89
|
+
wsClose: { method: '_rpcWsClose', args: [0, 1, 2, 'pid'] },
|
|
90
|
+
fsOpen: { method: '_rpcFsOpen', args: [0, 1, 'pid'] },
|
|
91
|
+
fsRead: { method: '_rpcFsRead', args: [0, 1, 2, 'pid'] },
|
|
92
|
+
fsWrite: { method: '_rpcFsWrite', args: [0, 1, 2, 'pid'] },
|
|
93
|
+
fsClose: { method: '_rpcFsClose', args: [0, 'pid'] },
|
|
94
|
+
fsReadRange: { method: '_rpcFsReadRange', args: [0, 1, 2, 'pid'] },
|
|
95
|
+
fsReadRangeUncached: { method: '_rpcFsReadRangeUncached', args: [0, 1, 2, 'pid'] },
|
|
96
|
+
fsReadBatch: { method: '_rpcFsReadBatch', args: [0, 'pid'] },
|
|
97
|
+
fsWriteRange: { method: '_rpcFsWriteRange', args: [0, 1, 2, 'pid'] },
|
|
98
|
+
fsAppend: { method: '_rpcFsAppend', args: [0, 'writerId', 1, 2, 3, 'pid'] },
|
|
99
|
+
fsAppendAck: { method: '_rpcFsAppendAck', args: ['writerId', 0, 1, 'pid'] },
|
|
100
|
+
fsTruncate: { method: '_rpcFsTruncate', args: [0, 1, 'pid'] },
|
|
101
|
+
writeBatch: { method: '_rpcWriteBatch', args: [0, 'pid'] },
|
|
102
|
+
writeBatchStream: { method: '_rpcWriteBatchStream', args: ['stream', 'mutationOwner', 'pid'] },
|
|
103
|
+
putRegistryEntries: { method: '_rpcPutRegistryEntries', args: [0] },
|
|
104
|
+
stdout: { method: '_rpcStdout', args: ['pid', 0] },
|
|
105
|
+
stderr: { method: '_rpcStderr', args: ['pid', 0] },
|
|
106
|
+
prefetch: { method: '_rpcPrefetch', args: [0, 1] },
|
|
107
|
+
registerPort: { method: '_rpcRegisterPort', args: ['pid', 0] },
|
|
108
|
+
unregisterPort: { method: '_rpcUnregisterPort', args: [0] },
|
|
109
|
+
reportExit: { method: '_rpcReportExit', args: ['pid', 0, 1] },
|
|
110
|
+
routeLoopback: { method: '_rpcRouteLoopback', args: [0, 1] },
|
|
111
|
+
transform: { method: '_rpcTransform', args: [0, 1] },
|
|
112
|
+
cpSpawn: { method: '_rpcCpSpawn', args: [0] },
|
|
113
|
+
cpStdinWrite: { method: '_rpcCpStdinWrite', args: [0, 1] },
|
|
114
|
+
cpStdinEnd: { method: '_rpcCpStdinEnd', args: [0] },
|
|
115
|
+
cpReadStdin: { method: '_rpcCpReadStdin', args: [0, 1] },
|
|
116
|
+
cpReadOutput: { method: '_rpcCpReadOutput', args: [0, 1, 2, 3] },
|
|
117
|
+
cpDrainOutput: { method: '_rpcCpDrainOutput', args: [0] },
|
|
118
|
+
cpKill: { method: '_rpcCpKill', args: [0, 1] },
|
|
119
|
+
cpWait: { method: '_rpcCpWait', args: [0, 1] },
|
|
120
|
+
cpDispatchInline: { method: '_rpcCpDispatchInline', args: [0, 1] },
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The ops `createSupervisorOpHandler` serves natively — one pid-keyed
|
|
124
|
+
* filesystem bridge, plus the output stream. A session's `extend` overrides
|
|
125
|
+
* never cover these by accident: `sessionSupervisorOps` builds its delegate
|
|
126
|
+
* set from this name list, not a hand-copied table.
|
|
127
|
+
*/
|
|
128
|
+
export const SUPERVISOR_NATIVE_OPS = new Set([
|
|
129
|
+
'readFile', 'readFileBytes', 'stat', 'lstat', 'exists', 'readdir',
|
|
130
|
+
'readlink', 'fsReadRange', 'fsReadRangeUncached', 'fsRevision',
|
|
131
|
+
'hasLegacySymlinkUnder', 'writeFile', 'mkdir', 'rmdir', 'unlink',
|
|
132
|
+
'rename', 'symlink', 'chmod', 'utimes', 'fsTruncate',
|
|
133
|
+
'writeBatchStream', 'stdout', 'stderr',
|
|
134
|
+
]);
|
|
135
|
+
/**
|
|
136
|
+
* Exported so the session's `supervisorBridge` — used by RPC bodies the
|
|
137
|
+
* envelope delegates back to (fsOpen, fsAppend, writeBatch, …) — is the
|
|
138
|
+
* same cache the handler's native ops serve from, never a second one.
|
|
139
|
+
*/
|
|
140
|
+
export function createSupervisorBridgeStore(deps) {
|
|
141
|
+
const bridges = new Map();
|
|
142
|
+
return {
|
|
143
|
+
bridge: (pid) => {
|
|
144
|
+
const key = pid ?? 0;
|
|
145
|
+
const credentialed = deps.vfs.as(credFor(deps, pid));
|
|
146
|
+
const held = bridges.get(key);
|
|
147
|
+
if (held) {
|
|
148
|
+
held.updateCredential(credentialed);
|
|
149
|
+
return held;
|
|
150
|
+
}
|
|
151
|
+
const built = new SqliteRuntimeFsBridge(credentialed, deps.vfs);
|
|
152
|
+
bridges.set(key, built);
|
|
153
|
+
return built;
|
|
154
|
+
},
|
|
155
|
+
forget: (pid) => { bridges.delete(pid); },
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** One dispatch method lets any host serve its workspace to process facets. */
|
|
159
|
+
export function createSupervisorOpHandler(deps) {
|
|
160
|
+
const bridgeFor = deps.bridge?.bridge ?? createSupervisorBridgeStore(deps).bridge;
|
|
161
|
+
const tools = {
|
|
162
|
+
bridge: bridgeFor,
|
|
163
|
+
vfs: deps.vfs,
|
|
164
|
+
cred: (pid) => credFor(deps, pid),
|
|
165
|
+
output: deps.output,
|
|
166
|
+
};
|
|
167
|
+
const ops = {
|
|
168
|
+
readFile: async (e) => {
|
|
169
|
+
const bytes = await bridgeFor(e.pid).readFile(stringArg(e, 0));
|
|
170
|
+
return bytes === null ? null : new TextDecoder().decode(bytes);
|
|
171
|
+
},
|
|
172
|
+
readFileBytes: (e) => bridgeFor(e.pid).readFile(stringArg(e, 0)),
|
|
173
|
+
stat: (e) => bridgeFor(e.pid).stat(stringArg(e, 0)),
|
|
174
|
+
lstat: (e) => bridgeFor(e.pid).stat(stringArg(e, 0), { followSymlinks: false }),
|
|
175
|
+
exists: async (e) => (await bridgeFor(e.pid).stat(stringArg(e, 0))) !== null,
|
|
176
|
+
readdir: (e) => bridgeFor(e.pid).readdir(stringArg(e, 0)),
|
|
177
|
+
readlink: (e) => bridgeFor(e.pid).readlink(stringArg(e, 0)),
|
|
178
|
+
fsReadRange: (e) => bridgeFor(e.pid).readRange(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2)),
|
|
179
|
+
fsReadRangeUncached: (e) => bridgeFor(e.pid).readRange(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2), { cached: false }),
|
|
180
|
+
fsRevision: (e) => bridgeFor(e.pid).revision(e.args?.[0] === undefined ? undefined : stringArg(e, 0)),
|
|
181
|
+
hasLegacySymlinkUnder: (e) => getSymlinkRegistry(deps.vfs).hasAtOrBelow(stringArg(e, 0)),
|
|
182
|
+
writeFile: (e) => bridgeFor(e.pid).writeFile(stringArg(e, 0), contentArg(e, 1)),
|
|
183
|
+
mkdir: (e) => bridgeFor(e.pid).mkdir(stringArg(e, 0), { recursive: true }),
|
|
184
|
+
rmdir: (e) => bridgeFor(e.pid).rmdir(stringArg(e, 0)),
|
|
185
|
+
unlink: (e) => bridgeFor(e.pid).unlink(stringArg(e, 0)),
|
|
186
|
+
rename: (e) => bridgeFor(e.pid).rename(stringArg(e, 0), stringArg(e, 1)),
|
|
187
|
+
symlink: (e) => bridgeFor(e.pid).symlink(stringArg(e, 0), stringArg(e, 1)),
|
|
188
|
+
chmod: (e) => bridgeFor(e.pid).chmod(stringArg(e, 0), numberArg(e, 1)),
|
|
189
|
+
utimes: (e) => bridgeFor(e.pid).utimes(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2)),
|
|
190
|
+
fsTruncate: (e) => bridgeFor(e.pid).truncate(stringArg(e, 0), numberArg(e, 1)),
|
|
191
|
+
writeBatchStream: (e) => {
|
|
192
|
+
if (!e.stream)
|
|
193
|
+
throw new Error('supervisor op writeBatchStream: no stream');
|
|
194
|
+
return deps.vfs.as(credFor(deps, e.pid)).writeStream(e.stream, { mutationOwner: e.mutationOwner });
|
|
195
|
+
},
|
|
196
|
+
stdout: (e) => { deps.output?.('stdout', e.pid ?? 0, stringArg(e, 0)); },
|
|
197
|
+
stderr: (e) => { deps.output?.('stderr', e.pid ?? 0, stringArg(e, 0)); },
|
|
198
|
+
};
|
|
199
|
+
const extend = deps.extend ?? {};
|
|
200
|
+
return async (envelope) => {
|
|
201
|
+
if (!envelope || typeof envelope.op !== 'string') {
|
|
202
|
+
throw new Error('supervisor op: envelope names no operation');
|
|
203
|
+
}
|
|
204
|
+
// Priority: the embedder's own handler → the native filesystem op → the
|
|
205
|
+
// canonical route table onto the host's _rpc* methods. An op in none of
|
|
206
|
+
// these is not served by this host.
|
|
207
|
+
const handler = Object.hasOwn(extend, envelope.op) ? extend[envelope.op]
|
|
208
|
+
: Object.hasOwn(ops, envelope.op) ? ops[envelope.op] : undefined;
|
|
209
|
+
if (handler)
|
|
210
|
+
return handler(envelope, tools);
|
|
211
|
+
const route = Object.hasOwn(SUPERVISOR_OP_ROUTES, envelope.op)
|
|
212
|
+
? SUPERVISOR_OP_ROUTES[envelope.op] : undefined;
|
|
213
|
+
if (!route)
|
|
214
|
+
throw new Error(`supervisor op: '${envelope.op}' is not served by this host`);
|
|
215
|
+
const host = deps.host;
|
|
216
|
+
if (!host)
|
|
217
|
+
throw new Error(`supervisor op: '${envelope.op}' needs a host that this workspace does not have`);
|
|
218
|
+
const method = host[route.method];
|
|
219
|
+
if (typeof method !== 'function')
|
|
220
|
+
throw new Error(`supervisor op: missing host method ${route.method}`);
|
|
221
|
+
const args = route.args.map((slot) => typeof slot === 'number' ? envelope.args?.[slot] : envelope[slot]);
|
|
222
|
+
return Reflect.apply(method, host, args);
|
|
223
|
+
};
|
|
224
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-sh/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Backend-agnostic half of Nimbus — the durable filesystem, the shell, and the OS/process contracts, over a host-supplied SQL port.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"typecheck": "tsc --noEmit"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@nimbus-sh/platform": "^0.
|
|
58
|
+
"@nimbus-sh/platform": "^0.3.0",
|
|
59
59
|
"@renovatebot/pep440": "^5.0.0",
|
|
60
60
|
"acorn": "^8.15.0",
|
|
61
61
|
"acorn-walk": "^8.3.5",
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
/**
|
|
19
19
|
* A command's output destination.
|
|
20
20
|
*
|
|
21
|
-
* `writeBytes` is present on sinks that store bytes verbatim
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* fallback: a terminal has no way to hold a byte that is
|
|
21
|
+
* `writeBytes` is present on sinks that store bytes verbatim — files,
|
|
22
|
+
* `/dev/null`, and shell pipes, which carry the producer's exact bytes.
|
|
23
|
+
* Sinks without it take decoded text instead. This is a real capability
|
|
24
|
+
* difference, not a fallback: a terminal has no way to hold a byte that is
|
|
25
|
+
* not text.
|
|
25
26
|
*/
|
|
26
27
|
export interface ByteSink {
|
|
27
28
|
write(text: string): void;
|
|
@@ -38,6 +38,13 @@
|
|
|
38
38
|
* constructed Session's connect/post are inert.
|
|
39
39
|
* Tools (e.g. nuxi) that open a Session purely
|
|
40
40
|
* for optional profiling degrade cleanly.
|
|
41
|
+
* - node:zlib — full surface: every *Sync variant, brotli/zstd,
|
|
42
|
+
* crc32, constants, and streaming create* factories
|
|
43
|
+
* (probe-verified 2026-08-23 at compat date
|
|
44
|
+
* 2026-04-01). Forwarded verbatim by the zlib
|
|
45
|
+
* block in node-shims.ts; results are the host
|
|
46
|
+
* realm's own Buffers, which the widened
|
|
47
|
+
* __BufferMod.isBuffer recognizes.
|
|
41
48
|
*/
|
|
42
49
|
|
|
43
50
|
export function getRealNodeImportsCode(): string {
|
|
@@ -49,5 +56,6 @@ import * as __real_diagnostics_channel from 'node:diagnostics_channel';
|
|
|
49
56
|
import * as __real_repl from 'node:repl';
|
|
50
57
|
import * as __real_vm from 'node:vm';
|
|
51
58
|
import * as __real_inspector from 'node:inspector';
|
|
59
|
+
import * as __real_zlib from 'node:zlib';
|
|
52
60
|
`.trim();
|
|
53
61
|
}
|
package/src/_shared/tarball.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Tarball extraction for streaming installers and buffered archive consumers.
|
|
3
3
|
*
|
|
4
4
|
* The streaming primitives it walks with (`parseTarHeader`, `streamTarEntries`,
|
|
5
5
|
* `readableStreamToAsyncIterable`) live in `./tarball-stream.ts` — a
|
|
@@ -7,10 +7,8 @@
|
|
|
7
7
|
* into a string the loader pool injects into dynamic workers, where an import
|
|
8
8
|
* would not resolve.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* once. An installer streaming a tarball it is still downloading should drive
|
|
13
|
-
* `streamTarEntries` itself and never hold the whole thing.
|
|
10
|
+
* Installers use writeTarballStream. extractTarball retains a map for callers
|
|
11
|
+
* such as gem install, which must open an archive nested inside another one.
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
import {
|
|
@@ -18,6 +16,63 @@ import {
|
|
|
18
16
|
readableStreamToAsyncIterable,
|
|
19
17
|
} from './tarball-stream.js';
|
|
20
18
|
|
|
19
|
+
export interface TarballWriteTarget {
|
|
20
|
+
exists(path: string): boolean;
|
|
21
|
+
mkdir(path: string, options?: { recursive?: boolean }): void;
|
|
22
|
+
writeFile(path: string, data: Uint8Array | string): unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TarballWriteResult {
|
|
26
|
+
/** Regular files written, including the manifest. */
|
|
27
|
+
files: number;
|
|
28
|
+
/** Total decompressed bytes written. */
|
|
29
|
+
bytes: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const PACKAGE_MANIFEST = 'package.json';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stream a gzipped npm archive into a package directory. Entry names are
|
|
36
|
+
* already canonical and prefix-stripped by streamTarEntries. Hold only the
|
|
37
|
+
* current entry and the manifest; write the manifest last so a failed install
|
|
38
|
+
* is not mistaken for a complete package on retry. A second manifest in one
|
|
39
|
+
* archive is a malformed package, not an overwrite. Filesystem failures reject.
|
|
40
|
+
*/
|
|
41
|
+
export async function writeTarballStream(
|
|
42
|
+
body: ReadableStream<Uint8Array>,
|
|
43
|
+
targetDir: string,
|
|
44
|
+
vfs: TarballWriteTarget,
|
|
45
|
+
): Promise<TarballWriteResult> {
|
|
46
|
+
const ensureDir = (path: string): void => {
|
|
47
|
+
if (!vfs.exists(path)) vfs.mkdir(path, { recursive: true });
|
|
48
|
+
};
|
|
49
|
+
ensureDir(targetDir);
|
|
50
|
+
let files = 0;
|
|
51
|
+
let bytes = 0;
|
|
52
|
+
let manifest: Uint8Array | null = null;
|
|
53
|
+
const entries = streamTarEntries(
|
|
54
|
+
readableStreamToAsyncIterable(body.pipeThrough(new DecompressionStream('gzip'))),
|
|
55
|
+
);
|
|
56
|
+
for await (const entry of entries) {
|
|
57
|
+
if (entry.name === PACKAGE_MANIFEST) {
|
|
58
|
+
if (manifest !== null) {
|
|
59
|
+
throw new Error(`tarball for ${targetDir} carried two ${PACKAGE_MANIFEST} entries`);
|
|
60
|
+
}
|
|
61
|
+
manifest = entry.data;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const fullPath = `${targetDir}/${entry.name}`;
|
|
65
|
+
const cut = fullPath.lastIndexOf('/');
|
|
66
|
+
if (cut > 0) ensureDir(fullPath.slice(0, cut));
|
|
67
|
+
await vfs.writeFile(fullPath, entry.data);
|
|
68
|
+
files++;
|
|
69
|
+
bytes += entry.data.length;
|
|
70
|
+
}
|
|
71
|
+
if (!manifest) throw new Error(`tarball for ${targetDir} carried no ${PACKAGE_MANIFEST}`);
|
|
72
|
+
await vfs.writeFile(`${targetDir}/${PACKAGE_MANIFEST}`, manifest);
|
|
73
|
+
return { files: files + 1, bytes: bytes + manifest.length };
|
|
74
|
+
}
|
|
75
|
+
|
|
21
76
|
/** Extract every regular file. Gzipped input is decompressed first. */
|
|
22
77
|
export async function extractTarball(
|
|
23
78
|
tarball: ArrayBuffer,
|