@nimbus-sh/worker 0.8.0 → 0.10.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.
Files changed (124) hide show
  1. package/dist/esbuild-wasm-bundle.generated.d.ts +24 -24
  2. package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -1
  3. package/dist/esbuild-wasm-bundle.generated.js +24 -24
  4. package/dist/facets/cirrus-real.js +3 -3
  5. package/dist/facets/compose.d.ts +3 -0
  6. package/dist/facets/compose.d.ts.map +1 -1
  7. package/dist/facets/compose.js +10 -3
  8. package/dist/facets/esbuild-bundle-pool.d.ts +1 -1
  9. package/dist/facets/esbuild-bundle-pool.js +3 -3
  10. package/dist/facets/esbuild-transform.d.ts +6 -2
  11. package/dist/facets/esbuild-transform.d.ts.map +1 -1
  12. package/dist/facets/esbuild-transform.js +8 -4
  13. package/dist/facets/manager.d.ts +54 -15
  14. package/dist/facets/manager.d.ts.map +1 -1
  15. package/dist/facets/manager.js +227 -144
  16. package/dist/facets/real-vite-hmr.d.ts +2 -0
  17. package/dist/facets/real-vite-hmr.d.ts.map +1 -1
  18. package/dist/facets/real-vite-hmr.js +3 -2
  19. package/dist/git/commands.d.ts +17 -1
  20. package/dist/git/commands.d.ts.map +1 -1
  21. package/dist/git/commands.js +73 -9
  22. package/dist/git/network-facet.d.ts +2 -0
  23. package/dist/git/network-facet.d.ts.map +1 -1
  24. package/dist/git/network-facet.js +10 -5
  25. package/dist/hosted/commands.d.ts.map +1 -1
  26. package/dist/hosted/commands.js +28 -39
  27. package/dist/hosted/runtime.d.ts +24 -2
  28. package/dist/hosted/runtime.d.ts.map +1 -1
  29. package/dist/hosted/runtime.js +70 -13
  30. package/dist/hosted/services.d.ts +14 -2
  31. package/dist/hosted/services.d.ts.map +1 -1
  32. package/dist/hosted/services.js +15 -2
  33. package/dist/loaders/generated-workers.d.ts +2 -2
  34. package/dist/loaders/generated-workers.d.ts.map +1 -1
  35. package/dist/loaders/generated-workers.js +4 -4
  36. package/dist/loaders/pre-bundle-preamble.d.ts +6 -3
  37. package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -1
  38. package/dist/loaders/pre-bundle-preamble.js +13 -8
  39. package/dist/node-shims-artifact.generated.js +3 -3
  40. package/dist/npm/cache.d.ts +6 -1
  41. package/dist/npm/cache.d.ts.map +1 -1
  42. package/dist/npm/cache.js +8 -6
  43. package/dist/npm/install-batch-facet.d.ts +2 -0
  44. package/dist/npm/install-batch-facet.d.ts.map +1 -1
  45. package/dist/npm/install-batch-facet.js +29 -15
  46. package/dist/npm/installer.d.ts +8 -4
  47. package/dist/npm/installer.d.ts.map +1 -1
  48. package/dist/npm/installer.js +173 -113
  49. package/dist/npm/npx-install.d.ts +3 -1
  50. package/dist/npm/npx-install.d.ts.map +1 -1
  51. package/dist/npm/npx-install.js +4 -1
  52. package/dist/npm/placement.d.ts +16 -0
  53. package/dist/npm/placement.d.ts.map +1 -0
  54. package/dist/npm/placement.js +33 -0
  55. package/dist/npm/r2-cache.d.ts +10 -6
  56. package/dist/npm/r2-cache.d.ts.map +1 -1
  57. package/dist/npm/r2-cache.js +32 -16
  58. package/dist/npm/resolve-one-facet.d.ts +3 -0
  59. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  60. package/dist/npm/resolve-one-facet.js +1 -0
  61. package/dist/npm/resolver.d.ts +11 -6
  62. package/dist/npm/resolver.d.ts.map +1 -1
  63. package/dist/npm/resolver.js +12 -3
  64. package/dist/runtime/bash-repl.d.ts +5 -3
  65. package/dist/runtime/bash-repl.d.ts.map +1 -1
  66. package/dist/runtime/bash-repl.js +8 -3
  67. package/dist/runtime/cpython-resident.d.ts.map +1 -1
  68. package/dist/runtime/cpython-resident.js +2 -10
  69. package/dist/runtime/esbuild-wasm-bytes.d.ts +25 -18
  70. package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
  71. package/dist/runtime/esbuild-wasm-bytes.js +55 -30
  72. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  73. package/dist/runtime/facet-loader-host.js +0 -9
  74. package/dist/runtime/node-shims.d.ts.map +1 -1
  75. package/dist/runtime/node-shims.js +853 -181
  76. package/dist/runtime/python-repl.d.ts +4 -3
  77. package/dist/runtime/python-repl.d.ts.map +1 -1
  78. package/dist/runtime/python-repl.js +10 -39
  79. package/dist/runtime/ruby-repl.d.ts +3 -2
  80. package/dist/runtime/ruby-repl.d.ts.map +1 -1
  81. package/dist/runtime/ruby-repl.js +7 -6
  82. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  83. package/dist/runtime/ruby-resident.js +5 -16
  84. package/dist/runtime/runtime-catalog.js +1 -1
  85. package/dist/runtime/wasi/preamble.d.ts +0 -22
  86. package/dist/runtime/wasi/preamble.d.ts.map +1 -1
  87. package/dist/runtime/wasi/preamble.js +160 -1445
  88. package/dist/runtime-catalog.generated.js +1 -1
  89. package/dist/session/hibernation.d.ts +45 -2
  90. package/dist/session/hibernation.d.ts.map +1 -1
  91. package/dist/session/hibernation.js +78 -1
  92. package/dist/session/init.d.ts.map +1 -1
  93. package/dist/session/init.js +1 -0
  94. package/dist/session/nimbus-session.d.ts +16 -23
  95. package/dist/session/nimbus-session.d.ts.map +1 -1
  96. package/dist/session/nimbus-session.js +25 -36
  97. package/dist/session/npm-install-port.d.ts.map +1 -1
  98. package/dist/session/npm-install-port.js +1 -0
  99. package/dist/session/port-capability.d.ts.map +1 -1
  100. package/dist/session/port-capability.js +11 -1
  101. package/dist/session/programmatic.js +2 -2
  102. package/dist/session/routes.d.ts.map +1 -1
  103. package/dist/session/routes.js +4 -2
  104. package/dist/session/rpc.d.ts +7 -44
  105. package/dist/session/rpc.d.ts.map +1 -1
  106. package/dist/session/rpc.js +17 -67
  107. package/dist/session/supervisor-op.d.ts +8 -3
  108. package/dist/session/supervisor-op.d.ts.map +1 -1
  109. package/dist/session/supervisor-op.js +10 -45
  110. package/dist/session/supervisor-rpc.d.ts +47 -22
  111. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  112. package/dist/session/supervisor-rpc.js +64 -26
  113. package/dist/vfs/facet-resident-store.d.ts.map +1 -1
  114. package/dist/vfs/facet-resident-store.js +4 -2
  115. package/dist/workspace-host.d.ts +2 -1
  116. package/dist/workspace-host.d.ts.map +1 -1
  117. package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
  118. package/dist/wrangler/nimbus-wrangler.js +3 -0
  119. package/package.json +30 -4
  120. package/public/_assets/esbuild-0.24.2.js +2380 -0
  121. package/public/_assets/runtime/{node-shims-8f8c64edd2a1f0f5.js → node-shims-7b4dd7a76d95d21e.js} +852 -180
  122. package/scripts/bundle-esbuild-wasm.mjs +55 -39
  123. package/scripts/bundle-facet-workers.mjs +45 -7
  124. package/scripts/bundle-runtime.mjs +35 -8
@@ -10,8 +10,10 @@
10
10
  * than the supervisor's. The facet needs two pieces of esbuild-wasm
11
11
  * at module-load time:
12
12
  *
13
- * 1. The JS adapter (`esbuild-wasm/esm/browser.js`) — small, stays
14
- * in the worker bundle as a string constant.
13
+ * 1. The JS adapter (`esbuild-wasm/esm/browser.js`, ~117 KiB), rewritten
14
+ * into a function body. The supervisor already imports the same
15
+ * build as a module for its own transforms, so the adapter is staged
16
+ * beside the wasm rather than embedded a second time as a string.
15
17
  *
16
18
  * 2. The wasm binary (`esbuild.wasm`) — 12 MiB. Lives in the
17
19
  * static-assets layer (Workers ASSETS binding) at
@@ -48,11 +50,15 @@
48
50
  * Output:
49
51
  * src/esbuild-wasm-bundle.generated.ts
50
52
  * export const ESBUILD_WASM_VERSION: string;
53
+ * export const ESBUILD_WASM_ASSET_PATH: string; // /_assets/esbuild-<version>.wasm
54
+ * export const ESBUILD_JS_ASSET_PATH: string; // /_assets/esbuild-<version>.js
51
55
  * export const ESBUILD_WASM_SHA256: string; // staged wasm digest
52
- * export const ESBUILD_WASM_JS_FN_BODY: string; // ESM browser source
56
+ * export const ESBUILD_JS_SHA256: string; // staged adapter digest
53
57
  *
54
58
  * public/_assets/esbuild-<version>.wasm
55
59
  * The raw wasm, picked up by the wrangler ASSETS binding.
60
+ * public/_assets/esbuild-<version>.js
61
+ * The adapter as a function body, same binding.
56
62
  *
57
63
  * Run via:
58
64
  * node scripts/bundle-esbuild-wasm.mjs
@@ -144,8 +150,8 @@ async function main() {
144
150
  // The ASSETS binding picks up everything under `assets.directory`
145
151
  // (./public per wrangler.jsonc). We version-name the file so a
146
152
  // future esbuild-wasm bump produces a different asset filename and
147
- // forces a fresh fetch (no stale-cache risk; cf. ESBUILD_WASM_ASSET_PATH
148
- // in src/esbuild-wasm-bytes.ts).
153
+ // forces a fresh fetch (no stale-cache risk; the generated module names
154
+ // the path and src/runtime/esbuild-wasm-bytes.ts reads it).
149
155
  await fs.mkdir(OUT_ASSETS_DIR, { recursive: true });
150
156
  const assetName = `esbuild-${version}.wasm`;
151
157
  const assetOut = path.join(OUT_ASSETS_DIR, assetName);
@@ -155,53 +161,48 @@ async function main() {
155
161
  console.log(
156
162
  `[bundle-esbuild-wasm] copied esbuild.wasm → ${path.relative(ROOT, assetOut)} (${(wasmBytes.length / (1024 * 1024)).toFixed(1)} MiB)`,
157
163
  );
164
+ // The JS adapter is staged the same way. Facets splice it into their
165
+ // source as a string; the supervisor imports esbuild-wasm as a module
166
+ // for its own transforms, so carrying the adapter inline as well
167
+ // shipped it twice in the Worker bundle.
168
+ const jsAssetName = `esbuild-${version}.js`;
169
+ const jsAssetOut = path.join(OUT_ASSETS_DIR, jsAssetName);
170
+ const jsBytes = Buffer.from(jsFn, 'utf8');
171
+ await fs.writeFile(jsAssetOut, jsBytes);
172
+ const jsSha256 = createHash('sha256').update(jsBytes).digest('hex');
173
+ console.log(
174
+ `[bundle-esbuild-wasm] wrote JS adapter → ${path.relative(ROOT, jsAssetOut)} (${(jsBytes.length / 1024).toFixed(1)} KiB)`,
175
+ );
158
176
 
159
177
  // ── 3. Clean up any stale-versioned wasm assets in public/_assets/ ──
160
178
  // Keeps the deploy lean: a developer who bumped the esbuild-wasm
161
179
  // version locally won't accidentally ship two copies.
162
180
  for (const entry of await fs.readdir(OUT_ASSETS_DIR)) {
163
- if (entry.startsWith('esbuild-') && entry.endsWith('.wasm') && entry !== assetName) {
181
+ if (entry.startsWith('esbuild-') && (entry.endsWith('.wasm') || entry.endsWith('.js'))
182
+ && entry !== assetName && entry !== jsAssetName) {
164
183
  const stale = path.join(OUT_ASSETS_DIR, entry);
165
184
  await fs.unlink(stale);
166
185
  console.log(`[bundle-esbuild-wasm] removed stale asset: ${entry}`);
167
186
  }
168
187
  }
169
188
 
170
- // ── 4. Emit the JS-only generated TS module ──────────────────────────
171
- // No ESBUILD_WASM_BASE64 export. The base64 string was 16 MiB and
172
- // sat in supervisor module scope for the isolate's lifetime; A'.5
173
- // moved the bytes to env.ASSETS to drop that residency.
189
+ // ── 4. Emit the generated TS module: versions and digests only ──────
190
+ // Neither artifact is inline. The base64 wasm string was 16 MiB and
191
+ // sat in supervisor module scope for the isolate's lifetime (A'.5
192
+ // moved it to env.ASSETS); the JS adapter followed so the bundle
193
+ // carries esbuild-wasm once, as the module the supervisor imports.
174
194
  const header = `/**
175
195
  * esbuild-wasm-bundle.generated.ts — AUTO-GENERATED by scripts/bundle-esbuild-wasm.mjs
176
196
  * DO NOT EDIT.
177
197
  *
178
- * Bundled esbuild-wasm ${version} adapter for use inside NimbusIsolatePool
179
- * isolates. Only the JS adapter is bundled here (~117 KiB); the wasm
180
- * binary itself lives in the static-assets layer at
181
- * public/_assets/esbuild-${version}.wasm and is fetched on demand by
182
- * src/esbuild-wasm-bytes.ts.
198
+ * Digests of the staged esbuild-wasm ${version} artifacts. Both live in
199
+ * the static-assets layer, public/_assets/esbuild-${version}.{wasm,js},
200
+ * and are fetched on demand and verified by src/runtime/esbuild-wasm-bytes.ts.
183
201
  *
184
- * Phase 2 A'.5 moved the base64 wasm string OUT of the supervisor
185
- * bundle (was 16 MiB UTF-8 → 21 MiB UTF-16 in JS string overhead).
186
- * If you're reading this and wondering where the bytes are: env.ASSETS.
187
- */
188
-
189
- export const ESBUILD_WASM_VERSION: string = ${JSON.stringify(version)};
190
-
191
- /**
192
- * SHA-256 of the staged public/_assets/esbuild-${version}.wasm bytes.
193
- * src/runtime/esbuild-wasm-bytes.ts verifies every fetch against it — the L2
194
- * (caches.default) tier included — before the bytes reach workerd's loader.
195
- */
196
- export const ESBUILD_WASM_SHA256: string = ${JSON.stringify(wasmSha256)};
197
-
198
- /**
199
- * Function body that, when wrapped in \`new Function(...)()\`, returns
200
- * the esbuild-wasm namespace (build, transform, initialize, ...).
201
- *
202
- * Source: node_modules/esbuild-wasm/esm/browser.js with the trailing
203
- * \`export { ... }\` rewritten to \`return { ... };\`. Why the browser
204
- * build:
202
+ * The JS adapter is node_modules/esbuild-wasm/esm/browser.js with the
203
+ * trailing \`export { ... }\` rewritten to \`return { ... };\`, so that
204
+ * \`new Function(body)()\` yields the esbuild namespace inside a facet.
205
+ * Why the browser build:
205
206
  * - Pure ESM with no \`createRequire\`, no \`require("fs")\`, and
206
207
  * no \`process.binding\` — safe to evaluate inside a workerd
207
208
  * dynamic-worker isolate.
@@ -219,13 +220,28 @@ export const ESBUILD_WASM_SHA256: string = ${JSON.stringify(wasmSha256)};
219
220
  * patterns (createRequire / dynamic node-builtin require / process.binding)
220
221
  * and bails loud if a future release reintroduces them.
221
222
  */
222
- export const ESBUILD_WASM_JS_FN_BODY: string = ${JSON.stringify(jsFn)};
223
+ export const ESBUILD_WASM_VERSION: string = ${JSON.stringify(version)};
224
+ /**
225
+ * Where the artifacts live inside env.ASSETS. Version-named, so a bump
226
+ * lands under a new name and never serves a stale cache entry. Naming them
227
+ * here is what puts them under scripts/dist-integrity.mjs's staged-asset
228
+ * check, which discovers asset paths by value in generated modules.
229
+ */
230
+ export const ESBUILD_WASM_ASSET_PATH: string = ${JSON.stringify('/_assets/' + assetName)};
231
+ export const ESBUILD_JS_ASSET_PATH: string = ${JSON.stringify('/_assets/' + jsAssetName)};
232
+ /**
233
+ * SHA-256 of the staged public/_assets/esbuild-${version}.wasm bytes.
234
+ * src/runtime/esbuild-wasm-bytes.ts verifies every fetch against it — the L2
235
+ * (caches.default) tier included — before the bytes reach workerd's loader.
236
+ */
237
+ export const ESBUILD_WASM_SHA256: string = ${JSON.stringify(wasmSha256)};
238
+ /** SHA-256 of the staged public/_assets/esbuild-${version}.js adapter, checked the same way. */
239
+ export const ESBUILD_JS_SHA256: string = ${JSON.stringify(jsSha256)};
223
240
  `;
224
-
225
241
  await fs.writeFile(OUT_TS, header, 'utf8');
226
242
  const stat = await fs.stat(OUT_TS);
227
243
  console.log(
228
- `[bundle-esbuild-wasm] wrote ${path.relative(ROOT, OUT_TS)} (${(stat.size / 1024).toFixed(1)} KiB — JS adapter only)`,
244
+ `[bundle-esbuild-wasm] wrote ${path.relative(ROOT, OUT_TS)} (${(stat.size / 1024).toFixed(1)} KiB — digests only)`,
229
245
  );
230
246
  }
231
247
 
@@ -47,12 +47,53 @@ import { build } from 'esbuild';
47
47
  import { writeFileSync, mkdirSync } from 'node:fs';
48
48
  import { dirname, join } from 'node:path';
49
49
  import { fileURLToPath, pathToFileURL } from 'node:url';
50
+ // core's own parser dependency, reached through the workspace hoist; the
51
+ // script runs under plain node at postinstall, so nothing here is TypeScript.
52
+ import { parse } from 'acorn';
50
53
 
51
54
  const __dirname = dirname(fileURLToPath(import.meta.url));
52
55
  const root = join(__dirname, '..');
53
56
  const coreRoot = join(root, '..', 'core');
54
57
  const platformRoot = join(root, '..', 'platform');
55
58
 
59
+ /**
60
+ * The bundle without its comments. esbuild keeps every comment that sits
61
+ * inside an expression or object literal, plus its own `// path` module
62
+ * markers, and nothing reads any of them: the strings are evaluated, never
63
+ * shown, mapped or `toString()`ed. The comment ranges come from a real
64
+ * parse of the output, so a `//` inside a string, template or regex is
65
+ * untouched; only comments go, and a line that held nothing but a comment
66
+ * goes with it. Every other character, identifier and line stays put.
67
+ */
68
+ function withoutComments(text) {
69
+ const comments = [];
70
+ parse(text, { ecmaVersion: 'latest', sourceType: 'module', allowAwaitOutsideFunction: true, onComment: comments });
71
+ let out = '';
72
+ let cursor = 0;
73
+ for (const { start, end } of comments) {
74
+ if (start < cursor) continue;
75
+ const lineStart = text.lastIndexOf('\n', start - 1) + 1;
76
+ const lineEndAt = text.indexOf('\n', end);
77
+ const lineEnd = lineEndAt < 0 ? text.length : lineEndAt;
78
+ const leading = text.slice(lineStart, start);
79
+ const trailing = text.slice(end, lineEnd);
80
+ if (leading.trim() === '' && trailing.trim() === '') {
81
+ // The comment is the whole line (or lines): drop them, newline included.
82
+ out += text.slice(cursor, lineStart);
83
+ cursor = lineEndAt < 0 ? text.length : lineEnd + 1;
84
+ } else if (trailing.trim() === '') {
85
+ // Trailing comment: drop it and the blank that led to it.
86
+ out += text.slice(cursor, start).replace(/[ \t]+$/, '');
87
+ cursor = end;
88
+ } else {
89
+ // Mid-line block comment: one space keeps the tokens on either side apart.
90
+ out += `${text.slice(cursor, start)} `;
91
+ cursor = end;
92
+ }
93
+ }
94
+ return out + text.slice(cursor);
95
+ }
96
+
56
97
  /**
57
98
  * Bundle one TS source into a self-contained ESM string suitable for
58
99
  * inlining as a facet preamble. Strips the leading `export` on
@@ -76,7 +117,7 @@ async function bundleAsPreamble(entryPath, label) {
76
117
  if (!result.outputFiles || result.outputFiles.length === 0) {
77
118
  throw new Error(`[bundle-facet-workers/${label}] esbuild produced no output`);
78
119
  }
79
- let stripped = result.outputFiles[0].text;
120
+ let stripped = withoutComments(result.outputFiles[0].text);
80
121
  stripped = stripped.replace(/^export\s+(async\s+function|function|const|class)\b/gm, '$1');
81
122
  stripped = stripped.replace(/\n?export\s*\{[^}]*\}\s*;\s*$/g, '');
82
123
  return stripped;
@@ -112,7 +153,7 @@ async function bundleVirtualSocketKernel() {
112
153
  if (!result.outputFiles || result.outputFiles.length === 0) {
113
154
  throw new Error('[bundle-facet-workers/virtual-socket-kernel] esbuild produced no output');
114
155
  }
115
- return result.outputFiles[0].text;
156
+ return withoutComments(result.outputFiles[0].text);
116
157
  }
117
158
 
118
159
  /**
@@ -126,10 +167,7 @@ const WASI_REQUIRED_TOP_LEVEL = [
126
167
  '__wasiMakeImports',
127
168
  '__wasiRunStart',
128
169
  '__wasiRunStartAsync',
129
- '__wasiReadFilesB64',
130
170
  '__wasiAdoptSupervisor',
131
- '__wasiDrainPersist',
132
- '__wasiRevalidateFS',
133
171
  'fdTable',
134
172
  ];
135
173
 
@@ -168,7 +206,7 @@ async function bundleWasiInstance() {
168
206
  if (!result.outputFiles || result.outputFiles.length === 0) {
169
207
  throw new Error('[bundle-facet-workers/wasi-instance] esbuild produced no output');
170
208
  }
171
- let src = result.outputFiles[0].text;
209
+ let src = withoutComments(result.outputFiles[0].text);
172
210
  // The body is spliced into another module; a re-export block there is a
173
211
  // syntax error, and callers append their own `export { … }`.
174
212
  src = src.replace(/^export\s+(async\s+function|function|const|let|var|class)\b/gm, '$1');
@@ -233,7 +271,7 @@ async function bundleBashRunner() {
233
271
  if (!result.outputFiles || result.outputFiles.length === 0) {
234
272
  throw new Error('[bundle-facet-workers/bash-runner] esbuild produced no output');
235
273
  }
236
- const src = result.outputFiles[0].text;
274
+ const src = withoutComments(result.outputFiles[0].text);
237
275
  for (const name of ['__bashBoot', '__bashFeed']) {
238
276
  if (!new RegExp(`^\\s*globalThis\\.${name}\\s*=`, 'm').test(src)) {
239
277
  throw new Error(
@@ -7,7 +7,7 @@
7
7
  * node scripts/bundle-runtime.mjs clang binji-2020 [--bucket nimbus-runtime-cache]
8
8
  * node scripts/bundle-runtime.mjs python 0.29.4 [--bucket nimbus-runtime-cache]
9
9
  * node scripts/bundle-runtime.mjs --pin-catalog (read-only; publishes nothing)
10
- * node scripts/bundle-runtime.mjs bash 5.2.37 --npm-package <dir> (local; no R2)
10
+ * node scripts/bundle-runtime.mjs bash 5.2.37-2 --npm-package <dir> (local; no R2)
11
11
  *
12
12
  * `--npm-package` is the SECOND publisher for the same artifacts: it stages
13
13
  * exactly what the R2 path stages, composes the same manifest bytes, and lays
@@ -50,6 +50,7 @@ import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, openSync, c
50
50
  import { execFileSync, execSync, spawnSync } from 'node:child_process';
51
51
  import { tmpdir } from 'node:os';
52
52
  import { basename, dirname, join } from 'node:path';
53
+ import { BASH_RUNNER } from '@nimbus-sh/core/runtime/os-contracts.js';
53
54
 
54
55
  const ACCOUNT = process.env.CLOUDFLARE_ACCOUNT_ID;
55
56
  // Resolve the wrangler bin from either the package-local or the
@@ -64,9 +65,14 @@ const PYODIDE_WORKERD_ADAPTER = JSON.parse(
64
65
  );
65
66
 
66
67
  const USAGE =
67
- 'usage: bundle-runtime.mjs <name> <version> [--bucket <bucket>]\n' +
68
+ 'usage: bundle-runtime.mjs <name> <version> [--bucket <bucket>] [--keep-default]\n' +
68
69
  ' bundle-runtime.mjs <name> <version> --npm-package <dir>\n' +
69
- ' bundle-runtime.mjs --pin-catalog [--bucket <bucket>]';
70
+ ' bundle-runtime.mjs --pin-catalog [--bucket <bucket>]\n' +
71
+ '\n' +
72
+ ' --keep-default add the version to the catalog without making it the\n' +
73
+ ' default: for a build whose runner contract a deployment\n' +
74
+ ' still reading the catalog cannot bind yet. Re-run without\n' +
75
+ ' the flag once every deployment can; the re-run is idempotent.';
70
76
 
71
77
  /** The bucket the deployed Worker's NIMBUS_RUNTIME_CACHE binding points at.
72
78
  * Only a publish to THIS bucket may rewrite the catalog pin: the pin
@@ -78,6 +84,7 @@ const rawArgs = process.argv.slice(2);
78
84
  const positionalArgs = [];
79
85
  let BUCKET = process.env.NIMBUS_RUNTIME_BUCKET || PRODUCTION_BUCKET;
80
86
  let PIN_ONLY = false;
87
+ let KEEP_DEFAULT = false;
81
88
  let NPM_OUT_DIR = null;
82
89
  for (let i = 0; i < rawArgs.length; i++) {
83
90
  const arg = rawArgs[i];
@@ -85,6 +92,10 @@ for (let i = 0; i < rawArgs.length; i++) {
85
92
  PIN_ONLY = true;
86
93
  continue;
87
94
  }
95
+ if (arg === '--keep-default') {
96
+ KEEP_DEFAULT = true;
97
+ continue;
98
+ }
88
99
  if (arg === '--npm-package') {
89
100
  NPM_OUT_DIR = rawArgs[++i];
90
101
  if (!NPM_OUT_DIR) {
@@ -226,10 +237,16 @@ const SPECS = {
226
237
  // `local_base`. The bash-runner aliases every applet name in
227
238
  // busybox.applets onto the busybox module so bash's PATH lookup finds
228
239
  // them in /bin and /usr/bin.
229
- 'bash/5.2.37': {
240
+ //
241
+ // The version is `<upstream>-<build>`: the build number advances with
242
+ // every rebuild, and the entrypoint names the runner contract the build
243
+ // was made for (BASH_RUNNER), so a deployment whose preamble predates it
244
+ // never binds it. 5.2.37 is build 1, published under `bash-runner`.
245
+ 'bash/5.2.37-2': {
230
246
  license: 'GPL-3.0-or-later AND GPL-2.0-only',
231
247
  wasi_namespace: 'wasi_snapshot_preview1',
232
248
  local_base: '../wasm/bash',
249
+ auxiliary_bins: 'coreutils/busybox.applets',
233
250
  npm: {
234
251
  name: '@nimbus-sh/runtime-bash',
235
252
  summary: 'GNU bash 5.2.37 and BusyBox 1.37.0, cross-compiled to wasm32-wasi',
@@ -238,7 +255,7 @@ const SPECS = {
238
255
  { src: 'bash.async.wasm', vfs: 'share/bash/bash.async.wasm' },
239
256
  { src: 'coreutils/busybox.wasm', vfs: 'share/bash/coreutils/busybox.wasm' },
240
257
  { src: 'coreutils/busybox.applets', vfs: 'share/bash/coreutils/busybox.applets' },
241
- { src: 'BIN_MARKER', vfs: 'bin/bash', mode: 'exec', runner: 'bash-runner', binName: 'bash' },
258
+ { src: 'BIN_MARKER', vfs: 'bin/bash', mode: 'exec', runner: BASH_RUNNER, binName: 'bash' },
242
259
  ],
243
260
  synthetic_files: {
244
261
  'BIN_MARKER': Buffer.from(
@@ -427,6 +444,15 @@ if (!spec) {
427
444
  console.error(`known: ${Object.keys(SPECS).join(', ')}`);
428
445
  process.exit(2);
429
446
  }
447
+ if (spec.auxiliary_bins) {
448
+ const list = new URL(`${spec.local_base}/${spec.auxiliary_bins}`, import.meta.url);
449
+ const names = readFileSync(list, 'utf8').split('\n').filter(Boolean);
450
+ spec.synthetic_files.NIMBUS_AUX_BIN = Buffer.from('Nimbus WASI multicall entry\n');
451
+ for (const name of names) {
452
+ if (name.includes('/') || name === '.' || name === '..') throw new Error(`Invalid multicall entry: ${name}`);
453
+ spec.files.push({ src: 'NIMBUS_AUX_BIN', vfs: `bin/${name}`, mode: 'exec' });
454
+ }
455
+ }
430
456
  // Checked before anything is staged: which runtimes we publish to npm is a
431
457
  // decision, and a spec that has not made it should cost nothing to find out.
432
458
  if (NPM_OUT_DIR) {
@@ -617,8 +643,9 @@ if (spec.ingest_only) {
617
643
  license: spec.license,
618
644
  };
619
645
  // Update default to the just-uploaded version (idempotent — if it was
620
- // already the default, no-op).
621
- catalog.runtimes[RUNTIME].default = VERSION;
646
+ // already the default, no-op), unless the publisher asked to hold it for
647
+ // deployments that cannot bind this build yet.
648
+ if (!KEEP_DEFAULT) catalog.runtimes[RUNTIME].default = VERSION;
622
649
 
623
650
  // The catalog indexes every runtime, so an update that drops one is a
624
651
  // publish that unregisters somebody else's. Cheap to assert, and it catches
@@ -644,7 +671,7 @@ if (spec.ingest_only) {
644
671
  console.log(`\n[bundle-runtime] DONE`);
645
672
  console.log(`[bundle-runtime] uploaded ${downloaded.length} files (${totalMb} MiB) for ${RUNTIME}@${VERSION}`);
646
673
  console.log(`[bundle-runtime] manifest: r2://${BUCKET}/${manifestR2Key}`);
647
- console.log(`[bundle-runtime] catalog: r2://${BUCKET}/${catalogR2Key}`);
674
+ console.log(`[bundle-runtime] catalog: r2://${BUCKET}/${catalogR2Key} (default ${catalog.runtimes[RUNTIME].default})`);
648
675
  }
649
676
 
650
677
  /**