@intentius/chant 0.124.27 → 0.124.28
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/README.md +1 -1
- package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
- package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
- package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
- package/dist/__golden__/wave-loop/op-held-on.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
- package/dist/__golden__/wave-loop/op-per-run.json +7 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
- package/dist/__golden__/wave-loop/op-sealed.json +3 -0
- package/dist/__golden__/wave-loop/op-spend.json +3 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
- package/dist/change-set-graph.d.ts +56 -0
- package/dist/change-set-graph.d.ts.map +1 -0
- package/dist/change-set-graph.js +128 -0
- package/dist/change-set.d.ts +52 -3
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +35 -4
- package/dist/chant-version.d.ts +9 -0
- package/dist/chant-version.d.ts.map +1 -0
- package/dist/chant-version.js +16 -0
- package/dist/cli/commands/build.d.ts +8 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +9 -8
- package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.js +30 -2
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +13 -2
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.js +17 -8
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +19 -4
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +28 -0
- package/dist/cli/handlers/change-set.d.ts +10 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.js +51 -1
- package/dist/cli/handlers/fan-out.d.ts +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +14 -3
- package/dist/cli/handlers/pr.d.ts +8 -2
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +148 -24
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +256 -21
- package/dist/cli/handlers/release.d.ts +34 -0
- package/dist/cli/handlers/release.d.ts.map +1 -0
- package/dist/cli/handlers/release.js +203 -0
- package/dist/cli/handlers/run.d.ts +28 -2
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +170 -13
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +88 -13
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +49 -3
- package/dist/cli/registry.d.ts +29 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/run-events.d.ts +29 -0
- package/dist/cli/run-events.d.ts.map +1 -0
- package/dist/cli/run-events.js +47 -0
- package/dist/cli/version.d.ts +3 -5
- package/dist/cli/version.d.ts.map +1 -1
- package/dist/cli/version.js +3 -13
- package/dist/codegen/docs-in-site.d.ts +71 -0
- package/dist/codegen/docs-in-site.d.ts.map +1 -0
- package/dist/codegen/docs-in-site.js +177 -0
- package/dist/codegen/docs-types.d.ts +14 -1
- package/dist/codegen/docs-types.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts +18 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/docs.js +40 -7
- package/dist/codegen/rehype-base-url.mjs +15 -9
- package/dist/components/auto-release.d.ts +4 -1
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +2 -0
- package/dist/components/cli-support.d.ts +6 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +10 -9
- package/dist/components/driver.d.ts +42 -9
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +81 -54
- package/dist/components/fan-out-run.d.ts +7 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +35 -26
- package/dist/components/pr-run.d.ts +11 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +20 -1
- package/dist/components/promote-order.d.ts +38 -0
- package/dist/components/promote-order.d.ts.map +1 -0
- package/dist/components/promote-order.js +62 -0
- package/dist/components/promote.d.ts +15 -2
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +17 -0
- package/dist/components/release-compile.d.ts +133 -0
- package/dist/components/release-compile.d.ts.map +1 -0
- package/dist/components/release-compile.js +376 -0
- package/dist/components/release-gate.d.ts +125 -0
- package/dist/components/release-gate.d.ts.map +1 -0
- package/dist/components/release-gate.js +282 -0
- package/dist/components/release-promote.d.ts +96 -0
- package/dist/components/release-promote.d.ts.map +1 -0
- package/dist/components/release-promote.js +204 -0
- package/dist/components/run-progress.d.ts +7 -0
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +3 -1
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lifecycle/change-set-store.d.ts +67 -0
- package/dist/lifecycle/change-set-store.d.ts.map +1 -0
- package/dist/lifecycle/change-set-store.js +96 -0
- package/dist/lifecycle/commit-sha.d.ts +13 -0
- package/dist/lifecycle/commit-sha.d.ts.map +1 -0
- package/dist/lifecycle/commit-sha.js +21 -0
- package/dist/lifecycle/gate-change-set.d.ts +69 -0
- package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
- package/dist/lifecycle/gate-change-set.js +106 -0
- package/dist/lifecycle/gate-ledger.d.ts +49 -1
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.js +18 -0
- package/dist/lifecycle/release-cut.d.ts +107 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -0
- package/dist/lifecycle/release-cut.js +273 -0
- package/dist/lifecycle/release-ledger.d.ts +54 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +53 -20
- package/dist/lifecycle/release-store.d.ts +190 -0
- package/dist/lifecycle/release-store.d.ts.map +1 -0
- package/dist/lifecycle/release-store.js +318 -0
- package/dist/lifecycle/run-ledger.d.ts +4 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +2 -0
- package/dist/op/activities/change-coverage.d.ts.map +1 -1
- package/dist/op/activities/change-coverage.js +4 -2
- package/dist/op/activities/source-release.d.ts.map +1 -1
- package/dist/op/activities/source-release.js +7 -3
- package/dist/op/activities/source-rollback.d.ts.map +1 -1
- package/dist/op/activities/source-rollback.js +7 -3
- package/dist/op/gate-name.d.ts +11 -2
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-name.js +12 -3
- package/dist/op/gate-run-locator.d.ts +22 -0
- package/dist/op/gate-run-locator.d.ts.map +1 -1
- package/dist/op/gate-run-locator.js +31 -0
- package/dist/op/gate.d.ts +27 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +57 -3
- package/dist/op/local-executor.d.ts +36 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +158 -52
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +4 -0
- package/dist/op/run-live.d.ts +2 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/run-live.js +1 -1
- package/dist/op/runtime.d.ts +46 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/runtimes/local.js +8 -0
- package/dist/point-decision.d.ts +2 -0
- package/dist/point-decision.d.ts.map +1 -1
- package/dist/point-decision.js +9 -2
- package/dist/run-event-store.d.ts +77 -0
- package/dist/run-event-store.d.ts.map +1 -0
- package/dist/run-event-store.js +204 -0
- package/dist/run-event-stream.d.ts +132 -0
- package/dist/run-event-stream.d.ts.map +1 -0
- package/dist/run-event-stream.js +238 -0
- package/dist/run-event.d.ts +267 -0
- package/dist/run-event.d.ts.map +1 -0
- package/dist/run-event.js +207 -0
- package/dist/wave-runtime.mjs +204 -199
- package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
- package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/run-event-check.js +59 -0
- package/dist/workspace/change-set-show.d.ts +56 -0
- package/dist/workspace/change-set-show.d.ts.map +1 -0
- package/dist/workspace/change-set-show.js +54 -0
- package/dist/workspace/change-set-show.schema.json +102 -0
- package/dist/workspace/change-set.schema.json +28 -1
- package/dist/workspace/changes-cli.d.ts.map +1 -1
- package/dist/workspace/changes-cli.js +10 -1
- package/dist/workspace/changes.d.ts +37 -0
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/changes.js +49 -2
- package/dist/workspace/changes.schema.json +135 -0
- package/dist/workspace/ci-green-cli.d.ts +19 -4
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +75 -12
- package/dist/workspace/ci-green-forge.d.ts +20 -3
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green-forge.js +6 -4
- package/dist/workspace/ci-green-workflow.d.ts +11 -0
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +6 -0
- package/dist/workspace/ci-green.d.ts +273 -13
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +449 -38
- package/dist/workspace/ci-last-green.schema.json +32 -2
- package/dist/workspace/ci-list.schema.json +525 -0
- package/dist/workspace/commit-links.d.ts +85 -0
- package/dist/workspace/commit-links.d.ts.map +1 -0
- package/dist/workspace/commit-links.js +163 -0
- package/dist/workspace/declaration.d.ts +19 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +1 -1
- package/dist/workspace/declaration.schema.json +12 -0
- package/dist/workspace/identity-gate-rule.d.ts +37 -0
- package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
- package/dist/workspace/identity-gate-rule.js +72 -7
- package/dist/workspace/identity.d.ts +12 -0
- package/dist/workspace/identity.d.ts.map +1 -1
- package/dist/workspace/identity.js +13 -1
- package/dist/workspace/intent.d.ts +6 -1
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +51 -1
- package/dist/workspace/ls.d.ts +4 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +2 -0
- package/dist/workspace/ls.schema.json +3 -1
- package/dist/workspace/member-pipeline.d.ts +6 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +6 -0
- package/dist/workspace/promotion-order.d.ts +41 -0
- package/dist/workspace/promotion-order.d.ts.map +1 -0
- package/dist/workspace/promotion-order.js +95 -0
- package/dist/workspace/reason-codes.d.ts +2 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +4 -1
- package/dist/workspace/releases.d.ts +72 -0
- package/dist/workspace/releases.d.ts.map +1 -0
- package/dist/workspace/releases.js +94 -0
- package/dist/workspace/releases.schema.json +391 -0
- package/dist/workspace/run-event.schema.json +288 -0
- package/dist/workspace/status-gates.d.ts +26 -1
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-gates.js +31 -2
- package/dist/workspace/status-stewards.d.ts +4 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status-stewards.js +2 -0
- package/dist/workspace/status.d.ts +39 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +48 -1
- package/dist/workspace/status.schema.json +70 -1
- package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
- package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
- package/dist/workspace/trust/ci-tag-statement.js +123 -0
- package/dist/workspace/trust/op-run-statement.d.ts +91 -0
- package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/op-run-statement.js +143 -0
- package/dist/workspace/trust/rotation.d.ts +18 -2
- package/dist/workspace/trust/rotation.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.js +65 -16
- package/dist/workspace/trust/seal.d.ts +11 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/seal.js +9 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/trust/ssh-commit.js +10 -10
- package/package.json +1 -1
- package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
- package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
- package/src/__golden__/wave-loop/fan-out-only.json +6 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
- package/src/__golden__/wave-loop/op-cost-member.json +3 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
- package/src/__golden__/wave-loop/op-held-on.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
- package/src/__golden__/wave-loop/op-per-run.json +7 -0
- package/src/__golden__/wave-loop/op-pr-review.json +7 -0
- package/src/__golden__/wave-loop/op-sealed.json +3 -0
- package/src/__golden__/wave-loop/op-spend.json +3 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
- package/src/change-set-graph.test.ts +72 -0
- package/src/change-set-graph.ts +133 -0
- package/src/change-set.ts +76 -6
- package/src/chant-version.ts +16 -0
- package/src/cli/commands/build.ts +19 -8
- package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
- package/src/cli/commands/check-lexicon-docs.ts +39 -2
- package/src/cli/commands/check-lexicon.ts +14 -2
- package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
- package/src/cli/commands/init-lexicon.test.ts +25 -0
- package/src/cli/commands/init-lexicon.ts +18 -8
- package/src/cli/handlers/approve-release.test.ts +80 -0
- package/src/cli/handlers/approve.ts +18 -2
- package/src/cli/handlers/build.test.ts +52 -0
- package/src/cli/handlers/build.ts +29 -0
- package/src/cli/handlers/change-set.ts +49 -1
- package/src/cli/handlers/fan-out.test.ts +48 -0
- package/src/cli/handlers/fan-out.ts +15 -3
- package/src/cli/handlers/operator.test.ts +37 -0
- package/src/cli/handlers/pr.test.ts +194 -0
- package/src/cli/handlers/pr.ts +168 -34
- package/src/cli/handlers/promote.test.ts +389 -5
- package/src/cli/handlers/promote.ts +279 -20
- package/src/cli/handlers/release-implicit.e2e.test.ts +153 -0
- package/src/cli/handlers/release.e2e.test.ts +85 -0
- package/src/cli/handlers/release.ts +206 -0
- package/src/cli/handlers/run-auto-release.test.ts +71 -0
- package/src/cli/handlers/run-events-cmd.test.ts +92 -0
- package/src/cli/handlers/run.test.ts +30 -0
- package/src/cli/handlers/run.ts +176 -15
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +88 -13
- package/src/cli/mcp/never-over-mcp.test.ts +2 -0
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +37 -3
- package/src/cli/registry.ts +29 -3
- package/src/cli/run-events.ts +54 -0
- package/src/cli/version.ts +3 -13
- package/src/codegen/docs-in-site.test.ts +166 -0
- package/src/codegen/docs-in-site.ts +182 -0
- package/src/codegen/docs-site/AgentPrompt.astro +26 -1
- package/src/codegen/docs-types.ts +14 -1
- package/src/codegen/docs.ts +60 -7
- package/src/codegen/rehype-base-url.d.mts +3 -1
- package/src/codegen/rehype-base-url.mjs +15 -9
- package/src/codegen/rehype-base-url.test.ts +34 -0
- package/src/components/auto-release.test.ts +21 -0
- package/src/components/auto-release.ts +6 -1
- package/src/components/cli-support.test.ts +82 -0
- package/src/components/cli-support.ts +16 -9
- package/src/components/driver.ts +129 -56
- package/src/components/fan-out-run.ts +56 -35
- package/src/components/pr-run.ts +30 -1
- package/src/components/promote-order.test.ts +90 -0
- package/src/components/promote-order.ts +75 -0
- package/src/components/promote.test.ts +10 -1
- package/src/components/promote.ts +26 -1
- package/src/components/release-compile.test.ts +147 -0
- package/src/components/release-compile.ts +445 -0
- package/src/components/release-gate.test.ts +215 -0
- package/src/components/release-gate.ts +341 -0
- package/src/components/release-promote.test.ts +159 -0
- package/src/components/release-promote.ts +232 -0
- package/src/components/run-progress.ts +7 -0
- package/src/components/wave-plan.ts +1 -1
- package/src/config.ts +7 -1
- package/src/index.ts +2 -0
- package/src/lifecycle/change-set-store.test.ts +217 -0
- package/src/lifecycle/change-set-store.ts +112 -0
- package/src/lifecycle/commit-sha.ts +23 -0
- package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
- package/src/lifecycle/gate-change-set.ts +133 -0
- package/src/lifecycle/gate-ledger.test.ts +33 -0
- package/src/lifecycle/gate-ledger.ts +66 -1
- package/src/lifecycle/release-cut-signed.test.ts +180 -0
- package/src/lifecycle/release-cut.test.ts +411 -0
- package/src/lifecycle/release-cut.ts +367 -0
- package/src/lifecycle/release-ledger.test.ts +14 -0
- package/src/lifecycle/release-ledger.ts +85 -17
- package/src/lifecycle/release-store.ts +423 -0
- package/src/lifecycle/run-ledger.test.ts +10 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/op/activities/change-coverage.ts +4 -1
- package/src/op/activities/source-release.ts +8 -3
- package/src/op/activities/source-rollback.ts +8 -4
- package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
- package/src/op/gate-name.ts +13 -3
- package/src/op/gate-release.test.ts +126 -0
- package/src/op/gate-run-locator.test.ts +110 -0
- package/src/op/gate-run-locator.ts +35 -0
- package/src/op/gate.ts +88 -6
- package/src/op/local-executor.ts +207 -52
- package/src/op/op-waves-run.ts +4 -0
- package/src/op/run-events.test.ts +166 -0
- package/src/op/run-live.ts +1 -1
- package/src/op/runtime.ts +39 -0
- package/src/op/runtimes/local.test.ts +16 -0
- package/src/op/runtimes/local.ts +9 -0
- package/src/point-decision.ts +12 -2
- package/src/run-event-store.test.ts +144 -0
- package/src/run-event-store.ts +213 -0
- package/src/run-event-stream.test.ts +175 -0
- package/src/run-event-stream.ts +306 -0
- package/src/run-event.test.ts +277 -0
- package/src/run-event.ts +451 -0
- package/src/wave-loop-golden.test.ts +9 -0
- package/src/workspace/__fixtures__/run-event-check.ts +58 -0
- package/src/workspace/ancestry-index.test.ts +93 -0
- package/src/workspace/change-set-show.schema.json +102 -0
- package/src/workspace/change-set-show.ts +72 -0
- package/src/workspace/change-set.schema.json +28 -1
- package/src/workspace/changes-cli.ts +10 -1
- package/src/workspace/changes.schema.json +135 -0
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/changes.ts +84 -2
- package/src/workspace/ci-green-cli.ts +75 -13
- package/src/workspace/ci-green-forge.ts +24 -4
- package/src/workspace/ci-green-runs.test.ts +242 -0
- package/src/workspace/ci-green-signed.test.ts +302 -0
- package/src/workspace/ci-green-workflow.test.ts +16 -0
- package/src/workspace/ci-green-workflow.ts +12 -0
- package/src/workspace/ci-green.test.ts +131 -2
- package/src/workspace/ci-green.ts +624 -42
- package/src/workspace/ci-last-green.schema.json +32 -2
- package/src/workspace/ci-list.schema.json +525 -0
- package/src/workspace/commit-links.test.ts +279 -0
- package/src/workspace/commit-links.ts +183 -0
- package/src/workspace/declaration.schema.json +12 -0
- package/src/workspace/declaration.ts +22 -2
- package/src/workspace/identity-gate-rule.ts +91 -7
- package/src/workspace/identity.ts +14 -1
- package/src/workspace/intent.ts +48 -1
- package/src/workspace/ls.schema.json +3 -1
- package/src/workspace/ls.ts +6 -0
- package/src/workspace/member-pipeline.test.ts +17 -0
- package/src/workspace/member-pipeline.ts +9 -0
- package/src/workspace/promotion-order.ts +97 -0
- package/src/workspace/read-contract.e2e.test.ts +4 -1
- package/src/workspace/reason-codes.test.ts +2 -1
- package/src/workspace/reason-codes.ts +4 -1
- package/src/workspace/releases.schema.json +391 -0
- package/src/workspace/releases.test.ts +108 -0
- package/src/workspace/releases.ts +122 -0
- package/src/workspace/run-event.schema.json +288 -0
- package/src/workspace/status-contract.test.ts +52 -1
- package/src/workspace/status-gates.ts +56 -1
- package/src/workspace/status-stewards.ts +6 -0
- package/src/workspace/status.schema.json +70 -1
- package/src/workspace/status.ts +80 -1
- package/src/workspace/trust/ci-tag-statement.ts +151 -0
- package/src/workspace/trust/op-run-statement.ts +148 -0
- package/src/workspace/trust/rotation.ts +61 -14
- package/src/workspace/trust/seal.ts +10 -0
- package/src/workspace/trust/ssh-commit.ts +10 -9
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Place a change set's entries on the graph (#4022).
|
|
3
|
+
*
|
|
4
|
+
* A reader such as behold draws the graph `chant graph --format ir` and
|
|
5
|
+
* `chant workspace graph` print, and wants each change of a change set on the
|
|
6
|
+
* node it changes. The planner's address is not that node's id: a terraform
|
|
7
|
+
* plan names an instance, `module.app.terraform_data.instance[0]`, where the
|
|
8
|
+
* graph has one node per block, `<root>/module.app/terraform_data.instance`
|
|
9
|
+
* (the terraform lexicon's entity key, `hcl/parse.ts`). So chant maps the
|
|
10
|
+
* address itself, against the node ids of the graph it read, and a reader
|
|
11
|
+
* never derives an id.
|
|
12
|
+
*
|
|
13
|
+
* {@link changeSetNodeCandidates} lists the ids an entry's address can have,
|
|
14
|
+
* by planner. {@link mapChangeSetToGraph} keeps the first that the graph
|
|
15
|
+
* holds and puts the member's name in front, as `chant workspace graph`
|
|
16
|
+
* does. An entry whose address the graph does not hold gets `node: null`:
|
|
17
|
+
* a delete of a block the source no longer declares, or a warden or sql
|
|
18
|
+
* change outside the graph.
|
|
19
|
+
*
|
|
20
|
+
* Like `./change-set.ts`, this imports nothing at run time, so it bundles
|
|
21
|
+
* into one file with the change-set code.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ChangeSetDocument, ChangeSetEntry } from "./change-set";
|
|
25
|
+
|
|
26
|
+
/** The graph a document is mapped onto: the node ids of one member's own IR. */
|
|
27
|
+
export interface ChangeSetGraphNodes {
|
|
28
|
+
/** The workspace member whose graph it is, its path of names joined with `/` when nested, or null outside a workspace. */
|
|
29
|
+
member: string | null;
|
|
30
|
+
/** The commit the graph was read at, or null when unknown. */
|
|
31
|
+
at: string | null;
|
|
32
|
+
/** Every node id of the member's `chant graph --format ir`, without the member in front. */
|
|
33
|
+
ids: Iterable<string>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Split a terraform address into its dot-separated parts, dropping every
|
|
38
|
+
* `[...]` index. A quoted key may hold `.` or `]`, so quotes are honoured.
|
|
39
|
+
*/
|
|
40
|
+
function terraformParts(address: string): string[] {
|
|
41
|
+
const parts: string[] = [];
|
|
42
|
+
let current = "";
|
|
43
|
+
let depth = 0;
|
|
44
|
+
let quoted = false;
|
|
45
|
+
for (let i = 0; i < address.length; i++) {
|
|
46
|
+
const c = address[i];
|
|
47
|
+
if (depth > 0) {
|
|
48
|
+
if (quoted) {
|
|
49
|
+
if (c === "\\") i++;
|
|
50
|
+
else if (c === '"') quoted = false;
|
|
51
|
+
} else if (c === '"') quoted = true;
|
|
52
|
+
else if (c === "]") depth--;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (c === "[") depth++;
|
|
56
|
+
else if (c === ".") {
|
|
57
|
+
parts.push(current);
|
|
58
|
+
current = "";
|
|
59
|
+
} else current += c;
|
|
60
|
+
}
|
|
61
|
+
parts.push(current);
|
|
62
|
+
return parts.filter((p) => p.length > 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The block key the terraform lexicon gives an address in its root:
|
|
67
|
+
* `module.<call>/` for each module call, then the block's own address with
|
|
68
|
+
* no index. `module.app.terraform_data.instance[0]` is
|
|
69
|
+
* `module.app/terraform_data.instance`.
|
|
70
|
+
*/
|
|
71
|
+
export function terraformBlockKey(address: string): string | undefined {
|
|
72
|
+
const parts = terraformParts(address);
|
|
73
|
+
const scope: string[] = [];
|
|
74
|
+
let i = 0;
|
|
75
|
+
while (parts[i] === "module" && i + 1 < parts.length) {
|
|
76
|
+
scope.push(`module.${parts[i + 1]}`);
|
|
77
|
+
i += 2;
|
|
78
|
+
}
|
|
79
|
+
const rest = parts.slice(i);
|
|
80
|
+
let block: string | undefined;
|
|
81
|
+
if (rest[0] === "data" && rest.length >= 3) block = `data.${rest[1]}.${rest[2]}`;
|
|
82
|
+
else if (rest.length >= 2) block = `${rest[0]}.${rest[1]}`;
|
|
83
|
+
else if (rest.length === 0 && scope.length > 0) {
|
|
84
|
+
// The module call itself is a block of its caller.
|
|
85
|
+
const call = scope.pop()!;
|
|
86
|
+
block = call;
|
|
87
|
+
}
|
|
88
|
+
if (block === undefined) return undefined;
|
|
89
|
+
return [...scope, block].join("/");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The ids, in the order tried, that an entry's address can have in its
|
|
94
|
+
* member's graph IR:
|
|
95
|
+
*
|
|
96
|
+
* - `chant`: the address, which is the entity name the IR uses as its id.
|
|
97
|
+
* - `terraform`, `tofu`, `choudoufu`: `<root>/<block key>`, the root being
|
|
98
|
+
* the change-set member (`terraformBlockKey`), then the address.
|
|
99
|
+
* - `warden`, `sql`: the address.
|
|
100
|
+
*/
|
|
101
|
+
export function changeSetNodeCandidates(entry: Pick<ChangeSetEntry, "member" | "planner" | "address">): string[] {
|
|
102
|
+
switch (entry.planner) {
|
|
103
|
+
case "terraform":
|
|
104
|
+
case "tofu":
|
|
105
|
+
case "choudoufu": {
|
|
106
|
+
const key = terraformBlockKey(entry.address);
|
|
107
|
+
return key === undefined ? [entry.address] : [`${entry.member}/${key}`, entry.address];
|
|
108
|
+
}
|
|
109
|
+
default:
|
|
110
|
+
return [entry.address];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The document with each entry's `node` set from `graph`, and `graph`
|
|
116
|
+
* recording which graph it was. A null `graph` (none could be read) gives
|
|
117
|
+
* every entry `node: null`. The digest is unchanged: `node` is not hashed.
|
|
118
|
+
*/
|
|
119
|
+
export function mapChangeSetToGraph(doc: ChangeSetDocument, graph: ChangeSetGraphNodes | null): ChangeSetDocument {
|
|
120
|
+
if (graph === null) {
|
|
121
|
+
return { ...doc, entries: doc.entries.map((e) => ({ ...e, node: null })), graph: null };
|
|
122
|
+
}
|
|
123
|
+
const ids = new Set(graph.ids);
|
|
124
|
+
const prefix = graph.member ? `${graph.member}/` : "";
|
|
125
|
+
return {
|
|
126
|
+
...doc,
|
|
127
|
+
entries: doc.entries.map((e) => {
|
|
128
|
+
const id = changeSetNodeCandidates(e).find((c) => ids.has(c));
|
|
129
|
+
return { ...e, node: id === undefined ? null : `${prefix}${id}` };
|
|
130
|
+
}),
|
|
131
|
+
graph: { member: graph.member, at: graph.at },
|
|
132
|
+
};
|
|
133
|
+
}
|
package/src/change-set.ts
CHANGED
|
@@ -113,6 +113,15 @@ export interface ChangeSetEntry {
|
|
|
113
113
|
/** The estate, environment or org the change lands in. */
|
|
114
114
|
scope?: string;
|
|
115
115
|
attributes: ChangeSetAttribute[];
|
|
116
|
+
/**
|
|
117
|
+
* The graph node the entry changes, as `chant graph --format ir` and
|
|
118
|
+
* `chant workspace graph` name it (#4022): `<member>/<id>` inside a
|
|
119
|
+
* workspace. Null when the graph holds no node for the address, such as
|
|
120
|
+
* a delete of a block the source no longer declares. Absent until the
|
|
121
|
+
* document is mapped onto a graph (`./change-set-graph.ts`), and never
|
|
122
|
+
* part of the digest.
|
|
123
|
+
*/
|
|
124
|
+
node?: string | null;
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
/** Something a planner could not read, so the plan says nothing about it. */
|
|
@@ -201,6 +210,30 @@ export interface ChangeSetDocument {
|
|
|
201
210
|
/** Triggered actions, sorted by member and address. Absent when no member runs one. */
|
|
202
211
|
sideEffects?: ChangeSetSideEffect[];
|
|
203
212
|
summary: ChangeSetSummary;
|
|
213
|
+
/**
|
|
214
|
+
* The graph the entries' `node` ids come from (#4022): the workspace member
|
|
215
|
+
* whose graph it is, or null outside a workspace, and the commit it was
|
|
216
|
+
* read at, or null when that is unknown. Null when no graph could be read,
|
|
217
|
+
* so every `node` is null. Absent on a document never mapped. Not hashed.
|
|
218
|
+
*/
|
|
219
|
+
graph?: ChangeSetGraph | null;
|
|
220
|
+
/**
|
|
221
|
+
* The release this change set was planned for (#4045): the `releaseDigest`
|
|
222
|
+
* of the release a `components promote --release` deploys. Set with
|
|
223
|
+
* `environment` on a release gate's document, absent on every other. Part
|
|
224
|
+
* of the digest when present, so a stored document names the one release
|
|
225
|
+
* and environment its gate bound, and two releases whose plans read the
|
|
226
|
+
* same never share a document or an approval.
|
|
227
|
+
*/
|
|
228
|
+
releaseDigest?: string;
|
|
229
|
+
/** The environment this change set was planned for (#4045), set with `releaseDigest`. Part of the digest when present. */
|
|
230
|
+
environment?: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Where a document's node ids come from. */
|
|
234
|
+
export interface ChangeSetGraph {
|
|
235
|
+
member: string | null;
|
|
236
|
+
at: string | null;
|
|
204
237
|
}
|
|
205
238
|
|
|
206
239
|
const byString = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);
|
|
@@ -235,22 +268,39 @@ export function changeSetDigest(members: ReadonlyArray<Pick<ChangeSetMember, "me
|
|
|
235
268
|
/**
|
|
236
269
|
* The document's digest: `computePlanDigest("change-set-document", …)` over
|
|
237
270
|
* the set digest ({@link changeSetDigest}), each member's name, status and
|
|
238
|
-
* holes, the entries and the side effects.
|
|
271
|
+
* holes, the entries (without their graph `node`) and the side effects.
|
|
272
|
+
* Members are sorted by name and
|
|
239
273
|
* entries and side effects by the keys {@link composeChangeSet} sorts them by,
|
|
240
274
|
* so order makes no difference.
|
|
241
275
|
*
|
|
242
276
|
* A provisional member is left out, with its entries and side effects, as
|
|
243
277
|
* the set digest leaves it out: no approval of the document covers it.
|
|
278
|
+
*
|
|
279
|
+
* A release gate's document (#4045) also hashes the `releaseDigest` and
|
|
280
|
+
* `environment` it was planned for. A document without them hashes as it
|
|
281
|
+
* always has, so no earlier digest moves.
|
|
244
282
|
*/
|
|
245
|
-
export function changeSetDocumentDigest(
|
|
283
|
+
export function changeSetDocumentDigest(
|
|
284
|
+
doc: Pick<ChangeSetDocument, "members" | "entries" | "sideEffects" | "releaseDigest" | "environment">,
|
|
285
|
+
): string {
|
|
246
286
|
const provisional = new Set(doc.members.filter((m) => m.provisional === true).map((m) => m.member));
|
|
247
287
|
const members = doc.members
|
|
248
288
|
.filter((m) => !provisional.has(m.member))
|
|
249
289
|
.map((m) => ({ member: m.member, status: m.status, holes: m.holes }))
|
|
250
290
|
.sort((a, b) => byString(a.member, b.member));
|
|
251
|
-
|
|
291
|
+
// An entry's graph node (#4022) says where on the graph the change is,
|
|
292
|
+
// not what the plan does, so it stays outside the digest: mapping a stored
|
|
293
|
+
// document onto a graph never moves the value its gate bound.
|
|
294
|
+
const entries = doc.entries
|
|
295
|
+
.filter((e) => !provisional.has(e.member))
|
|
296
|
+
.map(({ node: _node, ...e }) => e)
|
|
297
|
+
.sort((a, b) => byString(entryKey(a), entryKey(b)));
|
|
252
298
|
const sideEffects = (doc.sideEffects ?? []).filter((s) => !provisional.has(s.member)).sort(bySideEffect);
|
|
253
|
-
|
|
299
|
+
const planned =
|
|
300
|
+
doc.releaseDigest !== undefined || doc.environment !== undefined
|
|
301
|
+
? { planned: { releaseDigest: doc.releaseDigest ?? null, environment: doc.environment ?? null } }
|
|
302
|
+
: {};
|
|
303
|
+
return computePlanDigest("change-set-document", { set: changeSetDigest(doc.members), members, entries, sideEffects, ...planned });
|
|
254
304
|
}
|
|
255
305
|
|
|
256
306
|
/** The summary of a set of members and entries. */
|
|
@@ -296,6 +346,8 @@ function sortKeys<T>(o: Record<string, T>): Record<string, T> {
|
|
|
296
346
|
export interface ComposeChangeSetOptions {
|
|
297
347
|
/** The chant version to record in `chant`. */
|
|
298
348
|
chant?: string;
|
|
349
|
+
/** The release and environment a release gate's change set was planned for (#4045), recorded and hashed. */
|
|
350
|
+
planned?: { releaseDigest: string; environment: string };
|
|
299
351
|
}
|
|
300
352
|
|
|
301
353
|
/**
|
|
@@ -317,12 +369,14 @@ export function composeChangeSet(parts: ReadonlyArray<ChangeSetPart>, options: C
|
|
|
317
369
|
const sideEffects: ChangeSetSideEffect[] = parts
|
|
318
370
|
.flatMap((p) => (p.sideEffects ?? []).map((s) => ({ member: p.member.member, ...s })))
|
|
319
371
|
.sort(bySideEffect);
|
|
320
|
-
const
|
|
372
|
+
const planned = options.planned ? { releaseDigest: options.planned.releaseDigest, environment: options.planned.environment } : {};
|
|
373
|
+
const digest = changeSetDocumentDigest({ members, entries, sideEffects, ...planned });
|
|
321
374
|
return {
|
|
322
375
|
$schema: CHANGE_SET_SCHEMA_ID,
|
|
323
376
|
contract: CHANGE_SET_CONTRACT,
|
|
324
377
|
...(options.chant !== undefined ? { chant: options.chant } : {}),
|
|
325
378
|
digest,
|
|
379
|
+
...planned,
|
|
326
380
|
members,
|
|
327
381
|
entries,
|
|
328
382
|
...(sideEffects.length > 0 ? { sideEffects } : {}),
|
|
@@ -335,7 +389,9 @@ export function composeChangeSet(parts: ReadonlyArray<ChangeSetPart>, options: C
|
|
|
335
389
|
* effects give ({@link changeSetDocumentDigest}). A document that names a
|
|
336
390
|
* member twice has no digest and is not verified.
|
|
337
391
|
*/
|
|
338
|
-
export function verifyChangeSetDigest(
|
|
392
|
+
export function verifyChangeSetDigest(
|
|
393
|
+
doc: Pick<ChangeSetDocument, "digest" | "members" | "entries" | "sideEffects" | "releaseDigest" | "environment">,
|
|
394
|
+
): boolean {
|
|
339
395
|
try {
|
|
340
396
|
return changeSetDocumentDigest(doc) === doc.digest;
|
|
341
397
|
} catch {
|
|
@@ -343,6 +399,20 @@ export function verifyChangeSetDigest(doc: Pick<ChangeSetDocument, "digest" | "m
|
|
|
343
399
|
}
|
|
344
400
|
}
|
|
345
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Whether a change set changes nothing (ws-104 question 6, #4045): it has at
|
|
404
|
+
* least one member, every member planned, none failed or provisional, no
|
|
405
|
+
* member has a hole, and every entry is a `no-op` or a `read`. A release
|
|
406
|
+
* gate over such a document passes without an approval.
|
|
407
|
+
*/
|
|
408
|
+
export function isEmptyChangeSet(doc: Pick<ChangeSetDocument, "members" | "entries">): boolean {
|
|
409
|
+
if (doc.members.length === 0) return false;
|
|
410
|
+
for (const m of doc.members) {
|
|
411
|
+
if (m.status !== "planned" || m.provisional === true || m.holes.length > 0) return false;
|
|
412
|
+
}
|
|
413
|
+
return doc.entries.every((e) => e.action === "no-op" || e.action === "read");
|
|
414
|
+
}
|
|
415
|
+
|
|
346
416
|
// ── chant lifecycle plan ───────────────────────────────────────────────────────
|
|
347
417
|
|
|
348
418
|
export interface LifecyclePlanPartInput {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The installed chant's version, read from `@intentius/chant`'s own
|
|
5
|
+
* package.json, or "0.0.0" when it can't be read. The path is the same from
|
|
6
|
+
* `src/` and `dist/`, so it holds whether chant runs from source or from its
|
|
7
|
+
* build. Kept outside `cli/` so that a module the Op entry loads, such as the
|
|
8
|
+
* run-event writer (#4041), can name the version without loading the CLI.
|
|
9
|
+
*/
|
|
10
|
+
export const CHANT_VERSION: string = (() => {
|
|
11
|
+
try {
|
|
12
|
+
return (createRequire(import.meta.url)("../package.json") as { version?: string }).version ?? "0.0.0";
|
|
13
|
+
} catch {
|
|
14
|
+
return "0.0.0";
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
@@ -118,6 +118,20 @@ export interface BuildOptions {
|
|
|
118
118
|
* outputs of its own `serializers`, so the sources are evaluated once.
|
|
119
119
|
*/
|
|
120
120
|
sharedBuild?: { serializers: Serializer[]; result?: ReturnType<typeof build> };
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Where each output file goes: called with its absolute path and its
|
|
124
|
+
* content in place of creating the directory and writing the file. A
|
|
125
|
+
* release cut (#4046) passes one that keeps the files in memory, so it
|
|
126
|
+
* records exactly what this build writes and nothing left on disk by an
|
|
127
|
+
* earlier one.
|
|
128
|
+
*/
|
|
129
|
+
writeFile?: (path: string, content: string) => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeFileCreatingDir(path: string, content: string): void {
|
|
133
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
134
|
+
writeFileSync(path, content);
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
/**
|
|
@@ -735,6 +749,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
735
749
|
}
|
|
736
750
|
|
|
737
751
|
// Handle output
|
|
752
|
+
const writeOutput = options.writeFile ?? writeFileCreatingDir;
|
|
738
753
|
if (result.errors.length === 0 && errors.length === 0) {
|
|
739
754
|
// Extract primary content and collect additional files from SerializerResult.
|
|
740
755
|
// Each entry remembers which lexicon produced it, so a same-basename
|
|
@@ -866,8 +881,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
866
881
|
additionalFiles.delete(filename);
|
|
867
882
|
try {
|
|
868
883
|
const targetPath = join(projectDist, filename);
|
|
869
|
-
|
|
870
|
-
writeFileSync(targetPath, entry.content);
|
|
884
|
+
writeOutput(targetPath, entry.content);
|
|
871
885
|
opsWritten += 1;
|
|
872
886
|
} catch (err) {
|
|
873
887
|
errors.push(
|
|
@@ -893,8 +907,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
893
907
|
if (!config?.name) continue;
|
|
894
908
|
try {
|
|
895
909
|
const targetPath = join(projectDist, "ops", config.name, "op.json");
|
|
896
|
-
|
|
897
|
-
writeFileSync(targetPath, serializeOpIR(config));
|
|
910
|
+
writeOutput(targetPath, serializeOpIR(config));
|
|
898
911
|
irWritten += 1;
|
|
899
912
|
} catch (err) {
|
|
900
913
|
errors.push(
|
|
@@ -913,8 +926,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
913
926
|
// output path and any nested additional-file paths (e.g. ops/<name>/...).
|
|
914
927
|
try {
|
|
915
928
|
const outputPath = resolve(options.output);
|
|
916
|
-
|
|
917
|
-
writeFileSync(outputPath, output);
|
|
929
|
+
writeOutput(outputPath, output);
|
|
918
930
|
|
|
919
931
|
// Write additional files (e.g. nested stack templates) alongside the primary output
|
|
920
932
|
if (additionalFiles.size > 0) {
|
|
@@ -935,8 +947,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
935
947
|
}
|
|
936
948
|
}
|
|
937
949
|
const targetPath = join(outputDir, filename);
|
|
938
|
-
|
|
939
|
-
writeFileSync(targetPath, fileContent);
|
|
950
|
+
writeOutput(targetPath, fileContent);
|
|
940
951
|
}
|
|
941
952
|
}
|
|
942
953
|
} catch (err) {
|
|
@@ -88,3 +88,54 @@ describe("auditDocsReachability", () => {
|
|
|
88
88
|
expect(auditDocsReachability(d).unreachable).toEqual(["a", "c"]);
|
|
89
89
|
});
|
|
90
90
|
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* chant #3993: inside the chant repository a lexicon's pages are generated
|
|
94
|
+
* into the main docs site with a _sidebar.json beside them, and the audit
|
|
95
|
+
* reads those instead of a docs app the lexicon no longer has.
|
|
96
|
+
*/
|
|
97
|
+
describe("auditDocsReachability for a section of the main site", () => {
|
|
98
|
+
function mainSite(): void {
|
|
99
|
+
mkdirSync(join(dir, "docs"), { recursive: true });
|
|
100
|
+
writeFileSync(join(dir, "docs", "package.json"), JSON.stringify({ name: "@intentius/chant-docs" }));
|
|
101
|
+
writeFileSync(join(dir, "docs", "astro.config.mjs"), "export default {};\n");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function repoWithSection(pages: string[], group: unknown, hidden: string[] = []): string {
|
|
105
|
+
mainSite();
|
|
106
|
+
const section = join(dir, "docs", "src", "content", "docs", "lexicons", "fixture");
|
|
107
|
+
mkdirSync(section, { recursive: true });
|
|
108
|
+
for (const page of pages) writeFileSync(join(section, `${page}.mdx`), "body\n");
|
|
109
|
+
writeFileSync(join(section, "_sidebar.json"), JSON.stringify({ group, hidden }));
|
|
110
|
+
const lex = join(dir, "lexicons", "fixture");
|
|
111
|
+
mkdirSync(join(lex, "docs", "pages"), { recursive: true });
|
|
112
|
+
return lex;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
test("every page in the fragment, or hidden by it, is reachable", () => {
|
|
116
|
+
const lex = repoWithSection(
|
|
117
|
+
["index", "start", "secret"],
|
|
118
|
+
{
|
|
119
|
+
label: "Fixture",
|
|
120
|
+
items: [
|
|
121
|
+
{ label: "Overview", slug: "lexicons/fixture" },
|
|
122
|
+
{ label: "Tutorials", items: [{ label: "Start", slug: "lexicons/fixture/start" }] },
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
["lexicons/fixture/secret"],
|
|
126
|
+
);
|
|
127
|
+
expect(auditDocsReachability(lex)).toEqual({ hasSite: true, pages: ["index", "secret", "start"], unreachable: [] });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("a page the fragment does not name is unreachable", () => {
|
|
131
|
+
const lex = repoWithSection(["index", "orphan"], { label: "Fixture", items: [{ label: "Overview", slug: "lexicons/fixture" }] });
|
|
132
|
+
expect(auditDocsReachability(lex).unreachable).toEqual(["orphan"]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("before the section is generated there is nothing to audit", () => {
|
|
136
|
+
mainSite();
|
|
137
|
+
const lex = join(dir, "lexicons", "fixture");
|
|
138
|
+
mkdirSync(lex, { recursive: true });
|
|
139
|
+
expect(auditDocsReachability(lex).hasSite).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
18
|
-
import { join } from "path";
|
|
18
|
+
import { basename, join, resolve } from "path";
|
|
19
|
+
|
|
20
|
+
import { collectSidebarSlugs } from "../../codegen/docs";
|
|
21
|
+
import { findChantDocsSite, sectionContentDir } from "../../codegen/docs-in-site";
|
|
19
22
|
|
|
20
23
|
export interface DocsReachability {
|
|
21
24
|
/** Whether this lexicon ships a Starlight site at all. */
|
|
@@ -55,7 +58,7 @@ export function auditDocsReachability(lexiconDir: string): DocsReachability {
|
|
|
55
58
|
const configPath = join(lexiconDir, "docs", "astro.config.mjs");
|
|
56
59
|
|
|
57
60
|
if (!existsSync(contentDir) || !existsSync(configPath)) {
|
|
58
|
-
return
|
|
61
|
+
return auditSectionReachability(lexiconDir);
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
const slugs = sidebarSlugs(readFileSync(configPath, "utf-8"));
|
|
@@ -99,3 +102,37 @@ export function auditDocsClassification(lexiconDir: string): DocsClassification
|
|
|
99
102
|
}
|
|
100
103
|
return { hasPages: true, unclassified };
|
|
101
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Reachability for a lexicon whose docs are a section of the chant main site
|
|
108
|
+
* (chant #3993): its generated pages under
|
|
109
|
+
* `docs/src/content/docs/lexicons/<name>/` against the `_sidebar.json` the
|
|
110
|
+
* pipeline wrote beside them. A page the fragment lists as hidden is
|
|
111
|
+
* deliberately URL-only and does not count. Before the section has been
|
|
112
|
+
* generated there is nothing to audit, which reads as no site, like a
|
|
113
|
+
* lexicon with no docs.
|
|
114
|
+
*/
|
|
115
|
+
function auditSectionReachability(lexiconDir: string): DocsReachability {
|
|
116
|
+
const site = findChantDocsSite(lexiconDir);
|
|
117
|
+
const name = basename(resolve(lexiconDir));
|
|
118
|
+
const dir = site ? sectionContentDir(site, name) : undefined;
|
|
119
|
+
const fragmentPath = dir ? join(dir, "_sidebar.json") : undefined;
|
|
120
|
+
if (!dir || !fragmentPath || !existsSync(fragmentPath)) return { hasSite: false, pages: [], unreachable: [] };
|
|
121
|
+
|
|
122
|
+
const fragment = JSON.parse(readFileSync(fragmentPath, "utf-8")) as {
|
|
123
|
+
group: Record<string, unknown>;
|
|
124
|
+
hidden?: string[];
|
|
125
|
+
};
|
|
126
|
+
const prefix = `lexicons/${name}/`;
|
|
127
|
+
const reached = new Set(
|
|
128
|
+
[...collectSidebarSlugs([fragment.group]), ...(fragment.hidden ?? [])].map((slug) =>
|
|
129
|
+
slug.startsWith(prefix) ? slug.slice(prefix.length) : slug,
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
const pages = readdirSync(dir)
|
|
133
|
+
.filter((f) => f.endsWith(".mdx") || f.endsWith(".md"))
|
|
134
|
+
.map((f) => f.replace(/\.mdx?$/, ""))
|
|
135
|
+
.sort();
|
|
136
|
+
const unreachable = pages.filter((slug) => slug !== "index" && !reached.has(slug));
|
|
137
|
+
return { hasSite: true, pages, unreachable };
|
|
138
|
+
}
|
|
@@ -457,12 +457,24 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
|
|
|
457
457
|
pass: hasSerializerTest,
|
|
458
458
|
});
|
|
459
459
|
|
|
460
|
+
// A lexicon whose src/codegen/docs.ts runs docsPipeline always has a page:
|
|
461
|
+
// the pipeline writes the overview (index.mdx) from the manifest. Inside the
|
|
462
|
+
// chant repository that page is generated into the main docs site rather
|
|
463
|
+
// than committed under docs/ (chant #3993), so k3d, whose docs are all
|
|
464
|
+
// generated, has no .mdx of its own to count.
|
|
460
465
|
const mdxFiles = findFiles(join(dir, "docs"), (n) => n.endsWith(".mdx"));
|
|
466
|
+
const docsTs = join(dir, "src", "codegen", "docs.ts");
|
|
467
|
+
const pipelineOverview = existsSync(docsTs) && readFileSync(docsTs, "utf-8").includes("docsPipeline(");
|
|
461
468
|
items.push({
|
|
462
469
|
name: "At least 1 .mdx doc page",
|
|
463
470
|
tier: 1,
|
|
464
|
-
pass: mdxFiles.length > 0,
|
|
465
|
-
detail:
|
|
471
|
+
pass: mdxFiles.length > 0 || pipelineOverview,
|
|
472
|
+
detail:
|
|
473
|
+
mdxFiles.length > 0
|
|
474
|
+
? `${mdxFiles.length} page(s)`
|
|
475
|
+
: pipelineOverview
|
|
476
|
+
? "the docs pipeline's generated overview"
|
|
477
|
+
: undefined,
|
|
466
478
|
});
|
|
467
479
|
|
|
468
480
|
// Counting pages says nothing about whether a reader can find them.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Docs site template generators for init-lexicon scaffold.
|
|
2
|
+
* Docs site template generators for init-lexicon scaffold. The app files
|
|
3
|
+
* (package.json, tsconfig, Astro config, content config) are written only for a
|
|
4
|
+
* lexicon outside the chant repository; inside it, the lexicon's docs are a
|
|
5
|
+
* section of the main site (chant #3993).
|
|
3
6
|
*/
|
|
4
7
|
|
|
8
|
+
import { DOCS_SITE_DEPENDENCIES } from "../../../../codegen/docs-in-site";
|
|
9
|
+
|
|
5
10
|
export function generateDocsPackageJson(name: string): string {
|
|
6
11
|
return JSON.stringify(
|
|
7
12
|
{
|
|
@@ -14,11 +19,7 @@ export function generateDocsPackageJson(name: string): string {
|
|
|
14
19
|
build: "astro build",
|
|
15
20
|
preview: "astro preview",
|
|
16
21
|
},
|
|
17
|
-
dependencies: {
|
|
18
|
-
"@astrojs/starlight": "^0.37.6",
|
|
19
|
-
astro: "^5.6.1",
|
|
20
|
-
sharp: "^0.34.2",
|
|
21
|
-
},
|
|
22
|
+
dependencies: { ...DOCS_SITE_DEPENDENCIES },
|
|
22
23
|
},
|
|
23
24
|
null,
|
|
24
25
|
2,
|
|
@@ -78,6 +78,31 @@ describe("initLexiconCommand", () => {
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
// chant #3993: inside the chant repository the lexicon's docs are a section
|
|
82
|
+
// of the main site, so only docs/pages/ is scaffolded.
|
|
83
|
+
test("inside the chant repository it scaffolds docs/pages/ and no docs app", async () => {
|
|
84
|
+
mkdirSync(join(tmpDir, "docs"), { recursive: true });
|
|
85
|
+
writeFileSync(join(tmpDir, "docs", "package.json"), JSON.stringify({ name: "@intentius/chant-docs" }));
|
|
86
|
+
writeFileSync(join(tmpDir, "docs", "astro.config.mjs"), "export default {};\n");
|
|
87
|
+
const targetDir = join(tmpDir, "lexicons", "in-repo");
|
|
88
|
+
const result = await initLexiconCommand({ name: "in-repo", path: targetDir });
|
|
89
|
+
|
|
90
|
+
expect(result.success).toBe(true);
|
|
91
|
+
expect(existsSync(join(targetDir, "docs/pages/getting-started.mdx"))).toBe(true);
|
|
92
|
+
for (const file of ["docs/package.json", "docs/tsconfig.json", "docs/astro.config.mjs", "docs/src"]) {
|
|
93
|
+
expect(existsSync(join(targetDir, file)), file).toBe(false);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a standalone docs app pins the main site's Starlight and Astro", async () => {
|
|
98
|
+
const targetDir = join(tmpDir, "pins");
|
|
99
|
+
await initLexiconCommand({ name: "pins", path: targetDir });
|
|
100
|
+
const docsPkg = JSON.parse(readFileSync(join(targetDir, "docs/package.json"), "utf-8"));
|
|
101
|
+
const mainPkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "..", "..", "..", "docs", "package.json"), "utf-8"));
|
|
102
|
+
expect(docsPkg.dependencies["@astrojs/starlight"]).toBe(mainPkg.dependencies["@astrojs/starlight"]);
|
|
103
|
+
expect(docsPkg.dependencies.astro).toBe(mainPkg.dependencies.astro);
|
|
104
|
+
});
|
|
105
|
+
|
|
81
106
|
test("plugin.ts contains all 5 required lifecycle methods", async () => {
|
|
82
107
|
const targetDir = join(tmpDir, "lifecycle");
|
|
83
108
|
await initLexiconCommand({ name: "k8s", path: targetDir });
|
|
@@ -12,6 +12,7 @@ import { generatePackageJson, generateTsConfig, generateTsConfigBuild, generateJ
|
|
|
12
12
|
import { generatePluginTestTs, generateSerializerTestTs, generateCompletionsTestTs, generateHoverTestTs } from "./init-lexicon/templates/tests";
|
|
13
13
|
import { generateDocsPackageJson, generateDocsTsConfig, generateDocsAstroConfig, generateDocsContentConfig, generateDocsIndexMdx, generateDocsGettingStartedMdx } from "./init-lexicon/templates/docs";
|
|
14
14
|
import { generateExamplePackageJson, generateExampleInfraTs } from "./init-lexicon/templates/examples";
|
|
15
|
+
import { findChantDocsSite } from "../../codegen/docs-in-site";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Init-lexicon command options
|
|
@@ -101,6 +102,13 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
// Inside the chant repository a lexicon's docs are a section of the main
|
|
106
|
+
// site (chant #3993): the pipeline writes its pages into docs/, so the
|
|
107
|
+
// lexicon gets only docs/pages/. Anywhere else it gets a standalone
|
|
108
|
+
// Starlight app of its own, as before.
|
|
109
|
+
const standaloneDocs = findChantDocsSite(targetDir) === undefined;
|
|
110
|
+
const standaloneDocsDirs = ["docs/src", "docs/src/content", "docs/src/content/docs"];
|
|
111
|
+
|
|
104
112
|
// Create directory structure
|
|
105
113
|
const dirs = [
|
|
106
114
|
"",
|
|
@@ -112,9 +120,7 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
|
|
|
112
120
|
"src/lsp",
|
|
113
121
|
"src/generated",
|
|
114
122
|
"docs",
|
|
115
|
-
|
|
116
|
-
"docs/src/content",
|
|
117
|
-
"docs/src/content/docs",
|
|
123
|
+
...(standaloneDocs ? standaloneDocsDirs : []),
|
|
118
124
|
"docs/pages",
|
|
119
125
|
"examples/getting-started",
|
|
120
126
|
"examples/getting-started/src",
|
|
@@ -157,11 +163,15 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
|
|
|
157
163
|
"justfile": generateJustfile(name),
|
|
158
164
|
".gitignore": generateGitignore(),
|
|
159
165
|
"README.md": generateReadme(name, names),
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
...(standaloneDocs
|
|
167
|
+
? {
|
|
168
|
+
"docs/package.json": generateDocsPackageJson(name),
|
|
169
|
+
"docs/tsconfig.json": generateDocsTsConfig(),
|
|
170
|
+
"docs/astro.config.mjs": generateDocsAstroConfig(name),
|
|
171
|
+
"docs/src/content.config.ts": generateDocsContentConfig(),
|
|
172
|
+
"docs/src/content/docs/index.mdx": generateDocsIndexMdx(name),
|
|
173
|
+
}
|
|
174
|
+
: {}),
|
|
165
175
|
"docs/pages/getting-started.mdx": generateDocsGettingStartedMdx(name),
|
|
166
176
|
"examples/getting-started/package.json": generateExamplePackageJson(name),
|
|
167
177
|
"examples/getting-started/src/infra.ts": generateExampleInfraTs(name, names),
|