@nimbus-sh/worker 0.6.0 → 0.7.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 +2 -2
- package/dist/cirrus-npm-cjs.generated.d.ts +1 -1
- package/dist/cirrus-npm-cjs.generated.js +1 -1
- package/dist/facets/cirrus-real.d.ts.map +1 -1
- package/dist/facets/cirrus-real.js +6 -5
- package/dist/facets/compose.d.ts +148 -0
- package/dist/facets/compose.d.ts.map +1 -0
- package/dist/facets/compose.js +146 -0
- package/dist/facets/durable-images.d.ts +9 -5
- package/dist/facets/durable-images.d.ts.map +1 -1
- package/dist/facets/durable-images.js +17 -7
- package/dist/facets/esbuild-bundle-pool.d.ts +49 -0
- package/dist/facets/esbuild-bundle-pool.d.ts.map +1 -0
- package/dist/facets/esbuild-bundle-pool.js +122 -0
- package/dist/facets/exec-telemetry.d.ts +8 -0
- package/dist/facets/exec-telemetry.d.ts.map +1 -1
- package/dist/facets/manager.d.ts +240 -109
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +661 -337
- package/dist/facets/process.d.ts +20 -16
- package/dist/facets/process.d.ts.map +1 -1
- package/dist/facets/process.js +59 -28
- package/dist/facets/real-vite-fs-shim.js +2 -2
- package/dist/facets/vite-dev-server.d.ts +25 -47
- package/dist/facets/vite-dev-server.d.ts.map +1 -1
- package/dist/facets/vite-dev-server.js +185 -219
- package/dist/facets/wasm-image-digest.d.ts +25 -0
- package/dist/facets/wasm-image-digest.d.ts.map +1 -0
- package/dist/facets/wasm-image-digest.js +25 -0
- package/dist/facets/wasm-swap-registry.d.ts +51 -92
- package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
- package/dist/facets/wasm-swap-registry.js +68 -227
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +6 -5
- package/dist/loaders/child-process/spawn-pool.d.ts +3 -2
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-pool.js +3 -2
- package/dist/loaders/generated-workers.d.ts +2 -2
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +3 -3
- package/dist/loaders/npm-resolve-preamble.d.ts +3 -6
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
- package/dist/loaders/npm-resolve-preamble.js +34 -108
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/npm/install-batch-facet.d.ts +3 -2
- package/dist/npm/install-batch-facet.d.ts.map +1 -1
- package/dist/npm/install-batch-facet.js +45 -15
- package/dist/npm/installer.d.ts +22 -17
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +209 -193
- package/dist/npm/pre-bundle-facet.d.ts +1 -1
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
- package/dist/npm/pre-bundle-facet.js +1 -1
- package/dist/npm/resolve-facet.d.ts +6 -0
- package/dist/npm/resolve-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.d.ts +21 -20
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +85 -84
- package/dist/npm/semver.d.ts +59 -0
- package/dist/npm/semver.d.ts.map +1 -0
- package/dist/npm/semver.js +226 -0
- package/dist/router/remote-api.js +31 -1
- package/dist/runtime/html-entrypoint.d.ts +7 -0
- package/dist/runtime/html-entrypoint.d.ts.map +1 -1
- package/dist/runtime/html-entrypoint.js +18 -2
- package/dist/runtime/node-runner.d.ts +3 -0
- package/dist/runtime/node-runner.d.ts.map +1 -1
- package/dist/runtime/node-runner.js +7 -0
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +305 -29
- package/dist/runtime/opencode-facet-runner.js +2 -2
- package/dist/session/helpers.d.ts +38 -0
- package/dist/session/helpers.d.ts.map +1 -1
- package/dist/session/helpers.js +76 -0
- package/dist/session/init-phases.d.ts +1 -1
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +2 -12
- package/dist/session/init.d.ts +15 -1
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +82 -603
- package/dist/session/nimbus-session.d.ts +56 -22
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +130 -101
- package/dist/session/npm-install-port.d.ts +16 -0
- package/dist/session/npm-install-port.d.ts.map +1 -0
- package/dist/session/npm-install-port.js +37 -0
- package/dist/session/port-capability.d.ts +44 -0
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +110 -0
- package/dist/session/programmatic.d.ts +31 -5
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +94 -68
- package/dist/session/routes.d.ts +30 -17
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +42 -131
- package/dist/session/rpc.d.ts +15 -15
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +97 -47
- package/dist/session/serving-port.d.ts +27 -0
- package/dist/session/serving-port.d.ts.map +1 -0
- package/dist/session/serving-port.js +27 -0
- package/dist/session/start-real-vite.d.ts +12 -0
- package/dist/session/start-real-vite.d.ts.map +1 -1
- package/dist/session/start-real-vite.js +4 -4
- package/dist/session/supervisor-op.d.ts +2 -2
- package/dist/session/supervisor-op.d.ts.map +1 -1
- package/dist/session/supervisor-op.js +13 -7
- package/dist/session/supervisor-rpc.d.ts +7 -7
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +4 -0
- package/dist/session/vite-command.d.ts +18 -0
- package/dist/session/vite-command.d.ts.map +1 -0
- package/dist/session/vite-command.js +609 -0
- package/dist/session/ws.d.ts +61 -0
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +122 -12
- package/dist/shell/npm-bin-entrypoints.d.ts +0 -35
- package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
- package/dist/shell/npm-bin-entrypoints.js +12 -85
- package/package.json +24 -4
- package/public/_assets/runtime/{node-shims-9e1380d9ee430fcc.js → node-shims-8f8c64edd2a1f0f5.js} +305 -29
- package/public/index.html +1 -1
- package/scripts/bundle-facet-workers.mjs +1 -1
- package/scripts/bundle-npm-cjs.mjs +2 -2
- package/dist/facets/on-demand-bundle-gate.d.ts +0 -65
- package/dist/facets/on-demand-bundle-gate.d.ts.map +0 -1
- package/dist/facets/on-demand-bundle-gate.js +0 -125
- package/dist/npm/install-args.d.ts +0 -21
- package/dist/npm/install-args.d.ts.map +0 -1
- package/dist/npm/install-args.js +0 -70
- package/dist/npm/npm-log.d.ts +0 -31
- package/dist/npm/npm-log.d.ts.map +0 -1
- package/dist/npm/npm-log.js +0 -51
package/dist/npm/installer.js
CHANGED
|
@@ -24,11 +24,11 @@ import { BUNDLER_VERSION } from '@nimbus-sh/core/runtime/esbuild-service.js';
|
|
|
24
24
|
import { NpmCache } from './cache.js';
|
|
25
25
|
import { computeHoistPlan, } from './resolver.js';
|
|
26
26
|
import { packumentUrl } from './r2-cache.js';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
27
|
+
import { satisfiesRange, isSemverRange } from './semver.js';
|
|
28
|
+
import { npmAddedLine, npmHttpCacheLine, npmHttpFetchLine, npmTitleLine, } from '@nimbus-sh/core/substrate/lifo/commands/system/npm-log.js';
|
|
29
|
+
import { applySwaps, findRejects, lookupSwap, isOptionalNativeBinding, formatSwapNotice, emitRegistryEvent, } from '../facets/wasm-swap-registry.js';
|
|
29
30
|
import { resolvePackageEntry } from '@nimbus-sh/core/_shared/exports-resolver.js';
|
|
30
31
|
import { encodeWriteBatchStream } from '@nimbus-sh/platform/w7-frame.js';
|
|
31
|
-
import { IsolatePool } from '@nimbus-sh/fabric/isolate-pool.js';
|
|
32
32
|
import { Fanout, IN_DO_THRESHOLD } from '@nimbus-sh/fabric/fanout.js';
|
|
33
33
|
import { TAR_STREAM_PREAMBLE, W7_FRAME_PREAMBLE } from '../loaders/generated-workers.js';
|
|
34
34
|
import { installPackagesInFacet, } from './install-batch-facet.js';
|
|
@@ -36,12 +36,10 @@ import { setInstallPhase, recordInstallFacetCounters, recordPreBundleSummary, re
|
|
|
36
36
|
import { recordCacheStatEvents } from '@nimbus-sh/core/_shared/cache-stats.js';
|
|
37
37
|
import { estimateSupervisorHeap } from '@nimbus-sh/platform/heap-estimate.js';
|
|
38
38
|
import { describeError } from '@nimbus-sh/platform/oom-classify.js';
|
|
39
|
-
import { resolveOnePackumentInFacet, } from './resolve-one-facet.js';
|
|
39
|
+
import { resolveOnePackumentInFacet, parseRegistryRequest, } from './resolve-one-facet.js';
|
|
40
40
|
import { NPM_RESOLVE_PREAMBLE } from '../loaders/npm-resolve-preamble.js';
|
|
41
41
|
import { prebundleOne, buildSliceForSpecifierWithCap, externalsForSpecifier, } from './pre-bundle-facet.js';
|
|
42
|
-
import {
|
|
43
|
-
import { fetchEsbuildWasmBytes } from '../runtime/esbuild-wasm-bytes.js';
|
|
44
|
-
import { CHUNK_SIZE, PRE_BUNDLE_CONCURRENCY, PRE_BUNDLE_SLICE_CAP_BYTES, SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES, } from '@nimbus-sh/platform/limits.js';
|
|
42
|
+
import { CHUNK_SIZE, PRE_BUNDLE_CONCURRENCY, PRE_BUNDLE_SLICE_CAP_BYTES, } from '@nimbus-sh/platform/limits.js';
|
|
45
43
|
import { acquireSupervisorAllocation } from '@nimbus-sh/platform/heavy-alloc-coord.js';
|
|
46
44
|
import { countPackageFiles, BARREL_PKG_FILE_THRESHOLD, packageNameFromSpecifier } from '@nimbus-sh/core/runtime/barrel-detect.js';
|
|
47
45
|
import { scanNamedImports, namedImportSignature, buildSyntheticEntry, buildScopedSliceForSynthetic, syntheticEntryPath, } from '../runtime/barrel-synthesizer.js';
|
|
@@ -53,6 +51,8 @@ export class NpmInstaller {
|
|
|
53
51
|
vfs;
|
|
54
52
|
cache;
|
|
55
53
|
esbuild;
|
|
54
|
+
/** The session's esbuild facet pool, shared with the on-demand dev-server path. */
|
|
55
|
+
bundlePool;
|
|
56
56
|
ctx;
|
|
57
57
|
env;
|
|
58
58
|
onProgress;
|
|
@@ -75,6 +75,7 @@ export class NpmInstaller {
|
|
|
75
75
|
this.vfs = vfs.as(CRED_KERNEL);
|
|
76
76
|
this.cache = new NpmCache(sql);
|
|
77
77
|
this.esbuild = opts?.esbuild ?? null;
|
|
78
|
+
this.bundlePool = opts?.bundlePool ?? null;
|
|
78
79
|
this.ctx = opts?.ctx;
|
|
79
80
|
this.env = opts?.env;
|
|
80
81
|
this.onProgress = opts?.onProgress;
|
|
@@ -93,6 +94,11 @@ export class NpmInstaller {
|
|
|
93
94
|
const start = Date.now();
|
|
94
95
|
const projDir = projectDir.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
95
96
|
const nmDir = projDir + '/node_modules';
|
|
97
|
+
// Per-invocation progress wins over whatever the ctor bound — the
|
|
98
|
+
// installer is shared, the terminal it should speak to is not.
|
|
99
|
+
const previousProgress = this.onProgress;
|
|
100
|
+
if (opts?.onProgress !== undefined)
|
|
101
|
+
this.onProgress = opts.onProgress;
|
|
96
102
|
const log = (msg) => this.onProgress?.(msg);
|
|
97
103
|
this.npmLog = opts?.npmLog ?? (() => { });
|
|
98
104
|
// Reset phase to 'idle' on any exit path so /api/_diag/memory
|
|
@@ -105,6 +111,7 @@ export class NpmInstaller {
|
|
|
105
111
|
finally {
|
|
106
112
|
setInstallPhase('idle');
|
|
107
113
|
this.npmLog = () => { };
|
|
114
|
+
this.onProgress = previousProgress;
|
|
108
115
|
}
|
|
109
116
|
}
|
|
110
117
|
async _installInner(projDir, nmDir, opts, log, start) {
|
|
@@ -118,19 +125,22 @@ export class NpmInstaller {
|
|
|
118
125
|
this.npmLog('verbose', npmTitleLine(opts?.packages ?? []));
|
|
119
126
|
let phaseStart = Date.now();
|
|
120
127
|
log('Checking lockfile...');
|
|
121
|
-
const specs = await this.buildSpecs(projDir, opts?.packages, opts?.production);
|
|
128
|
+
const { specs, devOnly, optionalRoots, advised } = await this.buildSpecs(projDir, opts?.packages, opts?.production);
|
|
129
|
+
// EBADPLATFORM refusals (os/cpu/libc allowlists on a required
|
|
130
|
+
// package) fail the install like real npm; every refused name was
|
|
131
|
+
// already logged as a [skip] line, name them again with the reason.
|
|
132
|
+
const recordRefusals = (rejected) => {
|
|
133
|
+
for (const r of rejected) {
|
|
134
|
+
if (!r.required)
|
|
135
|
+
continue;
|
|
136
|
+
failed.push(r.name);
|
|
137
|
+
log(`npm ERR! ${r.name}: ${r.reason}`);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
122
140
|
if (Object.keys(specs).length === 0) {
|
|
123
141
|
log('No dependencies to install.');
|
|
124
142
|
return { installed, failed, totalFiles: 0, elapsed: Date.now() - start, cachedHits: 0, phases: {} };
|
|
125
143
|
}
|
|
126
|
-
// W11: framework detection. If the project depends on a framework
|
|
127
|
-
// (next/astro/nuxt/remix/sveltekit) or generic vite, we exempt
|
|
128
|
-
// FRAMEWORK_REQUIRED_PACKAGES (vite, ...) from the SKIP_PACKAGES set
|
|
129
|
-
// so the framework's CLI can `import 'vite'` from node_modules.
|
|
130
|
-
const frameworkAware = await this.detectFrameworkAware(projDir);
|
|
131
|
-
if (frameworkAware) {
|
|
132
|
-
log(`Framework detected — installing framework-required packages (vite, …).`);
|
|
133
|
-
}
|
|
134
144
|
const lockfile = this.cache.readLockfile(projDir);
|
|
135
145
|
let resolved;
|
|
136
146
|
let usedLockfile = false;
|
|
@@ -156,9 +166,13 @@ export class NpmInstaller {
|
|
|
156
166
|
phaseStart = Date.now();
|
|
157
167
|
setInstallPhase('resolve');
|
|
158
168
|
log(`Resolving ${Object.keys(specs).length} dependencies (path: fanout, fetch: ${this.fetchFn ? 'facet-proxy' : 'global'})...`);
|
|
159
|
-
const tree = await this.resolveTreeViaFanout(specs, log, {
|
|
169
|
+
const tree = await this.resolveTreeViaFanout(specs, log, { optionalRoots, devOnly, advised });
|
|
160
170
|
resolved = tree.resolved;
|
|
161
171
|
phases['resolve'] = Date.now() - phaseStart;
|
|
172
|
+
// Transitive platform-gate refusals. Required ones join `failed`
|
|
173
|
+
// with their reason; optional-edge ones stay out of the outcome
|
|
174
|
+
// lists. Either way the rest of the tree resolved and installs.
|
|
175
|
+
recordRefusals(tree.rejected);
|
|
162
176
|
// A dependency the walk could not resolve is missing from the
|
|
163
177
|
// install and from every subtree beneath it. Name it and its
|
|
164
178
|
// reason, and carry it into `failed` so the command exits non-zero.
|
|
@@ -168,8 +182,9 @@ export class NpmInstaller {
|
|
|
168
182
|
}
|
|
169
183
|
if (resolved.size === 0) {
|
|
170
184
|
log('No packages resolved.');
|
|
185
|
+
const refused = new Set(tree.rejected.map((r) => r.name));
|
|
171
186
|
for (const name of Object.keys(specs)) {
|
|
172
|
-
if (!tree.unresolved.has(name))
|
|
187
|
+
if (!tree.unresolved.has(name) && !refused.has(name))
|
|
173
188
|
failed.push(name);
|
|
174
189
|
}
|
|
175
190
|
return {
|
|
@@ -306,7 +321,7 @@ export class NpmInstaller {
|
|
|
306
321
|
// the npm command-handler returns to the shell, the shell
|
|
307
322
|
// prints its prompt, and THEN the orphan promise's safeProgress
|
|
308
323
|
// fires — visually corrupting the freshly-rendered prompt
|
|
309
|
-
// ("user@nimbus:~/app$ [npm] Pre-bundle complete: ...").
|
|
324
|
+
// ("user@nimbus:~/example-app$ [npm] Pre-bundle complete: ...").
|
|
310
325
|
//
|
|
311
326
|
// Fix:
|
|
312
327
|
// Suppress writes to ctx.stdout once install() has returned.
|
|
@@ -433,7 +448,13 @@ export class NpmInstaller {
|
|
|
433
448
|
* - X.5-G G1 optional-native silent-skip,
|
|
434
449
|
* - X.5-drizzle best-effort tagging on optional-peer subtrees,
|
|
435
450
|
* - W6 swap / warn / reject decisions (top-level enforcement; the
|
|
436
|
-
* per-package task ALSO checks these for transitive correctness)
|
|
451
|
+
* per-package task ALSO checks these for transitive correctness).
|
|
452
|
+
* A package is required iff it is reachable from a required root
|
|
453
|
+
* (every selected spec except optionalDependencies roots) through
|
|
454
|
+
* a chain of required edges (`dependencies` minus the package's
|
|
455
|
+
* own optionalDependencies, required peers); W6 refusals are
|
|
456
|
+
* classified against the closure computed over the resolved
|
|
457
|
+
* graph at end of walk.
|
|
437
458
|
* - cache flushing (one batched putRegistryEntries at end).
|
|
438
459
|
*
|
|
439
460
|
* The per-package task (resolveOnePackumentInFacet) owns ONLY the
|
|
@@ -444,7 +465,19 @@ export class NpmInstaller {
|
|
|
444
465
|
*/
|
|
445
466
|
async resolveTreeViaFanout(specs, log, opts = {}) {
|
|
446
467
|
const t0 = Date.now();
|
|
447
|
-
const
|
|
468
|
+
const optionalRoots = opts.optionalRoots ?? new Set();
|
|
469
|
+
const devOnly = opts.devOnly ?? new Set();
|
|
470
|
+
// Policy advisories already announced — one `note:` line per name
|
|
471
|
+
// per install, shared with applyW6Registry which runs first.
|
|
472
|
+
const advised = opts.advised ?? new Set();
|
|
473
|
+
const noteAdvisory = (ev) => {
|
|
474
|
+
if (advised.has(ev.from))
|
|
475
|
+
return;
|
|
476
|
+
advised.add(ev.from);
|
|
477
|
+
const hint = ev.suggest ? ` … try: ${ev.suggest}` : '';
|
|
478
|
+
const marker = devOnly.has(ev.from) ? ' (declared in devDependencies)' : '';
|
|
479
|
+
log(`[npm] note: ${ev.from} has no Workers-compatible build${marker}: ${ev.reason}${hint}`);
|
|
480
|
+
};
|
|
448
481
|
const __f2Diag = (globalThis.process?.env?.NIMBUS_DIAG_INSTALL_PIPELINE === '1');
|
|
449
482
|
// Per-walk state — supervisor side.
|
|
450
483
|
const resolved = new Map();
|
|
@@ -452,6 +485,10 @@ export class NpmInstaller {
|
|
|
452
485
|
// A name lands here at most once: the frontier marks it `seen` before
|
|
453
486
|
// dispatch, so no later parent re-enqueues it.
|
|
454
487
|
const unresolved = new Map();
|
|
488
|
+
// EBADPLATFORM refusals (os/cpu/libc allowlists), in walk order.
|
|
489
|
+
// Announced as `[skip]` lines at record time; required-vs-optional is
|
|
490
|
+
// classified at end of walk, when every required edge has been seen.
|
|
491
|
+
const refusals = new Map();
|
|
455
492
|
const seen = new Set();
|
|
456
493
|
const topLevelNames = new Set(Object.keys(specs));
|
|
457
494
|
const optionalNames = new Set(); // X.5-G G1
|
|
@@ -554,9 +591,7 @@ export class NpmInstaller {
|
|
|
554
591
|
name,
|
|
555
592
|
range,
|
|
556
593
|
cachedEntries,
|
|
557
|
-
topLevel: topLevelNames.has(name),
|
|
558
594
|
isOptional: optionalNames.has(name),
|
|
559
|
-
frameworkAware,
|
|
560
595
|
fetchTimeoutMs: 15_000,
|
|
561
596
|
retries: 3,
|
|
562
597
|
};
|
|
@@ -587,7 +622,8 @@ export class NpmInstaller {
|
|
|
587
622
|
unresolved.set(taskName, `resolver fanout returned no result at layer ${layerN}`);
|
|
588
623
|
continue;
|
|
589
624
|
}
|
|
590
|
-
// Forward messages + events.
|
|
625
|
+
// Forward messages + events. `advisory` events are the policy's
|
|
626
|
+
// "cannot run here" list — one note per name per install.
|
|
591
627
|
for (const m of res.messages)
|
|
592
628
|
log(m);
|
|
593
629
|
for (const ev of res.events) {
|
|
@@ -595,6 +631,8 @@ export class NpmInstaller {
|
|
|
595
631
|
emitRegistryEvent(ev);
|
|
596
632
|
}
|
|
597
633
|
catch { /* swallow sink errors */ }
|
|
634
|
+
if (ev.type === 'advisory')
|
|
635
|
+
noteAdvisory(ev);
|
|
598
636
|
}
|
|
599
637
|
// Accumulate cache writes for end-of-walk flush.
|
|
600
638
|
for (const cw of res.cacheWrites)
|
|
@@ -617,7 +655,12 @@ export class NpmInstaller {
|
|
|
617
655
|
r2Losses++;
|
|
618
656
|
if (res.packumentBytesDecoded > 0)
|
|
619
657
|
totalPackumentsDecoded++;
|
|
620
|
-
//
|
|
658
|
+
// w6-reject: an os/cpu/libc platform-gate refusal — npm's
|
|
659
|
+
// EBADPLATFORM. Announced as a [skip] line now and classified
|
|
660
|
+
// required-vs-optional at end of walk, when the required-edge
|
|
661
|
+
// closure is final; best-effort optional-peer subtrees still
|
|
662
|
+
// skip silently. Table rejects are advisories handled above and
|
|
663
|
+
// never reach this branch.
|
|
621
664
|
if (res.error && res.error.type === 'w6-reject') {
|
|
622
665
|
if (bestEffortNames.has(taskName)) {
|
|
623
666
|
// X.5-drizzle: silent-skip inside best-effort optional-peer
|
|
@@ -627,14 +670,11 @@ export class NpmInstaller {
|
|
|
627
670
|
emitRegistryEvent({ type: 'transitive-skip', from: taskName, reason });
|
|
628
671
|
continue;
|
|
629
672
|
}
|
|
630
|
-
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
transitive: 'fail',
|
|
636
|
-
};
|
|
637
|
-
throw new RegistryRejectError([rejectEntry]);
|
|
673
|
+
refusals.set(taskName, res.error.reason);
|
|
674
|
+
const hint = res.error.suggest ? ` … try: ${res.error.suggest}` : '';
|
|
675
|
+
log(`[resolve-fanout] [skip] ${taskName} — ${res.error.reason}${hint}`);
|
|
676
|
+
emitRegistryEvent({ type: 'transitive-skip', from: taskName, reason: res.error.reason });
|
|
677
|
+
continue;
|
|
638
678
|
}
|
|
639
679
|
// Resolution failure. Optional (X.5-G G1) and best-effort
|
|
640
680
|
// optional-peer (X.5-drizzle) edges are allowed to disappear;
|
|
@@ -710,22 +750,15 @@ export class NpmInstaller {
|
|
|
710
750
|
}
|
|
711
751
|
}
|
|
712
752
|
// X.5-F R2.5 + X.5-J: optional peers when THIS pkg is the
|
|
713
|
-
// user's top-level.
|
|
753
|
+
// user's top-level. They resolve best-effort; a policy-listed
|
|
754
|
+
// name that resolves installs with an advisory like any
|
|
755
|
+
// package, and one that does not is skipped quietly.
|
|
714
756
|
if (topLevelNames.has(pkg.name)) {
|
|
715
757
|
const allPeers = pkg.__allPeerDependencies;
|
|
716
758
|
if (allPeers) {
|
|
717
759
|
for (const [peerName, peerRange] of Object.entries(allPeers)) {
|
|
718
760
|
if (resolved.has(peerName) || seen.has(peerName))
|
|
719
761
|
continue;
|
|
720
|
-
const peerFail = lookupReject(peerName);
|
|
721
|
-
const peerWarn = shouldWarnSkipTransitive(peerName);
|
|
722
|
-
const peerReject = peerFail || peerWarn;
|
|
723
|
-
if (peerReject) {
|
|
724
|
-
const reason = `optional peer in REJECT_INSTALL: ${peerName} — ${peerReject.reason}`;
|
|
725
|
-
log(`[resolve-fanout] [skip] ${peerName} — ${reason}`);
|
|
726
|
-
emitRegistryEvent({ type: 'transitive-skip', from: peerName, reason });
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
762
|
topLevelNames.add(peerName);
|
|
730
763
|
bestEffortNames.add(peerName);
|
|
731
764
|
queue.push([peerName, peerRange]);
|
|
@@ -735,6 +768,35 @@ export class NpmInstaller {
|
|
|
735
768
|
}
|
|
736
769
|
layerN++;
|
|
737
770
|
}
|
|
771
|
+
// G2: a refused package is required iff it is reachable from a
|
|
772
|
+
// required root — every selected spec except optionalDependencies
|
|
773
|
+
// roots — through required edges (`dependencies` minus the package's
|
|
774
|
+
// own optionalDependencies, which npm overrides as optional, and
|
|
775
|
+
// required peers). Computed now over the resolved graph: the walk
|
|
776
|
+
// sees names in BFS order, so incremental propagation would freeze a
|
|
777
|
+
// dev/optional first sighting before a later required edge lands.
|
|
778
|
+
const requiredNames = new Set();
|
|
779
|
+
const pendingRequired = Object.keys(specs).filter((name) => !optionalRoots.has(name));
|
|
780
|
+
for (let i = 0; i < pendingRequired.length; i++) {
|
|
781
|
+
const name = pendingRequired[i];
|
|
782
|
+
if (requiredNames.has(name))
|
|
783
|
+
continue;
|
|
784
|
+
requiredNames.add(name);
|
|
785
|
+
const pkg = resolved.get(name);
|
|
786
|
+
if (!pkg)
|
|
787
|
+
continue;
|
|
788
|
+
const optional = pkg.optionalDependencies ?? {};
|
|
789
|
+
for (const dep of Object.keys(pkg.dependencies)) {
|
|
790
|
+
if (!Object.hasOwn(optional, dep))
|
|
791
|
+
pendingRequired.push(dep);
|
|
792
|
+
}
|
|
793
|
+
for (const peer of Object.keys(pkg.peerDependencies ?? {}))
|
|
794
|
+
pendingRequired.push(peer);
|
|
795
|
+
}
|
|
796
|
+
const rejected = [];
|
|
797
|
+
for (const [name, reason] of refusals) {
|
|
798
|
+
rejected.push({ name, reason, required: requiredNames.has(name) });
|
|
799
|
+
}
|
|
738
800
|
// End-of-walk: flush all cache writes in one RPC-equivalent call
|
|
739
801
|
// (this.cache is a SQLite handle; one putRegistryEntries call =
|
|
740
802
|
// O(N) prepared statements within a single DO event-loop turn,
|
|
@@ -767,7 +829,7 @@ export class NpmInstaller {
|
|
|
767
829
|
`, dispatch barriers=${dispatchBarriers}, ` +
|
|
768
830
|
`elapsed=${((Date.now() - t0) / 1000).toFixed(1)}s` +
|
|
769
831
|
(unresolved.size > 0 ? `, unresolved=${unresolved.size}` : ''));
|
|
770
|
-
return { resolved, unresolved };
|
|
832
|
+
return { resolved, unresolved, rejected };
|
|
771
833
|
}
|
|
772
834
|
/**
|
|
773
835
|
* Batch install via two-tier fan-out (Fanout).
|
|
@@ -985,102 +1047,71 @@ export class NpmInstaller {
|
|
|
985
1047
|
}
|
|
986
1048
|
}
|
|
987
1049
|
// ── Spec building ─────────────────────────────────────────────────────
|
|
988
|
-
/**
|
|
989
|
-
* Build the dependency specs from package.json + explicit packages.
|
|
990
|
-
*/
|
|
991
|
-
/**
|
|
992
|
-
* W11: framework detection at install time. Reads package.json from the
|
|
993
|
-
* project root and runs detectFramework() against its deps + the basenames
|
|
994
|
-
* we can see in node_modules-adjacent siblings.
|
|
995
|
-
*
|
|
996
|
-
* Returns true if the project is detected as one of {next, astro, nuxt,
|
|
997
|
-
* remix, sveltekit, vite, wrangler}. False for 'unknown'.
|
|
998
|
-
*/
|
|
999
|
-
async detectFrameworkAware(projDir) {
|
|
1000
|
-
try {
|
|
1001
|
-
const pkgPath = projDir + '/package.json';
|
|
1002
|
-
if (!this.vfs.exists(pkgPath))
|
|
1003
|
-
return false;
|
|
1004
|
-
const pkg = JSON.parse(this.vfs.readFileString(pkgPath));
|
|
1005
|
-
// Lazy-load the detector to avoid a hard dep cycle.
|
|
1006
|
-
const { detectFramework } = await import('@nimbus-sh/core/runtime/framework-detect.js');
|
|
1007
|
-
// Snapshot root files. Best-effort — if readdir throws we proceed
|
|
1008
|
-
// with an empty set (still detects via deps for most frameworks).
|
|
1009
|
-
const files = new Set();
|
|
1010
|
-
try {
|
|
1011
|
-
for (const e of this.vfs.readdir(projDir))
|
|
1012
|
-
files.add(e.name);
|
|
1013
|
-
}
|
|
1014
|
-
catch { /* tolerate */ }
|
|
1015
|
-
// Optional file contents — read only the vite.config.* if present
|
|
1016
|
-
// (used by the Remix gate).
|
|
1017
|
-
const fileContents = {};
|
|
1018
|
-
for (const c of ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']) {
|
|
1019
|
-
if (files.has(c)) {
|
|
1020
|
-
try {
|
|
1021
|
-
fileContents[c] = this.vfs.readFileString(projDir + '/' + c);
|
|
1022
|
-
}
|
|
1023
|
-
catch { }
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
const result = detectFramework({
|
|
1027
|
-
pkg: { dependencies: pkg.dependencies, devDependencies: pkg.devDependencies, scripts: pkg.scripts },
|
|
1028
|
-
files,
|
|
1029
|
-
fileContents,
|
|
1030
|
-
});
|
|
1031
|
-
return result.framework !== 'unknown';
|
|
1032
|
-
}
|
|
1033
|
-
catch {
|
|
1034
|
-
return false;
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
1050
|
async buildSpecs(projDir, explicitPackages, production) {
|
|
1038
1051
|
const specs = {};
|
|
1052
|
+
// Which names only a devDependency asked for — diagnostics only, so an
|
|
1053
|
+
// advisory can say whether anything this project RUNS actually needs
|
|
1054
|
+
// the package. A devDependency is still a required root.
|
|
1055
|
+
const devOnly = new Set();
|
|
1056
|
+
// package.json optionalDependencies: the only roots the required
|
|
1057
|
+
// closure does not cover — npm installs them best-effort.
|
|
1058
|
+
const optionalRoots = new Set();
|
|
1059
|
+
// Names the top-level pass already announced — the walk dedupes its
|
|
1060
|
+
// own advisories against it.
|
|
1061
|
+
const advised = new Set();
|
|
1039
1062
|
if (explicitPackages && explicitPackages.length > 0) {
|
|
1040
1063
|
// Explicit packages: npm install react react-dom@18.2.0
|
|
1041
1064
|
for (const pkg of explicitPackages) {
|
|
1042
1065
|
const parsed = parseExplicitPackageSpec(pkg);
|
|
1043
1066
|
specs[parsed.name] = parsed.range;
|
|
1044
1067
|
}
|
|
1045
|
-
|
|
1068
|
+
const applied = this.applyW6Registry(specs, new Set(), { advised });
|
|
1069
|
+
return { specs: applied.specs, devOnly, optionalRoots, advised };
|
|
1046
1070
|
}
|
|
1047
1071
|
// Read from package.json
|
|
1048
1072
|
const pkgJsonPath = projDir + '/package.json';
|
|
1049
1073
|
if (!this.vfs.exists(pkgJsonPath))
|
|
1050
|
-
return specs;
|
|
1051
|
-
// Which names only a devDependency asked for, so a reject can say whether
|
|
1052
|
-
// anything this project RUNS actually needs the package it refused.
|
|
1053
|
-
const devOnly = new Set();
|
|
1074
|
+
return { specs, devOnly, optionalRoots, advised };
|
|
1054
1075
|
try {
|
|
1055
1076
|
const pkgJson = JSON.parse(this.vfs.readFileString(pkgJsonPath));
|
|
1056
|
-
//
|
|
1077
|
+
// Every declared dependency is a spec. What cannot run here is
|
|
1078
|
+
// named by the advisory pass below; nothing a project declares is
|
|
1079
|
+
// quietly left out of node_modules.
|
|
1057
1080
|
for (const [name, range] of Object.entries(pkgJson.dependencies || {})) {
|
|
1058
|
-
|
|
1059
|
-
specs[name] = range;
|
|
1060
|
-
}
|
|
1081
|
+
specs[name] = range;
|
|
1061
1082
|
}
|
|
1062
|
-
// Include devDeps unless production mode, skipping build-only
|
|
1063
1083
|
for (const [name, range] of Object.entries(pkgJson.devDependencies || {})) {
|
|
1064
|
-
if (
|
|
1065
|
-
continue;
|
|
1066
|
-
if (production)
|
|
1084
|
+
if (name in specs || production)
|
|
1067
1085
|
continue;
|
|
1068
1086
|
specs[name] = range;
|
|
1069
1087
|
devOnly.add(name);
|
|
1070
1088
|
}
|
|
1089
|
+
// npm semantics: an optionalDependencies entry overrides the same
|
|
1090
|
+
// name in dependencies — it is a best-effort root, not required.
|
|
1091
|
+
for (const [name, range] of Object.entries(pkgJson.optionalDependencies || {})) {
|
|
1092
|
+
specs[name] = range;
|
|
1093
|
+
optionalRoots.add(name);
|
|
1094
|
+
}
|
|
1071
1095
|
}
|
|
1072
1096
|
catch { /* corrupt package.json */ }
|
|
1073
|
-
|
|
1097
|
+
const applied = this.applyW6Registry(specs, optionalRoots, { declared: true, devOnly, advised });
|
|
1098
|
+
return { specs: applied.specs, devOnly, optionalRoots, advised };
|
|
1074
1099
|
}
|
|
1075
1100
|
/**
|
|
1076
1101
|
* W6: apply the PACKAGE_ABI_POLICY swap rewrites and reject deny list
|
|
1077
|
-
* to a top-level spec map. Emits `[swap]` notices via onProgress
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1102
|
+
* to a top-level spec map. Emits `[swap]` notices via onProgress.
|
|
1103
|
+
*
|
|
1104
|
+
* G2: rejects are advisories — a listed package stays in the spec map
|
|
1105
|
+
* and installs like any other (npm parity: it cannot run here, but
|
|
1106
|
+
* install is the wrong place to say so). Each gets one `[npm] note:`
|
|
1107
|
+
* line per install and an `advisory` registry event; the require-side
|
|
1108
|
+
* refusal carries the reason when the artifact is invoked.
|
|
1109
|
+
* `advised` dedupes across the top-level pass and the walk (the
|
|
1110
|
+
* package still resolves and can surface transitively).
|
|
1080
1111
|
*
|
|
1081
1112
|
* Idempotent: running on already-swapped specs is a no-op.
|
|
1082
1113
|
*/
|
|
1083
|
-
applyW6Registry(specs,
|
|
1114
|
+
applyW6Registry(specs, optionalRoots = new Set(), options = {}) {
|
|
1084
1115
|
const { specs: swapped, swaps } = applySwaps(specs);
|
|
1085
1116
|
for (const s of swaps) {
|
|
1086
1117
|
// onProgress is unguarded everywhere else in this file (rg the
|
|
@@ -1090,49 +1121,62 @@ export class NpmInstaller {
|
|
|
1090
1121
|
// W6.5: telemetry — fire-and-forget; sink swallows its own errors.
|
|
1091
1122
|
emitRegistryEvent({ type: 'swap', from: s.from, to: s.to, ctx: 'top' });
|
|
1092
1123
|
}
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
reason: r.reason,
|
|
1102
|
-
suggest: r.suggest,
|
|
1103
|
-
ctx: 'top',
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
throw new RegistryRejectError(rejects, devOnly);
|
|
1124
|
+
for (const r of findRejects(swapped, 'top')) {
|
|
1125
|
+
emitRegistryEvent({ type: 'advisory', from: r.from, reason: r.reason, suggest: r.suggest, ctx: 'top' });
|
|
1126
|
+
if (options.advised?.has(r.from))
|
|
1127
|
+
continue;
|
|
1128
|
+
options.advised?.add(r.from);
|
|
1129
|
+
const hint = r.suggest ? ` … try: ${r.suggest}` : '';
|
|
1130
|
+
const marker = options.devOnly?.has(r.from) ? ' (declared in devDependencies)' : '';
|
|
1131
|
+
this.onProgress?.(`[npm] note: ${r.from} has no Workers-compatible build${marker}: ${r.reason}${hint}`);
|
|
1107
1132
|
}
|
|
1108
|
-
return swapped;
|
|
1133
|
+
return { specs: swapped };
|
|
1109
1134
|
}
|
|
1110
1135
|
// ── Lockfile ──────────────────────────────────────────────────────────
|
|
1111
1136
|
/**
|
|
1112
1137
|
* Check if a lockfile is still valid against current package.json specs.
|
|
1113
1138
|
*/
|
|
1114
1139
|
isLockfileValid(lockfile, specs) {
|
|
1115
|
-
// Every spec must be in the lockfile
|
|
1116
|
-
|
|
1117
|
-
|
|
1140
|
+
// Every spec must be in the lockfile at a version that still
|
|
1141
|
+
// satisfies the declared range — a package.json edit must never be
|
|
1142
|
+
// answered by yesterday's pin.
|
|
1143
|
+
for (const [name, range] of Object.entries(specs)) {
|
|
1144
|
+
const entry = lockfile.get(name);
|
|
1145
|
+
if (!entry)
|
|
1146
|
+
return false;
|
|
1147
|
+
// An npm: alias answers the INNER range — the same parser the
|
|
1148
|
+
// resolver facet uses for alias specs turns `vliw: npm:react@^19`
|
|
1149
|
+
// into range `^19` checked against the pin under `vliw`. A spec
|
|
1150
|
+
// that is not a semver range at all (git:, github:, URL, file:)
|
|
1151
|
+
// answers presence-only: the pin's record is the lockfile's whole
|
|
1152
|
+
// truth for it.
|
|
1153
|
+
const req = parseRegistryRequest(name, range);
|
|
1154
|
+
if (!isSemverRange(req.range))
|
|
1118
1155
|
continue;
|
|
1119
|
-
if (!
|
|
1156
|
+
if (!satisfiesRange(entry.resolvedVer, req.range))
|
|
1120
1157
|
return false;
|
|
1121
1158
|
}
|
|
1122
|
-
//
|
|
1123
|
-
//
|
|
1124
|
-
//
|
|
1125
|
-
//
|
|
1126
|
-
// the
|
|
1127
|
-
// the
|
|
1128
|
-
//
|
|
1159
|
+
// Every locked package's REQUIRED edges — `dependencies` minus the
|
|
1160
|
+
// names its optionalDependencies override, plus required peers —
|
|
1161
|
+
// must also be in the lockfile. Lockfiles built before X.5-F lack
|
|
1162
|
+
// peer entries and lockfiles written after a required refusal lack
|
|
1163
|
+
// the refused name's siblings' targets; invalidating either forces
|
|
1164
|
+
// the next install to re-resolve and re-report the refusal instead
|
|
1165
|
+
// of printing Done! over a tree that is missing packages. The edge
|
|
1166
|
+
// info comes from the registry cache — if the cache miss happens
|
|
1167
|
+
// too, we play it safe and invalidate (a fresh resolve repopulates
|
|
1168
|
+
// it and is always correct).
|
|
1129
1169
|
for (const [, entry] of lockfile) {
|
|
1130
1170
|
const cached = this.cache.getRegistryEntry(entry.name, entry.resolvedVer);
|
|
1131
|
-
if (!cached)
|
|
1132
|
-
// Registry cache miss for this locked entry — can't verify
|
|
1133
|
-
// peers. Invalidate to be safe (next install will repopulate
|
|
1134
|
-
// the registry cache while resolving).
|
|
1171
|
+
if (!cached)
|
|
1135
1172
|
return false;
|
|
1173
|
+
const optional = safeJsonParse(cached.optionalDepsJson || '{}', {});
|
|
1174
|
+
const deps = safeJsonParse(cached.depsJson || '{}', {});
|
|
1175
|
+
for (const depName of Object.keys(deps)) {
|
|
1176
|
+
if (Object.hasOwn(optional, depName))
|
|
1177
|
+
continue;
|
|
1178
|
+
if (!lockfile.has(depName))
|
|
1179
|
+
return false;
|
|
1136
1180
|
}
|
|
1137
1181
|
const peers = safeJsonParse(cached.peerDepsJson || '{}', {});
|
|
1138
1182
|
for (const peerName of Object.keys(peers)) {
|
|
@@ -1313,9 +1357,9 @@ export class NpmInstaller {
|
|
|
1313
1357
|
//
|
|
1314
1358
|
// We gate on this.esbuild presence purely as a feature flag: a caller
|
|
1315
1359
|
// that constructs the installer without esbuild (e.g. minimal
|
|
1316
|
-
// headless test) opts out of pre-bundling entirely.
|
|
1317
|
-
// also be present because the
|
|
1318
|
-
if (!this.esbuild || !this.
|
|
1360
|
+
// headless test) opts out of pre-bundling entirely. The session's
|
|
1361
|
+
// bundle pool must also be present because the facets run in it.
|
|
1362
|
+
if (!this.esbuild || !this.bundlePool)
|
|
1319
1363
|
return;
|
|
1320
1364
|
const usedSpecifiers = this.scanBareImports(projDir);
|
|
1321
1365
|
// Vite plugins / postcss plugins / build-time tools NEVER ship to the
|
|
@@ -1509,12 +1553,12 @@ export class NpmInstaller {
|
|
|
1509
1553
|
// esbuild's WASM linear memory is per-FACET (~30–80 MiB) and lives
|
|
1510
1554
|
// outside the supervisor. Per-slot try/catch handles failures —
|
|
1511
1555
|
// /preview/@modules/ on-demand bundling recovers.
|
|
1512
|
-
//
|
|
1513
|
-
//
|
|
1514
|
-
//
|
|
1515
|
-
//
|
|
1516
|
-
//
|
|
1517
|
-
//
|
|
1556
|
+
// The esbuild-wasm bytes ride in the session's shared bundle pool
|
|
1557
|
+
// (facets/esbuild-bundle-pool.ts), constructed once per session and
|
|
1558
|
+
// disposed with the installer and dev server — not per pre-bundle
|
|
1559
|
+
// phase — so the supervisor retains one copy, leased once from the
|
|
1560
|
+
// shared allocation budget. See src/esbuild-wasm-bytes.ts for why
|
|
1561
|
+
// the bytes are never cached supervisor-side beyond that.
|
|
1518
1562
|
//
|
|
1519
1563
|
// Bytes are shipped into each facet via IsolatePool's
|
|
1520
1564
|
// `wasmModules` option which workerd registers in the LOADER
|
|
@@ -1540,7 +1584,7 @@ export class NpmInstaller {
|
|
|
1540
1584
|
// slots' settled work, and surface in the supervisor as an
|
|
1541
1585
|
// unhandled rejection. Swallow with a console.error so the
|
|
1542
1586
|
// pre-bundle phase keeps running. Same pattern is used in the
|
|
1543
|
-
//
|
|
1587
|
+
// summary finally block below.
|
|
1544
1588
|
const safeProgress = (msg) => {
|
|
1545
1589
|
try {
|
|
1546
1590
|
this.onProgress?.(msg);
|
|
@@ -1552,42 +1596,18 @@ export class NpmInstaller {
|
|
|
1552
1596
|
catch { }
|
|
1553
1597
|
}
|
|
1554
1598
|
};
|
|
1599
|
+
// Acquired BEFORE any per-slice lease below: first construction
|
|
1600
|
+
// reserves the full supervisor budget while the wasm bytes are
|
|
1601
|
+
// fetched, so a slice lease taken first would wait on itself.
|
|
1555
1602
|
let pool;
|
|
1556
|
-
let retainedWasmRelease = null;
|
|
1557
|
-
const setupAllocation = await acquireSupervisorAllocation(SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES);
|
|
1558
1603
|
try {
|
|
1559
|
-
|
|
1560
|
-
// the thrown fetch error so this background phase aborts cleanly.
|
|
1561
|
-
const wasmBytes = await fetchEsbuildWasmBytes(this.env);
|
|
1562
|
-
const maxRetainedWasmBytes = SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES - PRE_BUNDLE_SLICE_CAP_BYTES;
|
|
1563
|
-
if (wasmBytes.byteLength > maxRetainedWasmBytes) {
|
|
1564
|
-
throw new RangeError(`pre-bundle wasm payload ${wasmBytes.byteLength} exceeds the ${maxRetainedWasmBytes}-byte retained budget`);
|
|
1565
|
-
}
|
|
1566
|
-
// IsolatePool keeps the constructor-time module bytes until
|
|
1567
|
-
// dispose(), so retain their exact credit rather than treating
|
|
1568
|
-
// construction as a handoff that immediately frees the ArrayBuffer.
|
|
1569
|
-
setupAllocation.shrinkTo(wasmBytes.byteLength);
|
|
1570
|
-
try {
|
|
1571
|
-
pool = new IsolatePool(this.env, this.ctx, {
|
|
1572
|
-
concurrency: PRE_BUNDLE_CONCURRENCY,
|
|
1573
|
-
timeoutMs: 60_000,
|
|
1574
|
-
retries: 0,
|
|
1575
|
-
tag: 'pre-bundle',
|
|
1576
|
-
preamble: PRE_BUNDLE_PREAMBLE,
|
|
1577
|
-
wasmModules: { 'esbuild.wasm': wasmBytes },
|
|
1578
|
-
});
|
|
1579
|
-
retainedWasmRelease = setupAllocation.release;
|
|
1580
|
-
}
|
|
1581
|
-
catch (e) {
|
|
1582
|
-
// Pool construction failures remain best-effort pre-bundle failures,
|
|
1583
|
-
// not npm install failures.
|
|
1584
|
-
safeProgress(`Pre-bundle skipped: failed to construct facet pool: ${e?.message || e}`);
|
|
1585
|
-
return;
|
|
1586
|
-
}
|
|
1604
|
+
pool = await this.bundlePool.acquire();
|
|
1587
1605
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1606
|
+
catch (e) {
|
|
1607
|
+
// Pool construction failures remain best-effort pre-bundle failures,
|
|
1608
|
+
// not npm install failures.
|
|
1609
|
+
safeProgress(`Pre-bundle skipped: failed to construct facet pool: ${e?.message || e}`);
|
|
1610
|
+
return;
|
|
1591
1611
|
}
|
|
1592
1612
|
const queue = pending.slice(); // copy; will shift
|
|
1593
1613
|
let okCount = 0;
|
|
@@ -1777,10 +1797,11 @@ export class NpmInstaller {
|
|
|
1777
1797
|
// failure mode that can throw inside runSlot is caught above
|
|
1778
1798
|
// (slice walk, externals, pool.submit, putEsmBundle) — but a
|
|
1779
1799
|
// future regression that adds an unguarded throw to runSlot
|
|
1780
|
-
// would bubble out here. The outer try/finally guarantees
|
|
1781
|
-
//
|
|
1782
|
-
//
|
|
1783
|
-
// logs so the failure mode is visible
|
|
1800
|
+
// would bubble out here. The outer try/finally guarantees the
|
|
1801
|
+
// diag counters get updated for whatever partial run completed;
|
|
1802
|
+
// the pool itself belongs to the session and outlives this phase.
|
|
1803
|
+
// The catch below additionally logs so the failure mode is visible
|
|
1804
|
+
// in supervisor logs.
|
|
1784
1805
|
await Promise.all(Array.from({ length: PRE_BUNDLE_CONCURRENCY }, (_, i) => runSlot(i)));
|
|
1785
1806
|
}
|
|
1786
1807
|
catch (e) {
|
|
@@ -1822,11 +1843,6 @@ export class NpmInstaller {
|
|
|
1822
1843
|
}
|
|
1823
1844
|
catch { }
|
|
1824
1845
|
}
|
|
1825
|
-
try {
|
|
1826
|
-
pool.dispose();
|
|
1827
|
-
}
|
|
1828
|
-
catch { /* best-effort */ }
|
|
1829
|
-
retainedWasmRelease?.();
|
|
1830
1846
|
}
|
|
1831
1847
|
}
|
|
1832
1848
|
/**
|