@nimbus-sh/core 0.4.0 → 0.6.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 +35 -7
- package/dist/_shared/cache-stats.d.ts +23 -1
- package/dist/_shared/cache-stats.d.ts.map +1 -1
- package/dist/_shared/cache-stats.js +13 -1
- package/dist/_shared/error-text.d.ts +15 -0
- package/dist/_shared/error-text.d.ts.map +1 -0
- package/dist/_shared/error-text.js +21 -0
- package/dist/_shared/exports-resolver.d.ts +19 -7
- package/dist/_shared/exports-resolver.d.ts.map +1 -1
- package/dist/_shared/exports-resolver.js +1 -1
- package/dist/_shared/retry.d.ts.map +1 -1
- package/dist/_shared/retry.js +4 -2
- package/dist/constants.d.ts +5 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +23 -49
- package/dist/runtime/assets-loader.js +1 -1
- package/dist/runtime/esbuild-service.d.ts +4 -3
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +9 -4
- package/dist/runtime/installed-runtimes.d.ts +4 -3
- package/dist/runtime/installed-runtimes.d.ts.map +1 -1
- package/dist/runtime/long-running-handle.d.ts +3 -3
- package/dist/runtime/long-running-handle.d.ts.map +1 -1
- package/dist/runtime/os-contracts.d.ts +7 -5
- package/dist/runtime/os-contracts.d.ts.map +1 -1
- package/dist/runtime/port-registry.d.ts +28 -0
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +79 -9
- package/dist/runtime/process-logs.d.ts +9 -1
- package/dist/runtime/process-logs.d.ts.map +1 -1
- package/dist/runtime/project-detect.d.ts +6 -1
- package/dist/runtime/project-detect.d.ts.map +1 -1
- package/dist/runtime/require-resolver.d.ts.map +1 -1
- package/dist/runtime/require-resolver.js +3 -4
- package/dist/runtime/runtime-registry.d.ts +4 -3
- package/dist/runtime/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/runtime-registry.js +2 -1
- package/dist/runtime/sqlite-runtime-fs-bridge.js +1 -5
- package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.d.ts +2 -2
- package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.js +3 -3
- package/dist/runtime/virtual-socket-kernel.js +10 -5
- package/dist/runtime/wasm-runner.d.ts.map +1 -1
- package/dist/runtime/wasm-runner.js +22 -14
- package/dist/shell/features.d.ts.map +1 -1
- package/dist/shell/features.js +5 -4
- package/dist/shell/unix-commands.d.ts +14 -1
- package/dist/shell/unix-commands.d.ts.map +1 -1
- package/dist/shell/unix-commands.js +115 -69
- package/dist/substrate/lifo/commands/net/ip.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/system/ps.d.ts +2 -1
- package/dist/substrate/lifo/commands/system/ps.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/ServiceManager.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/ServiceManager.js +0 -2
- package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts +22 -11
- package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/network/NetworkStack.js +0 -2
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts +2 -1
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.js +3 -4
- package/dist/substrate/lifo/kernel/vfs/VFS.d.ts +17 -0
- package/dist/substrate/lifo/kernel/vfs/VFS.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/vfs/VFS.js +33 -0
- package/dist/substrate/lifo/node-compat/esbuild.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/esbuild.js +2 -4
- package/dist/substrate/lifo/node-compat/index.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/index.js +3 -2
- package/dist/substrate/lifo/node-compat/loose-equality.d.ts +19 -0
- package/dist/substrate/lifo/node-compat/loose-equality.d.ts.map +1 -0
- package/dist/substrate/lifo/node-compat/loose-equality.js +91 -0
- package/dist/substrate/lifo/node-compat/process.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/process.js +6 -3
- package/dist/substrate/lifo/shell/Shell.d.ts +19 -0
- package/dist/substrate/lifo/shell/Shell.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/Shell.js +86 -75
- package/dist/substrate/lifo/shell/lexer.d.ts +9 -0
- package/dist/substrate/lifo/shell/lexer.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/lexer.js +40 -0
- package/dist/vfs/seed-project.d.ts.map +1 -1
- package/dist/vfs/seed-project.js +9 -3
- package/dist/vfs/sqlite-vfs.d.ts +63 -31
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +206 -41
- package/package.json +3 -2
- package/src/_shared/cache-stats.ts +27 -1
- package/src/_shared/error-text.ts +21 -0
- package/src/_shared/exports-resolver.ts +24 -4
- package/src/_shared/retry.ts +5 -3
- package/src/constants.ts +37 -53
- package/src/runtime/assets-loader.ts +1 -1
- package/src/runtime/esbuild-service.ts +16 -11
- package/src/runtime/installed-runtimes.ts +4 -3
- package/src/runtime/long-running-handle.ts +3 -3
- package/src/runtime/os-contracts.ts +7 -5
- package/src/runtime/port-registry.ts +97 -9
- package/src/runtime/process-logs.ts +12 -3
- package/src/runtime/project-detect.ts +7 -1
- package/src/runtime/require-resolver.ts +7 -7
- package/src/runtime/runtime-registry.ts +9 -7
- package/src/runtime/sqlite-runtime-fs-bridge.ts +9 -6
- package/src/runtime/virtual-socket-kernel.generated.ts +3 -3
- package/src/runtime/virtual-socket-kernel.ts +10 -5
- package/src/runtime/wasm-runner.ts +55 -29
- package/src/shell/features.ts +5 -4
- package/src/shell/unix-commands.ts +273 -128
- package/src/substrate/lifo/commands/net/ip.ts +15 -15
- package/src/substrate/lifo/commands/system/ps.ts +2 -1
- package/src/substrate/lifo/kernel/ServiceManager.ts +0 -2
- package/src/substrate/lifo/kernel/network/NetworkStack.ts +24 -17
- package/src/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.ts +37 -12
- package/src/substrate/lifo/kernel/vfs/VFS.ts +34 -0
- package/src/substrate/lifo/node-compat/esbuild.ts +16 -8
- package/src/substrate/lifo/node-compat/index.ts +3 -2
- package/src/substrate/lifo/node-compat/loose-equality.ts +81 -0
- package/src/substrate/lifo/node-compat/process.ts +5 -2
- package/src/substrate/lifo/shell/Shell.ts +92 -70
- package/src/substrate/lifo/shell/lexer.ts +38 -0
- package/src/vfs/seed-project.ts +13 -6
- package/src/vfs/sqlite-vfs.ts +233 -77
- package/dist/_shared/install-phase.d.ts +0 -47
- package/dist/_shared/install-phase.d.ts.map +0 -1
- package/dist/_shared/install-phase.js +0 -1
- package/dist/_shared/rpc-dispose.d.ts +0 -4
- package/dist/_shared/rpc-dispose.d.ts.map +0 -1
- package/dist/_shared/rpc-dispose.js +0 -30
- package/dist/_shared/w7-frame.d.ts +0 -79
- package/dist/_shared/w7-frame.d.ts.map +0 -1
- package/dist/_shared/w7-frame.js +0 -839
- package/dist/_shared/weighted-credit-pool.d.ts +0 -61
- package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
- package/dist/_shared/weighted-credit-pool.js +0 -196
- package/dist/observability/diag-counters.d.ts +0 -236
- package/dist/observability/diag-counters.d.ts.map +0 -1
- package/dist/observability/diag-counters.js +0 -195
- package/dist/observability/heap-estimate.d.ts +0 -225
- package/dist/observability/heap-estimate.d.ts.map +0 -1
- package/dist/observability/heap-estimate.js +0 -185
- package/dist/observability/heavy-alloc-coord.d.ts +0 -93
- package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
- package/dist/observability/heavy-alloc-coord.js +0 -181
- package/dist/observability/oom-classify.d.ts +0 -138
- package/dist/observability/oom-classify.d.ts.map +0 -1
- package/dist/observability/oom-classify.js +0 -261
- package/dist/observability/oom-discriminator.d.ts +0 -176
- package/dist/observability/oom-discriminator.d.ts.map +0 -1
- package/dist/observability/oom-discriminator.js +0 -272
- package/src/_shared/install-phase.ts +0 -56
- package/src/_shared/rpc-dispose.ts +0 -31
- package/src/_shared/w7-frame.ts +0 -1012
- package/src/_shared/weighted-credit-pool.ts +0 -244
- package/src/observability/diag-counters.ts +0 -343
- package/src/observability/heap-estimate.ts +0 -331
- package/src/observability/heavy-alloc-coord.ts +0 -241
- package/src/observability/oom-classify.ts +0 -253
- package/src/observability/oom-discriminator.ts +0 -405
package/src/constants.ts
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* constants.ts — Single source of truth for all Nimbus configuration.
|
|
3
|
+
*
|
|
4
|
+
* Nimbus POLICY only. The measured platform limits these policies are
|
|
5
|
+
* derived from (tx bounds, the RPC envelope, supervisor budgets) live in
|
|
6
|
+
* `@nimbus-sh/platform/limits.js`.
|
|
3
7
|
*/
|
|
4
8
|
|
|
9
|
+
import { MAX_RPC_SAFE_PAYLOAD_BYTES } from '@nimbus-sh/platform/limits.js';
|
|
10
|
+
|
|
11
|
+
// Published-surface forwarding: @nimbus-sh/core@0.5.0 exports these from
|
|
12
|
+
// this module and real consumers import them here (Proteus merge-back.ts
|
|
13
|
+
// pulls CHUNK_SIZE and the MAX_TX_* set). The platform split moved the
|
|
14
|
+
// definitions to @nimbus-sh/platform/limits.js, which stays the single
|
|
15
|
+
// source of truth; core's own modules import platform directly.
|
|
16
|
+
export {
|
|
17
|
+
CHUNK_SIZE,
|
|
18
|
+
MAX_GLOBAL_WRITE_STREAM_CREDIT_BYTES,
|
|
19
|
+
MAX_RPC_SAFE_PAYLOAD_BYTES,
|
|
20
|
+
MAX_TX_BLOB_BYTES,
|
|
21
|
+
MAX_TX_LOGICAL_ROWS,
|
|
22
|
+
MAX_TX_SQL_EXECS,
|
|
23
|
+
PRE_BUNDLE_CONCURRENCY,
|
|
24
|
+
PRE_BUNDLE_SLICE_CAP_BYTES,
|
|
25
|
+
SUPERVISOR_HEAP_CEILING_BYTES,
|
|
26
|
+
SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES,
|
|
27
|
+
SUPERVISOR_READ_RESERVE_BYTES,
|
|
28
|
+
} from '@nimbus-sh/platform/limits.js';
|
|
29
|
+
|
|
5
30
|
// ── Versions ────────────────────────────────────────────────────────────
|
|
6
31
|
export const NIMBUS_VERSION = '2.0.0';
|
|
7
32
|
//
|
|
@@ -55,19 +80,11 @@ export const SQLJS_VERSION = '1.14.1';
|
|
|
55
80
|
export const OPENCODE_VERSION = '1.16.2';
|
|
56
81
|
|
|
57
82
|
// ── VFS Constants ───────────────────────────────────────────────────────
|
|
58
|
-
|
|
83
|
+
// The chunk size itself (CHUNK_SIZE) and the transaction bounds are
|
|
84
|
+
// platform limits — see @nimbus-sh/platform/limits.js.
|
|
59
85
|
export const LRU_MAX_ENTRIES = 512; // 512 × 64KB = 32MB hot cache
|
|
60
86
|
export const BATCH_SIZE = 64; // rows per batch INSERT
|
|
61
87
|
export const VFS_CAPACITY = 10 * 1024 * 1024 * 1024; // 10 GB
|
|
62
|
-
export const MAX_TX_BLOB_BYTES = 1 * 1024 * 1024;
|
|
63
|
-
export const MAX_TX_LOGICAL_ROWS = 256;
|
|
64
|
-
export const MAX_TX_SQL_EXECS = 64;
|
|
65
|
-
export const MAX_GLOBAL_WRITE_STREAM_CREDIT_BYTES = 8 * 1024 * 1024;
|
|
66
|
-
|
|
67
|
-
// Largest byte payload Nimbus sends through an ordinary Workers RPC value.
|
|
68
|
-
// The platform limit is 32 MiB; 28 MiB leaves room for structured-clone
|
|
69
|
-
// metadata and matches the proven on-demand facet transfer envelope.
|
|
70
|
-
export const MAX_RPC_SAFE_PAYLOAD_BYTES = 28 * 1024 * 1024;
|
|
71
88
|
|
|
72
89
|
// ── Batched filesystem reads ────────────────────────────────────────────
|
|
73
90
|
// A round trip costs an order of magnitude more than the SQLite lookup
|
|
@@ -142,10 +159,13 @@ export const FACET_TIMEOUT_MS = 30_000; // 30s execution timeout
|
|
|
142
159
|
// JavaScript, so workerd's per-module text-size limit applies to the
|
|
143
160
|
// JSON-escaped form (each `\n` / `\"` / `\u` adds bytes, plus the
|
|
144
161
|
// per-key string-quote overhead).
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
162
|
+
// Measured: 8 MiB raw failed to boot; the passing raw bound was measured
|
|
163
|
+
// too, but the number was lost to comment truncation before it ever
|
|
164
|
+
// reached git, so only the failing side is known. Encoded as JSON that's
|
|
165
|
+
// roughly 18-25 MiB of module text. We target 22 MiB encoded as the hard
|
|
166
|
+
// ceiling, leaving ~2-3 MiB of headroom for the rest of the worker module
|
|
167
|
+
// (shims, runner boot code) and any minor drift in the eviction loop's
|
|
168
|
+
// accounting.
|
|
149
169
|
//
|
|
150
170
|
// facet-manager.ts:buildPrefetchBundle uses TextEncoder().encode().length
|
|
151
171
|
// to measure exact UTF-8 bytes (not JS string .length, which counts UTF-16
|
|
@@ -198,11 +218,12 @@ export const PREFETCH_CACHE_MAX_BYTES = 16 * 1024 * 1024;
|
|
|
198
218
|
export const CWD_SNAPSHOT_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
199
219
|
|
|
200
220
|
// ── npm Constants ───────────────────────────────────────────────────────
|
|
221
|
+
// The pre-bundle admission envelope (PRE_BUNDLE_SLICE_CAP_BYTES,
|
|
222
|
+
// PRE_BUNDLE_CONCURRENCY) is a measured platform envelope — see
|
|
223
|
+
// @nimbus-sh/platform/limits.js.
|
|
201
224
|
export const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
202
225
|
export const NPM_CONCURRENCY = 12;
|
|
203
226
|
export const NPM_DECOMPRESS_TIMEOUT = 15_000;
|
|
204
|
-
export const PRE_BUNDLE_SLICE_CAP_BYTES = MAX_RPC_SAFE_PAYLOAD_BYTES;
|
|
205
|
-
export const PRE_BUNDLE_CONCURRENCY = 1;
|
|
206
227
|
|
|
207
228
|
// ── Dev Server Constants ────────────────────────────────────────────────
|
|
208
229
|
export const DEFAULT_VITE_PORT = 5173;
|
|
@@ -222,43 +243,6 @@ export const NIMBUS_AI_GATEWAY_PORT = 8790;
|
|
|
222
243
|
// ── Compatibility ───────────────────────────────────────────────────────
|
|
223
244
|
export const CF_COMPAT_DATE = '2026-04-01';
|
|
224
245
|
|
|
225
|
-
// ── Supervisor heap budget [C'.1] ───────────────────────────────────────
|
|
226
|
-
//
|
|
227
|
-
// The supervisor isolate's 128 MiB workerd cap is a HARD platform ceiling
|
|
228
|
-
// (per docs/research/cf-primitives-dossier.md §6 invariant I1 — 128 MiB
|
|
229
|
-
// per V8 isolate, may be shared across same-class peer DOs co-tenanting in
|
|
230
|
-
// one process). Nimbus targets HALF of that as a self-imposed soft ceiling
|
|
231
|
-
// so the supervisor always has runway when workerd LRU-evicts neighbours
|
|
232
|
-
// or AIR (Asynchronous Isolate Recreation) folds growing isolates.
|
|
233
|
-
//
|
|
234
|
-
// 64 MiB is a budget, not a measurement, and nothing enforces it.
|
|
235
|
-
// src/observability/heap-estimate.ts sums the INSTRUMENTED contributors —
|
|
236
|
-
// the supervisor baseline, VFS LRU and in-flight writes, pre-bundle slices,
|
|
237
|
-
// and streaming RPC buffers — which is a lower bound, not full coverage.
|
|
238
|
-
// The prefetch-bundle path in facets/manager.ts allocates against this
|
|
239
|
-
// budget without accounting for it; see HEAP_BLIND_SPOTS for the current
|
|
240
|
-
// gap. Read a low percentOfCeiling accordingly.
|
|
241
|
-
export const SUPERVISOR_HEAP_CEILING_BYTES = 64 * 1024 * 1024;
|
|
242
|
-
|
|
243
|
-
// Shared allowance for transient allocations in the supervisor DO. With the
|
|
244
|
-
// VFS LRU shrunk to 8 MiB during an active reservation, 40 MiB of admitted
|
|
245
|
-
// payload plus the 9 MiB bundle baseline stays below the 64 MiB soft ceiling
|
|
246
|
-
// with 7 MiB left for metadata, structured-clone overhead, and runtime state.
|
|
247
|
-
// This is 31.25% of the platform's 128 MiB hard isolate ceiling.
|
|
248
|
-
export const SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES = 40 * 1024 * 1024;
|
|
249
|
-
|
|
250
|
-
// Reserved slice of that allowance for chunk-sized filesystem reads, so a
|
|
251
|
-
// read is never queued behind a multi-megabyte owner for a wait that has
|
|
252
|
-
// nothing to do with its own cost. A read only draws on this when the shared
|
|
253
|
-
// budget is already contended — which is exactly when the VFS LRU is shrunk
|
|
254
|
-
// to 8 MiB — so peak accounting is 40 + 1 admitted, 8 LRU, 9 bundle baseline:
|
|
255
|
-
// 58 MiB, still under the 64 MiB soft ceiling.
|
|
256
|
-
//
|
|
257
|
-
// 1 MiB is 16 concurrent READ_STREAM_CHUNK_BYTES reads. The point is not
|
|
258
|
-
// depth, it is that a read loop keeps moving while a heavy owner works
|
|
259
|
-
// instead of stopping dead for the duration.
|
|
260
|
-
export const SUPERVISOR_READ_RESERVE_BYTES = 1024 * 1024;
|
|
261
|
-
|
|
262
246
|
// ── OS Defaults ─────────────────────────────────────────────────────────
|
|
263
247
|
export const DEFAULT_HOSTNAME = 'nimbus';
|
|
264
248
|
export const DEFAULT_HOME = '/home/user';
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
import type { CredentialedVfs, SqliteVFS } from '../vfs/sqlite-vfs.js';
|
|
21
21
|
import { CRED_KERNEL } from './os-contracts.js';
|
|
22
|
-
import { resolvePackageEntry, resolveExports } from '../_shared/exports-resolver.js';
|
|
22
|
+
import { resolvePackageEntry, resolveExports, type ResolvablePackageJson } from '../_shared/exports-resolver.js';
|
|
23
23
|
import { normalizeVfsPath, stripLeadingSlashes } from '../vfs/path.js';
|
|
24
|
+
import { errorText } from '../_shared/error-text.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Bundler version tag. BUMP THIS whenever bundling semantics change —
|
|
@@ -175,7 +176,6 @@ export function hasTopLevelAwait(src: string): boolean {
|
|
|
175
176
|
// inside `(...)`; a `{` only opens a function body when a `function`/
|
|
176
177
|
// `=>` is pending AND we're at parenDepth 0 (past the param list).
|
|
177
178
|
const re = /\b(await|function|class)\b|=>|\{|\}|\(|\)/g;
|
|
178
|
-
let m: RegExpExecArray | null;
|
|
179
179
|
const fnEntryDepths: number[] = [];
|
|
180
180
|
let depth = 0;
|
|
181
181
|
let parenDepth = 0;
|
|
@@ -189,7 +189,7 @@ export function hasTopLevelAwait(src: string): boolean {
|
|
|
189
189
|
// `x => ({ ... })` (object-returning expression body, `{` at parenDepth
|
|
190
190
|
// 1) does not leak a body slot onto a later top-level block.
|
|
191
191
|
let arrowPending = false;
|
|
192
|
-
|
|
192
|
+
for (let m = re.exec(stripped); m !== null; m = re.exec(stripped)) {
|
|
193
193
|
const tok = m[0];
|
|
194
194
|
if (arrowPending && tok !== '{') arrowPending = false;
|
|
195
195
|
if (tok === '(') { parenDepth++; }
|
|
@@ -803,7 +803,7 @@ async function loadEsbuild(): Promise<typeof esbuild> {
|
|
|
803
803
|
export interface TransformResult {
|
|
804
804
|
code: string;
|
|
805
805
|
map: string;
|
|
806
|
-
warnings: { text: string; location?:
|
|
806
|
+
warnings: { text: string; location?: esbuild.Location | null }[];
|
|
807
807
|
}
|
|
808
808
|
|
|
809
809
|
export interface BuildOutputFile {
|
|
@@ -813,8 +813,8 @@ export interface BuildOutputFile {
|
|
|
813
813
|
|
|
814
814
|
export interface BuildResult {
|
|
815
815
|
outputFiles: BuildOutputFile[];
|
|
816
|
-
errors: { text: string; location?:
|
|
817
|
-
warnings: { text: string; location?:
|
|
816
|
+
errors: { text: string; location?: esbuild.Location | null }[];
|
|
817
|
+
warnings: { text: string; location?: esbuild.Location | null }[];
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
// ── EsbuildService ──────────────────────────────────────────────────────
|
|
@@ -876,7 +876,11 @@ export class EsbuildService {
|
|
|
876
876
|
let initTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
877
877
|
await Promise.race([
|
|
878
878
|
esb.initialize({
|
|
879
|
-
|
|
879
|
+
// wrangler resolves this static `.wasm` import to a compiled
|
|
880
|
+
// module at bundle time; the asset stub for a `.wasm` module can
|
|
881
|
+
// only declare its default export as a string, and the guard
|
|
882
|
+
// above is what checks the resolution actually happened.
|
|
883
|
+
wasmModule: esbuildWasmUrl as unknown as WebAssembly.Module,
|
|
880
884
|
worker: false,
|
|
881
885
|
}),
|
|
882
886
|
new Promise<never>((_, reject) => {
|
|
@@ -890,14 +894,15 @@ export class EsbuildService {
|
|
|
890
894
|
}),
|
|
891
895
|
]).finally(() => { if (initTimeout) clearTimeout(initTimeout); });
|
|
892
896
|
this.initialized = true;
|
|
893
|
-
} catch (e
|
|
897
|
+
} catch (e) {
|
|
898
|
+
const message = errorText(e);
|
|
894
899
|
// "Cannot call initialize more than once" means it's already ready
|
|
895
|
-
if (
|
|
900
|
+
if (message.includes('more than once')) {
|
|
896
901
|
this.initialized = true;
|
|
897
902
|
return;
|
|
898
903
|
}
|
|
899
904
|
this.initPromise = null;
|
|
900
|
-
throw new Error('esbuild init failed: ' +
|
|
905
|
+
throw new Error('esbuild init failed: ' + message);
|
|
901
906
|
}
|
|
902
907
|
})();
|
|
903
908
|
|
|
@@ -1315,7 +1320,7 @@ export class EsbuildService {
|
|
|
1315
1320
|
if (vfs.exists(strip(nmDir)) && vfs.isDirectory(strip(nmDir))) {
|
|
1316
1321
|
// Read package.json so we can consult the exports field.
|
|
1317
1322
|
const pkgJsonPath = nmDir + '/package.json';
|
|
1318
|
-
let pkgJson:
|
|
1323
|
+
let pkgJson: ResolvablePackageJson | null = null;
|
|
1319
1324
|
if (vfs.exists(strip(pkgJsonPath))) {
|
|
1320
1325
|
try { pkgJson = JSON.parse(vfs.readFileString(strip(pkgJsonPath))); } catch {}
|
|
1321
1326
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import type { CredentialedVfs, SqliteVFS } from '../vfs/sqlite-vfs.js';
|
|
20
|
+
import type { Command } from '../substrate/lifo/commands/types.js';
|
|
20
21
|
import {
|
|
21
22
|
CRED_KERNEL,
|
|
22
23
|
NIMBUS_ABI_TARGET,
|
|
@@ -32,9 +33,9 @@ import {
|
|
|
32
33
|
|
|
33
34
|
/** Minimal shell-registry shape we depend on. */
|
|
34
35
|
export interface MinShellRegistry {
|
|
35
|
-
register(name: string, handler:
|
|
36
|
+
register(name: string, handler: Command): void;
|
|
36
37
|
unregister?(name: string): void;
|
|
37
|
-
resolve?(name: string):
|
|
38
|
+
resolve?(name: string): Promise<Command | null | undefined> | Command | null | undefined;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
/** Runner-factory contract. Each registered runner produces a shell-
|
|
@@ -46,7 +47,7 @@ export type RunnerFactory = (
|
|
|
46
47
|
installRoot: string,
|
|
47
48
|
binName: string,
|
|
48
49
|
binKind: string | undefined,
|
|
49
|
-
) =>
|
|
50
|
+
) => Command;
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* How a manifest entrypoint's `runner` key is resolved to code.
|
|
@@ -107,7 +107,7 @@ export function parsePortFromArgv(argv: string[]): number | null {
|
|
|
107
107
|
*/
|
|
108
108
|
export function resolveLongRunningPort(opts: {
|
|
109
109
|
argv?: string[];
|
|
110
|
-
env?: Record<string,
|
|
110
|
+
env?: Record<string, string> | undefined;
|
|
111
111
|
configPort?: number | undefined;
|
|
112
112
|
fallback: number;
|
|
113
113
|
}): number {
|
|
@@ -152,7 +152,7 @@ export function resolveLongRunningPort(opts: {
|
|
|
152
152
|
*/
|
|
153
153
|
export function expandShellDefaults(
|
|
154
154
|
token: string,
|
|
155
|
-
env: Record<string,
|
|
155
|
+
env: Record<string, string> | undefined,
|
|
156
156
|
): string {
|
|
157
157
|
if (!token || token.indexOf('${') < 0) return token;
|
|
158
158
|
const lookup = (name: string): string | undefined => {
|
|
@@ -179,7 +179,7 @@ export function expandShellDefaults(
|
|
|
179
179
|
*/
|
|
180
180
|
export function expandArgvShellDefaults(
|
|
181
181
|
argv: ReadonlyArray<string>,
|
|
182
|
-
env: Record<string,
|
|
182
|
+
env: Record<string, string> | undefined,
|
|
183
183
|
): string[] {
|
|
184
184
|
return argv.map((t) => expandShellDefaults(t, env));
|
|
185
185
|
}
|
|
@@ -312,11 +312,12 @@ export interface RuntimeTtyOptions {
|
|
|
312
312
|
/**
|
|
313
313
|
* Where a registered port's traffic goes: one process, reached by HTTP.
|
|
314
314
|
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
315
|
+
* Ordinary HTTP may cross Workers RPC as Request/Response values. A WebSocket
|
|
316
|
+
* upgrade may not: its 101 Response owns a live socket, and RPC's
|
|
317
|
+
* Request/Response transport reconstructs the value rather than handing over
|
|
318
|
+
* the socket. So a target that can serve an upgrade exposes a separate
|
|
319
|
+
* fetch-semantic entrypoint, and every hop of an upgrade stays on the HTTP
|
|
320
|
+
* service-binding path. A target that omits it serves HTTP only.
|
|
320
321
|
*
|
|
321
322
|
* Lives here rather than beside the registry because both ends need it and
|
|
322
323
|
* neither owns it: the port registry stores these, and the process fabric
|
|
@@ -325,6 +326,7 @@ export interface RuntimeTtyOptions {
|
|
|
325
326
|
*/
|
|
326
327
|
export interface RouteableFacetTarget {
|
|
327
328
|
handleHttpRequest(request: Request): Promise<Response>;
|
|
329
|
+
handleWebSocketRequest?(request: Request): Promise<Response>;
|
|
328
330
|
}
|
|
329
331
|
|
|
330
332
|
export interface RuntimePortBridge {
|
|
@@ -40,6 +40,17 @@ export interface PortEntry {
|
|
|
40
40
|
*/
|
|
41
41
|
facetStub: RouteableFacetTarget | null;
|
|
42
42
|
registeredAt: number;
|
|
43
|
+
/**
|
|
44
|
+
* Unguessable token for the lifetime of THIS registration, so an embedder
|
|
45
|
+
* can hand out one port's traffic without handing out the session. A fresh
|
|
46
|
+
* one per `register`, which is what makes an unexposed port stay unexposed.
|
|
47
|
+
*/
|
|
48
|
+
capability: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createPortCapability(): string {
|
|
52
|
+
const bytes = crypto.getRandomValues(new Uint8Array(12));
|
|
53
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
export class PortRegistry {
|
|
@@ -64,7 +75,13 @@ export class PortRegistry {
|
|
|
64
75
|
*/
|
|
65
76
|
register(port: number, pid: number): void {
|
|
66
77
|
const target = this.facetStubsByPid.get(pid) ?? null;
|
|
67
|
-
this.ports.set(port, {
|
|
78
|
+
this.ports.set(port, {
|
|
79
|
+
port,
|
|
80
|
+
pid,
|
|
81
|
+
facetStub: target,
|
|
82
|
+
registeredAt: Date.now(),
|
|
83
|
+
capability: createPortCapability(),
|
|
84
|
+
});
|
|
68
85
|
this.notifyPortWaiters(pid);
|
|
69
86
|
}
|
|
70
87
|
|
|
@@ -126,6 +143,24 @@ export class PortRegistry {
|
|
|
126
143
|
return [...this.ports.values()];
|
|
127
144
|
}
|
|
128
145
|
|
|
146
|
+
/** Answer only whether this capability matches, never what is registered. */
|
|
147
|
+
hasCapability(port: number, capability: string): boolean {
|
|
148
|
+
return this.ports.get(port)?.capability === capability;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Re-adopt a capability the embedder was already handed, after the supervisor
|
|
153
|
+
* was rebuilt. A restored dev server is a NEW registration with a new token,
|
|
154
|
+
* which would silently invalidate every preview URL already in circulation
|
|
155
|
+
* across an eviction — so the durable value wins over the fresh one.
|
|
156
|
+
*/
|
|
157
|
+
restoreCapability(port: number, capability: string): boolean {
|
|
158
|
+
const entry = this.ports.get(port);
|
|
159
|
+
if (!entry || !/^[a-f0-9]{24}$/.test(capability)) return false;
|
|
160
|
+
entry.capability = capability;
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
129
164
|
/**
|
|
130
165
|
* Forward an HTTP request to the facet owning a port.
|
|
131
166
|
*
|
|
@@ -148,6 +183,36 @@ export class PortRegistry {
|
|
|
148
183
|
* client receives.
|
|
149
184
|
*/
|
|
150
185
|
async routeRequest(port: number, request: Request, pathname: string): Promise<Response | null> {
|
|
186
|
+
return this.routeRequestInternal(port, request, pathname, false);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Route a request a trusted embedder has already authenticated against the
|
|
191
|
+
* port's capability and stripped its own credentials from.
|
|
192
|
+
*
|
|
193
|
+
* Unlike the generic route, this one PRESERVES `Authorization`. The generic
|
|
194
|
+
* route strips it because the header it sees is Nimbus's own, and handing a
|
|
195
|
+
* session credential to untrusted code is the thing that must never happen.
|
|
196
|
+
* Here the embedder has removed that credential already and what remains
|
|
197
|
+
* belongs to the guest application, which needs it to authenticate its own
|
|
198
|
+
* users.
|
|
199
|
+
*/
|
|
200
|
+
async routeCapabilityRequest(
|
|
201
|
+
port: number,
|
|
202
|
+
capability: string,
|
|
203
|
+
request: Request,
|
|
204
|
+
pathname: string,
|
|
205
|
+
): Promise<Response | null> {
|
|
206
|
+
if (!this.hasCapability(port, capability)) return null;
|
|
207
|
+
return this.routeRequestInternal(port, request, pathname, true);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private async routeRequestInternal(
|
|
211
|
+
port: number,
|
|
212
|
+
request: Request,
|
|
213
|
+
pathname: string,
|
|
214
|
+
preserveAuthorization: boolean,
|
|
215
|
+
): Promise<Response | null> {
|
|
151
216
|
const entry = this.ports.get(port);
|
|
152
217
|
// Honour the documented contract: no entry at all → null, so callers
|
|
153
218
|
// report "no process listening". (Pre-fix this fell into the 501 below,
|
|
@@ -189,7 +254,12 @@ export class PortRegistry {
|
|
|
189
254
|
// if a body is supplied on those methods.
|
|
190
255
|
const hasBody = request.method !== 'GET' && request.method !== 'HEAD';
|
|
191
256
|
const headers = new Headers(request.headers);
|
|
257
|
+
// The strip below removes Nimbus's own credentials. On a capability
|
|
258
|
+
// route the embedder has already removed those, so what is left belongs
|
|
259
|
+
// to the guest application and has to survive the hop.
|
|
260
|
+
const authorization = preserveAuthorization ? headers.get('authorization') : null;
|
|
192
261
|
sanitizeUntrustedHeaders(headers);
|
|
262
|
+
if (authorization) headers.set('authorization', authorization);
|
|
193
263
|
|
|
194
264
|
headers.set('X-Nimbus-Port', String(port));
|
|
195
265
|
// `duplex: 'half'` is required by workerd when body is a
|
|
@@ -205,11 +275,19 @@ export class PortRegistry {
|
|
|
205
275
|
if (hasBody) init.duplex = 'half';
|
|
206
276
|
const forwarded = new Request(innerUrl.toString(), init);
|
|
207
277
|
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
|
|
278
|
+
// HTTP crosses Workers RPC as Request/Response values, streamed with
|
|
279
|
+
// flow control and never materialised. A WebSocket upgrade cannot: its
|
|
280
|
+
// 101 owns a live socket, so it takes the fetch-semantic entrypoint,
|
|
281
|
+
// which every hop preserves. A target with no such entrypoint serves
|
|
282
|
+
// HTTP only, and says so rather than dropping the socket.
|
|
283
|
+
const isWebSocket = request.headers.get('upgrade')?.toLowerCase() === 'websocket';
|
|
284
|
+
const handler = isWebSocket
|
|
285
|
+
? entry.facetStub.handleWebSocketRequest
|
|
286
|
+
: entry.facetStub.handleHttpRequest;
|
|
287
|
+
if (!handler) {
|
|
288
|
+
return new Response('Port target does not expose a WebSocket fetch route', { status: 501 });
|
|
289
|
+
}
|
|
290
|
+
const response: Response = await handler(forwarded);
|
|
213
291
|
|
|
214
292
|
if (!(response instanceof Response)) {
|
|
215
293
|
// Defensive: if a facet ever returns something else (JSON
|
|
@@ -286,10 +364,20 @@ export class PortRegistry {
|
|
|
286
364
|
|
|
287
365
|
function routeableFacetTarget(value: unknown): RouteableFacetTarget | null {
|
|
288
366
|
if ((typeof value !== 'object' && typeof value !== 'function') || value === null) return null;
|
|
289
|
-
|
|
290
|
-
|
|
367
|
+
// A `fetch` target already has fetch semantics, so it serves both.
|
|
368
|
+
const fetchMethod = Reflect.get(value, 'fetch');
|
|
369
|
+
if (typeof fetchMethod === 'function') {
|
|
370
|
+
const fetch = fetchMethod.bind(value) as (request: Request) => Promise<Response>;
|
|
371
|
+
return { handleHttpRequest: fetch, handleWebSocketRequest: fetch };
|
|
372
|
+
}
|
|
373
|
+
const httpMethod = Reflect.get(value, 'handleHttpRequest');
|
|
374
|
+
if (typeof httpMethod !== 'function') return null;
|
|
375
|
+
const webSocketMethod = Reflect.get(value, 'handleWebSocketRequest');
|
|
291
376
|
return {
|
|
292
|
-
handleHttpRequest:
|
|
377
|
+
handleHttpRequest: httpMethod.bind(value),
|
|
378
|
+
...(typeof webSocketMethod === 'function'
|
|
379
|
+
? { handleWebSocketRequest: webSocketMethod.bind(value) }
|
|
380
|
+
: {}),
|
|
293
381
|
};
|
|
294
382
|
}
|
|
295
383
|
|
|
@@ -61,6 +61,15 @@ export interface SequencedLogChunk extends LogChunk {
|
|
|
61
61
|
seq: number;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* A chunk as a persist adapter hands it back on load. `seq` is the sequence
|
|
66
|
+
* number the adapter stored alongside it; hydration falls back to array
|
|
67
|
+
* position for a row that carries none.
|
|
68
|
+
*/
|
|
69
|
+
export interface PersistedLogChunk extends LogChunk {
|
|
70
|
+
seq?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
export interface ProcessLogReadOptions {
|
|
65
74
|
cursor?: number;
|
|
66
75
|
lines?: number;
|
|
@@ -130,7 +139,7 @@ interface PidState {
|
|
|
130
139
|
* against the SQLite engine); KV is not used by W9.
|
|
131
140
|
*/
|
|
132
141
|
export interface PersistAdapter {
|
|
133
|
-
load(pid: number): { chunks:
|
|
142
|
+
load(pid: number): { chunks: PersistedLogChunk[]; exit: ProcessExitInfo | null } | null;
|
|
134
143
|
persistChunks(pid: number, rows: { seq: number; chunk: LogChunk }[]): void;
|
|
135
144
|
persistExit(pid: number, info: ProcessExitInfo): void;
|
|
136
145
|
dropPid(pid: number): void;
|
|
@@ -710,7 +719,7 @@ export class ProcessLogStore {
|
|
|
710
719
|
if (state.hydrated) return;
|
|
711
720
|
state.hydrated = true;
|
|
712
721
|
if (!this._persist) return;
|
|
713
|
-
let loaded: { chunks:
|
|
722
|
+
let loaded: { chunks: PersistedLogChunk[]; exit: ProcessExitInfo | null } | null;
|
|
714
723
|
try {
|
|
715
724
|
loaded = this._persist.load(pid);
|
|
716
725
|
} catch {
|
|
@@ -736,7 +745,7 @@ export class ProcessLogStore {
|
|
|
736
745
|
let highestSeq = -1;
|
|
737
746
|
for (let i = loaded.chunks.length - 1; i >= 0; i--) {
|
|
738
747
|
const c = loaded.chunks[i];
|
|
739
|
-
const seq =
|
|
748
|
+
const seq = c.seq ?? i;
|
|
740
749
|
if (seq > highestSeq) highestSeq = seq;
|
|
741
750
|
const size = c.data.length;
|
|
742
751
|
if (bytes + size > this.perPidBytes && newest.length > 0) {
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* can add detectVite, detectNext, etc. here.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/** The filesystem reads a project probe needs. */
|
|
14
|
+
export interface ProjectProbeFs {
|
|
15
|
+
exists(path: string): boolean;
|
|
16
|
+
readFileString(path: string): string;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
/**
|
|
14
20
|
* W10: detect whether the project at `<root>` is a Cloudflare Workers
|
|
15
21
|
* project. Returns true if any of the standard markers are present:
|
|
@@ -18,7 +24,7 @@
|
|
|
18
24
|
* - <root>/wrangler.toml
|
|
19
25
|
* - <root>/package.json with `wrangler` in deps or devDeps
|
|
20
26
|
*/
|
|
21
|
-
export function detectCloudflareWorkersProject(vfs:
|
|
27
|
+
export function detectCloudflareWorkersProject(vfs: ProjectProbeFs, root: string): boolean {
|
|
22
28
|
const r = String(root).replace(/^\/+/, '').replace(/\/+$/, '');
|
|
23
29
|
for (const f of ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml']) {
|
|
24
30
|
try {
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
resolveExports as sharedResolveExports,
|
|
34
34
|
DEFAULT_CJS_CONDITIONS,
|
|
35
35
|
DEFAULT_ESM_CONDITIONS,
|
|
36
|
+
type ResolvablePackageJson,
|
|
36
37
|
} from '../_shared/exports-resolver.js';
|
|
37
38
|
import {
|
|
38
39
|
TYPESCRIPT_INDEX_CANDIDATES,
|
|
@@ -146,7 +147,7 @@ function resolveFile(vfs: CredentialedVfs, base: string, sink?: PkgJsonSink): st
|
|
|
146
147
|
const baseTrim = base.replace(/\/+$/, '');
|
|
147
148
|
const pkgJsonPath = normalizePath(baseTrim + '/package.json');
|
|
148
149
|
if (vfs.exists(pkgJsonPath) && !vfs.isDirectory(pkgJsonPath)) {
|
|
149
|
-
let pkg:
|
|
150
|
+
let pkg: ResolvablePackageJson | null = null;
|
|
150
151
|
try { pkg = JSON.parse(vfs.readFileString(pkgJsonPath)); } catch { /* fall through */ }
|
|
151
152
|
if (pkg && typeof pkg.main === 'string' && pkg.main.length > 0) {
|
|
152
153
|
// Record this package.json so the bundle carries the content the
|
|
@@ -257,7 +258,7 @@ function resolvePkgSubpathEx(vfs: CredentialedVfs, pkgDir: string, subpath: stri
|
|
|
257
258
|
// fallback (consistent behaviour across the no-pkgjson branch).
|
|
258
259
|
return tryLegacyDirectorySubpath(vfs, pkgDir, subpath, sink);
|
|
259
260
|
}
|
|
260
|
-
let pkg:
|
|
261
|
+
let pkg: ResolvablePackageJson;
|
|
261
262
|
try { pkg = JSON.parse(vfs.readFileString(pkgJsonPath)); }
|
|
262
263
|
catch {
|
|
263
264
|
const r = resolveFile(vfs, pkgDir + '/index', sink);
|
|
@@ -477,7 +478,7 @@ function resolveImportsField(
|
|
|
477
478
|
while (true) {
|
|
478
479
|
const pkgJsonPath = (dir ? dir + '/' : '') + 'package.json';
|
|
479
480
|
if (vfs.exists(pkgJsonPath) && !vfs.isDirectory(pkgJsonPath)) {
|
|
480
|
-
let pkg:
|
|
481
|
+
let pkg: ResolvablePackageJson | null = null;
|
|
481
482
|
try { pkg = JSON.parse(vfs.readFileString(pkgJsonPath)); } catch { /* malformed */ }
|
|
482
483
|
// First package.json wins (Node spec), even if no imports field.
|
|
483
484
|
if (pkg && pkg.imports) {
|
|
@@ -618,8 +619,7 @@ export function prefetchForRequire(
|
|
|
618
619
|
const stripped = stripCommentsForImports(code);
|
|
619
620
|
|
|
620
621
|
REQUIRE_RE.lastIndex = 0;
|
|
621
|
-
let match;
|
|
622
|
-
while ((match = REQUIRE_RE.exec(stripped)) !== null) {
|
|
622
|
+
for (let match = REQUIRE_RE.exec(stripped); match !== null; match = REQUIRE_RE.exec(stripped)) {
|
|
623
623
|
const specifier = match[2];
|
|
624
624
|
if (isFacetProvided(specifier)) continue;
|
|
625
625
|
const r = resolveRequireEx(vfs, specifier, fromDir, addPkgJson);
|
|
@@ -635,7 +635,7 @@ export function prefetchForRequire(
|
|
|
635
635
|
// runtime W3.5 Fix B's CJS rewrite calls require('./x') which then
|
|
636
636
|
// fails because `x` was never added.
|
|
637
637
|
IMPORT_RE.lastIndex = 0;
|
|
638
|
-
|
|
638
|
+
for (let match = IMPORT_RE.exec(stripped); match !== null; match = IMPORT_RE.exec(stripped)) {
|
|
639
639
|
const specifier = match[2];
|
|
640
640
|
if (isFacetProvided(specifier)) continue;
|
|
641
641
|
const r = resolveRequireEx(vfs, specifier, fromDir, addPkgJson);
|
|
@@ -647,7 +647,7 @@ export function prefetchForRequire(
|
|
|
647
647
|
// Follow static-string dynamic imports so a CLI entry that defers to
|
|
648
648
|
// import('./dist/index.js') has that subtree's content prefetched.
|
|
649
649
|
DYNIMPORT_RE.lastIndex = 0;
|
|
650
|
-
|
|
650
|
+
for (let match = DYNIMPORT_RE.exec(stripped); match !== null; match = DYNIMPORT_RE.exec(stripped)) {
|
|
651
651
|
const specifier = match[2];
|
|
652
652
|
if (isFacetProvided(specifier)) continue;
|
|
653
653
|
const r = resolveRequireEx(vfs, specifier, fromDir, addPkgJson);
|
|
@@ -43,6 +43,8 @@ import { CRED_KERNEL, type VfsCred } from './os-contracts.js';
|
|
|
43
43
|
import type { EsbuildService } from './esbuild-service.js';
|
|
44
44
|
import { parseFacetBundleProfile, type FacetBundleProfile } from './bundle-profile.js';
|
|
45
45
|
import { bindImportMetaResolve, importMetaDefines } from './import-meta-transform.js';
|
|
46
|
+
import type { Command, CommandContext } from '../substrate/lifo/commands/types.js';
|
|
47
|
+
import { errorText } from '../_shared/error-text.js';
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* Result shape that runtime-registry expects from a runner. Mirrors
|
|
@@ -130,7 +132,7 @@ export function resolveRuntimeScriptPath(
|
|
|
130
132
|
* a second one.
|
|
131
133
|
*/
|
|
132
134
|
export type RuntimeSubcommand = (
|
|
133
|
-
ctx:
|
|
135
|
+
ctx: CommandContext,
|
|
134
136
|
registry: ShellRegistry,
|
|
135
137
|
runAsRuntime: (args: string[]) => Promise<number>,
|
|
136
138
|
) => Promise<number>;
|
|
@@ -181,7 +183,7 @@ export interface RuntimeSpec {
|
|
|
181
183
|
* shell registry type tree when the runtime path only needs resolve().
|
|
182
184
|
*/
|
|
183
185
|
export interface ShellRegistry {
|
|
184
|
-
resolve(name: string): Promise<
|
|
186
|
+
resolve(name: string): Promise<Command | null | undefined> | Command | null | undefined;
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
/**
|
|
@@ -201,7 +203,7 @@ export function buildRuntimeHandler(
|
|
|
201
203
|
getEsbuild(): EsbuildService | Promise<EsbuildService>;
|
|
202
204
|
registry: ShellRegistry;
|
|
203
205
|
},
|
|
204
|
-
):
|
|
206
|
+
): Command {
|
|
205
207
|
const { vfs, getEsbuild, registry } = ctx0;
|
|
206
208
|
const fs = vfs.as(CRED_KERNEL);
|
|
207
209
|
|
|
@@ -211,7 +213,7 @@ export function buildRuntimeHandler(
|
|
|
211
213
|
* caller has already consumed them — so a verb handler can delegate
|
|
212
214
|
* back in with a rewritten argv without re-triggering itself.
|
|
213
215
|
*/
|
|
214
|
-
async function runtimeInvocation(ctx:
|
|
216
|
+
async function runtimeInvocation(ctx: CommandContext, args: string[]): Promise<number> {
|
|
215
217
|
const name = spec.name;
|
|
216
218
|
const nimbusCtx = ctx as {
|
|
217
219
|
__nimbusCaptureOutput?: unknown;
|
|
@@ -449,8 +451,8 @@ export function buildRuntimeHandler(
|
|
|
449
451
|
define: importMetaDefines(absUrl),
|
|
450
452
|
});
|
|
451
453
|
code = bindImportMetaResolve(transformed.code, absUrl);
|
|
452
|
-
} catch (e
|
|
453
|
-
ctx.stderr.write(`${name}: transform error for ${scriptPath}: ${e
|
|
454
|
+
} catch (e) {
|
|
455
|
+
ctx.stderr.write(`${name}: transform error for ${scriptPath}: ${errorText(e)}\n`);
|
|
454
456
|
return 1;
|
|
455
457
|
}
|
|
456
458
|
}
|
|
@@ -488,7 +490,7 @@ export function buildRuntimeHandler(
|
|
|
488
490
|
return result.exitCode;
|
|
489
491
|
}
|
|
490
492
|
|
|
491
|
-
return async function runtimeHandler(ctx:
|
|
493
|
+
return async function runtimeHandler(ctx: CommandContext): Promise<number> {
|
|
492
494
|
const args: string[] = ctx.args || [];
|
|
493
495
|
|
|
494
496
|
// ── Subcommand dispatch ──
|