zyrk 0.5.0 → 0.5.2
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 +10 -2
- package/VERSION +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +7 -11
- package/dist/commands/maintenance.d.ts.map +1 -1
- package/dist/commands/maintenance.js +22 -2
- package/dist/fetch/package.d.ts.map +1 -1
- package/dist/fetch/package.js +2 -1
- package/dist/fetch/registry.d.ts +5 -0
- package/dist/fetch/registry.d.ts.map +1 -1
- package/dist/fetch/registry.js +84 -10
- package/dist/install/import-npm-workspace.d.ts +2 -1
- package/dist/install/import-npm-workspace.d.ts.map +1 -1
- package/dist/install/import-npm-workspace.js +34 -12
- package/dist/install/import-npm.d.ts +3 -1
- package/dist/install/import-npm.d.ts.map +1 -1
- package/dist/install/import-npm.js +14 -9
- package/dist/install/registry-policy.d.ts +9 -0
- package/dist/install/registry-policy.d.ts.map +1 -0
- package/dist/install/registry-policy.js +30 -0
- package/dist/install/registry.d.ts.map +1 -1
- package/dist/install/registry.js +18 -13
- package/dist/install/run.d.ts.map +1 -1
- package/dist/install/run.js +22 -5
- package/dist/install/workspace-graph.d.ts +7 -1
- package/dist/install/workspace-graph.d.ts.map +1 -1
- package/dist/install/workspace-graph.js +53 -9
- package/dist/install/workspace-install.d.ts.map +1 -1
- package/dist/install/workspace-install.js +16 -6
- package/dist/install/workspace-lock.d.ts +1 -1
- package/dist/install/workspace-lock.d.ts.map +1 -1
- package/dist/install/workspace-lock.js +5 -1
- package/dist/install/workspace-mutation.d.ts.map +1 -1
- package/dist/install/workspace-mutation.js +4 -1
- package/dist/install/workspace-overrides.d.ts +11 -0
- package/dist/install/workspace-overrides.d.ts.map +1 -0
- package/dist/install/workspace-overrides.js +67 -0
- package/dist/install/workspaces.d.ts.map +1 -1
- package/dist/install/workspaces.js +4 -1
- package/dist/lockfile/fingerprints.d.ts +1 -0
- package/dist/lockfile/fingerprints.d.ts.map +1 -1
- package/dist/lockfile/fingerprints.js +16 -1
- package/dist/resolve/overrides.d.ts +19 -0
- package/dist/resolve/overrides.d.ts.map +1 -0
- package/dist/resolve/overrides.js +142 -0
- package/dist/resolve/resolver.d.ts +3 -0
- package/dist/resolve/resolver.d.ts.map +1 -1
- package/dist/resolve/resolver.js +62 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,8 +116,10 @@ Default registry and authentication settings are loaded from user/project
|
|
|
116
116
|
token placeholders are supported. Credentials are restricted to their configured
|
|
117
117
|
scheme, host, port, and path; redirects cannot expand that scope. Use
|
|
118
118
|
`--registry=https://your-registry.example/` on install for an explicit registry.
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
Scoped registries route metadata, exact sources, imports and tool commands by
|
|
120
|
+
the real package name, including aliases. Advisory requests are partitioned by
|
|
121
|
+
registry; private names never fall back to a public endpoint. Disabled TLS
|
|
122
|
+
verification is rejected before lookup. Git sources and non-workspace local sources remain unsupported;
|
|
121
123
|
they fail explicitly. Optional dependencies incompatible with the selected platform
|
|
122
124
|
are omitted; missing packages, integrity failures, and other optional failures
|
|
123
125
|
remain errors. Platform selection is recorded in the lock. macOS, Windows, and
|
|
@@ -354,3 +356,9 @@ Keep existing data until an explicit migration is supported.
|
|
|
354
356
|
|
|
355
357
|
[MIT](LICENSE). If you want to support development,
|
|
356
358
|
[sponsor the maintainer](https://github.com/sponsors/kingjethro999).
|
|
359
|
+
|
|
360
|
+
Root `package.json` dependency overrides support exact specifications, nested and
|
|
361
|
+
version-qualified selectors, self rules (`.`), and `$direct` references. Direct
|
|
362
|
+
dependency overrides must match the declared specification. Workspace member
|
|
363
|
+
overrides are ignored, matching npm root-only policy. Override rule order is
|
|
364
|
+
recorded in the manifest fingerprint; frozen installs reject policy drift.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v0.5.
|
|
1
|
+
v0.5.2
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAoEA,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1C,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAwEF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAqZjB"}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { installedRegistryConfig } from "../install/registry-policy.js";
|
|
1
2
|
/** Self-contained runtime deployment and explicit local Docker commands. */
|
|
2
3
|
import { constants } from "node:fs";
|
|
3
4
|
import { open, copyFile, chmod, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rename, rm, symlink, writeFile, } from "node:fs/promises";
|
|
@@ -141,16 +142,14 @@ export async function runDeployCommand(options) {
|
|
|
141
142
|
manifestFingerprint(manifest))
|
|
142
143
|
throw error("ZYRK_LOCK_MISMATCH", "Deployment manifest, lock, and installed generation disagree.");
|
|
143
144
|
if (resolved.nodes.length) {
|
|
144
|
-
const
|
|
145
|
-
if (origins.size !== 1)
|
|
146
|
-
throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Mixed-origin deployment requires a validated registry adapter.");
|
|
145
|
+
const registryConfig = installedRegistryConfig(state, resolved.nodes);
|
|
147
146
|
const patches = await loadProjectPatches(project);
|
|
148
147
|
const policy = object(manifest.zyrk) ? manifest.zyrk : {};
|
|
149
148
|
assertFrozenCompatible(doc, {
|
|
150
149
|
manifest: manifestFingerprint(manifest),
|
|
151
150
|
scripts: scriptPolicyFingerprint(policy.scripts),
|
|
152
151
|
config: configFingerprint({
|
|
153
|
-
|
|
152
|
+
...registryConfig,
|
|
154
153
|
linker: "shared",
|
|
155
154
|
platform: currentPlatform(),
|
|
156
155
|
...(Array.isArray(policy.mutablePackages)
|
|
@@ -800,15 +799,12 @@ async function exportWorkspace(options, project, manifest, state, out, parent, f
|
|
|
800
799
|
const policy = object(manifest.zyrk) ? manifest.zyrk : {};
|
|
801
800
|
if (locked.doc.zyrk.scripts !== scriptPolicyFingerprint(policy.scripts))
|
|
802
801
|
throw error("ZYRK_LOCK_MISMATCH", "Workspace deployment build policy changed after installation.");
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
if (origins.size > 1)
|
|
807
|
-
throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Mixed-origin workspace export requires a validated registry adapter.");
|
|
808
|
-
if (origins.size === 1) {
|
|
802
|
+
const registryNodesForPolicy = locked.graph.nodes.filter((n) => n.kind === "registry");
|
|
803
|
+
if (registryNodesForPolicy.length || state.registryConfig !== undefined) {
|
|
804
|
+
const registryConfig = installedRegistryConfig(state, registryNodesForPolicy);
|
|
809
805
|
const patches = await loadProjectPatches(project);
|
|
810
806
|
const expected = configFingerprint({
|
|
811
|
-
|
|
807
|
+
...registryConfig,
|
|
812
808
|
linker: "shared",
|
|
813
809
|
platform: currentPlatform(),
|
|
814
810
|
...(Array.isArray(policy.mutablePackages)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maintenance.d.ts","sourceRoot":"","sources":["../../src/commands/maintenance.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AASvE,QAAA,MAAM,UAAU,YAAI,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAC5E,KAAK,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,aAAa,CAAC;IAChD,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,QAAQ,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAUF,wBAAsB,qBAAqB,CAAC,OAAO,EAAE;IACnD,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,GAAG,OAAO,CAAC,aAAa,GAAG,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"maintenance.d.ts","sourceRoot":"","sources":["../../src/commands/maintenance.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AASvE,QAAA,MAAM,UAAU,YAAI,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAC5E,KAAK,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,aAAa,CAAC;IAChD,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,QAAQ,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAUF,wBAAsB,qBAAqB,CAAC,OAAO,EAAE;IACnD,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,GAAG,OAAO,CAAC,aAAa,GAAG,WAAW,CAAC,CAgQvC"}
|
|
@@ -126,6 +126,7 @@ export async function runMaintenanceCommand(options) {
|
|
|
126
126
|
scripts: scriptPolicyFingerprint(object(manifest.zyrk) ? manifest.zyrk.scripts : undefined),
|
|
127
127
|
config: configFingerprint({
|
|
128
128
|
registry: options.registry.origin,
|
|
129
|
+
registries: options.registry.scopedRegistries,
|
|
129
130
|
linker: "shared",
|
|
130
131
|
platform: currentPlatform(),
|
|
131
132
|
...(object(manifest.zyrk) && Array.isArray(manifest.zyrk.mutablePackages)
|
|
@@ -146,7 +147,7 @@ export async function runMaintenanceCommand(options) {
|
|
|
146
147
|
: lockedGraph(lock.doc).nodes;
|
|
147
148
|
const packages = Object.create(null);
|
|
148
149
|
for (const node of graphNodes) {
|
|
149
|
-
if (node.origin !== options.registry.
|
|
150
|
+
if (node.origin !== options.registry.originFor(node.name))
|
|
150
151
|
throw new ZyrkError("ZYRK_POLICY_DENIED", "Cannot audit a graph from a different registry origin.", {
|
|
151
152
|
remedy: "Select the registry recorded by the lockfile before sending dependency names.",
|
|
152
153
|
});
|
|
@@ -158,7 +159,26 @@ export async function runMaintenanceCommand(options) {
|
|
|
158
159
|
return base;
|
|
159
160
|
let response;
|
|
160
161
|
try {
|
|
161
|
-
|
|
162
|
+
const groups = new Map();
|
|
163
|
+
for (const [name, versions] of Object.entries(packages)) {
|
|
164
|
+
const client = options.registry.forPackage(name);
|
|
165
|
+
let selected = groups.get(client);
|
|
166
|
+
if (!selected) {
|
|
167
|
+
selected = Object.create(null);
|
|
168
|
+
groups.set(client, selected);
|
|
169
|
+
}
|
|
170
|
+
selected[name] = versions;
|
|
171
|
+
}
|
|
172
|
+
const replies = await Promise.all([...groups].map(async ([client, selected]) => {
|
|
173
|
+
const reply = await client.auditAdvisories(selected);
|
|
174
|
+
if (!object(reply) ||
|
|
175
|
+
Object.keys(reply).some((name) => !Object.hasOwn(selected, name)))
|
|
176
|
+
return null;
|
|
177
|
+
return reply;
|
|
178
|
+
}));
|
|
179
|
+
response = replies.some((reply) => reply === null)
|
|
180
|
+
? null
|
|
181
|
+
: Object.assign({}, ...replies);
|
|
162
182
|
}
|
|
163
183
|
catch {
|
|
164
184
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../src/fetch/package.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,EAA0B,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAA6B,MAAM,eAAe,CAAC;AAE1E;;2EAE2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,IAAI,IAAI,CAAC;IACnB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE;IACP,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACjB,GACL;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CAuDzE;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,WAAW,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnC,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC5B;AAED,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../src/fetch/package.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,EAA0B,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAA6B,MAAM,eAAe,CAAC;AAE1E;;2EAE2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,IAAI,IAAI,CAAC;IACnB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE;IACP,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACjB,GACL;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CAuDzE;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,WAAW,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnC,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC5B;AAED,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAgJxB"}
|
package/dist/fetch/package.js
CHANGED
|
@@ -72,7 +72,8 @@ export function createFetchOperation(options = {}) {
|
|
|
72
72
|
* Callers resolve the version first; this function never guesses.
|
|
73
73
|
*/
|
|
74
74
|
export async function fetchAndCommitSource(options) {
|
|
75
|
-
const { store,
|
|
75
|
+
const { store, txn, name, version } = options;
|
|
76
|
+
const registry = options.registry.forPackage(name);
|
|
76
77
|
const label = `${name}@${version}`;
|
|
77
78
|
// ---- 1. Metadata: exact version entry for the resolved identity ----
|
|
78
79
|
options.operation?.checkpoint();
|
package/dist/fetch/registry.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export interface Packument {
|
|
|
48
48
|
modified?: string;
|
|
49
49
|
}
|
|
50
50
|
export interface RegistryClientOptions {
|
|
51
|
+
scopedRegistries?: Readonly<Record<string, string>>;
|
|
51
52
|
/** Registry base URL; must be http(s). Trailing slash optional. */
|
|
52
53
|
registry: string;
|
|
53
54
|
userAgent: string;
|
|
@@ -83,6 +84,10 @@ export declare class RegistryClient {
|
|
|
83
84
|
/** Credential-free registry origin; participates in source identity (§8.1). */
|
|
84
85
|
get origin(): string;
|
|
85
86
|
get registry(): string;
|
|
87
|
+
/** Routing always follows the real package name, including npm aliases. */
|
|
88
|
+
forPackage(name: string): RegistryClient;
|
|
89
|
+
originFor(name: string): string;
|
|
90
|
+
get scopedRegistries(): Readonly<Record<string, string>>;
|
|
86
91
|
/** Fetch the (abbreviated) packument for an exact package name. */
|
|
87
92
|
packument(name: string, options?: {
|
|
88
93
|
signal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/fetch/registry.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/fetch/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/fetch/registry.js
CHANGED
|
@@ -22,6 +22,8 @@ export const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
|
22
22
|
/** npm's abbreviated packument media type, used for installs. */
|
|
23
23
|
const INSTALL_V1_ACCEPT = "application/vnd.npm.install-v1+json";
|
|
24
24
|
export class RegistryClient {
|
|
25
|
+
#routeBoundaries = [];
|
|
26
|
+
#scopedClients = new Map();
|
|
25
27
|
#registry;
|
|
26
28
|
#origin;
|
|
27
29
|
#userAgent;
|
|
@@ -30,7 +32,7 @@ export class RegistryClient {
|
|
|
30
32
|
#maxTarballBytes;
|
|
31
33
|
#maxMetadataBytes;
|
|
32
34
|
#maxTotalMetadataBytes;
|
|
33
|
-
#
|
|
35
|
+
#metadataBudget = { bytes: 0 };
|
|
34
36
|
#maxMetadataVersions;
|
|
35
37
|
#requestDeadlineMs;
|
|
36
38
|
#signal;
|
|
@@ -59,13 +61,51 @@ export class RegistryClient {
|
|
|
59
61
|
base.timeout = options.timeoutMs;
|
|
60
62
|
const parsed = safeTransportOptions(options.fetchOptions ?? {}, registry);
|
|
61
63
|
this.#fetchOptions = { ...base, ...parsed.transport, strictSSL: true };
|
|
62
|
-
|
|
64
|
+
const boundary = new URL(registry);
|
|
65
|
+
this.#credentials = parsed.credentials.filter(({ scope }) => scope.origin === boundary.origin &&
|
|
66
|
+
(scope.pathname.startsWith(boundary.pathname) ||
|
|
67
|
+
boundary.pathname.startsWith(scope.pathname)));
|
|
63
68
|
this.#maxMetadataBytes = positiveLimit(options.maxMetadataBytes ?? 32 * 1024 * 1024, "maxMetadataBytes");
|
|
64
69
|
this.#maxTotalMetadataBytes = positiveLimit(options.maxTotalMetadataBytes ?? 128 * 1024 * 1024, "maxTotalMetadataBytes");
|
|
65
70
|
this.#maxMetadataVersions = positiveLimit(options.maxMetadataVersions ?? 100_000, "maxMetadataVersions");
|
|
66
71
|
this.#requestDeadlineMs = positiveLimit(options.requestDeadlineMs ?? 120_000, "requestDeadlineMs");
|
|
67
72
|
positiveLimit(this.#maxTarballBytes, "maxTarballBytes");
|
|
68
73
|
this.#signal = options.signal;
|
|
74
|
+
const { scopedRegistries, ...baseOptions } = options;
|
|
75
|
+
for (const [scope, selected] of Object.entries(scopedRegistries ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
|
|
76
|
+
if (!/^@[a-z0-9][a-z0-9._-]*$/.test(scope))
|
|
77
|
+
throw new ZyrkError("ZYRK_POLICY_DENIED", "Invalid scoped registry name.", { remedy: "Use an npm @scope registry setting." });
|
|
78
|
+
const { headers, ...childFetchOptions } = options.fetchOptions ?? {};
|
|
79
|
+
const client = new RegistryClient({
|
|
80
|
+
...baseOptions,
|
|
81
|
+
fetchOptions: childFetchOptions,
|
|
82
|
+
registry: selected,
|
|
83
|
+
});
|
|
84
|
+
// Raw Authorization headers are scoped once to the original registry.
|
|
85
|
+
// Cloning must never reinterpret them as credentials for a child host.
|
|
86
|
+
for (const authorization of Object.values((headers ?? {}))) {
|
|
87
|
+
const originalScope = new URL(registry), childScope = new URL(client.registry);
|
|
88
|
+
if (originalScope.origin === childScope.origin &&
|
|
89
|
+
(originalScope.pathname.startsWith(childScope.pathname) ||
|
|
90
|
+
childScope.pathname.startsWith(originalScope.pathname)))
|
|
91
|
+
client.#credentials.push({ scope: originalScope, authorization });
|
|
92
|
+
}
|
|
93
|
+
client.#credentials.sort((a, b) => b.scope.pathname.length - a.scope.pathname.length);
|
|
94
|
+
client.#metadataBudget = this.#metadataBudget;
|
|
95
|
+
this.#scopedClients.set(scope, client);
|
|
96
|
+
}
|
|
97
|
+
// A containing public registry must not borrow a credential belonging to a
|
|
98
|
+
// narrower configured private registry on the same host. Explicit broad
|
|
99
|
+
// credentials retain their declared scope, including use by child routes.
|
|
100
|
+
const clients = [this, ...this.#scopedClients.values()];
|
|
101
|
+
const boundaries = clients.map((client) => new URL(client.registry));
|
|
102
|
+
for (const client of clients) {
|
|
103
|
+
const own = new URL(client.registry);
|
|
104
|
+
client.#routeBoundaries = boundaries;
|
|
105
|
+
client.#credentials = client.#credentials.filter(({ scope }) => !boundaries.some((boundary) => boundary.origin === scope.origin &&
|
|
106
|
+
boundary.pathname.length > own.pathname.length &&
|
|
107
|
+
scope.pathname.startsWith(boundary.pathname)));
|
|
108
|
+
}
|
|
69
109
|
}
|
|
70
110
|
/** Credential-free registry origin; participates in source identity (§8.1). */
|
|
71
111
|
get origin() {
|
|
@@ -74,9 +114,24 @@ export class RegistryClient {
|
|
|
74
114
|
get registry() {
|
|
75
115
|
return this.#registry;
|
|
76
116
|
}
|
|
117
|
+
/** Routing always follows the real package name, including npm aliases. */
|
|
118
|
+
forPackage(name) {
|
|
119
|
+
validateRegistryPackageName(name);
|
|
120
|
+
return name.startsWith("@")
|
|
121
|
+
? (this.#scopedClients.get(name.slice(0, name.indexOf("/"))) ?? this)
|
|
122
|
+
: this;
|
|
123
|
+
}
|
|
124
|
+
originFor(name) {
|
|
125
|
+
return this.forPackage(name).origin;
|
|
126
|
+
}
|
|
127
|
+
get scopedRegistries() {
|
|
128
|
+
return Object.fromEntries([...this.#scopedClients].map(([scope, client]) => [scope, client.origin]));
|
|
129
|
+
}
|
|
77
130
|
/** Fetch the (abbreviated) packument for an exact package name. */
|
|
78
131
|
async packument(name, options = {}) {
|
|
79
|
-
|
|
132
|
+
const selected = this.forPackage(name);
|
|
133
|
+
if (selected !== this)
|
|
134
|
+
return selected.packument(name, options);
|
|
80
135
|
const url = `${this.#registry}${escapePackageName(name)}`;
|
|
81
136
|
this.#logger?.debug("fetching packument", { name, url });
|
|
82
137
|
let raw;
|
|
@@ -96,6 +151,14 @@ export class RegistryClient {
|
|
|
96
151
|
versions.some((version) => typeof version !== "string"))
|
|
97
152
|
throw new ZyrkError("ZYRK_POLICY_DENIED", "Invalid registry advisory package versions.");
|
|
98
153
|
}
|
|
154
|
+
const routes = new Set(Object.keys(packages).map((name) => this.forPackage(name)));
|
|
155
|
+
if (routes.size > 1)
|
|
156
|
+
throw new ZyrkError("ZYRK_POLICY_DENIED", "Mixed-registry audit requests must be partitioned by registry.", {
|
|
157
|
+
remedy: "Audit each selected registry independently so private names are never disclosed across origins.",
|
|
158
|
+
});
|
|
159
|
+
const selected = [...routes][0];
|
|
160
|
+
if (selected && selected !== this)
|
|
161
|
+
return selected.auditAdvisories(packages, options);
|
|
99
162
|
const body = JSON.stringify(packages);
|
|
100
163
|
if (Buffer.byteLength(body) > this.#maxMetadataBytes)
|
|
101
164
|
throw resourceLimit("advisory request bytes", this.#maxMetadataBytes, "maxMetadataBytes");
|
|
@@ -107,10 +170,10 @@ export class RegistryClient {
|
|
|
107
170
|
}
|
|
108
171
|
}
|
|
109
172
|
#countMetadataBytes(bytes, responseBytes) {
|
|
110
|
-
this.#
|
|
173
|
+
this.#metadataBudget.bytes += bytes;
|
|
111
174
|
if (responseBytes > this.#maxMetadataBytes)
|
|
112
175
|
throw resourceLimit("metadata bytes", this.#maxMetadataBytes, "maxMetadataBytes");
|
|
113
|
-
if (this.#
|
|
176
|
+
if (this.#metadataBudget.bytes > this.#maxTotalMetadataBytes)
|
|
114
177
|
throw resourceLimit("total metadata bytes", this.#maxTotalMetadataBytes, "maxTotalMetadataBytes");
|
|
115
178
|
}
|
|
116
179
|
async #readMetadata(response) {
|
|
@@ -226,6 +289,15 @@ export class RegistryClient {
|
|
|
226
289
|
throw mapFetchError(error, `download tarball for ${options.label}`);
|
|
227
290
|
}
|
|
228
291
|
}
|
|
292
|
+
#insideAdvisoryBoundary(url) {
|
|
293
|
+
const registryScope = new URL(this.#registry);
|
|
294
|
+
return (url.origin === registryScope.origin &&
|
|
295
|
+
url.pathname.startsWith(registryScope.pathname) &&
|
|
296
|
+
!/%(?:2f|5c|2e)/i.test(url.pathname) &&
|
|
297
|
+
!this.#routeBoundaries.some((boundary) => boundary.origin === url.origin &&
|
|
298
|
+
boundary.pathname.length > registryScope.pathname.length &&
|
|
299
|
+
url.pathname.startsWith(boundary.pathname)));
|
|
300
|
+
}
|
|
229
301
|
async #withRequest(url, accept, consume, signal, request = {}) {
|
|
230
302
|
const controller = new AbortController();
|
|
231
303
|
let active;
|
|
@@ -246,6 +318,10 @@ export class RegistryClient {
|
|
|
246
318
|
cancel();
|
|
247
319
|
try {
|
|
248
320
|
let current = parseTarballUrl(url, "registry request");
|
|
321
|
+
if (request.method === "POST" && !this.#insideAdvisoryBoundary(current))
|
|
322
|
+
throw new ZyrkError("ZYRK_FETCH_REFUSED", "Registry advisory endpoint overlaps another configured registry or has an ambiguous path.", {
|
|
323
|
+
remedy: "Use a distinct advisory endpoint within the selected registry's own route.",
|
|
324
|
+
});
|
|
249
325
|
let crossedBoundary = false;
|
|
250
326
|
for (let hop = 0; hop <= 10; hop++) {
|
|
251
327
|
if (controller.signal.aborted)
|
|
@@ -317,10 +393,8 @@ export class RegistryClient {
|
|
|
317
393
|
if (current.protocol === "https:" && next.protocol !== "https:")
|
|
318
394
|
throw new ZyrkError("ZYRK_FETCH_REFUSED", "Registry HTTPS downgrade redirect refused.", { remedy: "Use HTTPS for every registry redirect." });
|
|
319
395
|
if (request.method === "POST") {
|
|
320
|
-
const registryScope = new URL(this.#registry);
|
|
321
396
|
if (![307, 308].includes(active.status) ||
|
|
322
|
-
next
|
|
323
|
-
!next.pathname.startsWith(registryScope.pathname))
|
|
397
|
+
!this.#insideAdvisoryBoundary(next))
|
|
324
398
|
throw new ZyrkError("ZYRK_FETCH_REFUSED", "Registry advisory redirect crosses its authorized endpoint boundary or changes POST semantics.", {
|
|
325
399
|
remedy: "Configure the advisory service directly within the selected registry; private package names are never forwarded outside that boundary.",
|
|
326
400
|
});
|
|
@@ -421,8 +495,8 @@ export function validateRegistryPackageName(name) {
|
|
|
421
495
|
// eslint-disable-next-line no-control-regex
|
|
422
496
|
if (/[\u0000-\u0020\u007f]/.test(name))
|
|
423
497
|
bad("control or whitespace character");
|
|
424
|
-
if (!/^(?:@[
|
|
425
|
-
bad("
|
|
498
|
+
if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(name))
|
|
499
|
+
bad("name must use supported lowercase npm package characters");
|
|
426
500
|
for (const segment of name.split("/")) {
|
|
427
501
|
if (segment === "." || segment === "..")
|
|
428
502
|
bad("path-like segment");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NpmLockDoc } from "../lockfile/types.ts";
|
|
2
2
|
import type { WorkspaceProject } from "./workspaces.ts";
|
|
3
3
|
import type { WorkspaceGraph } from "./workspace-graph.ts";
|
|
4
|
-
|
|
4
|
+
/** Import stays pure while live-node scope refinement converges. */
|
|
5
|
+
export declare function importNpmWorkspaceLock(doc: NpmLockDoc, project: WorkspaceProject, origin: string | ((name: string) => string)): WorkspaceGraph;
|
|
5
6
|
//# sourceMappingURL=import-npm-workspace.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-npm-workspace.d.ts","sourceRoot":"","sources":["../../src/install/import-npm-workspace.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"import-npm-workspace.d.ts","sourceRoot":"","sources":["../../src/install/import-npm-workspace.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAQvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,sBAAsB,CAAC;AA0V9B,oEAAoE;AACpE,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAC1C,cAAc,CAWhB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Translate npm's physical virtual tree without selecting new registry versions. */
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { workspaceOverrides, refineWorkspaceOverride, WorkspaceOverrideRestart, } from "./workspace-overrides.js";
|
|
3
4
|
import { satisfies } from "semver";
|
|
4
5
|
import { validateLockDoc } from "../lockfile/read.js";
|
|
5
6
|
import { sha512Hex } from "../util/hash.js";
|
|
@@ -8,7 +9,7 @@ import { importNpmLock } from "./import-npm.js";
|
|
|
8
9
|
import { lockMismatch, cleanUrl } from "./lock.js";
|
|
9
10
|
import { ZyrkError } from "../errors.js";
|
|
10
11
|
import { assertWorkspacePlatform } from "./workspace-graph.js";
|
|
11
|
-
|
|
12
|
+
function importNpmWorkspaceLockPass(doc, project, origin, overrideState) {
|
|
12
13
|
if (!doc ||
|
|
13
14
|
typeof doc !== "object" ||
|
|
14
15
|
Array.isArray(doc) ||
|
|
@@ -62,9 +63,11 @@ export function importNpmWorkspaceLock(doc, project, origin) {
|
|
|
62
63
|
if (doc.zyrk ||
|
|
63
64
|
![2, 3].includes(doc.lockfileVersion ?? 0) ||
|
|
64
65
|
!doc.packages ||
|
|
65
|
-
!cleanUrl(origin))
|
|
66
|
+
(typeof origin === "string" && !cleanUrl(origin)))
|
|
66
67
|
throw lockMismatch("workspace adoption needs an unannotated npm v2/v3 lock");
|
|
67
68
|
const packages = doc.packages;
|
|
69
|
+
const overrides = overrideState.policy;
|
|
70
|
+
const contexts = new Map(overrideState.contexts);
|
|
68
71
|
const graph = { nodes: [], importers: {}, omissions: [] };
|
|
69
72
|
const locations = new Map();
|
|
70
73
|
const local = new Map(project.importers.map((i) => [i.id === "." ? "" : i.id, i]));
|
|
@@ -131,7 +134,10 @@ export function importNpmWorkspaceLock(doc, project, origin) {
|
|
|
131
134
|
}
|
|
132
135
|
};
|
|
133
136
|
const populated = new Set();
|
|
134
|
-
const load = (location, name, spec, from) => {
|
|
137
|
+
const load = (location, name, spec, from, parentContext) => {
|
|
138
|
+
const selectedOverride = overrides.select(parentContext, name, spec);
|
|
139
|
+
spec = selectedOverride.spec;
|
|
140
|
+
const contextKey = `${location}\0${selectedOverride.context.id}`;
|
|
135
141
|
const entry = packages[location];
|
|
136
142
|
if (entry.link === true) {
|
|
137
143
|
const target = locations.get(entry.resolved);
|
|
@@ -143,6 +149,7 @@ export function importNpmWorkspaceLock(doc, project, origin) {
|
|
|
143
149
|
(!["*", "^", "~"].includes(range) &&
|
|
144
150
|
!satisfies(target.version, range)))
|
|
145
151
|
throw lockMismatch("npm workspace link disagrees with requirement");
|
|
152
|
+
refineWorkspaceOverride(overrideState.contexts, target.key, selectedOverride.context);
|
|
146
153
|
return target;
|
|
147
154
|
}
|
|
148
155
|
// Reuse the strict single-selection adapter's archive, alias, source and
|
|
@@ -155,12 +162,12 @@ export function importNpmWorkspaceLock(doc, project, origin) {
|
|
|
155
162
|
lockfileVersion: 3,
|
|
156
163
|
packages: { "": {}, [`node_modules/${name}`]: shallow },
|
|
157
164
|
}, { [name]: spec }, origin).nodes[0];
|
|
158
|
-
let node = locations.get(
|
|
165
|
+
let node = locations.get(contextKey);
|
|
159
166
|
if (!node) {
|
|
160
167
|
const { peerBindings: _bindings, ...source } = selected;
|
|
161
168
|
node = {
|
|
162
169
|
...source,
|
|
163
|
-
key: `${selected.name}@${selected.version}#${sha512Hex(location).slice(0, 16)}`,
|
|
170
|
+
key: `${selected.name}@${selected.version}#${sha512Hex(selectedOverride.context.id ? contextKey : location).slice(0, 16)}`,
|
|
164
171
|
kind: "registry",
|
|
165
172
|
edges: [],
|
|
166
173
|
peers: {},
|
|
@@ -168,25 +175,26 @@ export function importNpmWorkspaceLock(doc, project, origin) {
|
|
|
168
175
|
};
|
|
169
176
|
assertWorkspacePlatform({ nodes: [node], importers: {}, omissions: [] });
|
|
170
177
|
graph.nodes.push(node);
|
|
171
|
-
locations.set(
|
|
178
|
+
locations.set(contextKey, node);
|
|
179
|
+
contexts.set(node.key, selectedOverride.context);
|
|
172
180
|
}
|
|
173
181
|
try {
|
|
174
182
|
populate(location, node);
|
|
175
183
|
}
|
|
176
184
|
catch (cause) {
|
|
177
|
-
populated.delete(
|
|
178
|
-
locations.delete(
|
|
185
|
+
populated.delete(node.key);
|
|
186
|
+
locations.delete(contextKey);
|
|
179
187
|
graph.nodes = graph.nodes.filter((n) => n !== node);
|
|
180
188
|
throw cause;
|
|
181
189
|
}
|
|
182
190
|
return node;
|
|
183
191
|
};
|
|
184
192
|
const populate = (location, node) => {
|
|
185
|
-
if (populated.has(
|
|
193
|
+
if (populated.has(node.key))
|
|
186
194
|
return;
|
|
187
195
|
if (populated.size >= 10000)
|
|
188
196
|
throw lockMismatch("npm import resource limit exceeded");
|
|
189
|
-
populated.add(
|
|
197
|
+
populated.add(node.key);
|
|
190
198
|
const entry = packages[location];
|
|
191
199
|
const requirements = new Map();
|
|
192
200
|
for (const [field, kind] of [
|
|
@@ -227,7 +235,7 @@ export function importNpmWorkspaceLock(doc, project, origin) {
|
|
|
227
235
|
}
|
|
228
236
|
let target;
|
|
229
237
|
try {
|
|
230
|
-
target = load(targetLocation, name, requirement.spec, location);
|
|
238
|
+
target = load(targetLocation, name, requirement.spec, location, contexts.get(node.key));
|
|
231
239
|
}
|
|
232
240
|
catch (cause) {
|
|
233
241
|
if (requirement.kind === "optional" &&
|
|
@@ -259,7 +267,7 @@ export function importNpmWorkspaceLock(doc, project, origin) {
|
|
|
259
267
|
node.peers[name] = null;
|
|
260
268
|
continue;
|
|
261
269
|
}
|
|
262
|
-
const target = load(targetLocation, name, spec, location);
|
|
270
|
+
const target = load(targetLocation, name, spec, location, contexts.get(node.key));
|
|
263
271
|
node.peers[name] = target.key;
|
|
264
272
|
if (!node.edges.some((edge) => edge.name === name))
|
|
265
273
|
node.edges.push({ name, target: target.key, kind: "peer", spec });
|
|
@@ -281,3 +289,17 @@ export function importNpmWorkspaceLock(doc, project, origin) {
|
|
|
281
289
|
graph.nodes.sort((a, b) => a.key.localeCompare(b.key));
|
|
282
290
|
return graph;
|
|
283
291
|
}
|
|
292
|
+
/** Import stays pure while live-node scope refinement converges. */
|
|
293
|
+
export function importNpmWorkspaceLock(doc, project, origin) {
|
|
294
|
+
const state = workspaceOverrides(project), expires = Date.now() + 30000;
|
|
295
|
+
for (let round = 0; round < 128 && Date.now() < expires; round++) {
|
|
296
|
+
try {
|
|
297
|
+
return importNpmWorkspaceLockPass(doc, project, origin, state);
|
|
298
|
+
}
|
|
299
|
+
catch (cause) {
|
|
300
|
+
if (!(cause instanceof WorkspaceOverrideRestart))
|
|
301
|
+
throw cause;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
throw lockMismatch("npm workspace override import resource limit exceeded");
|
|
305
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
/** Adopt npm v2/v3 exact selections without asking the registry to pick versions. */
|
|
2
|
+
import { type OverridePolicy } from "../resolve/overrides.ts";
|
|
1
3
|
import type { NpmLockDoc } from "../lockfile/types.ts";
|
|
2
4
|
import type { FinalizedGraph } from "../resolve/resolver.ts";
|
|
3
|
-
export declare function importNpmLock(doc: NpmLockDoc, requirements: Record<string, string>, origin: string): FinalizedGraph;
|
|
5
|
+
export declare function importNpmLock(doc: NpmLockDoc, requirements: Record<string, string>, origin: string | ((name: string) => string), overrides?: OverridePolicy): FinalizedGraph;
|
|
4
6
|
//# sourceMappingURL=import-npm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-npm.d.ts","sourceRoot":"","sources":["../../src/install/import-npm.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"import-npm.d.ts","sourceRoot":"","sources":["../../src/install/import-npm.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,wBAAwB,CAAC;AAKhC,wBAAgB,aAAa,CAC3B,GAAG,EAAE,UAAU,EACf,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,EAC3C,SAAS,GAAE,cAAwC,GAClD,cAAc,CAqLhB"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/** Adopt npm v2/v3 exact selections without asking the registry to pick versions. */
|
|
2
|
+
import { createRootOverrides, } from "../resolve/overrides.js";
|
|
2
3
|
import { validateLockDoc } from "../lockfile/read.js";
|
|
3
4
|
import { canonicalStrongSri } from "../util/integrity.js";
|
|
4
5
|
import { valid, satisfies } from "semver";
|
|
5
6
|
import { parseRequirement } from "../resolve/spec.js";
|
|
6
7
|
import { sourceIdOf, sha512Hex } from "../util/hash.js";
|
|
7
8
|
import { cleanUrl, lockMismatch, packageName } from "./lock.js";
|
|
8
|
-
export function importNpmLock(doc, requirements, origin) {
|
|
9
|
+
export function importNpmLock(doc, requirements, origin, overrides = createRootOverrides({})) {
|
|
9
10
|
validateLockDoc(doc, "package-lock.json");
|
|
10
11
|
const packages = doc.packages;
|
|
11
12
|
if (!packages || !packages[""])
|
|
@@ -26,12 +27,16 @@ export function importNpmLock(doc, requirements, origin) {
|
|
|
26
27
|
: "";
|
|
27
28
|
}
|
|
28
29
|
};
|
|
29
|
-
const load = (location, edgeName, spec) => {
|
|
30
|
-
const
|
|
30
|
+
const load = (location, edgeName, spec, parentContext) => {
|
|
31
|
+
const selected = overrides.select(parentContext, edgeName, spec);
|
|
32
|
+
spec = selected.spec;
|
|
33
|
+
const contextKey = `${location}\0${selected.context.id}`;
|
|
34
|
+
const existing = locations.get(contextKey);
|
|
31
35
|
const entry = packages[location];
|
|
32
36
|
if (!entry || entry.link === true || entry.inBundle === true)
|
|
33
37
|
throw lockMismatch("npm lock contains an unsupported linked or bundled selection");
|
|
34
38
|
const request = parseRequirement(edgeName, spec);
|
|
39
|
+
const selectedOrigin = typeof origin === "string" ? origin : origin(request.realName);
|
|
35
40
|
const name = typeof entry.name === "string" ? entry.name : request.realName;
|
|
36
41
|
const integrity = typeof entry.integrity === "string"
|
|
37
42
|
? canonicalStrongSri(entry.integrity)
|
|
@@ -52,16 +57,16 @@ export function importNpmLock(doc, requirements, origin) {
|
|
|
52
57
|
if (existing)
|
|
53
58
|
return existing;
|
|
54
59
|
const node = {
|
|
55
|
-
key: `${name}@${entry.version}#${sha512Hex(location).slice(0, 16)}`,
|
|
60
|
+
key: `${name}@${entry.version}#${sha512Hex(selected.context.id ? contextKey : location).slice(0, 16)}`,
|
|
56
61
|
name,
|
|
57
62
|
version: entry.version,
|
|
58
|
-
origin,
|
|
63
|
+
origin: selectedOrigin,
|
|
59
64
|
integrity,
|
|
60
65
|
sourceId: sourceIdOf({
|
|
61
66
|
sourceType: "registry",
|
|
62
67
|
name,
|
|
63
68
|
version: entry.version,
|
|
64
|
-
origin,
|
|
69
|
+
origin: selectedOrigin,
|
|
65
70
|
integrity,
|
|
66
71
|
}),
|
|
67
72
|
tarballUrl: entry.resolved,
|
|
@@ -95,7 +100,7 @@ export function importNpmLock(doc, requirements, origin) {
|
|
|
95
100
|
node.platform[field] = value;
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
|
-
locations.set(
|
|
103
|
+
locations.set(contextKey, node);
|
|
99
104
|
const dependencies = Object.create(null);
|
|
100
105
|
for (const [group, kind] of [
|
|
101
106
|
["dependencies", "prod"],
|
|
@@ -127,7 +132,7 @@ export function importNpmLock(doc, requirements, origin) {
|
|
|
127
132
|
}
|
|
128
133
|
throw lockMismatch("npm lock is missing a required dependency");
|
|
129
134
|
}
|
|
130
|
-
const target = load(targetLocation, dep, requirement.spec);
|
|
135
|
+
const target = load(targetLocation, dep, requirement.spec, selected.context);
|
|
131
136
|
node.edges.push({
|
|
132
137
|
name: dep,
|
|
133
138
|
target: target.key,
|
|
@@ -147,7 +152,7 @@ export function importNpmLock(doc, requirements, origin) {
|
|
|
147
152
|
const location = find("", name);
|
|
148
153
|
if (!location)
|
|
149
154
|
throw lockMismatch("npm lock is missing a direct selection");
|
|
150
|
-
return { name, target: load(location, name, spec).key };
|
|
155
|
+
return { name, target: load(location, name, spec, overrides.root).key };
|
|
151
156
|
});
|
|
152
157
|
return {
|
|
153
158
|
nodes: [...locations.values()].sort((a, b) => a.key.localeCompare(b.key)),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type InstalledRegistryConfig = {
|
|
2
|
+
registry: string;
|
|
3
|
+
registries?: Readonly<Record<string, string>>;
|
|
4
|
+
};
|
|
5
|
+
export declare function installedRegistryConfig(state: Record<string, unknown>, nodes: readonly {
|
|
6
|
+
name: string;
|
|
7
|
+
origin: string;
|
|
8
|
+
}[]): InstalledRegistryConfig;
|
|
9
|
+
//# sourceMappingURL=registry-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-policy.d.ts","sourceRoot":"","sources":["../../src/install/registry-policy.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC/C,CAAC;AACF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,GACjD,uBAAuB,CAqCzB"}
|