@intentius/chant 0.124.46 → 0.124.48
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/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
- package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
- package/dist/__golden__/wave-loop/op-denied.json +16 -0
- package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on.json +16 -0
- package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/dist/__golden__/wave-loop/op-overridden.json +16 -0
- package/dist/__golden__/wave-loop/op-per-run.json +20 -0
- package/dist/__golden__/wave-loop/op-point.json +13 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
- package/dist/__golden__/wave-loop/op-sealed.json +10 -0
- package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/dist/__golden__/wave-loop/op-spend.json +10 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/dist/__golden__/wave-loop/op-wide.json +39 -0
- package/dist/action-pins.d.ts +61 -0
- package/dist/action-pins.d.ts.map +1 -0
- package/dist/action-pins.js +77 -0
- package/dist/cli/commands/carve.d.ts +3 -0
- package/dist/cli/commands/carve.d.ts.map +1 -1
- package/dist/cli/commands/carve.js +36 -7
- package/dist/cli/commands/import-report.d.ts +26 -0
- package/dist/cli/commands/import-report.d.ts.map +1 -0
- package/dist/cli/commands/import-report.js +65 -0
- package/dist/cli/commands/import.d.ts +11 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +22 -3
- package/dist/cli/commands/migrate.d.ts +13 -1
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +134 -10
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +21 -2
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +6 -1
- package/dist/cli/handlers/ledger.d.ts +3 -0
- package/dist/cli/handlers/ledger.d.ts.map +1 -0
- package/dist/cli/handlers/ledger.js +55 -0
- package/dist/cli/handlers/migrate.d.ts.map +1 -1
- package/dist/cli/handlers/migrate.js +18 -11
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +12 -1
- package/dist/cli/handlers/pr-env.js +2 -2
- package/dist/cli/handlers/pr.d.ts +15 -1
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +143 -39
- package/dist/cli/handlers/query.d.ts +7 -2
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +16 -4
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +6 -3
- package/dist/cli/handlers/search.d.ts +2 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +13 -39
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +65 -14
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +36 -2
- package/dist/cli/registry.d.ts +14 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/builders.d.ts +5 -1
- package/dist/components/builders.d.ts.map +1 -1
- package/dist/components/builders.js +5 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +16 -1
- package/dist/components/capability-plugin.d.ts +14 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/pr-hooks.d.ts +21 -1
- package/dist/components/pr-hooks.d.ts.map +1 -1
- package/dist/components/pr-hooks.js +72 -4
- package/dist/components/pr-pipeline.d.ts +45 -10
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +47 -14
- package/dist/components/pr-policy.d.ts +36 -0
- package/dist/components/pr-policy.d.ts.map +1 -1
- package/dist/components/pr-policy.js +36 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +5 -1
- package/dist/components/verbs/index.d.ts +2 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +2 -0
- package/dist/components/verbs/publish-artifact.d.ts +65 -0
- package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
- package/dist/components/verbs/publish-artifact.js +74 -0
- package/dist/components/verbs/publish-image.d.ts +129 -0
- package/dist/components/verbs/publish-image.d.ts.map +1 -0
- package/dist/components/verbs/publish-image.js +145 -0
- package/dist/config-environments.d.ts +20 -0
- package/dist/config-environments.d.ts.map +1 -1
- package/dist/config-environments.js +12 -0
- package/dist/config.d.ts +9 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -2
- package/dist/gate-decision.d.ts +11 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +15 -3
- package/dist/graph-ir.d.ts +25 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-walk.d.ts +66 -0
- package/dist/graph-walk.d.ts.map +1 -0
- package/dist/graph-walk.js +70 -0
- package/dist/import/parser.d.ts +19 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/lexicon.d.ts +71 -6
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +11 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +2 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/ledger-read.d.ts +65 -0
- package/dist/lifecycle/ledger-read.d.ts.map +1 -0
- package/dist/lifecycle/ledger-read.js +160 -0
- package/dist/lifecycle/release-workstation.d.ts +7 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -1
- package/dist/lifecycle/release-workstation.js +25 -0
- package/dist/lifecycle/run-ledger.d.ts +4 -3
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lint/discover.js +3 -1
- package/dist/lint/post-synth.d.ts +127 -1
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/post-synth.js +84 -2
- package/dist/lint/rules/op/index.d.ts +1 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +2 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
- package/dist/op/activities/activity-contracts.d.ts +5 -2
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +5 -2
- package/dist/op/activities/index.d.ts +2 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +1 -1
- package/dist/op/activities/review-agent.d.ts +90 -4
- package/dist/op/activities/review-agent.d.ts.map +1 -1
- package/dist/op/activities/review-agent.js +217 -19
- package/dist/op/activities/work-evidence.d.ts +8 -1
- package/dist/op/activities/work-evidence.d.ts.map +1 -1
- package/dist/op/activities/work-evidence.js +1 -0
- package/dist/op/builders.d.ts +9 -5
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -5
- package/dist/op/composites/apply-op.d.ts +29 -2
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/apply-op.js +20 -1
- package/dist/op/gate-approval.d.ts +11 -5
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +11 -6
- package/dist/op/gate-quorum.d.ts +100 -3
- package/dist/op/gate-quorum.d.ts.map +1 -1
- package/dist/op/gate-quorum.js +108 -6
- package/dist/op/gate-review.d.ts +9 -0
- package/dist/op/gate-review.d.ts.map +1 -1
- package/dist/op/gate-review.js +13 -0
- package/dist/op/gate.d.ts +44 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +25 -4
- package/dist/op/index.d.ts +3 -3
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +38 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +48 -6
- package/dist/op/op-waves-run.d.ts +26 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +259 -13
- package/dist/op/op-waves.d.ts +72 -5
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +110 -15
- package/dist/op/runtime.d.ts +11 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/schedule-overdue.d.ts +16 -0
- package/dist/op/schedule-overdue.d.ts.map +1 -1
- package/dist/op/schedule-overdue.js +36 -0
- package/dist/op/stage-hooks.d.ts +9 -2
- package/dist/op/stage-hooks.d.ts.map +1 -1
- package/dist/op/stage-hooks.js +7 -5
- package/dist/op/workspace-run-env.d.ts +22 -0
- package/dist/op/workspace-run-env.d.ts.map +1 -0
- package/dist/op/workspace-run-env.js +21 -0
- package/dist/pr-forge.d.ts +24 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +47 -0
- package/dist/pr-post.d.ts +42 -0
- package/dist/pr-post.d.ts.map +1 -1
- package/dist/pr-post.js +67 -1
- package/dist/read-filter.d.ts +65 -0
- package/dist/read-filter.d.ts.map +1 -0
- package/dist/read-filter.js +179 -0
- package/dist/run-event.d.ts +1 -1
- package/dist/run-event.d.ts.map +1 -1
- package/dist/terraform/carve.d.ts +24 -3
- package/dist/terraform/carve.d.ts.map +1 -1
- package/dist/terraform/carve.js +32 -14
- package/dist/terraform/cuts.d.ts +97 -0
- package/dist/terraform/cuts.d.ts.map +1 -0
- package/dist/terraform/cuts.js +138 -0
- package/dist/terraform/folds.d.ts +26 -0
- package/dist/terraform/folds.d.ts.map +1 -0
- package/dist/terraform/folds.js +38 -0
- package/dist/terraform/graph.d.ts.map +1 -1
- package/dist/terraform/graph.js +10 -0
- package/dist/terraform/reach.d.ts +140 -0
- package/dist/terraform/reach.d.ts.map +1 -0
- package/dist/terraform/reach.js +170 -0
- package/dist/terraform/score.d.ts +14 -0
- package/dist/terraform/score.d.ts.map +1 -1
- package/dist/terraform/score.js +14 -31
- package/dist/terraform/state.d.ts.map +1 -1
- package/dist/terraform/state.js +4 -2
- package/dist/terraform/types.d.ts +8 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/dist/wave-blocked.d.ts +10 -1
- package/dist/wave-blocked.d.ts.map +1 -1
- package/dist/wave-blocked.js +31 -3
- package/dist/wave-runtime.mjs +263 -254
- package/dist/workspace/check.schema.json +11 -2
- package/dist/workspace/checks/ci-branch.d.ts +44 -0
- package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
- package/dist/workspace/checks/ci-branch.js +133 -0
- package/dist/workspace/checks/links.d.ts +3 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/links.js +12 -1
- package/dist/workspace/checks/org.d.ts +48 -0
- package/dist/workspace/checks/org.d.ts.map +1 -0
- package/dist/workspace/checks/org.js +156 -0
- package/dist/workspace/checks.d.ts +11 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +59 -7
- package/dist/workspace/ci-branches.d.ts +16 -0
- package/dist/workspace/ci-branches.d.ts.map +1 -1
- package/dist/workspace/ci-branches.js +27 -0
- package/dist/workspace/ci-green-cli.js +7 -7
- package/dist/workspace/ci-green-workflow.d.ts +1 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +13 -8
- package/dist/workspace/ci-green.d.ts +14 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +44 -11
- package/dist/workspace/ci-run.d.ts +9 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +67 -6
- package/dist/workspace/ci-serve.d.ts +9 -0
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +114 -76
- package/dist/workspace/compose-graph.d.ts +14 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +4 -1
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/composites.js +5 -2
- package/dist/workspace/composites.schema.json +22 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/dist/workspace/cross-root-lint.d.ts +56 -0
- package/dist/workspace/cross-root-lint.d.ts.map +1 -0
- package/dist/workspace/cross-root-lint.js +111 -0
- package/dist/workspace/dbt-models-reader.d.ts +44 -0
- package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
- package/dist/workspace/dbt-models-reader.js +356 -0
- package/dist/workspace/dbt-project-common.d.ts +40 -0
- package/dist/workspace/dbt-project-common.d.ts.map +1 -0
- package/dist/workspace/dbt-project-common.js +106 -0
- package/dist/workspace/dbt-project-reader.d.ts +4 -3
- package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
- package/dist/workspace/dbt-project-reader.js +14 -108
- package/dist/workspace/decision-rank.d.ts +26 -0
- package/dist/workspace/decision-rank.d.ts.map +1 -0
- package/dist/workspace/decision-rank.js +16 -0
- package/dist/workspace/declaration.d.ts +43 -2
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +45 -11
- package/dist/workspace/declaration.schema.json +133 -24
- package/dist/workspace/environments.d.ts +26 -2
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/environments.js +26 -2
- package/dist/workspace/flyway-dir.d.ts +39 -0
- package/dist/workspace/flyway-dir.d.ts.map +1 -0
- package/dist/workspace/flyway-dir.js +104 -0
- package/dist/workspace/flyway-layout.d.ts +96 -0
- package/dist/workspace/flyway-layout.d.ts.map +1 -0
- package/dist/workspace/flyway-layout.js +308 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +26 -8
- package/dist/workspace/graph.schema.json +34 -0
- package/dist/workspace/intent.d.ts +2 -3
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +5 -9
- package/dist/workspace/kind-link-readers.d.ts +59 -2
- package/dist/workspace/kind-link-readers.d.ts.map +1 -1
- package/dist/workspace/kind-link-readers.js +17 -1
- package/dist/workspace/kinds.d.ts +66 -2
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +98 -4
- package/dist/workspace/ledger.schema.json +158 -0
- package/dist/workspace/lexicon-outputs.d.ts +20 -1
- package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
- package/dist/workspace/lexicon-outputs.js +102 -10
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-check.js +23 -4
- package/dist/workspace/links.d.ts +8 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/links.js +25 -1
- package/dist/workspace/ls.d.ts +8 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +7 -1
- package/dist/workspace/ls.schema.json +7 -0
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +54 -5
- package/dist/workspace/migrate-links.d.ts +45 -0
- package/dist/workspace/migrate-links.d.ts.map +1 -0
- package/dist/workspace/migrate-links.js +100 -0
- package/dist/workspace/migration-dirs.d.ts +74 -0
- package/dist/workspace/migration-dirs.d.ts.map +1 -0
- package/dist/workspace/migration-dirs.js +254 -0
- package/dist/workspace/reason-codes.d.ts +8 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +11 -0
- package/dist/workspace/records-cli.d.ts +11 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-cli.js +57 -5
- package/dist/workspace/records.schema.json +5 -0
- package/dist/workspace/release-candidates.d.ts +2 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -1
- package/dist/workspace/release-candidates.js +5 -2
- package/dist/workspace/release-train.d.ts +53 -0
- package/dist/workspace/release-train.d.ts.map +1 -0
- package/dist/workspace/release-train.js +134 -0
- package/dist/workspace/release-workflow.d.ts.map +1 -1
- package/dist/workspace/release-workflow.js +4 -3
- package/dist/workspace/repo-ref.d.ts +45 -0
- package/dist/workspace/repo-ref.d.ts.map +1 -0
- package/dist/workspace/repo-ref.js +114 -0
- package/dist/workspace/run-event.schema.json +2 -2
- package/dist/workspace/runs-cli.d.ts +9 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs-cli.js +48 -3
- package/dist/workspace/runs.schema.json +7 -0
- package/dist/workspace/search-contract.d.ts +1 -1
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/status-decisions.d.ts +37 -0
- package/dist/workspace/status-decisions.d.ts.map +1 -0
- package/dist/workspace/status-decisions.js +147 -0
- package/dist/workspace/status-gates.d.ts +9 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +4 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +14 -2
- package/dist/workspace/status.schema.json +6 -0
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-cli.js +5 -3
- package/dist/workspace/work-evidence-ops.d.ts +83 -0
- package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
- package/dist/workspace/work-evidence-ops.js +169 -0
- package/dist/workspace/work-evidence.d.ts +12 -2
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-evidence.js +21 -3
- package/dist/workspace/work-evidence.schema.json +49 -2
- package/dist/workspace/workspace-gates.d.ts +110 -0
- package/dist/workspace/workspace-gates.d.ts.map +1 -0
- package/dist/workspace/workspace-gates.js +140 -0
- package/dist/workspace/workspace-kinds.schema.json +58 -3
- package/dist/workspace/workspace-ledger.d.ts +76 -0
- package/dist/workspace/workspace-ledger.d.ts.map +1 -0
- package/dist/workspace/workspace-ledger.js +91 -0
- package/dist/workspace/workspace-run.d.ts +112 -0
- package/dist/workspace/workspace-run.d.ts.map +1 -0
- package/dist/workspace/workspace-run.js +432 -0
- package/package.json +1 -1
- package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/src/__golden__/wave-loop/op-cost-member.json +13 -0
- package/src/__golden__/wave-loop/op-denied-source.json +16 -0
- package/src/__golden__/wave-loop/op-denied.json +16 -0
- package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/src/__golden__/wave-loop/op-held-on.json +16 -0
- package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/src/__golden__/wave-loop/op-overridden.json +16 -0
- package/src/__golden__/wave-loop/op-per-run.json +20 -0
- package/src/__golden__/wave-loop/op-point.json +13 -0
- package/src/__golden__/wave-loop/op-pr-review.json +10 -0
- package/src/__golden__/wave-loop/op-sealed.json +10 -0
- package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/src/__golden__/wave-loop/op-spend.json +10 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/src/__golden__/wave-loop/op-wide.json +39 -0
- package/src/action-pins.test.ts +78 -0
- package/src/action-pins.ts +92 -0
- package/src/cli/commands/carve-score-once.test.ts +99 -0
- package/src/cli/commands/carve.ts +37 -6
- package/src/cli/commands/check-lexicon.test.ts +2 -2
- package/src/cli/commands/import-report.ts +71 -0
- package/src/cli/commands/import.ts +33 -3
- package/src/cli/commands/migrate-dialect.test.ts +145 -0
- package/src/cli/commands/migrate.ts +132 -11
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
- package/src/cli/handlers/approve.ts +23 -2
- package/src/cli/handlers/build.ts +7 -1
- package/src/cli/handlers/ledger.ts +54 -0
- package/src/cli/handlers/migrate.ts +21 -12
- package/src/cli/handlers/misc.ts +10 -1
- package/src/cli/handlers/pr-env.ts +2 -2
- package/src/cli/handlers/pr.ts +138 -36
- package/src/cli/handlers/query.ts +16 -4
- package/src/cli/handlers/release.ts +6 -3
- package/src/cli/handlers/search.ts +13 -34
- package/src/cli/main.test.ts +15 -0
- package/src/cli/main.ts +65 -14
- package/src/cli/parse-args.ts +27 -2
- package/src/cli/registry.ts +14 -4
- package/src/components/builders.ts +8 -0
- package/src/components/capability-plugin-loader.ts +13 -1
- package/src/components/capability-plugin.ts +16 -0
- package/src/components/pr-hooks.test.ts +73 -5
- package/src/components/pr-hooks.ts +95 -5
- package/src/components/pr-pipeline.ts +64 -16
- package/src/components/pr-policy.test.ts +64 -1
- package/src/components/pr-policy.ts +53 -0
- package/src/components/registry.test.ts +1 -1
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +2 -0
- package/src/components/verbs/publish-artifact.test.ts +60 -0
- package/src/components/verbs/publish-artifact.ts +122 -0
- package/src/components/verbs/publish-image.test.ts +101 -0
- package/src/components/verbs/publish-image.ts +265 -0
- package/src/config-environments.ts +28 -1
- package/src/config.ts +8 -1
- package/src/gate-decision-bundle.test.ts +1 -0
- package/src/gate-decision-identity.test.ts +9 -0
- package/src/gate-decision.ts +26 -3
- package/src/graph-ir.ts +27 -0
- package/src/graph-walk.test.ts +44 -0
- package/src/graph-walk.ts +102 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +74 -6
- package/src/lifecycle/gate-ledger.ts +8 -0
- package/src/lifecycle/git.ts +2 -2
- package/src/lifecycle/ledger-read.test.ts +107 -0
- package/src/lifecycle/ledger-read.ts +195 -0
- package/src/lifecycle/release-workstation.ts +24 -0
- package/src/lifecycle/run-ledger.ts +5 -2
- package/src/lint/cross-root.test.ts +122 -0
- package/src/lint/discover.ts +3 -1
- package/src/lint/post-synth.ts +212 -2
- package/src/lint/rules/op/index.ts +2 -0
- package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
- package/src/op/activities/activity-contracts.ts +5 -2
- package/src/op/activities/index.ts +2 -2
- package/src/op/activities/review-agent.test.ts +221 -4
- package/src/op/activities/review-agent.ts +265 -22
- package/src/op/activities/work-evidence.ts +9 -1
- package/src/op/builders.ts +9 -5
- package/src/op/composites/apply-op.test.ts +108 -0
- package/src/op/composites/apply-op.ts +37 -2
- package/src/op/gate-approval.test.ts +6 -1
- package/src/op/gate-approval.ts +12 -6
- package/src/op/gate-quorum-at-base.test.ts +211 -0
- package/src/op/gate-quorum.ts +156 -8
- package/src/op/gate-review.ts +14 -0
- package/src/op/gate.ts +67 -8
- package/src/op/index.ts +3 -2
- package/src/op/local-executor.ts +69 -9
- package/src/op/op-waves-depends-on.test.ts +81 -0
- package/src/op/op-waves-hook-targets.test.ts +4 -2
- package/src/op/op-waves-quorum-product.test.ts +158 -0
- package/src/op/op-waves-run.ts +288 -14
- package/src/op/op-waves.ts +143 -14
- package/src/op/runtime.ts +8 -0
- package/src/op/schedule-overdue.ts +33 -0
- package/src/op/stage-hooks.ts +13 -5
- package/src/op/workspace-run-env.ts +29 -0
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +47 -0
- package/src/pr-forge.ts +68 -0
- package/src/pr-post-open.e2e.test.ts +36 -0
- package/src/pr-post.test.ts +102 -3
- package/src/pr-post.ts +89 -1
- package/src/read-filter.test.ts +54 -0
- package/src/read-filter.ts +193 -0
- package/src/run-event.ts +1 -1
- package/src/stage-hooks-bundle.test.ts +1 -0
- package/src/terraform/carve.ts +52 -15
- package/src/terraform/cuts.test.ts +173 -0
- package/src/terraform/cuts.ts +188 -0
- package/src/terraform/folds.ts +44 -0
- package/src/terraform/graph.ts +9 -0
- package/src/terraform/radius.test.ts +90 -0
- package/src/terraform/reach.test.ts +146 -0
- package/src/terraform/reach.ts +247 -0
- package/src/terraform/score.ts +27 -29
- package/src/terraform/state.ts +3 -1
- package/src/terraform/types.ts +8 -0
- package/src/wave-blocked.ts +28 -3
- package/src/wave-loop-golden.test.ts +4 -3
- package/src/workspace/behold-kinds.test.ts +1 -1
- package/src/workspace/check-contract.test.ts +7 -6
- package/src/workspace/check.schema.json +11 -2
- package/src/workspace/checks/ci-branch.test.ts +128 -0
- package/src/workspace/checks/ci-branch.ts +141 -0
- package/src/workspace/checks/links.ts +12 -1
- package/src/workspace/checks/org.ts +169 -0
- package/src/workspace/checks.ts +64 -7
- package/src/workspace/ci-branches.ts +31 -0
- package/src/workspace/ci-green-branches.test.ts +153 -0
- package/src/workspace/ci-green-cli.ts +7 -7
- package/src/workspace/ci-green-workflow.test.ts +4 -3
- package/src/workspace/ci-green-workflow.ts +13 -9
- package/src/workspace/ci-green.ts +49 -12
- package/src/workspace/ci-run.test.ts +72 -1
- package/src/workspace/ci-run.ts +68 -8
- package/src/workspace/ci-serve.test.ts +95 -0
- package/src/workspace/ci-serve.ts +106 -65
- package/src/workspace/component-environments.test.ts +25 -2
- package/src/workspace/compose-graph.ts +9 -2
- package/src/workspace/composites.schema.json +22 -0
- package/src/workspace/composites.test.ts +39 -8
- package/src/workspace/composites.ts +6 -2
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/src/workspace/cross-root-lint.test.ts +90 -0
- package/src/workspace/cross-root-lint.ts +126 -0
- package/src/workspace/dbt-kind.test.ts +3 -2
- package/src/workspace/dbt-models-reader.ts +362 -0
- package/src/workspace/dbt-models.test.ts +248 -0
- package/src/workspace/dbt-project-common.ts +113 -0
- package/src/workspace/dbt-project-reader.ts +29 -112
- package/src/workspace/decision-rank.ts +40 -0
- package/src/workspace/declaration.schema.json +133 -24
- package/src/workspace/declaration.ts +84 -15
- package/src/workspace/environments.ts +40 -4
- package/src/workspace/flyway-dir.ts +113 -0
- package/src/workspace/flyway-layout.ts +337 -0
- package/src/workspace/graph-cli.ts +25 -10
- package/src/workspace/graph-contract.test.ts +4 -2
- package/src/workspace/graph.schema.json +34 -0
- package/src/workspace/identity.test.ts +14 -2
- package/src/workspace/intent.ts +6 -14
- package/src/workspace/kind-link-readers.ts +68 -3
- package/src/workspace/kinds.test.ts +12 -7
- package/src/workspace/kinds.ts +132 -7
- package/src/workspace/ledger.schema.json +158 -0
- package/src/workspace/lexicon-outputs.ts +132 -13
- package/src/workspace/lineage-check.ts +30 -7
- package/src/workspace/links.ts +24 -1
- package/src/workspace/ls-contract.test.ts +1 -1
- package/src/workspace/ls.schema.json +7 -0
- package/src/workspace/ls.ts +10 -1
- package/src/workspace/member-commands.ts +60 -2
- package/src/workspace/member-file-schema.test.ts +124 -0
- package/src/workspace/migrate-links.test.ts +99 -0
- package/src/workspace/migrate-links.ts +108 -0
- package/src/workspace/migration-dirs.test.ts +272 -0
- package/src/workspace/migration-dirs.ts +267 -0
- package/src/workspace/org-scope.test.ts +238 -0
- package/src/workspace/reason-codes.test.ts +6 -1
- package/src/workspace/reason-codes.ts +11 -0
- package/src/workspace/records-cli.ts +58 -5
- package/src/workspace/records.schema.json +5 -0
- package/src/workspace/release-candidates.ts +7 -2
- package/src/workspace/release-train.ts +157 -0
- package/src/workspace/release-workflow.ts +4 -3
- package/src/workspace/repo-ref.ts +113 -0
- package/src/workspace/run-event.schema.json +2 -2
- package/src/workspace/runs-cli.ts +48 -4
- package/src/workspace/runs.schema.json +7 -0
- package/src/workspace/status-contract.test.ts +46 -1
- package/src/workspace/status-decisions.test.ts +100 -0
- package/src/workspace/status-decisions.ts +151 -0
- package/src/workspace/status-gates.ts +9 -0
- package/src/workspace/status.schema.json +6 -0
- package/src/workspace/status.ts +17 -3
- package/src/workspace/where-filters.test.ts +39 -0
- package/src/workspace/work-cli.ts +5 -3
- package/src/workspace/work-evidence-ops.test.ts +176 -0
- package/src/workspace/work-evidence-ops.ts +189 -0
- package/src/workspace/work-evidence.schema.json +49 -2
- package/src/workspace/work-evidence.ts +22 -3
- package/src/workspace/workspace-gates.ts +200 -0
- package/src/workspace/workspace-kinds.schema.json +58 -3
- package/src/workspace/workspace-ledger.ts +130 -0
- package/src/workspace/workspace-run.test.ts +322 -0
- package/src/workspace/workspace-run.ts +461 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WSP152 (#4325): a member's generated pipeline targets the branch
|
|
3
|
+
* `ci.branches` routes the member to.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
11
|
+
import { runDeclarationChecks } from "../checks";
|
|
12
|
+
import { pipelineTargets, routeDifferences } from "./ci-branch";
|
|
13
|
+
import { REASONS } from "../reason-codes";
|
|
14
|
+
import { servedBranches } from "../ci-branches";
|
|
15
|
+
|
|
16
|
+
const scratch: string[] = [];
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
function repo(files: Record<string, string>): string {
|
|
21
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-ci-branch-")));
|
|
22
|
+
scratch.push(root);
|
|
23
|
+
execFileSync("git", ["init", "-q"], { cwd: root });
|
|
24
|
+
for (const [path, text] of Object.entries(files)) {
|
|
25
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
26
|
+
writeFileSync(join(root, path), text);
|
|
27
|
+
}
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A generated pull-request pipeline, as chant writes it for a target `branch` (`--branch` is on the record and apply commands only). */
|
|
32
|
+
const pipeline = (branch: string | null, triggers: string[] = branch === null ? [] : [branch]): string =>
|
|
33
|
+
[
|
|
34
|
+
"on:",
|
|
35
|
+
" pull_request:",
|
|
36
|
+
...(triggers.length > 0 ? [` branches: [${triggers.join(", ")}]`] : []),
|
|
37
|
+
" push:",
|
|
38
|
+
...(triggers.length > 0 ? [` branches: [${triggers.join(", ")}]`] : []),
|
|
39
|
+
"jobs:",
|
|
40
|
+
" plan:",
|
|
41
|
+
" steps:",
|
|
42
|
+
" - run: chant ci run plan --in-place --no-push --env prod --gate pr-apply --forge github",
|
|
43
|
+
" record:",
|
|
44
|
+
" steps:",
|
|
45
|
+
` - run: chant ci run record --in-place --env prod --gate pr-apply --forge github${branch === null ? "" : ` --branch "${branch}"`}`,
|
|
46
|
+
"",
|
|
47
|
+
].join("\n");
|
|
48
|
+
|
|
49
|
+
const PIPELINE_PATH = ".github/workflows/chant-pr.app.prod.yml";
|
|
50
|
+
const record = JSON.stringify({ schema: 1, files: [{ path: PIPELINE_PATH, command: "chant build" }] });
|
|
51
|
+
const workspace = (branches: Record<string, string[]> | undefined, text: string, members = ["app"]): Record<string, string> => ({
|
|
52
|
+
"chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, members: members.map((name) => ({ name, dir: name, kind: "other", because: name })), ...(branches ? { ci: { branches } } : {}) }),
|
|
53
|
+
...Object.fromEntries(members.map((m) => [`${m}/.chant/generated.json`, record])),
|
|
54
|
+
[PIPELINE_PATH]: text,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const found = async (root: string) =>
|
|
58
|
+
(await runDeclarationChecks(root, (f) => f, { gather: false })).diagnostics.filter((d) => d.ruleId === "WSP152").map((d) => [d.code, d.entity ?? null, d.severity]);
|
|
59
|
+
|
|
60
|
+
describe("what a pipeline targets", () => {
|
|
61
|
+
test("reads the --branch of each record and apply command, and the trigger branch lists", () => {
|
|
62
|
+
expect(pipelineTargets(pipeline("release"))).toEqual({
|
|
63
|
+
jobs: [{ line: 12, branch: "release" }],
|
|
64
|
+
triggers: [
|
|
65
|
+
{ trigger: "push", branches: ["release"] },
|
|
66
|
+
{ trigger: "pull_request", branches: ["release"] },
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
expect(pipelineTargets(pipeline(null)).jobs).toEqual([{ line: 10, branch: null }]);
|
|
70
|
+
expect(pipelineTargets("not: [valid").triggers).toEqual([]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("a routed member's pipeline names its route in both places; an unrouted one does not name a reserved branch", () => {
|
|
74
|
+
const listed = ["release"];
|
|
75
|
+
expect(routeDifferences(pipelineTargets(pipeline("release")), "release", listed, "app")).toEqual([]);
|
|
76
|
+
expect(routeDifferences(pipelineTargets(pipeline("main")), "release", listed, "app")).toHaveLength(3);
|
|
77
|
+
expect(routeDifferences(pipelineTargets(pipeline(null)), "release", listed, "app")).toEqual([expect.stringContaining("the default branch")]);
|
|
78
|
+
expect(routeDifferences(pipelineTargets(pipeline("release", ["release", "main"])), "release", listed, "app")).toHaveLength(2);
|
|
79
|
+
expect(routeDifferences(pipelineTargets(pipeline("main")), null, listed, "app")).toEqual([]);
|
|
80
|
+
expect(routeDifferences(pipelineTargets(pipeline("release")), null, listed, "app")).toHaveLength(3);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("WSP152 over a workspace", () => {
|
|
85
|
+
test("a pipeline on the route passes, and a workspace with no ci.branches is not read", async () => {
|
|
86
|
+
expect(await found(repo(workspace({ release: ["app"] }, pipeline("release"))))).toEqual([]);
|
|
87
|
+
expect(await found(repo(workspace(undefined, pipeline("anything"))))).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("a pipeline generated before the route changed fails", async () => {
|
|
91
|
+
const root = repo(workspace({ release: ["app"] }, pipeline("main")));
|
|
92
|
+
expect(await found(root)).toEqual([
|
|
93
|
+
["ci-branch-route-differs", "app", "error"],
|
|
94
|
+
["ci-branch-route-differs", "app", "error"],
|
|
95
|
+
["ci-branch-route-differs", "app", "error"],
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("an unrouted member's pipeline may not target a branch ci.branches reserves", async () => {
|
|
100
|
+
expect(await found(repo(workspace({ release: ["other"] }, pipeline("release"))))).toHaveLength(3);
|
|
101
|
+
expect(await found(repo(workspace({ release: ["other"] }, pipeline("main"))))).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("the code is a documented reason code", () => {
|
|
105
|
+
expect(REASONS["ci-branch-route-differs"]).toMatch(/targets a branch other than/);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("the branches ci serve polls (#4325)", () => {
|
|
110
|
+
const branches = { release: ["prod-*"], staging: ["stg-*"] };
|
|
111
|
+
|
|
112
|
+
test("a routed member polls its route alone, and a branch that disagrees is refused", () => {
|
|
113
|
+
expect(servedBranches(branches, "prod-eu", "main", false)).toEqual({ served: ["release"], routed: ["release"] });
|
|
114
|
+
expect(servedBranches(branches, "prod-eu", "release", true).served).toEqual(["release"]);
|
|
115
|
+
expect(() => servedBranches(branches, "prod-eu", "main", true)).toThrow(/routes member prod-eu to release/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("an unrouted member polls the default branch, and not one reserved for other members", () => {
|
|
119
|
+
expect(servedBranches(branches, "docs", "main", false)).toEqual({ served: ["main"], routed: [] });
|
|
120
|
+
expect(() => servedBranches(branches, "docs", "release", true)).toThrow(/member docs is not one/);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("with no member, the default branch and then each routed branch", () => {
|
|
124
|
+
expect(servedBranches(branches, undefined, "main", false)).toEqual({ served: ["main", "release", "staging"], routed: ["release", "staging"] });
|
|
125
|
+
expect(servedBranches(undefined, undefined, "main", false)).toEqual({ served: ["main"], routed: [] });
|
|
126
|
+
expect(servedBranches(branches, undefined, "release", true).served).toEqual(["release", "staging"]);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WSP152 (#4325): a member's generated pull-request pipeline targets the
|
|
3
|
+
* branch `ci.branches` routes the member to.
|
|
4
|
+
*
|
|
5
|
+
* `chant build --pr-loop` refuses a branch that disagrees with the route
|
|
6
|
+
* (../member-pipeline.ts), but only when it writes the file. A route changed
|
|
7
|
+
* after the pipeline was generated, or a pipeline edited by hand, goes
|
|
8
|
+
* unnoticed until a push to the wrong branch applies nothing. This check
|
|
9
|
+
* reads the files each member's generated-file record lists and compares what
|
|
10
|
+
* they target with the route. It reads files and never runs them.
|
|
11
|
+
*
|
|
12
|
+
* A pipeline names its target in two places: the `--branch` its record and
|
|
13
|
+
* apply commands carry, and the `branches` of its `push` and `pull_request`
|
|
14
|
+
* triggers. A routed member's pipeline names the route in both. A member with
|
|
15
|
+
* no route may name a branch `ci.branches` does not list, and not one it
|
|
16
|
+
* lists, since that branch applies only the members its globs match.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
|
|
20
|
+
import type { Member } from "../declaration";
|
|
21
|
+
import { routedBranch } from "../ci-branches";
|
|
22
|
+
import { GENERATED_RECORD_FILE } from "../member-pipeline";
|
|
23
|
+
import { joinPath } from "../tree";
|
|
24
|
+
import { parseYAMLDocument } from "../../yaml";
|
|
25
|
+
import { read } from "./security";
|
|
26
|
+
|
|
27
|
+
export const WSP_CI_BRANCH_ROUTE = "WSP152";
|
|
28
|
+
|
|
29
|
+
/** The finding code WSP152 reports. */
|
|
30
|
+
export const CI_BRANCH_FINDING_CODES = ["ci-branch-route-differs"] as const;
|
|
31
|
+
|
|
32
|
+
const memberDir = (dir: string) => (dir === "." ? "" : dir);
|
|
33
|
+
|
|
34
|
+
/** A record or apply command of the pull-request loop: `ci run record|apply`, or `components pr-record|pr-apply`. */
|
|
35
|
+
const JOB = /\bci\s+run\s+(?:record|apply)\b|\bcomponents\s+pr-(?:record|apply)\b/;
|
|
36
|
+
const BRANCH_FLAG = /--branch(?:\s+|=)(?:"([^"]+)"|'([^']+)'|(\S+))/;
|
|
37
|
+
|
|
38
|
+
/** What a pipeline file says it targets. */
|
|
39
|
+
export interface PipelineTargets {
|
|
40
|
+
/** The `--branch` of each record or apply command, by line; null for a command with none. */
|
|
41
|
+
jobs: { line: number; branch: string | null }[];
|
|
42
|
+
/** The branches its `push` and `pull_request` triggers list, by trigger. Absent for a trigger with no list. */
|
|
43
|
+
triggers: { trigger: "push" | "pull_request"; branches: string[] }[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The branches a pipeline file targets, read from its text. */
|
|
47
|
+
export function pipelineTargets(text: string): PipelineTargets {
|
|
48
|
+
const jobs: PipelineTargets["jobs"] = [];
|
|
49
|
+
text.split("\n").forEach((line, i) => {
|
|
50
|
+
if (!JOB.test(line)) return;
|
|
51
|
+
const m = BRANCH_FLAG.exec(line);
|
|
52
|
+
jobs.push({ line: i + 1, branch: m ? (m[1] ?? m[2] ?? m[3] ?? null) : null });
|
|
53
|
+
});
|
|
54
|
+
const triggers: PipelineTargets["triggers"] = [];
|
|
55
|
+
try {
|
|
56
|
+
const doc = parseYAMLDocument(text) as { on?: unknown; true?: unknown } | null;
|
|
57
|
+
// YAML 1.1 reads a bare `on` key as true.
|
|
58
|
+
const on = doc && typeof doc === "object" ? (doc.on ?? doc.true) : undefined;
|
|
59
|
+
if (on && typeof on === "object" && !Array.isArray(on)) {
|
|
60
|
+
for (const trigger of ["push", "pull_request"] as const) {
|
|
61
|
+
const t = (on as Record<string, unknown>)[trigger];
|
|
62
|
+
const branches = t && typeof t === "object" ? (t as { branches?: unknown }).branches : undefined;
|
|
63
|
+
if (Array.isArray(branches)) triggers.push({ trigger, branches: branches.filter((b): b is string => typeof b === "string") });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// A file that is not YAML has no triggers to compare; its commands still are.
|
|
68
|
+
}
|
|
69
|
+
return { jobs, triggers };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* What differs between `targets` and the branch `own` the member is routed to
|
|
74
|
+
* (null when it has no route), as sentences. `listed` is every branch
|
|
75
|
+
* `ci.branches` names.
|
|
76
|
+
*/
|
|
77
|
+
export function routeDifferences(targets: PipelineTargets, own: string | null, listed: readonly string[], member: string): string[] {
|
|
78
|
+
const out: string[] = [];
|
|
79
|
+
const said = (b: string | null): string => (b === null ? "the default branch (it passes no --branch)" : b);
|
|
80
|
+
for (const j of targets.jobs) {
|
|
81
|
+
if (own !== null && j.branch !== own) out.push(`line ${j.line} targets ${said(j.branch)}, and ci.branches routes member ${member} to ${own}`);
|
|
82
|
+
else if (own === null && j.branch !== null && listed.includes(j.branch)) out.push(`line ${j.line} targets ${j.branch}, which ci.branches reserves for the members its globs match, and member ${member} is not one`);
|
|
83
|
+
}
|
|
84
|
+
for (const t of targets.triggers) {
|
|
85
|
+
if (own !== null && !(t.branches.length === 1 && t.branches[0] === own)) out.push(`its ${t.trigger} trigger lists ${t.branches.join(", ") || "no branch"}, and ci.branches routes member ${member} to ${own}`);
|
|
86
|
+
else if (own === null) {
|
|
87
|
+
const reserved = t.branches.filter((b) => listed.includes(b));
|
|
88
|
+
if (reserved.length > 0) out.push(`its ${t.trigger} trigger lists ${reserved.join(", ")}, which ci.branches reserves for the members its globs match, and member ${member} is not one`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The files a member's generated-file record lists, which are pipelines the check can read. */
|
|
95
|
+
function recordedFiles(ctx: Parameters<WorkspaceCheck["check"]>[0], member: Member): string[] {
|
|
96
|
+
const text = read(ctx.tree, joinPath(memberDir(member.dir), GENERATED_RECORD_FILE));
|
|
97
|
+
if (text === null) return [];
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(text) as { files?: { path?: unknown }[] };
|
|
100
|
+
return (parsed.files ?? []).flatMap((f) => (typeof f.path === "string" && /\.ya?ml$/.test(f.path) ? [f.path] : []));
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const CI_BRANCH_CHECKS: readonly WorkspaceCheck[] = [
|
|
107
|
+
{
|
|
108
|
+
id: WSP_CI_BRANCH_ROUTE,
|
|
109
|
+
name: "ci-branch-route",
|
|
110
|
+
description: "A member's generated pull-request pipeline targets the branch ci.branches routes the member to, in its record and apply commands and its push and pull_request triggers.",
|
|
111
|
+
severity: "error",
|
|
112
|
+
configurable: true,
|
|
113
|
+
check(ctx) {
|
|
114
|
+
const branches = ctx.declaration.ci?.branches;
|
|
115
|
+
if (!branches || Object.keys(branches).length === 0) return [];
|
|
116
|
+
const listed = Object.keys(branches);
|
|
117
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
118
|
+
for (const member of ctx.declaration.members) {
|
|
119
|
+
const own = routedBranch(branches, member.name);
|
|
120
|
+
for (const path of recordedFiles(ctx, member)) {
|
|
121
|
+
const text = read(ctx.tree, path);
|
|
122
|
+
if (text === null) continue;
|
|
123
|
+
for (const why of routeDifferences(pipelineTargets(text), own, listed, member.name)) {
|
|
124
|
+
out.push({
|
|
125
|
+
checkId: this.id,
|
|
126
|
+
severity: this.severity,
|
|
127
|
+
code: CI_BRANCH_FINDING_CODES[0],
|
|
128
|
+
message: `WSP152: ${path}: ${why}; regenerate it with chant build --components --generate <forge> --pr-loop`,
|
|
129
|
+
pointer: "/ci/branches",
|
|
130
|
+
entity: member.name,
|
|
131
|
+
treeFile: path,
|
|
132
|
+
line: 1,
|
|
133
|
+
column: 1,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
];
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
* Links a kind's reader finds in a member's own files (#3932), such as a dbt
|
|
23
23
|
* project's sources, are checked by WSP093 to WSP095 like declared ones, and
|
|
24
24
|
* their findings name the member's file and line instead of the declaration.
|
|
25
|
+
* Links a member's lexicon reads from its declarations (#4397), such as the
|
|
26
|
+
* tables an otel `clickhouse` exporter writes, are checked the same way, with
|
|
27
|
+
* the reader named `lexicon <name>`.
|
|
25
28
|
*/
|
|
26
29
|
|
|
27
30
|
import type { WorkspaceCheck, WorkspaceCheckContext, WorkspaceDiagnostic } from "../checks";
|
|
@@ -33,11 +36,19 @@ import { DEFAULT_LINK_KIND, LINK_KINDS, TELEMETRY_LINK_KIND, linkTargetProblem,
|
|
|
33
36
|
const tables = new WeakMap<WorkspaceCheckContext, LinkTableRow[]>();
|
|
34
37
|
const reads = new WeakMap<WorkspaceCheckContext, Map<string, KindLinkRead>>();
|
|
35
38
|
|
|
36
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* What each member's kind reader found (#3932), and the links its lexicons
|
|
41
|
+
* read from its declarations (#4397), read once per check run.
|
|
42
|
+
*/
|
|
37
43
|
function kindReads(ctx: WorkspaceCheckContext): Map<string, KindLinkRead> {
|
|
38
44
|
let read = reads.get(ctx);
|
|
39
45
|
if (!read) {
|
|
40
46
|
read = readKindLinks(ctx.declaration, ctx.tree, ctx.kinds);
|
|
47
|
+
for (const [member, lexicon] of ctx.facts?.lexiconLinks ?? []) {
|
|
48
|
+
const prev = read.get(member);
|
|
49
|
+
// resolveLinks names one reader per member; the lexicon's links keep their file and line.
|
|
50
|
+
read.set(member, prev ? { ...prev, links: [...prev.links, ...lexicon.links], gaps: [...prev.gaps, ...lexicon.gaps] } : lexicon);
|
|
51
|
+
}
|
|
41
52
|
reads.set(ctx, read);
|
|
42
53
|
}
|
|
43
54
|
return read;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org scope (#3168): members of kind `repo`, which name another repository's
|
|
3
|
+
* workspace by URL and pinned revision, and the org-level workspace that
|
|
4
|
+
* holds only such members.
|
|
5
|
+
*
|
|
6
|
+
* | Id | Check |
|
|
7
|
+
* |---|---|
|
|
8
|
+
* | WSP013 | an org workspace holds only repo members |
|
|
9
|
+
* | WSP014 | every repository the forge lists is declared (with `--forge-repos`) |
|
|
10
|
+
* | WSP015 | every declared repository is one the forge lists (with `--forge-repos`) |
|
|
11
|
+
* | WSP016 | no repo member's URL carries a password or token |
|
|
12
|
+
*
|
|
13
|
+
* The forge's list is input, never fetched here: a warden enumerates the
|
|
14
|
+
* org's repositories from its own API and hands the list to
|
|
15
|
+
* `chant workspace check --forge-repos <file>`, so the check stays offline.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { isAbsolute, resolve } from "node:path";
|
|
19
|
+
import { REPO_KIND } from "../kinds";
|
|
20
|
+
import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
|
|
21
|
+
|
|
22
|
+
/** One repository the forge lists. */
|
|
23
|
+
export interface ForgeRepo {
|
|
24
|
+
url: string;
|
|
25
|
+
/** An archived repository need not be declared, and declaring one is not a drop. */
|
|
26
|
+
archived: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The forge's repository list for `--forge-repos` (#3168), as a warden writes it. */
|
|
30
|
+
export interface ForgeRepoList {
|
|
31
|
+
/** The file it was read from, for messages. */
|
|
32
|
+
source: string;
|
|
33
|
+
repos: ForgeRepo[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse a forge repository list. Three shapes are read: `{ "repos": [...] }`
|
|
38
|
+
* (a warden may add `forge` and `org` beside it), a bare array, and, for each
|
|
39
|
+
* repository, a URL string or `{ "url", "archived"? }`. Throws an Error that
|
|
40
|
+
* says what is wrong.
|
|
41
|
+
*/
|
|
42
|
+
export function parseForgeRepos(text: string, source: string): ForgeRepoList {
|
|
43
|
+
let raw: unknown;
|
|
44
|
+
try {
|
|
45
|
+
raw = JSON.parse(text);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
throw new Error(`${source} is not JSON (${(err as Error).message})`);
|
|
48
|
+
}
|
|
49
|
+
const list = Array.isArray(raw) ? raw : raw !== null && typeof raw === "object" && Array.isArray((raw as { repos?: unknown }).repos) ? (raw as { repos: unknown[] }).repos : undefined;
|
|
50
|
+
if (!list) throw new Error(`${source} is neither an array of repositories nor an object with a repos array`);
|
|
51
|
+
const repos = list.map((r, i): ForgeRepo => {
|
|
52
|
+
if (typeof r === "string" && r.length > 0) return { url: r, archived: false };
|
|
53
|
+
if (r !== null && typeof r === "object" && typeof (r as { url?: unknown }).url === "string") {
|
|
54
|
+
return { url: (r as { url: string }).url, archived: (r as { archived?: unknown }).archived === true };
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`${source}: repos[${i}] is neither a URL nor an object with a url`);
|
|
57
|
+
});
|
|
58
|
+
return { source, repos };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A repository URL reduced to what names the repository, so the forge's
|
|
63
|
+
* https URL and a declaration's ssh or scp-style URL compare equal:
|
|
64
|
+
* `host/owner/repo`, lowercased, with no scheme, user, port, `.git` or
|
|
65
|
+
* trailing slash. A local path or file URL becomes its absolute path,
|
|
66
|
+
* resolved from `base`.
|
|
67
|
+
*/
|
|
68
|
+
export function repoKey(url: string, base: string = process.cwd()): string {
|
|
69
|
+
const trimmed = url.trim();
|
|
70
|
+
const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/\/)(.+)$/.exec(trimmed);
|
|
71
|
+
let hostPath: string | undefined;
|
|
72
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
|
|
73
|
+
let u: URL;
|
|
74
|
+
try {
|
|
75
|
+
u = new URL(trimmed);
|
|
76
|
+
} catch {
|
|
77
|
+
return trimmed.toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
if (u.protocol === "file:") return stripGit(decodeURIComponent(u.pathname));
|
|
80
|
+
hostPath = `${u.hostname}${u.pathname}`;
|
|
81
|
+
} else if (scp && !isAbsolute(trimmed) && !/^[a-z]:[\\/]/i.test(trimmed)) {
|
|
82
|
+
hostPath = `${scp[1]}/${scp[2]}`;
|
|
83
|
+
} else {
|
|
84
|
+
return stripGit(resolve(base, trimmed));
|
|
85
|
+
}
|
|
86
|
+
return stripGit(hostPath.replace(/\/+/g, "/")).toLowerCase();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function stripGit(path: string): string {
|
|
90
|
+
return path.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Whether a URL carries a password or token, which a declaration must never hold. */
|
|
94
|
+
export function urlCredential(url: string): boolean {
|
|
95
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) return false;
|
|
96
|
+
try {
|
|
97
|
+
const u = new URL(url);
|
|
98
|
+
// ssh://git@host is a user name, not a secret; a password, or any user on http(s), is one.
|
|
99
|
+
return u.password !== "" || (u.username !== "" && /^https?:$/.test(u.protocol));
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const repoMembers = (ctx: Parameters<WorkspaceCheck["check"]>[0]) => ctx.declaration.members.filter((m) => m.kind === REPO_KIND && m.repo !== null);
|
|
106
|
+
|
|
107
|
+
function finding(check: WorkspaceCheck, message: string, pointer: string, entity?: string): WorkspaceDiagnostic {
|
|
108
|
+
return { checkId: check.id, severity: check.severity, message, pointer, ...(entity !== undefined ? { entity } : {}) };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const ORG_CHECKS: readonly WorkspaceCheck[] = [
|
|
112
|
+
{
|
|
113
|
+
id: "WSP013",
|
|
114
|
+
name: "org-member-local",
|
|
115
|
+
description: "An org workspace (scope org) holds only members of kind repo, each a reference to another repository's workspace (#3168).",
|
|
116
|
+
severity: "error",
|
|
117
|
+
configurable: false,
|
|
118
|
+
check(ctx) {
|
|
119
|
+
if (ctx.declaration.scope !== "org") return [];
|
|
120
|
+
return ctx.declaration.entries
|
|
121
|
+
.filter((e) => e.kind !== REPO_KIND)
|
|
122
|
+
.map((e) =>
|
|
123
|
+
finding(this, `${e.type === "group" ? "example group" : "member"} ${e.name} is kind ${e.kind}, and an org workspace holds only repo members; move it into a repository's own workspace and reference that`, `${e.pointer}/kind`, e.name),
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "WSP014",
|
|
129
|
+
name: "org-repo-undeclared",
|
|
130
|
+
description: "With --forge-repos, every repository the forge lists, archived ones aside, is named by a repo member (#3168).",
|
|
131
|
+
severity: "error",
|
|
132
|
+
configurable: true,
|
|
133
|
+
check(ctx) {
|
|
134
|
+
const forge = ctx.facts?.forgeRepos;
|
|
135
|
+
if (!forge) return [];
|
|
136
|
+
const declared = new Set(repoMembers(ctx).map((m) => repoKey(m.repo!.url)));
|
|
137
|
+
return forge.repos
|
|
138
|
+
.filter((r) => !r.archived && !declared.has(repoKey(r.url)))
|
|
139
|
+
.map((r) => finding(this, `the forge lists ${r.url} (${forge.source}), and no repo member names it; add a member of kind repo with its url and pinned rev`, "/members"));
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "WSP015",
|
|
144
|
+
name: "org-repo-dropped",
|
|
145
|
+
description: "With --forge-repos, every repository a repo member names is one the forge still lists (#3168).",
|
|
146
|
+
severity: "warning",
|
|
147
|
+
configurable: true,
|
|
148
|
+
check(ctx) {
|
|
149
|
+
const forge = ctx.facts?.forgeRepos;
|
|
150
|
+
if (!forge) return [];
|
|
151
|
+
const listed = new Set(forge.repos.map((r) => repoKey(r.url)));
|
|
152
|
+
return repoMembers(ctx)
|
|
153
|
+
.filter((m) => !listed.has(repoKey(m.repo!.url)))
|
|
154
|
+
.map((m) => finding(this, `member ${m.name} names ${m.repo!.url}, which the forge no longer lists (${forge.source}); remove the member or say why it stays`, `${m.pointer}/repo/url`, m.name));
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "WSP016",
|
|
159
|
+
name: "repo-url-credential",
|
|
160
|
+
description: "No repo member's URL carries a password or token: the declaration is committed, and git's own credential helpers supply access (#3168).",
|
|
161
|
+
severity: "error",
|
|
162
|
+
configurable: false,
|
|
163
|
+
check(ctx) {
|
|
164
|
+
return repoMembers(ctx)
|
|
165
|
+
.filter((m) => urlCredential(m.repo!.url))
|
|
166
|
+
.map((m) => finding(this, `member ${m.name}'s repo url carries a credential; name the repository without one and let git's credential helper supply access`, `${m.pointer}/repo/url`, m.name));
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
];
|
package/src/workspace/checks.ts
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
*
|
|
22
22
|
* | Ids | Checks |
|
|
23
23
|
* |---|---|
|
|
24
|
-
* | WSP001 to
|
|
24
|
+
* | WSP001 to WSP012 | the declaration and member kinds (this file), and a member's file against its kind's schema (#3672) |
|
|
25
|
+
* | WSP013 to WSP016 | org scope: repo members and the org workspace, checked against the forge's list with `--forge-repos` (#3168) |
|
|
25
26
|
* | WSP071, WSP072 | member ledgers |
|
|
26
27
|
* | WSP081 to WSP083 | recorded pipelines |
|
|
27
28
|
* | WSP091 to WSP099 | member links (#2539, #2558), and those a kind's reader finds in a member's files (#3932) |
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
* | WSP141, WSP142 | member links resolved against a live read, with `--live` (#2549) |
|
|
39
40
|
* | WSP131 to WSP133 | diagram artifacts: source and render exist, a recorded source hash still matches (#2764) |
|
|
40
41
|
* | WSP151 | what a member under `ci.workflow` still lacks (#4168 step 6) |
|
|
42
|
+
* | WSP152 | a member's generated pipeline targets the branch `ci.branches` routes it to (#4325) |
|
|
41
43
|
* | WSP161 to WSP165 | the security groups the declaration opts into: what each still lacks (#4168 step 7) |
|
|
42
44
|
*/
|
|
43
45
|
|
|
@@ -48,7 +50,7 @@ import type { PostSynthDiagnostic } from "../lint/post-synth";
|
|
|
48
50
|
import { readDeclaration, resolveGroups, WorkspaceReadError, type Declaration, type Entry, type ErrorLocation, type ResolvedGroup } from "./declaration";
|
|
49
51
|
import type { ReasonCode } from "./reason-codes";
|
|
50
52
|
import { parseJsonText, pointerToken, type TextLocation } from "./jsonc";
|
|
51
|
-
import { loadKindRegistry, probeKind, resolveKind, resolveMemberFields, type KindLoadProblem, type KindRegistry } from "./kinds";
|
|
53
|
+
import { checkMemberFile, loadKindRegistry, probeKind, REPO_KIND, resolveKind, resolveMemberFields, type KindLoadProblem, type KindRegistry } from "./kinds";
|
|
52
54
|
import { coreClassRegistry, loadClassRegistry, unknownScopeClasses, type ClassRegistry } from "./principal-classes";
|
|
53
55
|
import { gitTop, workingTree, type WorkspaceTree } from "./tree";
|
|
54
56
|
import { LINK_CHECKS, linkTable } from "./checks/links";
|
|
@@ -62,9 +64,12 @@ import { BOX_CHECKS } from "./checks/boxes";
|
|
|
62
64
|
import { DIAGRAM_CHECKS } from "./checks/diagrams";
|
|
63
65
|
import { SECURITY_CHECKS } from "./checks/security";
|
|
64
66
|
import { CI_WORKFLOW_CHECKS } from "./checks/ci-workflow";
|
|
67
|
+
import { CI_BRANCH_CHECKS } from "./checks/ci-branch";
|
|
68
|
+
import { ORG_CHECKS, type ForgeRepoList } from "./checks/org";
|
|
65
69
|
import { loadDeclaredKinds, type DeclaredKind } from "./declared-kinds";
|
|
66
70
|
import { resolveBoxIntents, type ResolvedBoxIntent } from "./box-intent";
|
|
67
|
-
import { gatherLexiconOutputs, type MemberLexiconOutputs } from "./lexicon-outputs";
|
|
71
|
+
import { gatherLexiconLinks, gatherLexiconOutputs, type MemberLexiconOutputs } from "./lexicon-outputs";
|
|
72
|
+
import type { KindLinkRead } from "./kind-link-readers";
|
|
68
73
|
|
|
69
74
|
/**
|
|
70
75
|
* What the checks beyond the declaration read, gathered from the checkout
|
|
@@ -88,6 +93,10 @@ export interface WorkspaceFacts {
|
|
|
88
93
|
boxIntents?: readonly ResolvedBoxIntent[];
|
|
89
94
|
/** The outputs linked members' lexicons report about their declarations, read by folding their source (#3931). */
|
|
90
95
|
lexiconOutputs?: ReadonlyMap<string, MemberLexiconOutputs>;
|
|
96
|
+
/** The links members' lexicons read from their declarations, such as an otel clickhouse exporter's tables (#4397). */
|
|
97
|
+
lexiconLinks?: ReadonlyMap<string, KindLinkRead>;
|
|
98
|
+
/** The forge's repository list, from `--forge-repos` (#3168). */
|
|
99
|
+
forgeRepos?: ForgeRepoList;
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
/** What every declaration check reads. */
|
|
@@ -174,9 +183,9 @@ function entryFinding(check: WorkspaceCheck, entry: Entry, field: string | null,
|
|
|
174
183
|
};
|
|
175
184
|
}
|
|
176
185
|
|
|
177
|
-
/** The dir exists and the kind is known: the members every probe check reads. */
|
|
186
|
+
/** The dir exists and the kind is known: the members every probe check reads. A repo member has no directory here (#3168). */
|
|
178
187
|
function readableMembers(ctx: WorkspaceCheckContext) {
|
|
179
|
-
return ctx.declaration.members.filter((m) => ctx.tree.stat(memberDir(m.dir)) === "dir" && ctx.kinds.get(m.kind) !== undefined);
|
|
188
|
+
return ctx.declaration.members.filter((m) => m.kind !== REPO_KIND && ctx.tree.stat(memberDir(m.dir)) === "dir" && ctx.kinds.get(m.kind) !== undefined);
|
|
180
189
|
}
|
|
181
190
|
|
|
182
191
|
/** The first id of the read failure, used by {@link runDeclarationChecks} when the declaration can't be read. */
|
|
@@ -240,7 +249,7 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
240
249
|
configurable: true,
|
|
241
250
|
check(ctx) {
|
|
242
251
|
return ctx.declaration.members
|
|
243
|
-
.filter((m) => ctx.tree.stat(memberDir(m.dir)) !== "dir")
|
|
252
|
+
.filter((m) => m.kind !== REPO_KIND && ctx.tree.stat(memberDir(m.dir)) !== "dir")
|
|
244
253
|
.map((m) => entryFinding(this, m, "dir", `member ${m.name}'s directory ${m.dir} does not exist${ctx.tree.label}`));
|
|
245
254
|
},
|
|
246
255
|
},
|
|
@@ -359,6 +368,43 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
359
368
|
return out;
|
|
360
369
|
},
|
|
361
370
|
},
|
|
371
|
+
{
|
|
372
|
+
id: "WSP012",
|
|
373
|
+
name: "member-file-invalid",
|
|
374
|
+
description: "Every member of a kind that names a member file has that file, and it passes the kind's JSON Schema (#3672).",
|
|
375
|
+
severity: "error",
|
|
376
|
+
configurable: true,
|
|
377
|
+
check(ctx) {
|
|
378
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
379
|
+
for (const m of readableMembers(ctx)) {
|
|
380
|
+
const kind = ctx.kinds.get(m.kind)!;
|
|
381
|
+
const mf = kind.memberFile;
|
|
382
|
+
if (!mf) continue;
|
|
383
|
+
const file = memberDir(m.dir) === "" ? mf.path : `${memberDir(m.dir)}/${mf.path}`;
|
|
384
|
+
if (!mf.validate) {
|
|
385
|
+
out.push(entryFinding(this, m, "kind", `member ${m.name}'s ${mf.path} can't be checked: kind ${m.kind}'s schema ${mf.schema} did not load (see WSP002)`));
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (ctx.tree.stat(file) !== "file") {
|
|
389
|
+
out.push(entryFinding(this, m, "dir", `member ${m.name} (${m.dir}) has no ${mf.path}, which kind ${m.kind} requires${ctx.tree.label}`));
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
let text: string;
|
|
393
|
+
try {
|
|
394
|
+
text = ctx.tree.read(file);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
out.push(entryFinding(this, m, "dir", `member ${m.name}'s ${mf.path} can't be read: ${(err as Error).message}`));
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
for (const p of checkMemberFile(mf, mf.path, text)) {
|
|
400
|
+
out.push({ ...entryFinding(this, m, null, `member ${m.name}'s ${mf.path} fails kind ${m.kind}'s schema ${mf.schema}: ${p.message}`), treeFile: file, line: 1, column: 1 });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
// Org scope (#3168), WSP013 to WSP016.
|
|
407
|
+
...ORG_CHECKS,
|
|
362
408
|
// Member ledgers (#2538).
|
|
363
409
|
...LEDGER_CHECKS,
|
|
364
410
|
// Recorded pipelines (#2542).
|
|
@@ -377,6 +423,8 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
377
423
|
...LIVE_CHECKS,
|
|
378
424
|
// What a member under ci.workflow still lacks (#4168 step 6), WSP151.
|
|
379
425
|
...CI_WORKFLOW_CHECKS,
|
|
426
|
+
// A member's pipeline targets the branch ci.branches routes it to (#4325), WSP152.
|
|
427
|
+
...CI_BRANCH_CHECKS,
|
|
380
428
|
// The security groups the declaration opts into (#4168 step 7), WSP161 to WSP165.
|
|
381
429
|
...SECURITY_CHECKS,
|
|
382
430
|
];
|
|
@@ -463,6 +511,8 @@ export interface DeclarationCheckOptions {
|
|
|
463
511
|
* account now. `onStderr` gets each member's stderr.
|
|
464
512
|
*/
|
|
465
513
|
live?: { env: string; onStderr?: (text: string) => void };
|
|
514
|
+
/** The forge's repository list, from `--forge-repos` (#3168), for WSP014 and WSP015. Read under `--at` too: it is input, not the tree. */
|
|
515
|
+
forgeRepos?: ForgeRepoList;
|
|
466
516
|
}
|
|
467
517
|
|
|
468
518
|
/**
|
|
@@ -534,12 +584,19 @@ export async function runDeclarationChecks(
|
|
|
534
584
|
// A box's intent is read from the working tree's records, so not under --at (#2850).
|
|
535
585
|
const boxIntents = declaredKinds && !options.tree ? await resolveBoxIntents(declaration, root, declaredKinds) : undefined;
|
|
536
586
|
// A linked member's lexicons name its tables and columns as outputs (#3931); reading them folds its source, never runs it.
|
|
587
|
+
// A member's lexicons may also read other members' outputs from its declarations (#4397); their targets are read too.
|
|
588
|
+
const outputsTree = options.tree ? workingTree(root) : tree;
|
|
589
|
+
const lexiconLinks = options.gather === false ? undefined : await gatherLexiconLinks(root, declaration, outputsTree, groups, registry, { atRevision: options.tree !== undefined });
|
|
537
590
|
const lexiconOutputs =
|
|
538
|
-
options.gather === false
|
|
591
|
+
options.gather === false
|
|
592
|
+
? undefined
|
|
593
|
+
: await gatherLexiconOutputs(root, declaration, outputsTree, groups, registry, { atRevision: options.tree !== undefined, ...(lexiconLinks ? { lexiconLinks } : {}) });
|
|
539
594
|
const facts: WorkspaceFacts = {
|
|
540
595
|
...(lexiconOutputs && lexiconOutputs.size > 0 ? { lexiconOutputs } : {}),
|
|
596
|
+
...(lexiconLinks && lexiconLinks.size > 0 ? { lexiconLinks } : {}),
|
|
541
597
|
...gathered,
|
|
542
598
|
...(options.records ? { records: options.records } : {}),
|
|
599
|
+
...(options.forgeRepos ? { forgeRepos: options.forgeRepos } : {}),
|
|
543
600
|
...(declaredKinds ? { declaredKinds } : {}),
|
|
544
601
|
...(boxIntents ? { boxIntents } : {}),
|
|
545
602
|
...(options.live && !options.tree ? { live: await gatherLiveLinkFacts(root, declaration, registry, options.live.env, options.live.onStderr) } : {}),
|
|
@@ -40,6 +40,37 @@ export function routeConflicts(branches: MemberGlobs | undefined, members: reado
|
|
|
40
40
|
return members.map((member) => ({ member, branches: keysMatching(branches, member) })).filter((c) => c.branches.length > 1);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The branches `chant ci serve` polls (#4325), and the ones among them a job
|
|
45
|
+
* is told with `--branch`.
|
|
46
|
+
*
|
|
47
|
+
* - A routed member polls its route alone. A `named` branch that differs
|
|
48
|
+
* from the route is refused, as `chant build --pr-loop` refuses it.
|
|
49
|
+
* - An unrouted member polls `fallback`, which may not be a routed branch.
|
|
50
|
+
* - Where no member is in play (the workspace root), `fallback` and then each
|
|
51
|
+
* routed branch, so every branch a member applies from is watched.
|
|
52
|
+
*
|
|
53
|
+
* `fallback` is the branch `--branch` names, else the default branch.
|
|
54
|
+
*/
|
|
55
|
+
export function servedBranches(
|
|
56
|
+
branches: MemberGlobs | undefined,
|
|
57
|
+
member: string | undefined,
|
|
58
|
+
fallback: string,
|
|
59
|
+
named: boolean,
|
|
60
|
+
): { served: string[]; routed: string[] } {
|
|
61
|
+
const keys = Object.keys(branches ?? {});
|
|
62
|
+
if (member === undefined) return { served: [fallback, ...keys.filter((k) => k !== fallback)], routed: keys };
|
|
63
|
+
const own = routedBranch(branches, member);
|
|
64
|
+
if (own !== null) {
|
|
65
|
+
if (named && fallback !== own) throw new Error(`--branch ${fallback}: ci.branches routes member ${member} to ${own}, so ci serve polls ${own}`);
|
|
66
|
+
return { served: [own], routed: [own] };
|
|
67
|
+
}
|
|
68
|
+
if (keys.includes(fallback)) {
|
|
69
|
+
throw new Error(`--branch ${fallback}: ci.branches applies only the members its globs match from ${fallback}, and member ${member} is not one`);
|
|
70
|
+
}
|
|
71
|
+
return { served: [fallback], routed: [] };
|
|
72
|
+
}
|
|
73
|
+
|
|
43
74
|
/**
|
|
44
75
|
* Whether a push to `pushed` applies `member`, with a line that says why.
|
|
45
76
|
* A routed member applies only from its branch. Any other member applies
|