@intentius/chant 0.124.32 → 0.124.34
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/dist/ci-jobs.d.ts +4 -0
- package/dist/ci-jobs.d.ts.map +1 -1
- package/dist/ci-jobs.js +9 -2
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +18 -3
- package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
- package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-health.js +150 -0
- package/dist/cli/handlers/lifecycle.d.ts +26 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +1 -1
- package/dist/cli/handlers/pr.d.ts +2 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +124 -2
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +277 -74
- package/dist/cli/handlers/release.d.ts +16 -1
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +83 -11
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +99 -22
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +30 -1
- package/dist/cli/registry.d.ts +21 -5
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/deploy-inputs.d.ts +91 -0
- package/dist/components/deploy-inputs.d.ts.map +1 -0
- package/dist/components/deploy-inputs.js +184 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +9 -0
- package/dist/components/gate-plan.d.ts +8 -0
- package/dist/components/gate-plan.d.ts.map +1 -1
- package/dist/components/gate-plan.js +6 -0
- package/dist/components/pr-pipeline.d.ts +33 -10
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +42 -11
- package/dist/components/pr-run.d.ts +1 -1
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/promote-rounds.d.ts +102 -0
- package/dist/components/promote-rounds.d.ts.map +1 -0
- package/dist/components/promote-rounds.js +174 -0
- package/dist/components/release-compile.d.ts +37 -2
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +64 -8
- package/dist/components/release-gate.d.ts +30 -0
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +92 -42
- package/dist/components/release-promote.d.ts +17 -3
- package/dist/components/release-promote.d.ts.map +1 -1
- package/dist/components/release-promote.js +79 -18
- package/dist/components/verbs/bom-writer.d.ts +1 -1
- package/dist/components/verbs/bom-writer.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
- package/dist/components/wave-plan.d.ts +10 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +32 -4
- package/dist/graph-declared.d.ts +4 -0
- package/dist/graph-declared.d.ts.map +1 -1
- package/dist/graph-declared.js +11 -1
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +32 -7
- package/dist/health.d.ts +110 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +105 -0
- package/dist/health.schema.json +191 -0
- package/dist/lexicon.d.ts +27 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/merge-record.d.ts +104 -0
- package/dist/lifecycle/merge-record.d.ts.map +1 -0
- package/dist/lifecycle/merge-record.js +102 -0
- package/dist/lifecycle/publish-attachments.d.ts +8 -5
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
- package/dist/lifecycle/publish-attachments.js +27 -13
- package/dist/lifecycle/publish-npm.d.ts +3 -2
- package/dist/lifecycle/publish-npm.d.ts.map +1 -1
- package/dist/lifecycle/publish-npm.js +4 -3
- package/dist/lifecycle/release-at.d.ts +40 -0
- package/dist/lifecycle/release-at.d.ts.map +1 -0
- package/dist/lifecycle/release-at.js +83 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +23 -12
- package/dist/lifecycle/release-publish.d.ts +28 -12
- package/dist/lifecycle/release-publish.d.ts.map +1 -1
- package/dist/lifecycle/release-publish.js +29 -17
- package/dist/lifecycle/release-store.d.ts +25 -2
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +40 -13
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lint/rules/op/index.d.ts +2 -1
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +4 -2
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
- package/dist/op/activities/activity-contracts.d.ts +5 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +3 -0
- package/dist/op/activities/shell.d.ts +38 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +89 -3
- package/dist/op/builders.d.ts +7 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +7 -0
- package/dist/op/runtime.d.ts +2 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/pr-loop.d.ts +14 -3
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +15 -5
- package/dist/pr-report-url.d.ts +2 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/runner-start.d.ts +179 -0
- package/dist/runner-start.d.ts.map +1 -0
- package/dist/runner-start.js +277 -0
- package/dist/wave-runtime.mjs +236 -229
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.js +13 -2
- package/dist/workspace/check.schema.json +88 -2
- package/dist/workspace/checks/ci-workflow.d.ts +17 -0
- package/dist/workspace/checks/ci-workflow.d.ts.map +1 -0
- package/dist/workspace/checks/ci-workflow.js +123 -0
- package/dist/workspace/checks/security.d.ts +50 -0
- package/dist/workspace/checks/security.d.ts.map +1 -0
- package/dist/workspace/checks/security.js +305 -0
- package/dist/workspace/checks.d.ts +2 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +8 -0
- package/dist/workspace/ci-builds.d.ts +163 -0
- package/dist/workspace/ci-builds.d.ts.map +1 -0
- package/dist/workspace/ci-builds.js +315 -0
- package/dist/workspace/ci-green-cli.d.ts +2 -1
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +25 -5
- package/dist/workspace/ci-green-forge.d.ts +12 -0
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green-forge.js +12 -0
- package/dist/workspace/ci-green-workflow.d.ts +55 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +128 -1
- package/dist/workspace/ci-green.d.ts +89 -4
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +310 -23
- package/dist/workspace/ci-init.d.ts +100 -0
- package/dist/workspace/ci-init.d.ts.map +1 -0
- package/dist/workspace/ci-init.js +434 -0
- package/dist/workspace/ci-last-green.schema.json +49 -0
- package/dist/workspace/ci-list.schema.json +35 -1
- package/dist/workspace/ci-run-index.d.ts +1 -1
- package/dist/workspace/ci-run-index.d.ts.map +1 -1
- package/dist/workspace/ci-run-index.schema.json +1 -1
- package/dist/workspace/ci-run.d.ts +12 -6
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +72 -16
- package/dist/workspace/ci-runner-start.d.ts +77 -0
- package/dist/workspace/ci-runner-start.d.ts.map +1 -0
- package/dist/workspace/ci-runner-start.js +185 -0
- package/dist/workspace/ci-serve.d.ts +76 -7
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +153 -19
- package/dist/workspace/ci-workflow.d.ts +91 -0
- package/dist/workspace/ci-workflow.d.ts.map +1 -0
- package/dist/workspace/ci-workflow.js +33 -0
- package/dist/workspace/declaration.d.ts +35 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +12 -2
- package/dist/workspace/declaration.schema.json +51 -0
- package/dist/workspace/init.js +2 -2
- package/dist/workspace/ls.d.ts +2 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +1 -1
- package/dist/workspace/ls.schema.json +2 -1
- package/dist/workspace/member-pipeline.d.ts +1 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +2 -0
- package/dist/workspace/merge-record.schema.json +49 -0
- package/dist/workspace/pr-report.schema.json +3 -3
- package/dist/workspace/reason-codes.d.ts +26 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +28 -0
- package/dist/workspace/release-candidates.d.ts +129 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -0
- package/dist/workspace/release-candidates.js +233 -0
- package/dist/workspace/release-candidates.schema.json +353 -0
- package/dist/workspace/release-workflow.d.ts +31 -0
- package/dist/workspace/release-workflow.d.ts.map +1 -0
- package/dist/workspace/release-workflow.js +150 -0
- package/dist/workspace/security.d.ts +40 -0
- package/dist/workspace/security.d.ts.map +1 -0
- package/dist/workspace/security.js +99 -0
- package/package.json +8 -1
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
- package/src/ci-jobs.ts +11 -2
- package/src/cli/handlers/build.test.ts +36 -0
- package/src/cli/handlers/build.ts +18 -3
- package/src/cli/handlers/lifecycle-health.test.ts +123 -0
- package/src/cli/handlers/lifecycle-health.ts +157 -0
- package/src/cli/handlers/lifecycle.ts +1 -1
- package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
- package/src/cli/handlers/pr-record.test.ts +201 -0
- package/src/cli/handlers/pr.test.ts +22 -0
- package/src/cli/handlers/pr.ts +127 -4
- package/src/cli/handlers/promote.test.ts +17 -0
- package/src/cli/handlers/promote.ts +296 -84
- package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
- package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
- package/src/cli/handlers/release.e2e.test.ts +19 -28
- package/src/cli/handlers/release.ts +81 -11
- package/src/cli/main.ts +99 -22
- package/src/cli/parse-args.ts +22 -1
- package/src/cli/registry.ts +21 -5
- package/src/components/deploy-inputs.test.ts +108 -0
- package/src/components/deploy-inputs.ts +225 -0
- package/src/components/driver.ts +11 -0
- package/src/components/gate-plan.ts +9 -0
- package/src/components/pr-pipeline.ts +56 -15
- package/src/components/pr-run.ts +1 -1
- package/src/components/promote-rounds.test.ts +220 -0
- package/src/components/promote-rounds.ts +232 -0
- package/src/components/release-compile.test.ts +34 -0
- package/src/components/release-compile.ts +89 -8
- package/src/components/release-gate.test.ts +47 -0
- package/src/components/release-gate.ts +111 -46
- package/src/components/release-promote.test.ts +55 -2
- package/src/components/release-promote.ts +93 -23
- package/src/components/verbs/bom-writer.ts +1 -1
- package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
- package/src/components/wave-plan.ts +32 -4
- package/src/graph-declared-exports.test.ts +40 -0
- package/src/graph-declared.ts +9 -1
- package/src/graph-detail.test.ts +57 -0
- package/src/graph-detail.ts +33 -7
- package/src/health.schema.json +191 -0
- package/src/health.test.ts +62 -0
- package/src/health.ts +178 -0
- package/src/lexicon.ts +41 -1
- package/src/lifecycle/merge-record.test.ts +111 -0
- package/src/lifecycle/merge-record.ts +154 -0
- package/src/lifecycle/publish-attachments.ts +25 -13
- package/src/lifecycle/publish-npm.ts +4 -3
- package/src/lifecycle/publish-targets.test.ts +34 -4
- package/src/lifecycle/release-at.test.ts +48 -0
- package/src/lifecycle/release-at.ts +100 -0
- package/src/lifecycle/release-cut.test.ts +41 -0
- package/src/lifecycle/release-cut.ts +22 -11
- package/src/lifecycle/release-publish.test.ts +43 -13
- package/src/lifecycle/release-publish.ts +38 -18
- package/src/lifecycle/release-store.ts +57 -13
- package/src/lifecycle/run-ledger.test.ts +13 -0
- package/src/lifecycle/run-ledger.ts +1 -0
- package/src/lint/rules/op/index.ts +4 -2
- package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
- package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
- package/src/op/activities/activity-contracts.ts +3 -0
- package/src/op/activities/shell-container.e2e.test.ts +67 -0
- package/src/op/activities/shell.test.ts +44 -1
- package/src/op/activities/shell.ts +110 -3
- package/src/op/builders.ts +7 -0
- package/src/op/runtime.ts +2 -0
- package/src/op/step-console.test.ts +4 -2
- package/src/pr-loop.test.ts +29 -0
- package/src/pr-loop.ts +26 -7
- package/src/pr-report-url.ts +2 -2
- package/src/runner-start-bundle.test.ts +29 -0
- package/src/runner-start.test.ts +179 -0
- package/src/runner-start.ts +329 -0
- package/src/workspace/__fixtures__/contract-repo.ts +11 -2
- package/src/workspace/check-contract.test.ts +4 -2
- package/src/workspace/check.schema.json +88 -2
- package/src/workspace/checks/ci-workflow.test.ts +150 -0
- package/src/workspace/checks/ci-workflow.ts +129 -0
- package/src/workspace/checks/security.test.ts +138 -0
- package/src/workspace/checks/security.ts +305 -0
- package/src/workspace/checks.ts +8 -0
- package/src/workspace/ci-builds.e2e.test.ts +208 -0
- package/src/workspace/ci-builds.test.ts +174 -0
- package/src/workspace/ci-builds.ts +385 -0
- package/src/workspace/ci-green-builds.test.ts +180 -0
- package/src/workspace/ci-green-cli.test.ts +54 -0
- package/src/workspace/ci-green-cli.ts +26 -5
- package/src/workspace/ci-green-forge.ts +24 -0
- package/src/workspace/ci-green-workflow.test.ts +84 -1
- package/src/workspace/ci-green-workflow.ts +139 -2
- package/src/workspace/ci-green.test.ts +5 -2
- package/src/workspace/ci-green.ts +359 -27
- package/src/workspace/ci-init.test.ts +159 -0
- package/src/workspace/ci-init.ts +445 -0
- package/src/workspace/ci-last-green.schema.json +49 -0
- package/src/workspace/ci-list.schema.json +35 -1
- package/src/workspace/ci-report-url.test.ts +6 -1
- package/src/workspace/ci-run-index.schema.json +1 -1
- package/src/workspace/ci-run-index.ts +1 -1
- package/src/workspace/ci-run.test.ts +24 -7
- package/src/workspace/ci-run.ts +78 -16
- package/src/workspace/ci-runner-start.test.ts +151 -0
- package/src/workspace/ci-runner-start.ts +193 -0
- package/src/workspace/ci-serve-builds.test.ts +86 -0
- package/src/workspace/ci-serve.test.ts +59 -0
- package/src/workspace/ci-serve.ts +177 -22
- package/src/workspace/ci-workflow.ts +71 -0
- package/src/workspace/declaration.schema.json +51 -0
- package/src/workspace/declaration.test.ts +17 -0
- package/src/workspace/declaration.ts +53 -3
- package/src/workspace/init.ts +2 -2
- package/src/workspace/ls.schema.json +2 -1
- package/src/workspace/ls.ts +3 -2
- package/src/workspace/member-pipeline.ts +2 -1
- package/src/workspace/merge-record.schema.json +49 -0
- package/src/workspace/pr-report.schema.json +3 -3
- package/src/workspace/reason-codes.test.ts +4 -0
- package/src/workspace/reason-codes.ts +28 -0
- package/src/workspace/release-candidates.schema.json +353 -0
- package/src/workspace/release-candidates.test.ts +106 -0
- package/src/workspace/release-candidates.ts +315 -0
- package/src/workspace/release-workflow.ts +167 -0
- package/src/workspace/security.test.ts +70 -0
- package/src/workspace/security.ts +118 -0
|
@@ -39,6 +39,14 @@
|
|
|
39
39
|
* planned by that capability, as a pull request's plan is. A plan that
|
|
40
40
|
* returns no change-set part is a hole, since nothing says what it changes.
|
|
41
41
|
*
|
|
42
|
+
* A dependency the promote also deploys is read from its plan, not from the
|
|
43
|
+
* estate (#4168 step 4, the rule #3999 gave a pull request): a dependent is
|
|
44
|
+
* planned against the outputs its upstream's plan says it will expose, and a
|
|
45
|
+
* reference the plan can't say is listed in `unknownInputs`. Any other
|
|
46
|
+
* dependency is read as it stands. What each component was planned against is
|
|
47
|
+
* kept in `reads`, so the promote can hold it back when an upstream applies
|
|
48
|
+
* as another value (`./promote-rounds.ts`).
|
|
49
|
+
*
|
|
42
50
|
* Any other component is a `chant` member compared with what the
|
|
43
51
|
* environment runs now: its latest deploy record and the release that record
|
|
44
52
|
* names. The artifact digest, each template the release's target records for
|
|
@@ -51,7 +59,7 @@
|
|
|
51
59
|
* component a promote, rollback or redeploy left failed or gated with nothing
|
|
52
60
|
* recorded since (#4035): its record names what ran before that run.
|
|
53
61
|
*/
|
|
54
|
-
import { deployContext, resolveComponentGraph, resolveStepInput } from "./driver.js";
|
|
62
|
+
import { deployContext, resolveComponentGraph, resolveStepInput, unresolvedWiringReferences } from "./driver.js";
|
|
55
63
|
import { componentPlanDigest } from "./gate-plan.js";
|
|
56
64
|
import { gateName, RELEASE_GATE_OP } from "../op/gate-name.js";
|
|
57
65
|
import { capabilitySteps, planContext, readComponentOutputs } from "./wave-plan.js";
|
|
@@ -187,66 +195,108 @@ export function chantComponentPart(input) {
|
|
|
187
195
|
* that fails to plan: it becomes a failed member.
|
|
188
196
|
*/
|
|
189
197
|
export async function planReleaseChangeSet(input) {
|
|
198
|
+
return (await planReleaseSet(input)).doc;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* {@link planReleaseChangeSet}, with what each component read. Components are
|
|
202
|
+
* planned a wave at a time, so a dependency the promote deploys has planned
|
|
203
|
+
* before its dependents read its outputs.
|
|
204
|
+
*/
|
|
205
|
+
export async function planReleaseSet(input) {
|
|
190
206
|
const { release, env, registry } = input;
|
|
191
207
|
const components = [...input.components];
|
|
192
208
|
const byName = new Map(components.map((c) => [c.name, c]));
|
|
193
209
|
const outputs = { ...(input.seededOutputs ?? {}) };
|
|
194
210
|
const read = new Set(Object.keys(outputs));
|
|
195
211
|
const parts = [];
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
const plannedBy = new Map();
|
|
213
|
+
const planned = {};
|
|
214
|
+
const unknownInputs = {};
|
|
215
|
+
const reads = {};
|
|
216
|
+
const inPlan = (name) => input.only === undefined || input.only.has(name);
|
|
217
|
+
const isPlannable = (c) => capabilitySteps(c.deploy).some((s) => registry.has(s.kind) && registry.resolve(s.kind).plan !== undefined);
|
|
218
|
+
const { waves } = resolveComponentGraph(components.map((c) => ({ ...c, dependsOn: (c.dependsOn ?? []).filter((d) => byName.has(d)) })));
|
|
219
|
+
for (const wave of waves) {
|
|
220
|
+
const planning = wave.filter(inPlan);
|
|
221
|
+
for (const name of planning) {
|
|
222
|
+
const component = byName.get(name);
|
|
223
|
+
const digest = input.digests.get(name) ?? "";
|
|
224
|
+
const plannable = capabilitySteps(component.deploy).filter((s) => registry.has(s.kind) && registry.resolve(s.kind).plan !== undefined);
|
|
225
|
+
if (plannable.length === 0) {
|
|
226
|
+
parts.push(chantComponentPart({ component, release, env, digest, current: input.current.get(name), unsettled: input.unsettled?.get(name) }));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const ctx = deployContext({ env }, name);
|
|
230
|
+
try {
|
|
231
|
+
for (const dep of component.dependsOn ?? []) {
|
|
232
|
+
if (read.has(dep) || !byName.has(dep))
|
|
233
|
+
continue;
|
|
234
|
+
read.add(dep);
|
|
235
|
+
// A dependency this promote plans exposes what its plan says (#3999).
|
|
236
|
+
if (plannedBy.has(dep) && isPlannable(byName.get(dep))) {
|
|
237
|
+
const known = plannedBy.get(dep)?.known ?? {};
|
|
238
|
+
outputs[dep] = { ...known };
|
|
239
|
+
planned[dep] = Object.keys(known).sort();
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const found = await readComponentOutputs(byName.get(dep), registry, deployContext({ env }, dep), outputs);
|
|
243
|
+
if (found)
|
|
244
|
+
outputs[dep] = found;
|
|
245
|
+
}
|
|
246
|
+
reads[name] = Object.fromEntries((component.dependsOn ?? []).filter((d) => d in outputs).map((d) => [d, structuredClone(outputs[d])]));
|
|
247
|
+
const unknown = new Set();
|
|
248
|
+
let exposes;
|
|
249
|
+
for (const step of plannable) {
|
|
250
|
+
const { kind, ...rest } = step;
|
|
251
|
+
for (const r of unresolvedWiringReferences(rest, outputs))
|
|
252
|
+
if (plannedBy.has(r.source) && !r.publish)
|
|
253
|
+
unknown.add(r.ref);
|
|
254
|
+
const result = await registry.resolve(kind).plan(planContext(ctx, rest, outputs), resolveStepInput(rest, {}, outputs));
|
|
255
|
+
if (result.outputs)
|
|
256
|
+
exposes = { known: { ...(exposes?.known ?? {}), ...result.outputs.known }, unknown: [...new Set([...(exposes?.unknown ?? []), ...result.outputs.unknown])].sort() };
|
|
257
|
+
parts.push(result.changeSet ?? {
|
|
258
|
+
member: {
|
|
259
|
+
member: result.member,
|
|
260
|
+
lexicon: "chant",
|
|
261
|
+
planner: "chant",
|
|
262
|
+
status: "planned",
|
|
263
|
+
planDigest: result.planDigest,
|
|
264
|
+
holes: [{ address: result.member, reason: `its ${kind} plan describes no changes, so what it changes is unknown` }],
|
|
265
|
+
},
|
|
266
|
+
entries: [],
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
plannedBy.set(name, exposes);
|
|
270
|
+
if (unknown.size > 0)
|
|
271
|
+
unknownInputs[name] = [...unknown].sort();
|
|
214
272
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
parts.push(planned.changeSet ?? {
|
|
273
|
+
catch (err) {
|
|
274
|
+
plannedBy.set(name, undefined);
|
|
275
|
+
parts.push({
|
|
219
276
|
member: {
|
|
220
|
-
member:
|
|
277
|
+
member: name,
|
|
221
278
|
lexicon: "chant",
|
|
222
279
|
planner: "chant",
|
|
223
|
-
status: "
|
|
224
|
-
|
|
225
|
-
|
|
280
|
+
status: "failed",
|
|
281
|
+
error: err instanceof Error ? err.message : String(err),
|
|
282
|
+
planDigest: null,
|
|
283
|
+
holes: [],
|
|
226
284
|
},
|
|
227
285
|
entries: [],
|
|
228
286
|
});
|
|
229
287
|
}
|
|
230
288
|
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
parts.push({
|
|
233
|
-
member: {
|
|
234
|
-
member: name,
|
|
235
|
-
lexicon: "chant",
|
|
236
|
-
planner: "chant",
|
|
237
|
-
status: "failed",
|
|
238
|
-
error: err instanceof Error ? err.message : String(err),
|
|
239
|
-
planDigest: null,
|
|
240
|
-
holes: [],
|
|
241
|
-
},
|
|
242
|
-
entries: [],
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
289
|
}
|
|
246
|
-
|
|
290
|
+
const doc = composeChangeSet(parts, {
|
|
247
291
|
...(input.chant !== undefined ? { chant: input.chant } : {}),
|
|
248
292
|
planned: { releaseDigest: release.releaseDigest, environment: env },
|
|
249
293
|
});
|
|
294
|
+
return {
|
|
295
|
+
doc,
|
|
296
|
+
reads,
|
|
297
|
+
...(Object.keys(planned).length > 0 ? { plannedOutputs: planned } : {}),
|
|
298
|
+
...(Object.keys(unknownInputs).length > 0 ? { unknownInputs } : {}),
|
|
299
|
+
};
|
|
250
300
|
}
|
|
251
301
|
/** The members a release gate refuses: provisional ones, which no approval of the document covers (`change-set.mdx`). */
|
|
252
302
|
export function provisionalMembers(doc) {
|
|
@@ -11,9 +11,19 @@
|
|
|
11
11
|
* under `.chant/release-templates/<digest>/` in the member's directory and the
|
|
12
12
|
* step reads them from there. The path is relative and fixed by the digest,
|
|
13
13
|
* so a gate approval bound to the promote's plan still matches on a re-run.
|
|
14
|
+
*
|
|
15
|
+
* A release cut since #4155 (a target with `trees`) records every local path
|
|
16
|
+
* a deploy step names (`recordedInputRefs`), directories file by file. A
|
|
17
|
+
* promote points each of them at the recorded copy: a directory is written
|
|
18
|
+
* under `.chant/release-templates/<digest>/<name>/`, its digest taken over its
|
|
19
|
+
* files the way a gate's directory digest is (`./deploy-inputs.ts`). A path
|
|
20
|
+
* outside the template fields that the release does not record, such as a
|
|
21
|
+
* chart named from a repository, is left as written. A target cut before
|
|
22
|
+
* #4155 is pinned as it was then: template fields only, other paths read from
|
|
23
|
+
* the checkout.
|
|
14
24
|
*/
|
|
15
25
|
import type { DriverComponent } from "./driver.js";
|
|
16
|
-
import type
|
|
26
|
+
import { type Release, type ReleaseTarget } from "../lifecycle/release-store.js";
|
|
17
27
|
import type { ReleaseRecord } from "../lifecycle/release-ledger.js";
|
|
18
28
|
import type { BuildArchiveManifest } from "./verbs/build-archive.js";
|
|
19
29
|
/** Where a promote under a release writes the recorded templates, relative to the member's directory. */
|
|
@@ -22,8 +32,12 @@ export declare const RELEASE_TEMPLATES_DIR = ".chant/release-templates";
|
|
|
22
32
|
export interface PinnedTemplate {
|
|
23
33
|
/** Relative to the member's directory. */
|
|
24
34
|
path: string;
|
|
35
|
+
/** The text, or the base64 of the bytes under `encoding: "base64"`. */
|
|
25
36
|
content: string;
|
|
37
|
+
encoding?: "base64";
|
|
26
38
|
}
|
|
39
|
+
/** Reads a recorded template's stored copy by digest: its text, or its base64 under `base64`. */
|
|
40
|
+
export type TemplateReader = (digest: string, encoding?: "base64") => Promise<string | null>;
|
|
27
41
|
/** The release's target for `env`, or an error naming the environments it has. */
|
|
28
42
|
export declare function targetFor(release: Release, env: string): {
|
|
29
43
|
target: ReleaseTarget;
|
|
@@ -38,7 +52,7 @@ export declare function isImplicitRelease(release: Release): boolean;
|
|
|
38
52
|
* the promote is refused: a template the target does not record, one whose
|
|
39
53
|
* bytes are missing, or one whose bytes do not hash to the target's digest.
|
|
40
54
|
*/
|
|
41
|
-
export declare function pinReleaseTemplates(components: readonly DriverComponent[], release: Release, target: ReleaseTarget, readTemplate:
|
|
55
|
+
export declare function pinReleaseTemplates(components: readonly DriverComponent[], release: Release, target: ReleaseTarget, readTemplate: TemplateReader): Promise<{
|
|
42
56
|
components: DriverComponent[];
|
|
43
57
|
files: PinnedTemplate[];
|
|
44
58
|
} | {
|
|
@@ -53,7 +67,7 @@ export declare function pinReleaseTemplates(components: readonly DriverComponent
|
|
|
53
67
|
* member's directory; a template whose bytes are missing or don't hash to the
|
|
54
68
|
* target's digest is refused, as {@link pinReleaseTemplates} refuses one.
|
|
55
69
|
*/
|
|
56
|
-
export declare function targetFiles(release: Release, target: ReleaseTarget, readTemplate:
|
|
70
|
+
export declare function targetFiles(release: Release, target: ReleaseTarget, readTemplate: TemplateReader): Promise<{
|
|
57
71
|
files: PinnedTemplate[];
|
|
58
72
|
} | {
|
|
59
73
|
errors: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"release-promote.d.ts","sourceRoot":"","sources":["../../src/components/release-promote.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"release-promote.d.ts","sourceRoot":"","sources":["../../src/components/release-promote.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAuC,MAAM,UAAU,CAAC;AAErF,OAAO,EAAuB,KAAK,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEnG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,yGAAyG;AACzG,eAAO,MAAM,qBAAqB,6BAA6B,CAAC;AAEhE,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAE7F,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAMtG;AAED,sIAAsI;AACtI,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAG3D;AAUD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,aAAa,EACrB,YAAY,EAAE,cAAc,GAC3B,OAAO,CAAC;IAAE,UAAU,EAAE,eAAe,EAAE,CAAC;IAAC,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAyD5F;AA+CD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,aAAa,EACrB,YAAY,EAAE,cAAc,GAC3B,OAAO,CAAC;IAAE,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA2B7D;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAMxF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAUzF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,aAAa,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAC,EACpE,YAAY,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,GAC7E,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnB"}
|
|
@@ -11,11 +11,22 @@
|
|
|
11
11
|
* under `.chant/release-templates/<digest>/` in the member's directory and the
|
|
12
12
|
* step reads them from there. The path is relative and fixed by the digest,
|
|
13
13
|
* so a gate approval bound to the promote's plan still matches on a re-run.
|
|
14
|
+
*
|
|
15
|
+
* A release cut since #4155 (a target with `trees`) records every local path
|
|
16
|
+
* a deploy step names (`recordedInputRefs`), directories file by file. A
|
|
17
|
+
* promote points each of them at the recorded copy: a directory is written
|
|
18
|
+
* under `.chant/release-templates/<digest>/<name>/`, its digest taken over its
|
|
19
|
+
* files the way a gate's directory digest is (`./deploy-inputs.ts`). A path
|
|
20
|
+
* outside the template fields that the release does not record, such as a
|
|
21
|
+
* chart named from a repository, is left as written. A target cut before
|
|
22
|
+
* #4155 is pinned as it was then: template fields only, other paths read from
|
|
23
|
+
* the checkout.
|
|
14
24
|
*/
|
|
15
25
|
import { mkdirSync, readdirSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
|
|
16
26
|
import { basename, dirname, join } from "node:path";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
27
|
+
import { recordedFields, recordedInputRefs, TEMPLATE_FIELDS, templateRefs } from "./release-compile.js";
|
|
28
|
+
import { templateBytesDigest } from "../lifecycle/release-store.js";
|
|
29
|
+
import { computePlanDigest } from "../lifecycle/plan-digest.js";
|
|
19
30
|
/** Where a promote under a release writes the recorded templates, relative to the member's directory. */
|
|
20
31
|
export const RELEASE_TEMPLATES_DIR = ".chant/release-templates";
|
|
21
32
|
/** The release's target for `env`, or an error naming the environments it has. */
|
|
@@ -48,29 +59,41 @@ export async function pinReleaseTemplates(components, release, target, readTempl
|
|
|
48
59
|
const errors = [];
|
|
49
60
|
const files = [];
|
|
50
61
|
const out = [];
|
|
62
|
+
// #4155: a target with `trees` records every deploy input; one without was cut before and records template fields only.
|
|
63
|
+
const full = target.trees !== undefined;
|
|
51
64
|
for (const component of components) {
|
|
52
65
|
const replace = new Map();
|
|
53
|
-
|
|
66
|
+
const refs = full ? recordedInputRefs(component) : templateRefs(component).map((r) => ({ ...r, required: true }));
|
|
67
|
+
for (const ref of refs) {
|
|
54
68
|
if (replace.has(ref.value))
|
|
55
69
|
continue;
|
|
70
|
+
const dir = ref.path.replace(/\/+$/, "");
|
|
71
|
+
const tree = full ? target.trees.find((t) => t.component === component.name && t.path === dir) : undefined;
|
|
72
|
+
if (tree) {
|
|
73
|
+
const pinned = await pinTree(component.name, tree.path, target, readTemplate);
|
|
74
|
+
if ("errors" in pinned)
|
|
75
|
+
errors.push(...pinned.errors);
|
|
76
|
+
else {
|
|
77
|
+
files.push(...pinned.files);
|
|
78
|
+
replace.set(ref.value, pinned.path);
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
56
82
|
const recorded = target.templates.find((t) => t.component === component.name && t.path === ref.path);
|
|
57
83
|
if (!recorded) {
|
|
84
|
+
if (!ref.required)
|
|
85
|
+
continue;
|
|
58
86
|
errors.push(`component "${component.name}" reads ${ref.field} "${ref.value}", which release ${release.releaseDigest} does not record for "${target.env}"; ` +
|
|
59
87
|
`this checkout's deploy is not the one the release was compiled from (commit ${release.commit.slice(0, 12)})`);
|
|
60
88
|
continue;
|
|
61
89
|
}
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
errors.push(
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const got = contentDigest(content);
|
|
68
|
-
if (got !== recorded.digest) {
|
|
69
|
-
errors.push(`component "${component.name}": template "${ref.path}" hashes to ${got}, but the release's target for "${target.env}" records ${recorded.digest}; refusing to deploy it`);
|
|
90
|
+
const read = await readChecked(component.name, ref.path, recorded, target, readTemplate);
|
|
91
|
+
if ("error" in read) {
|
|
92
|
+
errors.push(read.error);
|
|
70
93
|
continue;
|
|
71
94
|
}
|
|
72
95
|
const path = join(RELEASE_TEMPLATES_DIR, recorded.digest.replace(/:/g, "_"), basename(ref.path));
|
|
73
|
-
files.push({ path, content });
|
|
96
|
+
files.push({ path, content: read.content, ...(recorded.encoding ? { encoding: recorded.encoding } : {}) });
|
|
74
97
|
replace.set(ref.value, path);
|
|
75
98
|
}
|
|
76
99
|
const rewrite = (phase) => ({
|
|
@@ -81,7 +104,7 @@ export async function pinReleaseTemplates(components, release, target, readTempl
|
|
|
81
104
|
if (isGate(step))
|
|
82
105
|
return step;
|
|
83
106
|
const next = { ...step };
|
|
84
|
-
for (const field of TEMPLATE_FIELDS) {
|
|
107
|
+
for (const field of full ? recordedFields(step.kind) : TEMPLATE_FIELDS) {
|
|
85
108
|
const v = next[field];
|
|
86
109
|
if (typeof v === "string" && replace.has(v))
|
|
87
110
|
next[field] = replace.get(v);
|
|
@@ -95,6 +118,42 @@ export async function pinReleaseTemplates(components, release, target, readTempl
|
|
|
95
118
|
}
|
|
96
119
|
return errors.length > 0 ? { errors } : { components: out, files };
|
|
97
120
|
}
|
|
121
|
+
/** One recorded template's stored copy, checked against its digest. */
|
|
122
|
+
async function readChecked(component, path, recorded, target, readTemplate) {
|
|
123
|
+
const who = component === null ? "" : `component "${component}": `;
|
|
124
|
+
const content = await readTemplate(recorded.digest, recorded.encoding);
|
|
125
|
+
if (content === null)
|
|
126
|
+
return { error: `${who}the bytes of template "${path}" (${recorded.digest}) are not on the chant/lifecycle branch; fetch it and try again` };
|
|
127
|
+
const got = templateBytesDigest(content, recorded.encoding);
|
|
128
|
+
if (got !== recorded.digest) {
|
|
129
|
+
return { error: `${who}template "${path}" hashes to ${got}, but the release's target for "${target.env}" records ${recorded.digest}; refusing to deploy it` };
|
|
130
|
+
}
|
|
131
|
+
return { content };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A recorded directory's files, under `.chant/release-templates/<digest>/<name>/`
|
|
135
|
+
* (#4155). The digest is taken over the files' paths from the directory and
|
|
136
|
+
* their bytes, as a gate's directory digest is, so the path names the tree.
|
|
137
|
+
*/
|
|
138
|
+
async function pinTree(component, dir, target, readTemplate) {
|
|
139
|
+
const prefix = `${dir}/`;
|
|
140
|
+
const entries = target.templates.filter((t) => t.component === component && t.path.startsWith(prefix));
|
|
141
|
+
const errors = [];
|
|
142
|
+
const read = [];
|
|
143
|
+
for (const e of entries) {
|
|
144
|
+
const got = await readChecked(component, e.path, e, target, readTemplate);
|
|
145
|
+
if ("error" in got)
|
|
146
|
+
errors.push(got.error);
|
|
147
|
+
else
|
|
148
|
+
read.push({ rel: e.path.slice(prefix.length), content: got.content, digest: e.digest, ...(e.encoding ? { encoding: e.encoding } : {}) });
|
|
149
|
+
}
|
|
150
|
+
if (errors.length > 0)
|
|
151
|
+
return { errors };
|
|
152
|
+
read.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
|
|
153
|
+
const digest = computePlanDigest("directory", read.map(({ rel, digest }) => ({ path: rel, digest })));
|
|
154
|
+
const path = join(RELEASE_TEMPLATES_DIR, digest.replace(/:/g, "_"), basename(dir));
|
|
155
|
+
return { path, files: read.map((r) => ({ path: join(path, r.rel), content: r.content, ...(r.encoding ? { encoding: r.encoding } : {}) })) };
|
|
156
|
+
}
|
|
98
157
|
/**
|
|
99
158
|
* The files a release's target records, read back by digest and checked, for
|
|
100
159
|
* `chant release export` (#4046). A project with no components has no deploy
|
|
@@ -113,12 +172,12 @@ export async function targetFiles(release, target, readTemplate) {
|
|
|
113
172
|
errors.push(`${who}template "${t.path}" is not a path inside the member's directory; refusing to write it`);
|
|
114
173
|
continue;
|
|
115
174
|
}
|
|
116
|
-
const content = await readTemplate(t.digest);
|
|
175
|
+
const content = await readTemplate(t.digest, t.encoding);
|
|
117
176
|
if (content === null) {
|
|
118
177
|
errors.push(`${who}the bytes of template "${t.path}" (${t.digest}) are not on the chant/lifecycle branch; fetch it and try again`);
|
|
119
178
|
continue;
|
|
120
179
|
}
|
|
121
|
-
const got =
|
|
180
|
+
const got = templateBytesDigest(content, t.encoding);
|
|
122
181
|
if (got !== t.digest) {
|
|
123
182
|
errors.push(`${who}template "${t.path}" hashes to ${got}, but release ${release.releaseDigest} records ${t.digest} for "${target.env}"; refusing to write it`);
|
|
124
183
|
continue;
|
|
@@ -129,7 +188,7 @@ export async function targetFiles(release, target, readTemplate) {
|
|
|
129
188
|
errors.push(`two components record different bytes at "${t.path}" for "${target.env}", so they can't both be written there`);
|
|
130
189
|
continue;
|
|
131
190
|
}
|
|
132
|
-
files.push({ path: t.path, content });
|
|
191
|
+
files.push({ path: t.path, content, ...(t.encoding ? { encoding: t.encoding } : {}) });
|
|
133
192
|
}
|
|
134
193
|
return errors.length > 0 ? { errors } : { files };
|
|
135
194
|
}
|
|
@@ -138,7 +197,7 @@ export function writePinnedTemplates(dir, files) {
|
|
|
138
197
|
for (const f of files) {
|
|
139
198
|
const full = join(dir, f.path);
|
|
140
199
|
mkdirSync(dirname(full), { recursive: true });
|
|
141
|
-
writeFileSync(full, f.content);
|
|
200
|
+
writeFileSync(full, f.encoding === "base64" ? Buffer.from(f.content, "base64") : f.content);
|
|
142
201
|
}
|
|
143
202
|
}
|
|
144
203
|
/**
|
|
@@ -148,8 +207,10 @@ export function writePinnedTemplates(dir, files) {
|
|
|
148
207
|
* again from the release.
|
|
149
208
|
*/
|
|
150
209
|
export function removePinnedTemplates(dir, files) {
|
|
210
|
+
// Each file sits under `.chant/release-templates/<digest>/`, a recorded tree's deeper; that digest directory goes.
|
|
211
|
+
const prefix = RELEASE_TEMPLATES_DIR.split("/").length;
|
|
151
212
|
for (const f of files)
|
|
152
|
-
rmSync(
|
|
213
|
+
rmSync(join(dir, ...f.path.split(/[\\/]/).slice(0, prefix + 1)), { recursive: true, force: true });
|
|
153
214
|
const root = join(dir, RELEASE_TEMPLATES_DIR);
|
|
154
215
|
try {
|
|
155
216
|
if (readdirSync(root).length === 0)
|
|
@@ -68,7 +68,7 @@ export interface BomPackage {
|
|
|
68
68
|
/** Version string, when known. Omitted for resources with no version concept (e.g. a CFN resource). */
|
|
69
69
|
version?: string;
|
|
70
70
|
/** Package manager / ecosystem this entry came from (e.g. "npm", "maven"), surfaced in SPDX's `externalRefs`/CycloneDX's `purl` when derivable. */
|
|
71
|
-
type?: "npm" | "maven" | "config" | "external-reference";
|
|
71
|
+
type?: "npm" | "maven" | "cargo" | "config" | "external-reference";
|
|
72
72
|
/** Package URL (purl), when derivable — e.g. `pkg:npm/left-pad@1.3.0`. */
|
|
73
73
|
purl?: string;
|
|
74
74
|
/** SPDX download location, when known. Defaults to `"NOASSERTION"` per the SPDX spec's requirement that every package declare one. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bom-writer.d.ts","sourceRoot":"","sources":["../../../src/components/verbs/bom-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAIrE;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,2HAA2H;IAC3H,IAAI,EAAE,MAAM,CAAC;IACb,uGAAuG;IACvG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mJAAmJ;IACnJ,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"bom-writer.d.ts","sourceRoot":"","sources":["../../../src/components/verbs/bom-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAIrE;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,2HAA2H;IAC3H,IAAI,EAAE,MAAM,CAAC;IACb,uGAAuG;IACvG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mJAAmJ;IACnJ,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,oBAAoB,CAAC;IACnE,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sIAAsI;IACtI,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0JAA0J;IAC1J,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,iGAAiG;IACjG,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+MAA+M;IAC/M,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,oGAAoG;AACpG,MAAM,WAAW,QAAQ;IACvB,yFAAyF;IACzF,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2IAA2I;IAC3I,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,gIAAgI;IAChI,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;CACjC;AAyDD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAE,MAAM,IAAuB,GAAG,MAAM,CAyErF;AAsBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAE,MAAM,IAAuB,GAAG,MAAM,CAgE1F;AAUD,+NAA+N;AAC/N,wBAAgB,QAAQ,CACtB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,QAAQ,EACf,GAAG,CAAC,EAAE,MAAM,IAAI,GACf;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAG1D"}
|
|
@@ -55,6 +55,26 @@ export declare function parseNpmPackageLock(content: string): BomPackage[];
|
|
|
55
55
|
* inheritance chain.
|
|
56
56
|
*/
|
|
57
57
|
export declare function parsePomXml(content: string): BomPackage[];
|
|
58
|
+
/**
|
|
59
|
+
* Parse a `Cargo.lock` (version 3 or 4) into a flat `BomPackage[]`: one
|
|
60
|
+
* entry per `[[package]]` with a `source`, so the workspace's own crates,
|
|
61
|
+
* which have none, are left out as the npm parser leaves out the root.
|
|
62
|
+
* A dependency is named `name` or `name version` in the lock; only the name
|
|
63
|
+
* is kept. A small line reader, not a TOML parser: the lock is written by
|
|
64
|
+
* cargo in one fixed shape.
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseCargoLock(content: string): BomPackage[];
|
|
67
|
+
/** Each lockfile kind's parser and file name. */
|
|
68
|
+
export declare const LOCKFILE_PARSERS: {
|
|
69
|
+
readonly npm: typeof parseNpmPackageLock;
|
|
70
|
+
readonly maven: typeof parsePomXml;
|
|
71
|
+
readonly cargo: typeof parseCargoLock;
|
|
72
|
+
};
|
|
73
|
+
export declare const LOCKFILE_NAMES: {
|
|
74
|
+
readonly npm: "package-lock.json";
|
|
75
|
+
readonly maven: "pom.xml";
|
|
76
|
+
readonly cargo: "Cargo.lock";
|
|
77
|
+
};
|
|
58
78
|
export interface LockfileSbomGeneratorOptions {
|
|
59
79
|
/** Where generated SBOM documents are written on disk, as a sibling of the scanned path (see `sbomOutputPath`). Default: alongside the scanned manifest, named `sbom.<format>.json`. */
|
|
60
80
|
outDir?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lockfile-sbom-generator.d.ts","sourceRoot":"","sources":["../../../src/components/verbs/lockfile-sbom-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,KAAK,EAMV,aAAa,EACd,MAAM,kBAAkB,CAAC;AAe1B;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAkCjE;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CA8BzD;AAID,MAAM,WAAW,4BAA4B;IAC3C,wLAAwL;IACxL,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAgBD,qIAAqI;AACrI,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,aAAa,CA0CrG;AAED,gMAAgM;AAChM,eAAO,MAAM,qBAAqB,EAAE,aAA6C,CAAC"}
|
|
1
|
+
{"version":3,"file":"lockfile-sbom-generator.d.ts","sourceRoot":"","sources":["../../../src/components/verbs/lockfile-sbom-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,KAAK,EAMV,aAAa,EACd,MAAM,kBAAkB,CAAC;AAe1B;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAkCjE;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CA8BzD;AAID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAsB5D;AAED,iDAAiD;AACjD,eAAO,MAAM,gBAAgB;aAAK,GAAG;aAAuB,KAAK;aAAe,KAAK;CAA2B,CAAC;AACjH,eAAO,MAAM,cAAc;aAAK,GAAG,EAAE,mBAAmB;aAAE,KAAK,EAAE,SAAS;aAAE,KAAK,EAAE,YAAY;CAAW,CAAC;AAI3G,MAAM,WAAW,4BAA4B;IAC3C,wLAAwL;IACxL,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAgBD,qIAAqI;AACrI,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,aAAa,CA0CrG;AAED,gMAAgM;AAChM,eAAO,MAAM,qBAAqB,EAAE,aAA6C,CAAC"}
|
|
@@ -125,9 +125,46 @@ export function parsePomXml(content) {
|
|
|
125
125
|
}
|
|
126
126
|
return packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
127
127
|
}
|
|
128
|
+
// ── Cargo.lock (Rust, #4168 decision 12) ────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Parse a `Cargo.lock` (version 3 or 4) into a flat `BomPackage[]`: one
|
|
131
|
+
* entry per `[[package]]` with a `source`, so the workspace's own crates,
|
|
132
|
+
* which have none, are left out as the npm parser leaves out the root.
|
|
133
|
+
* A dependency is named `name` or `name version` in the lock; only the name
|
|
134
|
+
* is kept. A small line reader, not a TOML parser: the lock is written by
|
|
135
|
+
* cargo in one fixed shape.
|
|
136
|
+
*/
|
|
137
|
+
export function parseCargoLock(content) {
|
|
138
|
+
const seen = new Map();
|
|
139
|
+
const blocks = content.split(/^\[\[package\]\]\s*$/m).slice(1);
|
|
140
|
+
for (const block of blocks) {
|
|
141
|
+
const body = block.split(/^\[/m)[0] ?? "";
|
|
142
|
+
const field = (key) => new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m").exec(body)?.[1];
|
|
143
|
+
const name = field("name");
|
|
144
|
+
const version = field("version");
|
|
145
|
+
if (!name || field("source") === undefined)
|
|
146
|
+
continue;
|
|
147
|
+
const deps = /^dependencies\s*=\s*\[([\s\S]*?)\]/m.exec(body)?.[1];
|
|
148
|
+
const dependsOn = deps ? [...deps.matchAll(/"([^"\s]+)[^"]*"/g)].map((m) => m[1]) : [];
|
|
149
|
+
const key = `${name}@${version ?? ""}`;
|
|
150
|
+
if (seen.has(key))
|
|
151
|
+
continue;
|
|
152
|
+
seen.set(key, {
|
|
153
|
+
name,
|
|
154
|
+
version,
|
|
155
|
+
type: "cargo",
|
|
156
|
+
purl: version ? `pkg:cargo/${encodeURIComponent(name)}@${version}` : undefined,
|
|
157
|
+
dependsOn: dependsOn.length ? [...new Set(dependsOn)] : undefined,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name) || (a.version ?? "").localeCompare(b.version ?? ""));
|
|
161
|
+
}
|
|
162
|
+
/** Each lockfile kind's parser and file name. */
|
|
163
|
+
export const LOCKFILE_PARSERS = { npm: parseNpmPackageLock, maven: parsePomXml, cargo: parseCargoLock };
|
|
164
|
+
export const LOCKFILE_NAMES = { npm: "package-lock.json", maven: "pom.xml", cargo: "Cargo.lock" };
|
|
128
165
|
/** Locate the first readable lockfile/manifest under `dir` this generator knows how to parse, in preference order (npm before Maven — most chant components are Node-first). Returns `undefined` if none is found. */
|
|
129
166
|
function findManifest(dir) {
|
|
130
|
-
for (const [kind, filename] of [["npm", "package-lock.json"], ["maven", "pom.xml"]]) {
|
|
167
|
+
for (const [kind, filename] of [["npm", "package-lock.json"], ["maven", "pom.xml"], ["cargo", "Cargo.lock"]]) {
|
|
131
168
|
const candidate = join(dir, filename);
|
|
132
169
|
try {
|
|
133
170
|
readFileSync(candidate, "utf-8");
|
|
@@ -160,12 +197,12 @@ export function createLockfileSbomGenerator(options = {}) {
|
|
|
160
197
|
function generateFromDir(dir, subjectName, digest, format = DEFAULT_SBOM_FORMAT) {
|
|
161
198
|
const manifest = findManifest(dir);
|
|
162
199
|
if (!manifest) {
|
|
163
|
-
throw new Error(`no supported lockfile/manifest found under "${dir}" (looked for package-lock.json, pom.xml) — ` +
|
|
200
|
+
throw new Error(`no supported lockfile/manifest found under "${dir}" (looked for package-lock.json, pom.xml, Cargo.lock) — ` +
|
|
164
201
|
`the hermetic lockfile SBOM backend requires a lockfile on disk; use the deep-scan path (#610) for images with no lockfile`);
|
|
165
202
|
}
|
|
166
203
|
const content = readFileSync(manifest.path, "utf-8");
|
|
167
|
-
const packages = manifest.kind
|
|
168
|
-
const generatorName =
|
|
204
|
+
const packages = LOCKFILE_PARSERS[manifest.kind](content);
|
|
205
|
+
const generatorName = `chant-lockfile-sbom/${LOCKFILE_NAMES[manifest.kind]}`;
|
|
169
206
|
const doc = writeBom(format, { subjectName, subjectId: digest ?? manifest.path, packages, generator: generatorName }, now);
|
|
170
207
|
const outDir = options.outDir ?? dir;
|
|
171
208
|
mkdirSync(outDir, { recursive: true });
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
* (`componentPlanDigest`, ./gate-plan.ts, #2574), so a wave mixing roots and
|
|
14
14
|
* other components still has one digest that moves when any of them does.
|
|
15
15
|
*
|
|
16
|
+
* The files a step without a `plan` deploys from are bound too (#4155,
|
|
17
|
+
* ./deploy-inputs.ts). A component with no plannable step folds them into its
|
|
18
|
+
* composition digest. A component with one gets a member more,
|
|
19
|
+
* `<component>#inputs`, over its other steps' files, so a `lambda-deploy`
|
|
20
|
+
* source or a Helm chart beside a `cfn-deploy` moves the wave's digest. A step
|
|
21
|
+
* whose content chant can't name is a hole on that member, so the plan note
|
|
22
|
+
* lists it as unbound.
|
|
23
|
+
*
|
|
16
24
|
* Known limit: a step input that reads an earlier step of the same
|
|
17
25
|
* component (`@Phase.field`) has no value before the component runs, so it
|
|
18
26
|
* plans as `undefined`. Cross-component wiring (`stackOutput()`,
|
|
@@ -40,6 +48,8 @@ export type WaveComponentPlan = {
|
|
|
40
48
|
component: string;
|
|
41
49
|
error: string;
|
|
42
50
|
};
|
|
51
|
+
/** The member a planning component's other steps add (#4155): their files, under the component's name. */
|
|
52
|
+
export declare const inputsMemberName: (component: string) => string;
|
|
43
53
|
/** Every capability step in a composition, nested phases included, gates left out. */
|
|
44
54
|
export declare function capabilitySteps(phases: readonly DriverPhase[]): DriverStep[];
|
|
45
55
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wave-plan.d.ts","sourceRoot":"","sources":["../../src/components/wave-plan.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"wave-plan.d.ts","sourceRoot":"","sources":["../../src/components/wave-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAA6C,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAI9H,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,cAAc,CAAA;CAAE,GAC9H;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAWzC,0GAA0G;AAC1G,eAAO,MAAM,gBAAgB,cAAe,MAAM,KAAG,MAA+B,CAAC;AAErF,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,UAAU,EAAE,CAc5E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,aAAa,EAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxD,aAAa,CAGf;AAED,oHAAoH;AACpH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,kBAAkB,EAC5B,GAAG,EAAE,aAAa,EAClB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxD,OAAO,CAAC,iBAAiB,CAAC,CAgD5B;AAUD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,kBAAkB,EAC5B,GAAG,EAAE,aAAa,EAClB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxD,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAU9C;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,EAChD,QAAQ,EAAE,kBAAkB,EAC5B,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,aAAa,EAC3C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC/C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAYnC"}
|