@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,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one discovery walk (chant#2527).
|
|
3
|
+
*
|
|
4
|
+
* Every command that decides which files chant reads walks the tree here:
|
|
5
|
+
* source discovery (`./files.ts`'s `findInfraFiles`, behind build, graph,
|
|
6
|
+
* list, explain and lexicon detection), `chant lint`, component discovery,
|
|
7
|
+
* Op discovery and the `chant audit` walk. They used to have five walkers
|
|
8
|
+
* with five sets of rules. v0.80.0 warned about every file whose status the
|
|
9
|
+
* convergence changes (#2571), and this module is that convergence. It is on
|
|
10
|
+
* #2525's level-0 exception list.
|
|
11
|
+
*
|
|
12
|
+
* The rules, for every walker:
|
|
13
|
+
*
|
|
14
|
+
* - `node_modules` and `.git` are never entered.
|
|
15
|
+
* - Dot-directories are skipped. The audit walk keeps `.github` and
|
|
16
|
+
* `.forgejo`, which hold the CI files it audits.
|
|
17
|
+
* - `dist` is skipped.
|
|
18
|
+
* - Git-ignored paths are skipped, nested `.gitignore` files, `.git/info/exclude`
|
|
19
|
+
* and the global excludes file included, the way git itself decides. Only
|
|
20
|
+
* paths below the scan root are asked about: when the scan root is itself
|
|
21
|
+
* ignored, nothing under it is dropped for being ignored, so building or
|
|
22
|
+
* linting an ignored directory still reads it.
|
|
23
|
+
* - When the scan root is inside a chant project, a child directory holding a
|
|
24
|
+
* project config is a boundary and is not entered. A `chant.config.json`
|
|
25
|
+
* holding only lint keys is a fragment, not a project config, and is not a
|
|
26
|
+
* boundary. A `chant.config.ts` always is one, since telling a lint-only one
|
|
27
|
+
* apart would mean running it. When the scan root is outside every project,
|
|
28
|
+
* every child project is read. Op discovery always stops at child projects
|
|
29
|
+
* below its root.
|
|
30
|
+
* - The project's `exclude` globs (#2519) drop what they match, and its
|
|
31
|
+
* `include` globs re-admit what `exclude` or any rule above would skip.
|
|
32
|
+
* `node_modules`, `.git` and workspace members are the exceptions: include
|
|
33
|
+
* never re-admits them.
|
|
34
|
+
* - Workspace members and group matches leave the root project's walk
|
|
35
|
+
* (#2525 rule 3), and only when a workspace declaration exists. Without one nothing is loaded and
|
|
36
|
+
* nothing is excluded; see {@link workspaceMemberDirs}.
|
|
37
|
+
*
|
|
38
|
+
* The walk is synchronous: the audit walk it replaced was, and the glob and
|
|
39
|
+
* ignore facts are gathered once before it starts.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { execFileSync } from "node:child_process";
|
|
43
|
+
import { closeSync, existsSync, openSync, readdirSync, readSync, statSync, type Dirent } from "node:fs";
|
|
44
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
45
|
+
// @ts-ignore — picomatch has no types declaration
|
|
46
|
+
import picomatch from "picomatch";
|
|
47
|
+
import { findProjectConfig, findWorkspaceRoot } from "../project-root";
|
|
48
|
+
import { isLintOnlyFragment, type DiscoveryGlobs } from "../config";
|
|
49
|
+
|
|
50
|
+
/** The commands that walk the tree. */
|
|
51
|
+
export type DiscoveryWalker = "source" | "lint" | "components" | "ops" | "audit";
|
|
52
|
+
|
|
53
|
+
/** Dot-directories the audit walk enters anyway, since they hold the CI files it audits. */
|
|
54
|
+
export const AUDIT_DOT_DIRS: ReadonlySet<string> = new Set([".github", ".forgejo"]);
|
|
55
|
+
|
|
56
|
+
/** Directories no rule and no glob ever lets the walk into. */
|
|
57
|
+
const NEVER_ENTER = new Set(["node_modules", ".git"]);
|
|
58
|
+
|
|
59
|
+
/** One walk: which walker, where from, which files it wants, and the facts only some walkers have. */
|
|
60
|
+
export interface WalkOptions {
|
|
61
|
+
walker: DiscoveryWalker;
|
|
62
|
+
/** The directory the walk starts from. Joined onto, never resolved, so relative roots give relative paths. */
|
|
63
|
+
root: string;
|
|
64
|
+
/**
|
|
65
|
+
* Which files the walker wants, by name and full path. Called only for a
|
|
66
|
+
* file every rule above has let through, so a marker check here reads only
|
|
67
|
+
* files that could be returned.
|
|
68
|
+
*/
|
|
69
|
+
accept: (name: string, fullPath: string) => boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The project's discovery globs. Omitted, none apply. Callers that can load
|
|
72
|
+
* the project config resolve them with `resolveDiscoveryGlobs`.
|
|
73
|
+
*/
|
|
74
|
+
globs?: DiscoveryGlobs;
|
|
75
|
+
/** Sort each directory's entries by name. The audit walk does, for a stable limit. */
|
|
76
|
+
sorted?: boolean;
|
|
77
|
+
/** Follow symlinked directories and files. `chant lint` always did; the others never did. */
|
|
78
|
+
followSymlinks?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* The audit walk's file limit. Every file the walk takes counts, accepted or
|
|
81
|
+
* not; `truncated` is set once a file past the limit is met.
|
|
82
|
+
*/
|
|
83
|
+
limit?: { max: number; truncated: boolean };
|
|
84
|
+
/**
|
|
85
|
+
* Called for a dot-directory the walk skips. Returning true takes the
|
|
86
|
+
* directory's own path as a file without entering it (the audit walk's
|
|
87
|
+
* `.terraform`, which TF023 reports by its presence).
|
|
88
|
+
*/
|
|
89
|
+
takeSkippedDir?: (name: string, fullPath: string) => boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Paths the git-ignore rule leaves alone, to be judged by the caller. The
|
|
92
|
+
* audit walk keeps Terraform state paths for TF023, which has its own
|
|
93
|
+
* `.gitignore` reading.
|
|
94
|
+
*/
|
|
95
|
+
keepIgnored?: (fullPath: string) => boolean;
|
|
96
|
+
/** Directories that leave this walk, absolute. See {@link workspaceMemberDirs}. */
|
|
97
|
+
excludeDirs?: readonly string[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Globs ────────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/** A project's `exclude` and `include` globs, compiled against the config's directory. */
|
|
103
|
+
interface CompiledGlobs {
|
|
104
|
+
/** True when an `exclude` pattern matches the path or a directory above it. */
|
|
105
|
+
excluded(full: string): boolean;
|
|
106
|
+
/** True when an `include` pattern matches the path or a directory above it. */
|
|
107
|
+
included(full: string): boolean;
|
|
108
|
+
/** True when some `include` pattern names a path strictly below directory `full`. */
|
|
109
|
+
includeBelow(full: string): boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Path parts below `home`, or undefined for `home` itself or a path outside it. */
|
|
113
|
+
function partsBelow(home: string, full: string): string[] | undefined {
|
|
114
|
+
const rel = relative(home, resolve(full));
|
|
115
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return undefined;
|
|
116
|
+
return rel.split(sep);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function compileGlobs(globs: DiscoveryGlobs | undefined): CompiledGlobs | undefined {
|
|
120
|
+
if (!globs || (globs.exclude.length === 0 && globs.include.length === 0)) return undefined;
|
|
121
|
+
const home = resolve(globs.root);
|
|
122
|
+
const matcher = (patterns: string[]): ((full: string) => boolean) => {
|
|
123
|
+
if (patterns.length === 0) return () => false;
|
|
124
|
+
const match: (p: string) => boolean = picomatch(patterns, { dot: true });
|
|
125
|
+
return (full) => {
|
|
126
|
+
const parts = partsBelow(home, full);
|
|
127
|
+
return parts !== undefined && parts.some((_, i) => match(parts.slice(0, i + 1).join("/")));
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
// The literal directory part of each include pattern: `.cache/keep/**`
|
|
131
|
+
// gives `.cache/keep`. The walk enters a skipped directory on the way to it.
|
|
132
|
+
const bases = globs.include.map((p) => (picomatch.scan(p) as { base: string }).base).filter((b) => b !== "");
|
|
133
|
+
return {
|
|
134
|
+
excluded: matcher(globs.exclude),
|
|
135
|
+
included: matcher(globs.include),
|
|
136
|
+
includeBelow: (full) => {
|
|
137
|
+
const parts = partsBelow(home, full);
|
|
138
|
+
if (parts === undefined) return false;
|
|
139
|
+
const rel = parts.join("/");
|
|
140
|
+
return bases.some((b) => b.startsWith(`${rel}/`));
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Git ──────────────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The untracked paths git ignores under `root`, absolute, with fully ignored
|
|
149
|
+
* directories listed once. One `git ls-files` call. A tree outside git, a
|
|
150
|
+
* missing `git`, or a root that is itself ignored yields an empty set: ignore
|
|
151
|
+
* rules are asked only about paths below the scan root.
|
|
152
|
+
*/
|
|
153
|
+
export function gitIgnoredBelow(root: string): Set<string> {
|
|
154
|
+
let out: string;
|
|
155
|
+
try {
|
|
156
|
+
out = execFileSync("git", ["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory"], {
|
|
157
|
+
cwd: root,
|
|
158
|
+
encoding: "utf-8",
|
|
159
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
160
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
161
|
+
});
|
|
162
|
+
} catch {
|
|
163
|
+
return new Set();
|
|
164
|
+
}
|
|
165
|
+
const ignored = new Set<string>();
|
|
166
|
+
const abs = resolve(root);
|
|
167
|
+
for (const entry of out.split("\0")) {
|
|
168
|
+
if (entry === "") continue;
|
|
169
|
+
const rel = entry.endsWith("/") ? entry.slice(0, -1) : entry;
|
|
170
|
+
// `./` (or `.`): the scan root is itself ignored.
|
|
171
|
+
if (rel === "." || rel === "") return new Set();
|
|
172
|
+
ignored.add(join(abs, rel));
|
|
173
|
+
}
|
|
174
|
+
return ignored;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Boundaries ───────────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
/** A directory that holds a project config: `chant.config.ts`, or a `chant.config.json` that is not a lint-only fragment. */
|
|
180
|
+
export function isProjectBoundary(dir: string): boolean {
|
|
181
|
+
if (existsSync(join(dir, "chant.config.ts"))) return true;
|
|
182
|
+
const json = join(dir, "chant.config.json");
|
|
183
|
+
return existsSync(json) && !isLintOnlyFragment(json);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Whether child projects end the walk from `root`: always for Ops, otherwise when `root` is inside a project. */
|
|
187
|
+
export function childProjectsAreBoundaries(walker: DiscoveryWalker, root: string): boolean {
|
|
188
|
+
return walker === "ops" || findProjectConfig(root).configPath !== undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Workspace members ────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The directories a workspace declaration moves out of the root project's
|
|
195
|
+
* discovery (#2525 rule 3), absolute and strictly below `root`, or none:
|
|
196
|
+
* every member's directory but the root's, and every group match
|
|
197
|
+
* (`rootExclusions` in `../workspace/declaration.ts`).
|
|
198
|
+
*
|
|
199
|
+
* Level 0 pays for `findWorkspaceRoot`, which only tests whether a
|
|
200
|
+
* `chant.workspace.json` or `.jsonc` exists from `root` up to the git root,
|
|
201
|
+
* and loads nothing. Only when a declaration is found are the workspace
|
|
202
|
+
* modules loaded to read it. A declaration that fails to read excludes
|
|
203
|
+
* nothing here; `chant workspace ls` is where its errors are reported. This
|
|
204
|
+
* is the only place discovery asks about workspaces; the walkers pass the
|
|
205
|
+
* result as `excludeDirs`.
|
|
206
|
+
*/
|
|
207
|
+
export async function workspaceMemberDirs(root: string): Promise<string[]> {
|
|
208
|
+
const found = findWorkspaceRoot(root);
|
|
209
|
+
if (!found) return [];
|
|
210
|
+
const start = resolve(root);
|
|
211
|
+
const [{ readDeclaration, resolveGroups, rootExclusions }, { workingTree }] = await Promise.all([
|
|
212
|
+
import("../workspace/declaration"),
|
|
213
|
+
import("../workspace/tree"),
|
|
214
|
+
]);
|
|
215
|
+
let dirs: string[];
|
|
216
|
+
try {
|
|
217
|
+
const tree = workingTree(found.dir);
|
|
218
|
+
const declaration = readDeclaration(tree);
|
|
219
|
+
dirs = rootExclusions(declaration, resolveGroups(declaration, tree)).map((e) => resolve(found.dir, e.dir));
|
|
220
|
+
} catch {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
return dirs.filter((d) => partsBelow(start, d) !== undefined);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── Markers ──────────────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
/** The first `bytes` of a file as UTF-8, or "" when it can't be read. */
|
|
229
|
+
export function readHead(file: string, bytes: number): string {
|
|
230
|
+
let fd: number | undefined;
|
|
231
|
+
try {
|
|
232
|
+
fd = openSync(file, "r");
|
|
233
|
+
const buf = Buffer.alloc(bytes);
|
|
234
|
+
const n = readSync(fd, buf, 0, bytes, 0);
|
|
235
|
+
return buf.toString("utf8", 0, n);
|
|
236
|
+
} catch {
|
|
237
|
+
return "";
|
|
238
|
+
} finally {
|
|
239
|
+
if (fd !== undefined) closeSync(fd);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── The walk ─────────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
/** Walk `opts.root` under the rules in the module doc and return the files `accept` took. */
|
|
246
|
+
export function walkDiscovery(opts: WalkOptions): string[] {
|
|
247
|
+
const out: string[] = [];
|
|
248
|
+
const globs = compileGlobs(opts.globs);
|
|
249
|
+
const ignored = gitIgnoredBelow(opts.root);
|
|
250
|
+
const boundaries = childProjectsAreBoundaries(opts.walker, opts.root);
|
|
251
|
+
const excludeDirs = new Set((opts.excludeDirs ?? []).map((d) => resolve(d)));
|
|
252
|
+
const dotDirsKept = opts.walker === "audit" ? AUDIT_DOT_DIRS : new Set<string>();
|
|
253
|
+
const limit = opts.limit;
|
|
254
|
+
|
|
255
|
+
const isIgnored = (full: string): boolean => ignored.size > 0 && ignored.has(resolve(full));
|
|
256
|
+
|
|
257
|
+
/** Take a path; false once the limit is reached. */
|
|
258
|
+
const take = (full: string): boolean => {
|
|
259
|
+
if (limit && out.length >= limit.max) {
|
|
260
|
+
limit.truncated = true;
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
out.push(full);
|
|
264
|
+
return true;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const kind = (dir: string, e: Dirent): "dir" | "file" | undefined => {
|
|
268
|
+
if (e.isDirectory()) return "dir";
|
|
269
|
+
if (e.isFile()) return "file";
|
|
270
|
+
if (opts.followSymlinks && e.isSymbolicLink()) {
|
|
271
|
+
try {
|
|
272
|
+
const s = statSync(join(dir, e.name));
|
|
273
|
+
return s.isDirectory() ? "dir" : s.isFile() ? "file" : undefined;
|
|
274
|
+
} catch {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Walk `dir`. `probing` is true below a directory a rule skipped that the
|
|
283
|
+
* walk entered only because an `include` pattern names something inside
|
|
284
|
+
* it: there, only what `include` matches is taken.
|
|
285
|
+
*/
|
|
286
|
+
const walk = (dir: string, probing: boolean): boolean => {
|
|
287
|
+
let entries: Dirent[];
|
|
288
|
+
try {
|
|
289
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
290
|
+
} catch {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
if (opts.sorted) entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
294
|
+
for (const e of entries) {
|
|
295
|
+
if (limit?.truncated) return false;
|
|
296
|
+
const full = join(dir, e.name);
|
|
297
|
+
const type = kind(dir, e);
|
|
298
|
+
if (type === "dir") {
|
|
299
|
+
if (NEVER_ENTER.has(e.name) || excludeDirs.has(resolve(full))) continue;
|
|
300
|
+
const dotSkip = e.name.startsWith(".") && !dotDirsKept.has(e.name);
|
|
301
|
+
if (dotSkip && opts.takeSkippedDir?.(e.name, full)) {
|
|
302
|
+
if (!take(full)) return false;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const ruleSkip =
|
|
306
|
+
dotSkip ||
|
|
307
|
+
e.name === "dist" ||
|
|
308
|
+
(isIgnored(full) && !opts.keepIgnored?.(full)) ||
|
|
309
|
+
(boundaries && isProjectBoundary(full));
|
|
310
|
+
if (ruleSkip || probing) {
|
|
311
|
+
if (globs?.included(full)) {
|
|
312
|
+
if (!walk(full, false)) return false;
|
|
313
|
+
} else if (globs?.includeBelow(full)) {
|
|
314
|
+
if (!walk(full, true)) return false;
|
|
315
|
+
}
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (!walk(full, false)) return false;
|
|
319
|
+
} else if (type === "file") {
|
|
320
|
+
const skipped =
|
|
321
|
+
probing ||
|
|
322
|
+
(isIgnored(full) && !opts.keepIgnored?.(full)) ||
|
|
323
|
+
(globs?.excluded(full) ?? false);
|
|
324
|
+
if (skipped && !globs?.included(full)) continue;
|
|
325
|
+
if (!opts.accept(e.name, full)) continue;
|
|
326
|
+
if (!take(full)) return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return true;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
walk(opts.root, false);
|
|
333
|
+
return out;
|
|
334
|
+
}
|
package/src/effect-receipt.ts
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* `sha256:<hex>` like the build-ledger digests in ./lifecycle/build-ledger.ts).
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
import {
|
|
41
|
+
import { contentDigest } from "./content-digest";
|
|
42
42
|
import { DECLARABLE_MARKER, type Declarable } from "./declarable";
|
|
43
43
|
import { isIntrinsic, type Intrinsic } from "./intrinsic";
|
|
44
44
|
|
|
@@ -293,7 +293,7 @@ function encodeCanonical(value: unknown, path: string, seen: Set<object>): strin
|
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
function sha256Digest(canonical: string): string {
|
|
296
|
-
return
|
|
296
|
+
return contentDigest(canonical);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
// ─────────────────────────────────────────────────────────────────────────
|
package/src/graph-ir.ts
CHANGED
|
@@ -230,7 +230,10 @@ export interface IRExport {
|
|
|
230
230
|
/** A cross-stack import this stack consumes — a `Parameter` fed from another
|
|
231
231
|
* stack's output at deploy time. A viewer matches an import `name` to another
|
|
232
232
|
* stack's export `name` to draw the cross-stack edge; the parameter's in-stack
|
|
233
|
-
* consumers are ordinary `$ref` edges to `node` (#513).
|
|
233
|
+
* consumers are ordinary `$ref` edges to `node` (#513). A reader that infers
|
|
234
|
+
* that match by name uses `joinKey()` from `./join-key.ts` and labels the edge
|
|
235
|
+
* `exact` or `folded`, the way `chant workspace graph` labels joins across
|
|
236
|
+
* members (#2539, ws-008). */
|
|
234
237
|
export interface IRImport {
|
|
235
238
|
/** Parameter name — the cross-stack handle (e.g. "clusterArn"). */
|
|
236
239
|
name: string;
|
|
@@ -290,8 +293,22 @@ export interface IRPipeline {
|
|
|
290
293
|
edges: IRPipelineEdge[];
|
|
291
294
|
}
|
|
292
295
|
|
|
296
|
+
/**
|
|
297
|
+
* The version `chant graph --format ir` writes on its output (#2529, #2524 D8).
|
|
298
|
+
* The format changes only additively: a new field or edge kind never bumps
|
|
299
|
+
* it, and a consumer ignores the fields and edge kinds it does not know. The
|
|
300
|
+
* number moves only for a change a consumer could not ignore.
|
|
301
|
+
*/
|
|
302
|
+
export const GRAPH_IR_VERSION = 1;
|
|
303
|
+
|
|
293
304
|
/** The full graph IR for a project at the default (declarable) detail level. */
|
|
294
305
|
export interface GraphIR {
|
|
306
|
+
/**
|
|
307
|
+
* {@link GRAPH_IR_VERSION}, stamped by `chant graph --format ir` when it
|
|
308
|
+
* writes the IR (#2529). Absent on an IR built in memory, and on output
|
|
309
|
+
* from a chant older than the field, which a reader treats as version 1.
|
|
310
|
+
*/
|
|
311
|
+
version?: number;
|
|
295
312
|
nodes: IRNode[];
|
|
296
313
|
edges: IREdge[];
|
|
297
314
|
groups: IRGroups;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { joinKey, joinLabel } from "./join-key";
|
|
3
|
+
|
|
4
|
+
describe("joinKey (#2539, ws-008)", () => {
|
|
5
|
+
test("folds case and punctuation", () => {
|
|
6
|
+
for (const name of ["ClusterArn", "clusterArn", "cluster_arn", "cluster-arn", "CLUSTER.ARN", "Cluster Arn"]) {
|
|
7
|
+
expect(joinKey(name)).toBe("clusterarn");
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("keeps digits and letters outside ASCII", () => {
|
|
12
|
+
expect(joinKey("Subnet1")).toBe("subnet1");
|
|
13
|
+
expect(joinKey("Subnet1")).not.toBe(joinKey("Subnet2"));
|
|
14
|
+
expect(joinKey("Größe")).toBe("größe");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("labels a join exact or folded, and says when names don't join", () => {
|
|
18
|
+
expect(joinLabel("ClusterArn", "ClusterArn")).toBe("exact");
|
|
19
|
+
expect(joinLabel("clusterArn", "ClusterArn")).toBe("folded");
|
|
20
|
+
expect(joinLabel("vpc_id", "VpcId")).toBe("folded");
|
|
21
|
+
expect(joinLabel("VpcId", "SubnetId")).toBeUndefined();
|
|
22
|
+
expect(joinLabel("", "")).toBeUndefined();
|
|
23
|
+
expect(joinLabel("--", "__")).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
});
|
package/src/join-key.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one join key for names matched across projects (#2524 D6, ws-008).
|
|
3
|
+
*
|
|
4
|
+
* A consumer's parameter and a producer's output are joined by name when
|
|
5
|
+
* nobody wrote the join down. Deploy matches names exactly, and real projects
|
|
6
|
+
* rarely spell both sides the same (`clusterArn` against `ClusterArn`), so an
|
|
7
|
+
* inferred join compares {@link joinKey}s and says how it matched: `exact`
|
|
8
|
+
* when the names are equal, `folded` when only case or punctuation differ.
|
|
9
|
+
* A declared member link is always compared exactly and never uses this.
|
|
10
|
+
*
|
|
11
|
+
* Before this module, core only published the handles (a graph IR's
|
|
12
|
+
* `exports` and `imports`) and each viewer matched them with its own rule.
|
|
13
|
+
* Every reader that infers a join by name uses this function instead, so the
|
|
14
|
+
* same workspace shows the same inferred edges everywhere. It imports nothing,
|
|
15
|
+
* so any reader can load it through `@intentius/chant/join-key`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** How an inferred join matched its two names. */
|
|
19
|
+
export type JoinLabel = "exact" | "folded";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The key two names are joined on: Unicode-normalised, lowercased, with every
|
|
23
|
+
* character that is not a letter or a digit removed. `ClusterArn`,
|
|
24
|
+
* `clusterArn`, `cluster_arn` and `cluster-arn` share the key `clusterarn`.
|
|
25
|
+
*/
|
|
26
|
+
export function joinKey(name: string): string {
|
|
27
|
+
return name.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]/gu, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* How `a` and `b` join: `exact` when they are the same string, `folded` when
|
|
32
|
+
* only their {@link joinKey}s are equal, and undefined when they don't join.
|
|
33
|
+
* Names whose key is empty never join.
|
|
34
|
+
*/
|
|
35
|
+
export function joinLabel(a: string, b: string): JoinLabel | undefined {
|
|
36
|
+
if (a === b) return a === "" ? undefined : "exact";
|
|
37
|
+
const key = joinKey(a);
|
|
38
|
+
return key !== "" && key === joinKey(b) ? "folded" : undefined;
|
|
39
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant #2520 — a lexicon declared by module path in `chant.config.ts`
|
|
3
|
+
* (`{ name, module }`) loads with no `@intentius/chant-lexicon-<name>`
|
|
4
|
+
* package, for build, lint, run and the capability registry.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
7
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { loadChantConfig } from "./config";
|
|
12
|
+
import { lexiconModulePath, lexiconNames, resetLexiconModules } from "./lexicon-module";
|
|
13
|
+
import { loadPlugins, resolveProjectLexicons, loadPlugin } from "./cli/plugins";
|
|
14
|
+
import { loadActivities } from "./op/activity-registry";
|
|
15
|
+
import { loadActivityContracts } from "./op/activity-contract-registry";
|
|
16
|
+
import { loadGatePolicyEvaluator } from "./op/gate-approval";
|
|
17
|
+
import { buildCapabilityRegistry } from "./components/capability-plugin-loader";
|
|
18
|
+
import { buildCommand } from "./cli/commands/build";
|
|
19
|
+
import { lintCommand } from "./cli/commands/lint";
|
|
20
|
+
|
|
21
|
+
const activityContractModule = resolve(import.meta.dirname, "op/activity-contract.ts");
|
|
22
|
+
const zodModule = createRequire(import.meta.url).resolve("zod");
|
|
23
|
+
|
|
24
|
+
/** The whole project-local lexicon, in one module, importing core by absolute path so it needs no package. */
|
|
25
|
+
function lexiconSource(pluginName = "site"): string {
|
|
26
|
+
return `
|
|
27
|
+
import { z } from ${JSON.stringify(zodModule)};
|
|
28
|
+
import { activityContract } from ${JSON.stringify(activityContractModule)};
|
|
29
|
+
|
|
30
|
+
export const sitePlugin = {
|
|
31
|
+
name: ${JSON.stringify(pluginName)},
|
|
32
|
+
serializer: {
|
|
33
|
+
name: "site",
|
|
34
|
+
rulePrefix: "SITE",
|
|
35
|
+
serialize: (entities) => JSON.stringify({ pages: [...entities.keys()].sort() }),
|
|
36
|
+
},
|
|
37
|
+
generate: async () => {},
|
|
38
|
+
validate: async () => {},
|
|
39
|
+
coverage: async () => {},
|
|
40
|
+
package: async () => {},
|
|
41
|
+
activities: () => ({ siteDeploy: async (args) => ({ deployed: args.target }) }),
|
|
42
|
+
activityContracts: () => [activityContract("siteDeploy", z.strictObject({ target: z.string() }))],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const siteCapabilities = {
|
|
46
|
+
name: "site-capabilities",
|
|
47
|
+
version: "0.0.0",
|
|
48
|
+
capabilities: () => [{ kind: "site-publish", run: async () => ({}) }],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export async function evaluateGatePolicy() {
|
|
52
|
+
return { decision: "allow", determiningPolicies: [], errors: [] };
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function opSource(args: Record<string, unknown>): string {
|
|
58
|
+
return `
|
|
59
|
+
export default {
|
|
60
|
+
[Symbol.for("chant.declarable")]: true,
|
|
61
|
+
entityType: "Chant::Op",
|
|
62
|
+
lexicon: "chant",
|
|
63
|
+
kind: "resource",
|
|
64
|
+
props: {
|
|
65
|
+
name: "publish",
|
|
66
|
+
overview: "test",
|
|
67
|
+
phases: [
|
|
68
|
+
{ name: "Publish", steps: [{ kind: "activity", fn: "siteDeploy", args: ${JSON.stringify(args)} }] },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe("lexicons declared by module path (#2520)", () => {
|
|
76
|
+
let dir: string;
|
|
77
|
+
|
|
78
|
+
beforeEach(async () => {
|
|
79
|
+
resetLexiconModules();
|
|
80
|
+
dir = await mkdtemp(join(tmpdir(), "chant-lexicon-module-"));
|
|
81
|
+
await mkdir(join(dir, "lexicon"));
|
|
82
|
+
await writeFile(join(dir, "lexicon", "index.ts"), lexiconSource());
|
|
83
|
+
await writeFile(
|
|
84
|
+
join(dir, "chant.config.ts"),
|
|
85
|
+
`export default { lexicons: [{ name: "site", module: "./lexicon/index.ts" }] };\n`,
|
|
86
|
+
);
|
|
87
|
+
await writeFile(
|
|
88
|
+
join(dir, "main.ts"),
|
|
89
|
+
`export const home = { lexicon: "site", entityType: "Site::Page", kind: "resource", props: {}, [Symbol.for("chant.declarable")]: true };\n`,
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterEach(async () => {
|
|
94
|
+
resetLexiconModules();
|
|
95
|
+
await rm(dir, { recursive: true, force: true });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("the loaded config holds the name, and the module path is recorded against the config's directory", async () => {
|
|
99
|
+
const { config } = await loadChantConfig(dir);
|
|
100
|
+
expect(config.lexicons).toEqual(["site"]);
|
|
101
|
+
expect(config.lexiconModules).toEqual({ site: join(dir, "lexicon", "index.ts") });
|
|
102
|
+
expect(lexiconModulePath("site")).toBe(join(dir, "lexicon", "index.ts"));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("a config of plain names comes back as written, with nothing recorded", async () => {
|
|
106
|
+
const plain = join(dir, "plain");
|
|
107
|
+
await mkdir(plain);
|
|
108
|
+
await writeFile(join(plain, "chant.config.ts"), `export default { lexicons: ["aws"] };\n`);
|
|
109
|
+
const { config } = await loadChantConfig(plain);
|
|
110
|
+
expect(config).toEqual({ lexicons: ["aws"] });
|
|
111
|
+
expect(lexiconModulePath("aws")).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a later config naming the lexicon as a package drops the recorded path", async () => {
|
|
115
|
+
await loadChantConfig(dir);
|
|
116
|
+
expect(lexiconModulePath("site")).toBeDefined();
|
|
117
|
+
// A second project; a config file is a module, cached by path.
|
|
118
|
+
const other = join(dir, "other");
|
|
119
|
+
await mkdir(other);
|
|
120
|
+
await writeFile(join(other, "chant.config.ts"), `export default { lexicons: ["site"] };\n`);
|
|
121
|
+
await loadChantConfig(other);
|
|
122
|
+
expect(lexiconModulePath("site")).toBeUndefined();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("an entry with an unknown key is rejected", async () => {
|
|
126
|
+
const pinned = join(dir, "pinned");
|
|
127
|
+
await mkdir(pinned);
|
|
128
|
+
await writeFile(
|
|
129
|
+
join(pinned, "chant.config.ts"),
|
|
130
|
+
`export default { lexicons: [{ name: "site", module: "./lexicon/index.ts", pin: "x" }] };\n`,
|
|
131
|
+
);
|
|
132
|
+
await expect(loadChantConfig(pinned)).rejects.toThrow(/Invalid chant config/);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("lexiconNames reads both entry forms", () => {
|
|
136
|
+
expect(lexiconNames(["aws", { name: "site", module: "./x.ts" }])).toEqual(["aws", "site"]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("build: the plugin loads from the module and the project builds with it", async () => {
|
|
140
|
+
const names = await resolveProjectLexicons(dir);
|
|
141
|
+
expect(names).toEqual(["site"]);
|
|
142
|
+
const plugins = await loadPlugins(names);
|
|
143
|
+
expect(plugins.map((p) => p.name)).toEqual(["site"]);
|
|
144
|
+
|
|
145
|
+
await writeFile(join(dir, "publish.op.ts"), opSource({ target: "prod" }));
|
|
146
|
+
const result = await buildCommand({
|
|
147
|
+
path: dir,
|
|
148
|
+
format: "json",
|
|
149
|
+
serializers: plugins.map((p) => p.serializer),
|
|
150
|
+
plugins,
|
|
151
|
+
});
|
|
152
|
+
expect(result.errors).toEqual([]);
|
|
153
|
+
expect(result.success).toBe(true);
|
|
154
|
+
// The page and the Op.
|
|
155
|
+
expect(result.resourceCount).toBe(2);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("a plugin whose name differs from the declared name is refused", async () => {
|
|
159
|
+
await writeFile(join(dir, "lexicon", "index.ts"), lexiconSource("other"));
|
|
160
|
+
await loadChantConfig(dir);
|
|
161
|
+
await expect(loadPlugin("site")).rejects.toThrow(/plugin it exports is named "other"/);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("run: the plugin's activities are loaded by name, with no op/activities subpath", async () => {
|
|
165
|
+
await loadChantConfig(dir);
|
|
166
|
+
const activities = await loadActivities(["site"]);
|
|
167
|
+
expect(await activities.get("siteDeploy")?.({ target: "prod" })).toEqual({ deployed: "prod" });
|
|
168
|
+
// Core's base activities are still there.
|
|
169
|
+
expect(activities.get("shellCmd")).toBeDefined();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("activity contracts are found by name, with no op/activity-contracts subpath", async () => {
|
|
173
|
+
await loadChantConfig(dir);
|
|
174
|
+
const contracts = await loadActivityContracts(["site"]);
|
|
175
|
+
expect(contracts.get("siteDeploy")).toBeDefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("lint: the plugin's contract validates an Op step calling its activity", async () => {
|
|
179
|
+
await writeFile(join(dir, "publish.op.ts"), opSource({ target: 42 }));
|
|
180
|
+
const bad = await lintCommand({ path: dir, format: "stylish" });
|
|
181
|
+
expect(
|
|
182
|
+
bad.diagnostics.some((d) => d.ruleId === "OPS012" && d.message.includes("siteDeploy")),
|
|
183
|
+
).toBe(true);
|
|
184
|
+
|
|
185
|
+
// A fresh path: ES modules are cached by URL, so the corrected Op needs its own file.
|
|
186
|
+
await rm(join(dir, "publish.op.ts"));
|
|
187
|
+
await writeFile(join(dir, "publish2.op.ts"), opSource({ target: "prod" }));
|
|
188
|
+
const good = await lintCommand({ path: dir, format: "stylish" });
|
|
189
|
+
expect(good.diagnostics.filter((d) => d.ruleId === "OPS012")).toEqual([]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("the capability registry registers the module's capability plugin", async () => {
|
|
193
|
+
await loadChantConfig(dir);
|
|
194
|
+
const registry = await buildCapabilityRegistry({ lexicons: ["site"] });
|
|
195
|
+
expect(registry.has("site-publish")).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("the gate policy evaluator is read from the module", async () => {
|
|
199
|
+
await loadChantConfig(dir);
|
|
200
|
+
const evaluator = await loadGatePolicyEvaluator("site");
|
|
201
|
+
expect(typeof evaluator.evaluateGatePolicy).toBe("function");
|
|
202
|
+
});
|
|
203
|
+
});
|