@intentius/chant 0.80.0 → 0.82.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/discover.d.ts +8 -16
- 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 +4 -5
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +25 -9
- package/dist/cli/commands/audit.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.map +1 -1
- package/dist/cli/commands/lint.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/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/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +7 -4
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/run-generate.d.ts.map +1 -1
- 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/plugins.d.ts +12 -7
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +32 -0
- 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/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/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/promote.d.ts +60 -7
- package/dist/components/promote.d.ts.map +1 -1
- 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 +32 -10
- 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 +10 -5
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +20 -4
- 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 +4 -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 +51 -6
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon.d.ts +58 -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 +41 -8
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
- 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 +18 -2
- 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/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/discover.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/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 +35 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +144 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +271 -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 +152 -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/records-cli.d.ts +28 -4
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records.d.ts.map +1 -1
- 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 +2 -1
- package/src/audit/discover.ts +66 -78
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +4 -9
- package/src/audit/terraform-state.ts +4 -5
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +89 -60
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +18 -0
- package/src/cli/commands/import-agents.ts +6 -2
- package/src/cli/commands/init.ts +22 -3
- package/src/cli/commands/lint.test.ts +17 -23
- package/src/cli/commands/lint.ts +24 -84
- package/src/cli/commands/update.ts +13 -4
- package/src/cli/commands/vendor.ts +11 -6
- 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.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 +5 -3
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/init.ts +58 -1
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +37 -9
- package/src/cli/handlers/promote.test.ts +118 -2
- package/src/cli/handlers/promote.ts +82 -16
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +6 -8
- package/src/cli/handlers/run-generate.ts +36 -1
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +28 -5
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +105 -1
- package/src/cli/main.ts +232 -17
- package/src/cli/plugins.ts +17 -17
- package/src/cli/registry.ts +32 -0
- 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/cli-support.ts +6 -1
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +13 -68
- package/src/components/driver.test.ts +78 -1
- package/src/components/driver.ts +43 -7
- package/src/components/gate-plan.ts +53 -0
- package/src/components/promote.test.ts +276 -2
- package/src/components/promote.ts +119 -15
- 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 +2 -2
- package/src/config.ts +48 -15
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +10 -10
- package/src/discovery/files.ts +25 -81
- package/src/discovery/fold-import.ts +49 -5
- 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/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +4 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.ts +108 -10
- package/src/lexicon.ts +79 -1
- package/src/lifecycle/build-ledger-store.ts +11 -10
- 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 +115 -23
- package/src/lifecycle/legacy-digest.ts +60 -19
- 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 +23 -7
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- 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/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -27
- 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/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 +158 -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 +134 -0
- package/src/workspace/lineage-init-dir.test.ts +243 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +560 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +365 -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 +723 -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/records-cli.ts +86 -14
- package/src/workspace/records-contract.test.ts +5 -0
- package/src/workspace/records.schema.json +32 -2
- package/src/workspace/records.ts +3 -2
- 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
- package/dist/components/unbound-gate-approval.d.ts +0 -24
- package/dist/components/unbound-gate-approval.d.ts.map +0 -1
- package/dist/discovery/convergence.d.ts +0 -97
- package/dist/discovery/convergence.d.ts.map +0 -1
- package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
- package/src/components/unbound-gate-approval.test.ts +0 -82
- package/src/components/unbound-gate-approval.ts +0 -51
- package/src/discovery/convergence.test.ts +0 -231
- package/src/discovery/convergence.ts +0 -399
- package/src/op/discover-convergence.test.ts +0 -70
package/src/op/discover.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { getRuntime } from "../runtime-adapter";
|
|
2
|
-
import { readdir } from "node:fs/promises";
|
|
3
2
|
import { existsSync } from "node:fs";
|
|
4
3
|
import { dirname, join, resolve } from "node:path";
|
|
5
4
|
import type { OpConfig } from "./types";
|
|
6
|
-
import {
|
|
5
|
+
import { walkDiscovery, workspaceMemberDirs } from "../discovery/walk";
|
|
6
|
+
import { hasDiscoveryMarkerSync } from "../discovery/files";
|
|
7
|
+
import { resolveDiscoveryGlobs } from "../config";
|
|
7
8
|
|
|
8
9
|
export interface DiscoveredOp {
|
|
9
10
|
config: OpConfig;
|
|
@@ -53,28 +54,22 @@ async function findDiscoveryRoot(cwd?: string): Promise<string> {
|
|
|
53
54
|
return gitRoot;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
!entry.name.endsWith(".spec.ts")
|
|
73
|
-
) {
|
|
74
|
-
files.push(fullPath);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return files;
|
|
57
|
+
/**
|
|
58
|
+
* Every `*.op.ts` file below the Op root, through the one discovery walk
|
|
59
|
+
* (`../discovery/walk.ts`, #2527). The walk down stops at child projects
|
|
60
|
+
* whatever the root is, so a configless layout whose Op root is the git root
|
|
61
|
+
* no longer collects its sibling projects' Ops. The project's
|
|
62
|
+
* `exclude`/`include` globs and the skip marker apply.
|
|
63
|
+
*/
|
|
64
|
+
async function collectOpFiles(root: string): Promise<string[]> {
|
|
65
|
+
return walkDiscovery({
|
|
66
|
+
walker: "ops",
|
|
67
|
+
root,
|
|
68
|
+
globs: await resolveDiscoveryGlobs(root),
|
|
69
|
+
excludeDirs: await workspaceMemberDirs(root),
|
|
70
|
+
accept: (name, full) =>
|
|
71
|
+
name.endsWith(".op.ts") && !name.endsWith(".test.ts") && !name.endsWith(".spec.ts") && !hasDiscoveryMarkerSync(full),
|
|
72
|
+
});
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
/** The `OpConfig` behind an exported value, or `undefined` when the value is not an Op. An Op entity carries its config on `.props` (`./resource.ts`); `name` and `phases` are what every consumer of a discovered Op reads. */
|
|
@@ -149,9 +144,6 @@ export async function discoverOps(opts?: { cwd?: string }): Promise<OpDiscoveryR
|
|
|
149
144
|
|
|
150
145
|
const root = await findDiscoveryRoot(opts?.cwd);
|
|
151
146
|
const files = await collectOpFiles(root);
|
|
152
|
-
// #2527's warning release: Op discovery stops at child projects below the
|
|
153
|
-
// root next release, and skips git-ignored files. `files` is unchanged.
|
|
154
|
-
await warnDiscoveryChanges({ walker: "ops", root, files });
|
|
155
147
|
|
|
156
148
|
const nameToFile = new Map<string, string>();
|
|
157
149
|
|
package/src/op/gate-name.ts
CHANGED
|
@@ -29,6 +29,14 @@ import type { OpConfig, PhaseDefinition, StepDefinition } from "./types";
|
|
|
29
29
|
*/
|
|
30
30
|
export const FAN_OUT_GATE_OP = "fan-out";
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The op name a `chant workspace upgrade` gate is recorded under (#2550). The
|
|
34
|
+
* gate is the scope (`.` for a plain project), and it binds the digest of the
|
|
35
|
+
* upgrade's patch. Like a fan-out, the upgrade is a command with no `*.op.ts`
|
|
36
|
+
* behind it, so `chant approve` must not warn that it found none.
|
|
37
|
+
*/
|
|
38
|
+
export const WORKSPACE_UPGRADE_GATE_OP = "workspace-upgrade";
|
|
39
|
+
|
|
32
40
|
/** Either spelling of a gate step's name. `gate` since #2202; `signalName` through 0.59.0. */
|
|
33
41
|
export interface GateNamed {
|
|
34
42
|
gate?: string;
|
|
@@ -58,6 +58,20 @@ describe("gatedRunSummaryMarkdown (#2243)", () => {
|
|
|
58
58
|
expect(md).toContain("and not the next run");
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
// #2574 with #2538: a component gate in a workspace member names both its
|
|
62
|
+
// environment in the approve line and the member's gate ledger path.
|
|
63
|
+
test("a member's component gate names its environment and its member ledger path", () => {
|
|
64
|
+
const md = gatedRunSummaryMarkdown({
|
|
65
|
+
op: "search-service",
|
|
66
|
+
gate: "release",
|
|
67
|
+
expiresAt: "2026-09-08T00:00:00Z",
|
|
68
|
+
environment: "prod",
|
|
69
|
+
ledgerPrefix: "_members/services/search/",
|
|
70
|
+
});
|
|
71
|
+
expect(md).toContain("chant approve search-service release --env prod --approver <you>");
|
|
72
|
+
expect(md).toContain("`_members/services/search/_gates/search-service.jsonl`");
|
|
73
|
+
});
|
|
74
|
+
|
|
61
75
|
test("a gate that binds no plan says nothing about one", () => {
|
|
62
76
|
expect(gatedRunSummaryMarkdown(summary)).not.toContain("and not the next run");
|
|
63
77
|
});
|
package/src/op/gate-summary.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { appendFileSync } from "node:fs";
|
|
22
22
|
import { gateLedgerPath } from "../lifecycle/gate-ledger";
|
|
23
|
+
import { resolveMemberLedger } from "../lifecycle/member-ledger";
|
|
23
24
|
import { approveCommand } from "./gate";
|
|
24
25
|
|
|
25
26
|
/** What a gated run knows about the gate it stopped on. */
|
|
@@ -50,6 +51,28 @@ export interface GatedRunSummary {
|
|
|
50
51
|
* to see that the digest has moved on.
|
|
51
52
|
*/
|
|
52
53
|
planDigest?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The project's ledger prefix, `_members/<member>/` for a workspace member
|
|
56
|
+
* (#2538), so the ledger path shown is the one the fact was written to.
|
|
57
|
+
* Absent at level 0 and for the root member.
|
|
58
|
+
*/
|
|
59
|
+
ledgerPrefix?: string;
|
|
60
|
+
/** The environment a component gate's pending fact was recorded in (#2574). The `chant approve` line names it. */
|
|
61
|
+
environment?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* `{ ledgerPrefix }` for the project at `cwd` when it is a workspace member,
|
|
66
|
+
* else `{}`. A summary is a report, so a declaration that cannot be read
|
|
67
|
+
* leaves the path as it is rather than failing the report.
|
|
68
|
+
*/
|
|
69
|
+
export async function summaryLedgerPrefix(cwd: string = process.cwd()): Promise<{ ledgerPrefix?: string }> {
|
|
70
|
+
try {
|
|
71
|
+
const { prefix } = await resolveMemberLedger(cwd);
|
|
72
|
+
return prefix ? { ledgerPrefix: prefix } : {};
|
|
73
|
+
} catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
53
76
|
}
|
|
54
77
|
|
|
55
78
|
/**
|
|
@@ -78,10 +101,10 @@ export function gatedRunSummaryMarkdown(summary: GatedRunSummary): string {
|
|
|
78
101
|
"Approve it, then re-run this workflow:",
|
|
79
102
|
"",
|
|
80
103
|
"```",
|
|
81
|
-
`${approveCommand(summary.op, summary.gate)} --approver <you>`,
|
|
104
|
+
`${approveCommand(summary.op, summary.gate, summary.environment)} --approver <you>`,
|
|
82
105
|
"```",
|
|
83
106
|
"",
|
|
84
|
-
`Ledger: \`${gateLedgerPath(summary.op)}\` on the \`chant/lifecycle\` branch.`,
|
|
107
|
+
`Ledger: \`${gateLedgerPath(summary.op, summary.ledgerPrefix)}\` on the \`chant/lifecycle\` branch.`,
|
|
85
108
|
);
|
|
86
109
|
if (summary.expiresAt) lines.push(`Expires: ${summary.expiresAt}`);
|
|
87
110
|
if (summary.url) lines.push(`Approve at: ${summary.url}`);
|
package/src/op/gate.ts
CHANGED
|
@@ -170,6 +170,14 @@ export interface GateCheckInput {
|
|
|
170
170
|
* the standing pending fact satisfies the gate whatever has changed since.
|
|
171
171
|
*/
|
|
172
172
|
planDigest?: string;
|
|
173
|
+
/**
|
|
174
|
+
* The environment this run deploys to (#2574). The component driver sets it,
|
|
175
|
+
* because a component's gate ledger is keyed by component and holds every
|
|
176
|
+
* environment's facts. Set, the gate reads only this environment's pending
|
|
177
|
+
* facts and resolutions (plus resolutions that record no environment, which
|
|
178
|
+
* come back as a mismatch), and the pending fact it records carries it.
|
|
179
|
+
*/
|
|
180
|
+
environment?: string;
|
|
173
181
|
/**
|
|
174
182
|
* The gate's quorum and policy (#2508), context already resolved. Absent,
|
|
175
183
|
* one approval passes the gate, which is the rule every gate had before.
|
|
@@ -194,6 +202,8 @@ export interface GateDigestMismatch {
|
|
|
194
202
|
resolvedBy: string;
|
|
195
203
|
/** When they recorded it. */
|
|
196
204
|
timestamp: string;
|
|
205
|
+
/** The environment this run deploys to, on a component gate (#2574). The approve command a refusal prints names it. */
|
|
206
|
+
environment?: string;
|
|
197
207
|
}
|
|
198
208
|
|
|
199
209
|
/**
|
|
@@ -210,15 +220,18 @@ export interface GateDigestMismatch {
|
|
|
210
220
|
export function describeGateMismatch(op: string, gate: string, mismatch: GateDigestMismatch): string {
|
|
211
221
|
const why =
|
|
212
222
|
mismatch.approved === undefined
|
|
213
|
-
?
|
|
214
|
-
"
|
|
223
|
+
? mismatch.environment !== undefined
|
|
224
|
+
? "That approval predates environment- and plan-bound component gates (#2574) and records neither, " +
|
|
225
|
+
"so it cannot answer for this one. Approving again binds it:"
|
|
226
|
+
: "That resolution predates plan-bound gates (#2300) and records no plan at all, so it cannot " +
|
|
227
|
+
"answer for this one. Approving again binds it:"
|
|
215
228
|
: "The configuration or the live system changed between that approval and this plan, so it needs " +
|
|
216
229
|
"a fresh one:";
|
|
217
230
|
return (
|
|
218
231
|
`Gate "${gate}" is approved, but not for this plan. ` +
|
|
219
232
|
`approved: ${describePlanDigest(mismatch.approved)} (by ${mismatch.resolvedBy} at ${mismatch.timestamp}); ` +
|
|
220
233
|
`planned: ${mismatch.planned}. ` +
|
|
221
|
-
`${why} ${approveCommand(op, gate)}`
|
|
234
|
+
`${why} ${approveCommand(op, gate, mismatch.environment)}`
|
|
222
235
|
);
|
|
223
236
|
}
|
|
224
237
|
|
|
@@ -374,7 +387,17 @@ const EPOCH = new Date(0).toISOString();
|
|
|
374
387
|
*/
|
|
375
388
|
export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput): Promise<GateCheck> {
|
|
376
389
|
const now = input.now ?? new Date().toISOString();
|
|
377
|
-
const
|
|
390
|
+
const ledger = await port.read(input.op);
|
|
391
|
+
// #2574: a component's ledger holds every environment's facts. This run
|
|
392
|
+
// answers only to its own environment's pending facts and approvals. An
|
|
393
|
+
// approval that records no environment at all is kept, so it comes back as
|
|
394
|
+
// a mismatch with a message instead of silently not counting.
|
|
395
|
+
const env = input.environment;
|
|
396
|
+
const pending = env === undefined ? ledger.pending : ledger.pending.filter((p) => p.environment === env);
|
|
397
|
+
const resolutions =
|
|
398
|
+
env === undefined
|
|
399
|
+
? ledger.resolutions
|
|
400
|
+
: ledger.resolutions.filter((r) => r.environment === undefined || r.environment === env);
|
|
378
401
|
|
|
379
402
|
const standing = latestPendingGate(pending, input.gate);
|
|
380
403
|
const since = standing?.timestamp ?? EPOCH;
|
|
@@ -410,6 +433,7 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
410
433
|
planned: input.planDigest!,
|
|
411
434
|
resolvedBy: mismatched.resolvedBy,
|
|
412
435
|
timestamp: mismatched.timestamp,
|
|
436
|
+
...(env !== undefined ? { environment: env } : {}),
|
|
413
437
|
}
|
|
414
438
|
: undefined;
|
|
415
439
|
const asMismatch = { ...(mismatch ? { mismatch } : {}), ...(quorum ? { quorum } : {}) };
|
|
@@ -441,6 +465,7 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
441
465
|
...(input.runId ? { runId: input.runId } : {}),
|
|
442
466
|
...(url ? { url } : {}),
|
|
443
467
|
...(input.planDigest !== undefined ? { planDigest: input.planDigest } : {}),
|
|
468
|
+
...(env !== undefined ? { environment: env } : {}),
|
|
444
469
|
...(input.approval ? { approval: input.approval } : {}),
|
|
445
470
|
});
|
|
446
471
|
return {
|
|
@@ -453,7 +478,11 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
453
478
|
};
|
|
454
479
|
}
|
|
455
480
|
|
|
456
|
-
/**
|
|
457
|
-
|
|
458
|
-
|
|
481
|
+
/**
|
|
482
|
+
* The line every renderer prints to say how a pending gate is cleared. A
|
|
483
|
+
* component gate names its environment (#2574), so the approval picks that
|
|
484
|
+
* environment's pending fact when others stand beside it.
|
|
485
|
+
*/
|
|
486
|
+
export function approveCommand(op: string, gate: string, environment?: string): string {
|
|
487
|
+
return environment === undefined ? `chant approve ${op} ${gate}` : `chant approve ${op} ${gate} --env ${environment}`;
|
|
459
488
|
}
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
* `../components/cli-support.ts`'s `generateComponentsPipeline`. Mirrors that
|
|
4
4
|
* module's own test style (`cli-support.test.ts`): a minimal mocked lexicon
|
|
5
5
|
* plugin satisfying `isLexiconPlugin`, real `discoverOps()` resolution
|
|
6
|
-
* against this repo's actual `*.op.ts` fixtures
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* against this repo's actual `*.op.ts` fixtures
|
|
7
|
+
* (`examples/gitlab-aws-alb-infra/ops/alb-deploy.op.ts`, see
|
|
8
|
+
* `./discover.test.ts`) so Op-name validation exercises the real discovery
|
|
9
|
+
* path rather than a stub. Discovery starts in that example: since #2527 the
|
|
10
|
+
* walk from the git root stops at child projects, so the repo root finds none.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import { describe, test, expect, vi } from "vitest";
|
|
14
|
+
import { dirname, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
12
16
|
import { generateOpsPipeline, withOpSchedules } from "./generate-pipeline";
|
|
13
17
|
import type { DiscoveredOp } from "./discover";
|
|
14
18
|
import type { OpConfig } from "./types";
|
|
@@ -34,6 +38,9 @@ vi.mock("@intentius/chant-lexicon-gitlab", () => ({
|
|
|
34
38
|
},
|
|
35
39
|
}));
|
|
36
40
|
|
|
41
|
+
/** The example project that holds `alb-deploy.op.ts`. */
|
|
42
|
+
const ALB_INFRA = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../examples/gitlab-aws-alb-infra");
|
|
43
|
+
|
|
37
44
|
describe("generateOpsPipeline", () => {
|
|
38
45
|
test("errors when the target lexicon has no generateOpPipeline", async () => {
|
|
39
46
|
const result = await generateOpsPipeline([{ name: "alb-deploy", schedule: "0 6 * * *" }], "aws");
|
|
@@ -45,6 +52,8 @@ describe("generateOpsPipeline", () => {
|
|
|
45
52
|
const result = await generateOpsPipeline(
|
|
46
53
|
[{ name: "definitely-not-a-real-op", schedule: "0 6 * * *" }],
|
|
47
54
|
"gitlab",
|
|
55
|
+
undefined,
|
|
56
|
+
ALB_INFRA,
|
|
48
57
|
);
|
|
49
58
|
expect(result.success).toBe(false);
|
|
50
59
|
expect(result.error).toMatch(/Unknown Op\(s\): definitely-not-a-real-op/);
|
|
@@ -52,7 +61,7 @@ describe("generateOpsPipeline", () => {
|
|
|
52
61
|
|
|
53
62
|
test("validates every named Op and delegates to the lexicon plugin", async () => {
|
|
54
63
|
const specs: ScheduledOpSpec[] = [{ name: "alb-deploy", schedule: "0 6 * * *", findingMode: "issue" }];
|
|
55
|
-
const result = await generateOpsPipeline(specs, "gitlab");
|
|
64
|
+
const result = await generateOpsPipeline(specs, "gitlab", undefined, ALB_INFRA);
|
|
56
65
|
expect(result.success).toBe(true);
|
|
57
66
|
expect(result.files).toEqual([{ name: "alb-deploy.yml", yaml: "# alb-deploy @ 0 6 * * *" }]);
|
|
58
67
|
expect(result.jobs).toEqual([
|
|
@@ -66,7 +75,7 @@ describe("generateOpsPipeline", () => {
|
|
|
66
75
|
// `PipelineAuditOp`, `ReconcileOp`) only ever sets `schedule`.
|
|
67
76
|
test("a legacy `{ schedule }` spec with no `trigger` round-trips unchanged", async () => {
|
|
68
77
|
const specs: ScheduledOpSpec[] = [{ name: "alb-deploy", schedule: "0 6 * * *" }];
|
|
69
|
-
const result = await generateOpsPipeline(specs, "gitlab");
|
|
78
|
+
const result = await generateOpsPipeline(specs, "gitlab", undefined, ALB_INFRA);
|
|
70
79
|
expect(result.success).toBe(true);
|
|
71
80
|
expect(result.files).toEqual([{ name: "alb-deploy.yml", yaml: "# alb-deploy @ 0 6 * * *" }]);
|
|
72
81
|
expect(result.jobs).toEqual([
|
package/src/project-root.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { tmpdir } from "os";
|
|
5
|
-
import { findProjectConfig, findProjectRoot } from "./project-root";
|
|
5
|
+
import { findProjectConfig, findProjectRoot, findWorkspaceRoot } from "./project-root";
|
|
6
6
|
|
|
7
7
|
// Every test builds its own isolated directory tree under a fresh tmpdir —
|
|
8
8
|
// never reused across tests, and never anywhere near the real repo's own
|
|
@@ -103,3 +103,68 @@ describe("findProjectConfig / findProjectRoot (chant #1117)", () => {
|
|
|
103
103
|
expect(result.configPath).toBe(join(projectRoot, "chant.config.ts"));
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
|
+
|
|
107
|
+
describe("findWorkspaceRoot (chant #2534)", () => {
|
|
108
|
+
const declare = (dir: string, name = "chant.workspace.json") => {
|
|
109
|
+
mkdirSync(dir, { recursive: true });
|
|
110
|
+
writeFileSync(join(dir, name), "{}");
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
test("walks up to the nearest declaration, and never looks at chant.config", () => {
|
|
114
|
+
mkdirSync(join(root, ".git"));
|
|
115
|
+
declare(root);
|
|
116
|
+
const deep = join(root, "lexicons", "aws", "examples", "one");
|
|
117
|
+
mkdirSync(deep, { recursive: true });
|
|
118
|
+
writeFileSync(join(root, "lexicons", "aws", "package.json"), "{}");
|
|
119
|
+
writeFileSync(join(deep, "chant.config.ts"), "export default {};");
|
|
120
|
+
expect(findWorkspaceRoot(deep)).toEqual({ dir: root, file: join(root, "chant.workspace.json") });
|
|
121
|
+
// findProjectConfig is unchanged: it still stops at the project's own config.
|
|
122
|
+
expect(findProjectConfig(deep).dir).toBe(deep);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("the nearest declaration wins, so a nested workspace finds itself", () => {
|
|
126
|
+
mkdirSync(join(root, ".git"));
|
|
127
|
+
declare(root);
|
|
128
|
+
const inner = join(root, "vendor", "kit");
|
|
129
|
+
declare(inner, "chant.workspace.jsonc");
|
|
130
|
+
mkdirSync(join(inner, "src"));
|
|
131
|
+
expect(findWorkspaceRoot(join(inner, "src"))).toEqual({ dir: inner, file: join(inner, "chant.workspace.jsonc") });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("stops at the git root after checking it", () => {
|
|
135
|
+
declare(root);
|
|
136
|
+
const repo = join(root, "repo");
|
|
137
|
+
mkdirSync(join(repo, ".git"), { recursive: true });
|
|
138
|
+
mkdirSync(join(repo, "src"));
|
|
139
|
+
expect(findWorkspaceRoot(join(repo, "src"))).toBeUndefined();
|
|
140
|
+
declare(repo);
|
|
141
|
+
expect(findWorkspaceRoot(join(repo, "src"))?.dir).toBe(repo);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("a .git file (a worktree or submodule) is a git root too", () => {
|
|
145
|
+
declare(root);
|
|
146
|
+
const repo = join(root, "wt");
|
|
147
|
+
mkdirSync(repo);
|
|
148
|
+
writeFileSync(join(repo, ".git"), "gitdir: /elsewhere\n");
|
|
149
|
+
expect(findWorkspaceRoot(repo)).toBeUndefined();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("outside a git repository only the start directory is checked", () => {
|
|
153
|
+
declare(root);
|
|
154
|
+
const sub = join(root, "sub");
|
|
155
|
+
mkdirSync(sub);
|
|
156
|
+
expect(findWorkspaceRoot(sub)).toBeUndefined();
|
|
157
|
+
expect(findWorkspaceRoot(root)?.dir).toBe(root);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("both names present is reported, not thrown", () => {
|
|
161
|
+
mkdirSync(join(root, ".git"));
|
|
162
|
+
declare(root);
|
|
163
|
+
declare(root, "chant.workspace.jsonc");
|
|
164
|
+
expect(findWorkspaceRoot(root)).toEqual({
|
|
165
|
+
dir: root,
|
|
166
|
+
file: join(root, "chant.workspace.json"),
|
|
167
|
+
conflicting: join(root, "chant.workspace.jsonc"),
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
package/src/project-root.ts
CHANGED
|
@@ -76,3 +76,51 @@ export function findProjectConfig(startDir: string): ProjectConfigSearch {
|
|
|
76
76
|
export function findProjectRoot(startDir: string): string {
|
|
77
77
|
return findProjectConfig(startDir).dir;
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
/** The two names a workspace declaration may have (#2524 D1). */
|
|
81
|
+
export const WORKSPACE_DECLARATION_FILES = ["chant.workspace.json", "chant.workspace.jsonc"] as const;
|
|
82
|
+
|
|
83
|
+
/** Where {@link findWorkspaceRoot} found a declaration. */
|
|
84
|
+
export interface WorkspaceRootSearch {
|
|
85
|
+
/** The directory holding the declaration. */
|
|
86
|
+
dir: string;
|
|
87
|
+
/** Absolute path of the declaration file. When both names are present this is the `.json` one. */
|
|
88
|
+
file: string;
|
|
89
|
+
/**
|
|
90
|
+
* Absolute path of the `.jsonc` file when `.json` is also there. Having both
|
|
91
|
+
* is an error, which the reader reports; the walk itself never throws.
|
|
92
|
+
*/
|
|
93
|
+
conflicting?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Walk up from `startDir` (inclusive) to the nearest `chant.workspace.json` or
|
|
98
|
+
* `chant.workspace.jsonc` (#2524 D1). The walk stops at the git root, the first
|
|
99
|
+
* directory holding `.git`, after checking it. Outside a git repository only
|
|
100
|
+
* `startDir` itself is checked, so a stray declaration in an unrelated
|
|
101
|
+
* ancestor is never picked up.
|
|
102
|
+
*
|
|
103
|
+
* It only tests whether files exist and loads nothing else, so a level-0
|
|
104
|
+
* command can afford to call it. {@link findProjectConfig} is unchanged and
|
|
105
|
+
* never looks at a declaration.
|
|
106
|
+
*/
|
|
107
|
+
export function findWorkspaceRoot(startDir: string): WorkspaceRootSearch | undefined {
|
|
108
|
+
const start = resolve(startDir);
|
|
109
|
+
let top: string | undefined;
|
|
110
|
+
for (let dir = start; ; dir = dirname(dir)) {
|
|
111
|
+
if (existsSync(join(dir, ".git"))) {
|
|
112
|
+
top = dir;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
if (dirname(dir) === dir) break;
|
|
116
|
+
}
|
|
117
|
+
for (let dir = start; ; dir = dirname(dir)) {
|
|
118
|
+
const [json, jsonc] = WORKSPACE_DECLARATION_FILES.map((name) => join(dir, name));
|
|
119
|
+
const hasJson = existsSync(json);
|
|
120
|
+
const hasJsonc = existsSync(jsonc);
|
|
121
|
+
if (hasJson && hasJsonc) return { dir, file: json, conflicting: jsonc };
|
|
122
|
+
if (hasJson) return { dir, file: json };
|
|
123
|
+
if (hasJsonc) return { dir, file: jsonc };
|
|
124
|
+
if (top === undefined || dir === top || dirname(dir) === dir) return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers the read-contract tests share (#2536): throwaway git repositories,
|
|
3
|
+
* a draft 2020-12 validator, and a fake chant toolchain.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { chmodSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import Ajv2020 from "ajv/dist/2020";
|
|
11
|
+
import { expect } from "vitest";
|
|
12
|
+
|
|
13
|
+
/** The chant checkout these tests run in. */
|
|
14
|
+
export const REPO = realpathSync(join(import.meta.dirname, "..", "..", "..", "..", ".."));
|
|
15
|
+
|
|
16
|
+
const scratch: string[] = [];
|
|
17
|
+
|
|
18
|
+
/** Remove every directory {@link scratchDir} and {@link repo} made. Call it from `afterAll`. */
|
|
19
|
+
export function cleanScratch(): void {
|
|
20
|
+
for (const d of scratch.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function scratchDir(prefix = "chant-contract-"): string {
|
|
24
|
+
const d = realpathSync(mkdtempSync(join(tmpdir(), prefix)));
|
|
25
|
+
scratch.push(d);
|
|
26
|
+
return d;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function git(cwd: string, ...args: string[]): string {
|
|
30
|
+
return execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", ...args], {
|
|
31
|
+
cwd,
|
|
32
|
+
encoding: "utf-8",
|
|
33
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
34
|
+
}).trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Write `files` under `root`; a value of `{ text, mode }` sets the mode too. */
|
|
38
|
+
export function writeFiles(root: string, files: Record<string, string | { text: string; mode: number }>): void {
|
|
39
|
+
for (const [path, value] of Object.entries(files)) {
|
|
40
|
+
const full = join(root, path);
|
|
41
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
42
|
+
writeFileSync(full, typeof value === "string" ? value : value.text);
|
|
43
|
+
if (typeof value !== "string") chmodSync(full, value.mode);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A git repository holding `files`, with nothing committed unless `commit` is set. */
|
|
48
|
+
export function repo(files: Record<string, string | { text: string; mode: number }>, commit = false): string {
|
|
49
|
+
const root = scratchDir("chant-contract-repo-");
|
|
50
|
+
writeFiles(root, files);
|
|
51
|
+
git(root, "init", "-q");
|
|
52
|
+
if (commit) {
|
|
53
|
+
git(root, "add", "-A");
|
|
54
|
+
git(root, "commit", "-q", "-m", "one");
|
|
55
|
+
}
|
|
56
|
+
return root;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function commitAll(root: string, message = "next"): string {
|
|
60
|
+
git(root, "add", "-A");
|
|
61
|
+
git(root, "commit", "-q", "-m", message);
|
|
62
|
+
return git(root, "rev-parse", "HEAD");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const ajv = new Ajv2020({ strict: true, allErrors: true });
|
|
66
|
+
|
|
67
|
+
/** A validator for `schema`, and an assertion that a document passes it. */
|
|
68
|
+
export function contract(schema: object): { validate: ReturnType<typeof ajv.compile>; expectValid(doc: unknown): void } {
|
|
69
|
+
const validate = ajv.compile(schema);
|
|
70
|
+
return {
|
|
71
|
+
validate,
|
|
72
|
+
expectValid(doc) {
|
|
73
|
+
const ok = validate(doc);
|
|
74
|
+
expect(ok, `${JSON.stringify(validate.errors, null, 2)}\n${JSON.stringify(doc, null, 2).slice(0, 2000)}`).toBe(true);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function validSchema(schema: object): boolean {
|
|
80
|
+
return ajv.validateSchema(schema) as boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const declaration = (members: unknown[], extra: Record<string, unknown> = {}) =>
|
|
84
|
+
JSON.stringify({ name: "acme", schema: 1, ...extra, members }, null, 2);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A chant older than `workspace member-run`: it answers `chant graph` in the
|
|
88
|
+
* member's directory with one node per line of the member's `ids.txt`.
|
|
89
|
+
*/
|
|
90
|
+
export const FAKE_GRAPH_CHANT = `#!/bin/sh
|
|
91
|
+
case "$1" in
|
|
92
|
+
graph)
|
|
93
|
+
printf '{"version":1,"nodes":['
|
|
94
|
+
sep=""
|
|
95
|
+
while read -r id; do printf '%s{"id":"%s","kind":"Thing","lexicon":"fake","attrs":{}}' "$sep" "$id"; sep=","; done < ids.txt
|
|
96
|
+
printf '],"edges":[],"groups":{"byLexicon":{}}}\\n'
|
|
97
|
+
;;
|
|
98
|
+
*) echo "Error: Unknown command: $1" >&2; exit 1 ;;
|
|
99
|
+
esac
|
|
100
|
+
`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A throwaway git repository holding a workspace with two members (api and
|
|
3
|
+
* web), optionally a root member, and an example group with one match. Used
|
|
4
|
+
* by the per-member pipeline tests (#2542).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { mkdirSync, mkdtempSync, realpathSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
|
|
12
|
+
/** A git repository holding a workspace with two members, a root member and an example group. */
|
|
13
|
+
export function twoMemberWorkspace(options: { root?: boolean; prefix?: string } = {}): string {
|
|
14
|
+
const repo = realpathSync(mkdtempSync(join(tmpdir(), "chant-member-pipeline-")));
|
|
15
|
+
execFileSync("git", ["init", "-q"], { cwd: repo });
|
|
16
|
+
const ws = options.prefix ? join(repo, options.prefix) : repo;
|
|
17
|
+
const members: unknown[] = [
|
|
18
|
+
{ name: "api", dir: "services/api", kind: "chant" },
|
|
19
|
+
{ name: "web", dir: "apps/web", kind: "chant" },
|
|
20
|
+
{ name: "samples", kind: "examples", glob: "examples/*" },
|
|
21
|
+
];
|
|
22
|
+
if (options.root) members.unshift({ name: "platform", dir: ".", kind: "chant" });
|
|
23
|
+
const files: Record<string, string> = {
|
|
24
|
+
"chant.workspace.json": JSON.stringify({ name: "shop", schema: 1, members }, null, 2),
|
|
25
|
+
"services/api/chant.config.ts": "export default {};\n",
|
|
26
|
+
"services/api/src/infra.ts": "\n",
|
|
27
|
+
"apps/web/chant.config.ts": "export default {};\n",
|
|
28
|
+
"examples/demo/chant.config.ts": "export default {};\n",
|
|
29
|
+
};
|
|
30
|
+
for (const [path, text] of Object.entries(files)) {
|
|
31
|
+
mkdirSync(dirname(join(ws, path)), { recursive: true });
|
|
32
|
+
writeFileSync(join(ws, path), text);
|
|
33
|
+
}
|
|
34
|
+
return repo;
|
|
35
|
+
}
|