zyrk 0.6.9 → 0.7.0
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 +22 -10
- package/VERSION +1 -1
- package/bin/zyrk.js +1 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +44 -0
- package/dist/commands/dependencies.d.ts +1 -0
- package/dist/commands/dependencies.d.ts.map +1 -1
- package/dist/commands/dependencies.js +3 -0
- package/dist/install/private.d.ts +9 -0
- package/dist/install/private.d.ts.map +1 -1
- package/dist/install/private.js +71 -14
- package/dist/install/run.d.ts +1 -0
- package/dist/install/run.d.ts.map +1 -1
- package/dist/install/run.js +32 -8
- package/dist/install/workspace-install.d.ts.map +1 -1
- package/dist/install/workspace-install.js +30 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,9 +105,11 @@ Manifest, lock, and projection changes are journaled for process-crash recovery;
|
|
|
105
105
|
a failed resolution, verification, or build-policy check keeps the prior graph.
|
|
106
106
|
|
|
107
107
|
Dependency `preinstall`, `install`, and `postinstall` scripts (plus implicit
|
|
108
|
-
`binding.gyp` builds) are supported
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
`binding.gyp` builds) are supported. In an interactive terminal, `zyrk install`
|
|
109
|
+
shows the exact candidates and lets you select which scripts to run. Press Enter
|
|
110
|
+
to skip all pending scripts and continue with an intentionally unbuilt install.
|
|
111
|
+
Headless installs also skip unapproved scripts and report that readiness state.
|
|
112
|
+
Review candidates separately with:
|
|
111
113
|
|
|
112
114
|
```bash
|
|
113
115
|
zyrk approve-builds # inspect exact sources and commands; executes no package code
|
|
@@ -117,8 +119,12 @@ zyrk rebuild --offline # rebuild the complete approved locked graph
|
|
|
117
119
|
```
|
|
118
120
|
|
|
119
121
|
Approvals are local to the exact graph, sources, patch contents, and script
|
|
120
|
-
policy.
|
|
121
|
-
|
|
122
|
+
policy. Interactive selections are written to the checked-in
|
|
123
|
+
`zyrk-workspace.json` file with exact package source identities, so collaborators
|
|
124
|
+
can reuse the review alongside the lockfile. Manual `zyrk approve-builds
|
|
125
|
+
<package>` approvals are recorded there too. Mandatory user denies override
|
|
126
|
+
project approvals. Approved scripts run with your account's permissions;
|
|
127
|
+
permission is not a sandbox. Packages that need
|
|
122
128
|
writes use a complete private dependency generation under `.zyrk-local/`, with
|
|
123
129
|
independent writable files and dependency links confined to that generation.
|
|
124
130
|
Build failure keeps the previous project graph active. `--ignore-scripts`
|
|
@@ -127,6 +133,12 @@ hooks never run automatically. A project can request runtime writable packages
|
|
|
127
133
|
with `"zyrk": { "mutablePackages": ["package-name"] }`; this also uses a complete
|
|
128
134
|
private graph.
|
|
129
135
|
|
|
136
|
+
`zyrk-workspace.json` is portable review state, not a machine-specific store
|
|
137
|
+
manifest. It records each approved package name, version, and exact source ID;
|
|
138
|
+
the file can be committed with `package-lock.json` so collaborators do not have
|
|
139
|
+
to repeat the same approval for the same verified source. User-level denials
|
|
140
|
+
still take precedence.
|
|
141
|
+
|
|
130
142
|
### Run project scripts
|
|
131
143
|
|
|
132
144
|
Use `zyrk run <script>` or the package-manager shortcut `zyrk <script>`; both
|
|
@@ -413,11 +425,11 @@ The commit hook runs the full check gate, increments the version in
|
|
|
413
425
|
`package.json`, `package-lock.json`, and `VERSION`, and creates a matching version
|
|
414
426
|
tag after the commit. Keep changes focused and never bypass the hooks.
|
|
415
427
|
|
|
416
|
-
CI checks pushed commits and pull requests. The
|
|
417
|
-
[npm publishing action](.github/workflows/npm-publish.yml)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
publish
|
|
428
|
+
CI checks pushed commits and pull requests. The
|
|
429
|
+
[npm publishing action](.github/workflows/npm-publish.yml) runs when the commit
|
|
430
|
+
hook's version tag is pushed, verifies formatting, typecheck, build, tests, and
|
|
431
|
+
the tag/version match, then publishes that exact commit. A manual workflow
|
|
432
|
+
dispatch can publish a prior version tag for release backfills. Dependabot and a scheduled advisory check supplement CI; an
|
|
421
433
|
unavailable advisory service fails separately from a clean result.
|
|
422
434
|
|
|
423
435
|
The contributor guide is [AGENTS.md](AGENTS.md). Bug reports and reproducible
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v0.
|
|
1
|
+
v0.7.0
|
package/bin/zyrk.js
CHANGED
package/dist/cli/main.d.ts
CHANGED
package/dist/cli/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AA+BA,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;AA6DzC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0BhD;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,KAAK,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC1B,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;AAuDF,gFAAgF;AAChF,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA2fpE"}
|
package/dist/cli/main.js
CHANGED
|
@@ -13,6 +13,7 @@ import { installProject } from "../install/run.js";
|
|
|
13
13
|
import { installRegistry } from "../install/registry.js";
|
|
14
14
|
import { readFileSync } from "node:fs";
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
16
|
+
import { createInterface } from "node:readline/promises";
|
|
16
17
|
import * as path from "node:path";
|
|
17
18
|
import { fileURLToPath } from "node:url";
|
|
18
19
|
import { runCreate, starterSpecFor } from "../dlx/create.js";
|
|
@@ -214,6 +215,39 @@ function readCliVersion() {
|
|
|
214
215
|
return "unknown";
|
|
215
216
|
}
|
|
216
217
|
}
|
|
218
|
+
async function promptForBuildApproval(builds, input, output) {
|
|
219
|
+
output.write("\nDependency lifecycle scripts need approval. Review these exact package sources:\n");
|
|
220
|
+
builds.forEach((build, index) => {
|
|
221
|
+
const stages = build.stages
|
|
222
|
+
.map((stage) => `${stage.name}: ${redactSecrets(stage.command)
|
|
223
|
+
.replace(/[\x00-\x1f\x7f-\x9f]/g, "")
|
|
224
|
+
.slice(0, 180)}`)
|
|
225
|
+
.join("; ");
|
|
226
|
+
output.write(` ${index + 1}. ${build.name}@${build.version} (${build.sourceId}) — ${stages}\n`);
|
|
227
|
+
});
|
|
228
|
+
output.write("Enter numbers separated by commas to approve selected sources, `all` to approve all listed sources, or press Enter to skip their scripts and continue unbuilt.\n");
|
|
229
|
+
const prompt = createInterface({
|
|
230
|
+
input,
|
|
231
|
+
output: output,
|
|
232
|
+
terminal: true,
|
|
233
|
+
});
|
|
234
|
+
try {
|
|
235
|
+
const answer = (await prompt.question("Approve builds> ")).trim();
|
|
236
|
+
if (!answer)
|
|
237
|
+
return [];
|
|
238
|
+
if (answer.toLowerCase() === "all")
|
|
239
|
+
return ["*"];
|
|
240
|
+
const numbers = answer.split(/[ ,]+/).map((value) => Number(value));
|
|
241
|
+
if (numbers.some((number) => !Number.isInteger(number) || number < 1 || number > builds.length))
|
|
242
|
+
throw new ZyrkError("ZYRK_USAGE", "Build selection contains an invalid candidate number.", {
|
|
243
|
+
remedy: `Select numbers from 1 to ${builds.length}, enter all, or press Enter to skip scripts.`,
|
|
244
|
+
});
|
|
245
|
+
return [...new Set(numbers)].map((number) => builds[number - 1].sourceId);
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
prompt.close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
217
251
|
/** Parse, dispatch, delegate, or run a tool, and emit. The only impure seam. */
|
|
218
252
|
export async function runCli(options) {
|
|
219
253
|
const parsed = parseArgs(options.argv);
|
|
@@ -466,6 +500,11 @@ export async function runCli(options) {
|
|
|
466
500
|
env: options.env,
|
|
467
501
|
specs,
|
|
468
502
|
projectDir: cwd,
|
|
503
|
+
...(options.stdin?.isTTY && options.stderr.isTTY && !parsed.json
|
|
504
|
+
? {
|
|
505
|
+
requestBuildApproval: (builds) => promptForBuildApproval(builds, options.stdin, options.stderr),
|
|
506
|
+
}
|
|
507
|
+
: {}),
|
|
469
508
|
...tools,
|
|
470
509
|
});
|
|
471
510
|
progress.close();
|
|
@@ -522,6 +561,11 @@ export async function runCli(options) {
|
|
|
522
561
|
projectDir: options.cwd ?? process.cwd(),
|
|
523
562
|
store: tools.store,
|
|
524
563
|
registry: tools.registry,
|
|
564
|
+
...(options.stdin?.isTTY && options.stderr.isTTY && !parsed.json
|
|
565
|
+
? {
|
|
566
|
+
requestBuildApproval: (builds) => promptForBuildApproval(builds, options.stdin, options.stderr),
|
|
567
|
+
}
|
|
568
|
+
: {}),
|
|
525
569
|
});
|
|
526
570
|
progress.close();
|
|
527
571
|
if (parsed.json) {
|
|
@@ -10,5 +10,6 @@ export declare function runDependencyCommand(options: {
|
|
|
10
10
|
registry: RegistryClient;
|
|
11
11
|
env?: NodeJS.ProcessEnv;
|
|
12
12
|
onProgress?: InstallOptions["onProgress"];
|
|
13
|
+
requestBuildApproval?: InstallOptions["requestBuildApproval"];
|
|
13
14
|
}): Promise<InstallResult>;
|
|
14
15
|
//# sourceMappingURL=dependencies.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../../src/commands/dependencies.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAavE,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../../src/commands/dependencies.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAavE,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;IAC1C,oBAAoB,CAAC,EAAE,cAAc,CAAC,sBAAsB,CAAC,CAAC;CAC/D,GAAG,OAAO,CAAC,aAAa,CAAC,CA+HzB"}
|
|
@@ -16,6 +16,9 @@ export async function runDependencyCommand(options) {
|
|
|
16
16
|
registry: options.registry,
|
|
17
17
|
...(options.env ? { env: options.env } : {}),
|
|
18
18
|
...(options.onProgress ? { onProgress: options.onProgress } : {}),
|
|
19
|
+
...(options.requestBuildApproval
|
|
20
|
+
? { requestBuildApproval: options.requestBuildApproval }
|
|
21
|
+
: {}),
|
|
19
22
|
};
|
|
20
23
|
const names = [];
|
|
21
24
|
let from;
|
|
@@ -12,6 +12,7 @@ export type BuildCandidate = {
|
|
|
12
12
|
command: string;
|
|
13
13
|
}>;
|
|
14
14
|
};
|
|
15
|
+
export declare const WORKSPACE_POLICY_FILE = "zyrk-workspace.json";
|
|
15
16
|
export declare function candidates(nodes: readonly ResolvedOccurrence[], dirs: ReadonlyMap<string, string>): Promise<BuildCandidate[]>;
|
|
16
17
|
export declare function candidateDigest(graph: GraphDoc, builds: readonly BuildCandidate[], policy: unknown, patches: string): string;
|
|
17
18
|
export declare function approveCandidate(options: {
|
|
@@ -21,7 +22,15 @@ export declare function approveCandidate(options: {
|
|
|
21
22
|
selected: readonly string[];
|
|
22
23
|
env?: NodeJS.ProcessEnv;
|
|
23
24
|
policy?: unknown;
|
|
25
|
+
portable?: boolean;
|
|
24
26
|
}): Promise<void>;
|
|
27
|
+
export declare function approvedCandidates(options: {
|
|
28
|
+
project: string;
|
|
29
|
+
env: NodeJS.ProcessEnv;
|
|
30
|
+
digest: string;
|
|
31
|
+
builds: readonly BuildCandidate[];
|
|
32
|
+
policy: unknown;
|
|
33
|
+
}): Promise<BuildCandidate[]>;
|
|
25
34
|
export declare function assertApproved(options: {
|
|
26
35
|
project: string;
|
|
27
36
|
env: NodeJS.ProcessEnv;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/install/private.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C,OAAO,EAA2B,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAOjE,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD,CAAC;
|
|
1
|
+
{"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/install/private.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C,OAAO,EAA2B,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAOjE,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD,CAAC;AACF,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAe3D,wBAAsB,UAAU,CAC9B,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,CAAC,cAAc,EAAE,CAAC,CA8C3B;AAyBD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,GACd,MAAM,CASR;AACD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgChB;AA0ED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,MAAM,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAgC5B;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,MAAM,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AACD,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,QAAQ,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,aAAa,CAAC,CAmGzB;AAID,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC,QAAQ,EAAE,aAAa,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFhB"}
|
package/dist/install/private.js
CHANGED
|
@@ -13,6 +13,7 @@ import { canonicalJson } from "../util/json.js";
|
|
|
13
13
|
import { isErrno, pathExists, writeFileAtomic } from "../util/fsx.js";
|
|
14
14
|
import { superviseChild } from "../util/proc.js";
|
|
15
15
|
import { redactSecrets } from "../log.js";
|
|
16
|
+
export const WORKSPACE_POLICY_FILE = "zyrk-workspace.json";
|
|
16
17
|
function fail(message) {
|
|
17
18
|
throw new ZyrkError("ZYRK_SCRIPT_BLOCKED", message, {
|
|
18
19
|
remedy: "Review the exact candidate with zyrk approve-builds, then explicitly approve its package names. Mandatory denials cannot be overridden.",
|
|
@@ -93,16 +94,62 @@ export async function approveCandidate(options) {
|
|
|
93
94
|
const selected = new Set(options.selected);
|
|
94
95
|
const all = selected.has("*");
|
|
95
96
|
for (const name of selected)
|
|
96
|
-
if (name !== "*" &&
|
|
97
|
+
if (name !== "*" &&
|
|
98
|
+
!options.builds.some((b) => b.name === name || b.sourceId === name))
|
|
97
99
|
fail("An approval selector does not match a reviewed build candidate.");
|
|
98
100
|
await writeFileAtomic(path.join(options.project, ".zyrk-local", "approvals.json"), JSON.stringify({
|
|
99
101
|
schemaVersion: 1,
|
|
100
102
|
candidate: options.digest,
|
|
101
103
|
sources: options.builds
|
|
102
|
-
.filter((b) => all || selected.has(b.name))
|
|
104
|
+
.filter((b) => all || selected.has(b.name) || selected.has(b.sourceId))
|
|
103
105
|
.map((b) => b.sourceId)
|
|
104
106
|
.sort(),
|
|
105
107
|
}), { mode: 0o600 });
|
|
108
|
+
if (options.portable) {
|
|
109
|
+
const selectedBuilds = options.builds.filter((build) => all || selected.has(build.name) || selected.has(build.sourceId));
|
|
110
|
+
await writePortableApprovals(options.project, selectedBuilds);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function readPortableApprovals(project) {
|
|
114
|
+
const file = path.join(project, WORKSPACE_POLICY_FILE);
|
|
115
|
+
let raw;
|
|
116
|
+
try {
|
|
117
|
+
const stat = await lstat(file);
|
|
118
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
119
|
+
fail("The workspace build-approval file must be a regular file.");
|
|
120
|
+
raw = JSON.parse(await readFile(file, "utf8"));
|
|
121
|
+
}
|
|
122
|
+
catch (cause) {
|
|
123
|
+
if (cause instanceof ZyrkError)
|
|
124
|
+
throw cause;
|
|
125
|
+
if (isErrno(cause, "ENOENT"))
|
|
126
|
+
return [];
|
|
127
|
+
fail("The workspace build-approval file is unreadable or malformed.");
|
|
128
|
+
}
|
|
129
|
+
if (!object(raw) ||
|
|
130
|
+
raw.schemaVersion !== 1 ||
|
|
131
|
+
!Array.isArray(raw.approvedBuilds) ||
|
|
132
|
+
raw.approvedBuilds.some((item) => !object(item) ||
|
|
133
|
+
typeof item.name !== "string" ||
|
|
134
|
+
typeof item.version !== "string" ||
|
|
135
|
+
typeof item.sourceId !== "string" ||
|
|
136
|
+
!/^src1_[a-f0-9]{40}$/.test(item.sourceId)))
|
|
137
|
+
fail("The workspace build-approval file has an unsupported format.");
|
|
138
|
+
return raw.approvedBuilds;
|
|
139
|
+
}
|
|
140
|
+
async function writePortableApprovals(project, selected) {
|
|
141
|
+
const current = await readPortableApprovals(project);
|
|
142
|
+
const bySource = new Map(current.map((item) => [item.sourceId, item]));
|
|
143
|
+
for (const build of selected)
|
|
144
|
+
bySource.set(build.sourceId, {
|
|
145
|
+
name: build.name,
|
|
146
|
+
version: build.version,
|
|
147
|
+
sourceId: build.sourceId,
|
|
148
|
+
});
|
|
149
|
+
await writeFileAtomic(path.join(project, WORKSPACE_POLICY_FILE), `${JSON.stringify({
|
|
150
|
+
schemaVersion: 1,
|
|
151
|
+
approvedBuilds: [...bySource.values()].sort((a, b) => a.name.localeCompare(b.name)),
|
|
152
|
+
}, null, 2)}\n`, { mode: 0o644 });
|
|
106
153
|
}
|
|
107
154
|
async function readUserPolicy(env) {
|
|
108
155
|
let user = {};
|
|
@@ -117,9 +164,9 @@ async function readUserPolicy(env) {
|
|
|
117
164
|
fail("Mandatory user build policy is malformed.");
|
|
118
165
|
return user;
|
|
119
166
|
}
|
|
120
|
-
export async function
|
|
167
|
+
export async function approvedCandidates(options) {
|
|
121
168
|
if (!options.builds.length)
|
|
122
|
-
return;
|
|
169
|
+
return [];
|
|
123
170
|
const user = await readUserPolicy(options.env);
|
|
124
171
|
let local;
|
|
125
172
|
try {
|
|
@@ -133,19 +180,29 @@ export async function assertApproved(options) {
|
|
|
133
180
|
throw cause;
|
|
134
181
|
local = undefined;
|
|
135
182
|
}
|
|
136
|
-
|
|
183
|
+
const portable = await readPortableApprovals(options.project);
|
|
184
|
+
return options.builds.filter((build) => {
|
|
137
185
|
if (denied(user.scripts, build) || denied(options.policy, build))
|
|
138
|
-
|
|
139
|
-
const
|
|
186
|
+
return false;
|
|
187
|
+
const manifestAllowed = object(options.policy) &&
|
|
140
188
|
Array.isArray(options.policy.allow) &&
|
|
141
189
|
options.policy.allow.includes(build.sourceId);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
190
|
+
const workspaceAllowed = portable.some((approval) => approval.sourceId === build.sourceId);
|
|
191
|
+
const localAllowed = object(local) &&
|
|
192
|
+
local.schemaVersion === 1 &&
|
|
193
|
+
local.candidate === options.digest &&
|
|
194
|
+
Array.isArray(local.sources) &&
|
|
195
|
+
local.sources.includes(build.sourceId);
|
|
196
|
+
return manifestAllowed || workspaceAllowed || localAllowed;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
export async function assertApproved(options) {
|
|
200
|
+
const approved = await approvedCandidates(options);
|
|
201
|
+
if (approved.length !== options.builds.length) {
|
|
202
|
+
const pending = options.builds.find((build) => !approved.some((item) => item.sourceId === build.sourceId));
|
|
203
|
+
fail(pending
|
|
204
|
+
? `Build of ${pending.name}@${pending.version} has not been approved for this exact source.`
|
|
205
|
+
: "Not every dependency build candidate is approved.");
|
|
149
206
|
}
|
|
150
207
|
}
|
|
151
208
|
export async function materializePrivate(options) {
|
package/dist/install/run.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export type InstallOptions = {
|
|
|
43
43
|
env?: NodeJS.ProcessEnv;
|
|
44
44
|
reviewBuilds?: boolean;
|
|
45
45
|
approveBuilds?: readonly string[];
|
|
46
|
+
requestBuildApproval?: (builds: readonly BuildCandidate[]) => Promise<readonly string[]>;
|
|
46
47
|
rebuild?: boolean;
|
|
47
48
|
repair?: boolean;
|
|
48
49
|
resolveBudget?: Partial<ResolverLimits>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/install/run.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/install/run.ts"],"names":[],"mappings":"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"}
|
package/dist/install/run.js
CHANGED
|
@@ -4,7 +4,7 @@ import { findWorkspaceRoot } from "./workspaces.js";
|
|
|
4
4
|
import { assertSupportedSourcePayload } from "./source-policy.js";
|
|
5
5
|
import { prepareRepair } from "./repair.js";
|
|
6
6
|
import { createFetchOperation } from "../fetch/package.js";
|
|
7
|
-
import { candidates, candidateDigest, approveCandidate,
|
|
7
|
+
import { candidates, candidateDigest, approveCandidate, approvedCandidates, materializePrivate, buildPrivate, } from "./private.js";
|
|
8
8
|
import { loadProjectPatches, applyProjectPatches } from "../commands/patch.js";
|
|
9
9
|
import { registerGeneration } from "./metadata.js";
|
|
10
10
|
import { randomId } from "../util/ids.js";
|
|
@@ -556,6 +556,7 @@ export async function installProject(options) {
|
|
|
556
556
|
selected: options.approveBuilds,
|
|
557
557
|
env: options.env ?? process.env,
|
|
558
558
|
policy: scriptPolicy(manifest),
|
|
559
|
+
portable: true,
|
|
559
560
|
});
|
|
560
561
|
return {
|
|
561
562
|
readiness: "build-review",
|
|
@@ -566,15 +567,37 @@ export async function installProject(options) {
|
|
|
566
567
|
builds,
|
|
567
568
|
};
|
|
568
569
|
}
|
|
569
|
-
|
|
570
|
+
let approvedBuilds = [];
|
|
570
571
|
if (builds.length && !options.ignoreScripts) {
|
|
571
|
-
|
|
572
|
+
const approvalOptions = {
|
|
572
573
|
project,
|
|
573
574
|
env: options.env ?? process.env,
|
|
574
575
|
digest: reviewedDigest,
|
|
575
576
|
builds,
|
|
576
577
|
policy: scriptPolicy(manifest),
|
|
577
|
-
}
|
|
578
|
+
};
|
|
579
|
+
approvedBuilds = await approvedCandidates(approvalOptions);
|
|
580
|
+
if (approvedBuilds.length < builds.length &&
|
|
581
|
+
options.requestBuildApproval) {
|
|
582
|
+
const pending = builds.filter((build) => !approvedBuilds.some((item) => item.sourceId === build.sourceId));
|
|
583
|
+
const selected = await options.requestBuildApproval(pending);
|
|
584
|
+
if (selected.length) {
|
|
585
|
+
await approveCandidate({
|
|
586
|
+
project,
|
|
587
|
+
digest: reviewedDigest,
|
|
588
|
+
builds: pending,
|
|
589
|
+
selected,
|
|
590
|
+
env: options.env ?? process.env,
|
|
591
|
+
policy: scriptPolicy(manifest),
|
|
592
|
+
portable: true,
|
|
593
|
+
});
|
|
594
|
+
approvedBuilds = await approvedCandidates(approvalOptions);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const unbuilt = builds.length > 0 &&
|
|
599
|
+
(options.ignoreScripts === true || approvedBuilds.length < builds.length);
|
|
600
|
+
if (approvedBuilds.length && !options.ignoreScripts) {
|
|
578
601
|
if (!privateGraph) {
|
|
579
602
|
realized = await materializePrivate({
|
|
580
603
|
project,
|
|
@@ -589,7 +612,7 @@ export async function installProject(options) {
|
|
|
589
612
|
project,
|
|
590
613
|
generationId,
|
|
591
614
|
nodes: activeNodes,
|
|
592
|
-
builds,
|
|
615
|
+
builds: approvedBuilds,
|
|
593
616
|
realized,
|
|
594
617
|
env: options.env ?? process.env,
|
|
595
618
|
});
|
|
@@ -613,9 +636,10 @@ export async function installProject(options) {
|
|
|
613
636
|
privateGraph = true;
|
|
614
637
|
}
|
|
615
638
|
}
|
|
616
|
-
if (unbuilt)
|
|
617
|
-
|
|
618
|
-
|
|
639
|
+
if (unbuilt) {
|
|
640
|
+
const skipped = builds.length - approvedBuilds.length;
|
|
641
|
+
warnings.push(`${skipped} dependency build ${skipped === 1 ? "script was" : "scripts were"} skipped because approval was not granted; readiness is intentionally unbuilt. Run zyrk approve-builds to review and approve sources.`);
|
|
642
|
+
}
|
|
619
643
|
if (privateGraph)
|
|
620
644
|
warnings.push("Using a complete project-private dependency graph; build/runtime writes do not change shared source.");
|
|
621
645
|
const entries = selectedRoots.map((r) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-install.d.ts","sourceRoot":"","sources":["../../src/install/workspace-install.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAuH9D,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"workspace-install.d.ts","sourceRoot":"","sources":["../../src/install/workspace-install.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAuH9D,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAqtBxB"}
|
|
@@ -32,7 +32,7 @@ import { loadProjectPatches, applyProjectPatches } from "../commands/patch.js";
|
|
|
32
32
|
import { prepareRepair } from "./repair.js";
|
|
33
33
|
import { assertSupportedSourcePayload } from "./source-policy.js";
|
|
34
34
|
import { registerGeneration } from "./metadata.js";
|
|
35
|
-
import { candidates, candidateDigest, approveCandidate,
|
|
35
|
+
import { candidates, candidateDigest, approveCandidate, approvedCandidates, buildPrivate, } from "./private.js";
|
|
36
36
|
const object = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
37
37
|
const unsupported = (message) => {
|
|
38
38
|
throw new ZyrkError("ZYRK_NOT_IMPLEMENTED", message, {
|
|
@@ -444,6 +444,7 @@ export async function installWorkspaceProject(options) {
|
|
|
444
444
|
selected: options.approveBuilds,
|
|
445
445
|
env: options.env ?? process.env,
|
|
446
446
|
policy,
|
|
447
|
+
portable: true,
|
|
447
448
|
});
|
|
448
449
|
return {
|
|
449
450
|
readiness: "build-review",
|
|
@@ -454,14 +455,38 @@ export async function installWorkspaceProject(options) {
|
|
|
454
455
|
builds,
|
|
455
456
|
};
|
|
456
457
|
}
|
|
458
|
+
let approvedBuilds = [];
|
|
457
459
|
if (builds.length && !options.ignoreScripts) {
|
|
458
|
-
|
|
460
|
+
const approvalOptions = {
|
|
459
461
|
project: root,
|
|
460
462
|
env: options.env ?? process.env,
|
|
461
463
|
digest,
|
|
462
464
|
builds,
|
|
463
465
|
policy,
|
|
464
|
-
}
|
|
466
|
+
};
|
|
467
|
+
approvedBuilds = await approvedCandidates(approvalOptions);
|
|
468
|
+
if (approvedBuilds.length < builds.length &&
|
|
469
|
+
options.requestBuildApproval) {
|
|
470
|
+
const pending = builds.filter((build) => !approvedBuilds.some((item) => item.sourceId === build.sourceId));
|
|
471
|
+
const selected = await options.requestBuildApproval(pending);
|
|
472
|
+
if (selected.length) {
|
|
473
|
+
await approveCandidate({
|
|
474
|
+
project: root,
|
|
475
|
+
digest,
|
|
476
|
+
builds: pending,
|
|
477
|
+
selected,
|
|
478
|
+
env: options.env ?? process.env,
|
|
479
|
+
policy,
|
|
480
|
+
portable: true,
|
|
481
|
+
});
|
|
482
|
+
approvedBuilds = await approvedCandidates(approvalOptions);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const skippedBuilds = builds.length - approvedBuilds.length;
|
|
487
|
+
if (skippedBuilds > 0)
|
|
488
|
+
warnings.push(`${skippedBuilds} dependency build ${skippedBuilds === 1 ? "script was" : "scripts were"} skipped because approval was not granted; readiness is intentionally unbuilt. Run zyrk approve-builds to review and approve sources.`);
|
|
489
|
+
if (approvedBuilds.length && !options.ignoreScripts) {
|
|
465
490
|
// Approved writes need a coherent fully private registry closure.
|
|
466
491
|
await allocatePrivate(new Set(nodes.map((n) => n.key)));
|
|
467
492
|
options.onProgress?.({ phase: "building" });
|
|
@@ -469,12 +494,12 @@ export async function installWorkspaceProject(options) {
|
|
|
469
494
|
project: root,
|
|
470
495
|
generationId,
|
|
471
496
|
nodes,
|
|
472
|
-
builds,
|
|
497
|
+
builds: approvedBuilds,
|
|
473
498
|
realized,
|
|
474
499
|
env: options.env ?? process.env,
|
|
475
500
|
});
|
|
476
501
|
}
|
|
477
|
-
const readiness = builds.length && options.ignoreScripts
|
|
502
|
+
const readiness = builds.length && (options.ignoreScripts || skippedBuilds > 0)
|
|
478
503
|
? "intentionally-unbuilt"
|
|
479
504
|
: "ready";
|
|
480
505
|
registration = await registerGeneration({
|