zyrk 0.5.2 → 0.5.4

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
@@ -362,3 +362,13 @@ version-qualified selectors, self rules (`.`), and `$direct` references. Direct
362
362
  dependency overrides must match the declared specification. Workspace member
363
363
  overrides are ignored, matching npm root-only policy. Override rule order is
364
364
  recorded in the manifest fingerprint; frozen installs reject policy drift.
365
+
366
+ `zyrk help` and `zyrk --help` both show help successfully. Installs report
367
+ preparation, resolution, verified source counts, realization, approved builds,
368
+ and project linking on stderr. Interactive terminals show an activity spinner;
369
+ redirected output contains plain phase messages. `--json` disables progress.
370
+
371
+ Install resolution has a five-minute deadline; each archive extraction has a
372
+ two-minute deadline. Set `ZYRK_RESOLVE_TIMEOUT_MS` or `ZYRK_EXTRACT_TIMEOUT_MS`
373
+ to an integer from `1` to `900000` milliseconds to adjust these timeouts.
374
+ Graph, archive-entry, and byte limits still apply.
package/VERSION CHANGED
@@ -1 +1 @@
1
- v0.5.2
1
+ v0.5.4
@@ -30,6 +30,7 @@ export type RunCliOptions = {
30
30
  };
31
31
  stderr: {
32
32
  write(chunk: string): unknown;
33
+ isTTY?: boolean;
33
34
  };
34
35
  readVersion?: () => string;
35
36
  cwd?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAe,SAAS,EAAe,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,OAAO,EAAoB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAM3C,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AA2DzC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwBhD;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA6EF;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,GACnD,SAAS,CAgDX;AAmBD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,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,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;CACxC,CAAC;AAEF,gFAAgF;AAChF,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAqZpE"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAe,SAAS,EAAe,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,OAAO,EAAoB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAM3C,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AA2DzC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwBhD;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA6EF;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,GACnD,SAAS,CAgDX;AAmBD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,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,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;CACxC,CAAC;AAEF,gFAAgF;AAChF,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA4ZpE"}
package/dist/cli/main.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createInstallProgress } from "./progress.js";
1
2
  import { runMaintenanceCommand } from "../commands/maintenance.js";
2
3
  import { runDeployCommand } from "../commands/deploy.js";
3
4
  import { runPatchCommand } from "../commands/patch.js";
@@ -379,6 +380,7 @@ export async function runCli(options) {
379
380
  (parsed.command === "install" || parsed.command === "i")) {
380
381
  if (isGlobalInstallRequest(parsed.command, parsed.commandArgs, options.env))
381
382
  return emit(fail(globalInstallError(parsed.command), parsed));
383
+ const progress = createInstallProgress(options.stderr, parsed.json);
382
384
  try {
383
385
  if (parsed.commandArgs.includes("--help") ||
384
386
  parsed.commandArgs.includes("-h")) {
@@ -413,6 +415,7 @@ export async function runCli(options) {
413
415
  else
414
416
  specs.push(token);
415
417
  }
418
+ progress.update({ phase: "planning" });
416
419
  const cwd = options.cwd ?? process.cwd();
417
420
  const tools = options.tools
418
421
  ? await options.tools()
@@ -429,12 +432,14 @@ export async function runCli(options) {
429
432
  }),
430
433
  };
431
434
  const result = await installProject({
435
+ onProgress: progress.update,
432
436
  ...flags,
433
437
  env: options.env,
434
438
  specs,
435
439
  projectDir: cwd,
436
440
  ...tools,
437
441
  });
442
+ progress.close();
438
443
  if (parsed.json)
439
444
  return emit({
440
445
  exitCode: 0,
@@ -451,8 +456,12 @@ export async function runCli(options) {
451
456
  });
452
457
  }
453
458
  catch (error) {
459
+ progress.close();
454
460
  return emit(fail(toZyrkError(error, "zyrk install failed."), parsed));
455
461
  }
462
+ finally {
463
+ progress.close();
464
+ }
456
465
  }
457
466
  if (!parsed.help &&
458
467
  !parsed.version &&
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/cli/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAkD7D;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAOT"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/cli/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAmD7D;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAOT"}
package/dist/cli/parse.js CHANGED
@@ -45,6 +45,8 @@ export function parseArgs(argv) {
45
45
  }
46
46
  if (parsed.command === undefined && !token.startsWith("-")) {
47
47
  parsed.command = token;
48
+ if (token === "help")
49
+ parsed.help = true;
48
50
  commandIndex = index;
49
51
  return;
50
52
  }
@@ -0,0 +1,10 @@
1
+ import type { InstallProgress } from "../install/run.ts";
2
+ /** Presentation only: never writes to stdout or exposes package URLs or paths. */
3
+ export declare function createInstallProgress(stream: {
4
+ write(chunk: string): unknown;
5
+ isTTY?: boolean;
6
+ }, quiet: boolean): {
7
+ update(event: InstallProgress): void;
8
+ close(): void;
9
+ };
10
+ //# sourceMappingURL=progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/cli/progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAWzD,kFAAkF;AAClF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE;IAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC1D,KAAK,EAAE,OAAO,GACb;IAAE,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CAuCzD"}
@@ -0,0 +1,52 @@
1
+ const labels = {
2
+ planning: "Preparing installation",
3
+ resolving: "Resolving dependencies",
4
+ fetching: "Verifying package sources",
5
+ realizing: "Preparing shared realizations",
6
+ building: "Preparing approved private builds",
7
+ linking: "Linking project dependencies",
8
+ };
9
+ /** Presentation only: never writes to stdout or exposes package URLs or paths. */
10
+ export function createInstallProgress(stream, quiet) {
11
+ let timer;
12
+ let current = "", last = "", frame = 0, closed = false;
13
+ const write = (text) => {
14
+ try {
15
+ stream.write(text);
16
+ }
17
+ catch {
18
+ /* A display failure cannot alter installation. */
19
+ }
20
+ };
21
+ const render = () => write(`\r\u001b[2K${["|", "/", "-", "\\"][frame++ % 4]} ${current}`);
22
+ return {
23
+ update(event) {
24
+ if (quiet || closed)
25
+ return;
26
+ current =
27
+ labels[event.phase] +
28
+ (event.total === undefined
29
+ ? ""
30
+ : ` (${event.completed ?? 0}/${event.total})`);
31
+ if (stream.isTTY) {
32
+ render();
33
+ if (!timer) {
34
+ timer = setInterval(render, 100);
35
+ timer.unref();
36
+ }
37
+ }
38
+ else if (current !== last)
39
+ write(`zyrk: ${current}\n`);
40
+ last = current;
41
+ },
42
+ close() {
43
+ if (closed)
44
+ return;
45
+ closed = true;
46
+ if (timer)
47
+ clearInterval(timer);
48
+ if (!quiet && stream.isTTY && current)
49
+ write("\r\u001b[2K");
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,7 @@
1
+ import type { ResolverLimits } from "../resolve/resolver.ts";
2
+ import type { ExtractionLimits } from "../extract/tarball.ts";
3
+ export declare function installBudgets(env: NodeJS.ProcessEnv): {
4
+ resolveBudget: Partial<ResolverLimits>;
5
+ extractBudget: Partial<ExtractionLimits>;
6
+ };
7
+ //# sourceMappingURL=budgets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budgets.d.ts","sourceRoot":"","sources":["../../src/install/budgets.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAwB9D,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG;IACtD,aAAa,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACvC,aAAa,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC1C,CASA"}
@@ -0,0 +1,25 @@
1
+ /** Public, bounded install timeout configuration. Node and byte limits stay intact. */
2
+ import { ZyrkError } from "../errors.js";
3
+ function timeout(env, key, fallback) {
4
+ const raw = env[key];
5
+ if (raw === undefined)
6
+ return fallback;
7
+ if (!/^[0-9]+$/.test(raw) ||
8
+ !Number.isSafeInteger(Number(raw)) ||
9
+ Number(raw) < 1 ||
10
+ Number(raw) > 900000)
11
+ throw new ZyrkError("ZYRK_POLICY_DENIED", `Cannot configure install: ${key} must be an integer from 1 to 900000.`, {
12
+ remedy: `Set ${key} to a timeout in milliseconds from 1 to 900000, or unset it to use the default.`,
13
+ });
14
+ return Number(raw);
15
+ }
16
+ export function installBudgets(env) {
17
+ return {
18
+ resolveBudget: {
19
+ deadlineMs: timeout(env, "ZYRK_RESOLVE_TIMEOUT_MS", 300000),
20
+ },
21
+ extractBudget: {
22
+ maxDurationMs: timeout(env, "ZYRK_EXTRACT_TIMEOUT_MS", 120000),
23
+ },
24
+ };
25
+ }
@@ -1 +1 @@
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
+ {"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,CAyMhB"}
@@ -2,7 +2,7 @@
2
2
  import { createRootOverrides, } from "../resolve/overrides.js";
3
3
  import { validateLockDoc } from "../lockfile/read.js";
4
4
  import { canonicalStrongSri } from "../util/integrity.js";
5
- import { valid, satisfies } from "semver";
5
+ import { valid, validRange, satisfies } from "semver";
6
6
  import { parseRequirement } from "../resolve/spec.js";
7
7
  import { sourceIdOf, sha512Hex } from "../util/hash.js";
8
8
  import { cleanUrl, lockMismatch, packageName } from "./lock.js";
@@ -76,12 +76,28 @@ export function importNpmLock(doc, requirements, origin, overrides = createRootO
76
76
  peerBindings: [],
77
77
  };
78
78
  if (entry.engines !== undefined) {
79
- if (typeof entry.engines !== "object" ||
80
- entry.engines === null ||
81
- Array.isArray(entry.engines) ||
82
- Object.values(entry.engines).some((value) => typeof value !== "string"))
83
- throw lockMismatch("invalid npm engine constraint");
84
- node.engines = entry.engines;
79
+ if (Array.isArray(entry.engines)) {
80
+ // Old npm manifests used ["node >= 0.2.0"] instead of an engine map.
81
+ const engines = {};
82
+ for (const value of entry.engines) {
83
+ const match = typeof value === "string"
84
+ ? /^([a-z][a-z0-9._-]*)\s+(.+)$/.exec(value)
85
+ : null;
86
+ if (!match ||
87
+ Object.hasOwn(engines, match[1]) ||
88
+ validRange(match[2]) === null)
89
+ throw lockMismatch("invalid npm engine constraint");
90
+ engines[match[1]] = match[2];
91
+ }
92
+ node.engines = engines;
93
+ }
94
+ else {
95
+ if (typeof entry.engines !== "object" ||
96
+ entry.engines === null ||
97
+ Object.values(entry.engines).some((value) => typeof value !== "string"))
98
+ throw lockMismatch("invalid npm engine constraint");
99
+ node.engines = entry.engines;
100
+ }
85
101
  }
86
102
  if (typeof entry.bin === "object" &&
87
103
  entry.bin !== null &&
@@ -1 +1 @@
1
- {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/install/lock.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAKvD,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,wBAAwB,CAAC;AAKhC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMjD;AACD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAa/C;AACD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CASnD;AAID,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,cAAc,CA6M3D;AAED,kFAAkF;AAClF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAK,GAAG,IAAI,CAmC3E"}
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/install/lock.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAKvD,OAAO,KAAK,EACV,cAAc,EAEf,MAAM,wBAAwB,CAAC;AAKhC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMjD;AACD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAa/C;AACD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CASnD;AAID,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,cAAc,CA6M3D;AAUD,kFAAkF;AAClF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAK,GAAG,IAAI,CAM3E"}
@@ -201,10 +201,20 @@ export function lockedGraph(doc) {
201
201
  }
202
202
  return { nodes, roots };
203
203
  }
204
+ // npm publishes this canonical GitHub sponsorship link in funding metadata.
205
+ // Keep source/archive URL validation query-free, and allow no other query here.
206
+ function githubFundingUrl(value) {
207
+ return /^https:\/\/github\.com\/[A-Za-z0-9_-]+\/[A-Za-z0-9_.-]+\?sponsor=1$/.test(value);
208
+ }
204
209
  /** Preserved metadata is subject to the same portability boundary as new data. */
205
210
  export function assertPortableLockMetadata(value, field = "") {
211
+ visitPortableLockMetadata(value, field, field === "funding" ? "entry" : undefined);
212
+ }
213
+ function visitPortableLockMetadata(value, field, funding) {
206
214
  if (typeof value === "string") {
207
- if (/^(?:https?:|file:|git\+|ssh:)/i.test(value) && !cleanUrl(value))
215
+ if (/^(?:https?:|file:|git\+|ssh:)/i.test(value) &&
216
+ !cleanUrl(value) &&
217
+ !(funding && githubFundingUrl(value)))
208
218
  throw lockMismatch("portable metadata contains an unsafe URL");
209
219
  if (value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value))
210
220
  throw lockMismatch("portable metadata contains a local path");
@@ -212,7 +222,7 @@ export function assertPortableLockMetadata(value, field = "") {
212
222
  }
213
223
  if (Array.isArray(value)) {
214
224
  for (const item of value)
215
- assertPortableLockMetadata(item, field);
225
+ visitPortableLockMetadata(item, field, funding === "entry" ? "entry" : undefined);
216
226
  return;
217
227
  }
218
228
  if (value && typeof value === "object")
@@ -234,6 +244,10 @@ export function assertPortableLockMetadata(value, field = "") {
234
244
  ".",
235
245
  ].includes(field))
236
246
  throw lockMismatch("portable metadata contains a credential field");
237
- assertPortableLockMetadata(item, key);
247
+ visitPortableLockMetadata(item, key, key === "funding"
248
+ ? "entry"
249
+ : funding === "entry" && key === "url"
250
+ ? "url"
251
+ : undefined);
238
252
  }
239
253
  }
@@ -1,7 +1,15 @@
1
1
  import { type BuildCandidate } from "./private.ts";
2
2
  import { RegistryClient } from "../fetch/registry.ts";
3
+ import type { ResolverLimits } from "../resolve/resolver.ts";
4
+ import type { ExtractionLimits } from "../extract/tarball.ts";
3
5
  import { Store } from "../store/layout.ts";
6
+ export type InstallProgress = {
7
+ phase: "planning" | "resolving" | "fetching" | "realizing" | "building" | "linking";
8
+ completed?: number;
9
+ total?: number;
10
+ };
4
11
  export type InstallOptions = {
12
+ onProgress?: (event: InstallProgress) => void;
5
13
  projectDir: string;
6
14
  store: Store;
7
15
  registry: RegistryClient;
@@ -26,6 +34,8 @@ export type InstallOptions = {
26
34
  approveBuilds?: readonly string[];
27
35
  rebuild?: boolean;
28
36
  repair?: boolean;
37
+ resolveBudget?: Partial<ResolverLimits>;
38
+ extractBudget?: Partial<ExtractionLimits>;
29
39
  fetchBudget?: {
30
40
  maxArchiveBytes?: number;
31
41
  maxExpandedBytes?: number;
@@ -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;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"}
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;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAgC3C,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EACD,UAAU,GACV,WAAW,GACX,UAAU,GACV,WAAW,GACX,UAAU,GACV,SAAS,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AACF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC9C,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,aAAa,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC1C,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,CAs0BxB"}
@@ -15,6 +15,7 @@ import npa from "npm-package-arg";
15
15
  import { satisfies } from "semver";
16
16
  import { ZyrkError } from "../errors.js";
17
17
  import { RegistryClient } from "../fetch/registry.js";
18
+ import { installBudgets } from "./budgets.js";
18
19
  import { extractTarball } from "../extract/tarball.js";
19
20
  import { commitExtractedSource } from "../store/source.js";
20
21
  import { Store } from "../store/layout.js";
@@ -93,6 +94,13 @@ function scriptPolicy(manifest) {
93
94
  return object(manifest.zyrk) ? manifest.zyrk.scripts : undefined;
94
95
  }
95
96
  export async function installProject(options) {
97
+ options.onProgress?.({ phase: "planning" });
98
+ const budgets = installBudgets(options.env ?? process.env);
99
+ options = {
100
+ ...options,
101
+ resolveBudget: { ...budgets.resolveBudget, ...options.resolveBudget },
102
+ extractBudget: { ...budgets.extractBudget, ...options.extractBudget },
103
+ };
96
104
  if (await findWorkspaceRoot(options.projectDir)) {
97
105
  const { installWorkspaceProject } = await import("./workspace-install.js");
98
106
  return installWorkspaceProject(options);
@@ -248,8 +256,10 @@ export async function installProject(options) {
248
256
  throw error("ZYRK_OFFLINE_MISSING", "Offline installation requires an existing compatible zyrk lockfile.");
249
257
  const prior = existing?.zyrk ? lockedGraph(existing) : undefined;
250
258
  const fresh = new Set(specs.map((spec) => npa(spec).name));
259
+ options.onProgress?.({ phase: "resolving" });
251
260
  resolved = await resolveRequirements({
252
261
  overrides,
262
+ limits: options.resolveBudget,
253
263
  provider: {
254
264
  origin: options.registry.origin,
255
265
  originFor: (name) => options.registry.originFor(name),
@@ -460,10 +470,21 @@ export async function installProject(options) {
460
470
  })
461
471
  : undefined;
462
472
  const unique = new Map(activeNodes.map((n) => [n.sourceId, n]));
473
+ let completedSources = 0;
474
+ options.onProgress?.({
475
+ phase: "fetching",
476
+ completed: 0,
477
+ total: unique.size,
478
+ });
463
479
  for (const node of unique.values()) {
464
480
  operation.operation.checkpoint();
465
481
  if (await pathExists(sourceManifestPath(options.store, node.sourceId))) {
466
482
  await readSourceManifest(options.store, node.sourceId);
483
+ options.onProgress?.({
484
+ phase: "fetching",
485
+ completed: ++completedSources,
486
+ total: unique.size,
487
+ });
467
488
  continue;
468
489
  }
469
490
  if (options.offline)
@@ -480,6 +501,7 @@ export async function installProject(options) {
480
501
  label: `${node.name}@${node.version}`,
481
502
  });
482
503
  const extracted = await extractTarball({
504
+ limits: options.extractBudget,
483
505
  signal: operation.operation.signal,
484
506
  onExpandedBytes: operation.operation.consumeExpandedBytes,
485
507
  tarballPath: tarball,
@@ -497,6 +519,11 @@ export async function installProject(options) {
497
519
  if (source.sourceId !== node.sourceId)
498
520
  throw lockMismatch("download identity disagrees with the locked source");
499
521
  await rm(tarball, { force: true });
522
+ options.onProgress?.({
523
+ phase: "fetching",
524
+ completed: ++completedSources,
525
+ total: unique.size,
526
+ });
500
527
  }
501
528
  for (const node of unique.values())
502
529
  assertSupportedSourcePayload(node, await readSourceManifest(options.store, node.sourceId));
@@ -509,6 +536,7 @@ export async function installProject(options) {
509
536
  warnings,
510
537
  };
511
538
  const graph = createGraphDoc(activeNodes.map((n) => ({ ...n, deps: n.edges })));
539
+ options.onProgress?.({ phase: "realizing" });
512
540
  const sharedRealized = await realizeGraph({
513
541
  store: options.store,
514
542
  graph,
@@ -581,6 +609,7 @@ export async function installProject(options) {
581
609
  });
582
610
  privateGraph = true;
583
611
  }
612
+ options.onProgress?.({ phase: "building" });
584
613
  await buildPrivate({
585
614
  project,
586
615
  generationId,
@@ -667,6 +696,7 @@ export async function installProject(options) {
667
696
  roots: selectedRoots.map((r) => r.target),
668
697
  readiness,
669
698
  });
699
+ options.onProgress?.({ phase: "linking" });
670
700
  const published = await installProjection(project, entries, {
671
701
  generationId,
672
702
  lockHeld: true,
@@ -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;AAyH9D,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA6pBxB"}
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,CA+qBxB"}
@@ -161,6 +161,7 @@ export async function installWorkspaceProject(options) {
161
161
  if (!graph) {
162
162
  if (options.frozen || options.offline)
163
163
  throw lockMismatch("offline/frozen workspace install needs a compatible lock");
164
+ options.onProgress?.({ phase: "resolving" });
164
165
  graph = await resolveWorkspaceGraph(project, {
165
166
  origin: options.registry.origin,
166
167
  originFor: (name) => options.registry.originFor(name),
@@ -170,6 +171,7 @@ export async function installWorkspaceProject(options) {
170
171
  }, {
171
172
  ...(existing ? { previous: existing.graph } : {}),
172
173
  fresh: mutation.fresh,
174
+ ...options.resolveBudget,
173
175
  });
174
176
  }
175
177
  assertWorkspacePlatform(graph);
@@ -261,7 +263,14 @@ export async function installWorkspaceProject(options) {
261
263
  offline: options.offline === true,
262
264
  })
263
265
  : undefined;
264
- for (const node of new Map(nodes.map((n) => [n.sourceId, n])).values()) {
266
+ const uniqueSources = new Map(nodes.map((n) => [n.sourceId, n]));
267
+ let completedSources = 0;
268
+ options.onProgress?.({
269
+ phase: "fetching",
270
+ completed: 0,
271
+ total: uniqueSources.size,
272
+ });
273
+ for (const node of uniqueSources.values()) {
265
274
  operation.operation.checkpoint();
266
275
  if (!(await pathExists(sourceManifestPath(options.store, node.sourceId)))) {
267
276
  if (options.offline)
@@ -279,6 +288,7 @@ export async function installWorkspaceProject(options) {
279
288
  onBytes: operation.operation.consumeArchiveBytes,
280
289
  });
281
290
  const extraction = await extractTarball({
291
+ limits: options.extractBudget,
282
292
  tarballPath: archive,
283
293
  destDir: path.join(txn.dir, node.sourceId),
284
294
  signal: operation.operation.signal,
@@ -298,6 +308,11 @@ export async function installWorkspaceProject(options) {
298
308
  await rm(archive, { force: true });
299
309
  }
300
310
  assertSupportedSourcePayload(node, await readSourceManifest(options.store, node.sourceId));
311
+ options.onProgress?.({
312
+ phase: "fetching",
313
+ completed: ++completedSources,
314
+ total: uniqueSources.size,
315
+ });
301
316
  }
302
317
  if (options.fetchOnly)
303
318
  return {
@@ -306,6 +321,7 @@ export async function installWorkspaceProject(options) {
306
321
  reused: 0,
307
322
  warnings,
308
323
  };
324
+ options.onProgress?.({ phase: "realizing" });
309
325
  const shared = await realizeGraph({
310
326
  store: options.store,
311
327
  graph: pureGraph,
@@ -443,6 +459,7 @@ export async function installWorkspaceProject(options) {
443
459
  });
444
460
  // Approved writes need a coherent fully private registry closure.
445
461
  await allocatePrivate(new Set(nodes.map((n) => n.key)));
462
+ options.onProgress?.({ phase: "building" });
446
463
  await buildPrivate({
447
464
  project: root,
448
465
  generationId,
@@ -498,6 +515,7 @@ export async function installWorkspaceProject(options) {
498
515
  bins[name] = actual;
499
516
  }
500
517
  }
518
+ options.onProgress?.({ phase: "linking" });
501
519
  await installProjection(holder, node.edges.map((e) => ({
502
520
  name: e.name,
503
521
  linkTarget: realized.packageDirByKey.get(e.target),
@@ -1 +1 @@
1
- {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../src/resolve/spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,eAAe,CAAC;IACtB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,UAAU,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;CACvC,CAAC;AAcF;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,iBAAiB,CAqFnB"}
1
+ {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../src/resolve/spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,eAAe,CAAC;IACtB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,UAAU,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;CACvC,CAAC;AAcF;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,iBAAiB,CA6FnB"}
@@ -7,6 +7,7 @@
7
7
  * explicitly — they are never handed to a tool that might run scripts.
8
8
  */
9
9
  import npa from "npm-package-arg";
10
+ import { SemVer } from "semver";
10
11
  import { ZyrkError } from "../errors.js";
11
12
  function unsupported(what, spec) {
12
13
  return new ZyrkError("ZYRK_RESOLUTION_FAILED", `unsupported dependency protocol in "${spec}" (${what})`, {
@@ -46,7 +47,13 @@ export function parseRequirement(edgeName, spec) {
46
47
  edgeName,
47
48
  realName: parsed.name,
48
49
  kind: "exact",
49
- targetSpec: parsed.fetchSpec,
50
+ // npm accepts = and v prefixes for exact versions. SemVer.format()
51
+ // omits build metadata, so append it explicitly for registry lookup.
52
+ targetSpec: (() => {
53
+ const version = new SemVer(parsed.fetchSpec.replace(/^[=v\s]+/, ""));
54
+ return (version.version +
55
+ (version.build.length ? `+${version.build.join(".")}` : ""));
56
+ })(),
50
57
  targetKind: "exact",
51
58
  };
52
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zyrk",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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",