@intentius/chant 0.80.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/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 +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/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 +54 -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 +230 -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 +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/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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant#2591, chant#2589 — read the `lexicons` a project's config declares
|
|
3
|
+
* without running the config.
|
|
4
|
+
*
|
|
5
|
+
* `chant.config.ts` is project code. Most commands evaluate it (see
|
|
6
|
+
* `./config-sandbox.ts`), but a few only need to know which lexicons it
|
|
7
|
+
* declares by path (#2520) so their messages can name the path instead of a
|
|
8
|
+
* package: `init --force`, `dev onboard`, `import --agents` and `audit`.
|
|
9
|
+
* Those read the config here, statically, and never evaluate it.
|
|
10
|
+
*
|
|
11
|
+
* The reader parses the file and reduces the `lexicons` value with the fold
|
|
12
|
+
* machinery (`./fold/fold.ts`'s {@link fold}), given the file's own top-level
|
|
13
|
+
* `const`s and nothing else. It never imports anything, so an imported
|
|
14
|
+
* binding, a function call or `process.env` is not readable. When the value
|
|
15
|
+
* cannot be read, the result says why and callers treat path lexicons as
|
|
16
|
+
* unknown. They never fall back to evaluating the config.
|
|
17
|
+
*
|
|
18
|
+
* What the config's export looks like follows `selectConfigExport` in
|
|
19
|
+
* `./config-sandbox.ts`: `export default`, else `export const config`, else
|
|
20
|
+
* the module's named exports (`export const lexicons`).
|
|
21
|
+
*
|
|
22
|
+
* `chant.config.json` is data and is simply parsed.
|
|
23
|
+
*
|
|
24
|
+
* {@link readConfigFieldsStatically} reads other top-level fields the same
|
|
25
|
+
* way. `chant workspace check` reads a member's `ownership` and
|
|
26
|
+
* `environments` with it for the ledger checks (#2641).
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
29
|
+
import { dirname, join } from "node:path";
|
|
30
|
+
import * as ts from "typescript";
|
|
31
|
+
import { ChantConfigSchema, findProjectConfigPastFragments } from "./config";
|
|
32
|
+
import { collectConsts, fold, propName, FoldError } from "./fold/fold";
|
|
33
|
+
import type { LexiconDeclaration } from "./lexicon-module";
|
|
34
|
+
|
|
35
|
+
/** What {@link readLexiconDeclarationsStatically} found. */
|
|
36
|
+
export type StaticLexiconRead =
|
|
37
|
+
/** No `chant.config.ts` or `chant.config.json` for the directory. */
|
|
38
|
+
| { status: "no-config" }
|
|
39
|
+
/** The declarations, in order. Empty when the config declares no `lexicons`. */
|
|
40
|
+
| { status: "read"; configPath: string; entries: LexiconDeclaration[] }
|
|
41
|
+
/** The config exists, but its `lexicons` could not be read without running it. */
|
|
42
|
+
| { status: "unknown"; configPath: string; reason: string };
|
|
43
|
+
|
|
44
|
+
/** Signals a config shape the reader does not follow. The message is the reason reported. */
|
|
45
|
+
class Unreadable extends Error {}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Find the project config for `startDir` (the walk `loadChantConfigUpward`
|
|
49
|
+
* makes) and read its `lexicons` without evaluating it.
|
|
50
|
+
*/
|
|
51
|
+
export function readLexiconDeclarationsStatically(startDir: string): StaticLexiconRead {
|
|
52
|
+
const { configPath } = findProjectConfigPastFragments(startDir);
|
|
53
|
+
if (configPath === undefined) return { status: "no-config" };
|
|
54
|
+
try {
|
|
55
|
+
const value = readFields(configPath, ["lexicons"]).lexicons;
|
|
56
|
+
if (value === undefined) return { status: "read", configPath, entries: [] };
|
|
57
|
+
const parsed = ChantConfigSchema.shape.lexicons.safeParse(value);
|
|
58
|
+
if (!parsed.success) {
|
|
59
|
+
return { status: "unknown", configPath, reason: "`lexicons` is not a list of lexicon entries" };
|
|
60
|
+
}
|
|
61
|
+
return { status: "read", configPath, entries: (parsed.data ?? []) as LexiconDeclaration[] };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return { status: "unknown", configPath, reason: unreadableReason(err) };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function unreadableReason(err: unknown): string {
|
|
68
|
+
return err instanceof FoldError
|
|
69
|
+
? `line ${err.line}: ${err.message}`
|
|
70
|
+
: err instanceof Unreadable
|
|
71
|
+
? err.message
|
|
72
|
+
: `the file could not be read (${err instanceof Error ? err.message : String(err)})`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** What {@link readConfigFieldsStatically} found. */
|
|
76
|
+
export type StaticFieldsRead =
|
|
77
|
+
/** No `chant.config.ts` or `chant.config.json` in the directory. */
|
|
78
|
+
| { status: "no-config" }
|
|
79
|
+
/** Each asked-for field the config sets, folded to a value. A field it doesn't set is absent. */
|
|
80
|
+
| { status: "read"; configPath: string; fields: Record<string, unknown> }
|
|
81
|
+
/** The config exists, but a field could not be read without running it. */
|
|
82
|
+
| { status: "unknown"; configPath: string; reason: string };
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Read top-level `fields` of the config in `dir` itself, as
|
|
86
|
+
* `loadChantConfig(dir)` finds it (`chant.config.ts`, else
|
|
87
|
+
* `chant.config.json`), without evaluating it. The values are not checked
|
|
88
|
+
* against the config schema; the caller does that for the fields it reads.
|
|
89
|
+
*/
|
|
90
|
+
export function readConfigFieldsStatically(dir: string, fields: readonly string[]): StaticFieldsRead {
|
|
91
|
+
const configPath = ["chant.config.ts", "chant.config.json"].map((f) => join(dir, f)).find((f) => existsSync(f));
|
|
92
|
+
if (configPath === undefined) return { status: "no-config" };
|
|
93
|
+
try {
|
|
94
|
+
return { status: "read", configPath, fields: readFields(configPath, fields) };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return { status: "unknown", configPath, reason: unreadableReason(err) };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readFields(configPath: string, fields: readonly string[]): Record<string, unknown> {
|
|
101
|
+
const values = configPath.endsWith(".json") ? readJsonFields(configPath, fields) : readTsFields(configPath, fields);
|
|
102
|
+
// Absent fields are left out rather than set to undefined.
|
|
103
|
+
return Object.fromEntries(Object.entries(values).filter(([, v]) => v !== undefined));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The directory a read config's relative module paths resolve against. */
|
|
107
|
+
export function staticConfigBaseDir(read: { configPath: string }): string {
|
|
108
|
+
return dirname(read.configPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The line a command prints when {@link readLexiconDeclarationsStatically}
|
|
113
|
+
* could not read the config, or `undefined` when it could.
|
|
114
|
+
*/
|
|
115
|
+
export function unknownPathLexiconsNotice(read: StaticLexiconRead): string | undefined {
|
|
116
|
+
if (read.status !== "unknown") return undefined;
|
|
117
|
+
return (
|
|
118
|
+
`note: could not read the lexicons in ${read.configPath} without running it (${read.reason}). ` +
|
|
119
|
+
"chant does not run it for this command, so any lexicon it declares by path is unknown here and is treated as a package."
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readJsonFields(configPath: string, fields: readonly string[]): Record<string, unknown> {
|
|
124
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as unknown;
|
|
125
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
126
|
+
throw new Unreadable("the config is not a JSON object");
|
|
127
|
+
}
|
|
128
|
+
return Object.fromEntries(fields.map((f) => [f, (parsed as Record<string, unknown>)[f]]));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readTsFields(configPath: string, fields: readonly string[]): Record<string, unknown> {
|
|
132
|
+
const source = ts.createSourceFile(configPath, readFileSync(configPath, "utf-8"), ts.ScriptTarget.Latest, true);
|
|
133
|
+
const consts = collectConsts(source);
|
|
134
|
+
|
|
135
|
+
let defaultExport: ts.Expression | undefined;
|
|
136
|
+
const named = new Map<string, ts.Expression>();
|
|
137
|
+
for (const statement of source.statements) {
|
|
138
|
+
if (ts.isExportAssignment(statement)) {
|
|
139
|
+
if (statement.isExportEquals) throw new Unreadable("`export =` is not read");
|
|
140
|
+
defaultExport = statement.expression;
|
|
141
|
+
} else if (ts.isExportDeclaration(statement)) {
|
|
142
|
+
// `export { x as default }`, `export * from "./other"`: the export's
|
|
143
|
+
// value lives elsewhere, possibly in another module.
|
|
144
|
+
throw new Unreadable("an `export { … }` or `export … from` declaration is not read");
|
|
145
|
+
} else if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
|
|
146
|
+
if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) {
|
|
147
|
+
throw new Unreadable("an exported `let` or `var` is not read");
|
|
148
|
+
}
|
|
149
|
+
for (const decl of statement.declarationList.declarations) {
|
|
150
|
+
if (ts.isIdentifier(decl.name) && decl.initializer) named.set(decl.name.text, decl.initializer);
|
|
151
|
+
else throw new Unreadable("a destructured export is not read");
|
|
152
|
+
}
|
|
153
|
+
} else if (hasExportModifier(statement) && hasDefaultModifier(statement)) {
|
|
154
|
+
throw new Unreadable("the default export is not an object");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const configExpr = defaultExport ?? named.get("config");
|
|
159
|
+
if (configExpr === undefined) {
|
|
160
|
+
// The module's named exports are the config.
|
|
161
|
+
return Object.fromEntries(
|
|
162
|
+
fields.map((f) => {
|
|
163
|
+
const expr = named.get(f);
|
|
164
|
+
return [f, expr === undefined ? undefined : fold(expr, consts)];
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const obj = configObject(configExpr, consts, new Set());
|
|
169
|
+
return Object.fromEntries(fields.map((f) => [f, fieldOfObject(obj, f, consts)]));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The object literal a config export reduces to, following `const` aliases. */
|
|
173
|
+
function configObject(
|
|
174
|
+
node: ts.Expression,
|
|
175
|
+
consts: Map<string, ts.Expression>,
|
|
176
|
+
seen: Set<string>,
|
|
177
|
+
): ts.ObjectLiteralExpression {
|
|
178
|
+
const inner = unwrap(node);
|
|
179
|
+
if (ts.isObjectLiteralExpression(inner)) return inner;
|
|
180
|
+
if (ts.isIdentifier(inner)) {
|
|
181
|
+
const init = consts.get(inner.text);
|
|
182
|
+
if (init === undefined || seen.has(inner.text)) {
|
|
183
|
+
throw new Unreadable(`the exported config \`${inner.text}\` is not a top-level const of this file`);
|
|
184
|
+
}
|
|
185
|
+
seen.add(inner.text);
|
|
186
|
+
return configObject(init, consts, seen);
|
|
187
|
+
}
|
|
188
|
+
throw new Unreadable("the exported config is not an object literal");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** The value `field` takes in a config object literal, or `undefined` when it sets none. */
|
|
192
|
+
function fieldOfObject(obj: ts.ObjectLiteralExpression, field: string, consts: Map<string, ts.Expression>): unknown {
|
|
193
|
+
let value: unknown = undefined;
|
|
194
|
+
for (const prop of obj.properties) {
|
|
195
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
196
|
+
const spread = fold(prop.expression, consts);
|
|
197
|
+
if (spread !== null && typeof spread === "object" && Object.prototype.hasOwnProperty.call(spread, field)) {
|
|
198
|
+
value = (spread as Record<string, unknown>)[field];
|
|
199
|
+
}
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (prop.name === undefined) continue;
|
|
203
|
+
const name = propName(prop.name);
|
|
204
|
+
if (name !== field) continue;
|
|
205
|
+
if (ts.isPropertyAssignment(prop)) value = fold(prop.initializer, consts);
|
|
206
|
+
else if (ts.isShorthandPropertyAssignment(prop)) value = fold(prop.name, consts);
|
|
207
|
+
else throw new Unreadable(`\`${field}\` is a method or accessor`);
|
|
208
|
+
}
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function unwrap(node: ts.Expression): ts.Expression {
|
|
213
|
+
let current = node;
|
|
214
|
+
while (
|
|
215
|
+
ts.isParenthesizedExpression(current) ||
|
|
216
|
+
ts.isAsExpression(current) ||
|
|
217
|
+
ts.isSatisfiesExpression(current) ||
|
|
218
|
+
ts.isNonNullExpression(current) ||
|
|
219
|
+
ts.isTypeAssertionExpression(current)
|
|
220
|
+
) {
|
|
221
|
+
current = current.expression;
|
|
222
|
+
}
|
|
223
|
+
return current;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function hasExportModifier(node: ts.Node): boolean {
|
|
227
|
+
return (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) ?? false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function hasDefaultModifier(node: ts.Node): boolean {
|
|
231
|
+
return (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) ?? false;
|
|
232
|
+
}
|
package/src/config.test.ts
CHANGED
|
@@ -468,10 +468,10 @@ describe("exclude / include discovery globs (#2519)", () => {
|
|
|
468
468
|
await expect(resolveDiscoveryGlobs(TEST_DIR)).rejects.toThrow(message);
|
|
469
469
|
});
|
|
470
470
|
|
|
471
|
-
test("resolveDiscoveryGlobs
|
|
471
|
+
test("resolveDiscoveryGlobs returns include alone, since it re-admits the walk's default skips (#2527)", async () => {
|
|
472
472
|
expect(await resolveDiscoveryGlobs(TEST_DIR)).toBeUndefined();
|
|
473
473
|
writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ include: ["ops/**"] }));
|
|
474
|
-
expect(await resolveDiscoveryGlobs(TEST_DIR)).
|
|
474
|
+
expect(await resolveDiscoveryGlobs(TEST_DIR)).toEqual({ root: TEST_DIR, exclude: [], include: ["ops/**"] });
|
|
475
475
|
});
|
|
476
476
|
|
|
477
477
|
test("resolveDiscoveryGlobs anchors the globs at the project config's directory, past a lint fragment", async () => {
|
package/src/config.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { BuildParamsConfig } from "./build-params";
|
|
|
10
10
|
import type { BuildParamProvenance } from "./provenance";
|
|
11
11
|
import { findProjectConfig } from "./project-root";
|
|
12
12
|
import { evaluateProjectConfig } from "./config-sandbox";
|
|
13
|
-
import { lexiconNames, registerLexiconDeclarations, type LexiconDeclaration } from "./lexicon-module";
|
|
13
|
+
import { lexiconNames, pathLexiconRoot, registerLexiconDeclarations, type LexiconDeclaration } from "./lexicon-module";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* One project-declared environment (chant #1166). Historically always a bare
|
|
@@ -131,6 +131,7 @@ const LexiconEntrySchema = z.union([
|
|
|
131
131
|
z.object({
|
|
132
132
|
name: z.string().min(1),
|
|
133
133
|
module: z.string().min(1),
|
|
134
|
+
root: z.string().min(1).optional(),
|
|
134
135
|
}).strict(),
|
|
135
136
|
]);
|
|
136
137
|
|
|
@@ -195,6 +196,7 @@ export const ChantConfigSchema = z.object({
|
|
|
195
196
|
}).optional(),
|
|
196
197
|
exclude: z.array(z.string().min(1)).optional(),
|
|
197
198
|
include: z.array(z.string().min(1)).optional(),
|
|
199
|
+
rootOnly: z.boolean().optional(),
|
|
198
200
|
}).passthrough();
|
|
199
201
|
|
|
200
202
|
/**
|
|
@@ -207,7 +209,8 @@ export interface ChantConfig {
|
|
|
207
209
|
* Lexicons to load. A bare name (`"aws"`) loads the package
|
|
208
210
|
* `@intentius/chant-lexicon-aws`. `{ name, module }` (#2520) loads a
|
|
209
211
|
* project-local lexicon from a module path, resolved against the directory
|
|
210
|
-
* holding this config; see `./lexicon-module.ts`.
|
|
212
|
+
* holding this config; see `./lexicon-module.ts`. An optional `root`
|
|
213
|
+
* (chant#2590) names the directory holding the lexicon's other files.
|
|
211
214
|
*
|
|
212
215
|
* Once loaded, this holds names only: the loader replaces each
|
|
213
216
|
* `{ name, module }` entry with its name and records the path in
|
|
@@ -453,19 +456,33 @@ export interface ChantConfig {
|
|
|
453
456
|
* import or lint a matching file. A pattern matches a file when it matches
|
|
454
457
|
* the file's path or the path of any directory above it, so `"ops"` and
|
|
455
458
|
* `"ops/**"` both skip everything under `ops/`. Matched with picomatch,
|
|
456
|
-
* dot files included.
|
|
459
|
+
* dot files included. Op discovery and component discovery honour it too
|
|
460
|
+
* (#2527). See `./discovery/walk.ts`.
|
|
457
461
|
*/
|
|
458
462
|
exclude?: string[];
|
|
459
463
|
|
|
460
464
|
/**
|
|
461
|
-
* Globs that re-admit files {@link exclude} would skip
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
* `
|
|
465
|
-
*
|
|
466
|
-
*
|
|
465
|
+
* Globs that re-admit files {@link exclude} would skip, and, since #2527,
|
|
466
|
+
* files the discovery walk skips by default: dot-directories, `dist`,
|
|
467
|
+
* git-ignored paths and child projects. `include` always wins over
|
|
468
|
+
* `exclude`, and it never narrows discovery on its own: a file nothing
|
|
469
|
+
* skips is discovered whether or not it matches an `include`. It never
|
|
470
|
+
* re-admits `node_modules`, `.git`, test files, workspace members, or
|
|
471
|
+
* files carrying the generated or skip marker.
|
|
467
472
|
*/
|
|
468
473
|
include?: string[];
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Lets `chant build` and `chant lint` run on this project when it is the
|
|
477
|
+
* root of a declared workspace (#2537, #2524 D0). Without it, or the
|
|
478
|
+
* `--root-only` flag, both commands refuse there with `WSP000` and point to
|
|
479
|
+
* `chant workspace build` and `lint`. With it they act on the root project
|
|
480
|
+
* alone. Member directories and example-group matches are left out of
|
|
481
|
+
* discovery either way, as they are for every walk under a declaration
|
|
482
|
+
* (#2527, `workspaceMemberDirs` in `./discovery/walk.ts`). It means nothing
|
|
483
|
+
* in a project with no `chant.workspace.json`.
|
|
484
|
+
*/
|
|
485
|
+
rootOnly?: boolean;
|
|
469
486
|
}
|
|
470
487
|
|
|
471
488
|
/**
|
|
@@ -561,7 +578,7 @@ export async function loadChantConfigUpward(startDir: string): Promise<ResolvedC
|
|
|
561
578
|
}
|
|
562
579
|
|
|
563
580
|
/** The walk {@link loadChantConfigUpward} makes, lint-only fragments skipped, without its warning. */
|
|
564
|
-
function findProjectConfigPastFragments(startDir: string): { dir: string; configPath?: string } {
|
|
581
|
+
export function findProjectConfigPastFragments(startDir: string): { dir: string; configPath?: string } {
|
|
565
582
|
let { dir, configPath } = findProjectConfig(startDir);
|
|
566
583
|
while (configPath && isLintOnlyFragment(configPath)) {
|
|
567
584
|
const parent = dirname(dir);
|
|
@@ -582,8 +599,9 @@ export interface DiscoveryGlobs {
|
|
|
582
599
|
/**
|
|
583
600
|
* The `exclude`/`include` globs of the project `startDir` belongs to, found by
|
|
584
601
|
* the same upward walk as {@link loadChantConfigUpward}. `undefined` when the
|
|
585
|
-
* project declares
|
|
586
|
-
*
|
|
602
|
+
* project declares neither. `include` re-admits what `exclude` skips, and
|
|
603
|
+
* also what the discovery walk skips by default (#2527), so it means
|
|
604
|
+
* something on its own.
|
|
587
605
|
*
|
|
588
606
|
* A config that fails to load is left to the command's own config load to
|
|
589
607
|
* report, so discovery keeps working where it did before, unless the failure
|
|
@@ -600,8 +618,10 @@ export async function resolveDiscoveryGlobs(startDir: string): Promise<Discovery
|
|
|
600
618
|
if (err instanceof InvalidChantConfigError && (err.key === "exclude" || err.key === "include")) throw err;
|
|
601
619
|
return undefined;
|
|
602
620
|
}
|
|
603
|
-
|
|
604
|
-
|
|
621
|
+
const exclude = config.exclude ?? [];
|
|
622
|
+
const include = config.include ?? [];
|
|
623
|
+
if (exclude.length === 0 && include.length === 0) return undefined;
|
|
624
|
+
return { root: dir, exclude, include };
|
|
605
625
|
}
|
|
606
626
|
|
|
607
627
|
/**
|
|
@@ -941,7 +961,20 @@ function normalizeConfig(raw: Record<string, unknown>, source?: string): LoadedC
|
|
|
941
961
|
// the loaders and the entry becomes its name, so every reader of `lexicons`
|
|
942
962
|
// keeps seeing names. A config of plain names is returned untouched.
|
|
943
963
|
const lexicons = (raw as ChantConfig).lexicons;
|
|
944
|
-
const
|
|
964
|
+
const baseDir = source !== undefined ? dirname(source) : process.cwd();
|
|
965
|
+
const sourceDir = (raw as ChantConfig).sourceDir;
|
|
966
|
+
// chant#2590 — a declared `root` must stay inside the project and must not
|
|
967
|
+
// contain its source directory. Refused here rather than narrowed, since
|
|
968
|
+
// the project asked for it by name.
|
|
969
|
+
for (const [i, entry] of (lexicons ?? []).entries()) {
|
|
970
|
+
if (typeof entry === "string" || entry.root === undefined) continue;
|
|
971
|
+
const { problem } = pathLexiconRoot(entry, baseDir, sourceDir);
|
|
972
|
+
if (problem !== undefined) {
|
|
973
|
+
const loc = source ? ` in ${source}` : "";
|
|
974
|
+
throw new InvalidChantConfigError(`Invalid chant config${loc}: lexicons.${i}.root: ${problem}`, "lexicons");
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
const recorded = registerLexiconDeclarations(lexicons, baseDir, sourceDir);
|
|
945
978
|
if (Object.keys(recorded).length > 0) {
|
|
946
979
|
return { ...(raw as ChantConfig), lexicons: lexiconNames(lexicons), lexiconModules: recorded };
|
|
947
980
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant #2514 — `contentDigest` is real SHA-256 over UTF-8, and the manifest
|
|
3
|
+
* and SBOM digests built on it separate inputs one byte apart.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, test } from "vitest";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { contentDigest } from "./content-digest";
|
|
8
|
+
import { canonicalJson } from "./effect-receipt";
|
|
9
|
+
import {
|
|
10
|
+
addArchiveEntry,
|
|
11
|
+
computeManifestDigest,
|
|
12
|
+
contentDigest as archiveContentDigest,
|
|
13
|
+
createBuildArchiveManifest,
|
|
14
|
+
} from "./components/verbs/build-archive";
|
|
15
|
+
import { createGenerateSbomCapability } from "./components/verbs/sbom";
|
|
16
|
+
import type { SbomDocument, SbomGenerator } from "./components/verbs/sbom-generator";
|
|
17
|
+
import { computePlanDigest } from "./lifecycle/plan-digest";
|
|
18
|
+
import { hashProps } from "./lifecycle/digest";
|
|
19
|
+
import { isLegacyContentDigest } from "./lifecycle/legacy-digest";
|
|
20
|
+
|
|
21
|
+
describe("contentDigest (#2514)", () => {
|
|
22
|
+
test("matches the FIPS 180-2 test vectors", () => {
|
|
23
|
+
expect(contentDigest("abc")).toBe("sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
24
|
+
expect(contentDigest("")).toBe("sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("hashes the UTF-8 bytes, not UTF-16 code units", () => {
|
|
28
|
+
const text = "héllo, 世界 🌍";
|
|
29
|
+
const expected = createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex");
|
|
30
|
+
expect(contentDigest(text)).toBe(`sha256:${expected}`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("the build archive exports the same function", () => {
|
|
34
|
+
expect(archiveContentDigest).toBe(contentDigest);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("its output is never mistaken for the old form", () => {
|
|
38
|
+
for (const input of ["", "x", "hello", "{}"]) {
|
|
39
|
+
expect(isLegacyContentDigest(contentDigest(input))).toBe(false);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("inputs one byte apart get different digests", () => {
|
|
44
|
+
expect(contentDigest("template-a")).not.toBe(contentDigest("template-b"));
|
|
45
|
+
expect(contentDigest("abc")).not.toBe(contentDigest("abc "));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("manifest digest (#2514)", () => {
|
|
50
|
+
const entry = (digest: string, path = "web.json") => ({ kind: "template" as const, path, digest });
|
|
51
|
+
|
|
52
|
+
test("is contentDigest over the canonical JSON of the sorted entries", () => {
|
|
53
|
+
const a = entry(contentDigest("{}"), "b.json");
|
|
54
|
+
const b = entry(contentDigest("[]"), "a.json");
|
|
55
|
+
const expected = contentDigest(
|
|
56
|
+
canonicalJson([
|
|
57
|
+
{ kind: b.kind, path: b.path, digest: b.digest },
|
|
58
|
+
{ kind: a.kind, path: a.path, digest: a.digest },
|
|
59
|
+
]),
|
|
60
|
+
);
|
|
61
|
+
expect(computeManifestDigest([a, b])).toBe(expected);
|
|
62
|
+
expect(computeManifestDigest([b, a])).toBe(expected);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("templates one byte apart give different entry and manifest digests", () => {
|
|
66
|
+
const base = createBuildArchiveManifest("web", { now: () => new Date(0) });
|
|
67
|
+
const one = addArchiveEntry(base, entry(contentDigest('{"Resources":{"A":1}}')));
|
|
68
|
+
const two = addArchiveEntry(base, entry(contentDigest('{"Resources":{"A":2}}')));
|
|
69
|
+
expect(one.contents[0]!.digest).not.toBe(two.contents[0]!.digest);
|
|
70
|
+
expect(one.manifestDigest).not.toBe(two.manifestDigest);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("a path cannot be read as a separator", () => {
|
|
74
|
+
// Under the old `kind:path:digest` join these two could be confused.
|
|
75
|
+
const x = computeManifestDigest([entry("sha256:1", "a:template:b")]);
|
|
76
|
+
const y = computeManifestDigest([entry("template:b:sha256:1", "a")]);
|
|
77
|
+
expect(x).not.toBe(y);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("SBOM digest (#2514)", () => {
|
|
82
|
+
function fixedGenerator(bytes: string): SbomGenerator {
|
|
83
|
+
const doc = async (): Promise<SbomDocument> => ({
|
|
84
|
+
format: "spdx",
|
|
85
|
+
mediaType: "application/spdx+json",
|
|
86
|
+
bytes,
|
|
87
|
+
generator: "test",
|
|
88
|
+
});
|
|
89
|
+
return { forImage: doc, forJar: doc, forZip: doc, forDir: doc };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
test("SBOMs one byte apart get different digests, each real SHA-256", async () => {
|
|
93
|
+
const ctx = { env: "dev", component: "web" };
|
|
94
|
+
const input = { artifactType: "image" as const, path: "archive/web.tar", digest: contentDigest("image") };
|
|
95
|
+
const a = await createGenerateSbomCapability(fixedGenerator('{"packages":[1]}')).run(ctx, input);
|
|
96
|
+
const b = await createGenerateSbomCapability(fixedGenerator('{"packages":[2]}')).run(ctx, input);
|
|
97
|
+
expect(a.digest).toBe(contentDigest('{"packages":[1]}'));
|
|
98
|
+
expect(a.digest).not.toBe(b.digest);
|
|
99
|
+
expect(a.manifest.manifestDigest).not.toBe(b.manifest.manifestDigest);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("other hashes use contentDigest over canonicalJson (#2514)", () => {
|
|
104
|
+
test("a plan digest", () => {
|
|
105
|
+
const subject = { b: 1, a: [true, null] };
|
|
106
|
+
expect(computePlanDigest("lifecycle-diff", subject)).toBe(
|
|
107
|
+
contentDigest(canonicalJson({ kind: "lifecycle-diff", subject })),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("a plan digest orders keys by code unit, not by locale", () => {
|
|
112
|
+
// localeCompare puts "a" before "B"; canonicalJson puts "B" first.
|
|
113
|
+
expect(computePlanDigest("k", { a: 1, B: 2 })).toBe(contentDigest('{"kind":"k","subject":{"B":2,"a":1}}'));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("a props hash, which drops what JSON cannot hold instead of throwing", () => {
|
|
117
|
+
const props = { Name: "x", fn: () => 1, nested: { z: 1, a: undefined } };
|
|
118
|
+
expect(hashProps(props)).toBe(contentDigest('{"Name":"x","nested":{"z":1}}').slice("sha256:".length));
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one content digest chant computes itself (chant #2514).
|
|
3
|
+
*
|
|
4
|
+
* `contentDigest(x)` is `"sha256:"` followed by the lowercase hex SHA-256 of
|
|
5
|
+
* the UTF-8 bytes of `x`. Anything core hashes goes through it, and anything
|
|
6
|
+
* structured is first put in canonical form with `canonicalJson`
|
|
7
|
+
* (./effect-receipt.ts), so the same value always hashes the same way. The
|
|
8
|
+
* build archive re-exports it from ./components/verbs/build-archive.ts, where
|
|
9
|
+
* it used to live.
|
|
10
|
+
*
|
|
11
|
+
* Before #2514 this function computed a 32-bit string hash and repeated its
|
|
12
|
+
* eight hex digits eight times. Values recorded then are recognised and
|
|
13
|
+
* flagged by ./lifecycle/legacy-digest.ts.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
|
|
18
|
+
/** `sha256:<64 hex>` over the UTF-8 bytes of `input`. */
|
|
19
|
+
export function contentDigest(input: string): string {
|
|
20
|
+
return `sha256:${createHash("sha256").update(input, "utf8").digest("hex")}`;
|
|
21
|
+
}
|
|
@@ -189,11 +189,11 @@ describe("findInfraFiles — project exclude/include globs (#2519)", () => {
|
|
|
189
189
|
".stage/copy.ts": "export const copy = {};",
|
|
190
190
|
};
|
|
191
191
|
|
|
192
|
-
test("with neither key configured,
|
|
192
|
+
test("with neither key configured, every file but the dot directory's is discovered", async () => {
|
|
193
193
|
await withTestDir(async (testDir) => {
|
|
194
194
|
await project(testDir, { lexicons: ["aws"] }, LAYOUT);
|
|
195
195
|
expect(rel(testDir, await findInfraFiles(testDir))).toEqual([
|
|
196
|
-
"
|
|
196
|
+
"ops/keep.ts", "ops/run.ts", "src/app.ts", "src/lib/util.ts",
|
|
197
197
|
]);
|
|
198
198
|
});
|
|
199
199
|
});
|
|
@@ -201,14 +201,14 @@ describe("findInfraFiles — project exclude/include globs (#2519)", () => {
|
|
|
201
201
|
test.each([["ops"], ["ops/**"], ["ops/*.ts"]])("exclude %s skips everything under ops/", async (pattern) => {
|
|
202
202
|
await withTestDir(async (testDir) => {
|
|
203
203
|
await project(testDir, { exclude: [pattern] }, LAYOUT);
|
|
204
|
-
expect(rel(testDir, await findInfraFiles(testDir))).toEqual(["
|
|
204
|
+
expect(rel(testDir, await findInfraFiles(testDir))).toEqual(["src/app.ts", "src/lib/util.ts"]);
|
|
205
205
|
});
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
test("
|
|
208
|
+
test("include re-admits a dot directory the walk skips by default (#2527)", async () => {
|
|
209
209
|
await withTestDir(async (testDir) => {
|
|
210
|
-
await project(testDir, {
|
|
211
|
-
expect(rel(testDir, await findInfraFiles(testDir))).
|
|
210
|
+
await project(testDir, { include: [".stage"] }, LAYOUT);
|
|
211
|
+
expect(rel(testDir, await findInfraFiles(testDir))).toContain(".stage/copy.ts");
|
|
212
212
|
});
|
|
213
213
|
});
|
|
214
214
|
|
|
@@ -216,7 +216,7 @@ describe("findInfraFiles — project exclude/include globs (#2519)", () => {
|
|
|
216
216
|
await withTestDir(async (testDir) => {
|
|
217
217
|
await project(testDir, { exclude: ["ops"], include: ["ops/keep.ts"] }, LAYOUT);
|
|
218
218
|
expect(rel(testDir, await findInfraFiles(testDir))).toEqual([
|
|
219
|
-
"
|
|
219
|
+
"ops/keep.ts", "src/app.ts", "src/lib/util.ts",
|
|
220
220
|
]);
|
|
221
221
|
});
|
|
222
222
|
});
|
|
@@ -231,7 +231,7 @@ describe("findInfraFiles — project exclude/include globs (#2519)", () => {
|
|
|
231
231
|
test("include alone changes nothing", async () => {
|
|
232
232
|
await withTestDir(async (testDir) => {
|
|
233
233
|
await project(testDir, { include: ["src"] }, LAYOUT);
|
|
234
|
-
expect(await findInfraFiles(testDir)).toHaveLength(
|
|
234
|
+
expect(await findInfraFiles(testDir)).toHaveLength(4);
|
|
235
235
|
});
|
|
236
236
|
});
|
|
237
237
|
|
|
@@ -252,9 +252,9 @@ describe("findInfraFiles — project exclude/include globs (#2519)", () => {
|
|
|
252
252
|
test("explicit globs override the config; null applies none", async () => {
|
|
253
253
|
await withTestDir(async (testDir) => {
|
|
254
254
|
await project(testDir, { exclude: ["ops"] }, LAYOUT);
|
|
255
|
-
expect(await findInfraFiles(testDir, { globs: null })).toHaveLength(
|
|
255
|
+
expect(await findInfraFiles(testDir, { globs: null })).toHaveLength(4);
|
|
256
256
|
const only = await findInfraFiles(testDir, { globs: { root: testDir, exclude: ["src"], include: [] } });
|
|
257
|
-
expect(rel(testDir, only)).toEqual(["
|
|
257
|
+
expect(rel(testDir, only)).toEqual(["ops/keep.ts", "ops/run.ts"]);
|
|
258
258
|
});
|
|
259
259
|
});
|
|
260
260
|
});
|