@intentius/chant 0.79.0 → 0.81.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/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +30 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts +5 -0
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +13 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +13 -0
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/command-group.d.ts +6 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +44 -3
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +8 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/import-agents.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/update.d.ts +6 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/vendor.d.ts +5 -0
- package/dist/cli/commands/vendor.d.ts.map +1 -1
- package/dist/cli/conflict-check.d.ts +6 -0
- package/dist/cli/conflict-check.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/init.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +20 -0
- package/dist/cli/handlers/promote.d.ts.map +1 -0
- package/dist/cli/handlers/run-generate.d.ts +39 -0
- package/dist/cli/handlers/run-generate.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts +4 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/vendor.d.ts +8 -3
- package/dist/cli/handlers/vendor.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +14 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +46 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +10 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +3 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/driver.d.ts +16 -2
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts +7 -1
- package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -1
- package/dist/components/presets/ecs-fargate.d.ts +10 -4
- package/dist/components/presets/ecs-fargate.d.ts.map +1 -1
- package/dist/components/promote.d.ts +264 -0
- package/dist/components/promote.d.ts.map +1 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/run.d.ts.map +1 -1
- package/dist/components/verbs/build-archive.d.ts +22 -11
- package/dist/components/verbs/build-archive.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/config-static.d.ts +59 -0
- package/dist/config-static.d.ts.map +1 -0
- package/dist/config.d.ts +104 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/content-digest.d.ts +17 -0
- package/dist/content-digest.d.ts.map +1 -0
- package/dist/discovery/files.d.ts +48 -4
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +32 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.d.ts +20 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/fork.d.ts +20 -0
- package/dist/discovery/sandbox/fork.d.ts.map +1 -1
- package/dist/discovery/walk.d.ts +119 -0
- package/dist/discovery/walk.d.ts.map +1 -0
- package/dist/graph-ir.d.ts +17 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/join-key.d.ts +31 -0
- package/dist/join-key.d.ts.map +1 -0
- package/dist/lexicon-module.d.ts +110 -0
- package/dist/lexicon-module.d.ts.map +1 -0
- package/dist/lexicon.d.ts +82 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
- package/dist/lifecycle/digest.d.ts +5 -1
- package/dist/lifecycle/digest.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +20 -5
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +64 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +8 -1
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/legacy-digest.d.ts +59 -0
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -0
- package/dist/lifecycle/member-ledger.d.ts +67 -0
- package/dist/lifecycle/member-ledger.d.ts.map +1 -0
- package/dist/lifecycle/plan-digest.d.ts +4 -3
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +42 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +90 -5
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +2 -3
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +32 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +75 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -1
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +7 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-summary.d.ts +16 -0
- package/dist/op/gate-summary.d.ts.map +1 -1
- package/dist/op/gate.d.ts +16 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/project-root.d.ts +26 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
- package/dist/workspace/checks/generated.d.ts +156 -0
- package/dist/workspace/checks/generated.d.ts.map +1 -0
- package/dist/workspace/checks/ledgers.d.ts +78 -0
- package/dist/workspace/checks/ledgers.d.ts.map +1 -0
- package/dist/workspace/checks/links.d.ts +25 -0
- package/dist/workspace/checks/links.d.ts.map +1 -0
- package/dist/workspace/checks/pipelines.d.ts +76 -0
- package/dist/workspace/checks/pipelines.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +161 -0
- package/dist/workspace/checks.d.ts.map +1 -0
- package/dist/workspace/compose-graph.d.ts +115 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -0
- package/dist/workspace/compose-reports.d.ts +68 -0
- package/dist/workspace/compose-reports.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +220 -0
- package/dist/workspace/declaration.d.ts.map +1 -0
- package/dist/workspace/declaration.schema.json +434 -0
- package/dist/workspace/generated-files.d.ts +65 -0
- package/dist/workspace/generated-files.d.ts.map +1 -0
- package/dist/workspace/glob.d.ts +13 -0
- package/dist/workspace/glob.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +75 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +96 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +38 -0
- package/dist/workspace/jsonc.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +192 -0
- package/dist/workspace/kinds.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +84 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -0
- package/dist/workspace/lineage-cli.d.ts +31 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +101 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +258 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -0
- package/dist/workspace/lineage-merge.d.ts +14 -0
- package/dist/workspace/lineage-merge.d.ts.map +1 -0
- package/dist/workspace/lineage-migrations.d.ts +178 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -0
- package/dist/workspace/lineage-update.d.ts +59 -0
- package/dist/workspace/lineage-update.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +146 -0
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
- package/dist/workspace/lineage-vendor.d.ts +63 -0
- package/dist/workspace/lineage-vendor.d.ts.map +1 -0
- package/dist/workspace/lineage-version.d.ts +32 -0
- package/dist/workspace/lineage-version.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +150 -0
- package/dist/workspace/links.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +99 -0
- package/dist/workspace/ls.d.ts.map +1 -0
- package/dist/workspace/member-commands.d.ts +139 -0
- package/dist/workspace/member-commands.d.ts.map +1 -0
- package/dist/workspace/member-handles.d.ts +54 -0
- package/dist/workspace/member-handles.d.ts.map +1 -0
- package/dist/workspace/member-pipeline.d.ts +143 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -0
- package/dist/workspace/member-run.d.ts +67 -0
- package/dist/workspace/member-run.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +65 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -0
- package/dist/workspace/record-source.d.ts +22 -0
- package/dist/workspace/record-source.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +81 -0
- package/dist/workspace/records-cli.d.ts.map +1 -0
- package/dist/workspace/records.d.ts +118 -0
- package/dist/workspace/records.d.ts.map +1 -0
- package/dist/workspace/root-refusal.d.ts +53 -0
- package/dist/workspace/root-refusal.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +150 -0
- package/dist/workspace/status.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +90 -0
- package/dist/workspace/template-manifest.d.ts.map +1 -0
- package/dist/workspace/tree.d.ts +36 -0
- package/dist/workspace/tree.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +67 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +111 -0
- package/dist/workspace/trust/policy.d.ts.map +1 -0
- package/dist/workspace/trust/provenance.d.ts +67 -0
- package/dist/workspace/trust/provenance.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +43 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
- package/dist/workspace/trust/test-repo.d.ts +42 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -0
- package/dist/workspace/trust/verify-cli.d.ts +9 -0
- package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
- package/dist/workspace/trust/verify.d.ts +66 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -0
- package/dist/workspace/which-chant.d.ts +58 -0
- package/dist/workspace/which-chant.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +133 -0
- package/package.json +4 -3
- package/src/audit/core.ts +13 -2
- package/src/audit/discover.ts +102 -36
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +81 -1
- package/src/audit/terraform-state.ts +25 -0
- package/src/cli/command-group.ts +7 -0
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +132 -29
- package/src/cli/commands/check-lexicon-examples.ts +2 -1
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +21 -0
- package/src/cli/commands/import-agents.ts +15 -4
- package/src/cli/commands/init.ts +76 -25
- package/src/cli/commands/lint.test.ts +52 -1
- package/src/cli/commands/lint.ts +41 -71
- package/src/cli/commands/onboard.ts +15 -0
- package/src/cli/commands/update.ts +18 -5
- package/src/cli/commands/vendor.ts +11 -6
- package/src/cli/conflict-check.test.ts +18 -1
- package/src/cli/conflict-check.ts +13 -0
- package/src/cli/discovery-skip.test.ts +168 -0
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
- package/src/cli/handlers/build-member.test.ts +127 -0
- package/src/cli/handlers/build.test.ts +11 -0
- package/src/cli/handlers/build.ts +35 -1
- package/src/cli/handlers/components.test.ts +127 -0
- package/src/cli/handlers/components.ts +70 -8
- package/src/cli/handlers/dev.ts +6 -1
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +4 -2
- package/src/cli/handlers/init.ts +54 -1
- package/src/cli/handlers/lifecycle.ts +2 -1
- package/src/cli/handlers/misc.ts +16 -0
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +39 -10
- package/src/cli/handlers/promote.test.ts +377 -0
- package/src/cli/handlers/promote.ts +391 -0
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +158 -0
- package/src/cli/handlers/run-generate.ts +168 -0
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +41 -6
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +137 -1
- package/src/cli/main.ts +290 -24
- package/src/cli/mcp/op-tools.ts +2 -1
- package/src/cli/path-lexicon-messages.test.ts +152 -0
- package/src/cli/plugins.ts +43 -5
- package/src/cli/registry.ts +46 -1
- package/src/cli/static-config-read.test.ts +208 -0
- package/src/codegen/fetch.test.ts +63 -1
- package/src/codegen/fetch.ts +30 -1
- package/src/components/__fixtures__/alb-ecs-service.json +0 -8
- package/src/components/capability-plugin-loader.ts +3 -1
- package/src/components/cli-support.ts +9 -3
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +20 -51
- package/src/components/driver.test.ts +90 -12
- package/src/components/driver.ts +43 -5
- package/src/components/fan-out-support.ts +2 -1
- package/src/components/gate-plan.ts +53 -0
- package/src/components/pilots/README.md +1 -1
- package/src/components/pilots/alb-ecs.pilot.ts +7 -7
- package/src/components/presets/ecs-fargate.ts +10 -5
- package/src/components/promote.test.ts +645 -0
- package/src/components/promote.ts +641 -0
- package/src/components/sandbox/driver.ts +8 -8
- package/src/components/sandbox/run.ts +4 -1
- package/src/components/verbs/build-archive.ts +27 -21
- package/src/components/verbs/run-agent.ts +3 -5
- package/src/config-static.test.ts +136 -0
- package/src/config-static.ts +232 -0
- package/src/config.test.ts +40 -0
- package/src/config.ts +161 -11
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +127 -1
- package/src/discovery/files.ts +91 -59
- package/src/discovery/fold-import.ts +127 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/sandbox/driver.ts +73 -35
- package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
- package/src/discovery/sandbox/fork.ts +83 -43
- package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
- package/src/discovery/sandbox/path-lexicon-parity.test.ts +162 -0
- package/src/discovery/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +18 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.test.ts +203 -0
- package/src/lexicon-module.ts +215 -0
- package/src/lexicon.ts +105 -1
- package/src/lifecycle/build-ledger-store.ts +13 -2
- package/src/lifecycle/digest.ts +9 -5
- package/src/lifecycle/gate-ledger.test.ts +20 -0
- package/src/lifecycle/gate-ledger.ts +28 -6
- package/src/lifecycle/git.test.ts +13 -1
- package/src/lifecycle/git.ts +221 -176
- package/src/lifecycle/lease.ts +25 -10
- package/src/lifecycle/legacy-digest.test.ts +206 -0
- package/src/lifecycle/legacy-digest.ts +103 -0
- package/src/lifecycle/member-ledger.test.ts +319 -0
- package/src/lifecycle/member-ledger.ts +145 -0
- package/src/lifecycle/plan-digest.ts +7 -7
- package/src/lifecycle/release-ledger.test.ts +20 -0
- package/src/lifecycle/release-ledger.ts +53 -3
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +2 -3
- package/src/op/activities/activity-contracts.ts +37 -0
- package/src/op/activities/index.ts +10 -0
- package/src/op/activities/propose-upgrade.ts +206 -0
- package/src/op/activity-contract-registry.ts +18 -4
- package/src/op/activity-registry.ts +26 -0
- package/src/op/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -23
- package/src/op/gate-approval.ts +3 -1
- package/src/op/gate-name.ts +8 -0
- package/src/op/gate-summary.test.ts +14 -0
- package/src/op/gate-summary.ts +25 -2
- package/src/op/gate.ts +36 -7
- package/src/op/generate-pipeline.test.ts +14 -5
- package/src/op/generate-pipeline.ts +2 -1
- package/src/op/runtimes/local.ts +2 -1
- package/src/project-root.test.ts +66 -1
- package/src/project-root.ts +48 -0
- package/src/workspace/__fixtures__/contract-repo.ts +100 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
- package/src/workspace/behold-kinds.test.ts +162 -0
- package/src/workspace/check-contract.test.ts +127 -0
- package/src/workspace/check.schema.json +347 -0
- package/src/workspace/checks/generated.test.ts +287 -0
- package/src/workspace/checks/generated.ts +475 -0
- package/src/workspace/checks/ledgers.test.ts +139 -0
- package/src/workspace/checks/ledgers.ts +246 -0
- package/src/workspace/checks/links.ts +183 -0
- package/src/workspace/checks/pipelines.test.ts +133 -0
- package/src/workspace/checks/pipelines.ts +225 -0
- package/src/workspace/checks-repo.test.ts +71 -0
- package/src/workspace/checks.test.ts +450 -0
- package/src/workspace/checks.ts +461 -0
- package/src/workspace/compose-graph.test.ts +146 -0
- package/src/workspace/compose-graph.ts +228 -0
- package/src/workspace/compose-reports.test.ts +109 -0
- package/src/workspace/compose-reports.ts +186 -0
- package/src/workspace/declaration.schema.json +434 -0
- package/src/workspace/declaration.test.ts +347 -0
- package/src/workspace/declaration.ts +623 -0
- package/src/workspace/generated-files.test.ts +114 -0
- package/src/workspace/generated-files.ts +94 -0
- package/src/workspace/glob.ts +49 -0
- package/src/workspace/graph-cli.ts +220 -0
- package/src/workspace/graph-contract.test.ts +204 -0
- package/src/workspace/graph.schema.json +662 -0
- package/src/workspace/init.test.ts +223 -0
- package/src/workspace/init.ts +585 -0
- package/src/workspace/jsonc.test.ts +65 -0
- package/src/workspace/jsonc.ts +231 -0
- package/src/workspace/kinds.test.ts +288 -0
- package/src/workspace/kinds.ts +506 -0
- package/src/workspace/lineage-check.ts +264 -0
- package/src/workspace/lineage-cli.ts +114 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +365 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +357 -0
- package/src/workspace/lineage-merge.ts +44 -0
- package/src/workspace/lineage-migrations.test.ts +178 -0
- package/src/workspace/lineage-migrations.ts +343 -0
- package/src/workspace/lineage-update.ts +160 -0
- package/src/workspace/lineage-upgrade-cli.ts +151 -0
- package/src/workspace/lineage-upgrade.test.ts +474 -0
- package/src/workspace/lineage-upgrade.ts +624 -0
- package/src/workspace/lineage-vendor.test.ts +126 -0
- package/src/workspace/lineage-vendor.ts +215 -0
- package/src/workspace/lineage-version.ts +127 -0
- package/src/workspace/links.test.ts +344 -0
- package/src/workspace/links.ts +331 -0
- package/src/workspace/ls-contract.test.ts +200 -0
- package/src/workspace/ls.schema.json +190 -0
- package/src/workspace/ls.ts +254 -0
- package/src/workspace/member-commands.e2e.test.ts +244 -0
- package/src/workspace/member-commands.test.ts +217 -0
- package/src/workspace/member-commands.ts +540 -0
- package/src/workspace/member-handles.test.ts +76 -0
- package/src/workspace/member-handles.ts +228 -0
- package/src/workspace/member-pipeline.test.ts +135 -0
- package/src/workspace/member-pipeline.ts +317 -0
- package/src/workspace/member-run.ts +146 -0
- package/src/workspace/read-contract.test.ts +129 -0
- package/src/workspace/reason-codes.test.ts +113 -0
- package/src/workspace/reason-codes.ts +81 -0
- package/src/workspace/record-source.ts +117 -0
- package/src/workspace/records-cli.ts +198 -0
- package/src/workspace/records-contract.test.ts +93 -0
- package/src/workspace/records.schema.json +150 -0
- package/src/workspace/records.test.ts +243 -0
- package/src/workspace/records.ts +384 -0
- package/src/workspace/root-refusal.ts +97 -0
- package/src/workspace/status-contract.test.ts +308 -0
- package/src/workspace/status.schema.json +210 -0
- package/src/workspace/status.ts +412 -0
- package/src/workspace/template-manifest.test.ts +79 -0
- package/src/workspace/template-manifest.ts +221 -0
- package/src/workspace/tree.ts +132 -0
- package/src/workspace/trust/attestor.ts +93 -0
- package/src/workspace/trust/policy.ts +306 -0
- package/src/workspace/trust/provenance.ts +181 -0
- package/src/workspace/trust/ssh-commit.ts +138 -0
- package/src/workspace/trust/test-repo.ts +138 -0
- package/src/workspace/trust/trust.test.ts +397 -0
- package/src/workspace/trust/verify-cli.ts +56 -0
- package/src/workspace/trust/verify.ts +194 -0
- package/src/workspace/which-chant.test.ts +117 -0
- package/src/workspace/which-chant.ts +151 -0
- package/src/workspace/workspace-kinds.schema.json +133 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant components promote --from <env> --to <env>` (#2530), `chant
|
|
3
|
+
* components rollback <env>` (#2531) and `chant components redeploy <env>`
|
|
4
|
+
* (#2604): deploy digests a release ledger already records, without a build.
|
|
5
|
+
* A promote takes them from another environment's ledger; a rollback takes an
|
|
6
|
+
* earlier release from the environment's own, and a redeploy the release it
|
|
7
|
+
* records as current. The
|
|
8
|
+
* mechanism lives in ../../components/promote.ts; these handlers read the
|
|
9
|
+
* ledger, print the plan, run it, and append the release records.
|
|
10
|
+
*
|
|
11
|
+
* Every component that deployed gets a record, even when a later component
|
|
12
|
+
* failed or stopped at a gate, so the ledger never misses a deploy that
|
|
13
|
+
* happened. Unlike an auto-recorded release, the record is what these
|
|
14
|
+
* commands are for, so a failed write is an error rather than a warning.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { resolve } from "node:path";
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { loadChantConfig, type ChantConfig } from "../../config";
|
|
20
|
+
import { fetchLifecycle, getHeadCommit, pushLifecycle } from "../../lifecycle/git";
|
|
21
|
+
import {
|
|
22
|
+
appendReleaseRecord,
|
|
23
|
+
readReleaseLedger,
|
|
24
|
+
resolveRunId,
|
|
25
|
+
type ReleaseRecord,
|
|
26
|
+
type ReleaseRecordInput,
|
|
27
|
+
} from "../../lifecycle/release-ledger";
|
|
28
|
+
import { resolveComponentTargets } from "../../components/cli-support";
|
|
29
|
+
import { applyConfigDefaults } from "../../components/config-defaults";
|
|
30
|
+
import { fanOutRegistry } from "../../components/fan-out-support";
|
|
31
|
+
import { renderDriverHuman } from "../../components/driver-output";
|
|
32
|
+
import { ndjsonProgressSink } from "../../components/run-progress";
|
|
33
|
+
import {
|
|
34
|
+
parseDigestPins,
|
|
35
|
+
planPromotion,
|
|
36
|
+
planRollback,
|
|
37
|
+
planRedeploy,
|
|
38
|
+
withoutBuildSteps,
|
|
39
|
+
runPromotion,
|
|
40
|
+
promotionRecord,
|
|
41
|
+
rollbackRecord,
|
|
42
|
+
redeployRecord,
|
|
43
|
+
gateApprover,
|
|
44
|
+
type DeployRunInfo,
|
|
45
|
+
type PromotionItem,
|
|
46
|
+
type PromotionPlan,
|
|
47
|
+
} from "../../components/promote";
|
|
48
|
+
import type { DriverComponent } from "../../components/driver";
|
|
49
|
+
import { summaryLedgerPrefix, writeGatedRunSummary } from "../../op/gate-summary";
|
|
50
|
+
import { approveCommand, describeGateMismatch } from "../../op/gate";
|
|
51
|
+
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
52
|
+
import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
|
|
53
|
+
import { GATED_EXIT_CODE } from "./run";
|
|
54
|
+
import type { CommandContext } from "../registry";
|
|
55
|
+
|
|
56
|
+
const PROMOTE_USAGE =
|
|
57
|
+
"chant components promote --from <env> --to <env> [--component <name> [--digest <sha256:...>] | --digest <component>=<sha256:...> ...] [--dry-run] [--json]";
|
|
58
|
+
const ROLLBACK_USAGE =
|
|
59
|
+
"chant components rollback <env> --component <name> [--digest <sha256:...>] [--dry-run] [--json]";
|
|
60
|
+
const REDEPLOY_USAGE =
|
|
61
|
+
"chant components redeploy <env> --component <name> [--digest <sha256:...>] [--dry-run] [--json]";
|
|
62
|
+
|
|
63
|
+
type Verb = "promote" | "rollback" | "redeploy";
|
|
64
|
+
|
|
65
|
+
function renderPlan(verb: Verb, plan: PromotionPlan, removed: Map<string, string[]>): void {
|
|
66
|
+
console.error(formatBold(verb === "promote" ? `promote ${plan.from} -> ${plan.to}` : `${verb} ${plan.to}`));
|
|
67
|
+
for (const item of plan.items) {
|
|
68
|
+
console.error(` ${item.component} ${item.digest}`);
|
|
69
|
+
console.error(` released to ${plan.from} at ${item.source.timestamp} (run ${item.source.runId}, git ${item.source.gitSha.slice(0, 12)})`);
|
|
70
|
+
const kinds = removed.get(item.component) ?? [];
|
|
71
|
+
if (kinds.length > 0) console.error(` not run: ${kinds.join(", ")}`);
|
|
72
|
+
}
|
|
73
|
+
for (const skipped of plan.notPromoted) {
|
|
74
|
+
console.error(` ${skipped.component} not promoted: ${skipped.reason}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function planJson(plan: PromotionPlan, removed: Map<string, string[]>) {
|
|
79
|
+
return {
|
|
80
|
+
from: plan.from,
|
|
81
|
+
to: plan.to,
|
|
82
|
+
items: plan.items.map((i) => ({
|
|
83
|
+
component: i.component,
|
|
84
|
+
digest: i.digest,
|
|
85
|
+
source: i.source,
|
|
86
|
+
notRun: removed.get(i.component) ?? [],
|
|
87
|
+
})),
|
|
88
|
+
notPromoted: plan.notPromoted,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveActor(ctx: CommandContext): string | undefined {
|
|
93
|
+
return ctx.args.actor ?? process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_LOGIN ?? process.env.USER;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Config, params and the declared components, or the exit code of a refusal already printed. */
|
|
97
|
+
async function loadProject(ctx: CommandContext, selector: string) {
|
|
98
|
+
const { args } = ctx;
|
|
99
|
+
const projectPath = resolve(".");
|
|
100
|
+
const { config } = await loadChantConfig(projectPath).catch(() => ({ config: {} as ChantConfig }));
|
|
101
|
+
const paramsResolution = resolveCliBuildParams(config.buildParams, {
|
|
102
|
+
cli: parseParamFlags(args.param),
|
|
103
|
+
paramsFile: args.paramsFile,
|
|
104
|
+
verbose: args.verbose,
|
|
105
|
+
});
|
|
106
|
+
if (!paramsResolution.success) {
|
|
107
|
+
for (const message of paramsResolution.errors) console.error(message);
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
const targets = await resolveComponentTargets(projectPath, selector, args.sandbox, paramsResolution.provenance);
|
|
111
|
+
if (!targets.success) {
|
|
112
|
+
console.error(formatError({ message: targets.error ?? "Could not discover components" }));
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
return { projectPath, config, targets: targets.targets };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function readLedger(env: string): Promise<ReleaseRecord[]> {
|
|
119
|
+
const ledger = await readReleaseLedger(env);
|
|
120
|
+
if (ledger.malformed > 0) {
|
|
121
|
+
console.error(formatWarning({ message: `${ledger.malformed} malformed line(s) in the "${env}" release ledger were skipped` }));
|
|
122
|
+
}
|
|
123
|
+
return ledger.records;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Prepare, run and record a plan. Shared by promote, rollback and redeploy,
|
|
128
|
+
* which differ only in how the plan was chosen, whether the publish step runs,
|
|
129
|
+
* and which field names the earlier release on the new record.
|
|
130
|
+
*/
|
|
131
|
+
async function deployPlan(
|
|
132
|
+
ctx: CommandContext,
|
|
133
|
+
verb: Verb,
|
|
134
|
+
plan: PromotionPlan,
|
|
135
|
+
project: { projectPath: string; config: ChantConfig; targets: DriverComponent[] },
|
|
136
|
+
actor: string | undefined,
|
|
137
|
+
record: (item: PromotionItem, env: string, run: DeployRunInfo) => ReleaseRecordInput,
|
|
138
|
+
): Promise<number> {
|
|
139
|
+
const { args } = ctx;
|
|
140
|
+
const byName = new Map(project.targets.map((c) => [c.name, applyConfigDefaults(c, project.config)]));
|
|
141
|
+
const prepared: DriverComponent[] = [];
|
|
142
|
+
const removed = new Map<string, string[]>();
|
|
143
|
+
const refusals: string[] = [];
|
|
144
|
+
for (const item of plan.items) {
|
|
145
|
+
// A rollback and a redeploy deploy a release this environment already
|
|
146
|
+
// received, so neither runs the publish step.
|
|
147
|
+
const result = withoutBuildSteps(
|
|
148
|
+
byName.get(item.component)!,
|
|
149
|
+
verb === "promote" ? {} : { pinDigest: item.digest, verb: `a ${verb}` },
|
|
150
|
+
);
|
|
151
|
+
if ("error" in result) refusals.push(result.error);
|
|
152
|
+
else {
|
|
153
|
+
prepared.push(result.component);
|
|
154
|
+
removed.set(item.component, result.removed);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (refusals.length > 0) {
|
|
158
|
+
for (const message of refusals) console.error(formatError({ message }));
|
|
159
|
+
console.error(formatError({ message: `nothing was deployed` }));
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// The composition deployed is this checkout's. Say so when it is not the
|
|
164
|
+
// commit the artifact was built from.
|
|
165
|
+
const head = await getHeadCommit().catch(() => undefined);
|
|
166
|
+
for (const item of plan.items) {
|
|
167
|
+
if (head && head !== item.source.gitSha) {
|
|
168
|
+
console.error(formatWarning({
|
|
169
|
+
message: `${item.component}: the artifact was built from ${item.source.gitSha.slice(0, 12)}, ` +
|
|
170
|
+
`but its deploy steps come from this checkout at ${head.slice(0, 12)}`,
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (args.dryRun) {
|
|
176
|
+
if (args.json) console.log(JSON.stringify(planJson(plan, removed), null, 2));
|
|
177
|
+
else renderPlan(verb, plan, removed);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
if (!args.json) renderPlan(verb, plan, removed);
|
|
181
|
+
|
|
182
|
+
const seededOutputs: Record<string, Record<string, unknown>> = {};
|
|
183
|
+
for (const file of args.seedOutputs ?? []) {
|
|
184
|
+
try {
|
|
185
|
+
Object.assign(seededOutputs, JSON.parse(readFileSync(resolve(file), "utf8")));
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error(formatError({ message: `--seed-outputs: could not read "${file}": ${err instanceof Error ? err.message : String(err)}` }));
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const registry = await fanOutRegistry(project.projectPath, project.config);
|
|
193
|
+
const run = await runPromotion({
|
|
194
|
+
plan,
|
|
195
|
+
components: prepared,
|
|
196
|
+
registry,
|
|
197
|
+
componentOutputs: seededOutputs,
|
|
198
|
+
...(args.progressJson ? { onProgress: ndjsonProgressSink() } : {}),
|
|
199
|
+
});
|
|
200
|
+
if (!args.json) renderDriverHuman(run);
|
|
201
|
+
|
|
202
|
+
// Record every component that deployed, whatever happened to the rest: a
|
|
203
|
+
// component that reached the environment and is missing from its ledger
|
|
204
|
+
// would be exactly the unrecorded deploy the ledger exists to rule out.
|
|
205
|
+
const deployed = plan.items.filter((item) => run.results.some((r) => r.component === item.component && r.status === "ok"));
|
|
206
|
+
const { runId, runOrigin } = resolveRunId(args.runId);
|
|
207
|
+
const timestamp = new Date().toISOString();
|
|
208
|
+
const recorded: ReleaseRecord[] = [];
|
|
209
|
+
let recordError: string | undefined;
|
|
210
|
+
if (deployed.length > 0) {
|
|
211
|
+
try {
|
|
212
|
+
for (const item of deployed) {
|
|
213
|
+
const approver = gateApprover(run.results.find((r) => r.component === item.component));
|
|
214
|
+
const { record: written } = await appendReleaseRecord(
|
|
215
|
+
record(item, plan.to, { runId, ...(runOrigin ? { runOrigin } : {}), actor: actor!, timestamp, ...(approver ? { approver } : {}) }),
|
|
216
|
+
);
|
|
217
|
+
recorded.push(written);
|
|
218
|
+
}
|
|
219
|
+
await pushLifecycle();
|
|
220
|
+
} catch (err) {
|
|
221
|
+
recordError = err instanceof Error ? err.message : String(err);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (args.json) {
|
|
226
|
+
console.log(JSON.stringify({ ...planJson(plan, removed), run, recorded }, null, 2));
|
|
227
|
+
} else {
|
|
228
|
+
for (const r of recorded) {
|
|
229
|
+
console.error(formatSuccess(
|
|
230
|
+
verb === "promote"
|
|
231
|
+
? `Promoted ${formatBold(r.component)} ${plan.from} -> ${plan.to}: ${r.digest}`
|
|
232
|
+
: verb === "rollback"
|
|
233
|
+
? `Rolled back ${formatBold(r.component)} in ${plan.to} to ${r.digest}`
|
|
234
|
+
: `Redeployed ${formatBold(r.component)} in ${plan.to} at ${r.digest}`,
|
|
235
|
+
));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (recordError !== undefined) {
|
|
240
|
+
console.error(formatError({
|
|
241
|
+
message: `deployed to "${plan.to}", but the release record was not written: ${recordError}`,
|
|
242
|
+
hint: `Pull the chant/lifecycle branch and check \`chant components status ${plan.to}\` before running this again.`,
|
|
243
|
+
}));
|
|
244
|
+
return 1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (run.status === "gated" && run.gate) {
|
|
248
|
+
const pending = run.gate;
|
|
249
|
+
console.error(formatWarning({ message: `component "${run.gatedComponent}" is gated on "${pending.gate}" — pending approval` }));
|
|
250
|
+
// #2574: an approval stands, but for another environment, plan or none.
|
|
251
|
+
if (run.gateMismatch) {
|
|
252
|
+
console.error(formatWarning({ message: describeGateMismatch(pending.op, pending.gate, run.gateMismatch) }));
|
|
253
|
+
}
|
|
254
|
+
console.error(formatInfo(`approve : ${approveCommand(pending.op, pending.gate, pending.environment)}`));
|
|
255
|
+
console.error(formatInfo(`then run the same ${verb} again`));
|
|
256
|
+
writeGatedRunSummary({
|
|
257
|
+
op: pending.op,
|
|
258
|
+
gate: pending.gate,
|
|
259
|
+
...(pending.description ? { description: pending.description } : {}),
|
|
260
|
+
expiresAt: pending.expiresAt,
|
|
261
|
+
...(pending.url ? { url: pending.url } : {}),
|
|
262
|
+
...(pending.planDigest ? { planDigest: pending.planDigest } : {}),
|
|
263
|
+
...(await summaryLedgerPrefix()),
|
|
264
|
+
...(pending.environment ? { environment: pending.environment } : {}),
|
|
265
|
+
});
|
|
266
|
+
return GATED_EXIT_CODE;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (run.status !== "ok") {
|
|
270
|
+
console.error(formatError({ message: `${verb} failed at "${run.failedComponent ?? "unknown"}"` }));
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Refuse before anything deploys when nobody can be named on the record. */
|
|
277
|
+
function actorOrRefuse(ctx: CommandContext): { actor: string | undefined } | number {
|
|
278
|
+
const actor = resolveActor(ctx);
|
|
279
|
+
if (!actor && !ctx.args.dryRun) {
|
|
280
|
+
console.error(formatError({
|
|
281
|
+
message: "Could not resolve --actor from the environment",
|
|
282
|
+
hint: "Pass --actor explicitly, or set GITHUB_ACTOR / GITLAB_USER_LOGIN / USER.",
|
|
283
|
+
}));
|
|
284
|
+
return 1;
|
|
285
|
+
}
|
|
286
|
+
return { actor };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function runComponentsPromote(ctx: CommandContext): Promise<number> {
|
|
290
|
+
const { args } = ctx;
|
|
291
|
+
const from = args.migrateFrom;
|
|
292
|
+
const to = args.migrateTo;
|
|
293
|
+
if (!from || !to) {
|
|
294
|
+
console.error(formatError({ message: "--from <env> and --to <env> are required", hint: PROMOTE_USAGE }));
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
297
|
+
const who = actorOrRefuse(ctx);
|
|
298
|
+
if (typeof who === "number") return who;
|
|
299
|
+
|
|
300
|
+
const project = await loadProject(ctx, args.component ?? "all");
|
|
301
|
+
if (typeof project === "number") return project;
|
|
302
|
+
|
|
303
|
+
// Without --component, each --digest is <component>=<digest> (#2602): a
|
|
304
|
+
// generated promote job pins every component to the release its own
|
|
305
|
+
// pipeline run recorded, rather than whatever is latest in --from.
|
|
306
|
+
const digests = args.digests ?? (args.digest ? [args.digest] : []);
|
|
307
|
+
let pins: Record<string, string> | undefined;
|
|
308
|
+
if (!args.component && digests.length > 0) {
|
|
309
|
+
const parsed = parseDigestPins(digests);
|
|
310
|
+
if ("error" in parsed) {
|
|
311
|
+
console.error(formatError({ message: parsed.error, hint: PROMOTE_USAGE }));
|
|
312
|
+
return 1;
|
|
313
|
+
}
|
|
314
|
+
pins = parsed.pins;
|
|
315
|
+
} else if (args.component && digests.length > 1) {
|
|
316
|
+
console.error(formatError({ message: "--component takes one --digest", hint: PROMOTE_USAGE }));
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
await fetchLifecycle().catch(() => false);
|
|
321
|
+
const plan = planPromotion({
|
|
322
|
+
from,
|
|
323
|
+
to,
|
|
324
|
+
sourceRecords: await readLedger(from),
|
|
325
|
+
declared: project.targets.map((c) => c.name),
|
|
326
|
+
...(args.component ? { component: args.component } : {}),
|
|
327
|
+
...(args.component && args.digest ? { digest: args.digest } : {}),
|
|
328
|
+
...(pins ? { pins } : {}),
|
|
329
|
+
});
|
|
330
|
+
if ("error" in plan) {
|
|
331
|
+
console.error(formatError({ message: plan.error, hint: PROMOTE_USAGE }));
|
|
332
|
+
return 1;
|
|
333
|
+
}
|
|
334
|
+
return deployPlan(ctx, "promote", plan, project, who.actor, promotionRecord);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export async function runComponentsRollback(ctx: CommandContext): Promise<number> {
|
|
338
|
+
const { args } = ctx;
|
|
339
|
+
const env = args.extraPositional;
|
|
340
|
+
if (!env || !args.component) {
|
|
341
|
+
console.error(formatError({ message: "an environment and --component <name> are required", hint: ROLLBACK_USAGE }));
|
|
342
|
+
return 1;
|
|
343
|
+
}
|
|
344
|
+
const who = actorOrRefuse(ctx);
|
|
345
|
+
if (typeof who === "number") return who;
|
|
346
|
+
|
|
347
|
+
const project = await loadProject(ctx, args.component);
|
|
348
|
+
if (typeof project === "number") return project;
|
|
349
|
+
|
|
350
|
+
await fetchLifecycle().catch(() => false);
|
|
351
|
+
const plan = planRollback({
|
|
352
|
+
env,
|
|
353
|
+
records: await readLedger(env),
|
|
354
|
+
declared: project.targets.map((c) => c.name),
|
|
355
|
+
component: args.component,
|
|
356
|
+
...(args.digest ? { digest: args.digest } : {}),
|
|
357
|
+
});
|
|
358
|
+
if ("error" in plan) {
|
|
359
|
+
console.error(formatError({ message: plan.error, hint: ROLLBACK_USAGE }));
|
|
360
|
+
return 1;
|
|
361
|
+
}
|
|
362
|
+
return deployPlan(ctx, "rollback", plan, project, who.actor, rollbackRecord);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export async function runComponentsRedeploy(ctx: CommandContext): Promise<number> {
|
|
366
|
+
const { args } = ctx;
|
|
367
|
+
const env = args.extraPositional;
|
|
368
|
+
if (!env || !args.component) {
|
|
369
|
+
console.error(formatError({ message: "an environment and --component <name> are required", hint: REDEPLOY_USAGE }));
|
|
370
|
+
return 1;
|
|
371
|
+
}
|
|
372
|
+
const who = actorOrRefuse(ctx);
|
|
373
|
+
if (typeof who === "number") return who;
|
|
374
|
+
|
|
375
|
+
const project = await loadProject(ctx, args.component);
|
|
376
|
+
if (typeof project === "number") return project;
|
|
377
|
+
|
|
378
|
+
await fetchLifecycle().catch(() => false);
|
|
379
|
+
const plan = planRedeploy({
|
|
380
|
+
env,
|
|
381
|
+
records: await readLedger(env),
|
|
382
|
+
declared: project.targets.map((c) => c.name),
|
|
383
|
+
component: args.component,
|
|
384
|
+
...(args.digest ? { digest: args.digest } : {}),
|
|
385
|
+
});
|
|
386
|
+
if ("error" in plan) {
|
|
387
|
+
console.error(formatError({ message: plan.error, hint: REDEPLOY_USAGE }));
|
|
388
|
+
return 1;
|
|
389
|
+
}
|
|
390
|
+
return deployPlan(ctx, "redeploy", plan, project, who.actor, redeployRecord);
|
|
391
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant run --generate` inside a workspace member (#2542). Discovery is
|
|
3
|
+
* stubbed; the lexicon plugins are the real ones.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import type { ParsedArgs } from "../registry";
|
|
10
|
+
import type { OpConfig } from "../../op/types";
|
|
11
|
+
import { twoMemberWorkspace } from "../../workspace/__fixtures__/two-member-workspace";
|
|
12
|
+
|
|
13
|
+
const discovered = new Map<string, { config: Partial<OpConfig>; filePath: string; exportName: string }>();
|
|
14
|
+
vi.mock("../../op/discover", () => ({
|
|
15
|
+
discoverOps: async () => ({ ops: discovered, errors: [] }),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const { runOp } = await import("./run");
|
|
19
|
+
const { generateOpsPipeline } = await import("../../op/generate-pipeline");
|
|
20
|
+
const { parseArgs } = await import("../main");
|
|
21
|
+
|
|
22
|
+
function ctx(argv: string[]) {
|
|
23
|
+
return { args: parseArgs(argv) as ParsedArgs, plugins: [], serializers: [] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let repo: string;
|
|
27
|
+
let cwd: string;
|
|
28
|
+
let logs: string[];
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
repo = twoMemberWorkspace();
|
|
31
|
+
cwd = process.cwd();
|
|
32
|
+
discovered.clear();
|
|
33
|
+
discovered.set("nightly-audit", {
|
|
34
|
+
config: { name: "nightly-audit", phases: [], schedule: { cron: "0 6 * * *" } } as Partial<OpConfig>,
|
|
35
|
+
filePath: "nightly-audit.op.ts",
|
|
36
|
+
exportName: "default",
|
|
37
|
+
});
|
|
38
|
+
logs = [];
|
|
39
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
40
|
+
vi.spyOn(console, "log").mockImplementation((s: string) => {
|
|
41
|
+
logs.push(s);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
process.chdir(cwd);
|
|
46
|
+
vi.restoreAllMocks();
|
|
47
|
+
rmSync(repo, { recursive: true, force: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("chant run --generate in a workspace member", () => {
|
|
51
|
+
test("github: the file is named after the member, at the repository root, and recorded", async () => {
|
|
52
|
+
process.chdir(join(repo, "services/api"));
|
|
53
|
+
expect(await runOp(ctx(["run", "--generate", "github"]))).toBe(0);
|
|
54
|
+
|
|
55
|
+
expect(readdirSync(join(repo, ".github/workflows"))).toEqual(["api-nightly-audit.yml"]);
|
|
56
|
+
expect(existsSync(join(repo, "services/api/.github"))).toBe(false);
|
|
57
|
+
const member = { name: "api", dir: "services/api", fileDir: ".github/workflows" };
|
|
58
|
+
const direct = await generateOpsPipeline([{ name: "nightly-audit" }], "github", { member });
|
|
59
|
+
const text = readFileSync(join(repo, ".github/workflows/api-nightly-audit.yml"), "utf-8");
|
|
60
|
+
expect(text).toBe(
|
|
61
|
+
"# Generated by chant — do not edit directly. Regenerate with: chant run --generate github (in the member's directory)\n" + direct.files![0].yaml,
|
|
62
|
+
);
|
|
63
|
+
const record = JSON.parse(readFileSync(join(repo, "services/api/.chant/generated.json"), "utf-8"));
|
|
64
|
+
expect(record.files).toEqual([{ path: ".github/workflows/api-nightly-audit.yml", command: "chant run --generate github" }]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("gitlab: the member's file goes in .gitlab/ci", async () => {
|
|
68
|
+
process.chdir(join(repo, "apps/web"));
|
|
69
|
+
expect(await runOp(ctx(["run", "--generate", "gitlab"]))).toBe(0);
|
|
70
|
+
const text = readFileSync(join(repo, ".gitlab/ci/web-ops.gitlab-ci.yml"), "utf-8");
|
|
71
|
+
expect(text).toContain("web-nightly-audit:");
|
|
72
|
+
expect(text).toContain("cd apps/web");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("--format json names the member and writes nothing", async () => {
|
|
76
|
+
process.chdir(join(repo, "services/api"));
|
|
77
|
+
expect(await runOp(ctx(["run", "--generate", "forgejo", "--format", "json"]))).toBe(0);
|
|
78
|
+
const out = JSON.parse(logs.join("\n"));
|
|
79
|
+
expect(out.member).toBe("api");
|
|
80
|
+
expect(out.files.map((f: { path: string }) => f.path)).toEqual([".forgejo/workflows/api-nightly-audit.yml"]);
|
|
81
|
+
expect(existsSync(join(repo, ".forgejo"))).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant run --generate <provider>` (#2533). Discovery is stubbed so the
|
|
3
|
+
* scheduled Ops are fixed; the github, gitlab and forgejo lexicon plugins are
|
|
4
|
+
* the real ones, so "identical to calling `generateOpsPipeline` directly" is
|
|
5
|
+
* checked against the real generators.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
|
|
9
|
+
import { mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import type { ParsedArgs } from "../registry";
|
|
13
|
+
import type { OpConfig } from "../../op/types";
|
|
14
|
+
|
|
15
|
+
const discovered = new Map<string, { config: Partial<OpConfig>; filePath: string; exportName: string }>();
|
|
16
|
+
|
|
17
|
+
vi.mock("../../op/discover", () => ({
|
|
18
|
+
discoverOps: async () => ({ ops: discovered, errors: [] }),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const { runOp } = await import("./run");
|
|
22
|
+
const { generateOpsPipeline } = await import("../../op/generate-pipeline");
|
|
23
|
+
const { GENERATED_MARKER } = await import("../../discovery/files");
|
|
24
|
+
const { parseArgs } = await import("../main");
|
|
25
|
+
|
|
26
|
+
function ctx(argv: string[]) {
|
|
27
|
+
const args = parseArgs(argv) as ParsedArgs;
|
|
28
|
+
return { args, plugins: [], serializers: [] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let dir: string;
|
|
32
|
+
let errSpy: ReturnType<typeof vi.spyOn>;
|
|
33
|
+
let logSpy: ReturnType<typeof vi.spyOn>;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
dir = mkdtempSync(join(tmpdir(), "chant-run-generate-"));
|
|
37
|
+
discovered.clear();
|
|
38
|
+
discovered.set("nightly-audit", {
|
|
39
|
+
config: { name: "nightly-audit", phases: [], schedule: { cron: "0 6 * * *" } } as Partial<OpConfig>,
|
|
40
|
+
filePath: "nightly-audit.op.ts",
|
|
41
|
+
exportName: "default",
|
|
42
|
+
});
|
|
43
|
+
discovered.set("weekly-report", {
|
|
44
|
+
config: { name: "weekly-report", phases: [], schedule: { cron: "0 7 * * 1" } } as Partial<OpConfig>,
|
|
45
|
+
filePath: "weekly-report.op.ts",
|
|
46
|
+
exportName: "default",
|
|
47
|
+
});
|
|
48
|
+
discovered.set("deploy", {
|
|
49
|
+
config: { name: "deploy", phases: [] } as Partial<OpConfig>,
|
|
50
|
+
filePath: "deploy.op.ts",
|
|
51
|
+
exportName: "default",
|
|
52
|
+
});
|
|
53
|
+
errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
54
|
+
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
errSpy.mockRestore();
|
|
59
|
+
logSpy.mockRestore();
|
|
60
|
+
rmSync(dir, { recursive: true, force: true });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("chant run --generate <provider>", () => {
|
|
64
|
+
test.each(["github", "gitlab", "forgejo"])(
|
|
65
|
+
"%s: each file is the generator's own output under one marker line",
|
|
66
|
+
async (provider) => {
|
|
67
|
+
const out = join(dir, provider);
|
|
68
|
+
expect(await runOp(ctx(["run", "--generate", provider, "--output", out]))).toBe(0);
|
|
69
|
+
|
|
70
|
+
const direct = await generateOpsPipeline([{ name: "nightly-audit" }, { name: "weekly-report" }], provider);
|
|
71
|
+
expect(direct.success).toBe(true);
|
|
72
|
+
const header = `# ${GENERATED_MARKER}. Regenerate with: chant run --generate ${provider}\n`;
|
|
73
|
+
|
|
74
|
+
expect(readdirSync(out).sort()).toEqual(direct.files!.map((f) => f.name).sort());
|
|
75
|
+
for (const file of direct.files!) {
|
|
76
|
+
expect(readFileSync(join(out, file.name), "utf-8")).toBe(header + file.yaml);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
test("github and forgejo get one file per scheduled Op; an Op with no schedule is left out", async () => {
|
|
82
|
+
for (const provider of ["github", "forgejo"]) {
|
|
83
|
+
const out = join(dir, provider);
|
|
84
|
+
expect(await runOp(ctx(["run", "--generate", provider, "--output", out]))).toBe(0);
|
|
85
|
+
expect(readdirSync(out).sort()).toEqual(["nightly-audit.yml", "weekly-report.yml"]);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("each github (#2580) and forgejo (#2601) workflow is named after its Op", async () => {
|
|
90
|
+
for (const provider of ["github", "forgejo"]) {
|
|
91
|
+
expect(await runOp(ctx(["run", "--generate", provider, "--output", join(dir, provider)]))).toBe(0);
|
|
92
|
+
const header = `# ${GENERATED_MARKER}. Regenerate with: chant run --generate ${provider}\n`;
|
|
93
|
+
for (const op of ["nightly-audit", "weekly-report"]) {
|
|
94
|
+
const written = readFileSync(join(dir, provider, `${op}.yml`), "utf-8");
|
|
95
|
+
expect(written.startsWith(`${header}name: ${op}\n\non:\n`)).toBe(true);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("--spec passes its ops and options to the generator unchanged", async () => {
|
|
101
|
+
const specFile = join(dir, "ops.json");
|
|
102
|
+
const spec = {
|
|
103
|
+
ops: [
|
|
104
|
+
{ name: "deploy", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "comment" },
|
|
105
|
+
{ name: "nightly-audit" },
|
|
106
|
+
],
|
|
107
|
+
options: { opsStage: "chant" },
|
|
108
|
+
};
|
|
109
|
+
writeFileSync(specFile, JSON.stringify(spec));
|
|
110
|
+
const out = join(dir, "gitlab");
|
|
111
|
+
expect(await runOp(ctx(["run", "--generate", "gitlab", "--spec", specFile, "--output", out]))).toBe(0);
|
|
112
|
+
|
|
113
|
+
const direct = await generateOpsPipeline(spec.ops as never, "gitlab", spec.options);
|
|
114
|
+
expect(direct.files!.map((f) => f.name)).toEqual(["chant.gitlab-ci.yml"]);
|
|
115
|
+
const written = readFileSync(join(out, "chant.gitlab-ci.yml"), "utf-8");
|
|
116
|
+
expect(written).toBe(
|
|
117
|
+
`# ${GENERATED_MARKER}. Regenerate with: chant run --generate gitlab --spec ${specFile}\n` + direct.files![0].yaml,
|
|
118
|
+
);
|
|
119
|
+
expect(written).toContain('$CI_PIPELINE_SOURCE == "merge_request_event"');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("--format json prints the files and jobs and writes nothing", async () => {
|
|
123
|
+
const out = join(dir, "none");
|
|
124
|
+
expect(await runOp(ctx(["run", "--generate", "github", "--output", out, "--format", "json"]))).toBe(0);
|
|
125
|
+
const printed = JSON.parse(String(logSpy.mock.calls[0][0]));
|
|
126
|
+
expect(printed.files.map((f: { path: string }) => f.path)).toEqual([
|
|
127
|
+
join(out, "nightly-audit.yml"),
|
|
128
|
+
join(out, "weekly-report.yml"),
|
|
129
|
+
]);
|
|
130
|
+
expect(printed.files[0].content.startsWith(`# ${GENERATED_MARKER}`)).toBe(true);
|
|
131
|
+
expect(printed.jobs).toHaveLength(2);
|
|
132
|
+
expect(() => readdirSync(out)).toThrow();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("with no scheduled Op and no --spec, it fails and says what to do", async () => {
|
|
136
|
+
discovered.delete("nightly-audit");
|
|
137
|
+
discovered.delete("weekly-report");
|
|
138
|
+
expect(await runOp(ctx(["run", "--generate", "github", "--output", dir]))).toBe(1);
|
|
139
|
+
expect(String(errSpy.mock.calls.flat().join("\n"))).toMatch(/No Op declares a schedule/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("a provider with no Op generator fails by name", async () => {
|
|
143
|
+
expect(await runOp(ctx(["run", "--generate", "aws", "--output", dir]))).toBe(1);
|
|
144
|
+
expect(String(errSpy.mock.calls.flat().join("\n"))).toMatch(/does not support Op generate mode/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("a --spec entry without a name is refused", async () => {
|
|
148
|
+
const specFile = join(dir, "bad.json");
|
|
149
|
+
writeFileSync(specFile, JSON.stringify([{ trigger: { kind: "push" } }]));
|
|
150
|
+
expect(await runOp(ctx(["run", "--generate", "github", "--spec", specFile, "--output", dir]))).toBe(1);
|
|
151
|
+
expect(String(errSpy.mock.calls.flat().join("\n"))).toMatch(/entry 0 has no "name"/);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("--components with --generate on run is refused", async () => {
|
|
155
|
+
expect(await runOp(ctx(["run", "--components", "--generate", "github"]))).toBe(1);
|
|
156
|
+
expect(String(errSpy.mock.calls.flat().join("\n"))).toMatch(/build --components --generate/);
|
|
157
|
+
});
|
|
158
|
+
});
|