@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,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant build` and `chant lint` at the root of a declared workspace (#2537,
|
|
3
|
+
* #2524 D0 and D12, ws-023).
|
|
4
|
+
*
|
|
5
|
+
* Once `chant.workspace.json` declares members, their directories leave the
|
|
6
|
+
* root project. A top-level `chant build` there would quietly build less than
|
|
7
|
+
* it did before the file existed, so both commands refuse with `WSP000` and
|
|
8
|
+
* point to `chant workspace build` and `chant workspace lint`. `--root-only`,
|
|
9
|
+
* or `rootOnly: true` in the root's `chant.config`, lets them run on the root
|
|
10
|
+
* project alone, with the member directories and example-group matches left
|
|
11
|
+
* out of discovery.
|
|
12
|
+
*
|
|
13
|
+
* `main.ts` decides with `findWorkspaceRoot` and `findProjectRoot`, which only
|
|
14
|
+
* test whether files exist, and imports this module only when the command's
|
|
15
|
+
* project is a declared root. A project with no declaration never loads it.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { relative } from "node:path";
|
|
19
|
+
import { formatError } from "../cli/format";
|
|
20
|
+
import { ownerOf, readDeclaration, resolveGroups, rootExclusions, WorkspaceReadError } from "./declaration";
|
|
21
|
+
import { workingTree } from "./tree";
|
|
22
|
+
|
|
23
|
+
/** The code a refusal carries. `WSP` ids name workspace findings (#2524 D16); `000` is the refusal itself. */
|
|
24
|
+
export const ROOT_REFUSAL_CODE = "WSP000";
|
|
25
|
+
|
|
26
|
+
export interface RootGuardInput {
|
|
27
|
+
verb: "build" | "lint";
|
|
28
|
+
/** The command's target, absolute. */
|
|
29
|
+
target: string;
|
|
30
|
+
/** The directory holding the declaration, absolute. */
|
|
31
|
+
workspaceDir: string;
|
|
32
|
+
/** `--root-only` on the command line, or `rootOnly: true` in the root's chant.config. */
|
|
33
|
+
rootOnly: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type RootGuardResult =
|
|
37
|
+
/** The target belongs to a member or an example group, so it is not the root project; nothing changes. */
|
|
38
|
+
| { action: "not-root" }
|
|
39
|
+
/** The command runs on the root project with these directories (relative to the root) left out. */
|
|
40
|
+
| { action: "root-only"; excluded: string[] }
|
|
41
|
+
/** The command stops; `message` says why and what to run instead. */
|
|
42
|
+
| { action: "refuse"; message: string };
|
|
43
|
+
|
|
44
|
+
/** Decide what a root `build` or `lint` does. Pure apart from reading the declaration. */
|
|
45
|
+
export function decideRootCommand(input: RootGuardInput): RootGuardResult {
|
|
46
|
+
const tree = workingTree(input.workspaceDir);
|
|
47
|
+
let declaration;
|
|
48
|
+
let groups;
|
|
49
|
+
try {
|
|
50
|
+
declaration = readDeclaration(tree);
|
|
51
|
+
groups = resolveGroups(declaration, tree);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (!(err instanceof WorkspaceReadError)) throw err;
|
|
54
|
+
return {
|
|
55
|
+
action: "refuse",
|
|
56
|
+
message:
|
|
57
|
+
`${ROOT_REFUSAL_CODE}: ${input.workspaceDir} holds a workspace declaration that can't be read, so chant ${input.verb} ` +
|
|
58
|
+
`can't tell which directories belong to the root project. ${err.code}: ${err.describe()}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const rel = relative(input.workspaceDir, input.target).split("\\").join("/") || ".";
|
|
62
|
+
const owner = ownerOf(declaration, groups, rel === "." ? "." : rel);
|
|
63
|
+
if (owner && ("group" in owner || owner.member.dir !== ".")) return { action: "not-root" };
|
|
64
|
+
|
|
65
|
+
const excluded = rootExclusions(declaration, groups).map((e) => e.dir);
|
|
66
|
+
if (input.rootOnly) return { action: "root-only", excluded };
|
|
67
|
+
|
|
68
|
+
const members = declaration.members.filter((m) => m.dir !== ".").length;
|
|
69
|
+
const matches = groups.reduce((n, g) => n + g.matches.length, 0);
|
|
70
|
+
const moved = [
|
|
71
|
+
`${members} member${members === 1 ? "" : "s"}`,
|
|
72
|
+
...(matches > 0 ? [`${matches} example project${matches === 1 ? "" : "s"}`] : []),
|
|
73
|
+
].join(" and ");
|
|
74
|
+
return {
|
|
75
|
+
action: "refuse",
|
|
76
|
+
message:
|
|
77
|
+
`${ROOT_REFUSAL_CODE}: this is the root of workspace ${declaration.name} (${declaration.file}), which moves ` +
|
|
78
|
+
`${moved} out of the root project. Run chant workspace ${input.verb} to ${input.verb} each member with its own ` +
|
|
79
|
+
`toolchain, or pass --root-only (or set rootOnly: true in chant.config) to ${input.verb} the root project alone.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Apply {@link decideRootCommand}: print the refusal and return 1, or return
|
|
85
|
+
* undefined so the command goes on. A root-only run needs nothing set up:
|
|
86
|
+
* every discovery walk under a declaration already leaves the member
|
|
87
|
+
* directories and group matches out (#2527, `workspaceMemberDirs` in
|
|
88
|
+
* `../discovery/walk.ts`), and `excluded` names the same directories.
|
|
89
|
+
*/
|
|
90
|
+
export function guardRootCommand(input: RootGuardInput): number | undefined {
|
|
91
|
+
const result = decideRootCommand(input);
|
|
92
|
+
if (result.action === "refuse") {
|
|
93
|
+
console.error(formatError({ message: result.message }));
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The read contract for `chant workspace status --json` (#2544, #2524 D15,
|
|
3
|
+
* D19): the output schema is a valid draft 2020-12 document, its closed code
|
|
4
|
+
* lists match the code, and real output validates against it, for workspaces
|
|
5
|
+
* built here with a `chant/lifecycle` branch holding member ledgers in both
|
|
6
|
+
* layouts, and for the chant repo's own declaration (#2557).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import Ajv2020 from "ajv/dist/2020";
|
|
14
|
+
import { afterAll, describe, expect, test, vi } from "vitest";
|
|
15
|
+
import type { CommandContext } from "../cli/registry";
|
|
16
|
+
import { listWorkspace } from "./ls";
|
|
17
|
+
import {
|
|
18
|
+
formatStatus,
|
|
19
|
+
runWorkspaceStatus,
|
|
20
|
+
STATUS_CONTRACT_VERSION,
|
|
21
|
+
STATUS_ERROR_CODES,
|
|
22
|
+
STATUS_OUTPUT_SCHEMA_ID,
|
|
23
|
+
STATUS_REASON_CODES,
|
|
24
|
+
workspaceStatus,
|
|
25
|
+
type StatusDocument,
|
|
26
|
+
} from "./status";
|
|
27
|
+
import schema from "./status.schema.json";
|
|
28
|
+
|
|
29
|
+
const REPO = join(import.meta.dirname, "..", "..", "..", "..");
|
|
30
|
+
|
|
31
|
+
const ajv = new Ajv2020({ strict: true, allErrors: true });
|
|
32
|
+
const validate = ajv.compile(schema);
|
|
33
|
+
|
|
34
|
+
function expectValid(doc: StatusDocument): void {
|
|
35
|
+
const ok = validate(doc);
|
|
36
|
+
expect(ok, JSON.stringify(validate.errors, null, 2)).toBe(true);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function result(doc: StatusDocument): Extract<StatusDocument, { members: unknown }> {
|
|
40
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
41
|
+
return doc;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const scratch: string[] = [];
|
|
45
|
+
afterAll(() => {
|
|
46
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function git(cwd: string, args: string[], env?: NodeJS.ProcessEnv, input?: string): string {
|
|
50
|
+
return execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", ...args], {
|
|
51
|
+
cwd,
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
input,
|
|
54
|
+
env: { ...process.env, ...env },
|
|
55
|
+
stdio: [input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
56
|
+
}).trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A git repository holding `files`, committed. */
|
|
60
|
+
function repo(files: Record<string, string>): string {
|
|
61
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-status-")));
|
|
62
|
+
scratch.push(root);
|
|
63
|
+
for (const [path, text] of Object.entries(files)) {
|
|
64
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
65
|
+
writeFileSync(join(root, path), text);
|
|
66
|
+
}
|
|
67
|
+
git(root, ["init", "-q", "-b", "main"]);
|
|
68
|
+
git(root, ["add", "-A"]);
|
|
69
|
+
git(root, ["commit", "-q", "--allow-empty", "-m", "init"]);
|
|
70
|
+
return root;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Write `files` as the whole tree of a `chant/lifecycle` commit, the way the
|
|
75
|
+
* lifecycle code stores ledgers, without touching the working tree or index.
|
|
76
|
+
*/
|
|
77
|
+
function lifecycle(root: string, files: Record<string, string>): string {
|
|
78
|
+
const index = join(root, ".git", "status-test-index");
|
|
79
|
+
const env = { GIT_INDEX_FILE: index };
|
|
80
|
+
rmSync(index, { force: true });
|
|
81
|
+
for (const [path, text] of Object.entries(files)) {
|
|
82
|
+
const blob = git(root, ["hash-object", "-w", "--stdin"], env, text);
|
|
83
|
+
git(root, ["update-index", "--add", "--cacheinfo", `100644,${blob},${path}`], env);
|
|
84
|
+
}
|
|
85
|
+
const tree = git(root, ["write-tree"], env);
|
|
86
|
+
const commit = git(root, ["commit-tree", tree, "-m", "ledger"], env);
|
|
87
|
+
git(root, ["update-ref", "refs/heads/chant/lifecycle", commit]);
|
|
88
|
+
rmSync(index, { force: true });
|
|
89
|
+
return commit;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let n = 0;
|
|
93
|
+
/** One ledger line. */
|
|
94
|
+
function release(component: string, env: string, digest: string, gitSha: string, extra: Record<string, string> = {}): string {
|
|
95
|
+
n++;
|
|
96
|
+
return JSON.stringify({
|
|
97
|
+
version: 1,
|
|
98
|
+
component,
|
|
99
|
+
env,
|
|
100
|
+
digest,
|
|
101
|
+
gitSha,
|
|
102
|
+
runId: `run-${n}`,
|
|
103
|
+
timestamp: `2026-09-${String(10 + n).padStart(2, "0")}T00:00:00.000Z`,
|
|
104
|
+
actor: "ci",
|
|
105
|
+
...extra,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const jsonl = (...lines: string[]) => lines.join("\n") + "\n";
|
|
109
|
+
|
|
110
|
+
const D = (c: string) => `sha256:${c.repeat(64)}`;
|
|
111
|
+
|
|
112
|
+
const declaration = (members: unknown[]) => JSON.stringify({ name: "acme", schema: 1, members }, null, 2);
|
|
113
|
+
|
|
114
|
+
/** web has moved to _members/ (#2538); api and the root member still read the flat ledger. */
|
|
115
|
+
function twoLayouts(): string {
|
|
116
|
+
const root = repo({
|
|
117
|
+
"chant.workspace.json": declaration([
|
|
118
|
+
{ name: "site", dir: ".", kind: "other", because: "the root project" },
|
|
119
|
+
{ name: "web", dir: "apps/web", kind: "chant" },
|
|
120
|
+
{ name: "api", dir: "apps/api", kind: "chant" },
|
|
121
|
+
{ name: "examples", kind: "examples", glob: "examples/*" },
|
|
122
|
+
]),
|
|
123
|
+
"apps/web/chant.config.ts": "",
|
|
124
|
+
"apps/api/chant.config.ts": "",
|
|
125
|
+
});
|
|
126
|
+
lifecycle(root, {
|
|
127
|
+
"_members/web/staging/releases.jsonl": jsonl(release("web", "staging", D("0"), "0".repeat(40)), release("web", "staging", D("a"), "a".repeat(40))),
|
|
128
|
+
"_members/web/prod/releases.jsonl": jsonl(release("web", "prod", D("a"), "a".repeat(40))),
|
|
129
|
+
"staging/releases.jsonl": jsonl(
|
|
130
|
+
release("api", "staging", D("b"), "b".repeat(40)),
|
|
131
|
+
release("worker", "staging", D("d"), "d".repeat(40)),
|
|
132
|
+
),
|
|
133
|
+
"prod/releases.jsonl": jsonl(release("api", "prod", D("c"), "c".repeat(40))),
|
|
134
|
+
});
|
|
135
|
+
return root;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
describe("status output schema", () => {
|
|
139
|
+
test("is a valid draft 2020-12 document with the published $id", () => {
|
|
140
|
+
expect(schema.$schema).toBe("https://json-schema.org/draft/2020-12/schema");
|
|
141
|
+
expect(ajv.validateSchema(schema)).toBe(true);
|
|
142
|
+
expect(schema.$id).toBe(STATUS_OUTPUT_SCHEMA_ID);
|
|
143
|
+
expect(STATUS_CONTRACT_VERSION).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("lists exactly the reason and error codes the code can return", () => {
|
|
147
|
+
expect(schema.$defs.environment.properties.reason.oneOf[1].properties!.code.enum).toEqual([...STATUS_REASON_CODES]);
|
|
148
|
+
expect(schema.$defs.failure.properties.error.properties.code.enum).toEqual([...STATUS_ERROR_CODES]);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("chant workspace status on built workspaces", () => {
|
|
153
|
+
test("lists each member's latest release per environment, from _members/ and from the flat layout", async () => {
|
|
154
|
+
const root = twoLayouts();
|
|
155
|
+
const doc = result(await workspaceStatus({ cwd: join(root, "apps", "web"), env: "staging" }));
|
|
156
|
+
expectValid(doc);
|
|
157
|
+
expect(doc.lifecycle.commit).toBe(git(root, ["rev-parse", "chant/lifecycle"]));
|
|
158
|
+
expect(doc.members.map((m) => m.name)).toEqual(["site", "web", "api"]);
|
|
159
|
+
const [site, web, api] = doc.members;
|
|
160
|
+
expect(web.environments).toHaveLength(1);
|
|
161
|
+
expect(web.environments[0].ledger).toEqual({ layout: "members", path: "_members/web/staging/releases.jsonl", shared: false });
|
|
162
|
+
// The later of the two records wins.
|
|
163
|
+
expect(web.environments[0].releases.map((r) => [r.component, r.digest, r.gitSha])).toEqual([["web", D("a"), "a".repeat(40)]]);
|
|
164
|
+
// api has no _members/api/ yet, so it falls back to the flat ledger, which the root member reads too.
|
|
165
|
+
expect(api.environments[0].ledger).toEqual({ layout: "flat", path: "staging/releases.jsonl", shared: true });
|
|
166
|
+
expect(site.environments[0].ledger).toEqual({ layout: "flat", path: "staging/releases.jsonl", shared: true });
|
|
167
|
+
expect(api.environments[0].releases.map((r) => r.component)).toEqual(["api", "worker"]);
|
|
168
|
+
expect(web.compare).toBeNull();
|
|
169
|
+
expect(doc.summary).toEqual({ members: 3, released: 3, unreadable: 0, differing: null });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("--compare-to shows both environments and marks the members whose digests differ", async () => {
|
|
173
|
+
const root = twoLayouts();
|
|
174
|
+
const doc = result(await workspaceStatus({ cwd: root, env: "staging", compareTo: "prod" }));
|
|
175
|
+
expectValid(doc);
|
|
176
|
+
const web = doc.members.find((m) => m.name === "web")!;
|
|
177
|
+
const api = doc.members.find((m) => m.name === "api")!;
|
|
178
|
+
expect(web.environments.map((e) => [e.env, e.ledger.path])).toEqual([
|
|
179
|
+
["staging", "_members/web/staging/releases.jsonl"],
|
|
180
|
+
["prod", "_members/web/prod/releases.jsonl"],
|
|
181
|
+
]);
|
|
182
|
+
expect(web.compare).toEqual({ differs: false, components: [{ component: "web", state: "same", digest: D("a"), compareDigest: D("a") }] });
|
|
183
|
+
expect(api.compare).toEqual({
|
|
184
|
+
differs: true,
|
|
185
|
+
components: [
|
|
186
|
+
{ component: "api", state: "differs", digest: D("b"), compareDigest: D("c") },
|
|
187
|
+
{ component: "worker", state: "only-env", digest: D("d"), compareDigest: null },
|
|
188
|
+
],
|
|
189
|
+
});
|
|
190
|
+
expect(doc.summary).toEqual({ members: 3, released: 3, unreadable: 0, differing: 2 });
|
|
191
|
+
|
|
192
|
+
const text = formatStatus(doc);
|
|
193
|
+
expect(text).toContain("acme staging compared to prod (chant/lifecycle at ");
|
|
194
|
+
expect(text).toMatch(/web\s+web\s+sha256:aaaaaaaaaaaa aaaaaaaa\s+sha256:aaaaaaaaaaaa aaaaaaaa\n/);
|
|
195
|
+
expect(text).toMatch(/api\s+api\s+sha256:bbbbbbbbbbbb bbbbbbbb\s+sha256:cccccccccccc cccccccc\s+differs/);
|
|
196
|
+
expect(text).toMatch(/api\s+worker\s+sha256:dddddddddddd dddddddd\s+-\s+only-env/);
|
|
197
|
+
expect(text).toContain("3 members, 3 with a release in staging, 2 differ from prod, 0 unreadable");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("compares on the input digest when a release has one", async () => {
|
|
201
|
+
const root = repo({ "chant.workspace.json": declaration([{ name: "chart", dir: "chart", kind: "other", because: "helm" }]) });
|
|
202
|
+
lifecycle(root, {
|
|
203
|
+
"staging/releases.jsonl": jsonl(release("chart", "staging", D("1"), "1".repeat(40), { inputDigest: D("9") })),
|
|
204
|
+
"prod/releases.jsonl": jsonl(release("chart", "prod", D("2"), "1".repeat(40), { inputDigest: D("9") })),
|
|
205
|
+
});
|
|
206
|
+
const doc = result(await workspaceStatus({ cwd: root, env: "staging", compareTo: "prod" }));
|
|
207
|
+
expectValid(doc);
|
|
208
|
+
expect(doc.members[0].compare?.components[0]).toMatchObject({ state: "same", digest: D("1"), compareDigest: D("2") });
|
|
209
|
+
expect(doc.members[0].environments[0].ledger.shared).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("a member whose ledger can't be read is listed with a reason code, and the read still succeeds", async () => {
|
|
213
|
+
const root = twoLayouts();
|
|
214
|
+
lifecycle(root, {
|
|
215
|
+
"_members/web/staging/releases.jsonl": jsonl(release("web", "staging", D("a"), "a".repeat(40)), "{not json", JSON.stringify({ component: "web" })),
|
|
216
|
+
"staging/releases.jsonl": jsonl(release("api", "staging", D("b"), "b".repeat(40))),
|
|
217
|
+
});
|
|
218
|
+
const doc = result(
|
|
219
|
+
await workspaceStatus({
|
|
220
|
+
cwd: root,
|
|
221
|
+
env: "staging",
|
|
222
|
+
compareTo: "prod",
|
|
223
|
+
readLedger: async (path, cwd) => {
|
|
224
|
+
if (path === "prod/releases.jsonl") throw new Error("fatal: bad object\nmore");
|
|
225
|
+
const { readReleaseLedger } = await import("../lifecycle/release-ledger");
|
|
226
|
+
return readReleaseLedger(path.slice(0, -"/releases.jsonl".length), { cwd });
|
|
227
|
+
},
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
expectValid(doc);
|
|
231
|
+
const web = doc.members.find((m) => m.name === "web")!;
|
|
232
|
+
const api = doc.members.find((m) => m.name === "api")!;
|
|
233
|
+
expect(web.readable).toBe(false);
|
|
234
|
+
expect(web.environments[0].reason).toEqual({
|
|
235
|
+
code: "ledger-malformed",
|
|
236
|
+
message: "_members/web/staging/releases.jsonl: 2 lines are not a release record and were skipped",
|
|
237
|
+
});
|
|
238
|
+
expect(web.environments[0].releases.map((r) => r.digest)).toEqual([D("a")]);
|
|
239
|
+
expect(api.environments[1].reason).toEqual({ code: "ledger-unreadable", message: "prod/releases.jsonl: fatal: bad object" });
|
|
240
|
+
expect(api.environments[1].releases).toEqual([]);
|
|
241
|
+
expect(doc.summary.unreadable).toBe(3);
|
|
242
|
+
expect(formatStatus(doc)).toContain(" ledger-unreadable: prod/releases.jsonl: fatal: bad object");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("a checkout with no chant/lifecycle branch lists every member with no release", async () => {
|
|
246
|
+
const root = repo({ "chant.workspace.json": declaration([{ name: "web", dir: "web", kind: "chant" }]) });
|
|
247
|
+
const doc = result(await workspaceStatus({ cwd: root, env: "prod" }));
|
|
248
|
+
expectValid(doc);
|
|
249
|
+
expect(doc.lifecycle.commit).toBeNull();
|
|
250
|
+
expect(doc.members[0].environments[0]).toMatchObject({ releases: [], reason: null, ledger: { layout: "flat", shared: false } });
|
|
251
|
+
expect(formatStatus(doc)).toContain("web - no release");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("every failure validates with its code", async () => {
|
|
255
|
+
const empty = repo({});
|
|
256
|
+
const outside = realpathSync(mkdtempSync(join(tmpdir(), "chant-status-nogit-")));
|
|
257
|
+
scratch.push(outside);
|
|
258
|
+
writeFileSync(join(outside, "chant.workspace.json"), declaration([]));
|
|
259
|
+
const broken = repo({ "chant.workspace.json": '{ "name": "acme", "schema": 1 "members": [] }' });
|
|
260
|
+
const ok = repo({ "chant.workspace.json": declaration([]) });
|
|
261
|
+
const docs = [
|
|
262
|
+
await workspaceStatus({ cwd: empty, env: "prod" }),
|
|
263
|
+
await workspaceStatus({ cwd: outside, env: "prod" }),
|
|
264
|
+
await workspaceStatus({ cwd: broken, env: "prod" }),
|
|
265
|
+
await workspaceStatus({ cwd: ok, env: "_members" }),
|
|
266
|
+
await workspaceStatus({ cwd: ok, env: "prod", compareTo: "../x" }),
|
|
267
|
+
];
|
|
268
|
+
for (const doc of docs) expectValid(doc);
|
|
269
|
+
expect(docs.map((d) => ("error" in d ? d.error.code : "ok"))).toEqual([
|
|
270
|
+
"declaration-missing",
|
|
271
|
+
"not-a-git-repository",
|
|
272
|
+
"declaration-unparseable",
|
|
273
|
+
"environment-invalid",
|
|
274
|
+
"environment-invalid",
|
|
275
|
+
]);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("the command needs an environment, refuses --live, and exits 0 with unreadable members", async () => {
|
|
279
|
+
const root = twoLayouts();
|
|
280
|
+
const err = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
281
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
282
|
+
try {
|
|
283
|
+
const run = (args: Record<string, unknown>) => runWorkspaceStatus({ args: { extraPositional2: root, ...args } } as unknown as CommandContext);
|
|
284
|
+
expect(await run({})).toBe(1);
|
|
285
|
+
expect(String(err.mock.calls.at(-1)?.[0])).toContain("needs an environment");
|
|
286
|
+
expect(await run({ extraPositional: "prod", compareTo: "--live" })).toBe(1);
|
|
287
|
+
expect(String(err.mock.calls.at(-1)?.[0])).toContain("doesn't read live state yet");
|
|
288
|
+
expect(await run({ extraPositional: "prod", compareTo: "staging", json: true })).toBe(0);
|
|
289
|
+
const printed = JSON.parse(String(log.mock.calls.at(-1)?.[0])) as StatusDocument;
|
|
290
|
+
expectValid(printed);
|
|
291
|
+
expect(await run({ extraPositional: "../prod", json: true })).toBe(1);
|
|
292
|
+
} finally {
|
|
293
|
+
err.mockRestore();
|
|
294
|
+
log.mockRestore();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe("chant workspace status on the chant repo (#2557)", () => {
|
|
300
|
+
test("validates and lists every declared member", async () => {
|
|
301
|
+
const doc = result(await workspaceStatus({ cwd: join(REPO, "packages", "core"), env: "prod", compareTo: "staging" }));
|
|
302
|
+
expectValid(doc);
|
|
303
|
+
expect(doc.workspace).toMatchObject({ name: "chant", root: ".", file: "chant.workspace.json" });
|
|
304
|
+
const ls = listWorkspace({ cwd: REPO });
|
|
305
|
+
if ("error" in ls) throw new Error(ls.error.message);
|
|
306
|
+
expect(doc.members.map((m) => m.name)).toEqual(ls.members.map((m) => m.name));
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://intentius.io/chant/schemas/workspace/status/v1/status.schema.json",
|
|
4
|
+
"title": "chant workspace status output",
|
|
5
|
+
"description": "What `chant workspace status <env> --json` prints, version 1 of the read contract for releases across members (#2524 D15, D19, #2544). Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists: a new code is a new contract version. Contract version 1 is written by chant 0.81.0 and newer; a reader that needs it refuses output whose `contract` it doesn't know. Every code is in the one closed list of `reason-codes.ts`.",
|
|
6
|
+
"oneOf": [{ "$ref": "#/$defs/result" }, { "$ref": "#/$defs/failure" }],
|
|
7
|
+
"$defs": {
|
|
8
|
+
"result": {
|
|
9
|
+
"description": "The declaration was read. Exit code 0, even when some ledgers can't be read or members differ.",
|
|
10
|
+
"type": "object",
|
|
11
|
+
"required": ["$schema", "contract", "chant", "env", "compareTo", "lifecycle", "workspace", "members", "summary"],
|
|
12
|
+
"properties": {
|
|
13
|
+
"$schema": { "const": "https://intentius.io/chant/schemas/workspace/status/v1/status.schema.json" },
|
|
14
|
+
"contract": { "const": 1 },
|
|
15
|
+
"chant": { "type": "string", "description": "The version of the chant that read the ledgers." },
|
|
16
|
+
"env": { "$ref": "#/$defs/env" },
|
|
17
|
+
"compareTo": {
|
|
18
|
+
"description": "The environment given to --compare-to, or null.",
|
|
19
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/env" }]
|
|
20
|
+
},
|
|
21
|
+
"error": false,
|
|
22
|
+
"lifecycle": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["ref", "commit"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"ref": { "const": "chant/lifecycle" },
|
|
27
|
+
"commit": {
|
|
28
|
+
"description": "The local branch tip the ledgers were read at, or null when the checkout has no such branch. The command never fetches it.",
|
|
29
|
+
"type": ["string", "null"],
|
|
30
|
+
"pattern": "^[0-9a-f]{40,64}$"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"workspace": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"required": ["name", "root", "file"],
|
|
37
|
+
"properties": {
|
|
38
|
+
"name": { "type": "string" },
|
|
39
|
+
"root": { "type": "string", "description": "The workspace root relative to the git root, with / separators, \".\" for the git root itself." },
|
|
40
|
+
"file": { "enum": ["chant.workspace.json", "chant.workspace.jsonc"] }
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"members": {
|
|
44
|
+
"description": "Every member, in declaration order. Example groups have no ledger and are not listed.",
|
|
45
|
+
"type": "array",
|
|
46
|
+
"items": { "$ref": "#/$defs/member" }
|
|
47
|
+
},
|
|
48
|
+
"summary": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"required": ["members", "released", "unreadable", "differing"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"members": { "type": "integer", "minimum": 0 },
|
|
53
|
+
"released": { "type": "integer", "minimum": 0, "description": "Members with at least one release in env." },
|
|
54
|
+
"unreadable": { "type": "integer", "minimum": 0, "description": "Members with a reason in any environment." },
|
|
55
|
+
"differing": {
|
|
56
|
+
"type": ["integer", "null"],
|
|
57
|
+
"minimum": 0,
|
|
58
|
+
"description": "Members whose compare differs, or null without --compare-to."
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"env": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$"
|
|
67
|
+
},
|
|
68
|
+
"member": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"required": ["name", "dir", "kind", "environments", "compare", "readable"],
|
|
71
|
+
"properties": {
|
|
72
|
+
"name": { "type": "string" },
|
|
73
|
+
"dir": { "type": "string", "description": "Relative to the workspace root, with / separators; \".\" is the root member." },
|
|
74
|
+
"kind": { "type": "string" },
|
|
75
|
+
"environments": {
|
|
76
|
+
"description": "env first, then compareTo when it is given and differs from env.",
|
|
77
|
+
"type": "array",
|
|
78
|
+
"minItems": 1,
|
|
79
|
+
"maxItems": 2,
|
|
80
|
+
"items": { "$ref": "#/$defs/environment" }
|
|
81
|
+
},
|
|
82
|
+
"compare": {
|
|
83
|
+
"description": "Null without --compare-to.",
|
|
84
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/compare" }]
|
|
85
|
+
},
|
|
86
|
+
"readable": { "type": "boolean", "description": "True exactly when no environment has a reason." }
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"environment": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"required": ["env", "ledger", "releases", "reason"],
|
|
92
|
+
"properties": {
|
|
93
|
+
"env": { "$ref": "#/$defs/env" },
|
|
94
|
+
"ledger": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"required": ["layout", "path", "shared"],
|
|
97
|
+
"properties": {
|
|
98
|
+
"layout": {
|
|
99
|
+
"enum": ["members", "flat"],
|
|
100
|
+
"description": "members: _members/<member>/<env>/releases.jsonl (#2538). flat: <env>/releases.jsonl, for the root member and for a member with no _members/<member>/ directory yet."
|
|
101
|
+
},
|
|
102
|
+
"path": { "type": "string", "description": "The ledger's path on chant/lifecycle." },
|
|
103
|
+
"shared": { "type": "boolean", "description": "True when more than one member in this output reads the same flat ledger, so they list the same records." }
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"releases": {
|
|
107
|
+
"description": "The latest release of each component in the ledger, sorted by component. Empty when the ledger has none.",
|
|
108
|
+
"type": "array",
|
|
109
|
+
"items": { "$ref": "#/$defs/release" }
|
|
110
|
+
},
|
|
111
|
+
"reason": {
|
|
112
|
+
"oneOf": [
|
|
113
|
+
{ "type": "null" },
|
|
114
|
+
{
|
|
115
|
+
"type": "object",
|
|
116
|
+
"required": ["code", "message"],
|
|
117
|
+
"properties": {
|
|
118
|
+
"code": { "enum": ["ledger-unreadable", "ledger-malformed"] },
|
|
119
|
+
"message": { "type": "string" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"release": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"required": ["component", "digest", "gitSha", "inputDigest", "runId", "timestamp", "actor", "flags"],
|
|
129
|
+
"properties": {
|
|
130
|
+
"component": { "type": "string" },
|
|
131
|
+
"digest": { "type": "string", "description": "The artifact digest the release deployed." },
|
|
132
|
+
"gitSha": { "type": "string", "description": "The commit the artifact was built from." },
|
|
133
|
+
"inputDigest": { "type": ["string", "null"], "description": "The input-side digest when digest is a rendered one. Compare joins on it when present." },
|
|
134
|
+
"runId": { "type": "string" },
|
|
135
|
+
"timestamp": { "type": "string" },
|
|
136
|
+
"actor": { "type": "string" },
|
|
137
|
+
"flags": { "type": "array", "items": { "enum": ["legacy-digest"] } }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"compare": {
|
|
141
|
+
"type": "object",
|
|
142
|
+
"required": ["differs", "components"],
|
|
143
|
+
"properties": {
|
|
144
|
+
"differs": { "type": "boolean", "description": "True when any component is not same." },
|
|
145
|
+
"components": {
|
|
146
|
+
"type": "array",
|
|
147
|
+
"items": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"required": ["component", "state", "digest", "compareDigest"],
|
|
150
|
+
"properties": {
|
|
151
|
+
"component": { "type": "string" },
|
|
152
|
+
"state": {
|
|
153
|
+
"enum": ["same", "differs", "only-env", "only-compare"],
|
|
154
|
+
"description": "same: both environments have the same digest (the input digest when there is one). only-env and only-compare: the component has a release in one of them."
|
|
155
|
+
},
|
|
156
|
+
"digest": { "type": ["string", "null"] },
|
|
157
|
+
"compareDigest": { "type": ["string", "null"] }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"failure": {
|
|
164
|
+
"description": "The declaration could not be read, or an environment name can't be one. Exit code 1.",
|
|
165
|
+
"type": "object",
|
|
166
|
+
"required": ["$schema", "contract", "chant", "error"],
|
|
167
|
+
"properties": {
|
|
168
|
+
"$schema": { "const": "https://intentius.io/chant/schemas/workspace/status/v1/status.schema.json" },
|
|
169
|
+
"contract": { "const": 1 },
|
|
170
|
+
"chant": { "type": "string" },
|
|
171
|
+
"error": {
|
|
172
|
+
"type": "object",
|
|
173
|
+
"required": ["code", "message", "location"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"code": {
|
|
176
|
+
"enum": [
|
|
177
|
+
"declaration-missing",
|
|
178
|
+
"declaration-ambiguous",
|
|
179
|
+
"declaration-unparseable",
|
|
180
|
+
"declaration-invalid",
|
|
181
|
+
"placement-invalid",
|
|
182
|
+
"reader-too-old",
|
|
183
|
+
"root-chant-required",
|
|
184
|
+
"not-a-git-repository",
|
|
185
|
+
"environment-invalid"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
"message": { "type": "string" },
|
|
189
|
+
"location": {
|
|
190
|
+
"oneOf": [
|
|
191
|
+
{ "type": "null" },
|
|
192
|
+
{
|
|
193
|
+
"type": "object",
|
|
194
|
+
"required": ["file", "line", "column"],
|
|
195
|
+
"properties": {
|
|
196
|
+
"file": { "type": "string" },
|
|
197
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
198
|
+
"column": { "type": "integer", "minimum": 1 }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"members": false,
|
|
206
|
+
"workspace": false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|