@seanhogg/builderforce-webcontainers-core 2026.10.1 → 2026.10.3
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/dist/build.d.ts +42 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +65 -0
- package/dist/build.js.map +1 -0
- package/dist/buildHost.d.ts +31 -0
- package/dist/buildHost.d.ts.map +1 -0
- package/dist/buildHost.js +125 -0
- package/dist/buildHost.js.map +1 -0
- package/dist/buildHtml.d.ts +28 -0
- package/dist/buildHtml.d.ts.map +1 -0
- package/dist/buildHtml.js +74 -0
- package/dist/buildHtml.js.map +1 -0
- package/dist/bundler.d.ts +129 -0
- package/dist/bundler.d.ts.map +1 -0
- package/dist/bundler.js +79 -0
- package/dist/bundler.js.map +1 -0
- package/dist/check/acquire.d.ts +46 -0
- package/dist/check/acquire.d.ts.map +1 -0
- package/dist/check/acquire.js +179 -0
- package/dist/check/acquire.js.map +1 -0
- package/dist/check/compilerHost.d.ts +22 -0
- package/dist/check/compilerHost.d.ts.map +1 -0
- package/dist/check/compilerHost.js +128 -0
- package/dist/check/compilerHost.js.map +1 -0
- package/dist/check/diagnostics.d.ts +23 -0
- package/dist/check/diagnostics.d.ts.map +1 -0
- package/dist/check/diagnostics.js +28 -0
- package/dist/check/diagnostics.js.map +1 -0
- package/dist/check/index.d.ts +19 -0
- package/dist/check/index.d.ts.map +1 -0
- package/dist/check/index.js +13 -0
- package/dist/check/index.js.map +1 -0
- package/dist/check/projects.d.ts +26 -0
- package/dist/check/projects.d.ts.map +1 -0
- package/dist/check/projects.js +124 -0
- package/dist/check/projects.js.map +1 -0
- package/dist/check/shims.d.ts +29 -0
- package/dist/check/shims.d.ts.map +1 -0
- package/dist/check/shims.js +76 -0
- package/dist/check/shims.js.map +1 -0
- package/dist/check/typeStore.d.ts +57 -0
- package/dist/check/typeStore.d.ts.map +1 -0
- package/dist/check/typeStore.js +89 -0
- package/dist/check/typeStore.js.map +1 -0
- package/dist/check/typecheck.d.ts +29 -0
- package/dist/check/typecheck.d.ts.map +1 -0
- package/dist/check/typecheck.js +91 -0
- package/dist/check/typecheck.js.map +1 -0
- package/dist/compileComponent.d.ts +8 -0
- package/dist/compileComponent.d.ts.map +1 -0
- package/dist/compileComponent.js +22 -0
- package/dist/compileComponent.js.map +1 -0
- package/dist/compileScript.d.ts +19 -4
- package/dist/compileScript.d.ts.map +1 -1
- package/dist/compileScript.js +37 -12
- package/dist/compileScript.js.map +1 -1
- package/dist/components.d.ts +41 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +14 -0
- package/dist/components.js.map +1 -0
- package/dist/detectProject.d.ts +3 -0
- package/dist/detectProject.d.ts.map +1 -1
- package/dist/detectProject.js +16 -6
- package/dist/detectProject.js.map +1 -1
- package/dist/devServer.d.ts +5 -2
- package/dist/devServer.d.ts.map +1 -1
- package/dist/devServer.js +16 -8
- package/dist/devServer.js.map +1 -1
- package/dist/hash.d.ts +2 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +22 -0
- package/dist/hash.js.map +1 -0
- package/dist/html.d.ts +8 -0
- package/dist/html.d.ts.map +1 -1
- package/dist/html.js +17 -1
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +25 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -4
- package/dist/index.js.map +1 -1
- package/dist/installer/bins.d.ts +24 -0
- package/dist/installer/bins.d.ts.map +1 -0
- package/dist/installer/bins.js +56 -0
- package/dist/installer/bins.js.map +1 -0
- package/dist/installer/index.d.ts +14 -0
- package/dist/installer/index.d.ts.map +1 -0
- package/dist/installer/index.js +9 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/installer/install.d.ts +46 -0
- package/dist/installer/install.d.ts.map +1 -0
- package/dist/installer/install.js +245 -0
- package/dist/installer/install.js.map +1 -0
- package/dist/installer/lockfile.d.ts +49 -0
- package/dist/installer/lockfile.d.ts.map +1 -0
- package/dist/installer/lockfile.js +110 -0
- package/dist/installer/lockfile.js.map +1 -0
- package/dist/installer/registry.d.ts +84 -0
- package/dist/installer/registry.d.ts.map +1 -0
- package/dist/installer/registry.js +130 -0
- package/dist/installer/registry.js.map +1 -0
- package/dist/installer/semver.d.ts +38 -0
- package/dist/installer/semver.d.ts.map +1 -0
- package/dist/installer/semver.js +187 -0
- package/dist/installer/semver.js.map +1 -0
- package/dist/installer/spec.d.ts +30 -0
- package/dist/installer/spec.d.ts.map +1 -0
- package/dist/installer/spec.js +30 -0
- package/dist/installer/spec.js.map +1 -0
- package/dist/installer/tar.d.ts +17 -0
- package/dist/installer/tar.d.ts.map +1 -0
- package/dist/installer/tar.js +150 -0
- package/dist/installer/tar.js.map +1 -0
- package/dist/installer/tree.d.ts +48 -0
- package/dist/installer/tree.d.ts.map +1 -0
- package/dist/installer/tree.js +179 -0
- package/dist/installer/tree.js.map +1 -0
- package/dist/node/assert.d.ts +15 -0
- package/dist/node/assert.d.ts.map +1 -0
- package/dist/node/assert.js +137 -0
- package/dist/node/assert.js.map +1 -0
- package/dist/node/buffer.d.ts +84 -0
- package/dist/node/buffer.d.ts.map +1 -0
- package/dist/node/buffer.js +304 -0
- package/dist/node/buffer.js.map +1 -0
- package/dist/node/builtins.d.ts +25 -0
- package/dist/node/builtins.d.ts.map +1 -0
- package/dist/node/builtins.js +101 -0
- package/dist/node/builtins.js.map +1 -0
- package/dist/node/callable.d.ts +13 -0
- package/dist/node/callable.d.ts.map +1 -0
- package/dist/node/callable.js +26 -0
- package/dist/node/callable.js.map +1 -0
- package/dist/node/childProcess.d.ts +13 -0
- package/dist/node/childProcess.d.ts.map +1 -0
- package/dist/node/childProcess.js +143 -0
- package/dist/node/childProcess.js.map +1 -0
- package/dist/node/crypto.d.ts +3 -0
- package/dist/node/crypto.d.ts.map +1 -0
- package/dist/node/crypto.js +180 -0
- package/dist/node/crypto.js.map +1 -0
- package/dist/node/esm.d.ts +9 -0
- package/dist/node/esm.d.ts.map +1 -0
- package/dist/node/esm.js +202 -0
- package/dist/node/esm.js.map +1 -0
- package/dist/node/events.d.ts +40 -0
- package/dist/node/events.d.ts.map +1 -0
- package/dist/node/events.js +160 -0
- package/dist/node/events.js.map +1 -0
- package/dist/node/fs/index.d.ts +7 -0
- package/dist/node/fs/index.d.ts.map +1 -0
- package/dist/node/fs/index.js +270 -0
- package/dist/node/fs/index.js.map +1 -0
- package/dist/node/fs/stats.d.ts +93 -0
- package/dist/node/fs/stats.d.ts.map +1 -0
- package/dist/node/fs/stats.js +104 -0
- package/dist/node/fs/stats.js.map +1 -0
- package/dist/node/fs/sync.d.ts +98 -0
- package/dist/node/fs/sync.d.ts.map +1 -0
- package/dist/node/fs/sync.js +348 -0
- package/dist/node/fs/sync.js.map +1 -0
- package/dist/node/hashes.d.ts +15 -0
- package/dist/node/hashes.d.ts.map +1 -0
- package/dist/node/hashes.js +195 -0
- package/dist/node/hashes.js.map +1 -0
- package/dist/node/http/client.d.ts +35 -0
- package/dist/node/http/client.d.ts.map +1 -0
- package/dist/node/http/client.js +161 -0
- package/dist/node/http/client.js.map +1 -0
- package/dist/node/http/index.d.ts +5 -0
- package/dist/node/http/index.d.ts.map +1 -0
- package/dist/node/http/index.js +125 -0
- package/dist/node/http/index.js.map +1 -0
- package/dist/node/http/server.d.ts +135 -0
- package/dist/node/http/server.d.ts.map +1 -0
- package/dist/node/http/server.js +308 -0
- package/dist/node/http/server.js.map +1 -0
- package/dist/node/http/status.d.ts +3 -0
- package/dist/node/http/status.d.ts.map +1 -0
- package/dist/node/http/status.js +13 -0
- package/dist/node/http/status.js.map +1 -0
- package/dist/node/inspect.d.ts +22 -0
- package/dist/node/inspect.d.ts.map +1 -0
- package/dist/node/inspect.js +172 -0
- package/dist/node/inspect.js.map +1 -0
- package/dist/node/loader.d.ts +91 -0
- package/dist/node/loader.d.ts.map +1 -0
- package/dist/node/loader.js +265 -0
- package/dist/node/loader.js.map +1 -0
- package/dist/node/loop.d.ts +71 -0
- package/dist/node/loop.d.ts.map +1 -0
- package/dist/node/loop.js +189 -0
- package/dist/node/loop.js.map +1 -0
- package/dist/node/misc.d.ts +10 -0
- package/dist/node/misc.d.ts.map +1 -0
- package/dist/node/misc.js +256 -0
- package/dist/node/misc.js.map +1 -0
- package/dist/node/path.d.ts +14 -0
- package/dist/node/path.d.ts.map +1 -0
- package/dist/node/path.js +128 -0
- package/dist/node/path.js.map +1 -0
- package/dist/node/process.d.ts +31 -0
- package/dist/node/process.d.ts.map +1 -0
- package/dist/node/process.js +309 -0
- package/dist/node/process.js.map +1 -0
- package/dist/node/program.d.ts +19 -0
- package/dist/node/program.d.ts.map +1 -0
- package/dist/node/program.js +165 -0
- package/dist/node/program.js.map +1 -0
- package/dist/node/readline.d.ts +2 -0
- package/dist/node/readline.d.ts.map +1 -0
- package/dist/node/readline.js +155 -0
- package/dist/node/readline.js.map +1 -0
- package/dist/node/resolve.d.ts +45 -0
- package/dist/node/resolve.d.ts.map +1 -0
- package/dist/node/resolve.js +206 -0
- package/dist/node/resolve.js.map +1 -0
- package/dist/node/runtime.d.ts +40 -0
- package/dist/node/runtime.d.ts.map +1 -0
- package/dist/node/runtime.js +149 -0
- package/dist/node/runtime.js.map +1 -0
- package/dist/node/stream/index.d.ts +52 -0
- package/dist/node/stream/index.d.ts.map +1 -0
- package/dist/node/stream/index.js +184 -0
- package/dist/node/stream/index.js.map +1 -0
- package/dist/node/stream/readable.d.ts +83 -0
- package/dist/node/stream/readable.d.ts.map +1 -0
- package/dist/node/stream/readable.js +325 -0
- package/dist/node/stream/readable.js.map +1 -0
- package/dist/node/stream/writable.d.ts +80 -0
- package/dist/node/stream/writable.d.ts.map +1 -0
- package/dist/node/stream/writable.js +224 -0
- package/dist/node/stream/writable.js.map +1 -0
- package/dist/node/urlqs.d.ts +22 -0
- package/dist/node/urlqs.d.ts.map +1 -0
- package/dist/node/urlqs.js +165 -0
- package/dist/node/urlqs.js.map +1 -0
- package/dist/node/util.d.ts +37 -0
- package/dist/node/util.d.ts.map +1 -0
- package/dist/node/util.js +160 -0
- package/dist/node/util.js.map +1 -0
- package/dist/node/zlib.d.ts +2 -0
- package/dist/node/zlib.d.ts.map +1 -0
- package/dist/node/zlib.js +133 -0
- package/dist/node/zlib.js.map +1 -0
- package/dist/packageCdn.d.ts +12 -0
- package/dist/packageCdn.d.ts.map +1 -1
- package/dist/packageCdn.js +3 -1
- package/dist/packageCdn.js.map +1 -1
- package/dist/projectConfig.d.ts +6 -2
- package/dist/projectConfig.d.ts.map +1 -1
- package/dist/projectConfig.js +32 -8
- package/dist/projectConfig.js.map +1 -1
- package/dist/shell/builtins.d.ts +23 -0
- package/dist/shell/builtins.d.ts.map +1 -0
- package/dist/shell/builtins.js +131 -0
- package/dist/shell/builtins.js.map +1 -0
- package/dist/shell/context.d.ts +41 -0
- package/dist/shell/context.d.ts.map +1 -0
- package/dist/shell/context.js +65 -0
- package/dist/shell/context.js.map +1 -0
- package/dist/shell/devHandoff.d.ts +18 -0
- package/dist/shell/devHandoff.d.ts.map +1 -0
- package/dist/shell/devHandoff.js +36 -0
- package/dist/shell/devHandoff.js.map +1 -0
- package/dist/shell/exec.d.ts +67 -0
- package/dist/shell/exec.d.ts.map +1 -0
- package/dist/shell/exec.js +247 -0
- package/dist/shell/exec.js.map +1 -0
- package/dist/shell/expand.d.ts +28 -0
- package/dist/shell/expand.d.ts.map +1 -0
- package/dist/shell/expand.js +149 -0
- package/dist/shell/expand.js.map +1 -0
- package/dist/shell/fileCommands.d.ts +10 -0
- package/dist/shell/fileCommands.d.ts.map +1 -0
- package/dist/shell/fileCommands.js +208 -0
- package/dist/shell/fileCommands.js.map +1 -0
- package/dist/shell/lineEditor.d.ts +43 -0
- package/dist/shell/lineEditor.d.ts.map +1 -0
- package/dist/shell/lineEditor.js +234 -0
- package/dist/shell/lineEditor.js.map +1 -0
- package/dist/shell/npm.d.ts +6 -0
- package/dist/shell/npm.d.ts.map +1 -0
- package/dist/shell/npm.js +234 -0
- package/dist/shell/npm.js.map +1 -0
- package/dist/shell/parse.d.ts +73 -0
- package/dist/shell/parse.d.ts.map +1 -0
- package/dist/shell/parse.js +300 -0
- package/dist/shell/parse.js.map +1 -0
- package/dist/shell/program.d.ts +12 -0
- package/dist/shell/program.d.ts.map +1 -0
- package/dist/shell/program.js +219 -0
- package/dist/shell/program.js.map +1 -0
- package/dist/shell/textCommands.d.ts +15 -0
- package/dist/shell/textCommands.d.ts.map +1 -0
- package/dist/shell/textCommands.js +185 -0
- package/dist/shell/textCommands.js.map +1 -0
- package/dist/styleModules.d.ts +6 -0
- package/dist/styleModules.d.ts.map +1 -1
- package/dist/styleModules.js +14 -4
- package/dist/styleModules.js.map +1 -1
- package/dist/svelteCompiler.d.ts +28 -0
- package/dist/svelteCompiler.d.ts.map +1 -0
- package/dist/svelteCompiler.js +13 -0
- package/dist/svelteCompiler.js.map +1 -0
- package/dist/system/commands.d.ts +26 -0
- package/dist/system/commands.d.ts.map +1 -0
- package/dist/system/commands.js +53 -0
- package/dist/system/commands.js.map +1 -0
- package/dist/system/index.d.ts +23 -0
- package/dist/system/index.d.ts.map +1 -0
- package/dist/system/index.js +16 -0
- package/dist/system/index.js.map +1 -0
- package/dist/system/kernel.d.ts +54 -0
- package/dist/system/kernel.d.ts.map +1 -0
- package/dist/system/kernel.js +81 -0
- package/dist/system/kernel.js.map +1 -0
- package/dist/system/ports.d.ts +32 -0
- package/dist/system/ports.d.ts.map +1 -0
- package/dist/system/ports.js +57 -0
- package/dist/system/ports.js.map +1 -0
- package/dist/system/process.d.ts +32 -0
- package/dist/system/process.d.ts.map +1 -0
- package/dist/system/process.js +130 -0
- package/dist/system/process.js.map +1 -0
- package/dist/system/programs.d.ts +10 -0
- package/dist/system/programs.d.ts.map +1 -0
- package/dist/system/programs.js +22 -0
- package/dist/system/programs.js.map +1 -0
- package/dist/system/terminal.d.ts +45 -0
- package/dist/system/terminal.d.ts.map +1 -0
- package/dist/system/terminal.js +127 -0
- package/dist/system/terminal.js.map +1 -0
- package/dist/system/types.d.ts +100 -0
- package/dist/system/types.d.ts.map +1 -0
- package/dist/system/types.js +2 -0
- package/dist/system/types.js.map +1 -0
- package/dist/vfs.d.ts +28 -4
- package/dist/vfs.d.ts.map +1 -1
- package/dist/vfs.js +95 -32
- package/dist/vfs.js.map +1 -1
- package/dist/vueCompiler.d.ts +75 -0
- package/dist/vueCompiler.d.ts.map +1 -0
- package/dist/vueCompiler.js +67 -0
- package/dist/vueCompiler.js.map +1 -0
- package/package.json +18 -2
- package/src/build.ts +105 -0
- package/src/buildHost.ts +154 -0
- package/src/buildHtml.ts +101 -0
- package/src/bundler.ts +190 -0
- package/src/check/acquire.ts +212 -0
- package/src/check/compilerHost.ts +138 -0
- package/src/check/diagnostics.ts +48 -0
- package/src/check/index.ts +28 -0
- package/src/check/projects.ts +149 -0
- package/src/check/shims.ts +88 -0
- package/src/check/typeStore.ts +103 -0
- package/src/check/typecheck.ts +134 -0
- package/src/compileComponent.ts +21 -0
- package/src/compileScript.ts +42 -13
- package/src/components.ts +59 -0
- package/src/detectProject.ts +18 -6
- package/src/devServer.ts +19 -10
- package/src/hash.ts +22 -0
- package/src/html.ts +19 -1
- package/src/index.ts +36 -7
- package/src/installer/bins.ts +64 -0
- package/src/installer/index.ts +13 -0
- package/src/installer/install.ts +275 -0
- package/src/installer/lockfile.ts +146 -0
- package/src/installer/registry.ts +175 -0
- package/src/installer/semver.ts +190 -0
- package/src/installer/spec.ts +53 -0
- package/src/installer/tar.ts +155 -0
- package/src/installer/tree.ts +236 -0
- package/src/node/assert.ts +127 -0
- package/src/node/buffer.ts +304 -0
- package/src/node/builtins.ts +123 -0
- package/src/node/callable.ts +25 -0
- package/src/node/childProcess.ts +164 -0
- package/src/node/crypto.ts +187 -0
- package/src/node/esm.ts +196 -0
- package/src/node/events.ts +176 -0
- package/src/node/fs/index.ts +277 -0
- package/src/node/fs/stats.ts +110 -0
- package/src/node/fs/sync.ts +335 -0
- package/src/node/hashes.ts +171 -0
- package/src/node/http/client.ts +173 -0
- package/src/node/http/index.ts +122 -0
- package/src/node/http/server.ts +308 -0
- package/src/node/http/status.ts +13 -0
- package/src/node/inspect.ts +166 -0
- package/src/node/loader.ts +293 -0
- package/src/node/loop.ts +193 -0
- package/src/node/misc.ts +250 -0
- package/src/node/path.ts +135 -0
- package/src/node/process.ts +324 -0
- package/src/node/program.ts +170 -0
- package/src/node/readline.ts +157 -0
- package/src/node/resolve.ts +216 -0
- package/src/node/runtime.ts +184 -0
- package/src/node/stream/index.ts +196 -0
- package/src/node/stream/readable.ts +345 -0
- package/src/node/stream/writable.ts +244 -0
- package/src/node/urlqs.ts +162 -0
- package/src/node/util.ts +145 -0
- package/src/node/zlib.ts +145 -0
- package/src/packageCdn.ts +11 -1
- package/src/projectConfig.ts +34 -7
- package/src/shell/builtins.ts +141 -0
- package/src/shell/context.ts +89 -0
- package/src/shell/devHandoff.ts +49 -0
- package/src/shell/exec.ts +268 -0
- package/src/shell/expand.ts +145 -0
- package/src/shell/fileCommands.ts +206 -0
- package/src/shell/lineEditor.ts +233 -0
- package/src/shell/npm.ts +229 -0
- package/src/shell/parse.ts +295 -0
- package/src/shell/program.ts +223 -0
- package/src/shell/textCommands.ts +177 -0
- package/src/styleModules.ts +15 -4
- package/src/svelteCompiler.ts +31 -0
- package/src/system/commands.ts +72 -0
- package/src/system/index.ts +22 -0
- package/src/system/kernel.ts +106 -0
- package/src/system/ports.ts +73 -0
- package/src/system/process.ts +158 -0
- package/src/system/programs.ts +31 -0
- package/src/system/terminal.ts +135 -0
- package/src/system/types.ts +103 -0
- package/src/vfs.ts +92 -28
- package/src/vueCompiler.ts +121 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { installPackages, chooseVersion } from './install.js';
|
|
2
|
+
export type { InstallOptions, InstallResult, InstallProgress } from './install.js';
|
|
3
|
+
export { RegistryClient, createMemoryCache, verifyIntegrity, DEFAULT_REGISTRY } from './registry.js';
|
|
4
|
+
export type { FetchLike, PackageCache, PackageManifest, Packument, RegistryOptions, BinField } from './registry.js';
|
|
5
|
+
export { resolveTree, childLocation, parentLocation, PLATFORM } from './tree.js';
|
|
6
|
+
export type { ResolvedPackage, ResolveTreeOptions } from './tree.js';
|
|
7
|
+
export { parseLockfile, buildLockfile, lockMatchesPackage } from './lockfile.js';
|
|
8
|
+
export type { Lockfile, LockEntry } from './lockfile.js';
|
|
9
|
+
export { parseInstallSpec, parseDependency } from './spec.js';
|
|
10
|
+
export type { ParsedSpec, PackageJson } from './spec.js';
|
|
11
|
+
export { normalizeBin, readBinLink, binShim, linkBins } from './bins.js';
|
|
12
|
+
export { untar, tar } from './tar.js';
|
|
13
|
+
export * as semver from './semver.js';
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npm install` into the virtual file system: read package.json, resolve the
|
|
3
|
+
* tree (lockfile first, registry second), prune what is no longer wanted, unpack
|
|
4
|
+
* what is missing, link `.bin`, write package-lock.json.
|
|
5
|
+
*
|
|
6
|
+
* Install scripts (`preinstall`/`install`/`postinstall`) are NOT run. Nearly all
|
|
7
|
+
* of them compile native addons (node-gyp) or download platform binaries, neither
|
|
8
|
+
* of which can work in a browser; running the rest would mean executing arbitrary
|
|
9
|
+
* package code at install time for little gain. Packages that declare them are
|
|
10
|
+
* reported in `warnings`.
|
|
11
|
+
*/
|
|
12
|
+
import { gunzipSync } from 'fflate';
|
|
13
|
+
import type { VirtualFileSystem } from '../vfs.js';
|
|
14
|
+
import { join } from '../paths.js';
|
|
15
|
+
import { RegistryClient, type FetchLike, type PackageCache, type PackageManifest, type Packument } from './registry.js';
|
|
16
|
+
import { parseInstallSpec, sortObject, type PackageJson, type ParsedSpec } from './spec.js';
|
|
17
|
+
import { childLocation, parentLocation, resolveTree, type ResolvedPackage } from './tree.js';
|
|
18
|
+
import { buildLockfile, lockMatchesPackage, lockedManifests, parseLockfile, pickLocked, type Lockfile } from './lockfile.js';
|
|
19
|
+
import { maxSatisfying, satisfies, validRange, valid } from './semver.js';
|
|
20
|
+
import { untar } from './tar.js';
|
|
21
|
+
import { linkBins } from './bins.js';
|
|
22
|
+
|
|
23
|
+
export interface InstallProgress {
|
|
24
|
+
phase: 'resolve' | 'fetch' | 'done';
|
|
25
|
+
/** `name@version` being worked on. */
|
|
26
|
+
package?: string;
|
|
27
|
+
done: number;
|
|
28
|
+
total: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface InstallOptions {
|
|
32
|
+
fs: VirtualFileSystem;
|
|
33
|
+
fetch: FetchLike;
|
|
34
|
+
registry?: string;
|
|
35
|
+
cache?: PackageCache;
|
|
36
|
+
/** The project directory holding package.json. Default `/`. */
|
|
37
|
+
cwd?: string;
|
|
38
|
+
/** Specs to add first, as `npm install <spec…>` does (`react`, `zod@^3`, `@scope/x@next`). */
|
|
39
|
+
add?: string[];
|
|
40
|
+
/** Save added specs to devDependencies. */
|
|
41
|
+
saveDev?: boolean;
|
|
42
|
+
/** `npm ci`: install exactly what the lockfile says; fail when it is missing or stale. */
|
|
43
|
+
ci?: boolean;
|
|
44
|
+
/** Leave devDependencies out. */
|
|
45
|
+
production?: boolean;
|
|
46
|
+
/** Parallel downloads. Default 8. */
|
|
47
|
+
concurrency?: number;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
onProgress?(progress: InstallProgress): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface InstallResult {
|
|
53
|
+
/** Packages unpacked this run. */
|
|
54
|
+
added: number;
|
|
55
|
+
/** Packages removed (no longer in the tree, or replaced by another version). */
|
|
56
|
+
removed: number;
|
|
57
|
+
/** Packages in the tree. */
|
|
58
|
+
total: number;
|
|
59
|
+
/** Top-level bins: name → absolute path of the script. */
|
|
60
|
+
bins: Record<string, string>;
|
|
61
|
+
lockfile: Lockfile;
|
|
62
|
+
warnings: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const fatalDecoder = new TextDecoder('utf-8', { fatal: true });
|
|
66
|
+
const BINARY_EXT = /\.(?:png|jpe?g|gif|webp|ico|bmp|wasm|node|gz|tgz|zip|br|woff2?|ttf|otf|eot|pdf|mp[34]|webm|ogg|wav|bin|dat)$/i;
|
|
67
|
+
|
|
68
|
+
/** Store text as strings (what the module loader reads), binaries as bytes. */
|
|
69
|
+
function decodeIfText(path: string, data: Uint8Array): string | Uint8Array {
|
|
70
|
+
if (BINARY_EXT.test(path)) return data.slice();
|
|
71
|
+
const head = data.subarray(0, 8000);
|
|
72
|
+
if (head.includes(0)) return data.slice();
|
|
73
|
+
try {
|
|
74
|
+
return fatalDecoder.decode(data);
|
|
75
|
+
} catch {
|
|
76
|
+
return data.slice();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Highest version allowed by `range` (or a dist-tag), preferring `latest` as npm does. */
|
|
81
|
+
export function chooseVersion(packument: Packument, range: string): string {
|
|
82
|
+
const tags = packument['dist-tags'] ?? {};
|
|
83
|
+
const r = range || '*';
|
|
84
|
+
if (!validRange(r)) {
|
|
85
|
+
const tagged = tags[r];
|
|
86
|
+
if (tagged && packument.versions[tagged]) return tagged;
|
|
87
|
+
throw new Error(`No version of ${packument.name} is tagged "${range}".`);
|
|
88
|
+
}
|
|
89
|
+
const latest = tags.latest;
|
|
90
|
+
if (latest && packument.versions[latest] && satisfies(latest, r)) return latest;
|
|
91
|
+
const best = maxSatisfying(Object.keys(packument.versions), r);
|
|
92
|
+
if (!best) throw new Error(`No matching version found for ${packument.name}@${range}.`);
|
|
93
|
+
return best;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function pool<T>(items: T[], size: number, run: (item: T) => Promise<void>): Promise<void> {
|
|
97
|
+
let next = 0;
|
|
98
|
+
const workers = Array.from({ length: Math.min(size, items.length) }, async () => {
|
|
99
|
+
while (next < items.length) await run(items[next++]!);
|
|
100
|
+
});
|
|
101
|
+
await Promise.all(workers);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readJson<T>(fs: VirtualFileSystem, path: string): T | undefined {
|
|
105
|
+
try {
|
|
106
|
+
const text = fs.readText(path);
|
|
107
|
+
return text === undefined ? undefined : (JSON.parse(text) as T);
|
|
108
|
+
} catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const writeJson = (fs: VirtualFileSystem, path: string, value: unknown) => fs.writeFile(path, JSON.stringify(value, null, 2) + '\n');
|
|
114
|
+
|
|
115
|
+
/** What is on disk now: location → `name@version`. */
|
|
116
|
+
function installedPackages(fs: VirtualFileSystem, base: string): Map<string, string> {
|
|
117
|
+
const out = new Map<string, string>();
|
|
118
|
+
const walk = (loc: string) => {
|
|
119
|
+
const nm = join(base, loc, 'node_modules');
|
|
120
|
+
const names: string[] = [];
|
|
121
|
+
for (const entry of fs.readdir(nm)) {
|
|
122
|
+
if (entry.startsWith('.')) continue;
|
|
123
|
+
if (entry.startsWith('@')) for (const sub of fs.readdir(join(nm, entry))) names.push(`${entry}/${sub}`);
|
|
124
|
+
else names.push(entry);
|
|
125
|
+
}
|
|
126
|
+
for (const name of names) {
|
|
127
|
+
const at = childLocation(loc, name);
|
|
128
|
+
const pkg = readJson<PackageJson>(fs, join(base, at, 'package.json'));
|
|
129
|
+
out.set(at, `${pkg?.name ?? ''}@${pkg?.version ?? ''}`);
|
|
130
|
+
walk(at);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
walk('');
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function addSpecs(client: RegistryClient, pkg: PackageJson, specs: string[], dev: boolean): Promise<void> {
|
|
138
|
+
const save = dev ? 'devDependencies' : 'dependencies';
|
|
139
|
+
const other = dev ? 'dependencies' : 'devDependencies';
|
|
140
|
+
for (const raw of specs) {
|
|
141
|
+
const spec: ParsedSpec = parseInstallSpec(raw);
|
|
142
|
+
let saved = spec.range;
|
|
143
|
+
if (!saved || !validRange(saved)) saved = `^${chooseVersion(await client.packument(spec.registryName), saved || 'latest')}`;
|
|
144
|
+
else if (valid(saved)) saved = `^${valid(saved)}`;
|
|
145
|
+
if (spec.registryName !== spec.name) saved = `npm:${spec.registryName}@${saved}`;
|
|
146
|
+
pkg[save] = sortObject({ ...(pkg[save] ?? {}), [spec.name]: saved });
|
|
147
|
+
if (pkg[other]?.[spec.name]) {
|
|
148
|
+
const rest = { ...pkg[other] };
|
|
149
|
+
delete rest[spec.name];
|
|
150
|
+
pkg[other] = rest;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function installPackages(options: InstallOptions): Promise<InstallResult> {
|
|
156
|
+
const { fs, signal } = options;
|
|
157
|
+
const base = join('/', options.cwd ?? '/');
|
|
158
|
+
const pkgPath = join(base, 'package.json');
|
|
159
|
+
const lockPath = join(base, 'package-lock.json');
|
|
160
|
+
const warnings: string[] = [];
|
|
161
|
+
const client = new RegistryClient({ fetch: options.fetch, registry: options.registry, cache: options.cache, signal });
|
|
162
|
+
const progress = (p: InstallProgress) => options.onProgress?.(p);
|
|
163
|
+
|
|
164
|
+
let pkg = readJson<PackageJson>(fs, pkgPath);
|
|
165
|
+
if (!pkg) {
|
|
166
|
+
if (fs.isFile(pkgPath)) throw new Error(`${pkgPath} is not valid JSON.`);
|
|
167
|
+
if (!options.add?.length) throw new Error(`No package.json in ${base}.`);
|
|
168
|
+
pkg = {};
|
|
169
|
+
}
|
|
170
|
+
if (options.add?.length) {
|
|
171
|
+
await addSpecs(client, pkg, options.add, options.saveDev ?? false);
|
|
172
|
+
writeJson(fs, pkgPath, pkg);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const lock = parseLockfile(fs.readText(lockPath));
|
|
176
|
+
if (options.ci) {
|
|
177
|
+
if (!lock) throw new Error('npm ci needs a package-lock.json (lockfileVersion 2 or 3).');
|
|
178
|
+
if (!lockMatchesPackage(lock, pkg)) throw new Error('package.json and package-lock.json are out of sync. Run npm install to update the lockfile.');
|
|
179
|
+
fs.rm(join(base, 'node_modules'));
|
|
180
|
+
}
|
|
181
|
+
const locked = lockedManifests(lock);
|
|
182
|
+
|
|
183
|
+
const picks = new Map<string, Promise<PackageManifest>>();
|
|
184
|
+
const pick = (spec: ParsedSpec): Promise<PackageManifest> => {
|
|
185
|
+
const key = `${spec.registryName}@${spec.range}`;
|
|
186
|
+
let pending = picks.get(key);
|
|
187
|
+
if (!pending) {
|
|
188
|
+
const hit = pickLocked(locked, spec.registryName, spec.range);
|
|
189
|
+
if (hit) pending = Promise.resolve(hit);
|
|
190
|
+
else if (options.ci) pending = Promise.reject(new Error(`${key} is missing from package-lock.json. Run npm install to update it.`));
|
|
191
|
+
else pending = client.packument(spec.registryName).then((p) => {
|
|
192
|
+
const manifest = p.versions[chooseVersion(p, spec.range)]!;
|
|
193
|
+
return { ...manifest, name: manifest.name ?? spec.registryName };
|
|
194
|
+
});
|
|
195
|
+
pending.catch(() => undefined); // observed by whoever awaits it
|
|
196
|
+
picks.set(key, pending);
|
|
197
|
+
}
|
|
198
|
+
return pending;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
let resolved = 0;
|
|
202
|
+
const tree = await resolveTree({
|
|
203
|
+
root: pkg,
|
|
204
|
+
pick,
|
|
205
|
+
prefetch: (spec) => void (pickLocked(locked, spec.registryName, spec.range) || options.ci || client.packument(spec.registryName).catch(() => undefined)),
|
|
206
|
+
production: options.production,
|
|
207
|
+
warn: (message) => warnings.push(message),
|
|
208
|
+
onResolved: ({ name, version }) => progress({ phase: 'resolve', package: `${name}@${version}`, done: ++resolved, total: resolved }),
|
|
209
|
+
});
|
|
210
|
+
signal?.throwIfAborted();
|
|
211
|
+
|
|
212
|
+
// Prune: anything not in the tree, or there at another version, goes (shallow first).
|
|
213
|
+
let removed = 0;
|
|
214
|
+
for (const [location, id] of [...installedPackages(fs, base)].sort(([a], [b]) => a.length - b.length)) {
|
|
215
|
+
const dir = join(base, location);
|
|
216
|
+
if (!fs.exists(dir)) continue; // went with an enclosing package
|
|
217
|
+
const want = tree.get(location);
|
|
218
|
+
if (!want || id !== `${want.manifest.name}@${want.manifest.version}`) {
|
|
219
|
+
fs.rm(dir);
|
|
220
|
+
removed++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const missing = [...tree.values()].filter((node) => !fs.isFile(join(base, node.location, 'package.json')));
|
|
225
|
+
let fetched = 0;
|
|
226
|
+
await pool(missing, options.concurrency ?? 8, async (node) => {
|
|
227
|
+
signal?.throwIfAborted();
|
|
228
|
+
await unpack(fs, join(base, node.location), await client.tarball(node.manifest));
|
|
229
|
+
progress({ phase: 'fetch', package: `${node.name}@${node.manifest.version}`, done: ++fetched, total: missing.length });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
for (const node of tree.values()) {
|
|
233
|
+
if (node.manifest.hasInstallScript) warnings.push(`skipped install scripts of ${node.name}@${node.manifest.version} (they cannot run in the browser)`);
|
|
234
|
+
if (node.manifest.deprecated) warnings.push(`deprecated ${node.name}@${node.manifest.version}: ${node.manifest.deprecated}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const bins = relinkBins(fs, base, tree);
|
|
238
|
+
const lockfile = buildLockfile(pkg, tree);
|
|
239
|
+
writeJson(fs, lockPath, lockfile);
|
|
240
|
+
progress({ phase: 'done', done: tree.size, total: tree.size });
|
|
241
|
+
return { added: missing.length, removed, total: tree.size, bins, lockfile, warnings };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** gunzip + untar into `dir`, stripping the archive's top folder (`package/`). */
|
|
245
|
+
async function unpack(fs: VirtualFileSystem, dir: string, tgz: Uint8Array): Promise<void> {
|
|
246
|
+
const entries = untar(gunzipSync(tgz));
|
|
247
|
+
let manifest: Uint8Array | undefined;
|
|
248
|
+
for (const entry of entries) {
|
|
249
|
+
if (entry.type !== 'file') continue;
|
|
250
|
+
const relative = entry.path.split('/').slice(1).join('/');
|
|
251
|
+
if (!relative || relative.split('/').includes('..')) continue;
|
|
252
|
+
// package.json goes last: its presence is what marks the package as installed.
|
|
253
|
+
if (relative === 'package.json') manifest = entry.data;
|
|
254
|
+
else fs.writeFile(join(dir, relative), decodeIfText(relative, entry.data));
|
|
255
|
+
}
|
|
256
|
+
if (!manifest) throw new Error(`The tarball for ${dir} has no package.json.`);
|
|
257
|
+
fs.writeFile(join(dir, 'package.json'), decodeIfText('package.json', manifest));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Rebuild every `.bin` directory from the tree; returns the top-level bins. */
|
|
261
|
+
function relinkBins(fs: VirtualFileSystem, base: string, tree: Map<string, ResolvedPackage>): Record<string, string> {
|
|
262
|
+
const groups = new Map<string, ResolvedPackage[]>();
|
|
263
|
+
for (const node of tree.values()) {
|
|
264
|
+
const parent = parentLocation(node.location);
|
|
265
|
+
groups.set(parent, [...(groups.get(parent) ?? []), node]);
|
|
266
|
+
}
|
|
267
|
+
fs.rm(join(base, 'node_modules/.bin'));
|
|
268
|
+
let top: Record<string, string> = {};
|
|
269
|
+
for (const [parent, nodes] of groups) {
|
|
270
|
+
const binDir = join(base, parent, 'node_modules/.bin');
|
|
271
|
+
const linked = linkBins(fs, binDir, nodes.map((n) => ({ dir: join(base, n.location), name: n.name, bin: readJson<PackageJson>(fs, join(base, n.location, 'package.json'))?.bin ?? n.manifest.bin })));
|
|
272
|
+
if (parent === '') top = linked;
|
|
273
|
+
}
|
|
274
|
+
return top;
|
|
275
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package-lock.json, lockfileVersion 3 (npm 7+): a flat `packages` map keyed by
|
|
3
|
+
* install location. Reading it lets an install skip the registry's metadata
|
|
4
|
+
* entirely when nothing changed — every locked entry carries its version,
|
|
5
|
+
* tarball, integrity and dependency ranges.
|
|
6
|
+
*/
|
|
7
|
+
import type { BinField, PackageManifest } from './registry.js';
|
|
8
|
+
import type { ResolvedPackage } from './tree.js';
|
|
9
|
+
import { sortObject, type PackageJson } from './spec.js';
|
|
10
|
+
import { satisfies, validRange } from './semver.js';
|
|
11
|
+
|
|
12
|
+
export interface LockEntry {
|
|
13
|
+
name?: string;
|
|
14
|
+
version?: string;
|
|
15
|
+
resolved?: string;
|
|
16
|
+
integrity?: string;
|
|
17
|
+
dev?: boolean;
|
|
18
|
+
optional?: boolean;
|
|
19
|
+
license?: string;
|
|
20
|
+
dependencies?: Record<string, string>;
|
|
21
|
+
devDependencies?: Record<string, string>;
|
|
22
|
+
optionalDependencies?: Record<string, string>;
|
|
23
|
+
peerDependencies?: Record<string, string>;
|
|
24
|
+
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
|
|
25
|
+
bin?: BinField;
|
|
26
|
+
engines?: Record<string, string>;
|
|
27
|
+
os?: string[];
|
|
28
|
+
cpu?: string[];
|
|
29
|
+
hasInstallScript?: boolean;
|
|
30
|
+
deprecated?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Lockfile {
|
|
34
|
+
name?: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
lockfileVersion: number;
|
|
37
|
+
requires?: boolean;
|
|
38
|
+
packages: Record<string, LockEntry>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseLockfile(text: string | undefined): Lockfile | undefined {
|
|
42
|
+
if (!text) return undefined;
|
|
43
|
+
try {
|
|
44
|
+
const lock = JSON.parse(text) as Lockfile;
|
|
45
|
+
// v1 lockfiles only have the nested `dependencies` tree; re-resolving is simpler than translating.
|
|
46
|
+
return lock.lockfileVersion >= 2 && lock.packages ? lock : undefined;
|
|
47
|
+
} catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const nameFromLocation = (location: string) => location.slice(location.lastIndexOf('node_modules/') + 'node_modules/'.length);
|
|
53
|
+
|
|
54
|
+
/** A locked entry as the manifest the resolver works with. */
|
|
55
|
+
export function lockEntryManifest(location: string, entry: LockEntry): PackageManifest | undefined {
|
|
56
|
+
if (!entry.version || !entry.resolved) return undefined;
|
|
57
|
+
return {
|
|
58
|
+
name: entry.name ?? nameFromLocation(location),
|
|
59
|
+
version: entry.version,
|
|
60
|
+
dependencies: entry.dependencies,
|
|
61
|
+
optionalDependencies: entry.optionalDependencies,
|
|
62
|
+
peerDependencies: entry.peerDependencies,
|
|
63
|
+
peerDependenciesMeta: entry.peerDependenciesMeta,
|
|
64
|
+
bin: entry.bin,
|
|
65
|
+
engines: entry.engines,
|
|
66
|
+
os: entry.os,
|
|
67
|
+
cpu: entry.cpu,
|
|
68
|
+
hasInstallScript: entry.hasInstallScript,
|
|
69
|
+
deprecated: entry.deprecated,
|
|
70
|
+
license: entry.license,
|
|
71
|
+
dist: { tarball: entry.resolved, integrity: entry.integrity },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Locked manifests by registry package name, for "prefer what is locked". */
|
|
76
|
+
export function lockedManifests(lock: Lockfile | undefined): Map<string, PackageManifest[]> {
|
|
77
|
+
const out = new Map<string, PackageManifest[]>();
|
|
78
|
+
for (const [location, entry] of Object.entries(lock?.packages ?? {})) {
|
|
79
|
+
if (!location.includes('node_modules/')) continue;
|
|
80
|
+
const manifest = lockEntryManifest(location, entry);
|
|
81
|
+
if (!manifest) continue;
|
|
82
|
+
const list = out.get(manifest.name) ?? [];
|
|
83
|
+
list.push(manifest);
|
|
84
|
+
out.set(manifest.name, list);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The highest locked version of `name` satisfying `range`, if any. */
|
|
90
|
+
export function pickLocked(locked: Map<string, PackageManifest[]>, name: string, range: string): PackageManifest | undefined {
|
|
91
|
+
const r = range || '*';
|
|
92
|
+
if (!validRange(r)) return undefined;
|
|
93
|
+
let best: PackageManifest | undefined;
|
|
94
|
+
for (const m of locked.get(name) ?? []) {
|
|
95
|
+
if (satisfies(m.version, r) && (!best || satisfies(m.version, `>${best.version}`))) best = m;
|
|
96
|
+
}
|
|
97
|
+
return best;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const ROOT_FIELDS = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'] as const;
|
|
101
|
+
|
|
102
|
+
/** Do the lockfile's root ranges match package.json exactly? (`npm ci`'s precondition.) */
|
|
103
|
+
export function lockMatchesPackage(lock: Lockfile, pkg: PackageJson): boolean {
|
|
104
|
+
const root = lock.packages[''] ?? {};
|
|
105
|
+
return ROOT_FIELDS.every((field) => {
|
|
106
|
+
const a = pkg[field] ?? {};
|
|
107
|
+
const b = (root as Record<string, Record<string, string> | undefined>)[field] ?? {};
|
|
108
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
109
|
+
return [...keys].every((k) => a[k] === b[k]);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const nonEmpty = <T extends object>(record: T | undefined): T | undefined => (record && Object.keys(record).length ? record : undefined);
|
|
114
|
+
|
|
115
|
+
export function buildLockfile(pkg: PackageJson, tree: Map<string, ResolvedPackage>): Lockfile {
|
|
116
|
+
const root: LockEntry & { name?: string } = { name: pkg.name, version: pkg.version };
|
|
117
|
+
for (const field of ROOT_FIELDS) if (nonEmpty(pkg[field])) (root as Record<string, unknown>)[field] = pkg[field];
|
|
118
|
+
if (pkg.bin) root.bin = pkg.bin;
|
|
119
|
+
const packages: Record<string, LockEntry> = { '': JSON.parse(JSON.stringify(root)) as LockEntry };
|
|
120
|
+
|
|
121
|
+
for (const location of [...tree.keys()].sort()) {
|
|
122
|
+
const node = tree.get(location)!;
|
|
123
|
+
const m = node.manifest;
|
|
124
|
+
const entry: LockEntry = {
|
|
125
|
+
name: m.name !== node.name ? m.name : undefined,
|
|
126
|
+
version: m.version,
|
|
127
|
+
resolved: m.dist.tarball,
|
|
128
|
+
integrity: m.dist.integrity,
|
|
129
|
+
dev: node.dev || undefined,
|
|
130
|
+
optional: node.optional || undefined,
|
|
131
|
+
license: m.license,
|
|
132
|
+
dependencies: nonEmpty(m.dependencies && sortObject(m.dependencies)),
|
|
133
|
+
optionalDependencies: nonEmpty(m.optionalDependencies),
|
|
134
|
+
peerDependencies: nonEmpty(m.peerDependencies),
|
|
135
|
+
peerDependenciesMeta: nonEmpty(m.peerDependenciesMeta),
|
|
136
|
+
bin: m.bin,
|
|
137
|
+
engines: nonEmpty(m.engines),
|
|
138
|
+
os: m.os,
|
|
139
|
+
cpu: m.cpu,
|
|
140
|
+
hasInstallScript: m.hasInstallScript || undefined,
|
|
141
|
+
deprecated: m.deprecated,
|
|
142
|
+
};
|
|
143
|
+
packages[location] = JSON.parse(JSON.stringify(entry)) as LockEntry; // drops the undefineds
|
|
144
|
+
}
|
|
145
|
+
return { name: pkg.name, version: pkg.version, lockfileVersion: 3, requires: true, packages };
|
|
146
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Talking to an npm registry: packuments (version metadata) and tarballs, both
|
|
3
|
+
* through an optional cache PORT so the browser can back it with Cache Storage
|
|
4
|
+
* and tests with a Map. The installer never touches `globalThis.fetch` — the
|
|
5
|
+
* caller injects one, which is what keeps this testable against a fake registry.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type FetchLike = (url: string, init?: { headers?: Record<string, string>; signal?: AbortSignal }) => Promise<{
|
|
9
|
+
ok: boolean;
|
|
10
|
+
status: number;
|
|
11
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
/** A byte cache keyed by string. Tarballs are immutable, so they are cached forever. */
|
|
15
|
+
export interface PackageCache {
|
|
16
|
+
get(key: string): Promise<Uint8Array | undefined>;
|
|
17
|
+
put(key: string, data: Uint8Array): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createMemoryCache(): PackageCache & { readonly size: number } {
|
|
21
|
+
const store = new Map<string, Uint8Array>();
|
|
22
|
+
return {
|
|
23
|
+
get: async (key) => store.get(key),
|
|
24
|
+
put: async (key, data) => void store.set(key, data),
|
|
25
|
+
get size() {
|
|
26
|
+
return store.size;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type BinField = string | Record<string, string>;
|
|
32
|
+
|
|
33
|
+
/** One version from a packument — the fields the abbreviated format carries. */
|
|
34
|
+
export interface PackageManifest {
|
|
35
|
+
name: string;
|
|
36
|
+
version: string;
|
|
37
|
+
dependencies?: Record<string, string>;
|
|
38
|
+
optionalDependencies?: Record<string, string>;
|
|
39
|
+
peerDependencies?: Record<string, string>;
|
|
40
|
+
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
|
|
41
|
+
bundleDependencies?: string[] | boolean;
|
|
42
|
+
bundledDependencies?: string[] | boolean;
|
|
43
|
+
bin?: BinField;
|
|
44
|
+
os?: string[];
|
|
45
|
+
cpu?: string[];
|
|
46
|
+
engines?: Record<string, string>;
|
|
47
|
+
hasInstallScript?: boolean;
|
|
48
|
+
deprecated?: string;
|
|
49
|
+
license?: string;
|
|
50
|
+
dist: { tarball: string; integrity?: string; shasum?: string };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface Packument {
|
|
54
|
+
name: string;
|
|
55
|
+
'dist-tags': Record<string, string>;
|
|
56
|
+
versions: Record<string, PackageManifest>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface RegistryOptions {
|
|
60
|
+
fetch: FetchLike;
|
|
61
|
+
registry?: string;
|
|
62
|
+
cache?: PackageCache;
|
|
63
|
+
/** How long a cached packument is trusted. Default 10 minutes. */
|
|
64
|
+
packumentMaxAgeMs?: number;
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
69
|
+
/** The abbreviated ("corgi") document: a fraction of the full packument's size. */
|
|
70
|
+
const ABBREVIATED = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*';
|
|
71
|
+
|
|
72
|
+
const encoder = new TextEncoder();
|
|
73
|
+
const decoder = new TextDecoder();
|
|
74
|
+
|
|
75
|
+
function toBase64(bytes: Uint8Array): string {
|
|
76
|
+
let binary = '';
|
|
77
|
+
for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
|
78
|
+
return btoa(binary);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function toHex(bytes: Uint8Array): string {
|
|
82
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check `integrity` (SRI, `sha512-…`) or the legacy sha1 `shasum`. Skipped where
|
|
87
|
+
* WebCrypto is missing — a tarball from the configured registry over https is
|
|
88
|
+
* still trustworthy, the check only guards against a corrupt cache entry.
|
|
89
|
+
*/
|
|
90
|
+
export async function verifyIntegrity(data: Uint8Array, dist: { integrity?: string; shasum?: string }): Promise<boolean> {
|
|
91
|
+
const subtle = globalThis.crypto?.subtle;
|
|
92
|
+
if (!subtle) return true;
|
|
93
|
+
const digest = async (algo: string) => new Uint8Array(await subtle.digest(algo, data as unknown as ArrayBuffer));
|
|
94
|
+
if (dist.integrity) {
|
|
95
|
+
const algos: Record<string, string> = { sha512: 'SHA-512', sha384: 'SHA-384', sha256: 'SHA-256', sha1: 'SHA-1' };
|
|
96
|
+
for (const token of dist.integrity.split(/\s+/)) {
|
|
97
|
+
const dash = token.indexOf('-');
|
|
98
|
+
const algo = algos[token.slice(0, dash)];
|
|
99
|
+
if (algo && toBase64(await digest(algo)) === token.slice(dash + 1)) return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
if (dist.shasum) return toHex(await digest('SHA-1')) === dist.shasum;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class RegistryClient {
|
|
108
|
+
readonly registry: string;
|
|
109
|
+
private readonly packuments = new Map<string, Promise<Packument>>();
|
|
110
|
+
|
|
111
|
+
constructor(private readonly options: RegistryOptions) {
|
|
112
|
+
this.registry = (options.registry ?? DEFAULT_REGISTRY).replace(/\/+$/, '');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Version metadata for a package, fetched once per client. */
|
|
116
|
+
packument(name: string): Promise<Packument> {
|
|
117
|
+
let pending = this.packuments.get(name);
|
|
118
|
+
if (!pending) {
|
|
119
|
+
pending = this.loadPackument(name);
|
|
120
|
+
pending.catch(() => this.packuments.delete(name));
|
|
121
|
+
this.packuments.set(name, pending);
|
|
122
|
+
}
|
|
123
|
+
return pending;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The tarball's bytes (still gzipped), integrity-checked. */
|
|
127
|
+
async tarball(manifest: Pick<PackageManifest, 'name' | 'version' | 'dist'>): Promise<Uint8Array> {
|
|
128
|
+
const { dist } = manifest;
|
|
129
|
+
const key = `tarball:${dist.integrity ?? dist.shasum ?? dist.tarball}`;
|
|
130
|
+
const cached = await this.options.cache?.get(key);
|
|
131
|
+
if (cached && (await verifyIntegrity(cached, dist))) return cached;
|
|
132
|
+
const data = await this.get(dist.tarball, {});
|
|
133
|
+
if (!(await verifyIntegrity(data, dist))) throw new Error(`Integrity check failed for ${manifest.name}@${manifest.version}`);
|
|
134
|
+
await this.options.cache?.put(key, data);
|
|
135
|
+
return data;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async loadPackument(name: string): Promise<Packument> {
|
|
139
|
+
const key = `packument:${this.registry}/${name}`;
|
|
140
|
+
const maxAge = this.options.packumentMaxAgeMs ?? 10 * 60_000;
|
|
141
|
+
const cached = await this.options.cache?.get(key);
|
|
142
|
+
if (cached) {
|
|
143
|
+
try {
|
|
144
|
+
const entry = JSON.parse(decoder.decode(cached)) as { t: number; p: Packument };
|
|
145
|
+
if (Date.now() - entry.t < maxAge) return entry.p;
|
|
146
|
+
} catch {
|
|
147
|
+
// a corrupt entry is just a miss
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const url = `${this.registry}/${name.startsWith('@') ? '@' + encodeURIComponent(name.slice(1)) : encodeURIComponent(name)}`;
|
|
151
|
+
const bytes = await this.get(url, { accept: ABBREVIATED }, name);
|
|
152
|
+
const packument = JSON.parse(decoder.decode(bytes)) as Packument;
|
|
153
|
+
if (!packument.versions) throw new Error(`The registry returned no versions for "${name}".`);
|
|
154
|
+
await this.options.cache?.put(key, encoder.encode(JSON.stringify({ t: Date.now(), p: packument })));
|
|
155
|
+
return packument;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async get(url: string, headers: Record<string, string>, packageName?: string): Promise<Uint8Array> {
|
|
159
|
+
let lastError: unknown;
|
|
160
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
161
|
+
let response: Awaited<ReturnType<FetchLike>>;
|
|
162
|
+
try {
|
|
163
|
+
response = await this.options.fetch(url, { headers, signal: this.options.signal });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (this.options.signal?.aborted) throw error;
|
|
166
|
+
lastError = error; // network blip: retry once
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (response.status === 404 && packageName) throw new Error(`Package "${packageName}" was not found in ${this.registry}.`);
|
|
170
|
+
if (!response.ok) throw new Error(`GET ${url} failed with HTTP ${response.status}.`);
|
|
171
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
172
|
+
}
|
|
173
|
+
throw new Error(`GET ${url} failed: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
174
|
+
}
|
|
175
|
+
}
|