zyrk 0.5.1 → 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 CHANGED
@@ -119,7 +119,7 @@ scheme, host, port, and path; redirects cannot expand that scope. Use
119
119
  Scoped registries route metadata, exact sources, imports and tool commands by
120
120
  the real package name, including aliases. Advisory requests are partitioned by
121
121
  registry; private names never fall back to a public endpoint. Disabled TLS
122
- verification is rejected before lookup. Git sources, non-workspace local sources, and overrides remain unsupported;
122
+ verification is rejected before lookup. Git sources and non-workspace local sources remain unsupported;
123
123
  they fail explicitly. Optional dependencies incompatible with the selected platform
124
124
  are omitted; missing packages, integrity failures, and other optional failures
125
125
  remain errors. Platform selection is recorded in the lock. macOS, Windows, and
@@ -356,3 +356,9 @@ Keep existing data until an explicit migration is supported.
356
356
 
357
357
  [MIT](LICENSE). If you want to support development,
358
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
1
+ v0.5.2
@@ -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
+ /** Import stays pure while live-node scope refinement converges. */
4
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":"AAGA,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;AAE9B,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,CAyThB"}
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
- export function importNpmWorkspaceLock(doc, project, origin) {
12
+ function importNpmWorkspaceLockPass(doc, project, origin, overrideState) {
12
13
  if (!doc ||
13
14
  typeof doc !== "object" ||
14
15
  Array.isArray(doc) ||
@@ -65,6 +66,8 @@ export function importNpmWorkspaceLock(doc, project, origin) {
65
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(location);
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(location, node);
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(location);
178
- locations.delete(location);
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(location))
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(location);
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 | ((name: string) => 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":"AAIA,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,GAC1C,cAAc,CA4KhB"}
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,8 +27,11 @@ export function importNpmLock(doc, requirements, origin) {
26
27
  : "";
27
28
  }
28
29
  };
29
- const load = (location, edgeName, spec) => {
30
- const existing = locations.get(location);
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");
@@ -53,7 +57,7 @@ export function importNpmLock(doc, requirements, origin) {
53
57
  if (existing)
54
58
  return existing;
55
59
  const node = {
56
- key: `${name}@${entry.version}#${sha512Hex(location).slice(0, 16)}`,
60
+ key: `${name}@${entry.version}#${sha512Hex(selected.context.id ? contextKey : location).slice(0, 16)}`,
57
61
  name,
58
62
  version: entry.version,
59
63
  origin: selectedOrigin,
@@ -96,7 +100,7 @@ export function importNpmLock(doc, requirements, origin) {
96
100
  node.platform[field] = value;
97
101
  }
98
102
  }
99
- locations.set(location, node);
103
+ locations.set(contextKey, node);
100
104
  const dependencies = Object.create(null);
101
105
  for (const [group, kind] of [
102
106
  ["dependencies", "prod"],
@@ -128,7 +132,7 @@ export function importNpmLock(doc, requirements, origin) {
128
132
  }
129
133
  throw lockMismatch("npm lock is missing a required dependency");
130
134
  }
131
- const target = load(targetLocation, dep, requirement.spec);
135
+ const target = load(targetLocation, dep, requirement.spec, selected.context);
132
136
  node.edges.push({
133
137
  name: dep,
134
138
  target: target.key,
@@ -148,7 +152,7 @@ export function importNpmLock(doc, requirements, origin) {
148
152
  const location = find("", name);
149
153
  if (!location)
150
154
  throw lockMismatch("npm lock is missing a direct selection");
151
- return { name, target: load(location, name, spec).key };
155
+ return { name, target: load(location, name, spec, overrides.root).key };
152
156
  });
153
157
  return {
154
158
  nodes: [...locations.values()].sort((a, b) => a.key.localeCompare(b.key)),
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/install/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAWtB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAgC3C,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE;QACZ,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH,CAAC;AACF,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EACL,OAAO,GACP,uBAAuB,GACvB,eAAe,GACf,SAAS,GACT,cAAc,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;CAC3B,CAAC;AA6GF,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAsxBxB"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/install/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAYtB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAgC3C,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE;QACZ,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH,CAAC;AACF,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EACL,OAAO,GACP,uBAAuB,GACvB,eAAe,GACf,SAAS,GACT,cAAc,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;CAC3B,CAAC;AA4GF,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAyyBxB"}
@@ -6,6 +6,7 @@ import { candidates, candidateDigest, approveCandidate, assertApproved, material
6
6
  import { loadProjectPatches, applyProjectPatches } from "../commands/patch.js";
7
7
  import { registerGeneration } from "./metadata.js";
8
8
  import { randomId } from "../util/ids.js";
9
+ import { createRootOverrides } from "../resolve/overrides.js";
9
10
  import { importNpmLock } from "./import-npm.js";
10
11
  /** Native project install: resolve -> verify -> realize -> journaled publication. */
11
12
  import { readFile, realpath, lstat, mkdir, rm } from "node:fs/promises";
@@ -65,10 +66,9 @@ async function readManifest(project) {
65
66
  throw error("ZYRK_MANIFEST_INVALID", `Project ${group} must contain valid package names and string specifiers.`);
66
67
  }
67
68
  if (doc.workspaces !== undefined ||
68
- doc.overrides !== undefined ||
69
69
  doc.bundledDependencies !== undefined ||
70
70
  doc.bundleDependencies !== undefined)
71
- throw error("ZYRK_NOT_IMPLEMENTED", "Project workspaces, overrides, and bundled dependencies are not supported by install yet.");
71
+ throw error("ZYRK_NOT_IMPLEMENTED", "Project workspaces and bundled dependencies are not supported by install yet.");
72
72
  if (doc.zyrk !== undefined &&
73
73
  (!object(doc.zyrk) ||
74
74
  Object.keys(doc.zyrk).some((key) => !["scripts", "audit", "patches", "mutablePackages"].includes(key)) ||
@@ -98,6 +98,13 @@ export async function installProject(options) {
98
98
  return installWorkspaceProject(options);
99
99
  }
100
100
  const project = await realpath(options.projectDir);
101
+ const preliminaryManifest = (await readManifest(project)).doc;
102
+ const preliminaryOverrides = createRootOverrides(preliminaryManifest);
103
+ if (options.importNpm) {
104
+ const preliminaryLock = (await readLockfile(path.join(project, "package-lock.json")))?.doc;
105
+ if (preliminaryLock && !preliminaryLock.zyrk)
106
+ importNpmLock(preliminaryLock, requirements(preliminaryManifest), (name) => options.registry.originFor(name), preliminaryOverrides);
107
+ }
101
108
  // A local control-directory link would redirect locks and recovery writes.
102
109
  const local = path.join(project, PROJECT_STATE_DIR);
103
110
  try {
@@ -189,6 +196,7 @@ export async function installProject(options) {
189
196
  if (other !== group && manifest[other])
190
197
  delete manifest[other][parsed.name];
191
198
  }
199
+ const overrides = createRootOverrides(manifest);
192
200
  const allDeps = requirements(manifest);
193
201
  for (const [name, spec] of Object.entries(allDeps))
194
202
  parseRequirement(name, spec);
@@ -215,7 +223,7 @@ export async function installProject(options) {
215
223
  if (existing && !existing.zyrk) {
216
224
  if (options.frozen || options.offline || specs.length)
217
225
  throw lockMismatch("npm-only lockfile requires a non-frozen adoption install before adding packages");
218
- resolved = importNpmLock(existing, allDeps, (name) => options.registry.originFor(name));
226
+ resolved = importNpmLock(existing, allDeps, (name) => options.registry.originFor(name), overrides);
219
227
  }
220
228
  if (existing?.zyrk &&
221
229
  specs.length === 0 &&
@@ -241,6 +249,7 @@ export async function installProject(options) {
241
249
  const prior = existing?.zyrk ? lockedGraph(existing) : undefined;
242
250
  const fresh = new Set(specs.map((spec) => npa(spec).name));
243
251
  resolved = await resolveRequirements({
252
+ overrides,
244
253
  provider: {
245
254
  origin: options.registry.origin,
246
255
  originFor: (name) => options.registry.originFor(name),
@@ -46,17 +46,23 @@ export type WorkspaceGraph = {
46
46
  reason: "platform";
47
47
  }>;
48
48
  };
49
- export declare function resolveWorkspaceGraph(project: WorkspaceProject, provider: MetadataProvider, options?: {
49
+ declare function resolveWorkspaceGraphPass(project: WorkspaceProject, provider: MetadataProvider, options?: {
50
50
  previous?: WorkspaceGraph;
51
51
  fresh?: ReadonlySet<string>;
52
52
  platform?: RuntimePlatform;
53
53
  maxNodes?: number;
54
54
  maxDepth?: number;
55
55
  deadlineMs?: number;
56
+ }, overrideState?: {
57
+ policy: import("../resolve/overrides.ts").OverridePolicy;
58
+ contexts: Map<string, import("../resolve/overrides.ts").OverrideContext>;
56
59
  }): Promise<WorkspaceGraph>;
60
+ /** Retry pure planning when a registry edge narrows a live importer's scope. */
61
+ export declare function resolveWorkspaceGraph(project: WorkspaceProject, provider: MetadataProvider, options?: Parameters<typeof resolveWorkspaceGraphPass>[2]): Promise<WorkspaceGraph>;
57
62
  export declare function workspaceRegistryOccurrences(graph: WorkspaceGraph): ResolvedOccurrence[];
58
63
  /** Rooted semantic context including live source identity; occurrence keys never split equal peers. */
59
64
  export declare function workspaceContextId(graph: WorkspaceGraph, rootKey: string, projectId: string): string;
60
65
  /** Recheck locked platform/runtime requirements as strictly as fresh resolution. */
61
66
  export declare function assertWorkspacePlatform(graph: WorkspaceGraph): void;
67
+ export {};
62
68
  //# sourceMappingURL=workspace-graph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-graph.d.ts","sourceRoot":"","sources":["../../src/install/workspace-graph.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace-graph.d.ts","sourceRoot":"","sources":["../../src/install/workspace-graph.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  import { maxSatisfying, satisfies, valid } from "semver";
3
3
  import { expectedIntegrityFromDist, } from "../fetch/registry.js";
4
4
  import { currentPlatform } from "../resolve/resolver.js";
5
+ import { workspaceOverrides, refineWorkspaceOverride, WorkspaceOverrideRestart, } from "./workspace-overrides.js";
5
6
  import { parseRequirement } from "../resolve/spec.js";
6
7
  import { sourceIdOf, sha512Hex } from "../util/hash.js";
7
8
  import { ZyrkError } from "../errors.js";
@@ -14,7 +15,9 @@ const fail = (reason) => {
14
15
  };
15
16
  class PlatformMismatch extends Error {
16
17
  }
17
- export async function resolveWorkspaceGraph(project, provider, options = {}) {
18
+ async function resolveWorkspaceGraphPass(project, provider, options = {}, overrideState = workspaceOverrides(project)) {
19
+ const overrides = overrideState.policy;
20
+ const overrideContexts = new Map(overrideState.contexts);
18
21
  const graph = { nodes: [], importers: {}, omissions: [] };
19
22
  const byKey = new Map(), manifests = new Map(), chains = new Map();
20
23
  const metadata = new Map();
@@ -83,6 +86,10 @@ export async function resolveWorkspaceGraph(project, provider, options = {}) {
83
86
  const build = async (parent, name, spec, kind, ancestry, explicitLocal) => {
84
87
  check(ancestry.length);
85
88
  try {
89
+ const overridden = overrides.select(overrideContexts.get(parent.key), name, spec);
90
+ if (overridden.spec !== spec)
91
+ explicitLocal = undefined;
92
+ spec = overridden.spec;
86
93
  let local = explicitLocal === undefined
87
94
  ? undefined
88
95
  : project.importers.find((item) => item.id === explicitLocal);
@@ -106,8 +113,11 @@ export async function resolveWorkspaceGraph(project, provider, options = {}) {
106
113
  local = candidate;
107
114
  }
108
115
  }
109
- if (local)
110
- return graph.importers[local.id];
116
+ if (local) {
117
+ const key = graph.importers[local.id];
118
+ refineWorkspaceOverride(overrideState.contexts, key, overridden.context);
119
+ return key;
120
+ }
111
121
  if (!requirement)
112
122
  throw lockMismatch("missing workspace requirement");
113
123
  const doc = await packument(requirement.realName);
@@ -145,7 +155,8 @@ export async function resolveWorkspaceGraph(project, provider, options = {}) {
145
155
  .map((key) => byKey.get(key))
146
156
  .find((node) => node?.kind === "registry" &&
147
157
  node.name === requirement.realName &&
148
- node.version === version);
158
+ node.version === version &&
159
+ overrideContexts.get(node.key)?.id === overridden.context.id);
149
160
  if (ancestor)
150
161
  return ancestor.key;
151
162
  const key = `${requirement.realName}@${version}#${sha512Hex(parent.key + "\0" + name).slice(0, 16)}`;
@@ -184,6 +195,7 @@ export async function resolveWorkspaceGraph(project, provider, options = {}) {
184
195
  optionalPeers: [],
185
196
  };
186
197
  graph.nodes.push(node);
198
+ overrideContexts.set(key, overridden.context);
187
199
  byKey.set(key, node);
188
200
  manifests.set(key, entry);
189
201
  chains.set(key, [...ancestry, parent.key]);
@@ -235,7 +247,8 @@ export async function resolveWorkspaceGraph(project, provider, options = {}) {
235
247
  for (const node of graph.nodes) {
236
248
  check();
237
249
  const manifest = manifests.get(node.key);
238
- for (const [name, range] of Object.entries(manifest.peerDependencies ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
250
+ for (const [name, rawRange] of Object.entries(manifest.peerDependencies ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
251
+ const range = overrides.select(overrideContexts.get(node.key), name, rawRange).spec;
239
252
  const optional = manifest.peerDependenciesMeta?.[name]?.optional === true;
240
253
  if (optional && !node.optionalPeers.includes(name))
241
254
  node.optionalPeers.push(name);
@@ -257,7 +270,7 @@ export async function resolveWorkspaceGraph(project, provider, options = {}) {
257
270
  const selected = byKey.get(target);
258
271
  const requested = range.startsWith("workspace:")
259
272
  ? range.slice(10)
260
- : range;
273
+ : parseRequirement(name, range).targetSpec;
261
274
  if (!["*", "^", "~"].includes(requested) &&
262
275
  !satisfies(selected.version, requested))
263
276
  throw new ZyrkError("ZYRK_PEER_CONFLICT", `Workspace peer ${name} required by ${node.name} conflicts with its consumer.`, { remedy: "Align the consumer and package peer requirements." });
@@ -294,6 +307,36 @@ export async function resolveWorkspaceGraph(project, provider, options = {}) {
294
307
  }
295
308
  return fail("peer binding iteration limit exceeded");
296
309
  }
310
+ /** Retry pure planning when a registry edge narrows a live importer's scope. */
311
+ export async function resolveWorkspaceGraph(project, provider, options = {}) {
312
+ const state = workspaceOverrides(project), expires = Date.now() + (options.deadlineMs ?? 30000);
313
+ const packuments = new Map();
314
+ const cached = {
315
+ ...provider,
316
+ origin: provider.origin,
317
+ ...(provider.originFor
318
+ ? { originFor: (name) => provider.originFor(name) }
319
+ : {}),
320
+ packument: (name) => {
321
+ let pending = packuments.get(name);
322
+ if (!pending) {
323
+ pending = provider.packument(name);
324
+ packuments.set(name, pending);
325
+ }
326
+ return pending;
327
+ },
328
+ };
329
+ for (let round = 0; round < 128 && Date.now() < expires; round++) {
330
+ try {
331
+ return await resolveWorkspaceGraphPass(project, cached, { ...options, deadlineMs: Math.max(1, expires - Date.now()) }, state);
332
+ }
333
+ catch (cause) {
334
+ if (!(cause instanceof WorkspaceOverrideRestart))
335
+ throw cause;
336
+ }
337
+ }
338
+ throw lockMismatch("workspace override planning resource limit exceeded");
339
+ }
297
340
  export function workspaceRegistryOccurrences(graph) {
298
341
  return graph.nodes
299
342
  .filter((node) => node.kind === "registry")
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-install.d.ts","sourceRoot":"","sources":["../../src/install/workspace-install.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAwH9D,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA+pBxB"}
1
+ {"version":3,"file":"workspace-install.d.ts","sourceRoot":"","sources":["../../src/install/workspace-install.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAyH9D,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA6pBxB"}
@@ -4,6 +4,7 @@ import { constants } from "node:fs";
4
4
  import * as path from "node:path";
5
5
  import { discoverWorkspaces, findWorkspaceRoot, assertNoLiveSourceBuilds, } from "./workspaces.js";
6
6
  import { resolveWorkspaceGraph, workspaceRegistryOccurrences, workspaceContextId, assertWorkspacePlatform, } from "./workspace-graph.js";
7
+ import { workspaceOverrides } from "./workspace-overrides.js";
7
8
  import { importNpmWorkspaceLock } from "./import-npm-workspace.js";
8
9
  import { readWorkspaceLock, workspaceLockDoc } from "./workspace-lock.js";
9
10
  import { publishWorkspaceProjection, recoverWorkspaceProjection, workspaceProjectionPaths, } from "./workspace-transaction.js";
@@ -80,6 +81,7 @@ export async function installWorkspaceProject(options) {
80
81
  const root = await findWorkspaceRoot(options.projectDir);
81
82
  if (!root)
82
83
  throw lockMismatch("no authoritative workspace root");
84
+ workspaceOverrides(await discoverWorkspaces(root));
83
85
  // Refuse untranslatable locks before creating control directories or
84
86
  // recovering a previous generation. Revalidate under the project lock below.
85
87
  if (options.importNpm)
@@ -99,10 +101,9 @@ export async function installWorkspaceProject(options) {
99
101
  await readFile(path.join(i.directory, "package.json"), "utf8"),
100
102
  ])));
101
103
  const mutation = await prepareWorkspaceMutation(project, options);
102
- if (manifest.overrides !== undefined ||
103
- manifest.bundledDependencies !== undefined ||
104
+ if (manifest.bundledDependencies !== undefined ||
104
105
  manifest.bundleDependencies !== undefined)
105
- unsupported("Workspace overrides and bundled root dependencies are unsupported.");
106
+ unsupported("Workspace bundled root dependencies are unsupported.");
106
107
  const policy = object(manifest.zyrk) ? manifest.zyrk.scripts : undefined;
107
108
  if (manifest.zyrk !== undefined &&
108
109
  (!object(manifest.zyrk) ||
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-mutation.d.ts","sourceRoot":"","sources":["../../src/install/workspace-mutation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAiB3D,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAW3E;AACD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC;IACT,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IACpC,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CA8ID"}
1
+ {"version":3,"file":"workspace-mutation.d.ts","sourceRoot":"","sources":["../../src/install/workspace-mutation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAiB3D,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAc3E;AACD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC;IACT,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IACpC,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CA8ID"}
@@ -18,7 +18,10 @@ export function refreshWorkspaceFingerprint(project) {
18
18
  member.id,
19
19
  member.manifest.name ?? null,
20
20
  member.manifest.version ?? null,
21
- manifestFingerprint(member.manifest),
21
+ manifestFingerprint({
22
+ ...member.manifest,
23
+ overrides: member.id === "." ? member.manifest.overrides : undefined,
24
+ }),
22
25
  ])));
23
26
  }
24
27
  export async function prepareWorkspaceMutation(project, options) {
@@ -0,0 +1,11 @@
1
+ import { type OverrideContext } from "../resolve/overrides.ts";
2
+ import { type WorkspaceProject } from "./workspaces.ts";
3
+ export declare function workspaceOverrides(project: WorkspaceProject): {
4
+ policy: import("../resolve/overrides.ts").OverridePolicy;
5
+ contexts: Map<string, OverrideContext>;
6
+ };
7
+ /** Internal signal: restart pure planning before any fetch/publication mutation. */
8
+ export declare class WorkspaceOverrideRestart extends Error {
9
+ }
10
+ export declare function refineWorkspaceOverride(contexts: Map<string, OverrideContext>, key: string, incoming: OverrideContext): void;
11
+ //# sourceMappingURL=workspace-overrides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-overrides.d.ts","sourceRoot":"","sources":["../../src/install/workspace-overrides.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAWzB,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB;;;EAuD3D;AACD,oFAAoF;AACpF,qBAAa,wBAAyB,SAAQ,KAAK;CAAG;AACtD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EACtC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,eAAe,GACxB,IAAI,CASN"}
@@ -0,0 +1,67 @@
1
+ /** Live importers have one dependency graph; compatible scopes may narrow it. */
2
+ import { satisfies } from "semver";
3
+ import { createRootOverrides, } from "../resolve/overrides.js";
4
+ import { parseRequirement } from "../resolve/spec.js";
5
+ import { planWorkspaceDependencies, } from "./workspaces.js";
6
+ import { lockMismatch } from "./lock.js";
7
+ function ancestor(outer, inner) {
8
+ for (let current = inner; current; current = current.parent)
9
+ if (current === outer)
10
+ return true;
11
+ return false;
12
+ }
13
+ export function workspaceOverrides(project) {
14
+ const policy = createRootOverrides(project.importers.find((i) => i.id === ".").manifest);
15
+ const contexts = new Map();
16
+ const plans = planWorkspaceDependencies(project);
17
+ for (const importer of project.importers)
18
+ contexts.set(`workspace:${importer.id}`, importer.id === "."
19
+ ? policy.root
20
+ : policy.select(policy.root, importer.manifest.name, importer.manifest.version).context);
21
+ for (let round = 0; round <= 128; round++) {
22
+ let changed = false;
23
+ for (const plan of plans) {
24
+ const parent = contexts.get(`workspace:${plan.importer.id}`);
25
+ for (const edge of plan.dependencies) {
26
+ const selected = policy.select(parent, edge.name, edge.spec);
27
+ const localId = edge.selection.kind === "local" ? edge.selection.importer : undefined;
28
+ let local = project.importers.find((i) => i.id === localId);
29
+ if (selected.spec !== edge.spec) {
30
+ const requirement = parseRequirement(edge.name, selected.spec);
31
+ local = project.importers.find((i) => i.id !== "." &&
32
+ i.manifest.name === requirement.realName &&
33
+ requirement.kind !== "alias" &&
34
+ requirement.targetKind !== "tag" &&
35
+ satisfies(i.manifest.version, requirement.targetSpec));
36
+ }
37
+ if (!local)
38
+ continue;
39
+ const key = `workspace:${local.id}`, current = contexts.get(key);
40
+ if (ancestor(current, selected.context)) {
41
+ if (current !== selected.context) {
42
+ contexts.set(key, selected.context);
43
+ changed = true;
44
+ }
45
+ }
46
+ else if (!ancestor(selected.context, current)) {
47
+ throw lockMismatch("one live workspace cannot represent incompatible scoped override contexts");
48
+ }
49
+ }
50
+ }
51
+ if (!changed)
52
+ return { policy, contexts };
53
+ }
54
+ throw lockMismatch("workspace override propagation resource limit exceeded");
55
+ }
56
+ /** Internal signal: restart pure planning before any fetch/publication mutation. */
57
+ export class WorkspaceOverrideRestart extends Error {
58
+ }
59
+ export function refineWorkspaceOverride(contexts, key, incoming) {
60
+ const current = contexts.get(key);
61
+ if (current === incoming || ancestor(incoming, current))
62
+ return;
63
+ if (!ancestor(current, incoming))
64
+ throw lockMismatch("one live workspace cannot represent incompatible scoped override contexts");
65
+ contexts.set(key, incoming);
66
+ throw new WorkspaceOverrideRestart();
67
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/install/workspaces.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,GAAG;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AACF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,0EAA0E;IAC1E,MAAM,EAAE;QAAE,IAAI,EAAE,gBAAgB,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9E,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AA2GF,kFAAkF;AAClF,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7D,OAAO,CAAC,gBAAgB,CAAC,CA4F3B;AACD,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,EACL;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACnC;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,WAAW,EAAE,iBAAiB,CAAA;KAAE,CAAC;CAC1D,CAAC;AACF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,YAAY,EAAE,mBAAmB,EAAE,CAAC;CACrC,CAAC;AACF,8FAA8F;AAC9F,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gBAAgB,GACxB,qBAAqB,EAAE,CAmFzB;AACD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,gBAAgB,EACzB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,iBAAiB,EAAE,CAerB;AACD,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,MAAM,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjD,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IAC9C,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C,GAAG,IAAI,CA+BP;AAED,sFAAsF;AACtF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,qBAAqB,EAAE,GACrC,MAAM,EAAE,CAoCV;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiC3E"}
1
+ {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/install/workspaces.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,GAAG;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AACF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,0EAA0E;IAC1E,MAAM,EAAE;QAAE,IAAI,EAAE,gBAAgB,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9E,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AA2GF,kFAAkF;AAClF,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7D,OAAO,CAAC,gBAAgB,CAAC,CAgG3B;AACD,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,EACL;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACnC;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,WAAW,EAAE,iBAAiB,CAAA;KAAE,CAAC;CAC1D,CAAC;AACF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,YAAY,EAAE,mBAAmB,EAAE,CAAC;CACrC,CAAC;AACF,8FAA8F;AAC9F,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gBAAgB,GACxB,qBAAqB,EAAE,CAmFzB;AACD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,gBAAgB,EACzB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,iBAAiB,EAAE,CAerB;AACD,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,MAAM,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjD,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IAC9C,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C,GAAG,IAAI,CA+BP;AAED,sFAAsF;AACtF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,qBAAqB,EAAE,GACrC,MAAM,EAAE,CAoCV;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiC3E"}
@@ -164,7 +164,10 @@ export async function discoverWorkspaces(rootDirectory, options = {}) {
164
164
  member.id,
165
165
  member.manifest.name ?? null,
166
166
  member.manifest.version ?? null,
167
- manifestFingerprint(member.manifest),
167
+ manifestFingerprint({
168
+ ...member.manifest,
169
+ overrides: member.id === "." ? member.manifest.overrides : undefined,
170
+ }),
168
171
  ]))),
169
172
  };
170
173
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fingerprints.d.ts","sourceRoot":"","sources":["../../src/lockfile/fingerprints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,MAAM,MAAM,mBAAmB,GAAG;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,CA2BzE;AAED,eAAO,MAAM,qBAAqB;IAAK,OAAO;IAAU,KAAK;IAAM,IAAI;CAAM,CAAC;AAE9E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAG/D;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAalE"}
1
+ {"version":3,"file":"fingerprints.d.ts","sourceRoot":"","sources":["../../src/lockfile/fingerprints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,MAAM,MAAM,mBAAmB,GAAG;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAcF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,CA2BzE;AAED,eAAO,MAAM,qBAAqB;IAAK,OAAO;IAAU,KAAK;IAAM,IAAI;CAAM,CAAC;AAE9E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAG/D;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAalE"}
@@ -16,6 +16,18 @@ import { canonicalJson } from "../util/json.js";
16
16
  function fingerprintOf(value) {
17
17
  return `sha512-${createHash("sha512").update(canonicalJson(value), "utf8").digest("base64")}`;
18
18
  }
19
+ /** npm evaluates sibling override rules in declaration order. Preserve that
20
+ * order inside canonical JSON instead of sorting the policy's object keys. */
21
+ function orderedOverridePolicy(value) {
22
+ if (Array.isArray(value))
23
+ return value.map(orderedOverridePolicy);
24
+ if (value && typeof value === "object")
25
+ return Object.entries(value).map(([key, rule]) => [
26
+ key,
27
+ orderedOverridePolicy(rule),
28
+ ]);
29
+ return value;
30
+ }
19
31
  export function manifestFingerprint(manifest) {
20
32
  const normalized = {};
21
33
  for (const group of [
@@ -40,7 +52,7 @@ export function manifestFingerprint(manifest) {
40
52
  peerDependenciesMeta: manifest.peerDependenciesMeta ?? {},
41
53
  ...(manifest.overrides === undefined
42
54
  ? {}
43
- : { overrides: manifest.overrides }),
55
+ : { overrides: orderedOverridePolicy(manifest.overrides) }),
44
56
  ...(manifest.workspaces === undefined
45
57
  ? {}
46
58
  : { workspaces: manifest.workspaces }),
@@ -0,0 +1,19 @@
1
+ import type { ManifestDeclaration } from "../lockfile/fingerprints.ts";
2
+ export type OverrideContext = {
3
+ readonly id: string;
4
+ readonly name?: string;
5
+ readonly range?: string;
6
+ readonly value?: string;
7
+ readonly parent?: OverrideContext;
8
+ readonly children: readonly OverrideContext[];
9
+ };
10
+ export declare class OverridePolicy {
11
+ readonly root: OverrideContext;
12
+ constructor(manifest: ManifestDeclaration);
13
+ select(context: OverrideContext, name: string, spec: string): {
14
+ spec: string;
15
+ context: OverrideContext;
16
+ };
17
+ }
18
+ export declare function createRootOverrides(manifest: ManifestDeclaration): OverridePolicy;
19
+ //# sourceMappingURL=overrides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../../src/resolve/overrides.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAGvE,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CAC/C,CAAC;AAaF,qBAAa,cAAc;IACzB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,YAAY,QAAQ,EAAE,mBAAmB,EAsFxC;IACD,MAAM,CACJ,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,eAAe,CAAA;KAAE,CA6C5C;CACF;AACD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,mBAAmB,GAC5B,cAAc,CAEhB"}
@@ -0,0 +1,142 @@
1
+ /** Root npm override policy. Parsing never fetches metadata or executes code. */
2
+ import npa from "npm-package-arg";
3
+ import { intersects, validRange } from "semver";
4
+ import { ZyrkError } from "../errors.js";
5
+ import { parseRequirement } from "./spec.js";
6
+ import { packageName } from "../install/lock.js";
7
+ function invalid(reason) {
8
+ throw new ZyrkError("ZYRK_MANIFEST_INVALID", `Cannot apply root dependency overrides (${reason}).`, {
9
+ remedy: "Use root registry override rules, valid nested selectors and references to declared direct dependencies.",
10
+ });
11
+ }
12
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
13
+ export class OverridePolicy {
14
+ root;
15
+ constructor(manifest) {
16
+ const raw = manifest.overrides;
17
+ if (raw !== undefined && !object(raw))
18
+ invalid("policy must be an object");
19
+ // npm's $reference lookup checks dev before optional, production and peer.
20
+ const references = {
21
+ ...manifest.peerDependencies,
22
+ ...manifest.dependencies,
23
+ ...manifest.optionalDependencies,
24
+ ...manifest.devDependencies,
25
+ };
26
+ let count = 0;
27
+ const build = (value, key, parent, depth = 0) => {
28
+ if (++count > 10000 || depth > 128)
29
+ invalid("policy resource limit exceeded");
30
+ if (typeof value === "string")
31
+ value = { ".": value };
32
+ if (!object(value))
33
+ invalid("each rule must be a string or object");
34
+ let name, range, replacement;
35
+ if (key !== undefined) {
36
+ try {
37
+ const spec = npa(key);
38
+ name = spec.name;
39
+ range = spec.rawSpec || "*";
40
+ if (!name ||
41
+ !packageName(name) ||
42
+ !["version", "range"].includes(spec.type) ||
43
+ validRange(range) === null)
44
+ invalid("selector must be a package name and optional semver range");
45
+ }
46
+ catch {
47
+ invalid("invalid registry override selector");
48
+ }
49
+ const self = value["."];
50
+ if (self !== undefined && typeof self !== "string")
51
+ invalid("self replacement must be a string");
52
+ replacement = self === undefined ? range : self || "*";
53
+ if (replacement.startsWith("$")) {
54
+ const reference = replacement.slice(1);
55
+ if (!packageName(reference) || !Object.hasOwn(references, reference))
56
+ invalid("unknown direct dependency reference");
57
+ replacement = references[reference];
58
+ }
59
+ try {
60
+ parseRequirement(name, replacement);
61
+ }
62
+ catch {
63
+ invalid("replacement must use a supported registry source");
64
+ }
65
+ }
66
+ else if (Object.hasOwn(value, "."))
67
+ invalid("root cannot contain a self replacement");
68
+ const children = [];
69
+ const context = {
70
+ id: parent ? `${parent.id}/${key}` : "",
71
+ ...(name ? { name } : {}),
72
+ ...(range ? { range } : {}),
73
+ ...(replacement !== undefined ? { value: replacement } : {}),
74
+ ...(parent ? { parent } : {}),
75
+ children,
76
+ };
77
+ for (const [child, spec] of Object.entries(value))
78
+ if (child !== ".")
79
+ children.push(build(spec, child, context, depth + 1));
80
+ return context;
81
+ };
82
+ this.root = build(raw ?? {}, undefined);
83
+ // Legality applies to the edge selected by zyrk's install groups, which
84
+ // prioritize optional > production > development, independently of refs.
85
+ const direct = {
86
+ ...manifest.peerDependencies,
87
+ ...manifest.devDependencies,
88
+ ...manifest.dependencies,
89
+ ...manifest.optionalDependencies,
90
+ };
91
+ for (const [name, spec] of Object.entries(direct)) {
92
+ const selected = this.select(this.root, name, spec);
93
+ if (selected.spec !== spec)
94
+ invalid("override conflicts with an exact direct dependency spec");
95
+ }
96
+ }
97
+ select(context, name, spec) {
98
+ const seen = new Set();
99
+ for (let ancestor = context; ancestor; ancestor = ancestor.parent) {
100
+ const rules = [
101
+ ...ancestor.children,
102
+ ...(ancestor.parent ? [ancestor] : []),
103
+ ];
104
+ for (const rule of rules) {
105
+ const key = `${rule.name}@${rule.range}`;
106
+ if (seen.has(key))
107
+ continue;
108
+ seen.add(key);
109
+ if (rule.name !== name)
110
+ continue;
111
+ let matches = rule.range === "*";
112
+ if (!matches) {
113
+ try {
114
+ const workspaceRange = spec.startsWith("workspace:")
115
+ ? spec.slice(10)
116
+ : undefined;
117
+ const requested = parseRequirement(name, workspaceRange === undefined
118
+ ? spec
119
+ : ["*", "^", "~"].includes(workspaceRange)
120
+ ? "*"
121
+ : workspaceRange);
122
+ matches =
123
+ requested.targetKind === "tag" ||
124
+ intersects(requested.targetSpec, rule.range);
125
+ }
126
+ catch {
127
+ matches = spec.startsWith("file:") || spec.startsWith("link:");
128
+ }
129
+ }
130
+ if (matches)
131
+ return {
132
+ spec: rule.value === "*" ? spec : rule.value,
133
+ context: rule,
134
+ };
135
+ }
136
+ }
137
+ return { spec, context };
138
+ }
139
+ }
140
+ export function createRootOverrides(manifest) {
141
+ return new OverridePolicy(manifest);
142
+ }
@@ -20,6 +20,7 @@
20
20
  * bindings stay distinct. Peerless occurrences share by name@version.
21
21
  */
22
22
  import { type Packument } from "../fetch/registry.ts";
23
+ import { type OverridePolicy } from "./overrides.ts";
23
24
  export type MetadataProvider = {
24
25
  readonly origin: string;
25
26
  originFor?(name: string): string;
@@ -66,6 +67,7 @@ export type ResolveOptions = {
66
67
  spec: string;
67
68
  optional?: boolean;
68
69
  }>;
70
+ overrides?: OverridePolicy;
69
71
  limits?: Partial<ResolverLimits>;
70
72
  platform?: RuntimePlatform;
71
73
  };
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolve/resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolve/resolver.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,7 @@ import { ZyrkError } from "../errors.js";
24
24
  import { expectedIntegrityFromDist, } from "../fetch/registry.js";
25
25
  import { sha512Hex, sourceIdOf } from "../util/hash.js";
26
26
  import { canonicalJson } from "../util/json.js";
27
+ import { createRootOverrides, } from "./overrides.js";
27
28
  import { parseRequirement } from "./spec.js";
28
29
  export function currentPlatform() {
29
30
  const result = { os: process.platform, cpu: process.arch };
@@ -38,7 +39,11 @@ export function currentPlatform() {
38
39
  }
39
40
  export async function resolveRequirements(options) {
40
41
  const state = new ResolverState(options);
41
- const rootFrame = { label: "<root>", children: [] };
42
+ const rootFrame = {
43
+ label: "<root>",
44
+ children: [],
45
+ overrideContext: state.overrides.root,
46
+ };
42
47
  const seenRoots = new Set();
43
48
  for (const requirement of [...options.requirements].sort((a, b) => compareStrings(a.name, b.name))) {
44
49
  if (seenRoots.has(requirement.name))
@@ -62,6 +67,7 @@ export async function resolveRequirements(options) {
62
67
  }
63
68
  class ResolverState {
64
69
  provider;
70
+ overrides;
65
71
  packuments = new Map();
66
72
  peerless = new Map();
67
73
  peerBearing = new Map();
@@ -74,6 +80,7 @@ class ResolverState {
74
80
  peerExpansions = 0;
75
81
  constructor(options) {
76
82
  this.provider = options.provider;
83
+ this.overrides = options.overrides ?? createRootOverrides({});
77
84
  this.limits = {
78
85
  maxNodes: 10000,
79
86
  maxDepth: 128,
@@ -153,7 +160,11 @@ class ResolverState {
153
160
  }
154
161
  async buildRequiredChild(parentFrame, chain, edgeName, rawSpec, kind) {
155
162
  this.checkBudget();
156
- const req = parseRequirement(edgeName, rawSpec);
163
+ const override = this.overrides.select(parentFrame.overrideContext, edgeName, rawSpec);
164
+ // Root override legality was checked against the declared manifest.
165
+ // A caller may pass an existing exact root pin inside that declaration;
166
+ // applying the identical declared range again would discard the pin.
167
+ const req = parseRequirement(edgeName, parentFrame.label === "<root>" ? rawSpec : override.spec);
157
168
  const { version, entry } = await selectVersion(this, req);
158
169
  this.checkPlatform(req.realName, entry);
159
170
  const integrity = expectedIntegrityFromDist(entry.dist);
@@ -164,9 +175,10 @@ class ResolverState {
164
175
  });
165
176
  }
166
177
  const baseKey = `${req.realName}@${version}`;
178
+ const memoKey = `${baseKey}\0${override.context.id}`;
167
179
  const hasPeers = peerSpecsOf(entry).length > 0;
168
180
  if (!hasPeers) {
169
- const existing = this.peerless.get(baseKey);
181
+ const existing = this.peerless.get(memoKey);
170
182
  if (existing !== undefined)
171
183
  return existing;
172
184
  }
@@ -177,28 +189,37 @@ class ResolverState {
177
189
  this.peerBearing.set(parentFrame, perParent);
178
190
  }
179
191
  else {
180
- const existing = perParent.get(baseKey);
192
+ const existing = perParent.get(memoKey);
181
193
  if (existing !== undefined)
182
194
  return existing;
183
195
  }
184
196
  }
185
197
  this.checkBudget(chain.length, true);
186
198
  const occ = {
199
+ overrideContext: override.context,
187
200
  name: req.realName,
188
201
  version,
189
202
  entry,
190
203
  integrity,
191
204
  children: [],
192
- peerSpecs: peerSpecsOf(entry),
205
+ peerSpecs: peerSpecsOf(entry).map((peer) => ({
206
+ ...peer,
207
+ range: this.overrides.select(override.context, peer.name, peer.range)
208
+ .spec,
209
+ })),
193
210
  peerBindings: new Map(),
194
211
  chain: hasPeers ? [...chain, parentFrame] : [],
195
212
  };
196
213
  if (!hasPeers)
197
- this.peerless.set(baseKey, occ);
214
+ this.peerless.set(memoKey, occ);
198
215
  else
199
- this.peerBearing.get(parentFrame)?.set(baseKey, occ);
216
+ this.peerBearing.get(parentFrame)?.set(memoKey, occ);
200
217
  this.occurrences.push(occ);
201
- const selfFrame = { label: baseKey, children: occ.children };
218
+ const selfFrame = {
219
+ label: baseKey,
220
+ children: occ.children,
221
+ overrideContext: override.context,
222
+ };
202
223
  const childChain = [...chain, parentFrame];
203
224
  for (const dep of regularDepsOf(entry)) {
204
225
  const child = await this.buildChild(selfFrame, childChain, dep.name, dep.spec, dep.kind);
@@ -294,7 +315,8 @@ class ResolverState {
294
315
  const existing = perOcc.get(spec.name);
295
316
  if (existing !== undefined)
296
317
  return existing;
297
- const req = parseRequirement(spec.name, spec.range);
318
+ const override = this.overrides.select(occ.overrideContext, spec.name, spec.range);
319
+ const req = parseRequirement(spec.name, override.spec);
298
320
  const { version, entry } = await selectVersion(this, {
299
321
  realName: req.realName,
300
322
  targetSpec: req.targetSpec,
@@ -310,12 +332,17 @@ class ResolverState {
310
332
  this.checkPlatform(req.realName, entry);
311
333
  this.checkBudget(occ.chain.length + 1, true);
312
334
  const fresh = {
335
+ overrideContext: override.context,
313
336
  name: req.realName,
314
337
  version,
315
338
  entry,
316
339
  integrity,
317
340
  children: [],
318
- peerSpecs: peerSpecsOf(entry),
341
+ peerSpecs: peerSpecsOf(entry).map((peer) => ({
342
+ ...peer,
343
+ range: this.overrides.select(override.context, peer.name, peer.range)
344
+ .spec,
345
+ })),
319
346
  peerBindings: new Map(),
320
347
  chain: [...occ.chain, this.frameOf(occ)],
321
348
  };
@@ -340,7 +367,11 @@ class ResolverState {
340
367
  frameOf(occ) {
341
368
  let frame = this.frames.get(occ);
342
369
  if (frame === undefined) {
343
- frame = { label: `${occ.name}@${occ.version}`, children: occ.children };
370
+ frame = {
371
+ label: `${occ.name}@${occ.version}`,
372
+ children: occ.children,
373
+ overrideContext: occ.overrideContext,
374
+ };
344
375
  this.frames.set(occ, frame);
345
376
  }
346
377
  return frame;
@@ -458,7 +489,10 @@ function lookupPeer(occ, peerName, range) {
458
489
  const matches = level.children.filter((link) => link.edgeName === peerName);
459
490
  if (matches.length === 0)
460
491
  continue;
461
- const satisfying = matches.filter((link) => satisfies(link.child.version, range));
492
+ const requested = parseRequirement(peerName, range);
493
+ const satisfying = matches.filter((link) => link.child.name === requested.realName &&
494
+ (requested.targetKind === "tag" ||
495
+ satisfies(link.child.version, requested.targetSpec)));
462
496
  const first = satisfying[0];
463
497
  if (first !== undefined)
464
498
  return { status: "bound", target: first.child };
@@ -490,10 +524,10 @@ function peerConflict(occ, spec, found) {
490
524
  /** Canonical fingerprint over the transitive peer closure (SPEC 9.4). */
491
525
  function occurrenceKey(occ, check) {
492
526
  const base = `${occ.name}@${occ.version}`;
493
- if (occ.peerSpecs.length === 0)
527
+ if (occ.peerSpecs.length === 0 && !occ.overrideContext.id)
494
528
  return base;
495
529
  const expansion = expandPeerBindings(occ, new Set([occ]), check);
496
- return `${base}#${sha512Hex(canonicalJson(expansion)).slice(0, 12)}`;
530
+ return `${base}#${sha512Hex(canonicalJson(occ.overrideContext.id ? [occ.overrideContext.id, expansion] : expansion)).slice(0, 12)}`;
497
531
  }
498
532
  function expandPeerBindings(occ, visiting, check) {
499
533
  check(visiting.size);
@@ -507,14 +541,24 @@ function expandPeerBindings(occ, visiting, check) {
507
541
  parts.push([spec.name, "absent"]);
508
542
  }
509
543
  else if (visiting.has(bound)) {
510
- parts.push([spec.name, ["ref", `${bound.name}@${bound.version}`]]);
544
+ parts.push([
545
+ spec.name,
546
+ [
547
+ "ref",
548
+ bound.overrideContext.id
549
+ ? [`${bound.name}@${bound.version}`, bound.overrideContext.id]
550
+ : `${bound.name}@${bound.version}`,
551
+ ],
552
+ ]);
511
553
  }
512
554
  else {
513
555
  parts.push([
514
556
  spec.name,
515
557
  [
516
558
  "node",
517
- `${bound.name}@${bound.version}`,
559
+ bound.overrideContext.id
560
+ ? [`${bound.name}@${bound.version}`, bound.overrideContext.id]
561
+ : `${bound.name}@${bound.version}`,
518
562
  expandPeerBindings(bound, new Set([...visiting, bound]), check),
519
563
  ],
520
564
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zyrk",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Storage-efficient Node package manager: one verified store per user, shared across projects through links.",
5
5
  "license": "MIT",
6
6
  "type": "module",