zyrk 0.5.4 → 0.5.6

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.
Files changed (48) hide show
  1. package/README.md +27 -4
  2. package/VERSION +1 -1
  3. package/dist/cli/main.js +18 -2
  4. package/dist/dlx/create.d.ts +5 -2
  5. package/dist/dlx/create.d.ts.map +1 -1
  6. package/dist/dlx/create.js +9 -4
  7. package/dist/dlx/next.d.ts +7 -0
  8. package/dist/dlx/next.d.ts.map +1 -0
  9. package/dist/dlx/next.js +164 -0
  10. package/dist/dlx/run.d.ts +9 -1
  11. package/dist/dlx/run.d.ts.map +1 -1
  12. package/dist/dlx/run.js +29 -5
  13. package/dist/install/import-npm-workspace.d.ts.map +1 -1
  14. package/dist/install/import-npm-workspace.js +1 -3
  15. package/dist/install/import-npm.d.ts.map +1 -1
  16. package/dist/install/import-npm.js +10 -28
  17. package/dist/install/lock.d.ts.map +1 -1
  18. package/dist/install/lock.js +8 -6
  19. package/dist/install/platform.d.ts +5 -0
  20. package/dist/install/platform.d.ts.map +1 -0
  21. package/dist/install/platform.js +97 -0
  22. package/dist/install/run.d.ts +3 -0
  23. package/dist/install/run.d.ts.map +1 -1
  24. package/dist/install/run.js +19 -48
  25. package/dist/install/workspace-graph.d.ts +1 -0
  26. package/dist/install/workspace-graph.d.ts.map +1 -1
  27. package/dist/install/workspace-graph.js +12 -3
  28. package/dist/install/workspace-install.d.ts.map +1 -1
  29. package/dist/install/workspace-install.js +5 -5
  30. package/dist/install/workspace-lock.d.ts +1 -1
  31. package/dist/install/workspace-lock.d.ts.map +1 -1
  32. package/dist/install/workspace-lock.js +11 -1
  33. package/dist/lockfile/fingerprints.js +0 -1
  34. package/dist/lockfile/read.d.ts +1 -1
  35. package/dist/lockfile/read.d.ts.map +1 -1
  36. package/dist/lockfile/read.js +11 -1
  37. package/dist/lockfile/types.d.ts +1 -0
  38. package/dist/lockfile/types.d.ts.map +1 -1
  39. package/dist/lockfile/types.js +1 -0
  40. package/dist/lockfile/write.d.ts.map +1 -1
  41. package/dist/lockfile/write.js +1 -0
  42. package/dist/resolve/resolver.d.ts +1 -21
  43. package/dist/resolve/resolver.d.ts.map +1 -1
  44. package/dist/resolve/resolver.js +13 -1
  45. package/dist/util/engines.d.ts +2 -0
  46. package/dist/util/engines.d.ts.map +1 -0
  47. package/dist/util/engines.js +23 -0
  48. package/package.json +1 -1
package/README.md CHANGED
@@ -122,7 +122,10 @@ registry; private names never fall back to a public endpoint. Disabled TLS
122
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
- remain errors. Platform selection is recorded in the lock. macOS, Windows, and
125
+ remain errors. Locks retain exact platform-qualified optional variants; each machine
126
+ selects its compatible local closure without rewriting the lock or recording store
127
+ paths. Older platform-pruned locks need an intentional non-frozen refresh; incomplete
128
+ npm locks must be freshly resolved before portable adoption. macOS, Windows, and
126
129
  broad framework compatibility have not passed their release gates.
127
130
 
128
131
  ### Workspaces
@@ -164,9 +167,29 @@ JavaScript, shell, or native executable bin entry with the supplied arguments. I
164
167
  removed afterward; verified sources and shared realizations remain in the
165
168
  store.
166
169
 
167
- `create` uses the same execution path and maps initializer names to their
168
- `create-*` packages. For example, `vite` maps to `create-vite`, while a scoped
169
- initializer stays in its scope. Compatibility depends on the selected tool:
170
+ `create` maps initializer names to their `create-*` packages. For example,
171
+ `vite` maps to `create-vite`, while a scoped initializer stays in its scope.
172
+ Initializers run from an independent writable private closure so copied templates
173
+ can be edited without modifying verified store files. The temporary closure is
174
+ removed afterward; generic `dlx` tools continue to use immutable shared files.
175
+
176
+ For `create next-app`, the native adapter accepts resolved create-next-app
177
+ 15.x and 16.x releases, whose official CLI provides `--skip-install`; the real
178
+ scaffold check covers 16.4.2. Other versions fail before execution. zyrk passes the official `--skip-install` flag to
179
+ create-next-app, validates the generated project manifest, then installs its
180
+ dependencies through zyrk using the same store and registry configuration. An
181
+ explicit `--skip-install` leaves the generated project uninstalled. The official
182
+ flag can select create-next-app's configuration defaults instead of prompting
183
+ for each choice; pass its explicit configuration flags to customize them. Its
184
+ interactive project-name prompt remains available. Supply an explicit target
185
+ for nested or existing empty directories; automatic interactive discovery is
186
+ limited to one newly created direct child directory. If generation exits without
187
+ a valid manifest, zyrk reports failure and retains any generated files for review.
188
+
189
+ Other initializers control their own dependency-install step and may invoke
190
+ another package manager. Use their skip-install option, when available, then run
191
+ `zyrk install` in the generated project to keep installation native to zyrk.
192
+ Compatibility depends on the selected tool:
170
193
  closures declaring install lifecycle scripts, implicit native builds, or bundled
171
194
  install hooks are blocked. Dependency executables are available on the tool
172
195
  PATH. Parent cancellation terminates the supervised process group with bounded
package/VERSION CHANGED
@@ -1 +1 @@
1
- v0.5.4
1
+ v0.5.6
package/dist/cli/main.js CHANGED
@@ -13,7 +13,7 @@ import { installRegistry } from "../install/registry.js";
13
13
  import { readFileSync } from "node:fs";
14
14
  import * as path from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
- import { runCreate } from "../dlx/create.js";
16
+ import { runCreate, starterSpecFor } from "../dlx/create.js";
17
17
  import { runDlx } from "../dlx/run.js";
18
18
  import { runDependencyCommand, } from "../commands/dependencies.js";
19
19
  import { runProjectCommand, } from "../commands/project.js";
@@ -610,5 +610,21 @@ async function runToolCommand(command, parsed, options, version) {
610
610
  if (command === "dlx") {
611
611
  return (await runDlx({ ...base, spec: first })).exitCode;
612
612
  }
613
- return (await runCreate({ ...base, input: first })).exitCode;
613
+ const isNext = starterSpecFor(first).startsWith("create-next-app@");
614
+ options.stderr.write(isNext
615
+ ? tail.includes("--skip-install")
616
+ ? "zyrk: Next scaffolding only; dependency installation was explicitly skipped.\n"
617
+ : "zyrk: Next scaffolder runs with --skip-install; zyrk will install dependencies.\n"
618
+ : "zyrk: This initializer controls its own dependency installation.\n");
619
+ const progress = createInstallProgress(options.stderr, false);
620
+ try {
621
+ return (await runCreate({
622
+ ...base,
623
+ input: first,
624
+ onInstallProgress: progress.update,
625
+ })).exitCode;
626
+ }
627
+ finally {
628
+ progress.close();
629
+ }
614
630
  }
@@ -5,13 +5,16 @@
5
5
  * `init` command): `foo` runs `create-foo`, `@scope/bar` runs
6
6
  * `@scope/create-bar`, and a bare `@scope` runs `@scope/create`, each with
7
7
  * the requested version or `*` when none was given. An already-prefixed
8
- * `create-foo` is used as-is. Execution is the dlx flow, so temporary
9
- * environments, trust policy, and cleanup all behave identically.
8
+ * `create-foo` is used as-is. Initializers use writable private temporary
9
+ * closures; trust policy and cleanup match dlx. The Next adapter installs
10
+ * generated dependencies natively after the initializer's skip-install step.
10
11
  */
12
+ import type { InstallOptions } from "../install/run.ts";
11
13
  import { type DlxOptions, type DlxResult } from "./run.ts";
12
14
  export type CreateOptions = Omit<DlxOptions, "spec"> & {
13
15
  /** Initializer as typed: `next-app`, `vite@^5`, `@scope`, `@scope/kit@beta`. */
14
16
  input: string;
17
+ onInstallProgress?: InstallOptions["onProgress"];
15
18
  };
16
19
  /**
17
20
  * Map an initializer to its starter package specifier. Throws ZYRK_USAGE
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/dlx/create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAU,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAEnE,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG;IACrD,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA6BpD;AAWD,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAG1E"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/dlx/create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAkB,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE3E,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG;IACrD,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;CAClD,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA6BpD;AAWD,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAM1E"}
@@ -5,12 +5,14 @@
5
5
  * `init` command): `foo` runs `create-foo`, `@scope/bar` runs
6
6
  * `@scope/create-bar`, and a bare `@scope` runs `@scope/create`, each with
7
7
  * the requested version or `*` when none was given. An already-prefixed
8
- * `create-foo` is used as-is. Execution is the dlx flow, so temporary
9
- * environments, trust policy, and cleanup all behave identically.
8
+ * `create-foo` is used as-is. Initializers use writable private temporary
9
+ * closures; trust policy and cleanup match dlx. The Next adapter installs
10
+ * generated dependencies natively after the initializer's skip-install step.
10
11
  */
12
+ import { runNextInitializer } from "./next.js";
11
13
  import npa from "npm-package-arg";
12
14
  import { ZyrkError } from "../errors.js";
13
- import { runDlx } from "./run.js";
15
+ import { runInitializer } from "./run.js";
14
16
  /**
15
17
  * Map an initializer to its starter package specifier. Throws ZYRK_USAGE
16
18
  * for non-registry inputs (Git/hosted initializers are deferred along with
@@ -55,5 +57,8 @@ function usage(input, cause) {
55
57
  }
56
58
  export async function runCreate(options) {
57
59
  const { input, ...rest } = options;
58
- return runDlx({ ...rest, spec: starterSpecFor(input) });
60
+ const spec = starterSpecFor(input);
61
+ return spec.startsWith("create-next-app@")
62
+ ? runNextInitializer({ ...rest, spec })
63
+ : runInitializer({ ...rest, spec });
59
64
  }
@@ -0,0 +1,7 @@
1
+ import { type InstallOptions } from "../install/run.ts";
2
+ import { type DlxOptions, type DlxResult } from "./run.ts";
3
+ export declare function nextTarget(args: readonly string[]): string | undefined;
4
+ export declare function runNextInitializer(options: DlxOptions & {
5
+ onInstallProgress?: InstallOptions["onProgress"];
6
+ }): Promise<DlxResult>;
7
+ //# sourceMappingURL=next.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/dlx/next.ts"],"names":[],"mappings":"AAQA,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,EAAkB,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAuC3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAEtE;AA4CD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,UAAU,GAAG;IAAE,iBAAiB,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,CAAA;CAAE,GACzE,OAAO,CAAC,SAAS,CAAC,CAkGpB"}
@@ -0,0 +1,164 @@
1
+ /** create-next-app's documented --skip-install keeps installation native.
2
+ * Target validation prevents a successful-but-aborted initializer from making
3
+ * zyrk install an unrelated project. No generated files are deleted on failure.
4
+ */
5
+ import { lstat, readFile, readdir } from "node:fs/promises";
6
+ import * as path from "node:path";
7
+ import { satisfies } from "semver";
8
+ import { ZyrkError } from "../errors.js";
9
+ import { installProject } from "../install/run.js";
10
+ import { isErrno } from "../util/fsx.js";
11
+ import { runInitializer } from "./run.js";
12
+ function failed(message) {
13
+ return new ZyrkError("ZYRK_RESOLUTION_FAILED", message, {
14
+ remedy: "Check the generated project, then run zyrk install there; rerun create with an explicit project directory when needed.",
15
+ });
16
+ }
17
+ /** Value-bearing options from the official create-next-app CLI. All original
18
+ * arguments are still forwarded verbatim; these only locate the target. */
19
+ function parseNextArgs(args) {
20
+ const values = new Set([
21
+ "--import-alias",
22
+ "--example",
23
+ "-e",
24
+ "--example-path",
25
+ ]);
26
+ const flags = new Set();
27
+ let target;
28
+ for (let i = 0; i < args.length; i++) {
29
+ const arg = args[i];
30
+ if (arg === "--") {
31
+ target ??= args[i + 1]?.trim();
32
+ break;
33
+ }
34
+ if (values.has(arg)) {
35
+ i++;
36
+ continue;
37
+ }
38
+ if (arg.startsWith("-"))
39
+ flags.add(arg);
40
+ else
41
+ target ??= arg.trim();
42
+ }
43
+ return { target, flags };
44
+ }
45
+ export function nextTarget(args) {
46
+ return parseNextArgs(args).target;
47
+ }
48
+ async function info(file) {
49
+ try {
50
+ return await lstat(file);
51
+ }
52
+ catch (cause) {
53
+ if (isErrno(cause, "ENOENT"))
54
+ return undefined;
55
+ throw cause;
56
+ }
57
+ }
58
+ async function manifestExists(directory) {
59
+ return (await info(path.join(directory, "package.json"))) !== undefined;
60
+ }
61
+ async function verifyManifest(directory) {
62
+ const file = path.join(directory, "package.json");
63
+ const entry = await info(file);
64
+ if (!entry?.isFile() || entry.isSymbolicLink())
65
+ throw failed("Next initializer did not generate a regular project manifest.");
66
+ let raw;
67
+ try {
68
+ raw = JSON.parse(await readFile(file, "utf8"));
69
+ }
70
+ catch {
71
+ throw failed("Next initializer generated an unreadable project manifest.");
72
+ }
73
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
74
+ throw failed("Next initializer generated an invalid project manifest.");
75
+ const manifest = raw;
76
+ const deps = manifest.dependencies;
77
+ if (typeof manifest.name !== "string" ||
78
+ !manifest.name ||
79
+ typeof deps !== "object" ||
80
+ deps === null ||
81
+ Array.isArray(deps) ||
82
+ typeof deps.next !== "string" ||
83
+ !deps.next)
84
+ throw failed("Next initializer did not generate a Next project manifest.");
85
+ }
86
+ export async function runNextInitializer(options) {
87
+ const args = options.args ?? [];
88
+ const { target, flags } = parseNextArgs(args);
89
+ // Help/version/reset are valid successful commands that generate no app.
90
+ if ([...flags].some((arg) => [
91
+ "--help",
92
+ "-h",
93
+ "--version",
94
+ "-v",
95
+ "--reset",
96
+ "--reset-preferences",
97
+ ].includes(arg)))
98
+ return runInitializer(options);
99
+ const cwd = options.cwd ?? process.cwd();
100
+ const explicit = target === undefined ? undefined : path.resolve(cwd, target);
101
+ const before = explicit === undefined ? undefined : await info(explicit);
102
+ if (before !== undefined &&
103
+ (!before.isDirectory() || before.isSymbolicLink()))
104
+ throw failed("Next initializer target must be a real project directory.");
105
+ if (explicit !== undefined && (await manifestExists(explicit)))
106
+ throw failed("Next initializer target already has a project manifest.");
107
+ const entries = explicit === undefined ? new Set(await readdir(cwd)) : undefined;
108
+ const skipInstall = flags.has("--skip-install");
109
+ // Insert before -- so it remains an option when the user supplies a literal target.
110
+ const forwarded = [...args];
111
+ if (!skipInstall) {
112
+ const separator = forwarded.indexOf("--");
113
+ forwarded.splice(separator < 0 ? forwarded.length : separator, 0, "--skip-install");
114
+ }
115
+ const result = await runInitializer({
116
+ ...options,
117
+ args: forwarded,
118
+ validateTool(tool) {
119
+ options.validateTool?.(tool);
120
+ if (!satisfies(tool.version, ">=15.0.0 <17.0.0"))
121
+ throw new ZyrkError("ZYRK_RESOLUTION_FAILED", "The resolved Next initializer version has no verified skip-install adapter.", {
122
+ detail: { name: tool.name, version: tool.version },
123
+ remedy: "Select create-next-app 15.x or 16.x; the native create adapter requires the verified --skip-install contract.",
124
+ });
125
+ },
126
+ });
127
+ if (result.exitCode !== 0)
128
+ return result;
129
+ let directory = explicit;
130
+ if (directory === undefined) {
131
+ const generated = [];
132
+ for (const entry of await readdir(cwd)) {
133
+ if (entries.has(entry))
134
+ continue;
135
+ const candidate = path.join(cwd, entry);
136
+ const current = await info(candidate);
137
+ if (current?.isDirectory() &&
138
+ !current.isSymbolicLink() &&
139
+ (await manifestExists(candidate)))
140
+ generated.push(candidate);
141
+ }
142
+ if (generated.length !== 1)
143
+ throw failed("Cannot identify one generated Next project after the initializer exited.");
144
+ directory = generated[0];
145
+ }
146
+ const after = await info(directory);
147
+ if (!after?.isDirectory() ||
148
+ after.isSymbolicLink() ||
149
+ (before !== undefined &&
150
+ (before.dev !== after.dev || before.ino !== after.ino)))
151
+ throw failed("Next initializer did not preserve its project directory.");
152
+ await verifyManifest(directory);
153
+ if (!skipInstall)
154
+ await installProject({
155
+ projectDir: directory,
156
+ store: options.store,
157
+ registry: options.registry,
158
+ ...(options.env === undefined ? {} : { env: options.env }),
159
+ ...(options.onInstallProgress === undefined
160
+ ? {}
161
+ : { onProgress: options.onInstallProgress }),
162
+ });
163
+ return result;
164
+ }
package/dist/dlx/run.d.ts CHANGED
@@ -31,13 +31,18 @@ export type DlxOptions = {
31
31
  /** Capture stdio instead of inheriting it (used by tests). */
32
32
  capture?: boolean;
33
33
  logger?: Logger;
34
+ /** Adapter contract checked against the exact resolved tool before execution. */
35
+ validateTool?: (tool: {
36
+ name: string;
37
+ version: string;
38
+ }) => void;
34
39
  };
35
40
  export type DlxResult = {
36
41
  exitCode: number;
37
42
  key: string;
38
43
  name: string;
39
44
  version: string;
40
- /** True when every needed realization already existed. */
45
+ /** True when every needed shared realization already existed. */
41
46
  reused: boolean;
42
47
  stdout: string;
43
48
  stderr: string;
@@ -50,4 +55,7 @@ export declare function splitDlxSpec(spec: string): {
50
55
  versionSpec: string;
51
56
  };
52
57
  export declare function runDlx(options: DlxOptions): Promise<DlxResult>;
58
+ /** Initializers copy and rewrite their shipped templates, so their full
59
+ * closure must use independent writable files (I-001, I-002, F-017). */
60
+ export declare function runInitializer(options: DlxOptions): Promise<DlxResult>;
53
61
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/dlx/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAchD,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB,CAqBA;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAwMpE"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/dlx/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAgBhD,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB,CAqBA;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAEpE;AAED;wEACwE;AACxE,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAE5E"}
package/dist/dlx/run.js CHANGED
@@ -29,6 +29,8 @@ import { currentProcessIdentity, superviseChild } from "../util/proc.js";
29
29
  import { acquireLock } from "../util/locks.js";
30
30
  import { registerGeneration } from "../install/metadata.js";
31
31
  import { openStoreDb } from "../store/db.js";
32
+ import { installBudgets } from "../install/budgets.js";
33
+ import { materializePrivate } from "../install/private.js";
32
34
  import { randomId } from "../util/ids.js";
33
35
  /** Split a tool specifier into its edge name and version part. */
34
36
  export function splitDlxSpec(spec) {
@@ -53,6 +55,14 @@ export function splitDlxSpec(spec) {
53
55
  return { name: parsed.name, versionSpec: spec.slice(parsed.name.length + 1) };
54
56
  }
55
57
  export async function runDlx(options) {
58
+ return runTemporaryTool(options, false);
59
+ }
60
+ /** Initializers copy and rewrite their shipped templates, so their full
61
+ * closure must use independent writable files (I-001, I-002, F-017). */
62
+ export async function runInitializer(options) {
63
+ return runTemporaryTool(options, true);
64
+ }
65
+ async function runTemporaryTool(options, writable) {
56
66
  const { store, registry } = options;
57
67
  const args = options.args ?? [];
58
68
  const cwd = options.cwd ?? process.cwd();
@@ -60,6 +70,7 @@ export async function runDlx(options) {
60
70
  const split = splitDlxSpec(options.spec);
61
71
  const resolved = await resolveRequirements({
62
72
  provider: registry,
73
+ limits: installBudgets(env).resolveBudget,
63
74
  requirements: [{ name: split.name, spec: split.versionSpec }],
64
75
  });
65
76
  const root = resolved.roots.find((entry) => entry.name === split.name);
@@ -69,10 +80,12 @@ export async function runDlx(options) {
69
80
  const tool = byKey.get(root.target);
70
81
  if (tool === undefined)
71
82
  throw internal(`resolution lost node ${root.target}`);
83
+ options.validateTool?.({ name: tool.name, version: tool.version });
72
84
  // Retain the publication/GC barrier until the child and temporary projection
73
85
  // are gone. The durable lease additionally protects recovery after a crash.
74
86
  const barrier = await acquireLock(path.join(store.paths.locksDir, "liveness.lock"), { name: "temporary tool liveness" });
75
87
  let txn;
88
+ let envDir;
76
89
  try {
77
90
  txn = await Transaction.begin(store, "dlx");
78
91
  await txn.setState("fetching");
@@ -109,17 +122,27 @@ export async function runDlx(options) {
109
122
  target: edge.target,
110
123
  })),
111
124
  })));
112
- const realized = await realizeGraph({
125
+ const sharedRealized = await realizeGraph({
113
126
  store,
114
127
  graph,
115
128
  ...(options.logger === undefined ? {} : { logger: options.logger }),
116
129
  });
117
130
  await txn.setState("realizations-ready");
131
+ await assertNoInstallScripts(sharedRealized.packageDirByKey, byKey);
132
+ envDir = await mkdtemp(path.join(os.tmpdir(), "zyrk-dlx-"));
133
+ const generationId = randomId("gen1_");
134
+ const realized = writable
135
+ ? await materializePrivate({
136
+ project: envDir,
137
+ store,
138
+ graph,
139
+ generationId,
140
+ })
141
+ : sharedRealized;
118
142
  const toolRealization = realized.realizationDirByKey.get(tool.key);
119
143
  if (toolRealization === undefined)
120
144
  throw internal(`missing realization for ${tool.key}`);
121
145
  const toolPkgDir = packageDirIn(toolRealization, tool.name);
122
- await assertNoInstallScripts(realized.packageDirByKey, byKey);
123
146
  const binKey = selectBin(tool, options.binName);
124
147
  const binTarget = tool.bin[binKey];
125
148
  if (binTarget === undefined)
@@ -139,8 +162,6 @@ export async function runDlx(options) {
139
162
  bins[name] = await resolveBinScript(directory, dependency, name, target);
140
163
  }
141
164
  }
142
- const envDir = await mkdtemp(path.join(os.tmpdir(), "zyrk-dlx-"));
143
- const generationId = randomId("gen1_");
144
165
  const projectId = randomId("proj1_");
145
166
  const leaseId = randomId("lease1_");
146
167
  let registered = false;
@@ -152,6 +173,7 @@ export async function runDlx(options) {
152
173
  generationId,
153
174
  graph,
154
175
  realized,
176
+ sharedRealized,
155
177
  roots: [tool.key],
156
178
  readiness: "ready",
157
179
  });
@@ -185,7 +207,7 @@ export async function runDlx(options) {
185
207
  key: tool.key,
186
208
  name: tool.name,
187
209
  version: tool.version,
188
- reused: realized.created.length === 0,
210
+ reused: sharedRealized.created.length === 0,
189
211
  envDir,
190
212
  };
191
213
  }
@@ -206,6 +228,8 @@ export async function runDlx(options) {
206
228
  }
207
229
  finally {
208
230
  try {
231
+ if (envDir !== undefined)
232
+ await rmrfNoFollow(envDir);
209
233
  await txn?.finish();
210
234
  }
211
235
  finally {
@@ -1 +1 @@
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
+ {"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;AAOvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,sBAAsB,CAAC;AA6V9B,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"}
@@ -8,7 +8,6 @@ import { canonicalJson } from "../util/json.js";
8
8
  import { importNpmLock } from "./import-npm.js";
9
9
  import { lockMismatch, cleanUrl } from "./lock.js";
10
10
  import { ZyrkError } from "../errors.js";
11
- import { assertWorkspacePlatform } from "./workspace-graph.js";
12
11
  function importNpmWorkspaceLockPass(doc, project, origin, overrideState) {
13
12
  if (!doc ||
14
13
  typeof doc !== "object" ||
@@ -173,7 +172,6 @@ function importNpmWorkspaceLockPass(doc, project, origin, overrideState) {
173
172
  peers: {},
174
173
  optionalPeers: [],
175
174
  };
176
- assertWorkspacePlatform({ nodes: [node], importers: {}, omissions: [] });
177
175
  graph.nodes.push(node);
178
176
  locations.set(contextKey, node);
179
177
  contexts.set(node.key, selectedOverride.context);
@@ -230,7 +228,7 @@ function importNpmWorkspaceLockPass(doc, project, origin, overrideState) {
230
228
  continue;
231
229
  }
232
230
  if (requirement.kind === "optional")
233
- continue;
231
+ throw lockMismatch("npm workspace lock lacks an exact optional selection; restore a complete npm lock or intentionally resolve fresh without the pruned lock");
234
232
  throw lockMismatch("npm workspace lock is missing a required selection");
235
233
  }
236
234
  let target;
@@ -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,CAyMhB"}
1
+ {"version":3,"file":"import-npm.d.ts","sourceRoot":"","sources":["../../src/install/import-npm.ts"],"names":[],"mappings":"AACA,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,CAkLhB"}
@@ -1,8 +1,9 @@
1
+ import { normalizeEngineConstraints } from "../util/engines.js";
1
2
  /** Adopt npm v2/v3 exact selections without asking the registry to pick versions. */
2
3
  import { createRootOverrides, } from "../resolve/overrides.js";
3
4
  import { validateLockDoc } from "../lockfile/read.js";
4
5
  import { canonicalStrongSri } from "../util/integrity.js";
5
- import { valid, validRange, satisfies } from "semver";
6
+ import { valid, satisfies } from "semver";
6
7
  import { parseRequirement } from "../resolve/spec.js";
7
8
  import { sourceIdOf, sha512Hex } from "../util/hash.js";
8
9
  import { cleanUrl, lockMismatch, packageName } from "./lock.js";
@@ -75,30 +76,10 @@ export function importNpmLock(doc, requirements, origin, overrides = createRootO
75
76
  edges: [],
76
77
  peerBindings: [],
77
78
  };
78
- if (entry.engines !== undefined) {
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
- }
101
- }
79
+ if (entry.engines !== undefined)
80
+ node.engines = normalizeEngineConstraints(entry.engines, () => {
81
+ throw lockMismatch("invalid npm engine constraint");
82
+ });
102
83
  if (typeof entry.bin === "object" &&
103
84
  entry.bin !== null &&
104
85
  !Array.isArray(entry.bin)) {
@@ -141,11 +122,12 @@ export function importNpmLock(doc, requirements, origin, overrides = createRootO
141
122
  const meta = entry.peerDependenciesMeta;
142
123
  const optionalPeer = requirement.kind === "peer" && meta?.[dep]?.optional === true;
143
124
  if (!targetLocation) {
144
- if (requirement.kind === "optional" || optionalPeer) {
145
- if (optionalPeer)
146
- node.peerBindings.push({ name: dep, target: null, optional: true });
125
+ if (optionalPeer) {
126
+ node.peerBindings.push({ name: dep, target: null, optional: true });
147
127
  continue;
148
128
  }
129
+ if (requirement.kind === "optional")
130
+ throw lockMismatch("npm lock lacks an exact optional selection; restore a complete npm lock or intentionally resolve fresh without the pruned lock");
149
131
  throw lockMismatch("npm lock is missing a required dependency");
150
132
  }
151
133
  const target = load(targetLocation, dep, requirement.spec, selected.context);
@@ -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;AAUD,kFAAkF;AAClF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAK,GAAG,IAAI,CAM3E"}
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,CA+M3D;AAYD,kFAAkF;AAClF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAK,GAAG,IAAI,CAM3E"}
@@ -39,6 +39,7 @@ export function lockedGraph(doc) {
39
39
  Object.keys(ext).some((key) => ![
40
40
  "version",
41
41
  "resolver",
42
+ "platformPolicy",
42
43
  "contextCanonicalization",
43
44
  "manifest",
44
45
  "scripts",
@@ -48,6 +49,7 @@ export function lockedGraph(doc) {
48
49
  ].includes(key)) ||
49
50
  ![ext.manifest, ext.scripts, ext.config].every((value) => typeof value === "string" && /^sha512-[A-Za-z0-9+/]{86}==$/.test(value)) ||
50
51
  ext.resolver !== ZYRK_RESOLVER_POLICY ||
52
+ ext.platformPolicy !== "portable-optional/1" ||
51
53
  ext.contextCanonicalization !== CONTEXT_CANONICALIZATION_VERSION ||
52
54
  !record(ext.nodes) ||
53
55
  !record(ext.roots) ||
@@ -180,7 +182,7 @@ export function lockedGraph(doc) {
180
182
  sourceId: raw.source,
181
183
  tarballUrl: raw.tarball,
182
184
  bin,
183
- engines,
185
+ ...(raw.engines === undefined ? {} : { engines }),
184
186
  platform,
185
187
  edges,
186
188
  peerBindings: peers,
@@ -204,11 +206,11 @@ export function lockedGraph(doc) {
204
206
  // npm publishes this canonical GitHub sponsorship link in funding metadata.
205
207
  // Keep source/archive URL validation query-free, and allow no other query here.
206
208
  function githubFundingUrl(value) {
207
- return /^https:\/\/github\.com\/[A-Za-z0-9_-]+\/[A-Za-z0-9_.-]+\?sponsor=1$/.test(value);
209
+ return (/^https:\/\/github\.com\/[A-Za-z0-9_-]+\/[A-Za-z0-9_.-]+\?sponsor=1$/.test(value) && new URL(value).href === value);
208
210
  }
209
211
  /** Preserved metadata is subject to the same portability boundary as new data. */
210
212
  export function assertPortableLockMetadata(value, field = "") {
211
- visitPortableLockMetadata(value, field, field === "funding" ? "entry" : undefined);
213
+ visitPortableLockMetadata(value, field, field === "funding" ? "funding" : undefined);
212
214
  }
213
215
  function visitPortableLockMetadata(value, field, funding) {
214
216
  if (typeof value === "string") {
@@ -222,7 +224,7 @@ function visitPortableLockMetadata(value, field, funding) {
222
224
  }
223
225
  if (Array.isArray(value)) {
224
226
  for (const item of value)
225
- visitPortableLockMetadata(item, field, funding === "entry" ? "entry" : undefined);
227
+ visitPortableLockMetadata(item, field, funding === "funding" ? "entry" : undefined);
226
228
  return;
227
229
  }
228
230
  if (value && typeof value === "object")
@@ -245,8 +247,8 @@ function visitPortableLockMetadata(value, field, funding) {
245
247
  ].includes(field))
246
248
  throw lockMismatch("portable metadata contains a credential field");
247
249
  visitPortableLockMetadata(item, key, key === "funding"
248
- ? "entry"
249
- : funding === "entry" && key === "url"
250
+ ? "funding"
251
+ : (funding === "funding" || funding === "entry") && key === "url"
250
252
  ? "url"
251
253
  : undefined);
252
254
  }
@@ -0,0 +1,5 @@
1
+ import { type RuntimePlatform, type FinalizedGraph } from "../resolve/resolver.ts";
2
+ import type { WorkspaceGraph } from "./workspace-graph.ts";
3
+ export declare function selectPlatformGraph(graph: FinalizedGraph, optionalRoots: ReadonlySet<string>, platform?: RuntimePlatform): FinalizedGraph;
4
+ export declare function selectWorkspacePlatform(graph: WorkspaceGraph, platform?: RuntimePlatform): WorkspaceGraph;
5
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/install/platform.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAuG3D,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,EACrB,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,EAClC,QAAQ,kBAAoB,GAC3B,cAAc,CAqBhB;AACD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,cAAc,EACrB,QAAQ,kBAAoB,GAC3B,cAAc,CAsBhB"}