@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,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dbt project's models as outputs (#3935), read beside its sources by the
|
|
3
|
+
* `dbt-project` reader.
|
|
4
|
+
*
|
|
5
|
+
* A model exposes `<schema>.<model>` and, for each column its YAML lists,
|
|
6
|
+
* `<schema>.<model>.<column>`, named by the contract of #3931: names as
|
|
7
|
+
* declared, Postgres case folding, never rendered per target. As with
|
|
8
|
+
* sources, nothing runs: dbt, Jinja and `profiles.yml` stay unread.
|
|
9
|
+
*
|
|
10
|
+
* - A model is a `.sql` file under the project's `model-paths`. Its name is
|
|
11
|
+
* the file's name, or its `alias`. Python models, seeds and snapshots are
|
|
12
|
+
* not read.
|
|
13
|
+
* - `enabled`, `schema` and `alias` come from the first of: the model's own
|
|
14
|
+
* `{{ config(...) }}`, the `config:` of its entry in a model YAML, then the
|
|
15
|
+
* `+` keys of `dbt_project.yml` that match the model's path (the project's
|
|
16
|
+
* name, each directory under the model path, the model's name), the
|
|
17
|
+
* deepest first.
|
|
18
|
+
* - The schema is the member's `modelSchema` field when it is set, and
|
|
19
|
+
* dbt's config is then not read for it. Otherwise it is the schema dbt
|
|
20
|
+
* config names, but dbt's default `generate_schema_name` joins the target's
|
|
21
|
+
* schema to a custom one (`<target_schema>_<custom>`), and a model with no
|
|
22
|
+
* custom schema lands in the target's schema, so the name depends on the
|
|
23
|
+
* target. The model is then a gap that says to set `modelSchema`. The
|
|
24
|
+
* exception is a project whose `generate_schema_name` macro is the usual
|
|
25
|
+
* override (the custom schema as it stands, the target's schema when there
|
|
26
|
+
* is none): a model with a custom schema is named by it. Any other
|
|
27
|
+
* override is not evaluated, and every model is a gap.
|
|
28
|
+
* - A templated schema, alias or `enabled`, or one that is not a plain
|
|
29
|
+
* string, is a gap; chant never renders it.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { posix } from "node:path";
|
|
33
|
+
import { parseYAMLDocument } from "../yaml";
|
|
34
|
+
import type { KindReadOutput, KindLinkRead, KindReaderInput } from "./kind-link-readers";
|
|
35
|
+
import {
|
|
36
|
+
isObject,
|
|
37
|
+
isTemplated,
|
|
38
|
+
keyLine,
|
|
39
|
+
lines,
|
|
40
|
+
nameLine,
|
|
41
|
+
scalar,
|
|
42
|
+
yamlFiles,
|
|
43
|
+
type Dialect,
|
|
44
|
+
type Quoting,
|
|
45
|
+
} from "./dbt-project-common";
|
|
46
|
+
import { joinPath, skippedDir, type WorkspaceTree } from "./tree";
|
|
47
|
+
|
|
48
|
+
export interface DbtModelsContext {
|
|
49
|
+
input: KindReaderInput;
|
|
50
|
+
project: Record<string, unknown>;
|
|
51
|
+
projectFile: string;
|
|
52
|
+
/** The tree-relative model paths, already checked. */
|
|
53
|
+
paths: string[];
|
|
54
|
+
quoting: Quoting;
|
|
55
|
+
dialect: Dialect;
|
|
56
|
+
result: KindLinkRead;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A config value found for a model: what it says, or that it can't be read. */
|
|
60
|
+
type Setting = { status: "unset" } | { status: "value"; value: unknown; at: string } | { status: "dynamic"; at: string; text: string };
|
|
61
|
+
|
|
62
|
+
interface ModelConfig {
|
|
63
|
+
schema: Setting;
|
|
64
|
+
alias: Setting;
|
|
65
|
+
enabled: Setting;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const KEYS = ["schema", "alias", "enabled"] as const;
|
|
69
|
+
type Key = (typeof KEYS)[number];
|
|
70
|
+
|
|
71
|
+
export function readDbtModels(ctx: DbtModelsContext): void {
|
|
72
|
+
const { input, project, projectFile, paths, quoting, dialect, result } = ctx;
|
|
73
|
+
const { tree, dir, fields } = input;
|
|
74
|
+
const gaps = result.gaps;
|
|
75
|
+
const outputs: KindReadOutput[] = (result.outputs ??= []);
|
|
76
|
+
const modelSchema = typeof fields?.modelSchema === "string" && fields.modelSchema !== "" ? fields.modelSchema : null;
|
|
77
|
+
const name = (v: string, quoted: boolean) => (dialect === "postgres" && !quoted ? v.toLowerCase() : v);
|
|
78
|
+
|
|
79
|
+
const projectName = scalar(project.name);
|
|
80
|
+
const macro = modelSchema === null ? schemaMacro(tree, dir, project) : { kind: "default" as const };
|
|
81
|
+
|
|
82
|
+
// Model YAML: config and columns, by model name.
|
|
83
|
+
const yamlModels = new Map<string, { config: Record<string, unknown>; columns: { name: string; quote: boolean; line: number; file: string }[]; file: string }>();
|
|
84
|
+
for (const p of paths) {
|
|
85
|
+
for (const file of yamlFiles(tree, p)) {
|
|
86
|
+
let text: string;
|
|
87
|
+
let doc: unknown;
|
|
88
|
+
try {
|
|
89
|
+
text = tree.read(file);
|
|
90
|
+
doc = parseYAMLDocument(text);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// The sources pass reports an unreadable file, as a gap that leaves outputs out.
|
|
93
|
+
void err;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!isObject(doc) || !Array.isArray(doc.models)) continue;
|
|
97
|
+
const fileLines = lines(text);
|
|
98
|
+
let cursor = keyLine(fileLines, "models");
|
|
99
|
+
for (const entry of doc.models) {
|
|
100
|
+
if (!isObject(entry)) continue;
|
|
101
|
+
const modelName = scalar(entry.name);
|
|
102
|
+
cursor = nameLine(fileLines, modelName, cursor);
|
|
103
|
+
if (modelName === undefined) continue;
|
|
104
|
+
const cols: { name: string; quote: boolean; line: number; file: string }[] = [];
|
|
105
|
+
let columnCursor = cursor;
|
|
106
|
+
for (const column of Array.isArray(entry.columns) ? entry.columns : []) {
|
|
107
|
+
if (!isObject(column)) continue;
|
|
108
|
+
const columnName = scalar(column.name);
|
|
109
|
+
columnCursor = nameLine(fileLines, columnName, columnCursor);
|
|
110
|
+
if (columnName === undefined || isTemplated(columnName)) {
|
|
111
|
+
gaps.push({ file, line: columnCursor, message: `model ${modelName}: column ${JSON.stringify(column.name)} is a Jinja template or not a name, which chant does not render; it was not read`, outputs: true });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
cols.push({ name: columnName, quote: column.quote === true, line: columnCursor, file });
|
|
115
|
+
}
|
|
116
|
+
const config = isObject(entry.config) ? { ...entry.config } : {};
|
|
117
|
+
yamlModels.set(modelName, { config, columns: cols, file });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const seen = new Set<string>();
|
|
123
|
+
for (const p of paths) {
|
|
124
|
+
for (const file of sqlFiles(tree, p)) {
|
|
125
|
+
const modelName = posix.basename(file).replace(/\.sql$/, "");
|
|
126
|
+
if (seen.has(modelName)) continue;
|
|
127
|
+
seen.add(modelName);
|
|
128
|
+
const text = tree.read(file);
|
|
129
|
+
const inline = readInlineConfig(text);
|
|
130
|
+
const yaml = yamlModels.get(modelName);
|
|
131
|
+
const rel = posix.relative(p || ".", posix.dirname(file));
|
|
132
|
+
const segments = [...(rel === "" || rel === "." ? [] : rel.split("/")), modelName];
|
|
133
|
+
const fromProject = projectName ? projectConfig(project.models, projectName, segments) : {};
|
|
134
|
+
const config: ModelConfig = {
|
|
135
|
+
schema: pick(inline.schema, setting(yaml?.config.schema, `config of ${modelName} in ${yaml?.file}`), setting(fromProject.schema, `+schema in ${projectFile}`)),
|
|
136
|
+
alias: pick(inline.alias, setting(yaml?.config.alias, `config of ${modelName} in ${yaml?.file}`), setting(fromProject.alias, `+alias in ${projectFile}`)),
|
|
137
|
+
enabled: pick(inline.enabled, setting(yaml?.config.enabled, `config of ${modelName} in ${yaml?.file}`), setting(fromProject.enabled, `+enabled in ${projectFile}`)),
|
|
138
|
+
};
|
|
139
|
+
const line = inline.line;
|
|
140
|
+
const gap = (message: string) => gaps.push({ file, line, message: `model ${modelName}: ${message}`, outputs: true });
|
|
141
|
+
|
|
142
|
+
const enabled = config.enabled;
|
|
143
|
+
if (enabled.status === "dynamic") {
|
|
144
|
+
gap(`enabled is ${enabled.text} (${enabled.at}), which chant does not render; the model was not read`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (enabled.status === "value" && enabled.value === false) continue;
|
|
148
|
+
if (enabled.status === "value" && enabled.value !== true) {
|
|
149
|
+
gap(`enabled is ${JSON.stringify(enabled.value)} (${enabled.at}), not true or false; the model was not read`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// The model's name: its alias, or the file's.
|
|
154
|
+
let relation = modelName;
|
|
155
|
+
if (config.alias.status === "dynamic") {
|
|
156
|
+
gap(`alias ${config.alias.text} (${config.alias.at}) is a Jinja template or not a name, which chant does not render; the model was not read`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (config.alias.status === "value") {
|
|
160
|
+
const alias = scalar(config.alias.value);
|
|
161
|
+
if (alias === undefined || alias === "" || isTemplated(alias)) {
|
|
162
|
+
gap(`alias ${JSON.stringify(config.alias.value)} (${config.alias.at}) is a Jinja template or not a name, which chant does not render; the model was not read`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
relation = alias;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The schema it lands in.
|
|
169
|
+
let schema: string;
|
|
170
|
+
if (modelSchema !== null) {
|
|
171
|
+
schema = modelSchema;
|
|
172
|
+
} else {
|
|
173
|
+
const resolved = modelSchemaFromConfig(config.schema, macro);
|
|
174
|
+
if (typeof resolved !== "string") {
|
|
175
|
+
gap(`${resolved.message}; set modelSchema on the dbt member to name the schema its models land in`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
schema = resolved;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const model = `${name(schema, quoting.schema)}.${name(relation, quoting.identifier)}`;
|
|
182
|
+
outputs.push({ name: model, file, line });
|
|
183
|
+
for (const column of yaml?.columns ?? []) outputs.push({ name: `${model}.${name(column.name, column.quote)}`, file: column.file, line: column.line });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The schema config names, or why that does not give a name. */
|
|
189
|
+
function modelSchemaFromConfig(schema: Setting, macro: SchemaMacro): string | { message: string } {
|
|
190
|
+
if (schema.status === "dynamic") return { message: `schema ${schema.text} (${schema.at}) is a Jinja template, which chant does not render for a target` };
|
|
191
|
+
if (macro.kind === "other") return { message: `the project overrides generate_schema_name (${macro.file}:${macro.line}), which chant does not evaluate, so it cannot tell which schema the model lands in` };
|
|
192
|
+
if (schema.status === "unset") return { message: "it sets no schema, so it lands in the target's schema, which profiles.yml names and chant does not read" };
|
|
193
|
+
const custom = scalar(schema.value);
|
|
194
|
+
if (custom === undefined || custom === "" || isTemplated(custom)) return { message: `schema ${JSON.stringify(schema.value)} (${schema.at}) is a Jinja template or not a name, which chant does not render` };
|
|
195
|
+
if (macro.kind === "custom-as-is") return custom;
|
|
196
|
+
return { message: `schema ${JSON.stringify(custom)} (${schema.at}) is joined to the target's schema by dbt's default generate_schema_name (<target_schema>_${custom}), so the name depends on the target` };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
type SchemaMacro = { kind: "default" } | { kind: "custom-as-is" } | { kind: "other"; file: string; line: number };
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Whether the project overrides `generate_schema_name`, in a `.sql` file under
|
|
203
|
+
* `macro-paths` (`macros` by default). The usual override returns the custom
|
|
204
|
+
* schema as it stands: with the tags removed, its body is the target's schema
|
|
205
|
+
* and `{{ custom_schema_name | trim }}`, one per branch of an `is none` test.
|
|
206
|
+
* Any other body is not evaluated.
|
|
207
|
+
*/
|
|
208
|
+
function schemaMacro(tree: WorkspaceTree, dir: string, project: Record<string, unknown>): SchemaMacro {
|
|
209
|
+
const raw = project["macro-paths"] ?? ["macros"];
|
|
210
|
+
if (!Array.isArray(raw)) return { kind: "default" };
|
|
211
|
+
let found: SchemaMacro = { kind: "default" };
|
|
212
|
+
for (const p of raw) {
|
|
213
|
+
if (typeof p !== "string" || isTemplated(p) || posix.isAbsolute(p)) continue;
|
|
214
|
+
const at = posix.normalize(joinPath(dir, p));
|
|
215
|
+
if (at === ".." || at.startsWith("../")) continue;
|
|
216
|
+
for (const file of sqlFiles(tree, at === "." ? "" : at)) {
|
|
217
|
+
let text: string;
|
|
218
|
+
try {
|
|
219
|
+
text = tree.read(file);
|
|
220
|
+
} catch {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const start = /\{%-?\s*macro\s+generate_schema_name\s*\(/.exec(text);
|
|
224
|
+
if (!start) continue;
|
|
225
|
+
const line = text.slice(0, start.index).split("\n").length;
|
|
226
|
+
const end = /\{%-?\s*endmacro\s*-?%\}/.exec(text.slice(start.index));
|
|
227
|
+
const body = text.slice(start.index + start[0].length, end ? start.index + end.index : undefined);
|
|
228
|
+
const afterSignature = body.slice(body.indexOf("%}") + 2);
|
|
229
|
+
const flat = afterSignature.replace(/\{%[\s\S]*?%\}/g, "").replace(/\s+/g, "");
|
|
230
|
+
const asIs = /^\{\{(?:target\.schema|default_schema)\}\}\{\{custom_schema_name\|trim\}\}$/.test(flat) && /custom_schema_name(?:is|==)none/.test(afterSignature.replace(/\s+/g, ""));
|
|
231
|
+
found = asIs ? { kind: "custom-as-is" } : { kind: "other", file, line };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return found;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** `.sql` files under tree-relative directory `dir`, skipping dot directories and node_modules; sorted. */
|
|
238
|
+
function sqlFiles(tree: WorkspaceTree, dir: string): string[] {
|
|
239
|
+
if (dir !== "" && tree.stat(dir) !== "dir") return [];
|
|
240
|
+
const out: string[] = [];
|
|
241
|
+
const walk = (at: string) => {
|
|
242
|
+
for (const e of tree.list(at) ?? []) {
|
|
243
|
+
const path = joinPath(at, e.name);
|
|
244
|
+
if (e.type === "dir") {
|
|
245
|
+
if (!skippedDir(e.name)) walk(path);
|
|
246
|
+
} else if (/\.sql$/.test(e.name)) {
|
|
247
|
+
out.push(path);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
walk(dir);
|
|
252
|
+
return out.sort();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The first setting that is set: the model's own, then its YAML's, then the project's. */
|
|
256
|
+
function pick(...settings: Setting[]): Setting {
|
|
257
|
+
return settings.find((s) => s.status !== "unset") ?? { status: "unset" };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function setting(value: unknown, at: string): Setting {
|
|
261
|
+
if (value === undefined || value === null) return { status: "unset" };
|
|
262
|
+
if (typeof value === "string" && isTemplated(value)) return { status: "dynamic", at, text: JSON.stringify(value) };
|
|
263
|
+
return { status: "value", value, at };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const PROJECT_KEYS = new Set<string>(KEYS);
|
|
267
|
+
|
|
268
|
+
/** The configs `dbt_project.yml` sets for a model: the project's block, then each directory down to the model's name; deeper wins. */
|
|
269
|
+
function projectConfig(models: unknown, projectName: string, segments: string[]): Partial<Record<Key, unknown>> {
|
|
270
|
+
const out: Partial<Record<Key, unknown>> = {};
|
|
271
|
+
if (!isObject(models)) return out;
|
|
272
|
+
let node: unknown = models[projectName];
|
|
273
|
+
const take = (n: Record<string, unknown>) => {
|
|
274
|
+
for (const [k, v] of Object.entries(n)) {
|
|
275
|
+
if (k.startsWith("+") && PROJECT_KEYS.has(k.slice(1))) out[k.slice(1) as Key] = v;
|
|
276
|
+
else if (PROJECT_KEYS.has(k) && !isObject(v)) out[k as Key] = v;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
if (!isObject(node)) return out;
|
|
280
|
+
take(node);
|
|
281
|
+
for (const seg of segments) {
|
|
282
|
+
node = node[seg];
|
|
283
|
+
if (!isObject(node)) break;
|
|
284
|
+
take(node);
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** What a model's own `{{ config(...) }}` calls set, and the line of the first one (1 when none). */
|
|
290
|
+
function readInlineConfig(text: string): ModelConfig & { line: number } {
|
|
291
|
+
const out: ModelConfig & { line: number } = { schema: { status: "unset" }, alias: { status: "unset" }, enabled: { status: "unset" }, line: 1 };
|
|
292
|
+
// Comments are blanked, not removed, so lines stay where they were.
|
|
293
|
+
const blank = (m: string) => m.replace(/[^\n]/g, " ");
|
|
294
|
+
const code = text.replace(/\{#[\s\S]*?#\}/g, blank).replace(/--[^\n]*/g, blank);
|
|
295
|
+
const open = /\{\{-?\s*config\s*\(/g;
|
|
296
|
+
let first = true;
|
|
297
|
+
for (let m = open.exec(code); m; m = open.exec(code)) {
|
|
298
|
+
const args = balanced(code, open.lastIndex);
|
|
299
|
+
if (args === undefined) continue;
|
|
300
|
+
if (first) {
|
|
301
|
+
out.line = code.slice(0, m.index).split("\n").length;
|
|
302
|
+
first = false;
|
|
303
|
+
}
|
|
304
|
+
for (const arg of splitTop(args)) {
|
|
305
|
+
const kv = /^\s*([A-Za-z_]\w*)\s*=\s*([\s\S]*?)\s*$/.exec(arg);
|
|
306
|
+
if (!kv || !(PROJECT_KEYS as Set<string>).has(kv[1])) continue;
|
|
307
|
+
const at = "the model's config()";
|
|
308
|
+
const raw = kv[2];
|
|
309
|
+
const str = /^(['"])((?:(?!\1)[^\\]|\\.)*)\1$/.exec(raw);
|
|
310
|
+
let s: Setting;
|
|
311
|
+
if (str) s = isTemplated(str[2]) ? { status: "dynamic", at, text: raw } : { status: "value", value: str[2], at };
|
|
312
|
+
else if (/^true$/i.test(raw)) s = { status: "value", value: true, at };
|
|
313
|
+
else if (/^false$/i.test(raw)) s = { status: "value", value: false, at };
|
|
314
|
+
else if (/^(none|null)$/i.test(raw)) s = { status: "unset" };
|
|
315
|
+
else s = { status: "dynamic", at, text: raw };
|
|
316
|
+
out[kv[1] as Key] = s;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** The text from `from` up to the parenthesis that closes the one before it, or undefined. */
|
|
323
|
+
function balanced(text: string, from: number): string | undefined {
|
|
324
|
+
let depth = 1;
|
|
325
|
+
let quote: string | null = null;
|
|
326
|
+
for (let i = from; i < text.length; i++) {
|
|
327
|
+
const c = text[i];
|
|
328
|
+
if (quote) {
|
|
329
|
+
if (c === "\\") i++;
|
|
330
|
+
else if (c === quote) quote = null;
|
|
331
|
+
} else if (c === "'" || c === '"') quote = c;
|
|
332
|
+
else if (c === "(" || c === "[" || c === "{") depth++;
|
|
333
|
+
else if (c === ")" || c === "]" || c === "}") {
|
|
334
|
+
depth--;
|
|
335
|
+
if (depth === 0) return text.slice(from, i);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** `text` split at the commas outside quotes and brackets. */
|
|
342
|
+
function splitTop(text: string): string[] {
|
|
343
|
+
const parts: string[] = [];
|
|
344
|
+
let depth = 0;
|
|
345
|
+
let quote: string | null = null;
|
|
346
|
+
let start = 0;
|
|
347
|
+
for (let i = 0; i < text.length; i++) {
|
|
348
|
+
const c = text[i];
|
|
349
|
+
if (quote) {
|
|
350
|
+
if (c === "\\") i++;
|
|
351
|
+
else if (c === quote) quote = null;
|
|
352
|
+
} else if (c === "'" || c === '"') quote = c;
|
|
353
|
+
else if (c === "(" || c === "[" || c === "{") depth++;
|
|
354
|
+
else if (c === ")" || c === "]" || c === "}") depth--;
|
|
355
|
+
else if (c === "," && depth === 0) {
|
|
356
|
+
parts.push(text.slice(start, i));
|
|
357
|
+
start = i + 1;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
parts.push(text.slice(start));
|
|
361
|
+
return parts;
|
|
362
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dbt member's models as outputs (#3935): the `dbt-project` reader's
|
|
3
|
+
* `outputs`, the schema rules, and `chant workspace check` over a downstream
|
|
4
|
+
* member that links to a model.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
10
|
+
import { cleanScratch, declaration, REPO, repo } from "./__fixtures__/contract-repo";
|
|
11
|
+
import { runDeclarationChecks } from "./checks";
|
|
12
|
+
import { parseDeclaration } from "./declaration";
|
|
13
|
+
import { readDbtProject } from "./dbt-project-reader";
|
|
14
|
+
import { resolveLinks, withReaderOutputs, type MemberHandles } from "./links";
|
|
15
|
+
import { workingTree } from "./tree";
|
|
16
|
+
|
|
17
|
+
afterAll(cleanScratch);
|
|
18
|
+
|
|
19
|
+
const SQL_KINDS = readFileSync(join(REPO, "lexicons", "sql", "workspace-kinds.json"), "utf-8");
|
|
20
|
+
const SQL_PACKAGE = JSON.stringify({ name: "@intentius/chant-lexicon-sql", version: "0.0.0", exports: { "./workspace-kinds": "./workspace-kinds.json" } });
|
|
21
|
+
|
|
22
|
+
const read = (files: Record<string, string>, fields: Record<string, unknown> = {}) => {
|
|
23
|
+
const root = repo({ "dbt_project.yml": "name: p\n", ...files });
|
|
24
|
+
return readDbtProject({ member: { name: "p" } as never, dir: "", fields: { sqlMember: null, dialect: "postgres", modelSchema: null, ...fields } as never, tree: workingTree(root) });
|
|
25
|
+
};
|
|
26
|
+
const names = (r: ReturnType<typeof read>) => (r.outputs ?? []).map((o) => o.name);
|
|
27
|
+
|
|
28
|
+
const ORDERS_SQL = "select 1 as id\n";
|
|
29
|
+
const ORDERS_YML = "version: 2\nmodels:\n - name: orders\n columns:\n - name: id\n - name: Total\n";
|
|
30
|
+
|
|
31
|
+
describe("model outputs with modelSchema (#3935)", () => {
|
|
32
|
+
test("each model is <modelSchema>.<model>, and each column its YAML lists is <modelSchema>.<model>.<column>", () => {
|
|
33
|
+
const r = read({ "models/orders.sql": ORDERS_SQL, "models/marts/customers.sql": ORDERS_SQL, "models/schema.yml": ORDERS_YML }, { modelSchema: "analytics" });
|
|
34
|
+
expect(names(r)).toEqual(["analytics.customers", "analytics.orders", "analytics.orders.id", "analytics.orders.total"]);
|
|
35
|
+
expect(r.gaps).toEqual([]);
|
|
36
|
+
expect(r.outputs!.find((o) => o.name === "analytics.orders")).toMatchObject({ file: "models/orders.sql", line: 1 });
|
|
37
|
+
expect(r.outputs!.find((o) => o.name === "analytics.orders.id")).toMatchObject({ file: "models/schema.yml", line: 5 });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("dbt config is not read for the schema when modelSchema is set, even a templated one", () => {
|
|
41
|
+
const r = read(
|
|
42
|
+
{ "models/a.sql": "{{ config(schema=var('s')) }}\nselect 1\n", "dbt_project.yml": "name: p\nmodels:\n p:\n +schema: \"{{ target.name }}\"\n" },
|
|
43
|
+
{ modelSchema: "analytics" },
|
|
44
|
+
);
|
|
45
|
+
expect(names(r)).toEqual(["analytics.a"]);
|
|
46
|
+
expect(r.gaps).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("alias overrides the model name, from config(), the YAML or dbt_project.yml; the closest wins", () => {
|
|
50
|
+
const r = read(
|
|
51
|
+
{
|
|
52
|
+
"dbt_project.yml": "name: p\nmodels:\n p:\n marts:\n +alias: from_project\n",
|
|
53
|
+
"models/marts/a.sql": "select 1\n",
|
|
54
|
+
"models/marts/b.sql": "select 1\n",
|
|
55
|
+
"models/marts/c.sql": "{{ config(alias='from_sql') }}\nselect 1\n",
|
|
56
|
+
"models/marts/m.yml": "models:\n - name: b\n config:\n alias: from_yaml\n - name: c\n config:\n alias: from_yaml\n",
|
|
57
|
+
},
|
|
58
|
+
{ modelSchema: "s" },
|
|
59
|
+
);
|
|
60
|
+
expect(names(r)).toEqual(["s.from_project", "s.from_yaml", "s.from_sql"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("a templated alias is a gap at the model's file and line, and the model is left out", () => {
|
|
64
|
+
const r = read({ "models/a.sql": "select 1\n\n{{ config(alias=var('x')) }}\n", "models/b.sql": "select 1\n" }, { modelSchema: "s" });
|
|
65
|
+
expect(names(r)).toEqual(["s.b"]);
|
|
66
|
+
expect(r.gaps).toEqual([expect.objectContaining({ file: "models/a.sql", line: 3, outputs: true, message: expect.stringMatching(/^model a: alias var\('x'\) \(the model's config\(\)\) is a Jinja template/) })]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("a disabled model is left out, by config(), the YAML or the project", () => {
|
|
70
|
+
const r = read(
|
|
71
|
+
{
|
|
72
|
+
"dbt_project.yml": "name: p\nmodels:\n p:\n old:\n +enabled: false\n",
|
|
73
|
+
"models/old/x.sql": "select 1\n",
|
|
74
|
+
"models/y.sql": "{{ config(enabled=false) }}\nselect 1\n",
|
|
75
|
+
"models/z.sql": "select 1\n",
|
|
76
|
+
"models/m.yml": "models:\n - name: z\n config:\n enabled: false\n - name: w\n",
|
|
77
|
+
"models/w.sql": "select 1\n",
|
|
78
|
+
},
|
|
79
|
+
{ modelSchema: "s" },
|
|
80
|
+
);
|
|
81
|
+
expect(names(r)).toEqual(["s.w"]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("postgres keeps a quoted name as written and folds an unquoted one; clickhouse keeps every name", () => {
|
|
85
|
+
const files = { "models/Orders.sql": "select 1\n", "models/m.yml": "models:\n - name: Orders\n columns:\n - name: CustomerId\n - name: OrderId\n quote: true\n" };
|
|
86
|
+
expect(names(read(files, { modelSchema: "Shop" }))).toEqual(["Shop.Orders", "Shop.Orders.customerid", "Shop.Orders.OrderId"]);
|
|
87
|
+
expect(names(read({ ...files, "dbt_project.yml": "name: p\nquoting:\n schema: false\n identifier: false\n" }, { modelSchema: "Shop" }))).toEqual(["shop.orders", "shop.orders.customerid", "shop.orders.OrderId"]);
|
|
88
|
+
expect(names(read(files, { modelSchema: "Shop", dialect: "clickhouse" }))).toEqual(["Shop.Orders", "Shop.Orders.CustomerId", "Shop.Orders.OrderId"]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("model-paths say where models live", () => {
|
|
92
|
+
const r = read({ "dbt_project.yml": "name: p\nmodel-paths: [transform]\n", "models/a.sql": "select 1\n", "transform/b.sql": "select 1\n" }, { modelSchema: "s" });
|
|
93
|
+
expect(names(r)).toEqual(["s.b"]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("model outputs without modelSchema (#3935)", () => {
|
|
98
|
+
test("under dbt's default generate_schema_name a custom schema depends on the target: a gap that says to set modelSchema", () => {
|
|
99
|
+
const r = read({ "dbt_project.yml": "name: p\nmodels:\n p:\n +schema: marts\n", "models/a.sql": "select 1\n" });
|
|
100
|
+
expect(names(r)).toEqual([]);
|
|
101
|
+
expect(r.gaps).toHaveLength(1);
|
|
102
|
+
expect(r.gaps[0]).toMatchObject({ file: "models/a.sql", line: 1, outputs: true });
|
|
103
|
+
expect(r.gaps[0].message).toMatch(/schema "marts" \(\+schema in dbt_project\.yml\) is joined to the target's schema by dbt's default generate_schema_name \(<target_schema>_marts\), so the name depends on the target; set modelSchema on the dbt member/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("a model with no schema lands in the target's schema: a gap", () => {
|
|
107
|
+
const r = read({ "models/a.sql": "select 1\n" });
|
|
108
|
+
expect(r.gaps[0].message).toMatch(/it sets no schema, so it lands in the target's schema.*set modelSchema/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("a templated schema is a gap, whatever the macro", () => {
|
|
112
|
+
const r = read({ "models/a.sql": "{{ config(schema=\"{{ var('s') }}\") }}\nselect 1\n" });
|
|
113
|
+
expect(r.gaps[0].message).toMatch(/schema .* \(the model's config\(\)\) is a Jinja template, which chant does not render for a target; set modelSchema/);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const AS_IS = "{% macro generate_schema_name(custom_schema_name, node) -%}\n {%- set default_schema = target.schema -%}\n {%- if custom_schema_name is none -%}\n {{ default_schema }}\n {%- else -%}\n {{ custom_schema_name | trim }}\n {%- endif -%}\n{%- endmacro %}\n";
|
|
117
|
+
|
|
118
|
+
test("with the usual generate_schema_name override, a custom schema names the model; config precedence is config(), YAML, project", () => {
|
|
119
|
+
const r = read({
|
|
120
|
+
"macros/schema.sql": AS_IS,
|
|
121
|
+
"dbt_project.yml": "name: p\nmodels:\n p:\n +schema: from_project\n marts:\n +schema: marts\n",
|
|
122
|
+
"models/a.sql": "select 1\n",
|
|
123
|
+
"models/marts/b.sql": "select 1\n",
|
|
124
|
+
"models/marts/c.sql": "select 1\n",
|
|
125
|
+
"models/marts/d.sql": "{{ config(schema='from_sql') }}\nselect 1\n",
|
|
126
|
+
"models/m.yml": "models:\n - name: c\n config:\n schema: from_yaml\n - name: d\n config:\n schema: from_yaml\n",
|
|
127
|
+
});
|
|
128
|
+
expect(names(r)).toEqual(["from_project.a", "marts.b", "from_yaml.c", "from_sql.d"]);
|
|
129
|
+
expect(r.gaps).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("under the override, a model with no custom schema still lands in the target's schema: a gap", () => {
|
|
133
|
+
const r = read({ "macros/schema.sql": AS_IS, "models/a.sql": "select 1\n" });
|
|
134
|
+
expect(names(r)).toEqual([]);
|
|
135
|
+
expect(r.gaps[0].message).toMatch(/lands in the target's schema/);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("any other generate_schema_name override is not evaluated: every model is a gap at the macro's line", () => {
|
|
139
|
+
const r = read({
|
|
140
|
+
"macros/schema.sql": "-- ours\n{% macro generate_schema_name(custom_schema_name, node) -%}\n {{ target.name }}_{{ custom_schema_name }}\n{%- endmacro %}\n",
|
|
141
|
+
"dbt_project.yml": "name: p\nmodels:\n p:\n +schema: marts\n",
|
|
142
|
+
"models/a.sql": "select 1\n",
|
|
143
|
+
});
|
|
144
|
+
expect(names(r)).toEqual([]);
|
|
145
|
+
expect(r.gaps[0].message).toMatch(/overrides generate_schema_name \(macros\/schema\.sql:2\).*set modelSchema/);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("modelSchema still names the models under an override chant can't evaluate", () => {
|
|
149
|
+
const r = read({ "macros/schema.sql": "{% macro generate_schema_name(c, n) %}{{ target.name }}{% endmacro %}", "models/a.sql": "select 1\n" }, { modelSchema: "s" });
|
|
150
|
+
expect(names(r)).toEqual(["s.a"]);
|
|
151
|
+
expect(r.gaps).toEqual([]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("config() is found past comments and across lines; a config call in a comment is not read", () => {
|
|
155
|
+
const r = read(
|
|
156
|
+
{ "models/a.sql": "-- {{ config(alias='no') }}\n{#- {{ config(alias='nor') }} -#}\n{{\n config(\n materialized='table',\n alias='yes'\n )\n}}\nselect 1\n" },
|
|
157
|
+
{ modelSchema: "s" },
|
|
158
|
+
);
|
|
159
|
+
expect(names(r)).toEqual(["s.yes"]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("withReaderOutputs (#3935)", () => {
|
|
164
|
+
const base: MemberHandles = { member: "analytics", outputs: [{ name: "kept" }], inputs: [], complete: true, why: null, exposesNone: false };
|
|
165
|
+
test("adds the reader's outputs, keeps the member's own and does not repeat a name", () => {
|
|
166
|
+
const out = withReaderOutputs(new Map([["analytics", base]]), new Map([["analytics", { reader: "dbt-project" as const, links: [], gaps: [], outputs: [{ name: "s.a", file: "f", line: 1 }, { name: "kept", file: "f", line: 1 }] }]]));
|
|
167
|
+
expect(out.get("analytics")!.outputs.map((o) => o.name)).toEqual(["kept", "s.a"]);
|
|
168
|
+
expect(out.get("analytics")!.complete).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
test("a gap flagged outputs makes the member incomplete, and a link to a name it lacks is unresolved", () => {
|
|
171
|
+
const out = withReaderOutputs(new Map([["analytics", base]]), new Map([["analytics", { reader: "dbt-project" as const, links: [], gaps: [{ file: "m.sql", line: 1, message: "model a: x", outputs: true }, { file: "s.yml", line: 1, message: "a source gap" }], outputs: [] }]]));
|
|
172
|
+
expect(out.get("analytics")).toMatchObject({ complete: false, why: "model a: x" });
|
|
173
|
+
});
|
|
174
|
+
test("returns the handles as they are when no reader reports outputs", () => {
|
|
175
|
+
const handles = new Map([["analytics", base]]);
|
|
176
|
+
expect(withReaderOutputs(handles, new Map([["analytics", { reader: "dbt-project" as const, links: [], gaps: [{ file: "s.yml", line: 1, message: "a source gap" }] }]]))).toBe(handles);
|
|
177
|
+
expect(withReaderOutputs(handles, undefined)).toBe(handles);
|
|
178
|
+
});
|
|
179
|
+
test("resolveLinks resolves a declared link to a reader output", () => {
|
|
180
|
+
const decl = parseDeclaration(
|
|
181
|
+
declaration([
|
|
182
|
+
{ name: "analytics", dir: "analytics", kind: "other", because: "x" },
|
|
183
|
+
{ name: "dash", dir: "dash", kind: "other", because: "x", links: [{ member: "analytics", output: "s.a" }] },
|
|
184
|
+
]),
|
|
185
|
+
"chant.workspace.json",
|
|
186
|
+
);
|
|
187
|
+
const handles = new Map([["analytics", base]]);
|
|
188
|
+
const read = new Map([["analytics", { reader: "dbt-project" as const, links: [], gaps: [], outputs: [{ name: "s.a", file: "f", line: 1 }] }]]);
|
|
189
|
+
expect(resolveLinks(decl, handles, undefined, read)[0]).toMatchObject({ origin: "declared", status: "resolved" });
|
|
190
|
+
expect(resolveLinks(decl, handles)[0]).toMatchObject({ status: "missing" });
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("a downstream member links to a dbt model, in chant workspace check (#3935)", () => {
|
|
195
|
+
const workspace = (opts: { models?: Record<string, string>; fields?: Record<string, unknown>; output?: string } = {}) =>
|
|
196
|
+
repo({
|
|
197
|
+
"chant.workspace.json": declaration(
|
|
198
|
+
[
|
|
199
|
+
{ name: "analytics", dir: "analytics", kind: "dbt", fields: opts.fields ?? { modelSchema: "analytics" } },
|
|
200
|
+
{ name: "dash", dir: "dash", kind: "other", because: "reads a model", suppress: [{ check: "WSP009", because: "a stand-in" }], links: [{ member: "analytics", output: opts.output ?? "analytics.orders" }] },
|
|
201
|
+
],
|
|
202
|
+
{ pins: [{ path: "plugins/sql" }] },
|
|
203
|
+
),
|
|
204
|
+
"plugins/sql/package.json": SQL_PACKAGE,
|
|
205
|
+
"plugins/sql/workspace-kinds.json": SQL_KINDS,
|
|
206
|
+
"dash/README": "",
|
|
207
|
+
"analytics/dbt_project.yml": "name: analytics\n",
|
|
208
|
+
"analytics/models/orders.sql": ORDERS_SQL,
|
|
209
|
+
"analytics/models/orders.yml": ORDERS_YML,
|
|
210
|
+
...(opts.models ?? {}),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("a link to a model and to a column resolves", async () => {
|
|
214
|
+
for (const output of ["analytics.orders", "analytics.orders.id"]) {
|
|
215
|
+
const report = await runDeclarationChecks(workspace({ output }));
|
|
216
|
+
expect(report.diagnostics).toEqual([]);
|
|
217
|
+
expect(report.links.find((r) => r.origin === "declared")).toMatchObject({ status: "resolved", producer: "analytics", output });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("removing the model fails check, naming the model and the outputs left", async () => {
|
|
222
|
+
expect((await runDeclarationChecks(workspace())).ok).toBe(true);
|
|
223
|
+
const gone = workspace();
|
|
224
|
+
const fs = await import("node:fs");
|
|
225
|
+
fs.rmSync(join(gone, "analytics/models/orders.sql"));
|
|
226
|
+
fs.rmSync(join(gone, "analytics/models/orders.yml"));
|
|
227
|
+
fs.writeFileSync(join(gone, "analytics/models/other.sql"), "select 1\n");
|
|
228
|
+
const failed = await runDeclarationChecks(gone);
|
|
229
|
+
expect(failed.ok).toBe(false);
|
|
230
|
+
expect(failed.diagnostics.map((d) => `${d.ruleId}:${d.severity}:${d.entity}`)).toEqual(["WSP093:error:dash"]);
|
|
231
|
+
expect(failed.diagnostics[0].message).toMatch(/analytics has no output analytics\.orders.*its outputs: analytics\.other/);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("removing a column from the model's YAML fails check", async () => {
|
|
235
|
+
const report = await runDeclarationChecks(workspace({ output: "analytics.orders.id", models: { "analytics/models/orders.yml": "models:\n - name: orders\n" } }));
|
|
236
|
+
expect(report.diagnostics.map((d) => d.ruleId)).toEqual(["WSP093"]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("a model the reader can't name leaves the member's outputs incomplete: the link is unresolved, not missing, and WSP099 says why", async () => {
|
|
240
|
+
const report = await runDeclarationChecks(
|
|
241
|
+
workspace({ fields: {}, output: "analytics.orders", models: { "analytics/dbt_project.yml": "name: analytics\nmodels:\n analytics:\n +schema: analytics\n" } }),
|
|
242
|
+
);
|
|
243
|
+
expect(report.links.find((r) => r.origin === "declared")).toMatchObject({ status: "unresolved" });
|
|
244
|
+
const ids = report.diagnostics.map((d) => `${d.ruleId}:${d.severity}`);
|
|
245
|
+
expect(ids).toContain("WSP099:info");
|
|
246
|
+
expect(report.diagnostics.find((d) => d.ruleId === "WSP099")!.message).toMatch(/set modelSchema on the dbt member/);
|
|
247
|
+
});
|
|
248
|
+
});
|