@intentius/chant 0.79.0 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +30 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts +5 -0
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +13 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +13 -0
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/command-group.d.ts +6 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +44 -3
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +8 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/import-agents.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/update.d.ts +6 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/vendor.d.ts +5 -0
- package/dist/cli/commands/vendor.d.ts.map +1 -1
- package/dist/cli/conflict-check.d.ts +6 -0
- package/dist/cli/conflict-check.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/init.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +20 -0
- package/dist/cli/handlers/promote.d.ts.map +1 -0
- package/dist/cli/handlers/run-generate.d.ts +39 -0
- package/dist/cli/handlers/run-generate.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts +4 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/vendor.d.ts +8 -3
- package/dist/cli/handlers/vendor.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +14 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +46 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +10 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +3 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/driver.d.ts +16 -2
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts +7 -1
- package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -1
- package/dist/components/presets/ecs-fargate.d.ts +10 -4
- package/dist/components/presets/ecs-fargate.d.ts.map +1 -1
- package/dist/components/promote.d.ts +264 -0
- package/dist/components/promote.d.ts.map +1 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/run.d.ts.map +1 -1
- package/dist/components/verbs/build-archive.d.ts +22 -11
- package/dist/components/verbs/build-archive.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/config-static.d.ts +59 -0
- package/dist/config-static.d.ts.map +1 -0
- package/dist/config.d.ts +104 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/content-digest.d.ts +17 -0
- package/dist/content-digest.d.ts.map +1 -0
- package/dist/discovery/files.d.ts +48 -4
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +32 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.d.ts +20 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/fork.d.ts +20 -0
- package/dist/discovery/sandbox/fork.d.ts.map +1 -1
- package/dist/discovery/walk.d.ts +119 -0
- package/dist/discovery/walk.d.ts.map +1 -0
- package/dist/graph-ir.d.ts +17 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/join-key.d.ts +31 -0
- package/dist/join-key.d.ts.map +1 -0
- package/dist/lexicon-module.d.ts +110 -0
- package/dist/lexicon-module.d.ts.map +1 -0
- package/dist/lexicon.d.ts +82 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
- package/dist/lifecycle/digest.d.ts +5 -1
- package/dist/lifecycle/digest.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +20 -5
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +64 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +8 -1
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/legacy-digest.d.ts +59 -0
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -0
- package/dist/lifecycle/member-ledger.d.ts +67 -0
- package/dist/lifecycle/member-ledger.d.ts.map +1 -0
- package/dist/lifecycle/plan-digest.d.ts +4 -3
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +42 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +90 -5
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +2 -3
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +32 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +75 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -1
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +7 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-summary.d.ts +16 -0
- package/dist/op/gate-summary.d.ts.map +1 -1
- package/dist/op/gate.d.ts +16 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/project-root.d.ts +26 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
- package/dist/workspace/checks/generated.d.ts +156 -0
- package/dist/workspace/checks/generated.d.ts.map +1 -0
- package/dist/workspace/checks/ledgers.d.ts +78 -0
- package/dist/workspace/checks/ledgers.d.ts.map +1 -0
- package/dist/workspace/checks/links.d.ts +25 -0
- package/dist/workspace/checks/links.d.ts.map +1 -0
- package/dist/workspace/checks/pipelines.d.ts +76 -0
- package/dist/workspace/checks/pipelines.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +161 -0
- package/dist/workspace/checks.d.ts.map +1 -0
- package/dist/workspace/compose-graph.d.ts +115 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -0
- package/dist/workspace/compose-reports.d.ts +68 -0
- package/dist/workspace/compose-reports.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +220 -0
- package/dist/workspace/declaration.d.ts.map +1 -0
- package/dist/workspace/declaration.schema.json +434 -0
- package/dist/workspace/generated-files.d.ts +65 -0
- package/dist/workspace/generated-files.d.ts.map +1 -0
- package/dist/workspace/glob.d.ts +13 -0
- package/dist/workspace/glob.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +75 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +96 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +38 -0
- package/dist/workspace/jsonc.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +192 -0
- package/dist/workspace/kinds.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +84 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -0
- package/dist/workspace/lineage-cli.d.ts +31 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +101 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +258 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -0
- package/dist/workspace/lineage-merge.d.ts +14 -0
- package/dist/workspace/lineage-merge.d.ts.map +1 -0
- package/dist/workspace/lineage-migrations.d.ts +178 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -0
- package/dist/workspace/lineage-update.d.ts +59 -0
- package/dist/workspace/lineage-update.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +146 -0
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
- package/dist/workspace/lineage-vendor.d.ts +63 -0
- package/dist/workspace/lineage-vendor.d.ts.map +1 -0
- package/dist/workspace/lineage-version.d.ts +32 -0
- package/dist/workspace/lineage-version.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +150 -0
- package/dist/workspace/links.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +99 -0
- package/dist/workspace/ls.d.ts.map +1 -0
- package/dist/workspace/member-commands.d.ts +139 -0
- package/dist/workspace/member-commands.d.ts.map +1 -0
- package/dist/workspace/member-handles.d.ts +54 -0
- package/dist/workspace/member-handles.d.ts.map +1 -0
- package/dist/workspace/member-pipeline.d.ts +143 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -0
- package/dist/workspace/member-run.d.ts +67 -0
- package/dist/workspace/member-run.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +65 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -0
- package/dist/workspace/record-source.d.ts +22 -0
- package/dist/workspace/record-source.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +81 -0
- package/dist/workspace/records-cli.d.ts.map +1 -0
- package/dist/workspace/records.d.ts +118 -0
- package/dist/workspace/records.d.ts.map +1 -0
- package/dist/workspace/root-refusal.d.ts +53 -0
- package/dist/workspace/root-refusal.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +150 -0
- package/dist/workspace/status.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +90 -0
- package/dist/workspace/template-manifest.d.ts.map +1 -0
- package/dist/workspace/tree.d.ts +36 -0
- package/dist/workspace/tree.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +67 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +111 -0
- package/dist/workspace/trust/policy.d.ts.map +1 -0
- package/dist/workspace/trust/provenance.d.ts +67 -0
- package/dist/workspace/trust/provenance.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +43 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
- package/dist/workspace/trust/test-repo.d.ts +42 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -0
- package/dist/workspace/trust/verify-cli.d.ts +9 -0
- package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
- package/dist/workspace/trust/verify.d.ts +66 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -0
- package/dist/workspace/which-chant.d.ts +58 -0
- package/dist/workspace/which-chant.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +133 -0
- package/package.json +4 -3
- package/src/audit/core.ts +13 -2
- package/src/audit/discover.ts +102 -36
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +81 -1
- package/src/audit/terraform-state.ts +25 -0
- package/src/cli/command-group.ts +7 -0
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +132 -29
- package/src/cli/commands/check-lexicon-examples.ts +2 -1
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +21 -0
- package/src/cli/commands/import-agents.ts +15 -4
- package/src/cli/commands/init.ts +76 -25
- package/src/cli/commands/lint.test.ts +52 -1
- package/src/cli/commands/lint.ts +41 -71
- package/src/cli/commands/onboard.ts +15 -0
- package/src/cli/commands/update.ts +18 -5
- package/src/cli/commands/vendor.ts +11 -6
- package/src/cli/conflict-check.test.ts +18 -1
- package/src/cli/conflict-check.ts +13 -0
- package/src/cli/discovery-skip.test.ts +168 -0
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
- package/src/cli/handlers/build-member.test.ts +127 -0
- package/src/cli/handlers/build.test.ts +11 -0
- package/src/cli/handlers/build.ts +35 -1
- package/src/cli/handlers/components.test.ts +127 -0
- package/src/cli/handlers/components.ts +70 -8
- package/src/cli/handlers/dev.ts +6 -1
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +4 -2
- package/src/cli/handlers/init.ts +54 -1
- package/src/cli/handlers/lifecycle.ts +2 -1
- package/src/cli/handlers/misc.ts +16 -0
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +39 -10
- package/src/cli/handlers/promote.test.ts +377 -0
- package/src/cli/handlers/promote.ts +391 -0
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +158 -0
- package/src/cli/handlers/run-generate.ts +168 -0
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +41 -6
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +137 -1
- package/src/cli/main.ts +290 -24
- package/src/cli/mcp/op-tools.ts +2 -1
- package/src/cli/path-lexicon-messages.test.ts +152 -0
- package/src/cli/plugins.ts +43 -5
- package/src/cli/registry.ts +46 -1
- package/src/cli/static-config-read.test.ts +208 -0
- package/src/codegen/fetch.test.ts +63 -1
- package/src/codegen/fetch.ts +30 -1
- package/src/components/__fixtures__/alb-ecs-service.json +0 -8
- package/src/components/capability-plugin-loader.ts +3 -1
- package/src/components/cli-support.ts +9 -3
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +20 -51
- package/src/components/driver.test.ts +90 -12
- package/src/components/driver.ts +43 -5
- package/src/components/fan-out-support.ts +2 -1
- package/src/components/gate-plan.ts +53 -0
- package/src/components/pilots/README.md +1 -1
- package/src/components/pilots/alb-ecs.pilot.ts +7 -7
- package/src/components/presets/ecs-fargate.ts +10 -5
- package/src/components/promote.test.ts +645 -0
- package/src/components/promote.ts +641 -0
- package/src/components/sandbox/driver.ts +8 -8
- package/src/components/sandbox/run.ts +4 -1
- package/src/components/verbs/build-archive.ts +27 -21
- package/src/components/verbs/run-agent.ts +3 -5
- package/src/config-static.test.ts +136 -0
- package/src/config-static.ts +232 -0
- package/src/config.test.ts +40 -0
- package/src/config.ts +161 -11
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +127 -1
- package/src/discovery/files.ts +91 -59
- package/src/discovery/fold-import.ts +127 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/sandbox/driver.ts +73 -35
- package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
- package/src/discovery/sandbox/fork.ts +83 -43
- package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
- package/src/discovery/sandbox/path-lexicon-parity.test.ts +162 -0
- package/src/discovery/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +18 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.test.ts +203 -0
- package/src/lexicon-module.ts +215 -0
- package/src/lexicon.ts +105 -1
- package/src/lifecycle/build-ledger-store.ts +13 -2
- package/src/lifecycle/digest.ts +9 -5
- package/src/lifecycle/gate-ledger.test.ts +20 -0
- package/src/lifecycle/gate-ledger.ts +28 -6
- package/src/lifecycle/git.test.ts +13 -1
- package/src/lifecycle/git.ts +221 -176
- package/src/lifecycle/lease.ts +25 -10
- package/src/lifecycle/legacy-digest.test.ts +206 -0
- package/src/lifecycle/legacy-digest.ts +103 -0
- package/src/lifecycle/member-ledger.test.ts +319 -0
- package/src/lifecycle/member-ledger.ts +145 -0
- package/src/lifecycle/plan-digest.ts +7 -7
- package/src/lifecycle/release-ledger.test.ts +20 -0
- package/src/lifecycle/release-ledger.ts +53 -3
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +2 -3
- package/src/op/activities/activity-contracts.ts +37 -0
- package/src/op/activities/index.ts +10 -0
- package/src/op/activities/propose-upgrade.ts +206 -0
- package/src/op/activity-contract-registry.ts +18 -4
- package/src/op/activity-registry.ts +26 -0
- package/src/op/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -23
- package/src/op/gate-approval.ts +3 -1
- package/src/op/gate-name.ts +8 -0
- package/src/op/gate-summary.test.ts +14 -0
- package/src/op/gate-summary.ts +25 -2
- package/src/op/gate.ts +36 -7
- package/src/op/generate-pipeline.test.ts +14 -5
- package/src/op/generate-pipeline.ts +2 -1
- package/src/op/runtimes/local.ts +2 -1
- package/src/project-root.test.ts +66 -1
- package/src/project-root.ts +48 -0
- package/src/workspace/__fixtures__/contract-repo.ts +100 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
- package/src/workspace/behold-kinds.test.ts +162 -0
- package/src/workspace/check-contract.test.ts +127 -0
- package/src/workspace/check.schema.json +347 -0
- package/src/workspace/checks/generated.test.ts +287 -0
- package/src/workspace/checks/generated.ts +475 -0
- package/src/workspace/checks/ledgers.test.ts +139 -0
- package/src/workspace/checks/ledgers.ts +246 -0
- package/src/workspace/checks/links.ts +183 -0
- package/src/workspace/checks/pipelines.test.ts +133 -0
- package/src/workspace/checks/pipelines.ts +225 -0
- package/src/workspace/checks-repo.test.ts +71 -0
- package/src/workspace/checks.test.ts +450 -0
- package/src/workspace/checks.ts +461 -0
- package/src/workspace/compose-graph.test.ts +146 -0
- package/src/workspace/compose-graph.ts +228 -0
- package/src/workspace/compose-reports.test.ts +109 -0
- package/src/workspace/compose-reports.ts +186 -0
- package/src/workspace/declaration.schema.json +434 -0
- package/src/workspace/declaration.test.ts +347 -0
- package/src/workspace/declaration.ts +623 -0
- package/src/workspace/generated-files.test.ts +114 -0
- package/src/workspace/generated-files.ts +94 -0
- package/src/workspace/glob.ts +49 -0
- package/src/workspace/graph-cli.ts +220 -0
- package/src/workspace/graph-contract.test.ts +204 -0
- package/src/workspace/graph.schema.json +662 -0
- package/src/workspace/init.test.ts +223 -0
- package/src/workspace/init.ts +585 -0
- package/src/workspace/jsonc.test.ts +65 -0
- package/src/workspace/jsonc.ts +231 -0
- package/src/workspace/kinds.test.ts +288 -0
- package/src/workspace/kinds.ts +506 -0
- package/src/workspace/lineage-check.ts +264 -0
- package/src/workspace/lineage-cli.ts +114 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +365 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +357 -0
- package/src/workspace/lineage-merge.ts +44 -0
- package/src/workspace/lineage-migrations.test.ts +178 -0
- package/src/workspace/lineage-migrations.ts +343 -0
- package/src/workspace/lineage-update.ts +160 -0
- package/src/workspace/lineage-upgrade-cli.ts +151 -0
- package/src/workspace/lineage-upgrade.test.ts +474 -0
- package/src/workspace/lineage-upgrade.ts +624 -0
- package/src/workspace/lineage-vendor.test.ts +126 -0
- package/src/workspace/lineage-vendor.ts +215 -0
- package/src/workspace/lineage-version.ts +127 -0
- package/src/workspace/links.test.ts +344 -0
- package/src/workspace/links.ts +331 -0
- package/src/workspace/ls-contract.test.ts +200 -0
- package/src/workspace/ls.schema.json +190 -0
- package/src/workspace/ls.ts +254 -0
- package/src/workspace/member-commands.e2e.test.ts +244 -0
- package/src/workspace/member-commands.test.ts +217 -0
- package/src/workspace/member-commands.ts +540 -0
- package/src/workspace/member-handles.test.ts +76 -0
- package/src/workspace/member-handles.ts +228 -0
- package/src/workspace/member-pipeline.test.ts +135 -0
- package/src/workspace/member-pipeline.ts +317 -0
- package/src/workspace/member-run.ts +146 -0
- package/src/workspace/read-contract.test.ts +129 -0
- package/src/workspace/reason-codes.test.ts +113 -0
- package/src/workspace/reason-codes.ts +81 -0
- package/src/workspace/record-source.ts +117 -0
- package/src/workspace/records-cli.ts +198 -0
- package/src/workspace/records-contract.test.ts +93 -0
- package/src/workspace/records.schema.json +150 -0
- package/src/workspace/records.test.ts +243 -0
- package/src/workspace/records.ts +384 -0
- package/src/workspace/root-refusal.ts +97 -0
- package/src/workspace/status-contract.test.ts +308 -0
- package/src/workspace/status.schema.json +210 -0
- package/src/workspace/status.ts +412 -0
- package/src/workspace/template-manifest.test.ts +79 -0
- package/src/workspace/template-manifest.ts +221 -0
- package/src/workspace/tree.ts +132 -0
- package/src/workspace/trust/attestor.ts +93 -0
- package/src/workspace/trust/policy.ts +306 -0
- package/src/workspace/trust/provenance.ts +181 -0
- package/src/workspace/trust/ssh-commit.ts +138 -0
- package/src/workspace/trust/test-repo.ts +138 -0
- package/src/workspace/trust/trust.test.ts +397 -0
- package/src/workspace/trust/verify-cli.ts +56 -0
- package/src/workspace/trust/verify.ts +194 -0
- package/src/workspace/which-chant.test.ts +117 -0
- package/src/workspace/which-chant.ts +151 -0
- package/src/workspace/workspace-kinds.schema.json +133 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant workspace build|lint|audit|graph [dir]` (#2537, #2524 D3, D8, D12,
|
|
3
|
+
* D16): run a level-0 command in every member, each under the member's own
|
|
4
|
+
* chant, and put the answers together.
|
|
5
|
+
*
|
|
6
|
+
* What runs:
|
|
7
|
+
*
|
|
8
|
+
* - Members of kind `chant`, the root member `.` included when it is one.
|
|
9
|
+
* Members of kind `other` are never read, and a nested `workspace` member
|
|
10
|
+
* runs its own workspace commands, so both are listed as skipped with the
|
|
11
|
+
* reason code `kind-not-run`.
|
|
12
|
+
* - For `build` and `lint` only, every project an example group matches
|
|
13
|
+
* (ws-051): groups are built and linted, and have no ledger, audit or place
|
|
14
|
+
* in the graph. On a large repository this is most of the run, so
|
|
15
|
+
* `--member <name>` narrows it to the named members and groups.
|
|
16
|
+
*
|
|
17
|
+
* Which chant runs a member (its toolchain): the `node_modules/.bin/chant`
|
|
18
|
+
* found by walking up from the member's directory to the workspace root,
|
|
19
|
+
* followed to its real path. A member with none uses the root's, and when
|
|
20
|
+
* the root has none either, the chant running this command. Members are
|
|
21
|
+
* grouped by that real path, and each group gets one process: the chant's
|
|
22
|
+
* `workspace member-run`, handed every member's command line on stdin (see
|
|
23
|
+
* `member-run.ts`). A chant too old to have `member-run` gets one level-0
|
|
24
|
+
* process per member instead, from inside the member's directory.
|
|
25
|
+
*
|
|
26
|
+
* Member `.` is the root project minus every other member, so its run leaves
|
|
27
|
+
* the member directories and group matches out of discovery, as
|
|
28
|
+
* `chant build --root-only` does.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { spawn } from "node:child_process";
|
|
32
|
+
import { existsSync, mkdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
33
|
+
import { dirname, join, resolve } from "node:path";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
import { formatError } from "../cli/format";
|
|
36
|
+
import type { CommandContext, ParsedArgs } from "../cli/registry";
|
|
37
|
+
import { findWorkspaceRoot } from "../project-root";
|
|
38
|
+
import type { ComposedMember, MemberReason } from "./compose-graph";
|
|
39
|
+
import { mergeAudit, mergeSarif, type MemberOutput } from "./compose-reports";
|
|
40
|
+
import { readDeclaration, resolveGroups, rootExclusions, WorkspaceReadError, type Declaration } from "./declaration";
|
|
41
|
+
import { builtinKindRegistry, type KindRegistry } from "./kinds";
|
|
42
|
+
import { memberReason } from "./ls";
|
|
43
|
+
import { MEMBER_RUN_PROTOCOL, PROTOCOL_PREFIX, type MemberRunLine, type MemberRunRequest } from "./member-run";
|
|
44
|
+
import { workingTree, type WorkspaceTree } from "./tree";
|
|
45
|
+
|
|
46
|
+
export type WorkspaceVerb = "build" | "lint" | "audit" | "graph";
|
|
47
|
+
|
|
48
|
+
/** One project a workspace command runs: a member, or one match of an example group. */
|
|
49
|
+
export interface RunUnit {
|
|
50
|
+
/** The member's name, or `<group>:<dir>` for a group match. */
|
|
51
|
+
id: string;
|
|
52
|
+
/** The member or group name. */
|
|
53
|
+
member: string;
|
|
54
|
+
kind: string;
|
|
55
|
+
group: boolean;
|
|
56
|
+
/** Relative to the workspace root, `"."` for the root member. */
|
|
57
|
+
dir: string;
|
|
58
|
+
abs: string;
|
|
59
|
+
/** Directories (relative to `dir`) left out of discovery; set for member `.`. */
|
|
60
|
+
exclude: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SkippedEntry {
|
|
64
|
+
name: string;
|
|
65
|
+
dir: string | null;
|
|
66
|
+
kind: string;
|
|
67
|
+
reason: MemberReason;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** How a toolchain was found. */
|
|
71
|
+
export type ToolchainSource = "member" | "root" | "reader";
|
|
72
|
+
|
|
73
|
+
export interface Toolchain {
|
|
74
|
+
/** The command that starts this chant: the executable, then any arguments that come before chant's own. */
|
|
75
|
+
command: string[];
|
|
76
|
+
/** The real path of its `bin/chant`: members with the same one share a process. */
|
|
77
|
+
identity: string;
|
|
78
|
+
source: ToolchainSource;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ToolchainGroup {
|
|
82
|
+
toolchain: Toolchain;
|
|
83
|
+
units: RunUnit[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface MemberPlan {
|
|
87
|
+
verb: WorkspaceVerb;
|
|
88
|
+
workspace: { name: string; root: string; file: string };
|
|
89
|
+
groups: ToolchainGroup[];
|
|
90
|
+
/** Chant members that can't be run, such as one whose directory is missing. They fail the run. */
|
|
91
|
+
unreadable: SkippedEntry[];
|
|
92
|
+
/** Members and groups the command does not run. */
|
|
93
|
+
skipped: SkippedEntry[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The `bin/chant` of the chant this module belongs to. */
|
|
97
|
+
export function readerBin(): string {
|
|
98
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "bin", "chant");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function real(path: string): string {
|
|
102
|
+
try {
|
|
103
|
+
return realpathSync(path);
|
|
104
|
+
} catch {
|
|
105
|
+
return path;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* How to start the chant running this command. When this process is chant's
|
|
111
|
+
* own CLI, it is started again the same way (node, its loader flags and
|
|
112
|
+
* `main.ts`), which works from any directory. `bin/chant` looks for tsx
|
|
113
|
+
* relative to an installed package, and from a source checkout it can only
|
|
114
|
+
* fall back to `npx tsx` in the current directory.
|
|
115
|
+
*/
|
|
116
|
+
export function readerToolchain(): Toolchain {
|
|
117
|
+
const main = process.argv[1] ?? "";
|
|
118
|
+
const command = /[\\/]cli[\\/]main\.(ts|js|mjs)$/.test(main) ? [process.execPath, ...process.execArgv, main] : [readerBin()];
|
|
119
|
+
return { command, identity: real(readerBin()), source: "reader" };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The chant a directory resolves: the first `node_modules/.bin/chant` from
|
|
124
|
+
* `dir` up to `root` (both absolute, `dir` inside `root`), else `reader`.
|
|
125
|
+
* A bin that is the reader's own chant is started the way the reader is.
|
|
126
|
+
*/
|
|
127
|
+
export function resolveToolchain(dir: string, root: string, reader: Toolchain = readerToolchain()): Toolchain {
|
|
128
|
+
for (let at = dir; ; at = dirname(at)) {
|
|
129
|
+
const bin = join(at, "node_modules", ".bin", "chant");
|
|
130
|
+
if (existsSync(bin)) {
|
|
131
|
+
const identity = real(bin);
|
|
132
|
+
const source: ToolchainSource = at === root ? "root" : "member";
|
|
133
|
+
return identity === reader.identity ? { ...reader, source } : { command: [bin], identity, source };
|
|
134
|
+
}
|
|
135
|
+
if (at === root || dirname(at) === at) break;
|
|
136
|
+
}
|
|
137
|
+
return reader;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface PlanOptions {
|
|
141
|
+
/** Only these member or group names. */
|
|
142
|
+
only?: string[];
|
|
143
|
+
kinds?: KindRegistry;
|
|
144
|
+
/** The chant to use when neither a member nor the root has one. Defaults to {@link readerToolchain}. */
|
|
145
|
+
reader?: Toolchain;
|
|
146
|
+
/** The files to plan from, such as a revision's (`--at`); the working tree under `root` by default. */
|
|
147
|
+
tree?: WorkspaceTree;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Work out which projects run and under which chant. Throws a {@link WorkspaceReadError} for an unreadable declaration or an unknown `--member` name. */
|
|
151
|
+
export function planMembers(verb: WorkspaceVerb, root: string, options: PlanOptions = {}): MemberPlan {
|
|
152
|
+
const tree = options.tree ?? workingTree(root);
|
|
153
|
+
const declaration: Declaration = readDeclaration(tree);
|
|
154
|
+
const resolvedGroups = resolveGroups(declaration, tree);
|
|
155
|
+
const kinds = options.kinds ?? builtinKindRegistry();
|
|
156
|
+
|
|
157
|
+
const only = options.only?.length ? new Set(options.only) : undefined;
|
|
158
|
+
if (only) {
|
|
159
|
+
const known = new Set(declaration.entries.map((e) => e.name));
|
|
160
|
+
const unknown = [...only].filter((n) => !known.has(n));
|
|
161
|
+
if (unknown.length > 0) {
|
|
162
|
+
throw new WorkspaceReadError(
|
|
163
|
+
"declaration-invalid",
|
|
164
|
+
`--member names ${unknown.join(", ")}, which the declaration does not declare; it declares ${[...known].join(", ")}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const units: RunUnit[] = [];
|
|
170
|
+
const skipped: SkippedEntry[] = [];
|
|
171
|
+
const unreadable: SkippedEntry[] = [];
|
|
172
|
+
const groupsRun = verb === "build" || verb === "lint";
|
|
173
|
+
|
|
174
|
+
for (const entry of declaration.entries) {
|
|
175
|
+
if (only && !only.has(entry.name)) continue;
|
|
176
|
+
if (entry.type === "group") {
|
|
177
|
+
const g = resolvedGroups.find((r) => r.group === entry)!;
|
|
178
|
+
if (!groupsRun) {
|
|
179
|
+
skipped.push({
|
|
180
|
+
name: entry.name,
|
|
181
|
+
dir: null,
|
|
182
|
+
kind: entry.kind,
|
|
183
|
+
reason: { code: "kind-not-run", message: `example groups are built and linted only; chant workspace ${verb} leaves them out` },
|
|
184
|
+
});
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
for (const dir of g.matches) {
|
|
188
|
+
units.push({ id: `${entry.name}:${dir}`, member: entry.name, kind: entry.kind, group: true, dir, abs: join(root, dir), exclude: [] });
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (entry.kind !== "chant") {
|
|
193
|
+
const why =
|
|
194
|
+
entry.kind === "workspace"
|
|
195
|
+
? "a nested workspace runs its own chant workspace commands"
|
|
196
|
+
: `chant workspace ${verb} runs members of kind chant, and this one is kind ${entry.kind}`;
|
|
197
|
+
skipped.push({ name: entry.name, dir: entry.dir, kind: entry.kind, reason: { code: "kind-not-run", message: why } });
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const reason = memberReason(entry, tree, kinds);
|
|
201
|
+
if (reason) {
|
|
202
|
+
unreadable.push({ name: entry.name, dir: entry.dir, kind: entry.kind, reason });
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
units.push({
|
|
206
|
+
id: entry.name,
|
|
207
|
+
member: entry.name,
|
|
208
|
+
kind: entry.kind,
|
|
209
|
+
group: false,
|
|
210
|
+
dir: entry.dir,
|
|
211
|
+
abs: entry.dir === "." ? root : join(root, entry.dir),
|
|
212
|
+
exclude: entry.dir === "." ? rootExclusions(declaration, resolvedGroups).map((e) => e.dir) : [],
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const reader = options.reader ?? readerToolchain();
|
|
217
|
+
const byIdentity = new Map<string, ToolchainGroup>();
|
|
218
|
+
for (const unit of units) {
|
|
219
|
+
// The walk reaches the root's chant before it gives up, so a member with
|
|
220
|
+
// none of its own runs under the root's, and under this chant only when
|
|
221
|
+
// the root has none either.
|
|
222
|
+
const tc = resolveToolchain(unit.abs, root, reader);
|
|
223
|
+
const group = byIdentity.get(tc.identity) ?? { toolchain: tc, units: [] };
|
|
224
|
+
group.units.push(unit);
|
|
225
|
+
byIdentity.set(tc.identity, group);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
verb,
|
|
230
|
+
workspace: { name: declaration.name, root, file: declaration.file },
|
|
231
|
+
groups: [...byIdentity.values()],
|
|
232
|
+
unreadable,
|
|
233
|
+
skipped,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── Command lines ────────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
/** What each member's run prints, for the verb and the workspace command's own flags. */
|
|
240
|
+
export function memberFormat(verb: WorkspaceVerb, args: Pick<ParsedArgs, "format" | "json">): string {
|
|
241
|
+
if (verb === "graph") return "ir";
|
|
242
|
+
// Audit always reads JSON, so member `.` can leave other members' findings to them.
|
|
243
|
+
if (verb === "audit") return "json";
|
|
244
|
+
if (verb === "lint") return args.format === "sarif" || args.format === "json" ? args.format : "stylish";
|
|
245
|
+
return args.format === "yaml" ? "yaml" : "json";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** The level-0 command line one member runs. */
|
|
249
|
+
export function memberArgv(verb: WorkspaceVerb, unit: RunUnit, args: ParsedArgs): string[] {
|
|
250
|
+
// A build keeps its own default format unless one was asked for.
|
|
251
|
+
const argv = verb === "build" && !args.format ? [verb, "."] : [verb, ".", "--format", memberFormat(verb, args)];
|
|
252
|
+
const params = () => {
|
|
253
|
+
for (const p of args.param ?? []) argv.push("--param", p);
|
|
254
|
+
if (args.paramsFile) argv.push("--params-file", resolve(args.paramsFile));
|
|
255
|
+
};
|
|
256
|
+
if (verb === "build") {
|
|
257
|
+
if (args.env) argv.push("--env", args.env);
|
|
258
|
+
params();
|
|
259
|
+
if (args.lexicon) argv.push("--lexicon", args.lexicon);
|
|
260
|
+
if (args.output) argv.push("--output", buildOutputPath(args.output, unit, memberFormat(verb, args)));
|
|
261
|
+
} else if (verb === "lint") {
|
|
262
|
+
params();
|
|
263
|
+
if (args.fix) argv.push("--fix");
|
|
264
|
+
} else if (verb === "audit") {
|
|
265
|
+
if (args.tier) argv.push("--tier", args.tier);
|
|
266
|
+
if (args.failOn) argv.push("--fail-on", args.failOn);
|
|
267
|
+
if (args.maxFiles !== undefined) argv.push("--max-files", String(args.maxFiles));
|
|
268
|
+
} else if (args.env) {
|
|
269
|
+
argv.push("--env", args.env);
|
|
270
|
+
}
|
|
271
|
+
return argv;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** `workspace build -o <dir>` writes `<dir>/<member>.<ext>`, and `<dir>/<group>/<match dir>.<ext>` for a group match. */
|
|
275
|
+
export function buildOutputPath(outDir: string, unit: RunUnit, format: string): string {
|
|
276
|
+
const ext = format === "yaml" ? "yaml" : "json";
|
|
277
|
+
return `${resolve(outDir, unit.group ? join(unit.member, unit.dir) : unit.member)}.${ext}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ── Running ──────────────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
export interface UnitResult extends MemberOutput {
|
|
283
|
+
unit: RunUnit;
|
|
284
|
+
toolchain: Toolchain;
|
|
285
|
+
/** The chant version the member-run header named; null when the per-member fallback ran. */
|
|
286
|
+
chant: string | null;
|
|
287
|
+
/** How the member ran: inside its toolchain's shared process, or in a level-0 process of its own. */
|
|
288
|
+
mode: "member-run" | "per-member";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
interface Spawned {
|
|
292
|
+
exitCode: number;
|
|
293
|
+
stdout: string;
|
|
294
|
+
stderr: string;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function run(command: string[], argv: string[], cwd: string, input?: string): Promise<Spawned> {
|
|
298
|
+
const [bin, ...pre] = command;
|
|
299
|
+
return new Promise((done) => {
|
|
300
|
+
const child = spawn(bin, [...pre, ...argv], { cwd, stdio: ["pipe", "pipe", "pipe"], env: process.env });
|
|
301
|
+
const out: Buffer[] = [];
|
|
302
|
+
const err: Buffer[] = [];
|
|
303
|
+
child.stdout.on("data", (b: Buffer) => out.push(b));
|
|
304
|
+
child.stderr.on("data", (b: Buffer) => err.push(b));
|
|
305
|
+
child.on("error", (e) => done({ exitCode: 127, stdout: "", stderr: `could not start ${bin}: ${e.message}\n` }));
|
|
306
|
+
child.on("close", (code) => done({ exitCode: code ?? 1, stdout: Buffer.concat(out).toString("utf8"), stderr: Buffer.concat(err).toString("utf8") }));
|
|
307
|
+
child.stdin.on("error", () => {
|
|
308
|
+
// A chant that never reads stdin closes it early; its answer says why.
|
|
309
|
+
});
|
|
310
|
+
child.stdin.end(input ?? "");
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Parse a member-run answer. `undefined` when there is no header: the chant has no member-run. */
|
|
315
|
+
export function parseMemberRunOutput(stdout: string): { chant: string; results: Map<string, Extract<MemberRunLine, { type: "result" }>>; stray: string } | undefined {
|
|
316
|
+
let header: Extract<MemberRunLine, { type: "header" }> | undefined;
|
|
317
|
+
const results = new Map<string, Extract<MemberRunLine, { type: "result" }>>();
|
|
318
|
+
const stray: string[] = [];
|
|
319
|
+
for (const line of stdout.split("\n")) {
|
|
320
|
+
if (!line.startsWith(PROTOCOL_PREFIX)) {
|
|
321
|
+
if (line.trim()) stray.push(line);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
let parsed: MemberRunLine;
|
|
325
|
+
try {
|
|
326
|
+
parsed = JSON.parse(line.slice(PROTOCOL_PREFIX.length)) as MemberRunLine;
|
|
327
|
+
} catch {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (parsed.type === "header" && !header) header = parsed;
|
|
331
|
+
else if (parsed.type === "result" && header) results.set(parsed.id, parsed);
|
|
332
|
+
}
|
|
333
|
+
return header ? { chant: header.chant, results, stray: stray.join("\n") } : undefined;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function runGroup(verb: WorkspaceVerb, group: ToolchainGroup, root: string, args: ParsedArgs): Promise<UnitResult[]> {
|
|
337
|
+
const { toolchain, units } = group;
|
|
338
|
+
const argvs = new Map(units.map((u) => [u.id, memberArgv(verb, u, args)]));
|
|
339
|
+
for (const u of units) {
|
|
340
|
+
const o = argvs.get(u.id)!;
|
|
341
|
+
const i = o.indexOf("--output");
|
|
342
|
+
if (i >= 0) mkdirSync(dirname(o[i + 1]), { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
const request: MemberRunRequest = {
|
|
345
|
+
protocol: MEMBER_RUN_PROTOCOL,
|
|
346
|
+
units: units.map((u) => ({ id: u.id, dir: u.abs, argv: argvs.get(u.id)!, ...(u.exclude.length ? { exclude: u.exclude } : {}) })),
|
|
347
|
+
};
|
|
348
|
+
const answer = await run(toolchain.command, ["workspace", "member-run"], root, JSON.stringify(request));
|
|
349
|
+
const parsed = parseMemberRunOutput(answer.stdout);
|
|
350
|
+
if (parsed) {
|
|
351
|
+
if (parsed.stray) process.stderr.write(`${parsed.stray}\n`);
|
|
352
|
+
return units.map((unit) => {
|
|
353
|
+
const r = parsed.results.get(unit.id);
|
|
354
|
+
const base = { unit, toolchain, chant: parsed.chant, mode: "member-run" as const, id: unit.id, member: unit.member, dir: unit.dir, exclude: unit.exclude };
|
|
355
|
+
if (r) return { ...base, exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
|
|
356
|
+
return { ...base, exitCode: answer.exitCode || 1, stdout: "", stderr: `the member-run process for ${toolchain.identity} ended before running this member\n${answer.stderr}` };
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// No header: a chant older than member-run. One level-0 process per member.
|
|
360
|
+
const out: UnitResult[] = [];
|
|
361
|
+
for (const unit of units) {
|
|
362
|
+
const r = await run(toolchain.command, argvs.get(unit.id)!, unit.abs);
|
|
363
|
+
out.push({ unit, toolchain, chant: null, mode: "per-member", id: unit.id, member: unit.member, dir: unit.dir, exclude: unit.exclude, ...r });
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Run every group of the plan, one process per toolchain at a time each, and return the results in plan order. */
|
|
369
|
+
export async function executePlan(plan: MemberPlan, args: ParsedArgs): Promise<UnitResult[]> {
|
|
370
|
+
const perGroup = await Promise.all(plan.groups.map((g) => runGroup(plan.verb, g, plan.workspace.root, args)));
|
|
371
|
+
const byId = new Map(perGroup.flat().map((r) => [r.id, r]));
|
|
372
|
+
const order = plan.groups.flatMap((g) => g.units);
|
|
373
|
+
return order.map((u) => byId.get(u.id)!);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ── The command ──────────────────────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
const USAGE: Record<WorkspaceVerb, string> = {
|
|
379
|
+
build: "chant workspace build [dir] [--member <name>] [-o <dir>] [--format json|yaml] [--env <env>] [--param k=v] [--dry-run]",
|
|
380
|
+
lint: "chant workspace lint [dir] [--member <name>] [--format stylish|json|sarif] [-o <file>] [--fix] [--dry-run]",
|
|
381
|
+
audit: "chant workspace audit [dir] [--member <name>] [--format stylish|json] [-o <file>] [--tier <tier>] [--fail-on <level>] [--dry-run]",
|
|
382
|
+
graph: "chant workspace graph [dir] [--member <name>] [-o <file>] [--env <env>] [--dry-run]",
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
export function describePlan(plan: MemberPlan): string {
|
|
386
|
+
const lines = [`chant workspace ${plan.verb} in ${plan.workspace.name} (${plan.workspace.root})`];
|
|
387
|
+
for (const g of plan.groups) {
|
|
388
|
+
lines.push("", `${g.toolchain.identity} (${g.toolchain.source}, ${g.units.length} project${g.units.length === 1 ? "" : "s"}, one process)`);
|
|
389
|
+
for (const u of g.units) lines.push(` ${u.id}${u.group ? "" : ` ${u.dir}`}`);
|
|
390
|
+
}
|
|
391
|
+
for (const s of plan.unreadable) lines.push("", `unreadable ${s.name}: ${s.reason.code}: ${s.reason.message}`);
|
|
392
|
+
if (plan.skipped.length) {
|
|
393
|
+
lines.push("", "skipped:");
|
|
394
|
+
for (const s of plan.skipped) lines.push(` ${s.name} ${s.reason.code}: ${s.reason.message}`);
|
|
395
|
+
}
|
|
396
|
+
return lines.join("\n");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function planJson(plan: MemberPlan): unknown {
|
|
400
|
+
return {
|
|
401
|
+
verb: plan.verb,
|
|
402
|
+
workspace: plan.workspace,
|
|
403
|
+
toolchains: plan.groups.map((g) => ({ ...g.toolchain, units: g.units.map((u) => ({ id: u.id, member: u.member, dir: u.dir, group: u.group })) })),
|
|
404
|
+
unreadable: plan.unreadable,
|
|
405
|
+
skipped: plan.skipped,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function emitDocument(doc: unknown, output: string | undefined): void {
|
|
410
|
+
const text = JSON.stringify(doc, null, 2);
|
|
411
|
+
if (output) {
|
|
412
|
+
mkdirSync(dirname(resolve(output)), { recursive: true });
|
|
413
|
+
writeFileSync(resolve(output), `${text}\n`);
|
|
414
|
+
} else {
|
|
415
|
+
console.log(text);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function summaryLine(plan: MemberPlan, results: UnitResult[]): string {
|
|
420
|
+
const failed = results.filter((r) => r.exitCode !== 0).length + plan.unreadable.length;
|
|
421
|
+
const n = results.length + plan.unreadable.length;
|
|
422
|
+
const processes = plan.groups.length;
|
|
423
|
+
return (
|
|
424
|
+
`chant workspace ${plan.verb}: ${n} project${n === 1 ? "" : "s"}, ${n - failed} passed, ${failed} failed; ` +
|
|
425
|
+
`${plan.skipped.length} skipped; ${processes} toolchain${processes === 1 ? "" : "s"}`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function printSections(plan: MemberPlan, results: UnitResult[], showStdout: boolean): void {
|
|
430
|
+
for (const r of results) {
|
|
431
|
+
const via = r.chant ? `chant ${r.chant}` : r.toolchain.identity;
|
|
432
|
+
console.log(`── ${r.id} ${r.dir} (${via}, exit ${r.exitCode})`);
|
|
433
|
+
if (showStdout && r.stdout.trim()) console.log(r.stdout.replace(/\n$/, ""));
|
|
434
|
+
if (r.stderr.trim()) process.stderr.write(r.stderr.endsWith("\n") ? r.stderr : `${r.stderr}\n`);
|
|
435
|
+
}
|
|
436
|
+
for (const s of plan.unreadable) console.log(`── ${s.name} ${s.dir} (${s.reason.code}: ${s.reason.message})`);
|
|
437
|
+
console.error(summaryLine(plan, results));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** The text form of `workspace audit`: each member's findings under a heading, one line each. */
|
|
441
|
+
function formatAuditText(doc: ReturnType<typeof mergeAudit>, plan: MemberPlan): string {
|
|
442
|
+
const lines: string[] = [];
|
|
443
|
+
for (const m of doc.members) {
|
|
444
|
+
const own = doc.findings.filter((f) => f.member === m.member);
|
|
445
|
+
lines.push(`── ${m.member} ${m.dir} (${m.status === "failed" ? `failed: ${m.error}` : m.note ?? `${own.length} finding${own.length === 1 ? "" : "s"}`})`);
|
|
446
|
+
for (const f of own) {
|
|
447
|
+
const at = `${String(f.file)}${f.line !== undefined ? `:${String(f.line)}` : ""}`;
|
|
448
|
+
lines.push(` ${at} ${String(f.severity)} ${String(f.checkId)} ${String(f.message)}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
for (const s of plan.unreadable) lines.push(`── ${s.name} ${s.dir} (${s.reason.code}: ${s.reason.message})`);
|
|
452
|
+
const errors = doc.findings.filter((f) => f.severity === "error").length;
|
|
453
|
+
lines.push("", `${doc.findings.length} finding${doc.findings.length === 1 ? "" : "s"}, ${errors} error${errors === 1 ? "" : "s"}`);
|
|
454
|
+
return lines.join("\n");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function memberStatus(name: string, dir: string, kind: string, status: ComposedMember["status"], reason: MemberReason | null, chant: string | null): ComposedMember {
|
|
458
|
+
return { name, dir, kind, status, reason, chant, irVersion: null };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export async function runWorkspaceMembers(ctx: CommandContext, verb: WorkspaceVerb): Promise<number> {
|
|
462
|
+
// graph is part of the read contract, with --at and its own document (#2536).
|
|
463
|
+
if (verb === "graph") return (await import("./graph-cli")).runWorkspaceGraph(ctx);
|
|
464
|
+
const { args } = ctx;
|
|
465
|
+
const start = resolve(args.extraPositional ?? ".");
|
|
466
|
+
const found = existsSync(start) ? findWorkspaceRoot(start) : undefined;
|
|
467
|
+
if (!found) {
|
|
468
|
+
console.error(formatError({ message: `declaration-missing: no chant.workspace.json or .jsonc between ${start} and the git root; chant workspace init proposes one`, hint: USAGE[verb] }));
|
|
469
|
+
return 1;
|
|
470
|
+
}
|
|
471
|
+
let plan: MemberPlan;
|
|
472
|
+
try {
|
|
473
|
+
plan = planMembers(verb, found.dir, { only: args.members });
|
|
474
|
+
} catch (err) {
|
|
475
|
+
if (!(err instanceof WorkspaceReadError)) throw err;
|
|
476
|
+
console.error(formatError({ message: `${err.code}: ${err.describe()}`, hint: USAGE[verb] }));
|
|
477
|
+
return 1;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (args.dryRun) {
|
|
481
|
+
if (args.json) console.log(JSON.stringify(planJson(plan), null, 2));
|
|
482
|
+
else console.log(describePlan(plan));
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const results = await executePlan(plan, args);
|
|
487
|
+
const anyFailed = results.some((r) => r.exitCode !== 0) || plan.unreadable.length > 0;
|
|
488
|
+
const workspace = { name: plan.workspace.name, root: plan.workspace.root };
|
|
489
|
+
|
|
490
|
+
if (verb === "lint" && args.format === "sarif") {
|
|
491
|
+
for (const r of results) if (r.stderr.trim()) process.stderr.write(r.stderr.endsWith("\n") ? r.stderr : `${r.stderr}\n`);
|
|
492
|
+
const unreadable: MemberOutput[] = plan.unreadable.map((s) => ({ id: s.name, member: s.name, dir: s.dir ?? ".", exitCode: 1, stdout: "", stderr: `${s.reason.code}: ${s.reason.message}` }));
|
|
493
|
+
emitDocument(mergeSarif([...results, ...unreadable], plan.workspace.root), args.output);
|
|
494
|
+
console.error(summaryLine(plan, results));
|
|
495
|
+
return anyFailed ? 1 : 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (verb === "audit" && !(args.json || args.format === "json")) {
|
|
499
|
+
for (const r of results) if (r.stderr.trim()) process.stderr.write(r.stderr.endsWith("\n") ? r.stderr : `${r.stderr}\n`);
|
|
500
|
+
const merged = mergeAudit(results, workspace);
|
|
501
|
+
console.log(formatAuditText(merged, plan));
|
|
502
|
+
console.error(summaryLine(plan, results));
|
|
503
|
+
return anyFailed ? 1 : 0;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if ((verb === "lint" && args.format === "json") || verb === "audit") {
|
|
507
|
+
for (const r of results) if (r.stderr.trim()) process.stderr.write(r.stderr.endsWith("\n") ? r.stderr : `${r.stderr}\n`);
|
|
508
|
+
let doc: unknown;
|
|
509
|
+
if (verb === "audit") {
|
|
510
|
+
const merged = mergeAudit(results, workspace);
|
|
511
|
+
for (const s of plan.unreadable) merged.members.push({ member: s.name, dir: s.dir ?? ".", exitCode: 1, status: "failed", summary: null, error: `${s.reason.code}: ${s.reason.message}` });
|
|
512
|
+
doc = { ...merged, skipped: plan.skipped };
|
|
513
|
+
} else {
|
|
514
|
+
doc = {
|
|
515
|
+
workspace,
|
|
516
|
+
members: [
|
|
517
|
+
...results.map((r) => {
|
|
518
|
+
let diagnostics: unknown = null;
|
|
519
|
+
try {
|
|
520
|
+
diagnostics = JSON.parse(r.stdout);
|
|
521
|
+
} catch {
|
|
522
|
+
// A member whose lint printed no JSON keeps null, with its exit code saying why.
|
|
523
|
+
}
|
|
524
|
+
return { member: r.id, dir: r.dir, exitCode: r.exitCode, diagnostics };
|
|
525
|
+
}),
|
|
526
|
+
...plan.unreadable.map((s) => ({ member: s.name, dir: s.dir, exitCode: 1, diagnostics: null, reason: s.reason })),
|
|
527
|
+
],
|
|
528
|
+
skipped: plan.skipped,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
emitDocument(doc, args.output);
|
|
532
|
+
console.error(summaryLine(plan, results));
|
|
533
|
+
return anyFailed ? 1 : 0;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Text: each member's own output under a heading. A build without -o prints
|
|
537
|
+
// no templates, since several members' templates can't share one stdout.
|
|
538
|
+
printSections(plan, results, verb !== "build" || args.output !== undefined);
|
|
539
|
+
return anyFailed ? 1 : 0;
|
|
540
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
5
|
+
import { readSourceHandles } from "./member-handles";
|
|
6
|
+
import { workingTree } from "./tree";
|
|
7
|
+
|
|
8
|
+
const scratch: string[] = [];
|
|
9
|
+
afterAll(() => {
|
|
10
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
11
|
+
});
|
|
12
|
+
function tree(files: Record<string, string>) {
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), "chant-handles-"));
|
|
14
|
+
scratch.push(root);
|
|
15
|
+
for (const [path, text] of Object.entries(files)) {
|
|
16
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
17
|
+
writeFileSync(join(root, path), text);
|
|
18
|
+
}
|
|
19
|
+
return workingTree(root);
|
|
20
|
+
}
|
|
21
|
+
const names = (hs: { name: string }[]) => hs.map((h) => h.name);
|
|
22
|
+
|
|
23
|
+
describe("readSourceHandles (#2539)", () => {
|
|
24
|
+
test("reads output names, stackOutput bindings and Parameter bindings, without running anything", () => {
|
|
25
|
+
const t = tree({
|
|
26
|
+
"src/outputs.ts": [
|
|
27
|
+
`import { output as out, stackOutput } from "@intentius/chant-lexicon-aws";`,
|
|
28
|
+
`const NAME = "ListenerArn";`,
|
|
29
|
+
`export const a = out(x.Arn, "ClusterArn");`,
|
|
30
|
+
`export const b = out(x.Arn, NAME);`,
|
|
31
|
+
`export const c = out(x.Arn, \`Plain\`);`,
|
|
32
|
+
`export const zoneServers = stackOutput(zone.NameServers);`,
|
|
33
|
+
`throw new Error("never run");`,
|
|
34
|
+
].join("\n"),
|
|
35
|
+
"src/params.ts": [
|
|
36
|
+
`import * as aws from "@intentius/chant-lexicon-aws";`,
|
|
37
|
+
`export const clusterArn = new aws.Parameter("String");`,
|
|
38
|
+
`const internal = new aws.Parameter("String");`,
|
|
39
|
+
].join("\n"),
|
|
40
|
+
});
|
|
41
|
+
const read = readSourceHandles(t, "");
|
|
42
|
+
expect(names(read.outputs).sort()).toEqual(["ClusterArn", "ListenerArn", "Plain", "zoneServers"]);
|
|
43
|
+
expect(names(read.inputs)).toEqual(["clusterArn"]);
|
|
44
|
+
expect(read.gaps).toEqual([]);
|
|
45
|
+
expect(read.outputs.find((o) => o.name === "ClusterArn")).toEqual({ name: "ClusterArn", file: "src/outputs.ts", line: 3 });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("only chant's functions count", () => {
|
|
49
|
+
const t = tree({
|
|
50
|
+
"a.ts": `import { output } from "./local";\nexport const a = output(1, "NotChant");\n`,
|
|
51
|
+
"b.ts": `import { stackOutput } from "@intentius/chant/components";\nexport const b = stackOutput("shared", "ClusterArn");\n`,
|
|
52
|
+
"c.ts": `function output(a: unknown, b: string) { return b; }\nexport const c = output(1, "Local");\n`,
|
|
53
|
+
});
|
|
54
|
+
expect(readSourceHandles(t, "")).toEqual({ outputs: [], inputs: [], gaps: [] });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("a name that isn't a literal is a gap", () => {
|
|
58
|
+
const t = tree({ "o.ts": `import { output } from "@intentius/chant";\nexport const o = output(1, prefix + "Arn");\n` });
|
|
59
|
+
expect(readSourceHandles(t, "").gaps).toEqual(["o.ts:2: an output whose name is not a string literal"]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("tests, declarations, build output, node_modules and excluded directories are not source", () => {
|
|
63
|
+
const decl = `import { output } from "@intentius/chant";\nexport const o = output(1, "X");\n`;
|
|
64
|
+
const t = tree({
|
|
65
|
+
"src/o.test.ts": decl,
|
|
66
|
+
"src/o.d.ts": decl,
|
|
67
|
+
"dist/o.ts": decl,
|
|
68
|
+
"node_modules/p/o.ts": decl,
|
|
69
|
+
".cache/o.ts": decl,
|
|
70
|
+
"chant.config.ts": decl,
|
|
71
|
+
"other/o.ts": decl,
|
|
72
|
+
"src/kept.ts": decl,
|
|
73
|
+
});
|
|
74
|
+
expect(readSourceHandles(t, "", new Set(["other"])).outputs.map((o) => o.file)).toEqual(["src/kept.ts"]);
|
|
75
|
+
});
|
|
76
|
+
});
|