@intentius/chant 0.124.45 → 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/capabilities/aws.paths +148 -0
- package/dist/capabilities/base-mappings.json +122 -0
- package/dist/capabilities/catalog.d.ts +89 -0
- package/dist/capabilities/catalog.d.ts.map +1 -0
- package/dist/capabilities/catalog.js +204 -0
- package/dist/capabilities/language.d.ts +103 -0
- package/dist/capabilities/language.d.ts.map +1 -0
- package/dist/capabilities/language.js +335 -0
- package/dist/capabilities/matcher.d.ts +65 -0
- package/dist/capabilities/matcher.d.ts.map +1 -0
- package/dist/capabilities/matcher.js +299 -0
- package/dist/capabilities/view.d.ts +207 -0
- package/dist/capabilities/view.d.ts.map +1 -0
- package/dist/capabilities/view.js +484 -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/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +1 -0
- 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/declared-graph.d.ts +44 -0
- package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
- package/dist/cli/handlers/declared-graph.js +87 -0
- package/dist/cli/handlers/graph.d.ts +25 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +93 -87
- 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.d.ts.map +1 -1
- package/dist/cli/handlers/pr-env.js +4 -3
- 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-count.d.ts +89 -0
- package/dist/cli/handlers/search-count.d.ts.map +1 -0
- package/dist/cli/handlers/search-count.js +132 -0
- package/dist/cli/handlers/search.d.ts +356 -6
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +1039 -155
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +88 -17
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +3 -0
- package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
- package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
- package/dist/cli/mcp/tools/capabilities.js +27 -0
- package/dist/cli/mcp/workspace-tools.d.ts +3 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.js +47 -2
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +100 -19
- package/dist/cli/registry.d.ts +36 -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-env-ports.d.ts +29 -0
- package/dist/components/pr-env-ports.d.ts.map +1 -0
- package/dist/components/pr-env-ports.js +77 -0
- package/dist/components/pr-env-run.d.ts +5 -21
- package/dist/components/pr-env-run.d.ts.map +1 -1
- package/dist/components/pr-env-run.js +6 -70
- package/dist/components/pr-environment.d.ts +2 -1
- package/dist/components/pr-environment.d.ts.map +1 -1
- package/dist/components/pr-environment.js +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 +112 -0
- package/dist/config-environments.d.ts.map +1 -0
- package/dist/config-environments.js +114 -0
- package/dist/config.d.ts +32 -84
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -96
- 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-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +15 -3
- package/dist/graph-effective.d.ts +33 -2
- package/dist/graph-effective.d.ts.map +1 -1
- package/dist/graph-effective.js +215 -68
- package/dist/graph-ir.d.ts +48 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +130 -0
- package/dist/graph-sub-template.d.ts +11 -0
- package/dist/graph-sub-template.d.ts.map +1 -0
- package/dist/graph-sub-template.js +19 -0
- 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 +80 -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/observe.d.ts +28 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +23 -1
- 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/rule.d.ts +6 -0
- package/dist/lint/rule.d.ts.map +1 -1
- 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/live-endpoint.d.ts +1 -1
- package/dist/live-endpoint.d.ts.map +1 -1
- package/dist/live-endpoint.js +1 -1
- package/dist/observation.d.ts +48 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +21 -5
- package/dist/op/activities/activity-contracts.d.ts +9 -6
- 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 +266 -256
- package/dist/workspace/capabilities-contract.d.ts +66 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -0
- package/dist/workspace/capabilities-contract.js +107 -0
- package/dist/workspace/capabilities.schema.json +229 -0
- 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 +23 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +3 -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/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +39 -2
- 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-cache.d.ts +8 -0
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cache.js +27 -3
- package/dist/workspace/graph-cli.d.ts +39 -2
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +92 -32
- package/dist/workspace/graph.schema.json +182 -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/kind-readers.d.ts +3 -0
- package/dist/workspace/kind-readers.d.ts.map +1 -1
- package/dist/workspace/kind-readers.js +7 -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 +10 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +71 -6
- 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/nested-graph.d.ts +2 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +32 -1
- package/dist/workspace/reason-codes.d.ts +18 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +23 -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-cli.d.ts +204 -0
- package/dist/workspace/search-cli.d.ts.map +1 -0
- package/dist/workspace/search-cli.js +466 -0
- package/dist/workspace/search-contract.d.ts +56 -0
- package/dist/workspace/search-contract.d.ts.map +1 -0
- package/dist/workspace/search-contract.js +70 -0
- package/dist/workspace/search.schema.json +822 -0
- 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/__snapshots__/okf.test.ts.snap +10 -0
- package/src/action-pins.test.ts +78 -0
- package/src/action-pins.ts +92 -0
- package/src/capabilities/aws.paths +148 -0
- package/src/capabilities/base-mappings.json +122 -0
- package/src/capabilities/catalog.ts +230 -0
- package/src/capabilities/language.test.ts +191 -0
- package/src/capabilities/language.ts +373 -0
- package/src/capabilities/matcher.ts +315 -0
- package/src/capabilities/view-compose.test.ts +76 -0
- package/src/capabilities/view.ts +631 -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/lint.ts +1 -0
- 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/declared-graph.ts +99 -0
- package/src/cli/handlers/graph.test.ts +94 -3
- package/src/cli/handlers/graph.ts +123 -96
- 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 +3 -4
- 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-build-roots.test.ts +144 -0
- package/src/cli/handlers/search-count.test.ts +191 -0
- package/src/cli/handlers/search-count.ts +165 -0
- package/src/cli/handlers/search-json.test.ts +333 -0
- package/src/cli/handlers/search-live.test.ts +387 -0
- package/src/cli/handlers/search-paths.test.ts +235 -0
- package/src/cli/handlers/search.ts +1291 -166
- package/src/cli/main.test.ts +43 -3
- package/src/cli/main.ts +88 -17
- package/src/cli/mcp/server.test.ts +3 -3
- package/src/cli/mcp/server.ts +3 -0
- package/src/cli/mcp/tools/capabilities.ts +32 -0
- package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
- package/src/cli/mcp/workspace-tools.ts +47 -2
- package/src/cli/parse-args.ts +79 -15
- package/src/cli/registry.ts +36 -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-env-ports.ts +89 -0
- package/src/components/pr-env-run-bundle.test.ts +54 -0
- package/src/components/pr-env-run.ts +5 -76
- package/src/components/pr-environment.ts +2 -1
- 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 +160 -0
- package/src/config.ts +44 -124
- 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-detail.ts +15 -3
- package/src/graph-effective.ts +226 -75
- package/src/graph-ir.test.ts +163 -0
- package/src/graph-ir.ts +174 -0
- package/src/graph-sub-template.ts +22 -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 +84 -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/observe.test.ts +27 -1
- package/src/lifecycle/observe.ts +43 -1
- 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/rule.ts +6 -0
- package/src/lint/rules/op/index.ts +2 -0
- package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
- package/src/live-endpoint.ts +1 -1
- package/src/observation.test.ts +25 -0
- package/src/observation.ts +69 -5
- 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/capabilities-contract.test.ts +88 -0
- package/src/workspace/capabilities-contract.ts +132 -0
- package/src/workspace/capabilities.schema.json +229 -0
- 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 +19 -3
- 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/conformance/conformance.e2e.test.ts +14 -1
- package/src/workspace/conformance/index.ts +31 -2
- 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-cache.ts +27 -3
- package/src/workspace/graph-cli.ts +119 -32
- package/src/workspace/graph-contract.test.ts +4 -2
- package/src/workspace/graph.schema.json +182 -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/kind-readers.e2e.test.ts +28 -0
- package/src/workspace/kind-readers.test.ts +1 -1
- package/src/workspace/kind-readers.ts +7 -1
- 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 +82 -3
- 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/nested-graph.ts +32 -1
- package/src/workspace/org-scope.test.ts +238 -0
- package/src/workspace/reason-codes.test.ts +13 -1
- package/src/workspace/reason-codes.ts +23 -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/search-cli.e2e.test.ts +305 -0
- package/src/workspace/search-cli.ts +591 -0
- package/src/workspace/search-contract.ts +95 -0
- package/src/workspace/search.schema.json +822 -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
package/src/pr-post.test.ts
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
-
import { afterAll, describe, expect, test } from "vitest";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { afterAll, describe, expect, test, vi } from "vitest";
|
|
12
|
+
import type { CommandContext } from "./cli/registry";
|
|
13
|
+
import { parseArgs } from "./cli/parse-args";
|
|
14
|
+
import { postOpenPlanReports, postPlanReport, readPlanReport } from "./pr-post";
|
|
15
|
+
import { prNoteMarker, prStatusState, type PrReport } from "./pr-loop";
|
|
14
16
|
import type { ForgeStatus, PrForge } from "./pr-forge";
|
|
15
17
|
|
|
16
18
|
const FIXTURE = join(import.meta.dirname, "__fixtures__", "pr-loop", "pr-plan.golden.json");
|
|
@@ -139,3 +141,100 @@ describe("pr-post asks for overdue scheduled Ops itself (#4289, #4295)", () => {
|
|
|
139
141
|
expect(quiet.notes[0]!.body).not.toContain("forged warning");
|
|
140
142
|
});
|
|
141
143
|
});
|
|
144
|
+
|
|
145
|
+
describe("pr-post --open posts each open merge request's plan from a pipeline of the target branch (#4328)", () => {
|
|
146
|
+
const JOB_URL = (id: number) => `https://gitlab.com/acme/estate/-/jobs/${id}`;
|
|
147
|
+
const OTHER = "c".repeat(40);
|
|
148
|
+
|
|
149
|
+
/** A GitLab client with the merge requests' plan jobs, their artifacts and the statuses already posted. */
|
|
150
|
+
function gitlab(input: { jobs: Array<{ pr: number; head: string; id: number; status: string }>; artifacts: Record<number, string>; posted?: Record<string, string> }) {
|
|
151
|
+
const statuses: Array<{ sha: string; status: ForgeStatus; url?: string }> = [];
|
|
152
|
+
const notes: Array<{ pr: number; body: string }> = [];
|
|
153
|
+
const asked: Array<[string, string]> = [];
|
|
154
|
+
const reads: string[] = [];
|
|
155
|
+
let overdue = 0;
|
|
156
|
+
const forge = {
|
|
157
|
+
kind: "gitlab",
|
|
158
|
+
host: "gitlab.com",
|
|
159
|
+
async mergeRequestJobs(branch: string, job: string) {
|
|
160
|
+
asked.push([branch, job]);
|
|
161
|
+
return input.jobs.map((j) => ({ ...j, url: JOB_URL(j.id) }));
|
|
162
|
+
},
|
|
163
|
+
async jobArtifact(id: number, path: string) {
|
|
164
|
+
reads.push(`${id}:${path}`);
|
|
165
|
+
return input.artifacts[id];
|
|
166
|
+
},
|
|
167
|
+
async statusUrl(sha: string) {
|
|
168
|
+
return input.posted?.[sha];
|
|
169
|
+
},
|
|
170
|
+
async setStatus(this: PrForge, sha: string, status: ForgeStatus) {
|
|
171
|
+
statuses.push({ sha, status, ...(this.runUrl ? { url: this.runUrl } : {}) });
|
|
172
|
+
},
|
|
173
|
+
async upsertNote(pr: number, _marker: string, body: string) {
|
|
174
|
+
notes.push({ pr, body });
|
|
175
|
+
return "1";
|
|
176
|
+
},
|
|
177
|
+
} as unknown as PrForge;
|
|
178
|
+
const count = async () => {
|
|
179
|
+
overdue++;
|
|
180
|
+
return [{ op: "drift", cron: "0 * * * *", since: "2025-10-01T00:00:00Z", message: "Op `drift` is overdue" }];
|
|
181
|
+
};
|
|
182
|
+
return { forge, statuses, notes, asked, reads, overdue: count, overdueCalls: () => overdue };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
test("a settled plan job's report is read from its artifacts and posted, linking the job; a running one waits", async () => {
|
|
186
|
+
const g = gitlab({
|
|
187
|
+
jobs: [
|
|
188
|
+
{ pr: REPORT.pr!, head: HEAD, id: 101, status: "success" },
|
|
189
|
+
{ pr: 8, head: OTHER, id: 102, status: "running" },
|
|
190
|
+
],
|
|
191
|
+
artifacts: { 101: JSON.stringify(REPORT) },
|
|
192
|
+
});
|
|
193
|
+
const { posts, warnings } = await postOpenPlanReports({ forge: g.forge, branch: "main", job: "plan", artifactDir: ".chant/pr/", env: REPORT.env, overdue: g.overdue });
|
|
194
|
+
expect(warnings).toEqual([]);
|
|
195
|
+
expect(g.asked).toEqual([["main", "plan"]]);
|
|
196
|
+
expect(g.reads).toEqual(["101:.chant/pr/pr-plan.json"]);
|
|
197
|
+
expect(posts.map((p) => [p.pr, p.skipped ?? p.posted!.state])).toEqual([[REPORT.pr, prStatusState(REPORT)], [8, "the plan job is running"]]);
|
|
198
|
+
expect(g.statuses).toEqual([{ sha: HEAD, status: expect.objectContaining({ context: "chant/plan", state: prStatusState(REPORT), url: JOB_URL(101) }), url: JOB_URL(101) }]);
|
|
199
|
+
expect(g.notes.map((n) => n.pr)).toEqual([REPORT.pr]);
|
|
200
|
+
expect(g.notes[0]!.body).toContain("Op `drift` is overdue");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("a job whose result is already posted is not posted again", async () => {
|
|
204
|
+
const g = gitlab({ jobs: [{ pr: REPORT.pr!, head: HEAD, id: 101, status: "success" }], artifacts: { 101: JSON.stringify(REPORT) }, posted: { [HEAD]: JOB_URL(101) } });
|
|
205
|
+
const { posts } = await postOpenPlanReports({ forge: g.forge, branch: "main", job: "plan", artifactDir: ".chant/pr", env: REPORT.env });
|
|
206
|
+
expect(posts).toEqual([{ pr: REPORT.pr, head: HEAD, skipped: "this plan job's result is already posted" }]);
|
|
207
|
+
expect(g.statuses).toEqual([]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("the pull request and head are GitLab's: a report for another head, a missing one and a failed job each fail the status", async () => {
|
|
211
|
+
const g = gitlab({
|
|
212
|
+
jobs: [
|
|
213
|
+
{ pr: REPORT.pr!, head: OTHER, id: 101, status: "success" },
|
|
214
|
+
{ pr: 8, head: HEAD, id: 102, status: "failed" },
|
|
215
|
+
{ pr: 9, head: HEAD, id: 103, status: "failed" },
|
|
216
|
+
],
|
|
217
|
+
artifacts: { 101: JSON.stringify(REPORT), 103: JSON.stringify({ ...REPORT, pr: 9 }) },
|
|
218
|
+
});
|
|
219
|
+
const { posts } = await postOpenPlanReports({ forge: g.forge, branch: "main", job: "plan", artifactDir: ".chant/pr", env: REPORT.env, overdue: g.overdue });
|
|
220
|
+
expect(posts.map((p) => [p.pr, p.posted!.state, p.posted!.refused ?? ""])).toEqual([
|
|
221
|
+
[REPORT.pr, "failure", expect.stringMatching(/the report is for head/)],
|
|
222
|
+
[8, "failure", "the plan job wrote no report"],
|
|
223
|
+
[9, "failure", ""],
|
|
224
|
+
]);
|
|
225
|
+
expect(g.notes.map((n) => n.pr)).toEqual([9]);
|
|
226
|
+
// The check runs once, however many merge requests it posts for.
|
|
227
|
+
expect(g.overdueCalls()).toBe(1);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("a forge that cannot list merge request jobs is refused", async () => {
|
|
231
|
+
await expect(postOpenPlanReports({ forge: fakeForge().forge, branch: "main", job: "plan", artifactDir: ".chant/pr", env: REPORT.env })).rejects.toThrow(/it is for GitLab/);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("chant components pr-post --open refuses what it cannot post for (#4328)", () => {
|
|
236
|
+
|
|
237
|
+
test("the flags parse", () => {
|
|
238
|
+
expect(parseArgs(["components", "pr-post", "--open", "--env", "prod", "--forge", "gitlab", "--branch", "main"])).toMatchObject({ open: true, env: "prod", forge: "gitlab", branch: "main" });
|
|
239
|
+
});
|
|
240
|
+
});
|
package/src/pr-post.ts
CHANGED
|
@@ -14,9 +14,16 @@
|
|
|
14
14
|
* every hidden HTML comment after its first line is defused, so a forged
|
|
15
15
|
* marker cannot claim another environment's note. A plan job that failed
|
|
16
16
|
* fails the status whatever its report says.
|
|
17
|
+
*
|
|
18
|
+
* On GitLab the post runs in a pipeline of the target branch (#4328): a
|
|
19
|
+
* merge request pipeline is built from the merge request's own files, so a
|
|
20
|
+
* job there that holds `CHANT_FORGE_TOKEN` runs whatever the change wrote.
|
|
21
|
+
* `pr-post --open` ({@link postOpenPlanReports}) asks GitLab for each open
|
|
22
|
+
* merge request's plan job and reads the report from its artifacts.
|
|
17
23
|
*/
|
|
18
24
|
|
|
19
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
25
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
26
|
+
import { tmpdir } from "node:os";
|
|
20
27
|
import { join } from "node:path";
|
|
21
28
|
import { prNoteMarker, prStatusContext, prStatusDescription, prStatusState, renderPrNote, type PrOverdue, type PrReport } from "./pr-loop";
|
|
22
29
|
import { forgePrincipalOf, type PrForge } from "./pr-forge";
|
|
@@ -157,3 +164,84 @@ export async function postPlanReport(input: PlanPostInput): Promise<PlanPostResu
|
|
|
157
164
|
}
|
|
158
165
|
return { state, description: cut(description), noted: note !== undefined, ...(refused ? { refused } : {}), warnings };
|
|
159
166
|
}
|
|
167
|
+
|
|
168
|
+
/** What {@link postOpenPlanReports} posts for. */
|
|
169
|
+
export interface OpenPlanPostInput {
|
|
170
|
+
/** A GitLab client with {@link PrForge.mergeRequestJobs}, {@link PrForge.jobArtifact} and {@link PrForge.statusUrl}. */
|
|
171
|
+
forge: PrForge;
|
|
172
|
+
/** The branch the merge requests merge into: the pipeline's own. */
|
|
173
|
+
branch: string;
|
|
174
|
+
/** The plan job's name in the merge request pipeline: `plan`, or `<member>-plan`. */
|
|
175
|
+
job: string;
|
|
176
|
+
/** The report directory from the repository root, as the plan job kept it as its artifact. */
|
|
177
|
+
artifactDir: string;
|
|
178
|
+
env: string;
|
|
179
|
+
member?: string;
|
|
180
|
+
overdue?: () => Promise<PrOverdue[]>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** What {@link postOpenPlanReports} did for one merge request. */
|
|
184
|
+
export interface OpenPlanPost {
|
|
185
|
+
pr: number;
|
|
186
|
+
head: string;
|
|
187
|
+
/** What was posted, or why nothing was: the plan job has not settled, or its result is already posted. */
|
|
188
|
+
posted?: PlanPostResult;
|
|
189
|
+
skipped?: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Post the plan note and status of every open merge request into `branch`
|
|
194
|
+
* whose plan job settled (#4328), from a pipeline of `branch`. GitLab says
|
|
195
|
+
* which merge requests are open, their heads, and their newest pipeline's
|
|
196
|
+
* plan job; the report is the one file `pr-plan.json` of that job's
|
|
197
|
+
* artifacts, read as data as {@link postPlanReport} reads any. The status
|
|
198
|
+
* links to the plan job, and a status that already links to it is the
|
|
199
|
+
* job's result posted, so a job is posted for once. The overdue check runs
|
|
200
|
+
* once for every merge request.
|
|
201
|
+
*/
|
|
202
|
+
export async function postOpenPlanReports(input: OpenPlanPostInput): Promise<{ posts: OpenPlanPost[]; warnings: string[] }> {
|
|
203
|
+
const { forge } = input;
|
|
204
|
+
if (!forge.mergeRequestJobs || !forge.jobArtifact || !forge.statusUrl) {
|
|
205
|
+
throw new Error(`pr-post --open reads the plan jobs of open merge requests, which ${forge.kind} cannot say; it is for GitLab`);
|
|
206
|
+
}
|
|
207
|
+
const context = prStatusContext("plan", input.member);
|
|
208
|
+
const warnings: string[] = [];
|
|
209
|
+
let overdue: Promise<PrOverdue[]> | undefined;
|
|
210
|
+
const overdueOnce = input.overdue ? () => (overdue ??= input.overdue!()) : undefined;
|
|
211
|
+
const posts: OpenPlanPost[] = [];
|
|
212
|
+
for (const job of await forge.mergeRequestJobs(input.branch, input.job)) {
|
|
213
|
+
const settled = job.status === "success" ? "success" : job.status === "failed" ? "failure" : job.status === "canceled" ? "cancelled" : undefined;
|
|
214
|
+
if (!settled) {
|
|
215
|
+
posts.push({ pr: job.pr, head: job.head, skipped: `the plan job is ${job.status}` });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
if (job.url && (await forge.statusUrl(job.head, context)) === job.url) {
|
|
220
|
+
posts.push({ pr: job.pr, head: job.head, skipped: "this plan job's result is already posted" });
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-pr-post-"));
|
|
224
|
+
try {
|
|
225
|
+
const text = await forge.jobArtifact(job.id, `${input.artifactDir.replace(/\/+$/, "")}/${PR_PLAN_REPORT_FILE}`);
|
|
226
|
+
if (text !== undefined) writeFileSync(join(dir, PR_PLAN_REPORT_FILE), text);
|
|
227
|
+
const posted = await postPlanReport({
|
|
228
|
+
// The status links to the plan job it reports on, which is how the next run knows it is posted.
|
|
229
|
+
forge: job.url ? { ...forge, runUrl: job.url } : forge,
|
|
230
|
+
dir,
|
|
231
|
+
pr: job.pr,
|
|
232
|
+
head: job.head,
|
|
233
|
+
env: input.env,
|
|
234
|
+
...(input.member ? { member: input.member } : {}),
|
|
235
|
+
result: settled,
|
|
236
|
+
...(overdueOnce ? { overdue: overdueOnce } : {}),
|
|
237
|
+
});
|
|
238
|
+
posts.push({ pr: job.pr, head: job.head, posted });
|
|
239
|
+
} finally {
|
|
240
|
+
rmSync(dir, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
warnings.push(`!${job.pr}: ${err instanceof Error ? err.message : String(err)}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return { posts, warnings };
|
|
247
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { FilterError, matchesWhere, parseInstant, parseSince, parseWhere, type FilterFields } from "./read-filter";
|
|
3
|
+
|
|
4
|
+
const fields: FilterFields = { name: "text", count: "number", ok: "boolean", at: "time", labels: "json", "unit.id": "text" };
|
|
5
|
+
const now = new Date("2026-10-23T12:00:00.000Z");
|
|
6
|
+
const keep = (rows: { name: string }[], ...where: string[]) => rows.filter((r) => matchesWhere(r, parseWhere(where, fields, now))).map((r) => r.name);
|
|
7
|
+
|
|
8
|
+
describe("typed filters (#4363)", () => {
|
|
9
|
+
const rows = [
|
|
10
|
+
{ name: "web", count: 3, ok: true, at: "2026-10-22T00:00:00.000Z", labels: { team: "core", size: 2 }, unit: { id: "W-1" } },
|
|
11
|
+
{ name: "worker", count: 10, ok: false, at: "2026-10-01T00:00:00.000Z", labels: '{"team":"edge","size":12}', unit: null },
|
|
12
|
+
{ name: "db", count: null, ok: true, at: null, labels: null, unit: { id: "W-2" } },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
it("filters each type with its operators, every clause holding", () => {
|
|
16
|
+
expect(keep(rows, "name=web")).toEqual(["web"]);
|
|
17
|
+
expect(keep(rows, "name~w")).toEqual(["web", "worker"]);
|
|
18
|
+
expect(keep(rows, "count>=3", "count<10")).toEqual(["web"]);
|
|
19
|
+
expect(keep(rows, "ok=false")).toEqual(["worker"]);
|
|
20
|
+
expect(keep(rows, "at>7d")).toEqual(["web"]);
|
|
21
|
+
expect(keep(rows, "at<2026-10-10")).toEqual(["worker"]);
|
|
22
|
+
expect(keep(rows, "unit.id=W-2")).toEqual(["db"]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("reads a key under a json field, parsing JSON text, as the type it holds", () => {
|
|
26
|
+
expect(keep(rows, "labels.team=edge")).toEqual(["worker"]);
|
|
27
|
+
expect(keep(rows, "labels.size>5")).toEqual(["worker"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("matches null with = and !=, and a null field only with !=", () => {
|
|
31
|
+
expect(keep(rows, "count=null")).toEqual(["db"]);
|
|
32
|
+
expect(keep(rows, "count!=null")).toEqual(["web", "worker"]);
|
|
33
|
+
expect(keep(rows, "count!=3")).toEqual(["worker", "db"]);
|
|
34
|
+
expect(keep(rows, "count<100")).toEqual(["web", "worker"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("refuses a field, operator or value that doesn't fit, before reading", () => {
|
|
38
|
+
expect(() => parseWhere(["nope=1"], fields)).toThrow(/no such field; this read filters on name, count/);
|
|
39
|
+
expect(() => parseWhere(["count=many"], fields)).toThrow(FilterError);
|
|
40
|
+
expect(() => parseWhere(["ok>true"], fields)).toThrow(/= or != only/);
|
|
41
|
+
expect(() => parseWhere(["count~1"], fields)).toThrow(/takes a text field/);
|
|
42
|
+
expect(() => parseWhere(["at=yesterday"], fields)).toThrow(/is a time/);
|
|
43
|
+
expect(() => parseWhere(["labels=x"], fields)).toThrow(/name a key under it/);
|
|
44
|
+
expect(() => parseWhere(["name>=null"], fields)).toThrow(/null takes = or != only/);
|
|
45
|
+
expect(() => parseWhere(["just-a-word"], fields)).toThrow(/<field><op><value>/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("reads --since as an ISO time or a span back from now", () => {
|
|
49
|
+
expect(parseSince("7d", now)).toBe("2026-10-16T12:00:00.000Z");
|
|
50
|
+
expect(parseSince("2026-10-01", now)).toBe("2026-10-01T00:00:00.000Z");
|
|
51
|
+
expect(parseInstant("90m", now)).toBe(now.getTime() - 90 * 60_000);
|
|
52
|
+
expect(() => parseSince("last week", now)).toThrow(FilterError);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed filters for the contract reads (#4363): `--where <field><op><value>`
|
|
3
|
+
* and `--since <time>`, in place of an embedded SQL engine.
|
|
4
|
+
*
|
|
5
|
+
* Each read declares the fields it can be filtered on and their types
|
|
6
|
+
* ({@link FilterFields}). A clause naming a field the read doesn't declare, an
|
|
7
|
+
* operator its type doesn't take, or a value that isn't of its type is a
|
|
8
|
+
* {@link FilterError} before anything is read, so a typo never filters to an
|
|
9
|
+
* empty answer. Several `--where` clauses all have to hold.
|
|
10
|
+
*
|
|
11
|
+
* Operators: `=` and `!=` for every type; `<`, `<=`, `>` and `>=` for
|
|
12
|
+
* numbers, times and text (text compares by code point); `~` for text, which
|
|
13
|
+
* holds when the field contains the value. The value `null` matches a field
|
|
14
|
+
* that is null or absent, with `=` and `!=` only.
|
|
15
|
+
*
|
|
16
|
+
* A field of type `json` holds an object: a clause can name a key under it
|
|
17
|
+
* with dots (`labels.team=core`), and the value is compared as the type it
|
|
18
|
+
* holds there (a number with a number, a boolean with `true` or `false`, any
|
|
19
|
+
* other value as text). A JSON text is parsed first, so a ledger row that
|
|
20
|
+
* keeps the object as text filters the same way.
|
|
21
|
+
*
|
|
22
|
+
* A null or absent field matches only `=null` and `!=` with a value other
|
|
23
|
+
* than null; the other operators never hold for it.
|
|
24
|
+
*
|
|
25
|
+
* Pure: no I/O, so a bundle that filters rows loads nothing else.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export type FilterFieldType = "text" | "number" | "boolean" | "time" | "json";
|
|
29
|
+
|
|
30
|
+
/** The fields a read can be filtered on, by name, with their types. */
|
|
31
|
+
export type FilterFields = Readonly<Record<string, FilterFieldType>>;
|
|
32
|
+
|
|
33
|
+
/** Every operator, longest first so `!=` is not read as `=`. */
|
|
34
|
+
export const WHERE_OPERATORS = ["!=", "<=", ">=", "=", "<", ">", "~"] as const;
|
|
35
|
+
export type WhereOperator = (typeof WHERE_OPERATORS)[number];
|
|
36
|
+
|
|
37
|
+
/** One parsed `--where` clause. */
|
|
38
|
+
export interface WhereClause {
|
|
39
|
+
/** The field as written, with any path under a `json` field. */
|
|
40
|
+
field: string;
|
|
41
|
+
/** The declared field the clause starts at. */
|
|
42
|
+
base: string;
|
|
43
|
+
/** The keys under a `json` field, or `[]`. */
|
|
44
|
+
path: string[];
|
|
45
|
+
type: FilterFieldType;
|
|
46
|
+
op: WhereOperator;
|
|
47
|
+
/** The value as written. */
|
|
48
|
+
raw: string;
|
|
49
|
+
/** The value read as the field's type: a number for `number` and `time` (epoch ms), a boolean, a string, or null. */
|
|
50
|
+
value: string | number | boolean | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A clause or a `--since` that can't be applied. The message is for a person. */
|
|
54
|
+
export class FilterError extends Error {}
|
|
55
|
+
|
|
56
|
+
const ORDERED: readonly WhereOperator[] = ["<", "<=", ">", ">="];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* An instant from an ISO 8601 date or time, or from a span back from `now`:
|
|
60
|
+
* `<n>m`, `<n>h`, `<n>d` or `<n>w`. Epoch milliseconds, or null when `text`
|
|
61
|
+
* is neither.
|
|
62
|
+
*/
|
|
63
|
+
export function parseInstant(text: string, now: Date = new Date()): number | null {
|
|
64
|
+
const span = /^(\d+)([mhdw])$/.exec(text.trim());
|
|
65
|
+
if (span) {
|
|
66
|
+
const unit = { m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 }[span[2] as "m" | "h" | "d" | "w"];
|
|
67
|
+
return now.getTime() - Number(span[1]) * unit;
|
|
68
|
+
}
|
|
69
|
+
if (!/^\d{4}-\d{2}-\d{2}/.test(text.trim())) return null;
|
|
70
|
+
const ms = Date.parse(text);
|
|
71
|
+
return Number.isNaN(ms) ? null : ms;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** `--since <time>` as an ISO instant, or a {@link FilterError}. */
|
|
75
|
+
export function parseSince(text: string, now: Date = new Date()): string {
|
|
76
|
+
const ms = parseInstant(text, now);
|
|
77
|
+
if (ms === null) throw new FilterError(`--since takes an ISO 8601 date or time, or a span back from now such as 24h or 7d, not ${JSON.stringify(text)}`);
|
|
78
|
+
return new Date(ms).toISOString();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The declared field a dotted name starts at: the whole name, or its longest declared `json` prefix. */
|
|
82
|
+
function resolveField(name: string, fields: FilterFields): { base: string; path: string[]; type: FilterFieldType } | null {
|
|
83
|
+
if (Object.prototype.hasOwnProperty.call(fields, name)) return { base: name, path: [], type: fields[name] };
|
|
84
|
+
const parts = name.split(".");
|
|
85
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
86
|
+
const base = parts.slice(0, i).join(".");
|
|
87
|
+
if (Object.prototype.hasOwnProperty.call(fields, base) && fields[base] === "json") return { base, path: parts.slice(i), type: "json" };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Parse each `--where` against the fields a read declares. Throws a {@link FilterError}. */
|
|
93
|
+
export function parseWhere(exprs: readonly string[], fields: FilterFields, now: Date = new Date()): WhereClause[] {
|
|
94
|
+
const names = Object.keys(fields).join(", ");
|
|
95
|
+
return exprs.map((expr) => {
|
|
96
|
+
const m = /^([A-Za-z_][\w.-]*)\s*(!=|<=|>=|=|<|>|~)(.*)$/s.exec(expr);
|
|
97
|
+
if (!m) throw new FilterError(`--where takes <field><op><value>, with op one of ${WHERE_OPERATORS.join(" ")}, not ${JSON.stringify(expr)}`);
|
|
98
|
+
const [, field, op, rawValue] = m as unknown as [string, string, WhereOperator, string];
|
|
99
|
+
const raw = rawValue.trim();
|
|
100
|
+
const found = resolveField(field, fields);
|
|
101
|
+
if (!found) throw new FilterError(`--where ${field}: no such field; this read filters on ${names}`);
|
|
102
|
+
const { base, path, type } = found;
|
|
103
|
+
if (type === "json" && path.length === 0) throw new FilterError(`--where ${field}: holds an object; name a key under it, such as ${field}.<key>`);
|
|
104
|
+
const clause = (value: WhereClause["value"]): WhereClause => ({ field, base, path, type, op, raw, value });
|
|
105
|
+
if (raw === "null") {
|
|
106
|
+
if (op !== "=" && op !== "!=") throw new FilterError(`--where ${field}${op}null: null takes = or != only`);
|
|
107
|
+
return clause(null);
|
|
108
|
+
}
|
|
109
|
+
if (op === "~" && type !== "text" && type !== "json") throw new FilterError(`--where ${field}~: ~ (contains) takes a text field; ${field} is ${type}`);
|
|
110
|
+
switch (type) {
|
|
111
|
+
case "number": {
|
|
112
|
+
const n = Number(raw);
|
|
113
|
+
if (raw === "" || !Number.isFinite(n)) throw new FilterError(`--where ${field}: ${field} is a number, not ${JSON.stringify(raw)}`);
|
|
114
|
+
return clause(n);
|
|
115
|
+
}
|
|
116
|
+
case "boolean": {
|
|
117
|
+
if (ORDERED.includes(op)) throw new FilterError(`--where ${field}${op}: a boolean takes = or != only`);
|
|
118
|
+
if (raw !== "true" && raw !== "false") throw new FilterError(`--where ${field}: ${field} is true or false, not ${JSON.stringify(raw)}`);
|
|
119
|
+
return clause(raw === "true");
|
|
120
|
+
}
|
|
121
|
+
case "time": {
|
|
122
|
+
const ms = parseInstant(raw, now);
|
|
123
|
+
if (ms === null) throw new FilterError(`--where ${field}: ${field} is a time; give an ISO 8601 date or time, or a span back from now such as 24h or 7d, not ${JSON.stringify(raw)}`);
|
|
124
|
+
return clause(ms);
|
|
125
|
+
}
|
|
126
|
+
default:
|
|
127
|
+
return clause(raw);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The value at a clause's field in `row`, parsing a JSON text on the way into a `json` field. */
|
|
133
|
+
function valueAt(row: unknown, c: WhereClause): unknown {
|
|
134
|
+
let v: unknown = row;
|
|
135
|
+
for (const key of c.base.split(".")) v = v !== null && typeof v === "object" ? (v as Record<string, unknown>)[key] : undefined;
|
|
136
|
+
for (const key of c.path) {
|
|
137
|
+
if (typeof v === "string") {
|
|
138
|
+
try {
|
|
139
|
+
v = JSON.parse(v);
|
|
140
|
+
} catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
v = v !== null && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>)[key] : undefined;
|
|
145
|
+
}
|
|
146
|
+
return v;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function compare(a: number | string, b: number | string, op: WhereOperator): boolean {
|
|
150
|
+
switch (op) {
|
|
151
|
+
case "=": return a === b;
|
|
152
|
+
case "!=": return a !== b;
|
|
153
|
+
case "<": return a < b;
|
|
154
|
+
case "<=": return a <= b;
|
|
155
|
+
case ">": return a > b;
|
|
156
|
+
case ">=": return a >= b;
|
|
157
|
+
case "~": return String(a).includes(String(b));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Whether one clause holds for `row`. */
|
|
162
|
+
export function clauseHolds(row: unknown, c: WhereClause): boolean {
|
|
163
|
+
const v = valueAt(row, c);
|
|
164
|
+
if (c.value === null) return c.op === "=" ? v === null || v === undefined : v !== null && v !== undefined;
|
|
165
|
+
if (v === null || v === undefined) return c.op === "!=";
|
|
166
|
+
switch (c.type) {
|
|
167
|
+
case "number":
|
|
168
|
+
return typeof v === "number" && compare(v, c.value as number, c.op);
|
|
169
|
+
case "boolean":
|
|
170
|
+
return typeof v === "boolean" ? (c.op === "=") === (v === c.value) : false;
|
|
171
|
+
case "time": {
|
|
172
|
+
const ms = typeof v === "string" ? Date.parse(v) : typeof v === "number" ? v : NaN;
|
|
173
|
+
return !Number.isNaN(ms) && compare(ms, c.value as number, c.op);
|
|
174
|
+
}
|
|
175
|
+
case "text":
|
|
176
|
+
return typeof v === "string" && compare(v, c.value as string, c.op);
|
|
177
|
+
case "json": {
|
|
178
|
+
// Compared as the type the value holds there.
|
|
179
|
+
if (typeof v === "number" && c.op !== "~") {
|
|
180
|
+
const n = Number(c.raw);
|
|
181
|
+
return Number.isFinite(n) && compare(v, n, c.op);
|
|
182
|
+
}
|
|
183
|
+
if (typeof v === "boolean") return (c.raw === "true" || c.raw === "false") && (c.op === "=" || c.op === "!=") ? (c.op === "=") === (String(v) === c.raw) : false;
|
|
184
|
+
const s = typeof v === "string" ? v : JSON.stringify(v);
|
|
185
|
+
return compare(s, c.raw, c.op);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Whether every clause holds for `row`. */
|
|
191
|
+
export function matchesWhere(row: unknown, clauses: readonly WhereClause[]): boolean {
|
|
192
|
+
return clauses.every((c) => clauseHolds(row, c));
|
|
193
|
+
}
|
package/src/run-event.ts
CHANGED
|
@@ -58,7 +58,7 @@ export type RunEventStepStatus = "ok" | "failed" | "skipped" | "cached" | "gated
|
|
|
58
58
|
export type RunEventSkipReason = "receipt" | "resumed" | "refused" | "not-run";
|
|
59
59
|
|
|
60
60
|
/** How a gate's approval passed, when it was more than one person's approval of that gate. */
|
|
61
|
-
export type RunEventGateVia = "quorum" | "policy" | "point" | "alias" | "empty";
|
|
61
|
+
export type RunEventGateVia = "quorum" | "policy" | "point" | "alias" | "empty" | "workspace";
|
|
62
62
|
|
|
63
63
|
/** The run every event of a stream names: the same on every line. */
|
|
64
64
|
export interface RunEventRun {
|
|
@@ -31,6 +31,7 @@ describe("the stage-hooks bundle", () => {
|
|
|
31
31
|
"packages/core/src/op/gate-quorum.ts",
|
|
32
32
|
"packages/core/src/op/op-waves.ts",
|
|
33
33
|
"packages/core/src/op/stage-hooks.ts",
|
|
34
|
+
"packages/core/src/wave-blocked.ts",
|
|
34
35
|
"packages/core/src/wave-layout.ts",
|
|
35
36
|
]);
|
|
36
37
|
expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(48 * 1024);
|
package/src/terraform/carve.ts
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import { inboundEdges, outboundEdges } from "./graph";
|
|
15
15
|
import { foldParentOf, resolveTier } from "./tier-map";
|
|
16
|
-
import { scoreEstate, type ScoreOptions } from "./score";
|
|
17
|
-
import type
|
|
16
|
+
import { scoreEstate, type Peelability, type ScoreOptions } from "./score";
|
|
17
|
+
import { adjacencyOf, type Adjacency } from "../graph-walk";
|
|
18
|
+
import type { TfEdge, TfGraph, TfNode } from "./types";
|
|
18
19
|
|
|
19
20
|
/** One resource in the carve set (the selected node plus its folded sub-resources). */
|
|
20
21
|
export interface CarveMember {
|
|
@@ -85,24 +86,53 @@ export interface CarveReport {
|
|
|
85
86
|
* that fold into it (same name, a `foldParentOf` child type whose parent is the
|
|
86
87
|
* selection). Those are carried along and inlined, not left behind.
|
|
87
88
|
*/
|
|
88
|
-
export function resolveCarveSet(graph: TfGraph, target: string): CarveMember[] {
|
|
89
|
-
const byAddress =
|
|
89
|
+
export function resolveCarveSet(graph: TfGraph, target: string, index?: BoundaryIndex): CarveMember[] {
|
|
90
|
+
const { byAddress, foldChildren } = index ?? boundaryIndex(graph);
|
|
90
91
|
const selected = byAddress.get(target);
|
|
91
92
|
if (!selected) return [];
|
|
92
93
|
|
|
93
94
|
const members: CarveMember[] = [{ address: selected.address, type: selected.type }];
|
|
94
95
|
if (selected.kind === "resource" && selected.type) {
|
|
95
|
-
for (const node of
|
|
96
|
-
|
|
97
|
-
const parentType = foldParentOf(node.type);
|
|
98
|
-
if (parentType === selected.type && node.name === selected.name) {
|
|
99
|
-
members.push({ address: node.address, type: node.type, foldedInto: selected.address });
|
|
100
|
-
}
|
|
96
|
+
for (const node of foldChildren.get(foldKey(selected.type, selected.name)) ?? []) {
|
|
97
|
+
members.push({ address: node.address, type: node.type, foldedInto: selected.address });
|
|
101
98
|
}
|
|
102
99
|
}
|
|
103
100
|
return members;
|
|
104
101
|
}
|
|
105
102
|
|
|
103
|
+
const foldKey = (parentType: string, name: string): string => `${parentType}\u001f${name}`;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* What `boundaryReport` looks up per target, built once per graph: nodes by
|
|
107
|
+
* address, the edges by both ends, and each fold parent's children in graph
|
|
108
|
+
* order. Without it each report scans the node and edge lists itself, which
|
|
109
|
+
* over a whole estate is O(V·E) (#4391).
|
|
110
|
+
*/
|
|
111
|
+
export interface BoundaryIndex {
|
|
112
|
+
byAddress: Map<string, TfNode>;
|
|
113
|
+
adjacency: Adjacency<TfEdge>;
|
|
114
|
+
/** `foldKey(parent type, name)` → resource nodes that fold into that parent. */
|
|
115
|
+
foldChildren: Map<string, TfNode[]>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function boundaryIndex(graph: TfGraph): BoundaryIndex {
|
|
119
|
+
const foldChildren = new Map<string, TfNode[]>();
|
|
120
|
+
for (const node of graph.nodes) {
|
|
121
|
+
if (node.kind !== "resource" || !node.type) continue;
|
|
122
|
+
const parentType = foldParentOf(node.type);
|
|
123
|
+
if (!parentType) continue;
|
|
124
|
+
const key = foldKey(parentType, node.name);
|
|
125
|
+
const list = foldChildren.get(key);
|
|
126
|
+
if (list) list.push(node);
|
|
127
|
+
else foldChildren.set(key, [node]);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
byAddress: new Map(graph.nodes.map((n) => [n.address, n])),
|
|
131
|
+
adjacency: adjacencyOf(graph.edges),
|
|
132
|
+
foldChildren,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
106
136
|
/**
|
|
107
137
|
* Which source the graph came from, and so which vocabulary its bridge patches
|
|
108
138
|
* are named in. Terraform is the default; `cdk` is the read-only cloud-assembly
|
|
@@ -115,6 +145,13 @@ export interface BoundaryOptions {
|
|
|
115
145
|
dialect?: CarveDialect;
|
|
116
146
|
/** Scoring hooks, so `peelability` is the same number the ranking reported. */
|
|
117
147
|
score?: ScoreOptions;
|
|
148
|
+
/**
|
|
149
|
+
* The target's score, when the caller already holds it (`carve advise`).
|
|
150
|
+
* Without it the estate is scored to find it (#4391).
|
|
151
|
+
*/
|
|
152
|
+
peelability?: Pick<Peelability, "score">;
|
|
153
|
+
/** `boundaryIndex(graph)`, shared across calls on one graph. */
|
|
154
|
+
index?: BoundaryIndex;
|
|
118
155
|
}
|
|
119
156
|
|
|
120
157
|
/** The bridge patch an inbound cut needs, per source. */
|
|
@@ -131,7 +168,7 @@ function inboundBridge(dialect: CarveDialect, fromOutput: boolean): BoundaryEdge
|
|
|
131
168
|
*/
|
|
132
169
|
export function boundaryReport(graph: TfGraph, target: string, opts: BoundaryOptions = {}): CarveReport | null {
|
|
133
170
|
const dialect = opts.dialect ?? "terraform";
|
|
134
|
-
const carveSet = resolveCarveSet(graph, target);
|
|
171
|
+
const carveSet = resolveCarveSet(graph, target, opts.index);
|
|
135
172
|
if (carveSet.length === 0) return null;
|
|
136
173
|
const inSet = new Set(carveSet.map((m) => m.address));
|
|
137
174
|
|
|
@@ -139,7 +176,7 @@ export function boundaryReport(graph: TfGraph, target: string, opts: BoundaryOpt
|
|
|
139
176
|
const outbound: BoundaryEdge[] = [];
|
|
140
177
|
|
|
141
178
|
for (const member of carveSet) {
|
|
142
|
-
for (const e of inboundEdges(graph, member.address)) {
|
|
179
|
+
for (const e of (opts.index ? (opts.index.adjacency.in.get(member.address) ?? []) : inboundEdges(graph, member.address))) {
|
|
143
180
|
if (inSet.has(e.from)) continue; // internal edge
|
|
144
181
|
inbound.push({
|
|
145
182
|
direction: "inbound",
|
|
@@ -152,7 +189,7 @@ export function boundaryReport(graph: TfGraph, target: string, opts: BoundaryOpt
|
|
|
152
189
|
...(e.crossStack ? { crossStack: true } : {}),
|
|
153
190
|
});
|
|
154
191
|
}
|
|
155
|
-
for (const e of outboundEdges(graph, member.address)) {
|
|
192
|
+
for (const e of (opts.index ? (opts.index.adjacency.out.get(member.address) ?? []) : outboundEdges(graph, member.address))) {
|
|
156
193
|
if (inSet.has(e.to)) continue; // internal edge
|
|
157
194
|
outbound.push({
|
|
158
195
|
direction: "outbound",
|
|
@@ -167,13 +204,13 @@ export function boundaryReport(graph: TfGraph, target: string, opts: BoundaryOpt
|
|
|
167
204
|
}
|
|
168
205
|
}
|
|
169
206
|
|
|
170
|
-
const score = scoreEstate(graph, opts.score).find((r) => r.address === target);
|
|
207
|
+
const score = opts.peelability ?? scoreEstate(graph, { ...opts.score, radius: false }).find((r) => r.address === target);
|
|
171
208
|
const diagnostics: string[] = [];
|
|
172
209
|
const tierOf = opts.score?.tierOf ?? resolveTier;
|
|
173
210
|
if (
|
|
174
211
|
carveSet[0].type &&
|
|
175
212
|
tierOf(carveSet[0].type) === null &&
|
|
176
|
-
graph.nodes.
|
|
213
|
+
(opts.index?.byAddress ?? new Map(graph.nodes.map((n) => [n.address, n]))).get(target)?.kind === "resource"
|
|
177
214
|
) {
|
|
178
215
|
diagnostics.push(`No known native mapping for ${carveSet[0].type} — emit will have no target type.`);
|
|
179
216
|
}
|