@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
|
@@ -3,6 +3,7 @@ import { realpathSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { bundleDriver } from "../../discovery/sandbox/bundle";
|
|
5
5
|
import { classifyChildError } from "../../discovery/sandbox/child-errors";
|
|
6
|
+
import { settleOnExitAfterChannelDrains } from "../../discovery/sandbox/fork";
|
|
6
7
|
import { DiscoveryError, type DiscoveryErrorType } from "../../errors";
|
|
7
8
|
import type { Component } from "../component";
|
|
8
9
|
import type { DiscoveredComponent } from "../discover";
|
|
@@ -163,7 +164,9 @@ function runChildProcess(
|
|
|
163
164
|
reject(err);
|
|
164
165
|
});
|
|
165
166
|
|
|
166
|
-
|
|
167
|
+
// chant#2461 — a payload can still be in the IPC socket when the exit is
|
|
168
|
+
// handled; see settleOnExitAfterChannelDrains.
|
|
169
|
+
settleOnExitAfterChannelDrains(child, (code, signal) => {
|
|
167
170
|
if (settled) return;
|
|
168
171
|
settled = true;
|
|
169
172
|
clearTimeout(timeout);
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
|
|
56
56
|
import type { ArtifactReproducibility, ProvenanceLink } from "./reproducibility";
|
|
57
57
|
import { defaultReproducibility } from "./reproducibility";
|
|
58
|
+
import { contentDigest } from "../../content-digest";
|
|
59
|
+
import { canonicalJson } from "../../effect-receipt";
|
|
58
60
|
|
|
59
61
|
export type BuildArchiveEntryKind = "image" | "template" | "asset" | "sbom";
|
|
60
62
|
|
|
@@ -129,6 +131,12 @@ export interface BuildArchiveManifest {
|
|
|
129
131
|
* changed contents, not merely a re-run.
|
|
130
132
|
*/
|
|
131
133
|
manifestDigest: string;
|
|
134
|
+
/**
|
|
135
|
+
* Set on read, never written: `["legacy-digest"]` when a manifest
|
|
136
|
+
* persisted on `chant/lifecycle` holds digests from the 32-bit hash chant
|
|
137
|
+
* used before real SHA-256 (#2514). See ../../lifecycle/legacy-digest.ts.
|
|
138
|
+
*/
|
|
139
|
+
flags?: Array<"legacy-digest">;
|
|
132
140
|
}
|
|
133
141
|
|
|
134
142
|
/**
|
|
@@ -147,30 +155,28 @@ const DEFAULT_MEDIA_TYPES: Record<BuildArchiveEntryKind, string> = {
|
|
|
147
155
|
};
|
|
148
156
|
|
|
149
157
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* digest their producing capability already computed (e.g. `docker-build`'s
|
|
158
|
-
* `executor.docker.build` digest).
|
|
158
|
+
* The content digest every entry chant computes itself carries: real
|
|
159
|
+
* SHA-256 over the UTF-8 bytes of its input (../../content-digest.ts,
|
|
160
|
+
* chant #2514). Callers that digest content that never goes through the
|
|
161
|
+
* `CloudExecutor` (a synthesized template's serialized bytes, see ./build.ts's
|
|
162
|
+
* `addArchiveTemplate`, or an SBOM) use it. Production `image`/`asset`
|
|
163
|
+
* entries instead carry the digest their producing capability already
|
|
164
|
+
* computed (e.g. `docker-build`'s `executor.docker.build` digest).
|
|
159
165
|
*/
|
|
160
|
-
export
|
|
161
|
-
let hash = 0;
|
|
162
|
-
for (let i = 0; i < input.length; i++) {
|
|
163
|
-
hash = (Math.imul(hash, 31) + input.charCodeAt(i)) | 0;
|
|
164
|
-
}
|
|
165
|
-
const hex = Math.abs(hash).toString(16).padStart(8, "0");
|
|
166
|
-
return `sha256:${hex.repeat(8).slice(0, 64)}`;
|
|
167
|
-
}
|
|
166
|
+
export { contentDigest };
|
|
168
167
|
|
|
169
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* Compute the aggregate `manifestDigest` over a set of entries. Each entry
|
|
170
|
+
* contributes its `kind`, `path` and `digest`; entries are sorted by path
|
|
171
|
+
* (UTF-16 code units, the order `canonicalJson` sorts keys in) so entry order
|
|
172
|
+
* never changes the digest, only content does. The list is hashed as
|
|
173
|
+
* `canonicalJson`, so no path or digest can be read as a field separator.
|
|
174
|
+
*/
|
|
170
175
|
export function computeManifestDigest(contents: BuildArchiveEntry[]): string {
|
|
171
|
-
const sorted =
|
|
172
|
-
|
|
173
|
-
|
|
176
|
+
const sorted = contents
|
|
177
|
+
.map((e) => ({ kind: e.kind, path: e.path, digest: e.digest }))
|
|
178
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
179
|
+
return contentDigest(canonicalJson(sorted));
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
/** Start a new, empty manifest for `component`. Entries are added with `addArchiveEntry`. */
|
|
@@ -237,7 +237,7 @@
|
|
|
237
237
|
* `externalParameters`, never replacing it), not a shape break.
|
|
238
238
|
*/
|
|
239
239
|
|
|
240
|
-
import {
|
|
240
|
+
import { contentDigest } from "../../content-digest";
|
|
241
241
|
import type { Capability, DeployContext } from "../capability";
|
|
242
242
|
import type { ProvenanceLink } from "./reproducibility";
|
|
243
243
|
import type { BuildArchiveEntry } from "./build-archive";
|
|
@@ -555,10 +555,8 @@ export function createRunAgentCapability(
|
|
|
555
555
|
const PROMPT_PATH = "/work/prompt";
|
|
556
556
|
const OUTPUT_PATH = "/work/output";
|
|
557
557
|
|
|
558
|
-
/** `sha256:<hex>` over a string
|
|
559
|
-
|
|
560
|
-
return `sha256:${createHash("sha256").update(content, "utf8").digest("hex")}`;
|
|
561
|
-
}
|
|
558
|
+
/** `sha256:<hex>` over a string: ../../content-digest.ts's `contentDigest`, the same digest ./build-archive.ts uses. */
|
|
559
|
+
const sha256Digest = contentDigest;
|
|
562
560
|
|
|
563
561
|
const EMPTY_DIGEST = sha256Digest("");
|
|
564
562
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant#2591 — the static reader of a config's `lexicons`: what it reads, and
|
|
3
|
+
* what it reports as unreadable instead of running the config.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { readConfigFieldsStatically, readLexiconDeclarationsStatically, unknownPathLexiconsNotice } from "./config-static";
|
|
10
|
+
|
|
11
|
+
let dir: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
dir = realpathSync(mkdtempSync(join(tmpdir(), "chant-2591-static-")));
|
|
15
|
+
// A project boundary, so the upward walk stops here.
|
|
16
|
+
writeFileSync(join(dir, "package.json"), "{}");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
rmSync(dir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function readTs(source: string) {
|
|
24
|
+
writeFileSync(join(dir, "chant.config.ts"), source);
|
|
25
|
+
return readLexiconDeclarationsStatically(dir);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const SITE = { name: "site", module: "./lexicon/index.ts" };
|
|
29
|
+
|
|
30
|
+
describe("readable configs", () => {
|
|
31
|
+
test.each([
|
|
32
|
+
["an object literal default export", `export default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };`],
|
|
33
|
+
[
|
|
34
|
+
"satisfies and a type import",
|
|
35
|
+
`import type { ChantConfig } from "@intentius/chant";\nexport default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] } satisfies ChantConfig;`,
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
"a const alias",
|
|
39
|
+
`const config = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }], sourceDir: "src" };\nexport default config;`,
|
|
40
|
+
],
|
|
41
|
+
["a named config export", `export const config = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };`],
|
|
42
|
+
["named exports", `export const lexicons = ["aws", { name: "site", module: "./lexicon/index.ts" }];\nexport const sourceDir = "src";`],
|
|
43
|
+
[
|
|
44
|
+
"consts, a spread and a template",
|
|
45
|
+
`const dir = "lexicon";\nconst base = ["aws"];\nexport default { lexicons: [...base, { name: "site", module: \`./\${dir}/index.ts\` }] };`,
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
"a spread config",
|
|
49
|
+
`const shared = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };\nexport default { ...shared, sourceDir: "src" };`,
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
"other keys that are not readable",
|
|
53
|
+
`import { plugin } from "./rules";\nexport default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }], lint: { plugins: [plugin()] } };`,
|
|
54
|
+
],
|
|
55
|
+
])("%s", (_label, source) => {
|
|
56
|
+
const read = readTs(source);
|
|
57
|
+
expect(read).toEqual({ status: "read", configPath: join(dir, "chant.config.ts"), entries: ["aws", SITE] });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("a same-file function the fold subset covers is interpreted, not run", () => {
|
|
61
|
+
const read = readTs(`const pick = (extra) => ["aws", extra];\nexport default { lexicons: pick("k8s") };`);
|
|
62
|
+
expect(read).toMatchObject({ status: "read", entries: ["aws", "k8s"] });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("a config with no lexicons reads as none", () => {
|
|
66
|
+
expect(readTs(`export default { sourceDir: "src" };`)).toMatchObject({ status: "read", entries: [] });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("chant.config.json is parsed", () => {
|
|
70
|
+
writeFileSync(join(dir, "chant.config.json"), JSON.stringify({ lexicons: ["aws", SITE] }));
|
|
71
|
+
expect(readLexiconDeclarationsStatically(dir)).toMatchObject({ status: "read", entries: ["aws", SITE] });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("the walk goes up from a subdirectory", () => {
|
|
75
|
+
writeFileSync(join(dir, "chant.config.ts"), `export default { lexicons: [{ name: "site", module: "./lexicon/index.ts" }] };`);
|
|
76
|
+
mkdirSync(join(dir, "src", "deep"), { recursive: true });
|
|
77
|
+
expect(readLexiconDeclarationsStatically(join(dir, "src", "deep"))).toMatchObject({ status: "read", entries: [SITE] });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("no config", () => {
|
|
81
|
+
expect(readLexiconDeclarationsStatically(dir)).toEqual({ status: "no-config" });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("unreadable configs are reported, never run", () => {
|
|
86
|
+
test.each([
|
|
87
|
+
["an imported binding", `import { lexicons } from "./shared";\nexport default { lexicons };`, /lexicons/],
|
|
88
|
+
["process.env", `export default { lexicons: process.env.CI ? ["aws"] : ["aws", "k8s"] };`, /process/],
|
|
89
|
+
["a call as the default export", `export default defineConfig({ lexicons: ["aws"] });`, /not an object literal/],
|
|
90
|
+
["a re-export", `export { default } from "./base";`, /export/],
|
|
91
|
+
["an imported function in lexicons", `import { pick } from "./pick";\nexport default { lexicons: pick() };`, /line 4: .*pick/],
|
|
92
|
+
["entries of the wrong shape", `export default { lexicons: [{ name: "site" }] };`, /not a list of lexicon entries/],
|
|
93
|
+
])("%s", (_label, source, reason) => {
|
|
94
|
+
const marker = join(dir, "ran");
|
|
95
|
+
const read = readTs(`import { writeFileSync } from "node:fs";\nwriteFileSync(${JSON.stringify(marker)}, "x");\n${source}`);
|
|
96
|
+
expect(read.status).toBe("unknown");
|
|
97
|
+
expect(read.status === "unknown" && read.reason).toMatch(reason);
|
|
98
|
+
expect(unknownPathLexiconsNotice(read)).toContain("is unknown here and is treated as a package");
|
|
99
|
+
expect(existsSync(marker)).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("a readable config has no notice", () => {
|
|
103
|
+
expect(unknownPathLexiconsNotice(readTs(`export default { lexicons: ["aws"] };`))).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("readConfigFieldsStatically (#2641)", () => {
|
|
108
|
+
test("reads the asked-for fields of a default export, a named config or named exports, and leaves unset ones out", () => {
|
|
109
|
+
writeFileSync(join(dir, "chant.config.ts"), `const env = "prod";\nexport default { environments: [env, { name: "staging" }], ownership: { stack: "shop", env }, lexicons: ["aws"] };`);
|
|
110
|
+
expect(readConfigFieldsStatically(dir, ["ownership", "environments", "sourceDir"])).toEqual({
|
|
111
|
+
status: "read",
|
|
112
|
+
configPath: join(dir, "chant.config.ts"),
|
|
113
|
+
fields: { ownership: { stack: "shop", env: "prod" }, environments: ["prod", { name: "staging" }] },
|
|
114
|
+
});
|
|
115
|
+
writeFileSync(join(dir, "chant.config.ts"), `export const ownership = { stack: "named" };`);
|
|
116
|
+
expect(readConfigFieldsStatically(dir, ["ownership"])).toMatchObject({ status: "read", fields: { ownership: { stack: "named" } } });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("a value it can't fold is unknown, with the reason, and the config is not run", () => {
|
|
120
|
+
const marker = join(dir, "ran");
|
|
121
|
+
writeFileSync(
|
|
122
|
+
join(dir, "chant.config.ts"),
|
|
123
|
+
`import { writeFileSync } from "node:fs";\nwriteFileSync(${JSON.stringify(marker)}, "x");\nexport default { ownership: { stack: process.env.STACK } };`,
|
|
124
|
+
);
|
|
125
|
+
const read = readConfigFieldsStatically(dir, ["ownership"]);
|
|
126
|
+
expect(read.status).toBe("unknown");
|
|
127
|
+
expect(existsSync(marker)).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("reads chant.config.json, and only in the directory itself", () => {
|
|
131
|
+
writeFileSync(join(dir, "chant.config.json"), JSON.stringify({ environments: ["prod"] }));
|
|
132
|
+
expect(readConfigFieldsStatically(dir, ["environments"])).toMatchObject({ status: "read", fields: { environments: ["prod"] } });
|
|
133
|
+
mkdirSync(join(dir, "sub"));
|
|
134
|
+
expect(readConfigFieldsStatically(join(dir, "sub"), ["environments"])).toEqual({ status: "no-config" });
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
environmentEndpoint,
|
|
13
13
|
matchesEnvironmentPattern,
|
|
14
14
|
matchesDeclaredEnvironment,
|
|
15
|
+
resolveDiscoveryGlobs,
|
|
15
16
|
} from "./config";
|
|
16
17
|
import { writeFileSync, mkdirSync, rmSync } from "fs";
|
|
17
18
|
import { join } from "path";
|
|
@@ -448,3 +449,42 @@ describe("resolveKnowledgeDir (#1864, design #1059)", () => {
|
|
|
448
449
|
expect(resolveKnowledgeDir({ knowledge: {} }, "/proj")).toBe(join("/proj", "knowledge"));
|
|
449
450
|
});
|
|
450
451
|
});
|
|
452
|
+
|
|
453
|
+
describe("exclude / include discovery globs (#2519)", () => {
|
|
454
|
+
test("loads string arrays", async () => {
|
|
455
|
+
writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ exclude: ["ops/**"], include: ["ops/keep.ts"] }));
|
|
456
|
+
const { config } = await loadChantConfig(TEST_DIR);
|
|
457
|
+
expect(config.exclude).toEqual(["ops/**"]);
|
|
458
|
+
expect(config.include).toEqual(["ops/keep.ts"]);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
test.each([
|
|
462
|
+
["a non-string entry", { exclude: ["ops/**", 42] }, /exclude\.1: .*expected string/],
|
|
463
|
+
["a bare string", { exclude: "ops/**" }, /exclude: .*expected array/],
|
|
464
|
+
["an empty pattern", { include: [""] }, /include\.0: /],
|
|
465
|
+
])("rejects %s loudly", async (_label, raw, message) => {
|
|
466
|
+
writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify(raw));
|
|
467
|
+
await expect(loadChantConfig(TEST_DIR)).rejects.toThrow(message);
|
|
468
|
+
await expect(resolveDiscoveryGlobs(TEST_DIR)).rejects.toThrow(message);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("resolveDiscoveryGlobs returns include alone, since it re-admits the walk's default skips (#2527)", async () => {
|
|
472
|
+
expect(await resolveDiscoveryGlobs(TEST_DIR)).toBeUndefined();
|
|
473
|
+
writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ include: ["ops/**"] }));
|
|
474
|
+
expect(await resolveDiscoveryGlobs(TEST_DIR)).toEqual({ root: TEST_DIR, exclude: [], include: ["ops/**"] });
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("resolveDiscoveryGlobs anchors the globs at the project config's directory, past a lint fragment", async () => {
|
|
478
|
+
const src = join(TEST_DIR, "src");
|
|
479
|
+
mkdirSync(src, { recursive: true });
|
|
480
|
+
writeFileSync(join(src, "chant.config.json"), JSON.stringify({ rules: { COR001: "off" } }));
|
|
481
|
+
writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ exclude: ["ops"] }));
|
|
482
|
+
expect(await resolveDiscoveryGlobs(src)).toEqual({ root: TEST_DIR, exclude: ["ops"], include: [] });
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test("resolveDiscoveryGlobs leaves an unrelated config error to the command's own load", async () => {
|
|
486
|
+
writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ lexicons: [7], exclude: ["ops"] }));
|
|
487
|
+
await expect(loadChantConfig(TEST_DIR)).rejects.toThrow(/lexicons\.0/);
|
|
488
|
+
expect(await resolveDiscoveryGlobs(TEST_DIR)).toBeUndefined();
|
|
489
|
+
});
|
|
490
|
+
});
|