@nimbus-sh/worker 0.1.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 +219 -0
- package/dist/_shared/bytes.d.ts +18 -0
- package/dist/_shared/bytes.d.ts.map +1 -0
- package/dist/_shared/bytes.js +17 -0
- package/dist/_shared/cache-stats.d.ts +146 -0
- package/dist/_shared/cache-stats.d.ts.map +1 -0
- package/dist/_shared/cache-stats.js +162 -0
- package/dist/_shared/exports-resolver.d.ts +76 -0
- package/dist/_shared/exports-resolver.d.ts.map +1 -0
- package/dist/_shared/exports-resolver.js +284 -0
- package/dist/_shared/install-phase.d.ts +47 -0
- package/dist/_shared/install-phase.d.ts.map +1 -0
- package/dist/_shared/install-phase.js +1 -0
- package/dist/_shared/real-node-imports.d.ts +38 -0
- package/dist/_shared/real-node-imports.d.ts.map +1 -0
- package/dist/_shared/real-node-imports.js +46 -0
- package/dist/_shared/retry.d.ts +93 -0
- package/dist/_shared/retry.d.ts.map +1 -0
- package/dist/_shared/retry.js +161 -0
- package/dist/_shared/session-id.d.ts +39 -0
- package/dist/_shared/session-id.d.ts.map +1 -0
- package/dist/_shared/session-id.js +127 -0
- package/dist/_shared/session-router.d.ts +104 -0
- package/dist/_shared/session-router.d.ts.map +1 -0
- package/dist/_shared/session-router.js +165 -0
- package/dist/_shared/w7-frame.d.ts +93 -0
- package/dist/_shared/w7-frame.d.ts.map +1 -0
- package/dist/_shared/w7-frame.js +402 -0
- package/dist/auth/index.d.ts +16 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +15 -0
- package/dist/auth/middleware.d.ts +85 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +150 -0
- package/dist/auth/token.d.ts +96 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +226 -0
- package/dist/auth/types.d.ts +141 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +114 -0
- package/dist/bindings/d1.d.ts +102 -0
- package/dist/bindings/d1.d.ts.map +1 -0
- package/dist/bindings/d1.js +549 -0
- package/dist/bindings/kv.d.ts +82 -0
- package/dist/bindings/kv.d.ts.map +1 -0
- package/dist/bindings/kv.js +290 -0
- package/dist/bindings/r2.d.ts +137 -0
- package/dist/bindings/r2.d.ts.map +1 -0
- package/dist/bindings/r2.js +379 -0
- package/dist/cirrus-npm-cjs.generated.d.ts +17 -0
- package/dist/cirrus-npm-cjs.generated.d.ts.map +1 -0
- package/dist/cirrus-npm-cjs.generated.js +41 -0
- package/dist/cirrus-plugin-react.generated.d.ts +34 -0
- package/dist/cirrus-plugin-react.generated.d.ts.map +1 -0
- package/dist/cirrus-plugin-react.generated.js +33 -0
- package/dist/constants.d.ts +34 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +116 -0
- package/dist/esbuild-wasm-bundle.generated.d.ts +41 -0
- package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -0
- package/dist/esbuild-wasm-bundle.generated.js +40 -0
- package/dist/facets/cirrus-real.d.ts +147 -0
- package/dist/facets/cirrus-real.d.ts.map +1 -0
- package/dist/facets/cirrus-real.js +981 -0
- package/dist/facets/inner-do-registry.d.ts +41 -0
- package/dist/facets/inner-do-registry.d.ts.map +1 -0
- package/dist/facets/inner-do-registry.js +51 -0
- package/dist/facets/manager.d.ts +314 -0
- package/dist/facets/manager.d.ts.map +1 -0
- package/dist/facets/manager.js +2681 -0
- package/dist/facets/process.d.ts +318 -0
- package/dist/facets/process.d.ts.map +1 -0
- package/dist/facets/process.js +567 -0
- package/dist/facets/real-vite-fs-shim.d.ts +151 -0
- package/dist/facets/real-vite-fs-shim.d.ts.map +1 -0
- package/dist/facets/real-vite-fs-shim.js +1279 -0
- package/dist/facets/real-vite-hmr.d.ts +142 -0
- package/dist/facets/real-vite-hmr.d.ts.map +1 -0
- package/dist/facets/real-vite-hmr.js +476 -0
- package/dist/facets/vite-dev-server.d.ts +293 -0
- package/dist/facets/vite-dev-server.d.ts.map +1 -0
- package/dist/facets/vite-dev-server.js +2580 -0
- package/dist/facets/wasm-swap-registry.d.ts +261 -0
- package/dist/facets/wasm-swap-registry.d.ts.map +1 -0
- package/dist/facets/wasm-swap-registry.js +551 -0
- package/dist/facets/ws-terminal.d.ts +107 -0
- package/dist/facets/ws-terminal.d.ts.map +1 -0
- package/dist/facets/ws-terminal.js +207 -0
- package/dist/git/commands.d.ts +13 -0
- package/dist/git/commands.d.ts.map +1 -0
- package/dist/git/commands.js +633 -0
- package/dist/git/network-facet.d.ts +61 -0
- package/dist/git/network-facet.d.ts.map +1 -0
- package/dist/git/network-facet.js +700 -0
- package/dist/git-bundle.generated.d.ts +16 -0
- package/dist/git-bundle.generated.d.ts.map +1 -0
- package/dist/git-bundle.generated.js +15 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/loaders/child-process/spawn-facet.d.ts +90 -0
- package/dist/loaders/child-process/spawn-facet.d.ts.map +1 -0
- package/dist/loaders/child-process/spawn-facet.js +103 -0
- package/dist/loaders/child-process/spawn-pool.d.ts +110 -0
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -0
- package/dist/loaders/child-process/spawn-pool.js +161 -0
- package/dist/loaders/fanout-pool.d.ts +195 -0
- package/dist/loaders/fanout-pool.d.ts.map +1 -0
- package/dist/loaders/fanout-pool.js +326 -0
- package/dist/loaders/generated-workers.d.ts +26 -0
- package/dist/loaders/generated-workers.d.ts.map +1 -0
- package/dist/loaders/generated-workers.js +25 -0
- package/dist/loaders/index.d.ts +15 -0
- package/dist/loaders/index.d.ts.map +1 -0
- package/dist/loaders/index.js +13 -0
- package/dist/loaders/loader-pool.d.ts +273 -0
- package/dist/loaders/loader-pool.d.ts.map +1 -0
- package/dist/loaders/loader-pool.js +624 -0
- package/dist/loaders/npm-resolve-preamble.d.ts +34 -0
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -0
- package/dist/loaders/npm-resolve-preamble.js +237 -0
- package/dist/loaders/pre-bundle-preamble.d.ts +41 -0
- package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -0
- package/dist/loaders/pre-bundle-preamble.js +114 -0
- package/dist/loaders/vendor/codegen.d.ts +24 -0
- package/dist/loaders/vendor/codegen.d.ts.map +1 -0
- package/dist/loaders/vendor/codegen.js +44 -0
- package/dist/loaders/vendor/errors.d.ts +24 -0
- package/dist/loaders/vendor/errors.d.ts.map +1 -0
- package/dist/loaders/vendor/errors.js +46 -0
- package/dist/loaders/vendor/index.d.ts +16 -0
- package/dist/loaders/vendor/index.d.ts.map +1 -0
- package/dist/loaders/vendor/index.js +11 -0
- package/dist/loaders/vendor/pool.d.ts +88 -0
- package/dist/loaders/vendor/pool.d.ts.map +1 -0
- package/dist/loaders/vendor/pool.js +353 -0
- package/dist/loaders/vendor/primitives.d.ts +11 -0
- package/dist/loaders/vendor/primitives.d.ts.map +1 -0
- package/dist/loaders/vendor/primitives.js +33 -0
- package/dist/loaders/vendor/serialize.d.ts +3 -0
- package/dist/loaders/vendor/serialize.d.ts.map +1 -0
- package/dist/loaders/vendor/serialize.js +25 -0
- package/dist/loaders/vendor/types.d.ts +60 -0
- package/dist/loaders/vendor/types.d.ts.map +1 -0
- package/dist/loaders/vendor/types.js +4 -0
- package/dist/npm/cache.d.ts +136 -0
- package/dist/npm/cache.d.ts.map +1 -0
- package/dist/npm/cache.js +429 -0
- package/dist/npm/install-batch-facet.d.ts +129 -0
- package/dist/npm/install-batch-facet.d.ts.map +1 -0
- package/dist/npm/install-batch-facet.js +598 -0
- package/dist/npm/install-facet.d.ts +29 -0
- package/dist/npm/install-facet.d.ts.map +1 -0
- package/dist/npm/install-facet.js +9 -0
- package/dist/npm/installer.d.ts +239 -0
- package/dist/npm/installer.d.ts.map +1 -0
- package/dist/npm/installer.js +2022 -0
- package/dist/npm/npx-install.d.ts +74 -0
- package/dist/npm/npx-install.d.ts.map +1 -0
- package/dist/npm/npx-install.js +302 -0
- package/dist/npm/pre-bundle-facet.d.ts +168 -0
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -0
- package/dist/npm/pre-bundle-facet.js +653 -0
- package/dist/npm/r2-cache.d.ts +286 -0
- package/dist/npm/r2-cache.d.ts.map +1 -0
- package/dist/npm/r2-cache.js +506 -0
- package/dist/npm/resolve-facet.d.ts +202 -0
- package/dist/npm/resolve-facet.d.ts.map +1 -0
- package/dist/npm/resolve-facet.js +833 -0
- package/dist/npm/resolve-one-facet.d.ts +204 -0
- package/dist/npm/resolve-one-facet.d.ts.map +1 -0
- package/dist/npm/resolve-one-facet.js +437 -0
- package/dist/npm/resolver.d.ts +109 -0
- package/dist/npm/resolver.d.ts.map +1 -0
- package/dist/npm/resolver.js +899 -0
- package/dist/npm/tarball-stream.d.ts +85 -0
- package/dist/npm/tarball-stream.d.ts.map +1 -0
- package/dist/npm/tarball-stream.js +205 -0
- package/dist/npm/tarball.d.ts +51 -0
- package/dist/npm/tarball.d.ts.map +1 -0
- package/dist/npm/tarball.js +185 -0
- package/dist/observability/diag-counters.d.ts +292 -0
- package/dist/observability/diag-counters.d.ts.map +1 -0
- package/dist/observability/diag-counters.js +302 -0
- package/dist/observability/heap-estimate.d.ts +166 -0
- package/dist/observability/heap-estimate.d.ts.map +1 -0
- package/dist/observability/heap-estimate.js +192 -0
- package/dist/observability/heavy-alloc-coord.d.ts +95 -0
- package/dist/observability/heavy-alloc-coord.d.ts.map +1 -0
- package/dist/observability/heavy-alloc-coord.js +149 -0
- package/dist/observability/oom-classify.d.ts +56 -0
- package/dist/observability/oom-classify.d.ts.map +1 -0
- package/dist/observability/oom-classify.js +118 -0
- package/dist/observability/oom-discriminator.d.ts +192 -0
- package/dist/observability/oom-discriminator.d.ts.map +1 -0
- package/dist/observability/oom-discriminator.js +233 -0
- package/dist/real-vite-bundle.generated.d.ts +46 -0
- package/dist/real-vite-bundle.generated.d.ts.map +1 -0
- package/dist/real-vite-bundle.generated.js +53 -0
- package/dist/replica/routing.d.ts +171 -0
- package/dist/replica/routing.d.ts.map +1 -0
- package/dist/replica/routing.js +288 -0
- package/dist/replica/suspension.d.ts +36 -0
- package/dist/replica/suspension.d.ts.map +1 -0
- package/dist/replica/suspension.js +49 -0
- package/dist/router/index.d.ts +156 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +220 -0
- package/dist/runtime/assets-loader.d.ts +71 -0
- package/dist/runtime/assets-loader.d.ts.map +1 -0
- package/dist/runtime/assets-loader.js +128 -0
- package/dist/runtime/barrel-detect.d.ts +46 -0
- package/dist/runtime/barrel-detect.d.ts.map +1 -0
- package/dist/runtime/barrel-detect.js +73 -0
- package/dist/runtime/barrel-synthesizer.d.ts +181 -0
- package/dist/runtime/barrel-synthesizer.d.ts.map +1 -0
- package/dist/runtime/barrel-synthesizer.js +541 -0
- package/dist/runtime/bun-repl.d.ts +46 -0
- package/dist/runtime/bun-repl.d.ts.map +1 -0
- package/dist/runtime/bun-repl.js +326 -0
- package/dist/runtime/bun-runner.d.ts +66 -0
- package/dist/runtime/bun-runner.d.ts.map +1 -0
- package/dist/runtime/bun-runner.js +219 -0
- package/dist/runtime/clang-runner.d.ts +38 -0
- package/dist/runtime/clang-runner.d.ts.map +1 -0
- package/dist/runtime/clang-runner.js +1033 -0
- package/dist/runtime/comment-strip.d.ts +37 -0
- package/dist/runtime/comment-strip.d.ts.map +1 -0
- package/dist/runtime/comment-strip.js +74 -0
- package/dist/runtime/esbuild-service.d.ts +201 -0
- package/dist/runtime/esbuild-service.d.ts.map +1 -0
- package/dist/runtime/esbuild-service.js +1444 -0
- package/dist/runtime/esbuild-wasm-bytes.d.ts +87 -0
- package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -0
- package/dist/runtime/esbuild-wasm-bytes.js +131 -0
- package/dist/runtime/framework-detect.d.ts +71 -0
- package/dist/runtime/framework-detect.d.ts.map +1 -0
- package/dist/runtime/framework-detect.js +178 -0
- package/dist/runtime/long-running-handle.d.ts +133 -0
- package/dist/runtime/long-running-handle.d.ts.map +1 -0
- package/dist/runtime/long-running-handle.js +192 -0
- package/dist/runtime/node-repl.d.ts +36 -0
- package/dist/runtime/node-repl.d.ts.map +1 -0
- package/dist/runtime/node-repl.js +293 -0
- package/dist/runtime/node-runner.d.ts +111 -0
- package/dist/runtime/node-runner.d.ts.map +1 -0
- package/dist/runtime/node-runner.js +194 -0
- package/dist/runtime/node-shims.d.ts +24 -0
- package/dist/runtime/node-shims.d.ts.map +1 -0
- package/dist/runtime/node-shims.js +3019 -0
- package/dist/runtime/package-manager.d.ts +118 -0
- package/dist/runtime/package-manager.d.ts.map +1 -0
- package/dist/runtime/package-manager.js +420 -0
- package/dist/runtime/port-registry.d.ts +80 -0
- package/dist/runtime/port-registry.d.ts.map +1 -0
- package/dist/runtime/port-registry.js +166 -0
- package/dist/runtime/process-logs-api.d.ts +102 -0
- package/dist/runtime/process-logs-api.d.ts.map +1 -0
- package/dist/runtime/process-logs-api.js +253 -0
- package/dist/runtime/process-logs.d.ts +311 -0
- package/dist/runtime/process-logs.d.ts.map +1 -0
- package/dist/runtime/process-logs.js +656 -0
- package/dist/runtime/process-table.d.ts +65 -0
- package/dist/runtime/process-table.d.ts.map +1 -0
- package/dist/runtime/process-table.js +107 -0
- package/dist/runtime/project-detect.d.ts +21 -0
- package/dist/runtime/project-detect.d.ts.map +1 -0
- package/dist/runtime/project-detect.js +46 -0
- package/dist/runtime/python-repl.d.ts +66 -0
- package/dist/runtime/python-repl.d.ts.map +1 -0
- package/dist/runtime/python-repl.js +563 -0
- package/dist/runtime/python-runner.d.ts +50 -0
- package/dist/runtime/python-runner.d.ts.map +1 -0
- package/dist/runtime/python-runner.js +870 -0
- package/dist/runtime/repl-session.d.ts +160 -0
- package/dist/runtime/repl-session.d.ts.map +1 -0
- package/dist/runtime/repl-session.js +393 -0
- package/dist/runtime/require-resolver.d.ts +52 -0
- package/dist/runtime/require-resolver.d.ts.map +1 -0
- package/dist/runtime/require-resolver.js +690 -0
- package/dist/runtime/router-basename.d.ts +36 -0
- package/dist/runtime/router-basename.d.ts.map +1 -0
- package/dist/runtime/router-basename.js +310 -0
- package/dist/runtime/ruby-repl.d.ts +43 -0
- package/dist/runtime/ruby-repl.d.ts.map +1 -0
- package/dist/runtime/ruby-repl.js +229 -0
- package/dist/runtime/ruby-runner.d.ts +62 -0
- package/dist/runtime/ruby-runner.d.ts.map +1 -0
- package/dist/runtime/ruby-runner.js +737 -0
- package/dist/runtime/runtime-catalog.d.ts +99 -0
- package/dist/runtime/runtime-catalog.d.ts.map +1 -0
- package/dist/runtime/runtime-catalog.js +183 -0
- package/dist/runtime/runtime-registry.d.ts +136 -0
- package/dist/runtime/runtime-registry.d.ts.map +1 -0
- package/dist/runtime/runtime-registry.js +318 -0
- package/dist/runtime/streams.d.ts +13 -0
- package/dist/runtime/streams.d.ts.map +1 -0
- package/dist/runtime/streams.js +402 -0
- package/dist/runtime/wasi-instance.d.ts +190 -0
- package/dist/runtime/wasi-instance.d.ts.map +1 -0
- package/dist/runtime/wasi-instance.js +2044 -0
- package/dist/runtime/wasm-runner.d.ts +117 -0
- package/dist/runtime/wasm-runner.d.ts.map +1 -0
- package/dist/runtime/wasm-runner.js +705 -0
- package/dist/session/bindings.d.ts +168 -0
- package/dist/session/bindings.d.ts.map +1 -0
- package/dist/session/bindings.js +524 -0
- package/dist/session/ctx-exports.d.ts +16 -0
- package/dist/session/ctx-exports.d.ts.map +1 -0
- package/dist/session/ctx-exports.js +22 -0
- package/dist/session/diag.d.ts +62 -0
- package/dist/session/diag.d.ts.map +1 -0
- package/dist/session/diag.js +118 -0
- package/dist/session/fs-watch.d.ts +132 -0
- package/dist/session/fs-watch.d.ts.map +1 -0
- package/dist/session/fs-watch.js +255 -0
- package/dist/session/helpers.d.ts +150 -0
- package/dist/session/helpers.d.ts.map +1 -0
- package/dist/session/helpers.js +472 -0
- package/dist/session/hibernation.d.ts +145 -0
- package/dist/session/hibernation.d.ts.map +1 -0
- package/dist/session/hibernation.js +385 -0
- package/dist/session/init-phases.d.ts +87 -0
- package/dist/session/init-phases.d.ts.map +1 -0
- package/dist/session/init-phases.js +137 -0
- package/dist/session/init.d.ts +46 -0
- package/dist/session/init.d.ts.map +1 -0
- package/dist/session/init.js +3579 -0
- package/dist/session/keys.d.ts +56 -0
- package/dist/session/keys.d.ts.map +1 -0
- package/dist/session/keys.js +55 -0
- package/dist/session/nimbus-session.d.ts +434 -0
- package/dist/session/nimbus-session.d.ts.map +1 -0
- package/dist/session/nimbus-session.js +1084 -0
- package/dist/session/programmatic.d.ts +105 -0
- package/dist/session/programmatic.d.ts.map +1 -0
- package/dist/session/programmatic.js +326 -0
- package/dist/session/replica-routes.d.ts +68 -0
- package/dist/session/replica-routes.d.ts.map +1 -0
- package/dist/session/replica-routes.js +77 -0
- package/dist/session/routes.d.ts +29 -0
- package/dist/session/routes.d.ts.map +1 -0
- package/dist/session/routes.js +1331 -0
- package/dist/session/rpc.d.ts +323 -0
- package/dist/session/rpc.d.ts.map +1 -0
- package/dist/session/rpc.js +808 -0
- package/dist/session/state-store.d.ts +210 -0
- package/dist/session/state-store.d.ts.map +1 -0
- package/dist/session/state-store.js +459 -0
- package/dist/session/supervisor-rpc.d.ts +269 -0
- package/dist/session/supervisor-rpc.d.ts.map +1 -0
- package/dist/session/supervisor-rpc.js +437 -0
- package/dist/session/ws-hibernation-config.d.ts +64 -0
- package/dist/session/ws-hibernation-config.d.ts.map +1 -0
- package/dist/session/ws-hibernation-config.js +89 -0
- package/dist/session/ws.d.ts +95 -0
- package/dist/session/ws.d.ts.map +1 -0
- package/dist/session/ws.js +395 -0
- package/dist/shell/features.d.ts +410 -0
- package/dist/shell/features.d.ts.map +1 -0
- package/dist/shell/features.js +1885 -0
- package/dist/shell/unix-commands.d.ts +15 -0
- package/dist/shell/unix-commands.d.ts.map +1 -0
- package/dist/shell/unix-commands.js +3703 -0
- package/dist/stubs/lifo-ui.d.ts +10 -0
- package/dist/stubs/lifo-ui.d.ts.map +1 -0
- package/dist/stubs/lifo-ui.js +1 -0
- package/dist/tailwind-play.generated.d.ts +19 -0
- package/dist/tailwind-play.generated.d.ts.map +1 -0
- package/dist/tailwind-play.generated.js +18 -0
- package/dist/vfs/dev-provider.d.ts +64 -0
- package/dist/vfs/dev-provider.d.ts.map +1 -0
- package/dist/vfs/dev-provider.js +191 -0
- package/dist/vfs/events.d.ts +51 -0
- package/dist/vfs/events.d.ts.map +1 -0
- package/dist/vfs/events.js +127 -0
- package/dist/vfs/path.d.ts +30 -0
- package/dist/vfs/path.d.ts.map +1 -0
- package/dist/vfs/path.js +41 -0
- package/dist/vfs/seed-project.d.ts +62 -0
- package/dist/vfs/seed-project.d.ts.map +1 -0
- package/dist/vfs/seed-project.js +1045 -0
- package/dist/vfs/sqlite-vfs.d.ts +435 -0
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -0
- package/dist/vfs/sqlite-vfs.js +1670 -0
- package/dist/vfs/symlink-registry.d.ts +64 -0
- package/dist/vfs/symlink-registry.d.ts.map +1 -0
- package/dist/vfs/symlink-registry.js +137 -0
- package/dist/wrangler/nimbus-wrangler.d.ts +157 -0
- package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -0
- package/dist/wrangler/nimbus-wrangler.js +931 -0
- package/package.json +77 -0
- package/public/_assets/cirrus-plugin-react.bundle.js +70740 -0
- package/public/_assets/esbuild-0.24.2.wasm +0 -0
- package/public/_assets/real-vite-bundle.js +101478 -0
- package/public/_assets/rollup.wasm +0 -0
- package/public/favicon.svg +8 -0
- package/public/index.html +565 -0
- package/public/s/index.html +2379 -0
- package/scripts/bundle-esbuild-wasm.mjs +244 -0
- package/scripts/bundle-facet-workers.mjs +137 -0
- package/scripts/bundle-git.mjs +118 -0
- package/scripts/bundle-npm-cjs.mjs +363 -0
- package/scripts/bundle-plugin-react.mjs +480 -0
- package/scripts/bundle-real-vite.mjs +959 -0
- package/scripts/bundle-runtime.mjs +600 -0
- package/scripts/bundle-tailwind-play.mjs +130 -0
- package/scripts/patch-lifo-ui.mjs +131 -0
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm-resolver.ts — Semver resolution, exports field, and hoisting for Nimbus npm v2.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* 1. Proper semver parsing + range matching (^, ~, >=, ||, *, x ranges)
|
|
6
|
+
* 2. Node.js-spec exports field resolution with conditions
|
|
7
|
+
* 3. Aggressive hoisting algorithm (one copy of each version at the highest level)
|
|
8
|
+
* 4. Build-only package skip list
|
|
9
|
+
*/
|
|
10
|
+
import { retryableFetch, DEFAULT_RETRIES } from '../_shared/retry.js';
|
|
11
|
+
import { setResolverPhase, packumentFetchStart, packumentFetchEnd, responseStubDisposed, } from '../observability/diag-counters.js';
|
|
12
|
+
import { lookupSwap, lookupReject, shouldWarnSkipTransitive, formatSwapNotice, formatTransitiveSkip, RegistryRejectError, emitRegistryEvent, isOptionalNativeBinding, classifyInstallError, } from '../facets/wasm-swap-registry.js';
|
|
13
|
+
// W2.6a D6: resolver-unification. The single source of truth for
|
|
14
|
+
// exports-field / package-entry resolution lives in
|
|
15
|
+
// src/_shared/exports-resolver.ts. Callers that need these helpers
|
|
16
|
+
// import directly from that module — no thin wrappers re-exported
|
|
17
|
+
// from this file. (Pre-W2.6a we kept thin re-exports here for back-
|
|
18
|
+
// compat; they were redundant and tripped a `grep "function resolve*"`
|
|
19
|
+
// test that wants exactly one impl.)
|
|
20
|
+
const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
21
|
+
/** Max concurrent registry fetches. Bounded to avoid ephemeral port exhaustion. */
|
|
22
|
+
const RESOLVE_CONCURRENCY = 6;
|
|
23
|
+
/** Timeout for registry fetches (ms). Aborts if DO fetch hangs. */
|
|
24
|
+
const FETCH_TIMEOUT_MS = 15_000;
|
|
25
|
+
/**
|
|
26
|
+
* Simple concurrency limiter. Prevents ephemeral port exhaustion when
|
|
27
|
+
* making many fetch() calls through a single proxy worker.
|
|
28
|
+
*/
|
|
29
|
+
export function pLimit(concurrency) {
|
|
30
|
+
let active = 0;
|
|
31
|
+
const queue = [];
|
|
32
|
+
return (fn) => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const run = async () => {
|
|
35
|
+
active++;
|
|
36
|
+
try {
|
|
37
|
+
resolve(await fn());
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
reject(e);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
active--;
|
|
44
|
+
if (queue.length > 0)
|
|
45
|
+
queue.shift()();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
if (active < concurrency)
|
|
49
|
+
run();
|
|
50
|
+
else
|
|
51
|
+
queue.push(run);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ── Semver ──────────────────────────────────────────────────────────────
|
|
56
|
+
/** Parse a semver string into [major, minor, patch, prerelease?]. */
|
|
57
|
+
function parseSemver(v) {
|
|
58
|
+
const m = v.replace(/^v/, '').match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
59
|
+
return m ? [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])] : null;
|
|
60
|
+
}
|
|
61
|
+
/** Compare two semver tuples. Returns <0, 0, >0. */
|
|
62
|
+
function compareSemver(a, b) {
|
|
63
|
+
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
64
|
+
}
|
|
65
|
+
/** Check if version satisfies a single comparator (^, ~, >=, >, <=, <, =, exact). */
|
|
66
|
+
function satisfiesComparator(version, comparator) {
|
|
67
|
+
const comp = comparator.trim();
|
|
68
|
+
if (!comp || comp === '*' || comp === 'latest' || comp === '' || comp === 'x')
|
|
69
|
+
return true;
|
|
70
|
+
// Handle >= <= > < = prefixes
|
|
71
|
+
let op = '';
|
|
72
|
+
let rangeStr = comp;
|
|
73
|
+
const prefixMatch = comp.match(/^([~^]|>=|<=|>|<|=)\s*/);
|
|
74
|
+
if (prefixMatch) {
|
|
75
|
+
op = prefixMatch[1];
|
|
76
|
+
rangeStr = comp.slice(prefixMatch[0].length);
|
|
77
|
+
}
|
|
78
|
+
// Handle x-ranges: 1.x, 1.2.x, 1.x.x
|
|
79
|
+
rangeStr = rangeStr.replace(/\.x/g, '.0');
|
|
80
|
+
if (rangeStr.match(/^\d+$/))
|
|
81
|
+
rangeStr += '.0.0';
|
|
82
|
+
else if (rangeStr.match(/^\d+\.\d+$/))
|
|
83
|
+
rangeStr += '.0';
|
|
84
|
+
const vParts = parseSemver(version);
|
|
85
|
+
const rParts = parseSemver(rangeStr);
|
|
86
|
+
if (!vParts || !rParts)
|
|
87
|
+
return false;
|
|
88
|
+
const cmp = compareSemver(vParts, rParts);
|
|
89
|
+
switch (op) {
|
|
90
|
+
case '^': {
|
|
91
|
+
// ^major.minor.patch: >=X.Y.Z <(next major)
|
|
92
|
+
// ^0.Y.Z: >=0.Y.Z <0.(Y+1).0
|
|
93
|
+
// ^0.0.Z: >=0.0.Z <0.0.(Z+1)
|
|
94
|
+
if (rParts[0] > 0) {
|
|
95
|
+
return vParts[0] === rParts[0] && cmp >= 0;
|
|
96
|
+
}
|
|
97
|
+
if (rParts[1] > 0) {
|
|
98
|
+
return vParts[0] === 0 && vParts[1] === rParts[1] && cmp >= 0;
|
|
99
|
+
}
|
|
100
|
+
return vParts[0] === 0 && vParts[1] === 0 && vParts[2] === rParts[2];
|
|
101
|
+
}
|
|
102
|
+
case '~': {
|
|
103
|
+
// ~major.minor.patch: >=X.Y.Z <X.(Y+1).0
|
|
104
|
+
return vParts[0] === rParts[0] && vParts[1] === rParts[1] && vParts[2] >= rParts[2];
|
|
105
|
+
}
|
|
106
|
+
case '>=': return cmp >= 0;
|
|
107
|
+
case '>': return cmp > 0;
|
|
108
|
+
case '<=': return cmp <= 0;
|
|
109
|
+
case '<': return cmp < 0;
|
|
110
|
+
case '=': return cmp === 0;
|
|
111
|
+
default: {
|
|
112
|
+
// No prefix: if original had no prefix and looks like a version, treat as ^
|
|
113
|
+
// This handles "1.2.3" which npm treats as "^1.2.3" in package.json
|
|
114
|
+
if (comp.match(/^\d/)) {
|
|
115
|
+
// Exact match for unprefixed versions
|
|
116
|
+
return cmp === 0;
|
|
117
|
+
}
|
|
118
|
+
return cmp === 0;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if a version satisfies a full range expression.
|
|
124
|
+
* Supports || (OR), space (AND within a range set), hyphen ranges.
|
|
125
|
+
*/
|
|
126
|
+
export function satisfiesRange(version, range) {
|
|
127
|
+
const trimmed = range.trim();
|
|
128
|
+
if (!trimmed || trimmed === '*' || trimmed === 'latest' || trimmed === '')
|
|
129
|
+
return true;
|
|
130
|
+
// Split on || for OR logic
|
|
131
|
+
const orParts = trimmed.split(/\s*\|\|\s*/);
|
|
132
|
+
for (const orPart of orParts) {
|
|
133
|
+
// Handle hyphen range: 1.0.0 - 2.0.0 → >=1.0.0 <=2.0.0
|
|
134
|
+
const hyphen = orPart.match(/^(\S+)\s+-\s+(\S+)$/);
|
|
135
|
+
if (hyphen) {
|
|
136
|
+
if (satisfiesComparator(version, '>=' + hyphen[1]) &&
|
|
137
|
+
satisfiesComparator(version, '<=' + hyphen[2])) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Split on space for AND logic within a range set
|
|
143
|
+
const andParts = orPart.trim().split(/\s+/);
|
|
144
|
+
const allMatch = andParts.every(part => satisfiesComparator(version, part));
|
|
145
|
+
if (allMatch)
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
/** Find the highest version matching a range from a list of versions. */
|
|
151
|
+
export function resolveVersion(versions, range) {
|
|
152
|
+
if (!range || range === 'latest' || range === '*' || range === '')
|
|
153
|
+
return null;
|
|
154
|
+
const matching = versions.filter(v => {
|
|
155
|
+
// Skip pre-release versions unless the range specifically targets them
|
|
156
|
+
if (v.includes('-') && !range.includes('-'))
|
|
157
|
+
return false;
|
|
158
|
+
return satisfiesRange(v, range);
|
|
159
|
+
});
|
|
160
|
+
if (matching.length === 0)
|
|
161
|
+
return null;
|
|
162
|
+
// Sort descending, pick highest
|
|
163
|
+
matching.sort((a, b) => {
|
|
164
|
+
const ap = parseSemver(a);
|
|
165
|
+
const bp = parseSemver(b);
|
|
166
|
+
if (!ap || !bp)
|
|
167
|
+
return 0;
|
|
168
|
+
return compareSemver(bp, ap);
|
|
169
|
+
});
|
|
170
|
+
return matching[0];
|
|
171
|
+
}
|
|
172
|
+
// ── Package resolution ──────────────────────────────────────────────────
|
|
173
|
+
/**
|
|
174
|
+
* Resolve a single package from the registry.
|
|
175
|
+
* Checks cache first, then fetches from npm.
|
|
176
|
+
*/
|
|
177
|
+
export async function resolvePackage(name, versionRange, cache, fetchFn, log) {
|
|
178
|
+
// 1. Check for exact version in registry cache
|
|
179
|
+
try {
|
|
180
|
+
const cleanRange = versionRange.replace(/^[~^>=<\s]+/, '');
|
|
181
|
+
if (cleanRange.match(/^\d+\.\d+\.\d+$/)) {
|
|
182
|
+
const cached = cache.getRegistryEntry(name, cleanRange);
|
|
183
|
+
if (cached) {
|
|
184
|
+
log?.(` ${name}: found exact ${cleanRange} in cache`);
|
|
185
|
+
return registryCacheToResolved(cached);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// 2. Check if we have cached versions to resolve against
|
|
189
|
+
const cachedVersions = cache.getRegistryVersions(name);
|
|
190
|
+
if (cachedVersions.length > 0) {
|
|
191
|
+
const isDistTag = !versionRange || versionRange === 'latest' || versionRange === '*' || versionRange === '';
|
|
192
|
+
if (isDistTag) {
|
|
193
|
+
// "latest", "*", "" → pick the highest cached version (sort descending)
|
|
194
|
+
const sorted = cachedVersions
|
|
195
|
+
.map(e => ({ entry: e, parsed: parseSemver(e.version) }))
|
|
196
|
+
.filter(x => x.parsed !== null)
|
|
197
|
+
.sort((a, b) => compareSemver(b.parsed, a.parsed));
|
|
198
|
+
if (sorted.length > 0) {
|
|
199
|
+
log?.(` ${name}: resolved ${versionRange || '(empty)'} → ${sorted[0].entry.version} (highest cached)`);
|
|
200
|
+
return registryCacheToResolved(sorted[0].entry);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Semver range: try to match against cached versions
|
|
205
|
+
const versions = cachedVersions.map(e => e.version);
|
|
206
|
+
const resolved = resolveVersion(versions, versionRange);
|
|
207
|
+
if (resolved) {
|
|
208
|
+
const entry = cachedVersions.find(e => e.version === resolved);
|
|
209
|
+
if (entry) {
|
|
210
|
+
log?.(` ${name}: resolved ${versionRange} → ${resolved} from cache`);
|
|
211
|
+
return registryCacheToResolved(entry);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Cached versions exist but none match — only skip fetch if cache is fresh
|
|
215
|
+
const newest = Math.max(...cachedVersions.map(e => e.fetchedAt));
|
|
216
|
+
if (Date.now() - newest < 3600_000) {
|
|
217
|
+
log?.(` ${name}: ${cachedVersions.length} cached versions, none match ${versionRange}, cache fresh`);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
// Cache read failed (schema issue, corrupt data, etc.) — fall through to network
|
|
225
|
+
log?.(` ${name}: cache read error: ${e?.message}`);
|
|
226
|
+
}
|
|
227
|
+
// 3. Fetch from registry
|
|
228
|
+
log?.(` ${name}: fetching from registry (${fetchFn ? 'proxy' : 'direct'})...`);
|
|
229
|
+
setResolverPhase('fetching');
|
|
230
|
+
packumentFetchStart(name);
|
|
231
|
+
let data;
|
|
232
|
+
let bytesDecoded = 0;
|
|
233
|
+
try {
|
|
234
|
+
const safeName = name.startsWith('@')
|
|
235
|
+
? '@' + encodeURIComponent(name.slice(1))
|
|
236
|
+
: encodeURIComponent(name);
|
|
237
|
+
const url = `${NPM_REGISTRY}/${safeName}`;
|
|
238
|
+
// retryableFetch: 3 retries on 5xx/network errors with jittered
|
|
239
|
+
// exponential backoff. Per-attempt timeout = the prior single-attempt
|
|
240
|
+
// budget (FETCH_TIMEOUT_MS) — fresh AbortController per attempt so a
|
|
241
|
+
// slow failure doesn't eat the whole retry window. fetchFn is
|
|
242
|
+
// forwarded so proxy-fetch paths keep working.
|
|
243
|
+
const resp = await retryableFetch(url, {
|
|
244
|
+
headers: { 'Accept': 'application/json' },
|
|
245
|
+
}, {
|
|
246
|
+
retries: DEFAULT_RETRIES,
|
|
247
|
+
name,
|
|
248
|
+
fetchImpl: fetchFn,
|
|
249
|
+
perAttemptTimeoutMs: FETCH_TIMEOUT_MS,
|
|
250
|
+
onRetry: (attempt, total, delayMs, reason) => {
|
|
251
|
+
log?.(` ${name}: retry ${attempt}/${total} after ${delayMs}ms (${reason})`);
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
log?.(` ${name}: registry responded ${resp.status}`);
|
|
255
|
+
// Dispose the (potentially RPC-stub-backed) Response explicitly once
|
|
256
|
+
// the body is consumed / dropped. When fetchFn is the supervisor fetch
|
|
257
|
+
// proxy, `resp` is a cross-isolate stub returned from
|
|
258
|
+
// entrypoint.fetch(...). Those stubs auto-dispose only at the end of
|
|
259
|
+
// the enclosing event-handler context — which for `npm install` means
|
|
260
|
+
// the stubs from ALL ~200 packument fetches stay live until the whole
|
|
261
|
+
// install completes. That accumulation is the trigger for
|
|
262
|
+
// "An RPC result was not disposed properly" warnings and the
|
|
263
|
+
// workerd queueState != ACTIVE fatal seen during cold-start installs
|
|
264
|
+
// (see WORKERD-CRASH.md / FINAL-H2-STATUS.md §4). Explicit disposal
|
|
265
|
+
// releases each stub immediately after we finish reading it, keeping
|
|
266
|
+
// the live-stub count at O(1) instead of O(packages resolved).
|
|
267
|
+
try {
|
|
268
|
+
if (!resp.ok) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
// Read body as text first so we know its size for diag accounting,
|
|
272
|
+
// THEN parse. Saves nothing memory-wise (one extra string copy)
|
|
273
|
+
// but is essential for surfacing `cumulativePackumentBytesDecoded`
|
|
274
|
+
// — the smoking gun that proves the resolver-OOM hypothesis.
|
|
275
|
+
// The text+parse split also gives us a chance to surface a
|
|
276
|
+
// honest decoded size to the diag layer even when the proxy
|
|
277
|
+
// didn't advertise Content-Length.
|
|
278
|
+
setResolverPhase('parsing');
|
|
279
|
+
const text = await resp.text();
|
|
280
|
+
bytesDecoded = text.length;
|
|
281
|
+
data = JSON.parse(text);
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
// Symbol.dispose is ES2023; our tsconfig targets ES2022 so we reach
|
|
285
|
+
// it via the any-cast. At runtime workerd provides the symbol on
|
|
286
|
+
// RPC stubs; on plain Response objects the getter simply returns
|
|
287
|
+
// undefined and the try-block is a no-op.
|
|
288
|
+
const disposerKey = Symbol.dispose;
|
|
289
|
+
const disposer = disposerKey ? resp?.[disposerKey] : undefined;
|
|
290
|
+
if (typeof disposer === 'function') {
|
|
291
|
+
try {
|
|
292
|
+
disposer.call(resp);
|
|
293
|
+
responseStubDisposed();
|
|
294
|
+
}
|
|
295
|
+
catch { /* best-effort */ }
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Plain Response — there is no stub to leak, but balance the
|
|
299
|
+
// counter we incremented at packumentFetchStart() so
|
|
300
|
+
// liveResponseStubs reflects reality.
|
|
301
|
+
responseStubDisposed();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch (e) {
|
|
306
|
+
log?.(` ${name}: fetch error: ${e?.message}`);
|
|
307
|
+
// Balance counters even on the error path. responseStubDisposed
|
|
308
|
+
// was either already called in the finally above (if we got past
|
|
309
|
+
// retryableFetch) or not — the safe move is to always run
|
|
310
|
+
// packumentFetchEnd which is paired with packumentFetchStart.
|
|
311
|
+
packumentFetchEnd(0);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
packumentFetchEnd(bytesDecoded);
|
|
315
|
+
if (!data.versions) {
|
|
316
|
+
log?.(` ${name}: no versions field in packument`);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
// Resolve version
|
|
320
|
+
let version = null;
|
|
321
|
+
// Try exact match
|
|
322
|
+
if (versionRange && data.versions[versionRange]) {
|
|
323
|
+
version = versionRange;
|
|
324
|
+
}
|
|
325
|
+
// Try range resolution
|
|
326
|
+
if (!version && versionRange && versionRange !== 'latest') {
|
|
327
|
+
const allVersions = Object.keys(data.versions);
|
|
328
|
+
version = resolveVersion(allVersions, versionRange);
|
|
329
|
+
}
|
|
330
|
+
// Try dist-tags
|
|
331
|
+
if (!version) {
|
|
332
|
+
version = data['dist-tags']?.[versionRange] || data['dist-tags']?.latest || null;
|
|
333
|
+
}
|
|
334
|
+
if (!version || !data.versions[version]) {
|
|
335
|
+
log?.(` ${name}: could not resolve version for range ${versionRange}`);
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
log?.(` ${name}: resolved → ${version}`);
|
|
339
|
+
const vData = data.versions[version];
|
|
340
|
+
const pkg = versionToResolved(vData);
|
|
341
|
+
setResolverPhase('caching');
|
|
342
|
+
// Cache the resolved version (non-fatal — if caching fails, we still return the package)
|
|
343
|
+
try {
|
|
344
|
+
cache.putRegistryEntry({
|
|
345
|
+
name: pkg.name,
|
|
346
|
+
version: pkg.version,
|
|
347
|
+
tarballUrl: pkg.tarballUrl,
|
|
348
|
+
integrity: pkg.integrity,
|
|
349
|
+
depsJson: JSON.stringify(pkg.dependencies),
|
|
350
|
+
peerDepsJson: JSON.stringify(pkg.peerDependencies ?? {}),
|
|
351
|
+
exportsJson: JSON.stringify(pkg.exports ?? {}),
|
|
352
|
+
main: pkg.main,
|
|
353
|
+
moduleField: pkg.module,
|
|
354
|
+
binJson: JSON.stringify(pkg.bin),
|
|
355
|
+
fetchedAt: Date.now(),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
console.error(`[npm-resolve] cache write failed for ${name}@${version}:`, e?.message);
|
|
360
|
+
}
|
|
361
|
+
// Also cache other popular versions from the packument (non-fatal)
|
|
362
|
+
try {
|
|
363
|
+
cachePopularVersions(data, cache, pkg.version);
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
console.error(`[npm-resolve] popular version cache failed for ${name}:`, e?.message);
|
|
367
|
+
}
|
|
368
|
+
return pkg;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Cache the latest + a few other versions from a packument.
|
|
372
|
+
* Avoids re-fetching the full packument for transitive deps that reference
|
|
373
|
+
* the same package with a different range.
|
|
374
|
+
*/
|
|
375
|
+
function cachePopularVersions(data, cache, alreadyCached) {
|
|
376
|
+
const versions = Object.keys(data.versions || {});
|
|
377
|
+
// Cache the latest dist-tag version if different
|
|
378
|
+
const latest = data['dist-tags']?.latest;
|
|
379
|
+
const toCacheVersions = new Set();
|
|
380
|
+
if (latest && latest !== alreadyCached)
|
|
381
|
+
toCacheVersions.add(latest);
|
|
382
|
+
// Cache the 5 most recent versions (transitive deps often reference recent versions)
|
|
383
|
+
const sorted = versions
|
|
384
|
+
.map(v => ({ v, p: parseSemver(v) }))
|
|
385
|
+
.filter(x => x.p !== null)
|
|
386
|
+
.sort((a, b) => compareSemver(b.p, a.p));
|
|
387
|
+
for (let i = 0; i < Math.min(5, sorted.length); i++) {
|
|
388
|
+
if (sorted[i].v !== alreadyCached)
|
|
389
|
+
toCacheVersions.add(sorted[i].v);
|
|
390
|
+
}
|
|
391
|
+
for (const ver of toCacheVersions) {
|
|
392
|
+
const vData = data.versions[ver];
|
|
393
|
+
if (!vData)
|
|
394
|
+
continue;
|
|
395
|
+
try {
|
|
396
|
+
const pkg = versionToResolved(vData);
|
|
397
|
+
cache.putRegistryEntry({
|
|
398
|
+
name: pkg.name,
|
|
399
|
+
version: pkg.version,
|
|
400
|
+
tarballUrl: pkg.tarballUrl,
|
|
401
|
+
integrity: pkg.integrity,
|
|
402
|
+
depsJson: JSON.stringify(pkg.dependencies),
|
|
403
|
+
peerDepsJson: JSON.stringify(pkg.peerDependencies ?? {}),
|
|
404
|
+
exportsJson: JSON.stringify(pkg.exports ?? {}),
|
|
405
|
+
main: pkg.main,
|
|
406
|
+
moduleField: pkg.module,
|
|
407
|
+
binJson: JSON.stringify(pkg.bin),
|
|
408
|
+
fetchedAt: Date.now(),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
catch { /* skip invalid version data */ }
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/** Convert npm registry version data to ResolvedPackage. */
|
|
415
|
+
function versionToResolved(vData) {
|
|
416
|
+
const binField = vData.bin || {};
|
|
417
|
+
const bin = typeof binField === 'string'
|
|
418
|
+
? { [vData.name.split('/').pop()]: binField }
|
|
419
|
+
: binField;
|
|
420
|
+
// X.5-F R2.5: keep the full peer set (including optionals) on a
|
|
421
|
+
// hidden field so the BFS walk can include optional peers when
|
|
422
|
+
// THIS package was the user's top-level request (npm CLI default).
|
|
423
|
+
const allPeers = vData.peerDependencies && typeof vData.peerDependencies === 'object'
|
|
424
|
+
? Object.fromEntries(Object.entries(vData.peerDependencies)
|
|
425
|
+
.filter(([, r]) => typeof r === 'string'))
|
|
426
|
+
: undefined;
|
|
427
|
+
// X.5-G G1: capture optionalDependencies + os/cpu/libc constraints.
|
|
428
|
+
// The resolver consumes these to decide silent-skip per npm 4828.
|
|
429
|
+
const optionalDependencies = vData.optionalDependencies && typeof vData.optionalDependencies === 'object'
|
|
430
|
+
? Object.fromEntries(Object.entries(vData.optionalDependencies)
|
|
431
|
+
.filter(([, r]) => typeof r === 'string'))
|
|
432
|
+
: undefined;
|
|
433
|
+
const out = {
|
|
434
|
+
name: vData.name,
|
|
435
|
+
version: vData.version,
|
|
436
|
+
tarballUrl: vData.dist?.tarball || '',
|
|
437
|
+
integrity: vData.dist?.integrity || vData.dist?.shasum || '',
|
|
438
|
+
dependencies: vData.dependencies || {},
|
|
439
|
+
// X.5-F R2: surface required peer-deps (optionals filtered) so
|
|
440
|
+
// the resolveTree breadth-first walk can enqueue them. Without
|
|
441
|
+
// this, packages like @radix-ui/react-dialog (peer: react,
|
|
442
|
+
// react-dom) get installed but their `require('react')` from
|
|
443
|
+
// inside the nested dist fails at runtime.
|
|
444
|
+
peerDependencies: extractRequiredPeers(vData),
|
|
445
|
+
optionalDependencies,
|
|
446
|
+
os: Array.isArray(vData.os) ? vData.os : undefined,
|
|
447
|
+
cpu: Array.isArray(vData.cpu) ? vData.cpu : undefined,
|
|
448
|
+
libc: Array.isArray(vData.libc) ? vData.libc : undefined,
|
|
449
|
+
exports: vData.exports ?? null,
|
|
450
|
+
main: vData.main || '',
|
|
451
|
+
module: vData.module || '',
|
|
452
|
+
bin,
|
|
453
|
+
};
|
|
454
|
+
if (allPeers && Object.keys(allPeers).length > 0) {
|
|
455
|
+
out.__allPeerDependencies = allPeers;
|
|
456
|
+
}
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Extract the SUBSET of `peerDependencies` that are not marked optional
|
|
461
|
+
* via `peerDependenciesMeta.<name>.optional === true`. Returns undefined
|
|
462
|
+
* when there are no required peers, so downstream code can use a single
|
|
463
|
+
* truthiness check.
|
|
464
|
+
*
|
|
465
|
+
* X.5-F R2 — see audit/sections/X5F-plan.md §3.
|
|
466
|
+
*/
|
|
467
|
+
function extractRequiredPeers(vData) {
|
|
468
|
+
const peers = vData.peerDependencies;
|
|
469
|
+
if (!peers || typeof peers !== 'object')
|
|
470
|
+
return undefined;
|
|
471
|
+
const meta = vData.peerDependenciesMeta;
|
|
472
|
+
const out = {};
|
|
473
|
+
for (const [name, range] of Object.entries(peers)) {
|
|
474
|
+
if (typeof range !== 'string')
|
|
475
|
+
continue;
|
|
476
|
+
if (meta && meta[name] && meta[name].optional === true)
|
|
477
|
+
continue;
|
|
478
|
+
out[name] = range;
|
|
479
|
+
}
|
|
480
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
481
|
+
}
|
|
482
|
+
/** Convert a RegistryCacheEntry back to ResolvedPackage. */
|
|
483
|
+
function registryCacheToResolved(entry) {
|
|
484
|
+
// X.5-F R2: surface cached peerDependencies so a registry-cache hit
|
|
485
|
+
// doesn't lose the peer-enqueue signal. Empty object → undefined so
|
|
486
|
+
// callers (resolveTree) can use a single truthy check.
|
|
487
|
+
const peers = safeJsonParse(entry.peerDepsJson || '{}', {});
|
|
488
|
+
return {
|
|
489
|
+
name: entry.name,
|
|
490
|
+
version: entry.version,
|
|
491
|
+
tarballUrl: entry.tarballUrl,
|
|
492
|
+
integrity: entry.integrity,
|
|
493
|
+
dependencies: safeJsonParse(entry.depsJson, {}),
|
|
494
|
+
peerDependencies: Object.keys(peers).length > 0 ? peers : undefined,
|
|
495
|
+
exports: safeJsonParse(entry.exportsJson, null),
|
|
496
|
+
main: entry.main,
|
|
497
|
+
module: entry.moduleField,
|
|
498
|
+
bin: safeJsonParse(entry.binJson, {}),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
// ── Full tree resolution (pipelined) ────────────────────────────────────
|
|
502
|
+
/**
|
|
503
|
+
* Resolve the full dependency tree, breadth-first.
|
|
504
|
+
* Calls onResolved() for each package as it's resolved (pipelined — caller
|
|
505
|
+
* can start fetching tarballs immediately).
|
|
506
|
+
*
|
|
507
|
+
* W11: pass `opts.frameworkAware = true` when the project is detected as
|
|
508
|
+
* one of {next, astro, nuxt, remix, sveltekit, vite, wrangler} so that
|
|
509
|
+
* `vite` (and any future FRAMEWORK_REQUIRED_PACKAGES additions) actually
|
|
510
|
+
* land in node_modules. See audit/sections/W11-plan.md §3.0.
|
|
511
|
+
*/
|
|
512
|
+
export async function resolveTree(specs, cache, onResolved, onProgress, fetchFn, opts) {
|
|
513
|
+
const frameworkAware = !!(opts && opts.frameworkAware);
|
|
514
|
+
const resolved = new Map();
|
|
515
|
+
const seen = new Set();
|
|
516
|
+
// X.5-F R1: names the user typed at the top level (or that are
|
|
517
|
+
// required peer-deps of an installed package — see R2 below) bypass
|
|
518
|
+
// SKIP_PACKAGES. Transitive `dependencies` walks do NOT add to this
|
|
519
|
+
// set, preserving silent-skip for build-tool noise. See audit/sections/
|
|
520
|
+
// X5F-plan.md §6.1.
|
|
521
|
+
const topLevelNames = new Set(Object.keys(specs));
|
|
522
|
+
// X.5-G G1: names that came from a parent's `optionalDependencies`.
|
|
523
|
+
// These are best-effort per npm 4828 — fetch failures and platform-
|
|
524
|
+
// native-binding detection silent-skip them rather than propagating
|
|
525
|
+
// as install errors. See audit/sections/X5G-plan.md §2.
|
|
526
|
+
const optionalNames = new Set();
|
|
527
|
+
// X.5-drizzle: names enqueued by the X.5-J top-level optional-peer
|
|
528
|
+
// path (R2.5) and ALL their transitive descendants. The user did not
|
|
529
|
+
// explicitly ask for them — npm CLI's --include=peer pulls them as a
|
|
530
|
+
// best-effort convenience. When a `transitive: 'fail'` REJECT_INSTALL
|
|
531
|
+
// fires for a name in this set, we silent-skip the offending name +
|
|
532
|
+
// log a notice, instead of throwing and killing the parent install.
|
|
533
|
+
// Mirrors src/npm-resolve-facet.ts. See audit/sections/X5-drizzle-plan.md.
|
|
534
|
+
const bestEffortNames = new Set();
|
|
535
|
+
const queue = Object.entries(specs);
|
|
536
|
+
const limit = pLimit(RESOLVE_CONCURRENCY);
|
|
537
|
+
// F-2 profiling support: emit per-layer width when
|
|
538
|
+
// NIMBUS_DIAG_INSTALL_PIPELINE=1 (same flag used by VFS pipeline diag).
|
|
539
|
+
// Used by audit/probes/f2-resolver-fanout/ to measure the BFS-frontier
|
|
540
|
+
// width distribution against the top-30 cohort. Zero cost in prod
|
|
541
|
+
// (env not set). See audit/sections/F2-RESOLVER-FANOUT-plan.md.
|
|
542
|
+
const __f2Diag = (globalThis.process?.env?.NIMBUS_DIAG_INSTALL_PIPELINE === '1');
|
|
543
|
+
let __f2LayerN = 0;
|
|
544
|
+
while (queue.length > 0) {
|
|
545
|
+
// Drain queue in bounded batches. Math.min ensures we process at most
|
|
546
|
+
// RESOLVE_CONCURRENCY packages per iteration; transitive deps enqueued
|
|
547
|
+
// by completed resolves are picked up in the next iteration. The
|
|
548
|
+
// previous Math.max drained the ENTIRE queue in one go, creating 100+
|
|
549
|
+
// Promises simultaneously — each with its own AbortController / setTimeout
|
|
550
|
+
// / stream pipeline through the RPC proxy, overwhelming the workerd
|
|
551
|
+
// loopback fabric with too many concurrent in-flight streams.
|
|
552
|
+
const batch = queue.splice(0, Math.min(queue.length, RESOLVE_CONCURRENCY));
|
|
553
|
+
if (__f2Diag) {
|
|
554
|
+
// queueRemain is the post-splice frontier still pending.
|
|
555
|
+
onProgress?.(`[f2-layer-width] N=${__f2LayerN} width=${batch.length} queueRemain=${queue.length} resolved=${resolved.size} seen=${seen.size}`);
|
|
556
|
+
__f2LayerN++;
|
|
557
|
+
}
|
|
558
|
+
const results = await Promise.all(batch.map(([name, range]) => limit(async () => {
|
|
559
|
+
if (seen.has(name))
|
|
560
|
+
return null;
|
|
561
|
+
seen.add(name);
|
|
562
|
+
// X.5-F R1: top-level user requests + required peer-deps bypass
|
|
563
|
+
// the SKIP_PACKAGES filter. Transitive deps do not.
|
|
564
|
+
if (!topLevelNames.has(name) &&
|
|
565
|
+
shouldSkipPackageWithFramework(name, frameworkAware)) {
|
|
566
|
+
onProgress?.(` skipping ${name} (build-only)`);
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
// W6: transitive registry — swap rewrites name in flight; reject
|
|
570
|
+
// with transitive='fail' throws (matches top-level fail policy);
|
|
571
|
+
// reject with transitive='warn' logs [skip] and drops.
|
|
572
|
+
// W6.5: each decision also emits a RegistryEvent for telemetry.
|
|
573
|
+
let resolveName = name;
|
|
574
|
+
const swap = lookupSwap(name);
|
|
575
|
+
if (swap) {
|
|
576
|
+
onProgress?.(formatSwapNotice(swap));
|
|
577
|
+
emitRegistryEvent({ type: 'swap', from: swap.from, to: swap.to, ctx: 'transitive' });
|
|
578
|
+
resolveName = swap.to;
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
const warnSkip = shouldWarnSkipTransitive(name);
|
|
582
|
+
if (warnSkip) {
|
|
583
|
+
onProgress?.(formatTransitiveSkip(warnSkip));
|
|
584
|
+
emitRegistryEvent({ type: 'transitive-skip', from: warnSkip.from, reason: warnSkip.reason });
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const rejectFail = lookupReject(name);
|
|
588
|
+
if (rejectFail && rejectFail.transitive === 'fail') {
|
|
589
|
+
emitRegistryEvent({
|
|
590
|
+
type: 'reject',
|
|
591
|
+
from: rejectFail.from,
|
|
592
|
+
reason: rejectFail.reason,
|
|
593
|
+
suggest: rejectFail.suggest,
|
|
594
|
+
ctx: 'transitive',
|
|
595
|
+
});
|
|
596
|
+
throw new RegistryRejectError([rejectFail]);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
onProgress?.(` resolving ${resolveName}...`);
|
|
600
|
+
const isOptional = optionalNames.has(name);
|
|
601
|
+
try {
|
|
602
|
+
const pkg = await resolvePackage(resolveName, range, cache, fetchFn, onProgress);
|
|
603
|
+
// X.5-G G1: silent-skip platform-native bindings sourced from
|
|
604
|
+
// optionalDependencies. Per npm 4828: best-effort installs MUST
|
|
605
|
+
// NOT fail the parent. We go further than npm's host-mismatch
|
|
606
|
+
// skip: even a host-matching .node binding is unloadable in
|
|
607
|
+
// workerd, so all native bindings are skipped regardless of
|
|
608
|
+
// host. The parent package's runtime fallback (e.g. rollup's
|
|
609
|
+
// native.js → @rollup/wasm-node) handles absence.
|
|
610
|
+
if (pkg && isOptional && isOptionalNativeBinding({
|
|
611
|
+
name: pkg.name,
|
|
612
|
+
os: pkg.os, cpu: pkg.cpu, libc: pkg.libc,
|
|
613
|
+
main: pkg.main,
|
|
614
|
+
})) {
|
|
615
|
+
const reason = `optional native binding (os=${pkg.os ?? '*'}, cpu=${pkg.cpu ?? '*'}, libc=${pkg.libc ?? '*'}, main=${pkg.main || '?'})`;
|
|
616
|
+
onProgress?.(`[npm] [skip] ${name} — ${reason}`);
|
|
617
|
+
emitRegistryEvent({
|
|
618
|
+
type: 'transitive-skip',
|
|
619
|
+
from: name,
|
|
620
|
+
reason,
|
|
621
|
+
});
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
return pkg;
|
|
625
|
+
}
|
|
626
|
+
catch (e) {
|
|
627
|
+
// X.5-G G1: errors on optional-dep resolution silent-skip
|
|
628
|
+
// rather than propagating. classifyInstallError preserves
|
|
629
|
+
// RegistryRejectError propagation (registry-reject errors
|
|
630
|
+
// ALWAYS bubble — a transitive=fail reject still throws).
|
|
631
|
+
const cls = classifyInstallError(e, { isOptional });
|
|
632
|
+
if (cls === 'optional-dep-skip') {
|
|
633
|
+
onProgress?.(`[npm] [skip] ${name} (optional dep — ${e?.message ?? 'fetch failed'})`);
|
|
634
|
+
emitRegistryEvent({
|
|
635
|
+
type: 'transitive-skip',
|
|
636
|
+
from: name,
|
|
637
|
+
reason: `optional dep fetch failed: ${e?.message ?? 'unknown'}`,
|
|
638
|
+
});
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
if (cls === 'registry-reject') {
|
|
642
|
+
// X.5-drizzle: registry-reject inside a best-effort
|
|
643
|
+
// optional-peer subtree (X.5-J R2.5 enqueue) silent-skips
|
|
644
|
+
// instead of bubbling up to kill the parent install. The
|
|
645
|
+
// user did not explicitly ask for the optional-peer-rooted
|
|
646
|
+
// subtree; mirror npm's --omit=optional behaviour for its
|
|
647
|
+
// descendants. See VERIFY-9D4B61D §6 and the mirror in
|
|
648
|
+
// src/npm-resolve-facet.ts. Canonical chain: drizzle-orm →
|
|
649
|
+
// expo-sqlite (optpeer) → expo (peer) → @expo/metro-config
|
|
650
|
+
// (dep) → lightningcss (dep, transitive='fail').
|
|
651
|
+
if (bestEffortNames.has(name)) {
|
|
652
|
+
onProgress?.(`[npm] [skip] ${name} — inside best-effort optional-peer subtree (X.5-drizzle): ${e?.message ?? 'reject'}`);
|
|
653
|
+
emitRegistryEvent({
|
|
654
|
+
type: 'transitive-skip',
|
|
655
|
+
from: name,
|
|
656
|
+
reason: `inside best-effort optional-peer subtree (X.5-drizzle): ${e?.message ?? 'reject'}`,
|
|
657
|
+
});
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
// Re-throw — registry rejects are loud at any depth.
|
|
661
|
+
throw e;
|
|
662
|
+
}
|
|
663
|
+
onProgress?.(` ${resolveName}: UNHANDLED ERROR: ${e?.message}`);
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
})));
|
|
667
|
+
for (const pkg of results) {
|
|
668
|
+
if (!pkg || resolved.has(pkg.name))
|
|
669
|
+
continue;
|
|
670
|
+
resolved.set(pkg.name, pkg);
|
|
671
|
+
onResolved?.(pkg);
|
|
672
|
+
// X.5-drizzle: when this pkg was best-effort (a child of an
|
|
673
|
+
// X.5-J optional-peer subtree), its newly-enqueued descendants
|
|
674
|
+
// inherit the best-effort flag so a deep `transitive: 'fail'`
|
|
675
|
+
// REJECT_INSTALL silent-skips instead of killing the parent.
|
|
676
|
+
const inheritBestEffort = bestEffortNames.has(pkg.name);
|
|
677
|
+
// Enqueue transitive deps
|
|
678
|
+
for (const [depName, depRange] of Object.entries(pkg.dependencies)) {
|
|
679
|
+
if (!resolved.has(depName) && !seen.has(depName)) {
|
|
680
|
+
if (inheritBestEffort)
|
|
681
|
+
bestEffortNames.add(depName);
|
|
682
|
+
queue.push([depName, depRange]);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// X.5-G G1: enqueue transitive optionalDependencies, tagged so
|
|
686
|
+
// resolveOne skips platform-native bindings silently and any
|
|
687
|
+
// fetch failure becomes optional-dep-skip rather than fail. See
|
|
688
|
+
// audit/sections/X5G-plan.md §2 for the npm 4828 rationale.
|
|
689
|
+
if (pkg.optionalDependencies) {
|
|
690
|
+
for (const [depName, depRange] of Object.entries(pkg.optionalDependencies)) {
|
|
691
|
+
if (!resolved.has(depName) && !seen.has(depName)) {
|
|
692
|
+
optionalNames.add(depName);
|
|
693
|
+
if (inheritBestEffort)
|
|
694
|
+
bestEffortNames.add(depName);
|
|
695
|
+
queue.push([depName, depRange]);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// X.5-F R2: enqueue REQUIRED peer-deps (optionals already
|
|
700
|
+
// filtered in versionToResolved). Mark them as topLevelNames so
|
|
701
|
+
// they bypass SKIP_PACKAGES — ts-jest needs typescript even
|
|
702
|
+
// though typescript is in SKIP_PACKAGES. See audit/sections/
|
|
703
|
+
// X5F-plan.md §3 for the bug evidence and §6.2 for the fix.
|
|
704
|
+
if (pkg.peerDependencies) {
|
|
705
|
+
for (const [peerName, peerRange] of Object.entries(pkg.peerDependencies)) {
|
|
706
|
+
if (resolved.has(peerName) || seen.has(peerName))
|
|
707
|
+
continue;
|
|
708
|
+
topLevelNames.add(peerName);
|
|
709
|
+
if (inheritBestEffort)
|
|
710
|
+
bestEffortNames.add(peerName);
|
|
711
|
+
queue.push([peerName, peerRange]);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
// X.5-F R2.5: when the user typed THIS package at top level,
|
|
715
|
+
// also enqueue optional peer-deps. Mirrors npm CLI's
|
|
716
|
+
// `--include=peer` default. Without this, framer-motion (whose
|
|
717
|
+
// peers are ALL marked optional including react) installs but
|
|
718
|
+
// its compiled CJS still imports react/jsx-runtime and fails.
|
|
719
|
+
// For TRANSITIVE packages we keep optionals filtered out — only
|
|
720
|
+
// top-level requests get this generous treatment.
|
|
721
|
+
//
|
|
722
|
+
// X.5-J: optional peers whose target is in REJECT_INSTALL get
|
|
723
|
+
// SOFT-SKIPPED at enqueue time. Without this carve-out, R2.5's
|
|
724
|
+
// generous include cascades into the W6 reject-throw and kills
|
|
725
|
+
// the parent install. Two real regressions surfaced this:
|
|
726
|
+
// - drizzle-orm declares optional peer 'sql.js' (W6.5 loader gap)
|
|
727
|
+
// - ts-node declares optional peer '@swc/core' (native Rust)
|
|
728
|
+
// Both packages have non-rejected primary code paths (drizzle
|
|
729
|
+
// works against d1/libsql/postgres/mysql; ts-node default mode
|
|
730
|
+
// uses TypeScript transformer not swc), so a soft-skip recovers
|
|
731
|
+
// the previously-working install. REQUIRED peers in REJECT_INSTALL
|
|
732
|
+
// still hard-fail via the R2 path above (peerDependencies set
|
|
733
|
+
// excludes optionals); transitive REQUIRED deps in REJECT_INSTALL
|
|
734
|
+
// also still hard-fail via the dep walk's resolveOne reject path.
|
|
735
|
+
// See audit/sections/X5J-plan.md §3 for the full rationale.
|
|
736
|
+
if (topLevelNames.has(pkg.name)) {
|
|
737
|
+
const allPeers = pkg.__allPeerDependencies;
|
|
738
|
+
if (allPeers) {
|
|
739
|
+
for (const [peerName, peerRange] of Object.entries(allPeers)) {
|
|
740
|
+
if (resolved.has(peerName) || seen.has(peerName))
|
|
741
|
+
continue;
|
|
742
|
+
// X.5-J: filter optional peers through REJECT_INSTALL.
|
|
743
|
+
const peerReject = lookupReject(peerName);
|
|
744
|
+
if (peerReject) {
|
|
745
|
+
const reason = `optional peer in REJECT_INSTALL: ${peerName} — ${peerReject.reason}`;
|
|
746
|
+
onProgress?.(`[npm] [skip] ${peerName} (${reason})`);
|
|
747
|
+
emitRegistryEvent({
|
|
748
|
+
type: 'transitive-skip',
|
|
749
|
+
from: peerName,
|
|
750
|
+
reason,
|
|
751
|
+
});
|
|
752
|
+
continue; // do NOT seen.add — let a later required-dep walk
|
|
753
|
+
// hit it via its own resolveOne path if needed.
|
|
754
|
+
}
|
|
755
|
+
topLevelNames.add(peerName);
|
|
756
|
+
// X.5-drizzle: tag X.5-J optional-peer enqueues as
|
|
757
|
+
// best-effort so a deep `transitive: 'fail'` REJECT
|
|
758
|
+
// (e.g., expo-sqlite → expo → @expo/metro-config →
|
|
759
|
+
// lightningcss) silent-skips the offending sub-tree
|
|
760
|
+
// instead of killing the parent install.
|
|
761
|
+
bestEffortNames.add(peerName);
|
|
762
|
+
queue.push([peerName, peerRange]);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
// Brief idle window between waves — the diag layer surfaces this so
|
|
768
|
+
// a probe scheduled here gets a clean readout rather than a snapshot
|
|
769
|
+
// mid-parse.
|
|
770
|
+
setResolverPhase('idle');
|
|
771
|
+
}
|
|
772
|
+
setResolverPhase('done');
|
|
773
|
+
return resolved;
|
|
774
|
+
}
|
|
775
|
+
// ── Hoisting algorithm ──────────────────────────────────────────────────
|
|
776
|
+
/**
|
|
777
|
+
* Compute npm-style hoisting: maximize packages at root node_modules/.
|
|
778
|
+
*
|
|
779
|
+
* Algorithm:
|
|
780
|
+
* 1. Collect all unique name@version pairs from the resolved tree.
|
|
781
|
+
* 2. For each package name, pick the most commonly depended-upon version
|
|
782
|
+
* for root hoisting.
|
|
783
|
+
* 3. Any dep that requires a different version of an already-hoisted name
|
|
784
|
+
* goes into nested: node_modules/<parent>/node_modules/<child>
|
|
785
|
+
*
|
|
786
|
+
* In practice, for well-maintained projects (e.g., Radix UI ecosystem),
|
|
787
|
+
* most packages agree on compatible versions and everything hoists to root.
|
|
788
|
+
*/
|
|
789
|
+
export function computeHoistPlan(resolved) {
|
|
790
|
+
const root = new Map();
|
|
791
|
+
const nested = new Map();
|
|
792
|
+
// Phase 1: Count how many packages depend on each name@version
|
|
793
|
+
// (used to choose the "best" version for root hoisting)
|
|
794
|
+
const versionCounts = new Map();
|
|
795
|
+
for (const [, pkg] of resolved) {
|
|
796
|
+
for (const [depName, depRange] of Object.entries(pkg.dependencies)) {
|
|
797
|
+
const depPkg = resolved.get(depName);
|
|
798
|
+
if (!depPkg)
|
|
799
|
+
continue;
|
|
800
|
+
if (!versionCounts.has(depName))
|
|
801
|
+
versionCounts.set(depName, new Map());
|
|
802
|
+
const counts = versionCounts.get(depName);
|
|
803
|
+
counts.set(depPkg.version, (counts.get(depPkg.version) || 0) + 1);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
// Phase 2: For each resolved package, determine if it can be hoisted to root.
|
|
807
|
+
// Since we resolve a flat tree (one version per name), everything goes to root
|
|
808
|
+
// unless there's a conflict. With our current flattenDeps (first-version-wins),
|
|
809
|
+
// there are no conflicts — every name has exactly one version.
|
|
810
|
+
for (const [name, pkg] of resolved) {
|
|
811
|
+
root.set(name, pkg);
|
|
812
|
+
}
|
|
813
|
+
// Phase 3: Future — handle cases where multiple versions of the same name
|
|
814
|
+
// are needed (peer dependency conflicts). For now, our resolver picks one
|
|
815
|
+
// version per name (same as npm's flat tree), so nested is always empty.
|
|
816
|
+
return { root, nested };
|
|
817
|
+
}
|
|
818
|
+
// ── Skip list ───────────────────────────────────────────────────────────
|
|
819
|
+
// W6: `esbuild` and `fsevents` were removed from SKIP_PACKAGES so the
|
|
820
|
+
// W6 swap/reject registry can own them. `esbuild` is in WASM_SWAPS
|
|
821
|
+
// (→ esbuild-wasm); `fsevents` is in REJECT_INSTALL (transitive='warn').
|
|
822
|
+
// node-gyp / node-pre-gyp remain here for transitive silence (they
|
|
823
|
+
// also appear in REJECT_INSTALL with transitive='warn' so a top-level
|
|
824
|
+
// `npm install node-gyp` reaches the registry first and emits a clear
|
|
825
|
+
// rejection — see plan §10 risk row).
|
|
826
|
+
//
|
|
827
|
+
// W11: `vite` was previously unconditionally skipped because the
|
|
828
|
+
// supervisor bundles real-vite. But Astro/Nuxt/Remix/SvelteKit `import`
|
|
829
|
+
// from the user's installed `vite` to call createServer() — so when a
|
|
830
|
+
// framework is detected, `vite` must actually land in node_modules.
|
|
831
|
+
// `shouldSkipPackageWithFramework({ frameworkAware: true })` exempts
|
|
832
|
+
// it. See audit/sections/W11-plan.md §3.0.
|
|
833
|
+
//
|
|
834
|
+
// X.5-G: `rollup` removed from SKIP_PACKAGES because it's now in
|
|
835
|
+
// WASM_SWAPS (rollup → @rollup/wasm-node). SKIP would mask the swap at
|
|
836
|
+
// transitive depth: vite → rollup transitive enqueue would silent-skip
|
|
837
|
+
// rollup before the swap fires. Removing the SKIP entry lets the
|
|
838
|
+
// transitive swap path (npm-resolver.ts:645) consult the registry.
|
|
839
|
+
const SKIP_PACKAGES = new Set([
|
|
840
|
+
// Build tools (X.5-G: rollup migrated to WASM_SWAPS)
|
|
841
|
+
'typescript', 'vite', 'webpack', 'parcel',
|
|
842
|
+
'postcss', 'autoprefixer', 'tailwindcss', 'cssnano',
|
|
843
|
+
'prettier', 'eslint', 'stylelint',
|
|
844
|
+
// Native modules / build-time (chokidar = real-vite intercepts;
|
|
845
|
+
// node-gyp/pre-gyp = build-time only, never run in Workers)
|
|
846
|
+
'chokidar', 'node-gyp', 'node-pre-gyp',
|
|
847
|
+
// Cloudflare dev tools
|
|
848
|
+
'@cloudflare/vite-plugin', '@cloudflare/workers-types', 'wrangler',
|
|
849
|
+
// Other build-only
|
|
850
|
+
'husky', 'lint-staged', 'commitlint',
|
|
851
|
+
]);
|
|
852
|
+
// W11: when a framework is detected at install time, packages in this
|
|
853
|
+
// set are removed from the skip list. Their dev binaries `import` from
|
|
854
|
+
// the project's node_modules and would crash with "Cannot find module"
|
|
855
|
+
// otherwise.
|
|
856
|
+
const FRAMEWORK_REQUIRED_PACKAGES = new Set([
|
|
857
|
+
'vite',
|
|
858
|
+
]);
|
|
859
|
+
const SKIP_PREFIXES = [
|
|
860
|
+
'@types/',
|
|
861
|
+
'@eslint/',
|
|
862
|
+
'@typescript-eslint/',
|
|
863
|
+
'eslint-plugin-',
|
|
864
|
+
'eslint-config-',
|
|
865
|
+
// Note: '@vitejs/' used to be skipped because the Cirrus shim
|
|
866
|
+
// ignored plugins anyway. With real-vite mode (Phase 1-4), those
|
|
867
|
+
// plugins are required — keep them installable and let whichever
|
|
868
|
+
// dev-server mode is active decide how to use them.
|
|
869
|
+
];
|
|
870
|
+
/** Check if a package should be skipped (build-only, native, types). */
|
|
871
|
+
export function shouldSkipPackage(name) {
|
|
872
|
+
if (SKIP_PACKAGES.has(name))
|
|
873
|
+
return true;
|
|
874
|
+
return SKIP_PREFIXES.some(p => name.startsWith(p));
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* W11: framework-aware skip variant. When `frameworkAware` is true, the
|
|
878
|
+
* resolver lets through packages in FRAMEWORK_REQUIRED_PACKAGES (currently
|
|
879
|
+
* just `vite`) so framework dev binaries can import them from node_modules.
|
|
880
|
+
*
|
|
881
|
+
* Callers detect framework presence via `framework-detect.ts` BEFORE
|
|
882
|
+
* starting resolution and thread the flag through `resolveTree`.
|
|
883
|
+
*
|
|
884
|
+
* See audit/sections/W11-plan.md §3.0.
|
|
885
|
+
*/
|
|
886
|
+
export function shouldSkipPackageWithFramework(name, frameworkAware) {
|
|
887
|
+
if (frameworkAware && FRAMEWORK_REQUIRED_PACKAGES.has(name))
|
|
888
|
+
return false;
|
|
889
|
+
return shouldSkipPackage(name);
|
|
890
|
+
}
|
|
891
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
892
|
+
function safeJsonParse(json, fallback) {
|
|
893
|
+
try {
|
|
894
|
+
return JSON.parse(json);
|
|
895
|
+
}
|
|
896
|
+
catch {
|
|
897
|
+
return fallback;
|
|
898
|
+
}
|
|
899
|
+
}
|