@runa-ai/runa-cli 0.5.72 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-V66FAQXB.js +1719 -0
- package/dist/cache-N7WNPEYF.js +111 -0
- package/dist/check-LOMVIRHX.js +12 -0
- package/dist/chunk-2APB25TT.js +442 -0
- package/dist/chunk-3FDQW524.js +544 -0
- package/dist/chunk-3WDV32GA.js +33 -0
- package/dist/chunk-5FT3F36G.js +59 -0
- package/dist/chunk-5NKWR4FF.js +254 -0
- package/dist/chunk-644FVGIQ.js +194 -0
- package/dist/chunk-6AALH2ED.js +121 -0
- package/dist/chunk-6FAU4IGR.js +63 -0
- package/dist/chunk-6Y3LAUGL.js +35 -0
- package/dist/chunk-7B5C6U2K.js +274 -0
- package/dist/chunk-AAIE4F2U.js +140 -0
- package/dist/chunk-AIP6MR42.js +12 -0
- package/dist/chunk-CCKG5R4Y.js +59 -0
- package/dist/chunk-DRSUEMAK.js +123 -0
- package/dist/chunk-FHG3ILE4.js +2011 -0
- package/dist/chunk-H2AHNI75.js +31 -0
- package/dist/chunk-HD74F6W2.js +460 -0
- package/dist/chunk-HKUWEGUX.js +36 -0
- package/dist/chunk-IBVVGH6X.js +33 -0
- package/dist/chunk-II7VYQEM.js +179 -0
- package/dist/chunk-JMJP4A47.js +204 -0
- package/dist/chunk-JQXOVCOP.js +574 -0
- package/dist/chunk-KE6QJBZG.js +41 -0
- package/dist/chunk-KWX3JHCY.js +85 -0
- package/dist/chunk-MXRWBNIY.js +74 -0
- package/dist/chunk-NPSRD26F.js +149 -0
- package/dist/chunk-QDF7QXBL.js +67 -0
- package/dist/chunk-QM53IQHM.js +209 -0
- package/dist/chunk-RZLYEO4U.js +219 -0
- package/dist/chunk-SGJG3BKD.js +351 -0
- package/dist/chunk-TYIAD6SB.js +74 -0
- package/dist/chunk-UWWSAPDR.js +31 -0
- package/dist/chunk-VM3IWOT5.js +458 -0
- package/dist/chunk-VRXHCR5K.js +42 -0
- package/dist/chunk-WJXC4MVY.js +75 -0
- package/dist/chunk-XDCHRVE3.js +215 -0
- package/dist/chunk-Z4Z5DNW4.js +1196 -0
- package/dist/chunk-ZZOXM6Q4.js +8 -0
- package/dist/ci-ZWRVWNFX.js +9298 -0
- package/dist/cli/contract-output.d.ts +1 -0
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/requested-command.d.ts +8 -0
- package/dist/cli-2JNBJUBB.js +704 -0
- package/dist/commands/build/actors/db-sync.d.ts +2 -0
- package/dist/commands/build/actors/static-checks.d.ts +7 -6
- package/dist/commands/build/actors/validate.d.ts +2 -0
- package/dist/commands/build/contract.d.ts +30 -30
- package/dist/commands/build/machine-dry-run.d.ts +3 -0
- package/dist/commands/build/machine-e2e-meta.d.ts +120 -0
- package/dist/commands/build/machine.d.ts +22 -22
- package/dist/commands/build/types.d.ts +2 -4
- package/dist/commands/check/commands/check.d.ts +8 -3
- package/dist/commands/ci/machine/actors/db/collect-schema-stats.d.ts +9 -6
- package/dist/commands/ci/machine/actors/db/schema-canonical-diff.d.ts +55 -0
- package/dist/commands/ci/machine/actors/db/schema-stats.d.ts +11 -0
- package/dist/commands/ci/machine/actors/db/sync-schema.d.ts +9 -1
- package/dist/commands/ci/machine/contract.d.ts +26 -26
- package/dist/commands/ci/machine/formatters/sections/final-comment.d.ts +1 -5
- package/dist/commands/ci/machine/formatters/sections/format-helpers.d.ts +5 -0
- package/dist/commands/ci/machine/formatters/sections/index.d.ts +2 -2
- package/dist/commands/ci/machine/formatters/sections/schema-matrix.d.ts +3 -3
- package/dist/commands/ci/machine/machine-execution-helpers.d.ts +40 -0
- package/dist/commands/ci/machine/machine-state-helpers.d.ts +14 -0
- package/dist/commands/ci/machine/machine.d.ts +12 -12
- package/dist/commands/ci/machine/types.d.ts +2 -5
- package/dist/commands/ci/utils/ci-summary.d.ts +15 -15
- package/dist/commands/ci/utils/execa-helpers.d.ts +2 -0
- package/dist/commands/db/apply/actors/idempotent-actors.d.ts +34 -0
- package/dist/commands/db/apply/actors/lock-actors.d.ts +16 -0
- package/dist/commands/db/apply/actors/pg-schema-diff-actors.d.ts +31 -0
- package/dist/commands/db/apply/actors/seed-actors.d.ts +11 -0
- package/dist/commands/db/apply/actors/shared.d.ts +9 -0
- package/dist/commands/db/apply/actors.d.ts +16 -65
- package/dist/commands/db/apply/contract.d.ts +8 -1
- package/dist/commands/db/apply/helpers/data-compatibility-checker.d.ts +3 -4
- package/dist/commands/db/apply/helpers/data-integrity-verifier.d.ts +37 -0
- package/dist/commands/db/apply/helpers/fresh-db-handler.d.ts +34 -0
- package/dist/commands/db/apply/helpers/hazard-handler.d.ts +60 -0
- package/dist/commands/db/apply/helpers/idempotent-object-registry.d.ts +96 -0
- package/dist/commands/db/apply/helpers/idempotent-transaction.d.ts +20 -0
- package/dist/commands/db/apply/helpers/index.d.ts +6 -0
- package/dist/commands/db/apply/helpers/partition-validator.d.ts +2 -15
- package/dist/commands/db/apply/helpers/pg-schema-diff-helpers.d.ts +18 -162
- package/dist/commands/db/apply/helpers/pg-schema-diff-patterns.d.ts +55 -0
- package/dist/commands/db/apply/helpers/pg-schema-diff-version.d.ts +50 -0
- package/dist/commands/db/apply/helpers/plan-validator.d.ts +4 -10
- package/dist/commands/db/apply/helpers/rbac-password-manager.d.ts +34 -0
- package/dist/commands/db/apply/helpers/retry-logic.d.ts +16 -2
- package/dist/commands/db/apply/helpers/shadow-db-manager.d.ts +1 -1
- package/dist/commands/db/apply/helpers/sql-utils.d.ts +26 -0
- package/dist/commands/db/apply/machine.d.ts +52 -1
- package/dist/commands/db/commands/db-apply.d.ts +18 -0
- package/dist/commands/db/commands/db-sync/boundary-classifier.d.ts +21 -0
- package/dist/commands/db/commands/db-sync/error-classifier.d.ts +9 -0
- package/dist/commands/db/commands/db-sync/plan-hazard-analyzer.d.ts +13 -0
- package/dist/commands/db/commands/db-sync/risk-reporter.d.ts +19 -0
- package/dist/commands/db/commands/db-sync/sql-parser.d.ts +25 -0
- package/dist/commands/db/commands/db-sync/types.d.ts +47 -0
- package/dist/commands/db/commands/db-sync.d.ts +14 -0
- package/dist/commands/db/sync/contract.d.ts +6 -2
- package/dist/commands/db/sync/machine.d.ts +2 -1
- package/dist/commands/db/types.d.ts +2 -0
- package/dist/commands/db/utils/boundary-policy/rule-compiler.d.ts +11 -0
- package/dist/commands/db/utils/boundary-policy/types.d.ts +105 -0
- package/dist/commands/db/utils/boundary-policy/validation.d.ts +20 -0
- package/dist/commands/db/utils/boundary-policy-runtime.d.ts +28 -0
- package/dist/commands/db/utils/boundary-policy.d.ts +5 -0
- package/dist/commands/db/utils/idempotent-risk-context.d.ts +29 -0
- package/dist/commands/db/utils/preflight-check.d.ts +14 -0
- package/dist/commands/db/utils/preflight-checks/domain-naming-checks.d.ts +106 -0
- package/dist/commands/db/utils/preflight-checks/orphan-checks.d.ts +36 -0
- package/dist/commands/db/utils/preflight-checks/schema-risk-checks.d.ts +22 -0
- package/dist/commands/db/utils/preflight-checks/supabase-checks.d.ts +55 -0
- package/dist/commands/db/utils/risk-detector-loader.d.ts +8 -0
- package/dist/commands/db/utils/schema-precheck-budget.d.ts +17 -0
- package/dist/commands/db/utils/sql-boundary-parser.d.ts +12 -0
- package/dist/commands/db/utils/sql-file-collector.d.ts +8 -0
- package/dist/commands/db/utils/sql-filename-parser.d.ts +20 -0
- package/dist/commands/db/utils/sql-table-extractor-ast.d.ts +19 -0
- package/dist/commands/db/utils/sql-table-extractor-regex.d.ts +50 -0
- package/dist/commands/db/utils/sql-table-extractor-rls.d.ts +13 -0
- package/dist/commands/db/utils/sql-table-extractor.d.ts +79 -1
- package/dist/commands/db/utils/table-registry-introspection.d.ts +68 -0
- package/dist/commands/db/utils/table-registry.d.ts +3 -38
- package/dist/commands/dev/actors/app-lifecycle.d.ts +18 -0
- package/dist/commands/dev/actors/index.d.ts +12 -2
- package/dist/commands/dev/actors/process-check.d.ts +12 -0
- package/dist/commands/dev/actors/shared.d.ts +15 -0
- package/dist/commands/dev/actors/tables-manifest.d.ts +16 -0
- package/dist/commands/dev/contract.d.ts +3 -3
- package/dist/commands/dev/guards.d.ts +24 -0
- package/dist/commands/dev/machine.d.ts +27 -32
- package/dist/commands/dev/types.d.ts +2 -0
- package/dist/commands/doctor.d.ts +9 -0
- package/dist/commands/env/commands/env-pull/auth.d.ts +13 -0
- package/dist/commands/env/commands/env-pull/dotenv-files.d.ts +14 -0
- package/dist/commands/env/commands/env-pull/security.d.ts +12 -0
- package/dist/commands/env/commands/env-pull/service.d.ts +8 -0
- package/dist/commands/env/commands/env-pull/shared.d.ts +79 -0
- package/dist/commands/env/commands/setup/types.d.ts +1 -1
- package/dist/commands/env/constants/local-supabase.d.ts +2 -0
- package/dist/commands/inject-test-attrs/defaults.d.ts +9 -0
- package/dist/commands/template-check/contract.d.ts +6 -6
- package/dist/commands/template-check/machine.d.ts +2 -2
- package/dist/commands/template-check/types.d.ts +0 -4
- package/dist/commands/template-check/utils/diff-analyzer.d.ts +0 -4
- package/dist/commands/utils/machine-state-logging.d.ts +20 -0
- package/dist/commands/utils/repo-root.d.ts +2 -0
- package/dist/config/env.d.ts +4 -4
- package/dist/config-loader-GT3HAQ7U.js +7 -0
- package/dist/db-XULCILOU.js +14137 -0
- package/dist/dev-5YXNPTCJ.js +992 -0
- package/dist/doctor-MZLOA53G.js +44 -0
- package/dist/env-HMMRSYCI.js +7 -0
- package/dist/env-SS66PZ4B.js +2623 -0
- package/dist/env-files-2UIUYLLR.js +8 -0
- package/dist/error-handler-HEXBRNVV.js +460 -0
- package/dist/hotfix-YA3DGLOM.js +1477 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +48 -42995
- package/dist/init-ZIL6LRFO.js +631 -0
- package/dist/inject-test-attrs-P44BVTQS.js +23 -0
- package/dist/internal/machines/snapshot-helpers.d.ts +6 -0
- package/dist/lib/sql-comment-utils.d.ts +25 -0
- package/dist/license-OB7GVJQ2.js +468 -0
- package/dist/link-VSNDVZZD.js +59 -0
- package/dist/manifest-TMFLESHW.js +19 -0
- package/dist/prepare-32DOVHTE.js +250 -0
- package/dist/risk-detector-4U6ZJ2G5.js +6 -0
- package/dist/risk-detector-core-TK4OAI3N.js +166 -0
- package/dist/risk-detector-plpgsql-HWKS4OLR.js +1886 -0
- package/dist/sdk-XK6HQU7S.js +348 -0
- package/dist/services-7VK5KZTO.js +177 -0
- package/dist/session-SFW5QSXZ.js +142 -0
- package/dist/signal-handler-DO3OANW5.js +6 -0
- package/dist/status-UTKS63AB.js +94 -0
- package/dist/telemetry-P56UBLZ2.js +93 -0
- package/dist/template-check-3P4HZXVY.js +1944 -0
- package/dist/test-V4KQL574.js +650 -0
- package/dist/test-gen-FS4CEY3P.js +88 -0
- package/dist/ui-RJAMCWUI.js +331 -0
- package/dist/upgrade-NUK3ZBCL.js +637 -0
- package/dist/utils/config-loader.d.ts +0 -3
- package/dist/validate-CAAW4Y44.js +54 -0
- package/dist/validators/risk-detector-content-risks.d.ts +13 -0
- package/dist/validators/risk-detector-core.d.ts +25 -0
- package/dist/validators/risk-detector-patterns.d.ts +15 -0
- package/dist/validators/risk-detector-plpgsql-expression-resolver.d.ts +22 -0
- package/dist/validators/risk-detector-plpgsql-parser.d.ts +5 -0
- package/dist/validators/risk-detector-plpgsql-tokenizer.d.ts +18 -0
- package/dist/validators/risk-detector-plpgsql.d.ts +9 -0
- package/dist/validators/risk-detector-text-utils.d.ts +6 -0
- package/dist/validators/risk-detector-types.d.ts +16 -0
- package/dist/validators/risk-detector.d.ts +7 -26
- package/dist/vuln-check-2W7N5TA2.js +121 -0
- package/dist/vuln-checker-IQJ56RUV.js +3223 -0
- package/dist/watch-PNTKZYFB.js +911 -0
- package/dist/workflow-H75N4BXX.js +897 -0
- package/package.json +5 -2
- package/dist/cli/contract-mode.d.ts.map +0 -1
- package/dist/cli/contract-output.d.ts.map +0 -1
- package/dist/cli/early-flags.d.ts.map +0 -1
- package/dist/cli/error-handler.d.ts.map +0 -1
- package/dist/cli/exec.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/json-output.d.ts.map +0 -1
- package/dist/cli/non-interactive.d.ts.map +0 -1
- package/dist/cli/output-format.d.ts.map +0 -1
- package/dist/cli/signal-handler.d.ts.map +0 -1
- package/dist/commands/build/actors/build.d.ts.map +0 -1
- package/dist/commands/build/actors/clean.d.ts.map +0 -1
- package/dist/commands/build/actors/db-sync.d.ts.map +0 -1
- package/dist/commands/build/actors/index.d.ts.map +0 -1
- package/dist/commands/build/actors/manifest.d.ts.map +0 -1
- package/dist/commands/build/actors/setup.d.ts.map +0 -1
- package/dist/commands/build/actors/static-checks.d.ts.map +0 -1
- package/dist/commands/build/actors/validate.d.ts.map +0 -1
- package/dist/commands/build/commands/build.d.ts.map +0 -1
- package/dist/commands/build/contract.d.ts.map +0 -1
- package/dist/commands/build/guards.d.ts.map +0 -1
- package/dist/commands/build/index.d.ts.map +0 -1
- package/dist/commands/build/machine.d.ts.map +0 -1
- package/dist/commands/build/types.d.ts.map +0 -1
- package/dist/commands/cache.d.ts.map +0 -1
- package/dist/commands/check/commands/check.d.ts.map +0 -1
- package/dist/commands/check/index.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-checks.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-layer-content.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-pr-capabilities.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-prod-apply.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-prod-db-operations.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-prod-github.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-prod-types.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-prod-utils.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-prod-workflow.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-resolvers.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-static.d.ts.map +0 -1
- package/dist/commands/ci/commands/ci-supabase-local.d.ts.map +0 -1
- package/dist/commands/ci/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/build/app-build.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/build/app-start.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/build/build-and-playwright.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/build/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/build/playwright-install.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/build/static-checks.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/apply-seeds.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/collect-schema-stats.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/pgtap-install.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/production-preview.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/pull-production.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/reset.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/schema-stats.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/setup-roles.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/db/sync-schema.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/finalize/github.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/finalize/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/finalize/summary.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/setup/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/setup/local.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/setup/pr-common.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/setup/pr-local.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/test/capabilities.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/test/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/actors/test/run-layers.d.ts.map +0 -1
- package/dist/commands/ci/machine/commands/ci-local.d.ts.map +0 -1
- package/dist/commands/ci/machine/commands/ci-pr.d.ts.map +0 -1
- package/dist/commands/ci/machine/commands/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/commands/machine-runner.d.ts.map +0 -1
- package/dist/commands/ci/machine/commands/runtime-env.d.ts.map +0 -1
- package/dist/commands/ci/machine/contract.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/github-comment-types.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/github-comment.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/sections/final-comment.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/sections/format-helpers.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/sections/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/sections/progress-comment.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/sections/schema-matrix.d.ts.map +0 -1
- package/dist/commands/ci/machine/formatters/summary.d.ts.map +0 -1
- package/dist/commands/ci/machine/guards.d.ts.map +0 -1
- package/dist/commands/ci/machine/helpers.d.ts.map +0 -1
- package/dist/commands/ci/machine/index.d.ts.map +0 -1
- package/dist/commands/ci/machine/machine.d.ts.map +0 -1
- package/dist/commands/ci/machine/types.d.ts.map +0 -1
- package/dist/commands/ci/utils/ai-report.d.ts.map +0 -1
- package/dist/commands/ci/utils/app-process.d.ts.map +0 -1
- package/dist/commands/ci/utils/app-runtime.d.ts.map +0 -1
- package/dist/commands/ci/utils/ci-config.d.ts.map +0 -1
- package/dist/commands/ci/utils/ci-env-schema.d.ts.map +0 -1
- package/dist/commands/ci/utils/ci-logging.d.ts.map +0 -1
- package/dist/commands/ci/utils/ci-summary.d.ts.map +0 -1
- package/dist/commands/ci/utils/config-readers.d.ts.map +0 -1
- package/dist/commands/ci/utils/db-url-utils.d.ts.map +0 -1
- package/dist/commands/ci/utils/e2e-auth-setup.d.ts.map +0 -1
- package/dist/commands/ci/utils/env-security.d.ts.map +0 -1
- package/dist/commands/ci/utils/execa-helpers.d.ts.map +0 -1
- package/dist/commands/ci/utils/exit-code-computation.d.ts.map +0 -1
- package/dist/commands/ci/utils/github-api.d.ts.map +0 -1
- package/dist/commands/ci/utils/github.d.ts.map +0 -1
- package/dist/commands/ci/utils/index.d.ts.map +0 -1
- package/dist/commands/ci/utils/pgtap-installer.d.ts.map +0 -1
- package/dist/commands/ci/utils/rls-verification.d.ts.map +0 -1
- package/dist/commands/ci/utils/schema-operations.d.ts.map +0 -1
- package/dist/commands/ci/utils/seed-operations.d.ts.map +0 -1
- package/dist/commands/ci/utils/test-parallel.d.ts.map +0 -1
- package/dist/commands/ci/utils/timestamp-invariants.d.ts.map +0 -1
- package/dist/commands/ci/utils/workflow-idempotency.d.ts.map +0 -1
- package/dist/commands/db/apply/actors.d.ts.map +0 -1
- package/dist/commands/db/apply/contract.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/advisory-lock.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/data-compatibility-checker.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/index.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/partition-acl-cleaner.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/partition-prefilter.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/partition-validator.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/pg-schema-diff-helpers.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/plan-validator.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/retry-logic.d.ts.map +0 -1
- package/dist/commands/db/apply/helpers/shadow-db-manager.d.ts.map +0 -1
- package/dist/commands/db/apply/index.d.ts.map +0 -1
- package/dist/commands/db/apply/machine.d.ts.map +0 -1
- package/dist/commands/db/commands/db-apply.d.ts.map +0 -1
- package/dist/commands/db/commands/db-audit.d.ts.map +0 -1
- package/dist/commands/db/commands/db-backup.d.ts.map +0 -1
- package/dist/commands/db/commands/db-cleanup.d.ts.map +0 -1
- package/dist/commands/db/commands/db-derive-role-passwords.d.ts.map +0 -1
- package/dist/commands/db/commands/db-derive-urls.d.ts.map +0 -1
- package/dist/commands/db/commands/db-diagram.d.ts.map +0 -1
- package/dist/commands/db/commands/db-drizzle.d.ts.map +0 -1
- package/dist/commands/db/commands/db-extension.d.ts.map +0 -1
- package/dist/commands/db/commands/db-generate-password.d.ts.map +0 -1
- package/dist/commands/db/commands/db-lifecycle.d.ts.map +0 -1
- package/dist/commands/db/commands/db-rollback.d.ts.map +0 -1
- package/dist/commands/db/commands/db-schema.d.ts.map +0 -1
- package/dist/commands/db/commands/db-seed-metadata.d.ts.map +0 -1
- package/dist/commands/db/commands/db-seed-verify.d.ts.map +0 -1
- package/dist/commands/db/commands/db-seed.d.ts.map +0 -1
- package/dist/commands/db/commands/db-snapshot.d.ts.map +0 -1
- package/dist/commands/db/commands/db-stack.d.ts.map +0 -1
- package/dist/commands/db/commands/db-stats.d.ts.map +0 -1
- package/dist/commands/db/commands/db-sync.d.ts.map +0 -1
- package/dist/commands/db/commands/db-test.d.ts.map +0 -1
- package/dist/commands/db/constants.d.ts.map +0 -1
- package/dist/commands/db/extension-registry.d.ts.map +0 -1
- package/dist/commands/db/index.d.ts.map +0 -1
- package/dist/commands/db/preflight/actors.d.ts.map +0 -1
- package/dist/commands/db/preflight/contract.d.ts.map +0 -1
- package/dist/commands/db/preflight/index.d.ts.map +0 -1
- package/dist/commands/db/sync/actors.d.ts.map +0 -1
- package/dist/commands/db/sync/contract.d.ts.map +0 -1
- package/dist/commands/db/sync/index.d.ts.map +0 -1
- package/dist/commands/db/sync/machine.d.ts.map +0 -1
- package/dist/commands/db/types.d.ts.map +0 -1
- package/dist/commands/db/utils/db-target.d.ts.map +0 -1
- package/dist/commands/db/utils/db-url-builder.d.ts.map +0 -1
- package/dist/commands/db/utils/error-handlers.d.ts.map +0 -1
- package/dist/commands/db/utils/import-impact-analyzer.d.ts.map +0 -1
- package/dist/commands/db/utils/preflight-check.d.ts.map +0 -1
- package/dist/commands/db/utils/psql.d.ts.map +0 -1
- package/dist/commands/db/utils/schema-detector.d.ts.map +0 -1
- package/dist/commands/db/utils/schema-sync.d.ts.map +0 -1
- package/dist/commands/db/utils/script-runner.d.ts.map +0 -1
- package/dist/commands/db/utils/seed-manager.d.ts.map +0 -1
- package/dist/commands/db/utils/semantic-mapper.d.ts.map +0 -1
- package/dist/commands/db/utils/sql-table-extractor.d.ts.map +0 -1
- package/dist/commands/db/utils/stack-detector.d.ts.map +0 -1
- package/dist/commands/db/utils/table-registry.d.ts.map +0 -1
- package/dist/commands/db/utils/table-source-classifier.d.ts.map +0 -1
- package/dist/commands/dev/actors/index.d.ts.map +0 -1
- package/dist/commands/dev/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev/contract.d.ts.map +0 -1
- package/dist/commands/dev/guards.d.ts.map +0 -1
- package/dist/commands/dev/helpers/stale-process-detector.d.ts.map +0 -1
- package/dist/commands/dev/machine.d.ts.map +0 -1
- package/dist/commands/dev/types.d.ts.map +0 -1
- package/dist/commands/env/commands/env-check.d.ts.map +0 -1
- package/dist/commands/env/commands/env-encrypt.d.ts.map +0 -1
- package/dist/commands/env/commands/env-pull.d.ts.map +0 -1
- package/dist/commands/env/commands/env-setup.d.ts.map +0 -1
- package/dist/commands/env/commands/env-sync.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/action.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/auth.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/file-export.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/github-api.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/helpers.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/index.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/parsers.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/prompts.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/supabase-api.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/types.d.ts.map +0 -1
- package/dist/commands/env/commands/setup/vercel-api.d.ts.map +0 -1
- package/dist/commands/env/constants/local-supabase.d.ts.map +0 -1
- package/dist/commands/env/index.d.ts.map +0 -1
- package/dist/commands/hotfix/actors.d.ts.map +0 -1
- package/dist/commands/hotfix/commands/hotfix-complete.d.ts.map +0 -1
- package/dist/commands/hotfix/commands/hotfix-create.d.ts.map +0 -1
- package/dist/commands/hotfix/commands/hotfix-deploy.d.ts.map +0 -1
- package/dist/commands/hotfix/commands/hotfix-status.d.ts.map +0 -1
- package/dist/commands/hotfix/contract.d.ts.map +0 -1
- package/dist/commands/hotfix/index.d.ts.map +0 -1
- package/dist/commands/hotfix/machine.d.ts.map +0 -1
- package/dist/commands/hotfix/metadata.d.ts.map +0 -1
- package/dist/commands/hotfix/utils/hotfix-machine-helper.d.ts.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/action.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/commands/inject-test-attrs.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/contract.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/detection-diagnostics.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/formatter.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/index.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/manifest-generator.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/processor-utils.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/processor.d.ts.map +0 -1
- package/dist/commands/inject-test-attrs/types.d.ts.map +0 -1
- package/dist/commands/link.d.ts.map +0 -1
- package/dist/commands/manifest/index.d.ts.map +0 -1
- package/dist/commands/prepare/commands/prepare.d.ts.map +0 -1
- package/dist/commands/prepare/index.d.ts.map +0 -1
- package/dist/commands/sdk/commands/publish.d.ts.map +0 -1
- package/dist/commands/sdk/index.d.ts.map +0 -1
- package/dist/commands/services/index.d.ts.map +0 -1
- package/dist/commands/session/index.d.ts.map +0 -1
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/telemetry.d.ts.map +0 -1
- package/dist/commands/template-check/actors/compare.d.ts.map +0 -1
- package/dist/commands/template-check/actors/discover.d.ts.map +0 -1
- package/dist/commands/template-check/actors/index.d.ts.map +0 -1
- package/dist/commands/template-check/actors/report.d.ts.map +0 -1
- package/dist/commands/template-check/commands/template-check.d.ts.map +0 -1
- package/dist/commands/template-check/config.d.ts.map +0 -1
- package/dist/commands/template-check/contract.d.ts.map +0 -1
- package/dist/commands/template-check/index.d.ts.map +0 -1
- package/dist/commands/template-check/machine.d.ts.map +0 -1
- package/dist/commands/template-check/types.d.ts.map +0 -1
- package/dist/commands/template-check/utils/diff-analyzer.d.ts.map +0 -1
- package/dist/commands/template-check/utils/normalizer.d.ts.map +0 -1
- package/dist/commands/template-check/utils/path-mapping.d.ts.map +0 -1
- package/dist/commands/test/commands/test-db.d.ts.map +0 -1
- package/dist/commands/test/commands/test-e2e.d.ts.map +0 -1
- package/dist/commands/test/commands/test-fast.d.ts.map +0 -1
- package/dist/commands/test/commands/test-integration.d.ts.map +0 -1
- package/dist/commands/test/commands/test-layer.d.ts.map +0 -1
- package/dist/commands/test/commands/test-owasp-generate.d.ts.map +0 -1
- package/dist/commands/test/commands/test-service.d.ts.map +0 -1
- package/dist/commands/test/commands/test-static.d.ts.map +0 -1
- package/dist/commands/test/commands/test.d.ts.map +0 -1
- package/dist/commands/test/index.d.ts.map +0 -1
- package/dist/commands/test-gen.d.ts.map +0 -1
- package/dist/commands/ui.d.ts.map +0 -1
- package/dist/commands/upgrade.d.ts.map +0 -1
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/vuln-check.d.ts.map +0 -1
- package/dist/commands/watch.d.ts.map +0 -1
- package/dist/commands/workflow/commands/deploy-production.d.ts.map +0 -1
- package/dist/commands/workflow/commands/final-status.d.ts.map +0 -1
- package/dist/commands/workflow/commands/log.d.ts.map +0 -1
- package/dist/commands/workflow/commands/notify.d.ts.map +0 -1
- package/dist/commands/workflow/commands/paths.d.ts.map +0 -1
- package/dist/commands/workflow/commands/sync.d.ts.map +0 -1
- package/dist/commands/workflow/commands/validate.d.ts.map +0 -1
- package/dist/commands/workflow/commands/verify-credentials.d.ts.map +0 -1
- package/dist/commands/workflow/index.d.ts.map +0 -1
- package/dist/commands/workflow/types.d.ts.map +0 -1
- package/dist/config/env-files.d.ts.map +0 -1
- package/dist/config/env.d.ts.map +0 -1
- package/dist/constants/versions.d.ts.map +0 -1
- package/dist/contracts/envelope.d.ts.map +0 -1
- package/dist/errors/catalog.d.ts.map +0 -1
- package/dist/errors/exit-codes.d.ts.map +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/incremental/affected-tests.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/internal/machines/index.d.ts.map +0 -1
- package/dist/internal/machines/machine-runner.d.ts.map +0 -1
- package/dist/internal/machines/snapshot-helpers.d.ts.map +0 -1
- package/dist/internal/machines/types.d.ts.map +0 -1
- package/dist/internal/vuln-checker/analyzers/dependency-analyzer.d.ts.map +0 -1
- package/dist/internal/vuln-checker/analyzers/rls-analyzer.d.ts.map +0 -1
- package/dist/internal/vuln-checker/analyzers/secret-analyzer.d.ts.map +0 -1
- package/dist/internal/vuln-checker/analyzers/typescript-analyzer.d.ts.map +0 -1
- package/dist/internal/vuln-checker/config/loader.d.ts.map +0 -1
- package/dist/internal/vuln-checker/constants.d.ts.map +0 -1
- package/dist/internal/vuln-checker/ignore/matcher.d.ts.map +0 -1
- package/dist/internal/vuln-checker/index.d.ts.map +0 -1
- package/dist/internal/vuln-checker/reporters/console-reporter.d.ts.map +0 -1
- package/dist/internal/vuln-checker/reporters/json-reporter.d.ts.map +0 -1
- package/dist/internal/vuln-checker/reporters/markdown-reporter.d.ts.map +0 -1
- package/dist/internal/vuln-checker/reporters/sarif-reporter.d.ts.map +0 -1
- package/dist/internal/vuln-checker/security/path-validation.d.ts.map +0 -1
- package/dist/internal/vuln-checker/types.d.ts.map +0 -1
- package/dist/notifiers/desktop-notifier.d.ts.map +0 -1
- package/dist/ui/components/db-panel.d.ts.map +0 -1
- package/dist/ui/components/status-bar.d.ts.map +0 -1
- package/dist/ui/components/test-panel.d.ts.map +0 -1
- package/dist/ui/dashboard.d.ts.map +0 -1
- package/dist/ui/index.d.ts.map +0 -1
- package/dist/utils/config-loader.d.ts.map +0 -1
- package/dist/utils/config-updater.d.ts.map +0 -1
- package/dist/utils/diagnostics.d.ts.map +0 -1
- package/dist/utils/dotenvx.d.ts.map +0 -1
- package/dist/utils/env-local-bridge.d.ts.map +0 -1
- package/dist/utils/execution-plan.d.ts.map +0 -1
- package/dist/utils/github-output-security.d.ts.map +0 -1
- package/dist/utils/help-system.d.ts.map +0 -1
- package/dist/utils/license/admin-auth.d.ts.map +0 -1
- package/dist/utils/license/allowlist-checker.d.ts.map +0 -1
- package/dist/utils/license/ci-detector.d.ts.map +0 -1
- package/dist/utils/license/index.d.ts.map +0 -1
- package/dist/utils/license/owner-resolver.d.ts.map +0 -1
- package/dist/utils/license/types.d.ts.map +0 -1
- package/dist/utils/license/validate-owner.d.ts.map +0 -1
- package/dist/utils/path-security.d.ts.map +0 -1
- package/dist/utils/port-allocator.d.ts.map +0 -1
- package/dist/utils/secure-exec.d.ts.map +0 -1
- package/dist/utils/template-fetcher.d.ts.map +0 -1
- package/dist/utils/type-guards.d.ts.map +0 -1
- package/dist/utils/vercel-project.d.ts.map +0 -1
- package/dist/utils/workspace-detector.d.ts.map +0 -1
- package/dist/validators/risk-detector.d.ts.map +0 -1
- package/dist/validators/schema-validator.d.ts.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/watchers/schema-watcher.d.ts.map +0 -1
- package/dist/watchers/test-watcher.d.ts.map +0 -1
|
@@ -0,0 +1,2011 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { psqlQuery, stripSqlComments } from './chunk-7B5C6U2K.js';
|
|
4
|
+
import { loadRunaConfig } from './chunk-5NKWR4FF.js';
|
|
5
|
+
import { init_esm_shims } from './chunk-VRXHCR5K.js';
|
|
6
|
+
import { existsSync, writeFileSync, unlinkSync, readdirSync, readFileSync, mkdirSync, realpathSync } from 'fs';
|
|
7
|
+
import path2, { join, isAbsolute, relative } from 'path';
|
|
8
|
+
import { SUPABASE_SYSTEM_SCHEMAS, detectSchemaNames } from '@runa-ai/runa';
|
|
9
|
+
import { isTable, getTableUniqueName, getTableName } from 'drizzle-orm';
|
|
10
|
+
import { isPgEnum } from 'drizzle-orm/pg-core';
|
|
11
|
+
import { createJiti } from 'jiti';
|
|
12
|
+
import postgres2 from 'postgres';
|
|
13
|
+
import { introspectDatabase } from '@runa-ai/runa/test-generators';
|
|
14
|
+
|
|
15
|
+
createRequire(import.meta.url);
|
|
16
|
+
|
|
17
|
+
// src/utils/env-local-bridge.ts
|
|
18
|
+
init_esm_shims();
|
|
19
|
+
var DEFAULT_API_PORT = 54321;
|
|
20
|
+
var DEFAULT_DB_PORT = 54322;
|
|
21
|
+
var RUNA_ENV_LOCAL_MARKER = "# RUNA_AUTO_GENERATED \u2014 do not edit (created by `runa db start`)";
|
|
22
|
+
function envLocalPath(projectRoot) {
|
|
23
|
+
return path2.join(projectRoot, ".env.local");
|
|
24
|
+
}
|
|
25
|
+
function isNonDefaultPorts(config) {
|
|
26
|
+
return config.api !== DEFAULT_API_PORT || config.db !== DEFAULT_DB_PORT;
|
|
27
|
+
}
|
|
28
|
+
function buildEnvLocalContent(config) {
|
|
29
|
+
const dbUrl = `postgresql://postgres:postgres@${config.host}:${config.db}/postgres`;
|
|
30
|
+
return [
|
|
31
|
+
RUNA_ENV_LOCAL_MARKER,
|
|
32
|
+
`# Supabase is running on non-default ports (default: api=${DEFAULT_API_PORT}, db=${DEFAULT_DB_PORT}).`,
|
|
33
|
+
"# This file overrides .env.development so that `pnpm dev` uses correct ports.",
|
|
34
|
+
"# Removed automatically by `runa db stop`. Safe to delete manually.",
|
|
35
|
+
`DATABASE_URL=${dbUrl}`,
|
|
36
|
+
`DATABASE_URL_ADMIN=${dbUrl}`,
|
|
37
|
+
`DATABASE_URL_SERVICE=${dbUrl}`,
|
|
38
|
+
`NEXT_PUBLIC_SUPABASE_URL=http://${config.host}:${config.api}`,
|
|
39
|
+
""
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
42
|
+
function isRunaManagedEnvLocal(projectRoot) {
|
|
43
|
+
const filePath = envLocalPath(projectRoot);
|
|
44
|
+
try {
|
|
45
|
+
const content = readFileSync(filePath, "utf-8");
|
|
46
|
+
return content.startsWith(RUNA_ENV_LOCAL_MARKER);
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function writeEnvLocalBridge(projectRoot, config) {
|
|
52
|
+
const filePath = envLocalPath(projectRoot);
|
|
53
|
+
const fileExists = existsSync(filePath);
|
|
54
|
+
if (isNonDefaultPorts(config)) {
|
|
55
|
+
if (fileExists && !isRunaManagedEnvLocal(projectRoot)) {
|
|
56
|
+
return { written: false, reason: "user-managed" };
|
|
57
|
+
}
|
|
58
|
+
writeFileSync(filePath, buildEnvLocalContent(config), "utf-8");
|
|
59
|
+
return { written: true, ports: { api: config.api, db: config.db } };
|
|
60
|
+
}
|
|
61
|
+
if (fileExists && isRunaManagedEnvLocal(projectRoot)) {
|
|
62
|
+
unlinkSync(filePath);
|
|
63
|
+
return { written: false, reason: "stale-removed" };
|
|
64
|
+
}
|
|
65
|
+
return { written: false, reason: "default-ports" };
|
|
66
|
+
}
|
|
67
|
+
function removeEnvLocalBridge(projectRoot) {
|
|
68
|
+
const filePath = envLocalPath(projectRoot);
|
|
69
|
+
if (!existsSync(filePath)) {
|
|
70
|
+
return { removed: false, reason: "not-found" };
|
|
71
|
+
}
|
|
72
|
+
if (!isRunaManagedEnvLocal(projectRoot)) {
|
|
73
|
+
return { removed: false, reason: "user-managed" };
|
|
74
|
+
}
|
|
75
|
+
unlinkSync(filePath);
|
|
76
|
+
return { removed: true };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/commands/db/utils/schema-sync.ts
|
|
80
|
+
init_esm_shims();
|
|
81
|
+
var VALID_PG_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/;
|
|
82
|
+
function validatePgIdentifier(name, context) {
|
|
83
|
+
if (!name || typeof name !== "string") {
|
|
84
|
+
throw new Error(`Invalid ${context}: empty or not a string`);
|
|
85
|
+
}
|
|
86
|
+
if (!VALID_PG_IDENTIFIER_PATTERN.test(name)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Invalid ${context} "${name}": must start with letter/underscore and contain only alphanumeric/underscore characters`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function escapePgStringLiteral(value) {
|
|
93
|
+
if (typeof value !== "string") {
|
|
94
|
+
throw new Error("Value must be a string");
|
|
95
|
+
}
|
|
96
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "''");
|
|
97
|
+
}
|
|
98
|
+
function buildSafeSchemaInClause(schemas) {
|
|
99
|
+
if (schemas.length === 0) {
|
|
100
|
+
throw new Error("No schemas provided for IN clause");
|
|
101
|
+
}
|
|
102
|
+
const safeSchemas = [];
|
|
103
|
+
for (const schema of schemas) {
|
|
104
|
+
validatePgIdentifier(schema, "schema name");
|
|
105
|
+
safeSchemas.push(`'${escapePgStringLiteral(schema)}'`);
|
|
106
|
+
}
|
|
107
|
+
return safeSchemas.join(",");
|
|
108
|
+
}
|
|
109
|
+
var ERROR_MESSAGES = {
|
|
110
|
+
PATH_TRAVERSAL: "Schema path validation failed",
|
|
111
|
+
SCHEMA_NOT_FOUND: "Schema file not found"
|
|
112
|
+
};
|
|
113
|
+
function containsPathTraversal(inputPath) {
|
|
114
|
+
const normalized = path2.normalize(inputPath);
|
|
115
|
+
return normalized.includes("..") || inputPath.includes("\0");
|
|
116
|
+
}
|
|
117
|
+
function isPathWithinBase(filePath, baseDir) {
|
|
118
|
+
try {
|
|
119
|
+
const resolvedFile = path2.resolve(filePath);
|
|
120
|
+
const resolvedBase = path2.resolve(baseDir);
|
|
121
|
+
const normalizedFile = path2.normalize(resolvedFile);
|
|
122
|
+
const normalizedBase = path2.normalize(resolvedBase);
|
|
123
|
+
return normalizedFile === normalizedBase || normalizedFile.startsWith(normalizedBase + path2.sep);
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function validateSchemaPath(dbPackagePath, projectRoot = process.cwd()) {
|
|
129
|
+
if (containsPathTraversal(dbPackagePath)) {
|
|
130
|
+
throw new Error(ERROR_MESSAGES.PATH_TRAVERSAL);
|
|
131
|
+
}
|
|
132
|
+
const schemaEntry = path2.join(dbPackagePath, "src", "schema", "index.ts");
|
|
133
|
+
const absoluteSchemaPath = path2.resolve(projectRoot, schemaEntry);
|
|
134
|
+
let resolvedProjectRoot;
|
|
135
|
+
try {
|
|
136
|
+
resolvedProjectRoot = realpathSync(projectRoot);
|
|
137
|
+
} catch {
|
|
138
|
+
resolvedProjectRoot = path2.resolve(projectRoot);
|
|
139
|
+
}
|
|
140
|
+
if (!isPathWithinBase(absoluteSchemaPath, resolvedProjectRoot)) {
|
|
141
|
+
throw new Error(ERROR_MESSAGES.PATH_TRAVERSAL);
|
|
142
|
+
}
|
|
143
|
+
if (!existsSync(absoluteSchemaPath)) {
|
|
144
|
+
throw new Error(ERROR_MESSAGES.SCHEMA_NOT_FOUND);
|
|
145
|
+
}
|
|
146
|
+
return absoluteSchemaPath;
|
|
147
|
+
}
|
|
148
|
+
function uniqueSorted(values) {
|
|
149
|
+
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
150
|
+
}
|
|
151
|
+
async function extractSchemaTablesAndEnums(dbPackagePath, projectRoot = process.cwd()) {
|
|
152
|
+
const validatedSchemaPath = validateSchemaPath(dbPackagePath, projectRoot);
|
|
153
|
+
const jiti = createJiti(projectRoot, { interopDefault: true });
|
|
154
|
+
let schemaModule;
|
|
155
|
+
try {
|
|
156
|
+
schemaModule = await jiti.import(validatedSchemaPath);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
159
|
+
const hint = errorMessage.includes("unknown is not defined") ? "\n\nHint: Add 'unknown' to drizzle-orm/pg-core imports:\n import { unknown, ... } from 'drizzle-orm/pg-core'" : "";
|
|
160
|
+
throw new Error(`Failed to load schema from ${validatedSchemaPath}: ${errorMessage}${hint}`);
|
|
161
|
+
}
|
|
162
|
+
const expectedTables = /* @__PURE__ */ new Set();
|
|
163
|
+
const expectedEnums = /* @__PURE__ */ new Map();
|
|
164
|
+
for (const value of Object.values(schemaModule)) {
|
|
165
|
+
if (isTable(value)) {
|
|
166
|
+
const unique = String(getTableUniqueName(value));
|
|
167
|
+
if (unique.startsWith("undefined.")) {
|
|
168
|
+
expectedTables.add(`public.${getTableName(value)}`);
|
|
169
|
+
} else {
|
|
170
|
+
expectedTables.add(unique);
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (isPgEnum(value)) {
|
|
175
|
+
expectedEnums.set(value.enumName, {
|
|
176
|
+
name: value.enumName,
|
|
177
|
+
values: uniqueSorted(value.enumValues)
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { expectedTables, expectedEnums };
|
|
182
|
+
}
|
|
183
|
+
async function fetchDbTablesAndEnums(databaseUrl, options) {
|
|
184
|
+
const schemaDir = options?.schemaDir ?? "packages/database/src/schema";
|
|
185
|
+
const managedSchemas = detectSchemaNames(schemaDir, process.cwd());
|
|
186
|
+
const systemSchemas = /* @__PURE__ */ new Set([
|
|
187
|
+
...SUPABASE_SYSTEM_SCHEMAS,
|
|
188
|
+
...options?.additionalSystemSchemas ?? []
|
|
189
|
+
]);
|
|
190
|
+
const filteredManagedSchemas = managedSchemas.filter((s) => !systemSchemas.has(s));
|
|
191
|
+
const schemaList = buildSafeSchemaInClause(filteredManagedSchemas);
|
|
192
|
+
const tablesSql = `
|
|
193
|
+
SELECT schemaname || '.' || tablename
|
|
194
|
+
FROM pg_tables
|
|
195
|
+
WHERE schemaname IN (${schemaList})
|
|
196
|
+
ORDER BY schemaname, tablename;`.trim();
|
|
197
|
+
const enumsSql = `
|
|
198
|
+
SELECT t.typname AS enum_name, string_agg(e.enumlabel, ',' ORDER BY e.enumsortorder) AS values
|
|
199
|
+
FROM pg_type t
|
|
200
|
+
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
201
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
202
|
+
WHERE n.nspname = 'public'
|
|
203
|
+
GROUP BY t.typname
|
|
204
|
+
ORDER BY t.typname;`.trim();
|
|
205
|
+
const tablesOut = await psqlQuery({ databaseUrl, sql: tablesSql, mode: "table" });
|
|
206
|
+
const dbTables = /* @__PURE__ */ new Set();
|
|
207
|
+
for (const line of tablesOut.split("\n")) {
|
|
208
|
+
const v = line.trim();
|
|
209
|
+
if (v.length > 0) dbTables.add(v);
|
|
210
|
+
}
|
|
211
|
+
const enumsOut = await psqlQuery({ databaseUrl, sql: enumsSql, mode: "table" });
|
|
212
|
+
const dbEnums = /* @__PURE__ */ new Map();
|
|
213
|
+
for (const line of enumsOut.split("\n")) {
|
|
214
|
+
const trimmed = line.trim();
|
|
215
|
+
if (trimmed.length === 0) continue;
|
|
216
|
+
const [enumName, valuesCsv] = trimmed.split("|").map((s) => s.trim());
|
|
217
|
+
const values = valuesCsv ? valuesCsv.split(",").map((s) => s.trim()) : [];
|
|
218
|
+
dbEnums.set(enumName, { name: enumName, values: uniqueSorted(values) });
|
|
219
|
+
}
|
|
220
|
+
return { dbTables, dbEnums };
|
|
221
|
+
}
|
|
222
|
+
function diffSchema(params) {
|
|
223
|
+
const missingTables = uniqueSorted(
|
|
224
|
+
[...params.expectedTables].filter((t) => !params.dbTables.has(t))
|
|
225
|
+
);
|
|
226
|
+
const exclusions = new Set(params.excludeFromOrphanDetection ?? []);
|
|
227
|
+
const exclusionPatterns = [...exclusions].filter((e) => e.includes("*"));
|
|
228
|
+
const exactExclusions = [...exclusions].filter((e) => !e.includes("*"));
|
|
229
|
+
const isExcluded = (table) => {
|
|
230
|
+
if (exactExclusions.includes(table)) return true;
|
|
231
|
+
for (const pattern of exclusionPatterns) {
|
|
232
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
233
|
+
const regex = new RegExp(`^${escaped.replace(/\\\*/g, ".*")}$`);
|
|
234
|
+
if (regex.test(table)) return true;
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
};
|
|
238
|
+
const orphanTables = uniqueSorted(
|
|
239
|
+
[...params.dbTables].filter((t) => !params.expectedTables.has(t) && !isExcluded(t))
|
|
240
|
+
);
|
|
241
|
+
const expectedEnumNames = new Set(params.expectedEnums.keys());
|
|
242
|
+
const dbEnumNames = new Set(params.dbEnums.keys());
|
|
243
|
+
const missingEnums = uniqueSorted([...expectedEnumNames].filter((n) => !dbEnumNames.has(n)));
|
|
244
|
+
const extraEnums = uniqueSorted([...dbEnumNames].filter((n) => !expectedEnumNames.has(n)));
|
|
245
|
+
const enumValueMismatches = [];
|
|
246
|
+
for (const name of uniqueSorted([...expectedEnumNames].filter((n) => dbEnumNames.has(n)))) {
|
|
247
|
+
const s = params.expectedEnums.get(name);
|
|
248
|
+
const d = params.dbEnums.get(name);
|
|
249
|
+
if (!s || !d) continue;
|
|
250
|
+
const schemaValues = uniqueSorted(s.values);
|
|
251
|
+
const dbValues = uniqueSorted(d.values);
|
|
252
|
+
const same = schemaValues.length === dbValues.length && schemaValues.every((v, i) => v === dbValues[i]);
|
|
253
|
+
if (same) continue;
|
|
254
|
+
const added = schemaValues.filter((v) => !dbValues.includes(v));
|
|
255
|
+
const removed = dbValues.filter((v) => !schemaValues.includes(v));
|
|
256
|
+
enumValueMismatches.push({ name, dbValues, schemaValues, added, removed });
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
expectedTables: params.expectedTables,
|
|
260
|
+
expectedEnums: params.expectedEnums,
|
|
261
|
+
dbTables: params.dbTables,
|
|
262
|
+
dbEnums: params.dbEnums,
|
|
263
|
+
missingTables,
|
|
264
|
+
orphanTables,
|
|
265
|
+
missingEnums,
|
|
266
|
+
extraEnums,
|
|
267
|
+
enumValueMismatches
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function extractTablesFromIdempotentSql(idempotentDir, projectRoot = process.cwd()) {
|
|
271
|
+
const fullPath = path2.resolve(projectRoot, idempotentDir);
|
|
272
|
+
if (!existsSync(fullPath)) {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
const tables = [];
|
|
276
|
+
const createTablePattern = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"?([a-zA-Z_][a-zA-Z0-9_]*)"?\.)?(?:"?([a-zA-Z_][a-zA-Z0-9_]*)"?)/gi;
|
|
277
|
+
try {
|
|
278
|
+
const files = readdirSync(fullPath).filter((f) => f.endsWith(".sql"));
|
|
279
|
+
for (const file of files) {
|
|
280
|
+
const filePath = path2.join(fullPath, file);
|
|
281
|
+
const content = readFileSync(filePath, "utf-8");
|
|
282
|
+
const contentWithoutComments = content.replace(/--.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
283
|
+
for (const match of contentWithoutComments.matchAll(createTablePattern)) {
|
|
284
|
+
const schema = match[1] || "public";
|
|
285
|
+
const tableName = match[2];
|
|
286
|
+
if (tableName) {
|
|
287
|
+
tables.push(`${schema}.${tableName}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
return [...new Set(tables)].sort();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/commands/db/utils/table-registry.ts
|
|
298
|
+
init_esm_shims();
|
|
299
|
+
|
|
300
|
+
// src/commands/db/utils/semantic-mapper.ts
|
|
301
|
+
init_esm_shims();
|
|
302
|
+
function snakeToCamel(str) {
|
|
303
|
+
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
304
|
+
}
|
|
305
|
+
function generateSemanticName(schema, tableName, useSchemaPrefix = false) {
|
|
306
|
+
const baseName = snakeToCamel(tableName);
|
|
307
|
+
if (useSchemaPrefix) {
|
|
308
|
+
const schemaPrefix = snakeToCamel(schema);
|
|
309
|
+
return schemaPrefix + baseName.charAt(0).toUpperCase() + baseName.slice(1);
|
|
310
|
+
}
|
|
311
|
+
return baseName;
|
|
312
|
+
}
|
|
313
|
+
function groupBySemanticName(tables) {
|
|
314
|
+
const bySemanticName = /* @__PURE__ */ new Map();
|
|
315
|
+
for (const table of tables) {
|
|
316
|
+
const baseName = snakeToCamel(table.name);
|
|
317
|
+
const existing = bySemanticName.get(baseName) ?? [];
|
|
318
|
+
existing.push(table);
|
|
319
|
+
bySemanticName.set(baseName, existing);
|
|
320
|
+
}
|
|
321
|
+
return bySemanticName;
|
|
322
|
+
}
|
|
323
|
+
function collectConflicts(bySemanticName) {
|
|
324
|
+
const conflicts = [];
|
|
325
|
+
for (const [semanticName, tables] of bySemanticName) {
|
|
326
|
+
if (tables.length <= 1) continue;
|
|
327
|
+
conflicts.push({
|
|
328
|
+
semanticName,
|
|
329
|
+
tables: tables.map((table) => table.qualifiedName)
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return conflicts;
|
|
333
|
+
}
|
|
334
|
+
function getSchemaPriorityRank(schema, prioritySchemas) {
|
|
335
|
+
const index = prioritySchemas.indexOf(schema);
|
|
336
|
+
return index === -1 ? Number.POSITIVE_INFINITY : index;
|
|
337
|
+
}
|
|
338
|
+
function compareTablesByPriorityAndName(left, right, prioritySchemas) {
|
|
339
|
+
const leftRank = getSchemaPriorityRank(left.schema, prioritySchemas);
|
|
340
|
+
const rightRank = getSchemaPriorityRank(right.schema, prioritySchemas);
|
|
341
|
+
if (leftRank !== rightRank) {
|
|
342
|
+
return leftRank - rightRank;
|
|
343
|
+
}
|
|
344
|
+
return left.qualifiedName.localeCompare(right.qualifiedName);
|
|
345
|
+
}
|
|
346
|
+
function applyOverride(table, overrides, mapping) {
|
|
347
|
+
const overrideName = overrides[table.qualifiedName];
|
|
348
|
+
if (!overrideName) return false;
|
|
349
|
+
mapping[overrideName] = table.qualifiedName;
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
function resolveFirstStrategy(table, baseName, tablesWithSameName, prioritySchemas) {
|
|
353
|
+
const sorted = [...tablesWithSameName].sort(
|
|
354
|
+
(left, right) => compareTablesByPriorityAndName(left, right, prioritySchemas)
|
|
355
|
+
);
|
|
356
|
+
if (sorted[0]?.qualifiedName === table.qualifiedName) {
|
|
357
|
+
return { mappedName: baseName };
|
|
358
|
+
}
|
|
359
|
+
return { skipped: true };
|
|
360
|
+
}
|
|
361
|
+
function resolveConflict(table, baseName, tablesWithSameName, conflictStrategy, prioritySchemas) {
|
|
362
|
+
switch (conflictStrategy) {
|
|
363
|
+
case "prefix":
|
|
364
|
+
return { mappedName: generateSemanticName(table.schema, table.name, true) };
|
|
365
|
+
case "error":
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Semantic name conflict: '${baseName}' maps to multiple tables: ${tablesWithSameName.map((candidate) => candidate.qualifiedName).join(", ")}`
|
|
368
|
+
);
|
|
369
|
+
case "first":
|
|
370
|
+
return resolveFirstStrategy(table, baseName, tablesWithSameName, prioritySchemas);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function generateMapping(tables, options = {}) {
|
|
374
|
+
const { conflictStrategy = "prefix", prioritySchemas = [], overrides = {} } = options;
|
|
375
|
+
const bySemanticName = groupBySemanticName(tables);
|
|
376
|
+
const conflicts = collectConflicts(bySemanticName);
|
|
377
|
+
const mapping = {};
|
|
378
|
+
const skipped = [];
|
|
379
|
+
for (const table of tables) {
|
|
380
|
+
if (applyOverride(table, overrides, mapping)) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const baseName = snakeToCamel(table.name);
|
|
384
|
+
const tablesWithSameName = bySemanticName.get(baseName) ?? [];
|
|
385
|
+
if (tablesWithSameName.length === 1) {
|
|
386
|
+
mapping[baseName] = table.qualifiedName;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const resolution = resolveConflict(
|
|
390
|
+
table,
|
|
391
|
+
baseName,
|
|
392
|
+
tablesWithSameName,
|
|
393
|
+
conflictStrategy,
|
|
394
|
+
prioritySchemas
|
|
395
|
+
);
|
|
396
|
+
if (resolution.mappedName) {
|
|
397
|
+
mapping[resolution.mappedName] = table.qualifiedName;
|
|
398
|
+
} else if (resolution.skipped) {
|
|
399
|
+
skipped.push(table.qualifiedName);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return { mapping, conflicts, skipped };
|
|
403
|
+
}
|
|
404
|
+
function applyMappingToTables(tables, mapping) {
|
|
405
|
+
const reverseMapping = /* @__PURE__ */ new Map();
|
|
406
|
+
for (const [semantic, qualified] of Object.entries(mapping)) {
|
|
407
|
+
reverseMapping.set(qualified, semantic);
|
|
408
|
+
}
|
|
409
|
+
return tables.map((table) => ({
|
|
410
|
+
...table,
|
|
411
|
+
semanticName: reverseMapping.get(table.qualifiedName) || table.semanticName
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/commands/db/utils/sql-table-extractor.ts
|
|
416
|
+
init_esm_shims();
|
|
417
|
+
|
|
418
|
+
// src/commands/db/utils/sql-table-extractor-regex.ts
|
|
419
|
+
init_esm_shims();
|
|
420
|
+
function findTablesRegex(ctx) {
|
|
421
|
+
const tables = [];
|
|
422
|
+
const regex = new RegExp(SQL_PATTERNS.createTable.source, "gi");
|
|
423
|
+
for (const match of ctx.content.matchAll(regex)) {
|
|
424
|
+
const reference = parseTableReference(match[1] ?? "");
|
|
425
|
+
if (!reference) continue;
|
|
426
|
+
const lineNumber = getLineNumber(ctx.content, match.index ?? 0);
|
|
427
|
+
const tableBody = extractTableBody(ctx.content, match.index ?? 0);
|
|
428
|
+
tables.push({ schema: reference.schema, name: reference.name, lineNumber, tableBody });
|
|
429
|
+
}
|
|
430
|
+
return tables;
|
|
431
|
+
}
|
|
432
|
+
function shouldSkipColumnLine(trimmed) {
|
|
433
|
+
return !trimmed || trimmed.startsWith("--") || /^(?:PRIMARY|FOREIGN|UNIQUE|CHECK|CONSTRAINT)\s/i.test(trimmed);
|
|
434
|
+
}
|
|
435
|
+
function isReservedKeyword(name) {
|
|
436
|
+
return /^(?:PRIMARY|FOREIGN|UNIQUE|CHECK|CONSTRAINT)$/i.test(name);
|
|
437
|
+
}
|
|
438
|
+
var COLUMN_CONSTRAINT_KEYWORDS = [
|
|
439
|
+
"NOT NULL",
|
|
440
|
+
"DEFAULT",
|
|
441
|
+
"REFERENCES",
|
|
442
|
+
"PRIMARY",
|
|
443
|
+
"UNIQUE",
|
|
444
|
+
"CHECK",
|
|
445
|
+
"CONSTRAINT"
|
|
446
|
+
];
|
|
447
|
+
function isBoundaryAtTopLevel(source, index, keyword) {
|
|
448
|
+
const before = index === 0 || /\s|[(),]/.test(source[index - 1] ?? "");
|
|
449
|
+
const after = index + keyword.length;
|
|
450
|
+
const afterChar = source[after] ?? "";
|
|
451
|
+
const afterBoundary = after === source.length || /\s|[(),]/.test(afterChar);
|
|
452
|
+
if (!before || !afterBoundary) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
return source.substring(index, after).toUpperCase() === keyword;
|
|
456
|
+
}
|
|
457
|
+
function createConstraintScanState() {
|
|
458
|
+
return {
|
|
459
|
+
depth: 0,
|
|
460
|
+
dollarTag: "",
|
|
461
|
+
inDollarQuote: false,
|
|
462
|
+
inDoubleQuote: false,
|
|
463
|
+
inSingleQuote: false
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function consumeConstraintDollarQuote(line, index, state) {
|
|
467
|
+
if ((line[index] ?? "") !== "$" || state.inSingleQuote || state.inDoubleQuote) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
if (state.inDollarQuote) {
|
|
471
|
+
const closeTag = `$${state.dollarTag}$`;
|
|
472
|
+
if (!line.slice(index).startsWith(closeTag)) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
state.inDollarQuote = false;
|
|
476
|
+
state.dollarTag = "";
|
|
477
|
+
return index + closeTag.length - 1;
|
|
478
|
+
}
|
|
479
|
+
const tagMatch = line.slice(index).match(/^\$([a-zA-Z_][a-zA-Z0-9_]*)?\$/);
|
|
480
|
+
if (!tagMatch) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
state.inDollarQuote = true;
|
|
484
|
+
state.dollarTag = tagMatch[1] ?? "";
|
|
485
|
+
return index + tagMatch[0].length - 1;
|
|
486
|
+
}
|
|
487
|
+
function consumeConstraintSingleQuote(line, index, state) {
|
|
488
|
+
if ((line[index] ?? "") !== "'" || state.inDoubleQuote || state.inDollarQuote) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
if (state.inSingleQuote && (line[index + 1] ?? "") === "'") {
|
|
492
|
+
return index + 1;
|
|
493
|
+
}
|
|
494
|
+
state.inSingleQuote = !state.inSingleQuote;
|
|
495
|
+
return index;
|
|
496
|
+
}
|
|
497
|
+
function consumeConstraintDoubleQuote(line, index, state) {
|
|
498
|
+
if ((line[index] ?? "") !== '"' || state.inSingleQuote || state.inDollarQuote) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
if (state.inDoubleQuote && (line[index + 1] ?? "") === '"') {
|
|
502
|
+
return index + 1;
|
|
503
|
+
}
|
|
504
|
+
state.inDoubleQuote = !state.inDoubleQuote;
|
|
505
|
+
return index;
|
|
506
|
+
}
|
|
507
|
+
function consumeConstraintQuote(line, index, state) {
|
|
508
|
+
const adjustedByDollar = consumeConstraintDollarQuote(line, index, state);
|
|
509
|
+
if (adjustedByDollar !== null) {
|
|
510
|
+
return adjustedByDollar;
|
|
511
|
+
}
|
|
512
|
+
const adjustedBySingle = consumeConstraintSingleQuote(line, index, state);
|
|
513
|
+
if (adjustedBySingle !== null) {
|
|
514
|
+
return adjustedBySingle;
|
|
515
|
+
}
|
|
516
|
+
return consumeConstraintDoubleQuote(line, index, state);
|
|
517
|
+
}
|
|
518
|
+
function isInsideConstraintQuote(state) {
|
|
519
|
+
return state.inSingleQuote || state.inDoubleQuote || state.inDollarQuote;
|
|
520
|
+
}
|
|
521
|
+
function updateConstraintDepth(char, state) {
|
|
522
|
+
if (char === "(") {
|
|
523
|
+
state.depth += 1;
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (char === ")" && state.depth > 0) {
|
|
527
|
+
state.depth -= 1;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function findKeywordAtTopLevel(upper, index, keywords) {
|
|
531
|
+
return keywords.some(
|
|
532
|
+
(keyword) => upper.startsWith(keyword, index) && isBoundaryAtTopLevel(upper, index, keyword)
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
function findConstraintStart(line, keywords = COLUMN_CONSTRAINT_KEYWORDS) {
|
|
536
|
+
const upper = line.toUpperCase();
|
|
537
|
+
const state = createConstraintScanState();
|
|
538
|
+
for (let i = 0; i < upper.length; i++) {
|
|
539
|
+
const adjustedIndex = consumeConstraintQuote(line, i, state);
|
|
540
|
+
if (adjustedIndex !== null) {
|
|
541
|
+
i = adjustedIndex;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (isInsideConstraintQuote(state)) continue;
|
|
545
|
+
const char = upper[i] ?? "";
|
|
546
|
+
updateConstraintDepth(char, state);
|
|
547
|
+
if (char === "(" || char === ")") continue;
|
|
548
|
+
if (state.depth !== 0) continue;
|
|
549
|
+
if (findKeywordAtTopLevel(upper, i, keywords)) return i;
|
|
550
|
+
}
|
|
551
|
+
return -1;
|
|
552
|
+
}
|
|
553
|
+
function parseInlineReference(constraintSource) {
|
|
554
|
+
const referenceStart = findConstraintStart(constraintSource, ["REFERENCES"]);
|
|
555
|
+
if (referenceStart === -1) {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
const afterConstraint = constraintSource.slice(referenceStart);
|
|
559
|
+
if (!/^\s*REFERENCES\s+/i.test(afterConstraint)) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
const refMatch = afterConstraint.match(
|
|
563
|
+
new RegExp(`^\\s*REFERENCES\\s+(${TABLE_REFERENCE})\\s*\\(\\s*(${SQL_IDENTIFIER})\\s*\\)`, "i")
|
|
564
|
+
);
|
|
565
|
+
if (!refMatch) {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
const ref = parseTableReference(refMatch[1] ?? "");
|
|
569
|
+
const refColumn = unquoteIdentifier(refMatch[2] ?? "");
|
|
570
|
+
if (!ref || !refColumn) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
return { table: `${ref.schema}.${ref.name}`, column: refColumn };
|
|
574
|
+
}
|
|
575
|
+
function splitColumnDeclaration(line) {
|
|
576
|
+
const columnMatch = line.match(
|
|
577
|
+
/^((?:"(?:[^"]|"")*"|[a-zA-Z_][a-zA-Z0-9_]*))\s+(.+?)(?:\s*,\s*)?$/
|
|
578
|
+
);
|
|
579
|
+
if (!columnMatch) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
const name = unquoteIdentifier(columnMatch[1] ?? "");
|
|
583
|
+
const rest = (columnMatch[2] ?? "").trim();
|
|
584
|
+
if (!name || isReservedKeyword(name)) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const typeEndIndex = (() => {
|
|
588
|
+
const withParens = rest;
|
|
589
|
+
const constraintsIndex = findConstraintStart(withParens);
|
|
590
|
+
if (constraintsIndex === -1) {
|
|
591
|
+
return withParens.length;
|
|
592
|
+
}
|
|
593
|
+
return constraintsIndex;
|
|
594
|
+
})();
|
|
595
|
+
const type = rest.slice(0, typeEndIndex).trim();
|
|
596
|
+
const remaining = rest.slice(typeEndIndex);
|
|
597
|
+
const inlineReference = parseInlineReference(remaining);
|
|
598
|
+
const hasDefault = findConstraintStart(remaining, ["DEFAULT"]) !== -1;
|
|
599
|
+
const notNull = findConstraintStart(remaining, ["NOT NULL"]) !== -1;
|
|
600
|
+
const isPrimaryKey = findConstraintStart(remaining, ["PRIMARY KEY"]) !== -1;
|
|
601
|
+
return {
|
|
602
|
+
name,
|
|
603
|
+
type,
|
|
604
|
+
hasDefault,
|
|
605
|
+
notNull,
|
|
606
|
+
isPrimaryKey,
|
|
607
|
+
inlineReferenceTable: inlineReference?.table,
|
|
608
|
+
inlineReferenceColumn: inlineReference?.column
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function parseColumnsRegex(tableBody) {
|
|
612
|
+
const columns = [];
|
|
613
|
+
const seen = /* @__PURE__ */ new Set();
|
|
614
|
+
const lines = splitTopLevelSqlStatements(tableBody);
|
|
615
|
+
for (const line of lines) {
|
|
616
|
+
const trimmed = line.trim();
|
|
617
|
+
if (shouldSkipColumnLine(trimmed)) continue;
|
|
618
|
+
const column = splitColumnDeclaration(trimmed);
|
|
619
|
+
if (!column) continue;
|
|
620
|
+
if (!column.name || seen.has(column.name) || isReservedKeyword(column.name)) continue;
|
|
621
|
+
seen.add(column.name);
|
|
622
|
+
columns.push({
|
|
623
|
+
name: column.name,
|
|
624
|
+
type: normalizeType(column.type),
|
|
625
|
+
notNull: column.notNull || column.isPrimaryKey,
|
|
626
|
+
hasDefault: column.hasDefault,
|
|
627
|
+
isPrimaryKey: column.isPrimaryKey
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return columns;
|
|
631
|
+
}
|
|
632
|
+
function parsePrimaryKeyRegex(tableBody) {
|
|
633
|
+
const regex = new RegExp(SQL_PATTERNS.primaryKey.source, "i");
|
|
634
|
+
const found = [];
|
|
635
|
+
for (const line of splitTopLevelSqlStatements(tableBody)) {
|
|
636
|
+
const match = line.match(regex);
|
|
637
|
+
if (!match || !match[1]) {
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
for (const col of match[1]?.split(",") ?? []) {
|
|
641
|
+
const normalized = unquoteIdentifier(col.trim());
|
|
642
|
+
if (normalized && !found.includes(normalized)) {
|
|
643
|
+
found.push(normalized);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
regex.lastIndex = 0;
|
|
647
|
+
}
|
|
648
|
+
return found;
|
|
649
|
+
}
|
|
650
|
+
function parseExplicitForeignKeys(tableBody) {
|
|
651
|
+
const fks = [];
|
|
652
|
+
const fkRegex = new RegExp(SQL_PATTERNS.foreignKey.source, "gi");
|
|
653
|
+
for (const line of splitTopLevelSqlStatements(tableBody)) {
|
|
654
|
+
for (const match of line.matchAll(fkRegex)) {
|
|
655
|
+
const column = unquoteIdentifier(match[1] ?? "");
|
|
656
|
+
const ref = parseTableReference(match[2] ?? "");
|
|
657
|
+
const refColumn = unquoteIdentifier(match[3] ?? "");
|
|
658
|
+
if (!column || !ref || !refColumn) continue;
|
|
659
|
+
fks.push({
|
|
660
|
+
column,
|
|
661
|
+
referencesTable: `${ref.schema}.${ref.name}`,
|
|
662
|
+
referencesColumn: refColumn,
|
|
663
|
+
onDelete: normalizeOnAction(match[4]),
|
|
664
|
+
onUpdate: normalizeOnAction(match[5])
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return fks;
|
|
669
|
+
}
|
|
670
|
+
function parseInlineForeignKeys(tableBody, existingColumns) {
|
|
671
|
+
const fks = [];
|
|
672
|
+
for (const fragment of splitTopLevelSqlStatements(tableBody)) {
|
|
673
|
+
const declaration = splitColumnDeclaration(fragment);
|
|
674
|
+
if (!declaration) continue;
|
|
675
|
+
if (!declaration.inlineReferenceTable || !declaration.inlineReferenceColumn) continue;
|
|
676
|
+
if (existingColumns.has(declaration.name)) continue;
|
|
677
|
+
fks.push({
|
|
678
|
+
column: declaration.name,
|
|
679
|
+
referencesTable: declaration.inlineReferenceTable,
|
|
680
|
+
referencesColumn: declaration.inlineReferenceColumn
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
return fks;
|
|
684
|
+
}
|
|
685
|
+
function parseForeignKeysRegex(tableBody) {
|
|
686
|
+
const explicitFks = parseExplicitForeignKeys(tableBody);
|
|
687
|
+
const existingColumns = new Set(explicitFks.map((fk) => fk.column));
|
|
688
|
+
const inlineFks = parseInlineForeignKeys(tableBody, existingColumns);
|
|
689
|
+
return [...explicitFks, ...inlineFks];
|
|
690
|
+
}
|
|
691
|
+
function parseIndexesRegex(content, schema, tableName) {
|
|
692
|
+
const indexes = [];
|
|
693
|
+
const regex = new RegExp(SQL_PATTERNS.createIndex.source, "gi");
|
|
694
|
+
for (const match of content.matchAll(regex)) {
|
|
695
|
+
const indexTableRef = parseTableReference(match[3] ?? "");
|
|
696
|
+
if (!indexTableRef) continue;
|
|
697
|
+
const indexSchema = indexTableRef.schema;
|
|
698
|
+
const indexTable = indexTableRef.name;
|
|
699
|
+
if (indexSchema === schema && indexTable === tableName) {
|
|
700
|
+
const indexName = unquoteIdentifier(match[2] ?? "");
|
|
701
|
+
if (!indexName) continue;
|
|
702
|
+
const rawColumns = match[4] ?? "";
|
|
703
|
+
indexes.push({
|
|
704
|
+
name: indexName,
|
|
705
|
+
columns: parseIndexColumns(rawColumns),
|
|
706
|
+
isUnique: !!match[1]
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return indexes;
|
|
711
|
+
}
|
|
712
|
+
function hasRlsEnabledRegex(content, schema, tableName) {
|
|
713
|
+
const regex = new RegExp(SQL_PATTERNS.enableRls.source, "gi");
|
|
714
|
+
for (const match of content.matchAll(regex)) {
|
|
715
|
+
const matchTable = parseTableReference(match[1] ?? "");
|
|
716
|
+
if (!matchTable) continue;
|
|
717
|
+
if (matchTable.schema === schema && matchTable.name === tableName) {
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/commands/db/utils/sql-table-extractor-rls.ts
|
|
725
|
+
init_esm_shims();
|
|
726
|
+
function readDollarTagAt(content, index) {
|
|
727
|
+
if (content[index] !== "$") return void 0;
|
|
728
|
+
const match = content.slice(index).match(/^\$([a-zA-Z_][a-zA-Z0-9_]*)?\$/);
|
|
729
|
+
return match?.[0];
|
|
730
|
+
}
|
|
731
|
+
function consumePolicySingleQuote(content, state) {
|
|
732
|
+
if (!state.inSingleQuote) return false;
|
|
733
|
+
const char = content[state.cursor] ?? "";
|
|
734
|
+
const next = content[state.cursor + 1] ?? "";
|
|
735
|
+
if (char === "'" && next === "'") {
|
|
736
|
+
state.cursor += 2;
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
if (char === "'") {
|
|
740
|
+
state.inSingleQuote = false;
|
|
741
|
+
}
|
|
742
|
+
state.cursor += 1;
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
function consumePolicyDoubleQuote(content, state) {
|
|
746
|
+
if (!state.inDoubleQuote) return false;
|
|
747
|
+
const char = content[state.cursor] ?? "";
|
|
748
|
+
const next = content[state.cursor + 1] ?? "";
|
|
749
|
+
if (char === '"' && next === '"') {
|
|
750
|
+
state.cursor += 2;
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
if (char === '"') {
|
|
754
|
+
state.inDoubleQuote = false;
|
|
755
|
+
}
|
|
756
|
+
state.cursor += 1;
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
function consumePolicyDollarQuote(content, state) {
|
|
760
|
+
if (!state.inDollarQuote) return false;
|
|
761
|
+
const closeTag = `$${state.dollarTag}$`;
|
|
762
|
+
if (content.startsWith(closeTag, state.cursor)) {
|
|
763
|
+
state.inDollarQuote = false;
|
|
764
|
+
state.dollarTag = "";
|
|
765
|
+
state.cursor += closeTag.length;
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
state.cursor += 1;
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
function trySkipPolicyLineComment(content, state) {
|
|
772
|
+
if (state.inSingleQuote || state.inDoubleQuote || state.inDollarQuote) return false;
|
|
773
|
+
const char = content[state.cursor] ?? "";
|
|
774
|
+
const next = content[state.cursor + 1] ?? "";
|
|
775
|
+
if (char !== "-" || next !== "-") return false;
|
|
776
|
+
const newlineIndex = content.indexOf("\n", state.cursor);
|
|
777
|
+
state.cursor = newlineIndex === -1 ? content.length : newlineIndex + 1;
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
function trySkipPolicyBlockComment(content, state) {
|
|
781
|
+
if (state.inSingleQuote || state.inDoubleQuote || state.inDollarQuote) return false;
|
|
782
|
+
const char = content[state.cursor] ?? "";
|
|
783
|
+
const next = content[state.cursor + 1] ?? "";
|
|
784
|
+
if (char !== "/" || next !== "*") return false;
|
|
785
|
+
const closeIndex = content.indexOf("*/", state.cursor + 2);
|
|
786
|
+
state.cursor = closeIndex === -1 ? content.length : closeIndex + 2;
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
function tryStartPolicyDollarQuote(content, state) {
|
|
790
|
+
if (state.inSingleQuote || state.inDoubleQuote) return false;
|
|
791
|
+
const tag = readDollarTagAt(content, state.cursor);
|
|
792
|
+
if (!tag) return false;
|
|
793
|
+
state.inDollarQuote = true;
|
|
794
|
+
state.dollarTag = tag.slice(1, -1);
|
|
795
|
+
state.cursor += tag.length;
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
function tryStartPolicySingleQuote(content, state) {
|
|
799
|
+
if (state.inDoubleQuote || state.inDollarQuote || content[state.cursor] !== "'") return false;
|
|
800
|
+
state.inSingleQuote = true;
|
|
801
|
+
state.cursor += 1;
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
function tryStartPolicyDoubleQuote(content, state) {
|
|
805
|
+
if (state.inSingleQuote || state.inDollarQuote || content[state.cursor] !== '"') return false;
|
|
806
|
+
state.inDoubleQuote = true;
|
|
807
|
+
state.cursor += 1;
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
function consumePolicySqlTrivia(content, state) {
|
|
811
|
+
if (consumePolicySingleQuote(content, state)) return true;
|
|
812
|
+
if (consumePolicyDoubleQuote(content, state)) return true;
|
|
813
|
+
if (consumePolicyDollarQuote(content, state)) return true;
|
|
814
|
+
if (trySkipPolicyLineComment(content, state)) return true;
|
|
815
|
+
if (trySkipPolicyBlockComment(content, state)) return true;
|
|
816
|
+
if (tryStartPolicyDollarQuote(content, state)) return true;
|
|
817
|
+
if (tryStartPolicySingleQuote(content, state)) return true;
|
|
818
|
+
return tryStartPolicyDoubleQuote(content, state);
|
|
819
|
+
}
|
|
820
|
+
function findOutsideSqlCharIndex(content, startIndex, predicate) {
|
|
821
|
+
const state = {
|
|
822
|
+
cursor: startIndex,
|
|
823
|
+
inSingleQuote: false,
|
|
824
|
+
inDoubleQuote: false,
|
|
825
|
+
inDollarQuote: false,
|
|
826
|
+
dollarTag: ""
|
|
827
|
+
};
|
|
828
|
+
while (state.cursor < content.length) {
|
|
829
|
+
if (consumePolicySqlTrivia(content, state)) continue;
|
|
830
|
+
const char = content[state.cursor] ?? "";
|
|
831
|
+
if (predicate(char, state.cursor)) return state.cursor;
|
|
832
|
+
state.cursor += 1;
|
|
833
|
+
}
|
|
834
|
+
return void 0;
|
|
835
|
+
}
|
|
836
|
+
function extractCreatePolicyStatements(content) {
|
|
837
|
+
const statements = [];
|
|
838
|
+
const startRegex = /\bCREATE\s+POLICY\b/gi;
|
|
839
|
+
let match;
|
|
840
|
+
while ((match = startRegex.exec(content)) !== null) {
|
|
841
|
+
const startIndex = match.index ?? 0;
|
|
842
|
+
const endIndex = findSqlStatementEndForPolicy(content, startIndex);
|
|
843
|
+
statements.push(content.slice(startIndex, endIndex).trim());
|
|
844
|
+
}
|
|
845
|
+
return statements;
|
|
846
|
+
}
|
|
847
|
+
function findSqlStatementEndForPolicy(content, startIndex) {
|
|
848
|
+
const semicolonIndex = findOutsideSqlCharIndex(content, startIndex, (char) => char === ";");
|
|
849
|
+
return semicolonIndex === void 0 ? content.length : semicolonIndex + 1;
|
|
850
|
+
}
|
|
851
|
+
function extractBalancedClause(statement, startIndex) {
|
|
852
|
+
const openParenIndex = statement.indexOf("(", startIndex);
|
|
853
|
+
if (openParenIndex === -1) return void 0;
|
|
854
|
+
let depth = 0;
|
|
855
|
+
let clauseStart = -1;
|
|
856
|
+
const closeParenIndex = findOutsideSqlCharIndex(statement, openParenIndex, (char, index) => {
|
|
857
|
+
if (char === "(") {
|
|
858
|
+
if (depth === 0) clauseStart = index + 1;
|
|
859
|
+
depth++;
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
if (char === ")") {
|
|
863
|
+
depth--;
|
|
864
|
+
return depth === 0 && clauseStart !== -1;
|
|
865
|
+
}
|
|
866
|
+
return false;
|
|
867
|
+
});
|
|
868
|
+
if (closeParenIndex === void 0 || clauseStart === -1) return void 0;
|
|
869
|
+
return statement.slice(clauseStart, closeParenIndex).trim();
|
|
870
|
+
}
|
|
871
|
+
function parsePolicyDefinitionFromStatement(statement, headerRegex, schema, tableName) {
|
|
872
|
+
const match = statement.match(headerRegex);
|
|
873
|
+
if (!match) return void 0;
|
|
874
|
+
const policyName = unquoteIdentifier(match[1] ?? match[2] ?? "");
|
|
875
|
+
const policyTableRef = parseTableReference(match[3] ?? "");
|
|
876
|
+
if (!policyName || !policyTableRef) return void 0;
|
|
877
|
+
if (policyTableRef.schema !== schema || policyTableRef.name !== tableName) return void 0;
|
|
878
|
+
const usingIndex = statement.search(/\bUSING\s*\(/i);
|
|
879
|
+
const withCheckIndex = statement.search(/\bWITH\s+CHECK\s*\(/i);
|
|
880
|
+
return {
|
|
881
|
+
name: policyName,
|
|
882
|
+
command: (match[4] || "ALL").toUpperCase(),
|
|
883
|
+
using: usingIndex !== -1 ? extractBalancedClause(statement, usingIndex) : void 0,
|
|
884
|
+
withCheck: withCheckIndex !== -1 ? extractBalancedClause(statement, withCheckIndex) : void 0
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function parsePoliciesRegex(content, schema, tableName) {
|
|
888
|
+
const policies = [];
|
|
889
|
+
const statements = extractCreatePolicyStatements(content);
|
|
890
|
+
const headerRegex = new RegExp(
|
|
891
|
+
`^\\s*CREATE\\s+POLICY\\s+(?:"((?:[^"]|"")*)"|([a-zA-Z_][a-zA-Z0-9_]*))\\s+ON\\s+(${TABLE_REFERENCE})(?:\\s+AS\\s+\\w+)?(?:\\s+FOR\\s+(\\w+))?`,
|
|
892
|
+
"i"
|
|
893
|
+
);
|
|
894
|
+
for (const statement of statements) {
|
|
895
|
+
const parsed = parsePolicyDefinitionFromStatement(statement, headerRegex, schema, tableName);
|
|
896
|
+
if (parsed) {
|
|
897
|
+
policies.push(parsed);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return policies;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/commands/db/utils/sql-table-extractor-ast.ts
|
|
904
|
+
init_esm_shims();
|
|
905
|
+
function convertAstColumns(columns, include) {
|
|
906
|
+
if (!include) return void 0;
|
|
907
|
+
return columns.map((col) => ({
|
|
908
|
+
name: col.name,
|
|
909
|
+
type: col.type,
|
|
910
|
+
notNull: col.notNull,
|
|
911
|
+
hasDefault: col.hasDefault,
|
|
912
|
+
isPrimaryKey: col.isPrimaryKey
|
|
913
|
+
}));
|
|
914
|
+
}
|
|
915
|
+
function convertAstForeignKeys(fks, include) {
|
|
916
|
+
if (!include) return void 0;
|
|
917
|
+
const result = fks.map((fk) => ({
|
|
918
|
+
column: fk.column,
|
|
919
|
+
referencesTable: fk.referencesTable,
|
|
920
|
+
referencesColumn: fk.referencesColumn,
|
|
921
|
+
onDelete: fk.onDelete,
|
|
922
|
+
onUpdate: fk.onUpdate
|
|
923
|
+
}));
|
|
924
|
+
return result.length > 0 ? result : void 0;
|
|
925
|
+
}
|
|
926
|
+
function convertAstIndexes(indexes, include) {
|
|
927
|
+
if (!include || indexes.length === 0) return void 0;
|
|
928
|
+
return indexes.map((idx) => ({
|
|
929
|
+
name: idx.name,
|
|
930
|
+
columns: idx.columns,
|
|
931
|
+
isUnique: idx.isUnique
|
|
932
|
+
}));
|
|
933
|
+
}
|
|
934
|
+
async function extractTablesWithAst(content, filePath, opts) {
|
|
935
|
+
const parser = await getSqlParserUtils();
|
|
936
|
+
if (!parser) return [];
|
|
937
|
+
const astTables = await parser.parseCreateTables(content);
|
|
938
|
+
const tables = [];
|
|
939
|
+
for (const astTable of astTables) {
|
|
940
|
+
if (opts.includeIndexes) {
|
|
941
|
+
await parser.parseIndexesForTables(content, [astTable]);
|
|
942
|
+
}
|
|
943
|
+
const hasRls = hasRlsEnabledRegex(content, astTable.schema, astTable.name);
|
|
944
|
+
const rlsPolicies = opts.includeRlsPolicies && hasRls ? parsePoliciesRegex(content, astTable.schema, astTable.name) : void 0;
|
|
945
|
+
tables.push({
|
|
946
|
+
schema: astTable.schema,
|
|
947
|
+
name: astTable.name,
|
|
948
|
+
qualifiedName: astTable.qualifiedName,
|
|
949
|
+
semanticName: snakeToCamel2(astTable.name),
|
|
950
|
+
sourceFile: filePath,
|
|
951
|
+
lineNumber: astTable.lineNumber ?? 1,
|
|
952
|
+
columns: convertAstColumns(astTable.columns, opts.includeColumns),
|
|
953
|
+
primaryKey: astTable.primaryKey,
|
|
954
|
+
foreignKeys: convertAstForeignKeys(astTable.foreignKeys, opts.includeForeignKeys),
|
|
955
|
+
indexes: convertAstIndexes(astTable.indexes, opts.includeIndexes),
|
|
956
|
+
hasRls,
|
|
957
|
+
rlsPolicies: rlsPolicies?.length ? rlsPolicies : void 0
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
return tables;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// src/commands/db/utils/sql-table-extractor.ts
|
|
964
|
+
var sqlParserUtils = null;
|
|
965
|
+
var astAvailable = null;
|
|
966
|
+
async function isAstParserAvailable() {
|
|
967
|
+
if (astAvailable !== null) return astAvailable;
|
|
968
|
+
try {
|
|
969
|
+
const { loadSqlParserUtils } = await import('@runa-ai/runa/ast');
|
|
970
|
+
sqlParserUtils = await loadSqlParserUtils();
|
|
971
|
+
const isAvailable = await sqlParserUtils.isSqlParserAvailable();
|
|
972
|
+
astAvailable = isAvailable;
|
|
973
|
+
return isAvailable;
|
|
974
|
+
} catch {
|
|
975
|
+
astAvailable = false;
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
async function getSqlParserUtils() {
|
|
980
|
+
if (sqlParserUtils) return sqlParserUtils;
|
|
981
|
+
await isAstParserAvailable();
|
|
982
|
+
return sqlParserUtils;
|
|
983
|
+
}
|
|
984
|
+
var SQL_IDENTIFIER = '(?:"(?:[^"]|"")*"|[a-zA-Z_][a-zA-Z0-9_]*)';
|
|
985
|
+
var TABLE_REFERENCE = `${SQL_IDENTIFIER}(?:\\s*\\.\\s*${SQL_IDENTIFIER})?`;
|
|
986
|
+
var TABLE_IDENTIFIER = /(?:"(?:[^"]|"")*"|[a-zA-Z_][a-zA-Z0-9_]*)/g;
|
|
987
|
+
var SQL_PATTERNS = {
|
|
988
|
+
// CREATE TABLE [IF NOT EXISTS] schema.table_name (
|
|
989
|
+
createTable: new RegExp(
|
|
990
|
+
`CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${TABLE_REFERENCE})\\s*\\(`,
|
|
991
|
+
"gi"
|
|
992
|
+
),
|
|
993
|
+
// PRIMARY KEY (columns)
|
|
994
|
+
primaryKey: /PRIMARY\s+KEY\s*\(([^)]+)\)/gi,
|
|
995
|
+
// FOREIGN KEY (column) REFERENCES schema.table(column) [ON DELETE|UPDATE ...]
|
|
996
|
+
foreignKey: new RegExp(
|
|
997
|
+
`FOREIGN\\s+KEY\\s*\\((${SQL_IDENTIFIER})\\)\\s*REFERENCES\\s+(${TABLE_REFERENCE})\\s*\\((${SQL_IDENTIFIER})\\)(?:\\s+ON\\s+DELETE\\s+(\\w+(?:\\s+\\w+)?))?(?:\\s+ON\\s+UPDATE\\s+(\\w+(?:\\s+\\w+)?))?`,
|
|
998
|
+
"gi"
|
|
999
|
+
),
|
|
1000
|
+
// CREATE [UNIQUE] INDEX name ON schema.table (columns)
|
|
1001
|
+
createIndex: new RegExp(
|
|
1002
|
+
`CREATE\\s+(UNIQUE\\s+)?INDEX\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${SQL_IDENTIFIER})\\s+ON\\s+(${TABLE_REFERENCE})(?:\\s+USING\\s+\\w+)?\\s*\\(([^)]+)\\)`,
|
|
1003
|
+
"gi"
|
|
1004
|
+
),
|
|
1005
|
+
// ALTER TABLE ... ENABLE ROW LEVEL SECURITY
|
|
1006
|
+
enableRls: new RegExp(
|
|
1007
|
+
`ALTER\\s+TABLE\\s+(${TABLE_REFERENCE})\\s+ENABLE\\s+ROW\\s+LEVEL\\s+SECURITY`,
|
|
1008
|
+
"gi"
|
|
1009
|
+
)
|
|
1010
|
+
};
|
|
1011
|
+
function createTopLevelSplitState() {
|
|
1012
|
+
return {
|
|
1013
|
+
depth: 0,
|
|
1014
|
+
dollarTag: "",
|
|
1015
|
+
inDollarQuote: false,
|
|
1016
|
+
inDoubleQuote: false,
|
|
1017
|
+
inSingleQuote: false,
|
|
1018
|
+
start: 0
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function consumeDollarQuoteToken(content, index, state) {
|
|
1022
|
+
if ((content[index] ?? "") !== "$" || state.inSingleQuote || state.inDoubleQuote) {
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
if (state.inDollarQuote) {
|
|
1026
|
+
const closeTag = `$${state.dollarTag}$`;
|
|
1027
|
+
if (!content.slice(index).startsWith(closeTag)) {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
state.inDollarQuote = false;
|
|
1031
|
+
state.dollarTag = "";
|
|
1032
|
+
return index + closeTag.length - 1;
|
|
1033
|
+
}
|
|
1034
|
+
const tagMatch = content.slice(index).match(/^\$([a-zA-Z_][a-zA-Z0-9_]*)?\$/);
|
|
1035
|
+
if (!tagMatch) {
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
1038
|
+
state.inDollarQuote = true;
|
|
1039
|
+
state.dollarTag = tagMatch[1] ?? "";
|
|
1040
|
+
return index + tagMatch[0].length - 1;
|
|
1041
|
+
}
|
|
1042
|
+
function consumeSingleQuoteToken(content, index, state) {
|
|
1043
|
+
if ((content[index] ?? "") !== "'" || state.inDoubleQuote || state.inDollarQuote) {
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
if (state.inSingleQuote && (content[index + 1] ?? "") === "'") {
|
|
1047
|
+
return index + 1;
|
|
1048
|
+
}
|
|
1049
|
+
state.inSingleQuote = !state.inSingleQuote;
|
|
1050
|
+
return index;
|
|
1051
|
+
}
|
|
1052
|
+
function consumeDoubleQuoteToken(content, index, state) {
|
|
1053
|
+
if ((content[index] ?? "") !== '"' || state.inSingleQuote || state.inDollarQuote) {
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1056
|
+
if (state.inDoubleQuote && (content[index + 1] ?? "") === '"') {
|
|
1057
|
+
return index + 1;
|
|
1058
|
+
}
|
|
1059
|
+
state.inDoubleQuote = !state.inDoubleQuote;
|
|
1060
|
+
return index;
|
|
1061
|
+
}
|
|
1062
|
+
function consumeQuoteToken(content, index, state) {
|
|
1063
|
+
const adjustedByDollarQuote = consumeDollarQuoteToken(content, index, state);
|
|
1064
|
+
if (adjustedByDollarQuote !== null) {
|
|
1065
|
+
return adjustedByDollarQuote;
|
|
1066
|
+
}
|
|
1067
|
+
const adjustedBySingleQuote = consumeSingleQuoteToken(content, index, state);
|
|
1068
|
+
if (adjustedBySingleQuote !== null) {
|
|
1069
|
+
return adjustedBySingleQuote;
|
|
1070
|
+
}
|
|
1071
|
+
return consumeDoubleQuoteToken(content, index, state);
|
|
1072
|
+
}
|
|
1073
|
+
function isInQuotedScope(state) {
|
|
1074
|
+
return state.inSingleQuote || state.inDoubleQuote || state.inDollarQuote;
|
|
1075
|
+
}
|
|
1076
|
+
function processStructuralToken(content, index, state, chunks) {
|
|
1077
|
+
const char = content[index] ?? "";
|
|
1078
|
+
if (char === "(") {
|
|
1079
|
+
state.depth += 1;
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (char === ")" && state.depth > 0) {
|
|
1083
|
+
state.depth -= 1;
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
if (char === "," && state.depth === 0) {
|
|
1087
|
+
chunks.push(content.slice(state.start, index));
|
|
1088
|
+
state.start = index + 1;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
function splitByTopLevelComma(content) {
|
|
1092
|
+
const chunks = [];
|
|
1093
|
+
const state = createTopLevelSplitState();
|
|
1094
|
+
for (let i = 0; i < content.length; i++) {
|
|
1095
|
+
const adjustedIndex = consumeQuoteToken(content, i, state);
|
|
1096
|
+
if (adjustedIndex !== null) {
|
|
1097
|
+
i = adjustedIndex;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (isInQuotedScope(state)) continue;
|
|
1101
|
+
processStructuralToken(content, i, state, chunks);
|
|
1102
|
+
}
|
|
1103
|
+
chunks.push(content.slice(state.start));
|
|
1104
|
+
return chunks.map((chunk) => chunk.trim()).filter(Boolean);
|
|
1105
|
+
}
|
|
1106
|
+
function splitTopLevelSqlStatements(content) {
|
|
1107
|
+
return splitByTopLevelComma(content);
|
|
1108
|
+
}
|
|
1109
|
+
function splitTopLevelCsv(content) {
|
|
1110
|
+
return splitByTopLevelComma(content);
|
|
1111
|
+
}
|
|
1112
|
+
function collectSqlFiles(dir) {
|
|
1113
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1114
|
+
const files = [];
|
|
1115
|
+
for (const entry of entries) {
|
|
1116
|
+
const fullPath = join(dir, entry.name);
|
|
1117
|
+
if (entry.isDirectory()) {
|
|
1118
|
+
files.push(...collectSqlFiles(fullPath));
|
|
1119
|
+
} else if (entry.isFile() && fullPath.endsWith(".sql")) {
|
|
1120
|
+
files.push(fullPath);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return files;
|
|
1124
|
+
}
|
|
1125
|
+
function snakeToCamel2(str) {
|
|
1126
|
+
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
1127
|
+
}
|
|
1128
|
+
function getLineNumber(content, position) {
|
|
1129
|
+
return content.substring(0, position).split("\n").length;
|
|
1130
|
+
}
|
|
1131
|
+
function extractTableBody(content, startPos) {
|
|
1132
|
+
let depth = 0;
|
|
1133
|
+
let bodyStart = -1;
|
|
1134
|
+
let bodyEnd = -1;
|
|
1135
|
+
for (let i = startPos; i < content.length; i++) {
|
|
1136
|
+
const char = content[i];
|
|
1137
|
+
if (char === "(") {
|
|
1138
|
+
if (depth === 0) {
|
|
1139
|
+
bodyStart = i + 1;
|
|
1140
|
+
}
|
|
1141
|
+
depth++;
|
|
1142
|
+
} else if (char === ")") {
|
|
1143
|
+
depth--;
|
|
1144
|
+
if (depth === 0) {
|
|
1145
|
+
bodyEnd = i;
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (bodyStart === -1 || bodyEnd === -1) {
|
|
1151
|
+
return "";
|
|
1152
|
+
}
|
|
1153
|
+
return content.substring(bodyStart, bodyEnd);
|
|
1154
|
+
}
|
|
1155
|
+
function normalizeType(type) {
|
|
1156
|
+
return type.trim().replace(/\s+/g, " ").toLowerCase().replace("character varying", "varchar").replace("timestamp with time zone", "timestamptz").replace("timestamp without time zone", "timestamp");
|
|
1157
|
+
}
|
|
1158
|
+
function unquoteIdentifier(identifier) {
|
|
1159
|
+
const trimmed = identifier.trim();
|
|
1160
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
1161
|
+
return trimmed.slice(1, -1).replace(/""/g, '"');
|
|
1162
|
+
}
|
|
1163
|
+
return trimmed;
|
|
1164
|
+
}
|
|
1165
|
+
function parseTableReference(ref) {
|
|
1166
|
+
const tokens = [...ref.matchAll(new RegExp(TABLE_IDENTIFIER.source, "g"))].map(
|
|
1167
|
+
(match) => unquoteIdentifier(match[0] ?? "")
|
|
1168
|
+
);
|
|
1169
|
+
if (tokens.length === 1) {
|
|
1170
|
+
return { schema: "public", name: tokens[0] ?? "" };
|
|
1171
|
+
}
|
|
1172
|
+
if (tokens.length === 2) {
|
|
1173
|
+
return { schema: tokens[0] ?? "", name: tokens[1] ?? "" };
|
|
1174
|
+
}
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
function parseIndexColumns(rawColumns) {
|
|
1178
|
+
return splitTopLevelCsv(rawColumns).map((col) => {
|
|
1179
|
+
const trimmed = col.trim();
|
|
1180
|
+
const quotedMatch = trimmed.match(/^"([^"]+)"/);
|
|
1181
|
+
if (quotedMatch) {
|
|
1182
|
+
return quotedMatch[1];
|
|
1183
|
+
}
|
|
1184
|
+
const unquotedMatch = trimmed.match(/^(\w+)/);
|
|
1185
|
+
if (unquotedMatch) {
|
|
1186
|
+
return unquotedMatch[1];
|
|
1187
|
+
}
|
|
1188
|
+
return "";
|
|
1189
|
+
}).filter(Boolean);
|
|
1190
|
+
}
|
|
1191
|
+
function normalizeOnAction(action) {
|
|
1192
|
+
if (!action) return void 0;
|
|
1193
|
+
const normalized = action.toUpperCase().replace(/\s+/g, " ").trim();
|
|
1194
|
+
switch (normalized) {
|
|
1195
|
+
case "CASCADE":
|
|
1196
|
+
return "CASCADE";
|
|
1197
|
+
case "SET NULL":
|
|
1198
|
+
return "SET NULL";
|
|
1199
|
+
case "SET DEFAULT":
|
|
1200
|
+
return "SET DEFAULT";
|
|
1201
|
+
case "RESTRICT":
|
|
1202
|
+
return "RESTRICT";
|
|
1203
|
+
case "NO ACTION":
|
|
1204
|
+
return "NO ACTION";
|
|
1205
|
+
default:
|
|
1206
|
+
return void 0;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function resolveOptions(options) {
|
|
1210
|
+
return {
|
|
1211
|
+
includeColumns: options.includeColumns ?? true,
|
|
1212
|
+
includeForeignKeys: options.includeForeignKeys ?? true,
|
|
1213
|
+
includeIndexes: options.includeIndexes ?? true,
|
|
1214
|
+
includeRlsPolicies: options.includeRlsPolicies ?? true
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
function buildTableEntryRegex(table, content, filePath, opts) {
|
|
1218
|
+
const qualifiedName = `${table.schema}.${table.name}`;
|
|
1219
|
+
const pkFromBody = parsePrimaryKeyRegex(table.tableBody);
|
|
1220
|
+
let columns;
|
|
1221
|
+
if (opts.includeColumns) {
|
|
1222
|
+
columns = parseColumnsRegex(table.tableBody);
|
|
1223
|
+
for (const col of columns) {
|
|
1224
|
+
if (pkFromBody.includes(col.name)) {
|
|
1225
|
+
col.isPrimaryKey = true;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
const foreignKeys = opts.includeForeignKeys ? parseForeignKeysRegex(table.tableBody) : void 0;
|
|
1230
|
+
const indexes = opts.includeIndexes ? parseIndexesRegex(content, table.schema, table.name) : void 0;
|
|
1231
|
+
const hasRls = hasRlsEnabledRegex(content, table.schema, table.name);
|
|
1232
|
+
const rlsPolicies = opts.includeRlsPolicies && hasRls ? parsePoliciesRegex(content, table.schema, table.name) : void 0;
|
|
1233
|
+
return {
|
|
1234
|
+
schema: table.schema,
|
|
1235
|
+
name: table.name,
|
|
1236
|
+
qualifiedName,
|
|
1237
|
+
semanticName: snakeToCamel2(table.name),
|
|
1238
|
+
sourceFile: filePath,
|
|
1239
|
+
lineNumber: table.lineNumber,
|
|
1240
|
+
columns,
|
|
1241
|
+
primaryKey: pkFromBody.length > 0 ? pkFromBody : void 0,
|
|
1242
|
+
foreignKeys: foreignKeys?.length ? foreignKeys : void 0,
|
|
1243
|
+
indexes: indexes?.length ? indexes : void 0,
|
|
1244
|
+
hasRls,
|
|
1245
|
+
rlsPolicies: rlsPolicies?.length ? rlsPolicies : void 0
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function processTablesFromFileRegex(filePath, opts, seen) {
|
|
1249
|
+
const rawContent = readFileSync(filePath, "utf-8");
|
|
1250
|
+
const content = stripSqlComments(rawContent);
|
|
1251
|
+
const ctx = { content, lines: content.split("\n") };
|
|
1252
|
+
const tables = findTablesRegex(ctx);
|
|
1253
|
+
const entries = [];
|
|
1254
|
+
for (const table of tables) {
|
|
1255
|
+
const qualifiedName = `${table.schema}.${table.name}`;
|
|
1256
|
+
if (seen?.has(qualifiedName)) continue;
|
|
1257
|
+
seen?.add(qualifiedName);
|
|
1258
|
+
entries.push(buildTableEntryRegex(table, content, filePath, opts));
|
|
1259
|
+
}
|
|
1260
|
+
return entries;
|
|
1261
|
+
}
|
|
1262
|
+
async function processTablesFromFile(filePath, opts, seen) {
|
|
1263
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1264
|
+
if (await isAstParserAvailable()) {
|
|
1265
|
+
const astTables = await extractTablesWithAst(content, filePath, opts);
|
|
1266
|
+
if (astTables.length > 0) {
|
|
1267
|
+
const entries = [];
|
|
1268
|
+
for (const table of astTables) {
|
|
1269
|
+
if (seen?.has(table.qualifiedName)) continue;
|
|
1270
|
+
seen?.add(table.qualifiedName);
|
|
1271
|
+
entries.push(table);
|
|
1272
|
+
}
|
|
1273
|
+
return entries;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return processTablesFromFileRegex(filePath, opts, seen);
|
|
1277
|
+
}
|
|
1278
|
+
async function extractTablesFromSqlDir(sqlDir, options = {}) {
|
|
1279
|
+
if (!existsSync(sqlDir)) return [];
|
|
1280
|
+
const opts = resolveOptions(options);
|
|
1281
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1282
|
+
const tableEntries = [];
|
|
1283
|
+
const files = collectSqlFiles(sqlDir).sort();
|
|
1284
|
+
for (const file of files) {
|
|
1285
|
+
const filePath = file;
|
|
1286
|
+
const entries = await processTablesFromFile(filePath, opts, seen);
|
|
1287
|
+
tableEntries.push(...entries);
|
|
1288
|
+
}
|
|
1289
|
+
return tableEntries;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// src/commands/db/utils/table-source-classifier.ts
|
|
1293
|
+
init_esm_shims();
|
|
1294
|
+
function splitQualifiedName(qualifiedName) {
|
|
1295
|
+
const [schema = "", table = ""] = qualifiedName.split(".", 2);
|
|
1296
|
+
return { schema, table };
|
|
1297
|
+
}
|
|
1298
|
+
function escapeRegexLiteral(value) {
|
|
1299
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1300
|
+
}
|
|
1301
|
+
function buildTablePatternMatcher(patterns) {
|
|
1302
|
+
const compiled = patterns.map((p) => p.trim()).filter((p) => p.length > 0).map((pattern) => {
|
|
1303
|
+
const target = pattern.includes(".") ? "qualified" : "table";
|
|
1304
|
+
const regex = new RegExp(`^${escapeRegexLiteral(pattern).replace(/\\\*/g, ".*")}$`);
|
|
1305
|
+
return { target, regex };
|
|
1306
|
+
});
|
|
1307
|
+
return (qualifiedName) => {
|
|
1308
|
+
const { table } = splitQualifiedName(qualifiedName);
|
|
1309
|
+
for (const entry of compiled) {
|
|
1310
|
+
const candidate = entry.target === "qualified" ? qualifiedName : table;
|
|
1311
|
+
if (entry.regex.test(candidate)) {
|
|
1312
|
+
return true;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return false;
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
function findIdempotentAncestor(table, partitionParentMap, idempotentManagedTables) {
|
|
1319
|
+
if (idempotentManagedTables.has(table)) {
|
|
1320
|
+
return table;
|
|
1321
|
+
}
|
|
1322
|
+
let current = table;
|
|
1323
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1324
|
+
while (!visited.has(current)) {
|
|
1325
|
+
visited.add(current);
|
|
1326
|
+
const parent = partitionParentMap.get(current);
|
|
1327
|
+
if (!parent) {
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
if (idempotentManagedTables.has(parent)) {
|
|
1331
|
+
return parent;
|
|
1332
|
+
}
|
|
1333
|
+
current = parent;
|
|
1334
|
+
}
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
function isSystemManagedTable(params) {
|
|
1338
|
+
const { schema } = splitQualifiedName(params.qualifiedName);
|
|
1339
|
+
return params.systemSchemas.has(schema) || params.knownSystemTables.has(params.qualifiedName);
|
|
1340
|
+
}
|
|
1341
|
+
function classifyIdempotentManagedTable(params) {
|
|
1342
|
+
const idempotentAncestor = findIdempotentAncestor(
|
|
1343
|
+
params.qualifiedName,
|
|
1344
|
+
params.partitionParentMap,
|
|
1345
|
+
params.idempotentManagedTables
|
|
1346
|
+
);
|
|
1347
|
+
if (!idempotentAncestor) {
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
qualifiedName: params.qualifiedName,
|
|
1352
|
+
detail: idempotentAncestor === params.qualifiedName ? "matched CREATE TABLE in idempotent SQL" : `partition child of ${idempotentAncestor}`
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
function classifyExtensionSystemOrAllowlistedTable(params) {
|
|
1356
|
+
const extensionName = params.extensionManagedTables.get(params.qualifiedName);
|
|
1357
|
+
if (extensionName) {
|
|
1358
|
+
return {
|
|
1359
|
+
qualifiedName: params.qualifiedName,
|
|
1360
|
+
detail: `managed by extension "${extensionName}"`
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
if (isSystemManagedTable({
|
|
1364
|
+
qualifiedName: params.qualifiedName,
|
|
1365
|
+
systemSchemas: params.systemSchemas,
|
|
1366
|
+
knownSystemTables: params.knownSystemTables
|
|
1367
|
+
})) {
|
|
1368
|
+
return {
|
|
1369
|
+
qualifiedName: params.qualifiedName,
|
|
1370
|
+
detail: "system-managed schema/table"
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
if (params.exclusionMatcher(params.qualifiedName)) {
|
|
1374
|
+
return {
|
|
1375
|
+
qualifiedName: params.qualifiedName,
|
|
1376
|
+
detail: "allowlisted by database.pgSchemaDiff.excludeFromOrphanDetection"
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
function classifyMissingSourceTables(params) {
|
|
1382
|
+
const extensionManagedTables = params.extensionManagedTables ?? /* @__PURE__ */ new Map();
|
|
1383
|
+
const partitionParentMap = params.partitionParentMap ?? /* @__PURE__ */ new Map();
|
|
1384
|
+
const exclusionMatcher = buildTablePatternMatcher(params.excludeFromOrphanDetection ?? []);
|
|
1385
|
+
const systemSchemas = new Set(params.systemSchemas ?? []);
|
|
1386
|
+
const knownSystemTables = new Set(params.knownSystemTables ?? []);
|
|
1387
|
+
const classified = {
|
|
1388
|
+
definedInIdempotentDynamicDdl: [],
|
|
1389
|
+
extensionManagedOrSystemTable: [],
|
|
1390
|
+
trulyOrphaned: []
|
|
1391
|
+
};
|
|
1392
|
+
for (const qualifiedName of params.tablesWithoutSource) {
|
|
1393
|
+
const idempotentManagedItem = classifyIdempotentManagedTable({
|
|
1394
|
+
qualifiedName,
|
|
1395
|
+
partitionParentMap,
|
|
1396
|
+
idempotentManagedTables: params.idempotentManagedTables
|
|
1397
|
+
});
|
|
1398
|
+
if (idempotentManagedItem) {
|
|
1399
|
+
classified.definedInIdempotentDynamicDdl.push(idempotentManagedItem);
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
const extensionSystemOrAllowlistedItem = classifyExtensionSystemOrAllowlistedTable({
|
|
1403
|
+
qualifiedName,
|
|
1404
|
+
extensionManagedTables,
|
|
1405
|
+
systemSchemas,
|
|
1406
|
+
knownSystemTables,
|
|
1407
|
+
exclusionMatcher
|
|
1408
|
+
});
|
|
1409
|
+
if (extensionSystemOrAllowlistedItem) {
|
|
1410
|
+
classified.extensionManagedOrSystemTable.push(extensionSystemOrAllowlistedItem);
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
classified.trulyOrphaned.push(qualifiedName);
|
|
1414
|
+
}
|
|
1415
|
+
return classified;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// src/commands/db/utils/table-registry-introspection.ts
|
|
1419
|
+
init_esm_shims();
|
|
1420
|
+
var VALID_PG_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/;
|
|
1421
|
+
function validatePgIdentifier2(name, context) {
|
|
1422
|
+
if (!name || typeof name !== "string") {
|
|
1423
|
+
throw new Error(`Invalid ${context}: empty or not a string`);
|
|
1424
|
+
}
|
|
1425
|
+
if (!VALID_PG_IDENTIFIER.test(name)) {
|
|
1426
|
+
throw new Error(
|
|
1427
|
+
`Invalid ${context} "${name}": must start with letter/underscore and contain only alphanumeric/underscore characters`
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function buildSafeSchemaInClause2(schemas) {
|
|
1432
|
+
if (schemas.length === 0) {
|
|
1433
|
+
throw new Error("No schemas provided for IN clause");
|
|
1434
|
+
}
|
|
1435
|
+
const safeSchemas = [];
|
|
1436
|
+
for (const schema of schemas) {
|
|
1437
|
+
validatePgIdentifier2(schema, "schema name");
|
|
1438
|
+
safeSchemas.push(`'${schema.replace(/'/g, "''")}'`);
|
|
1439
|
+
}
|
|
1440
|
+
return safeSchemas.join(",");
|
|
1441
|
+
}
|
|
1442
|
+
async function introspectTablesFromDb(databaseUrl, schemas) {
|
|
1443
|
+
try {
|
|
1444
|
+
const result = await introspectDatabase(databaseUrl, { schemas });
|
|
1445
|
+
return convertIntrospectionToTableEntries(result);
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1448
|
+
if (message.includes("ECONNREFUSED") || message.includes("connection refused")) {
|
|
1449
|
+
throw new Error(
|
|
1450
|
+
`[DB Introspection] Cannot connect to database.
|
|
1451
|
+
URL: ${databaseUrl.replace(/:[^:@]+@/, ":***@")}
|
|
1452
|
+
Cause: ${message}
|
|
1453
|
+
|
|
1454
|
+
Solutions:
|
|
1455
|
+
1. Ensure database is running: runa check --fix
|
|
1456
|
+
2. Check DATABASE_URL in .env.development`
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
if (message.includes("authentication") || message.includes("password")) {
|
|
1460
|
+
throw new Error(
|
|
1461
|
+
`[DB Introspection] Database authentication failed.
|
|
1462
|
+
Cause: ${message}
|
|
1463
|
+
|
|
1464
|
+
Solutions:
|
|
1465
|
+
1. Check DATABASE_URL credentials
|
|
1466
|
+
2. Verify database user has SELECT on pg_catalog`
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
if (message.includes("permission") || message.includes("denied")) {
|
|
1470
|
+
throw new Error(
|
|
1471
|
+
`[DB Introspection] Permission denied.
|
|
1472
|
+
Cause: ${message}
|
|
1473
|
+
|
|
1474
|
+
Solutions:
|
|
1475
|
+
1. Ensure database user has SELECT on information_schema
|
|
1476
|
+
2. Ensure database user has SELECT on pg_catalog`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
throw new Error(
|
|
1480
|
+
`[DB Introspection] Failed to introspect database.
|
|
1481
|
+
Cause: ${message}
|
|
1482
|
+
|
|
1483
|
+
Solutions:
|
|
1484
|
+
1. Run: runa check --verbose
|
|
1485
|
+
2. Verify database is running and accessible`
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function buildForeignKeyMap(foreignKeys) {
|
|
1490
|
+
const fksByTable = /* @__PURE__ */ new Map();
|
|
1491
|
+
for (const fk of foreignKeys) {
|
|
1492
|
+
const key = `${fk.schemaName}.${fk.tableName}`;
|
|
1493
|
+
if (!fksByTable.has(key)) fksByTable.set(key, []);
|
|
1494
|
+
fksByTable.get(key)?.push({
|
|
1495
|
+
column: fk.columnName,
|
|
1496
|
+
referencesTable: `${fk.referencedSchema}.${fk.referencedTable}`,
|
|
1497
|
+
referencesColumn: fk.referencedColumn,
|
|
1498
|
+
onDelete: normalizeOnAction2(fk.onDelete),
|
|
1499
|
+
onUpdate: normalizeOnAction2(fk.onUpdate)
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
return fksByTable;
|
|
1503
|
+
}
|
|
1504
|
+
function buildIndexMap(indexes) {
|
|
1505
|
+
const indexesByTable = /* @__PURE__ */ new Map();
|
|
1506
|
+
for (const idx of indexes) {
|
|
1507
|
+
const key = `${idx.schemaName}.${idx.tableName}`;
|
|
1508
|
+
if (!indexesByTable.has(key)) indexesByTable.set(key, []);
|
|
1509
|
+
indexesByTable.get(key)?.push({
|
|
1510
|
+
name: idx.indexName,
|
|
1511
|
+
columns: idx.columns,
|
|
1512
|
+
isUnique: idx.isUnique
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
return indexesByTable;
|
|
1516
|
+
}
|
|
1517
|
+
function buildRlsEnabledMap(rlsTables) {
|
|
1518
|
+
const rlsByTable = /* @__PURE__ */ new Map();
|
|
1519
|
+
for (const rls of rlsTables) {
|
|
1520
|
+
rlsByTable.set(`${rls.schemaName}.${rls.tableName}`, rls.rlsEnabled);
|
|
1521
|
+
}
|
|
1522
|
+
return rlsByTable;
|
|
1523
|
+
}
|
|
1524
|
+
function buildRlsPoliciesMap(rlsPolicies) {
|
|
1525
|
+
const rlsPoliciesByTable = /* @__PURE__ */ new Map();
|
|
1526
|
+
for (const policy of rlsPolicies) {
|
|
1527
|
+
const key = `${policy.schemaName}.${policy.tableName}`;
|
|
1528
|
+
if (!rlsPoliciesByTable.has(key)) rlsPoliciesByTable.set(key, []);
|
|
1529
|
+
rlsPoliciesByTable.get(key)?.push({
|
|
1530
|
+
name: policy.policyName,
|
|
1531
|
+
command: policy.command,
|
|
1532
|
+
using: policy.usingExpr ?? void 0,
|
|
1533
|
+
withCheck: policy.withCheckExpr ?? void 0
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
return rlsPoliciesByTable;
|
|
1537
|
+
}
|
|
1538
|
+
function buildCheckConstraintsMap(checkConstraints) {
|
|
1539
|
+
const checksByTable = /* @__PURE__ */ new Map();
|
|
1540
|
+
for (const check of checkConstraints) {
|
|
1541
|
+
if (!check.name || !check.definition) {
|
|
1542
|
+
console.warn(
|
|
1543
|
+
`[DB Introspection] Skipping CHECK constraint with missing name or definition in ${check.schemaName}.${check.tableName}`
|
|
1544
|
+
);
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
const key = `${check.schemaName}.${check.tableName}`;
|
|
1548
|
+
if (!checksByTable.has(key)) checksByTable.set(key, []);
|
|
1549
|
+
checksByTable.get(key)?.push({
|
|
1550
|
+
name: check.name,
|
|
1551
|
+
definition: check.definition,
|
|
1552
|
+
columns: check.columns ?? []
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
return checksByTable;
|
|
1556
|
+
}
|
|
1557
|
+
function buildTriggersMap(triggers) {
|
|
1558
|
+
const triggersByTable = /* @__PURE__ */ new Map();
|
|
1559
|
+
for (const trigger of triggers) {
|
|
1560
|
+
if (!trigger.actionStatement) {
|
|
1561
|
+
console.warn(
|
|
1562
|
+
`[DB Introspection] Skipping trigger ${trigger.triggerName} with missing actionStatement`
|
|
1563
|
+
);
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
const key = `${trigger.schemaName}.${trigger.tableName}`;
|
|
1567
|
+
if (!triggersByTable.has(key)) triggersByTable.set(key, []);
|
|
1568
|
+
const functionMatch = trigger.actionStatement.match(
|
|
1569
|
+
/EXECUTE\s+(?:FUNCTION|PROCEDURE)\s+([^\s(]+)/i
|
|
1570
|
+
);
|
|
1571
|
+
const functionName = functionMatch?.[1] ?? trigger.actionStatement;
|
|
1572
|
+
triggersByTable.get(key)?.push({
|
|
1573
|
+
name: trigger.triggerName,
|
|
1574
|
+
event: trigger.eventManipulation,
|
|
1575
|
+
timing: trigger.actionTiming,
|
|
1576
|
+
function: functionName
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
return triggersByTable;
|
|
1580
|
+
}
|
|
1581
|
+
function buildPrimaryKeyMap(indexes) {
|
|
1582
|
+
const pkByTable = /* @__PURE__ */ new Map();
|
|
1583
|
+
for (const idx of indexes) {
|
|
1584
|
+
if (idx.indexName.endsWith("_pkey")) {
|
|
1585
|
+
const key = `${idx.schemaName}.${idx.tableName}`;
|
|
1586
|
+
pkByTable.set(key, idx.columns);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return pkByTable;
|
|
1590
|
+
}
|
|
1591
|
+
function convertIntrospectionToTableEntries(result) {
|
|
1592
|
+
const fksByTable = buildForeignKeyMap(result.foreignKeys);
|
|
1593
|
+
const indexesByTable = buildIndexMap(result.indexes);
|
|
1594
|
+
const rlsByTable = buildRlsEnabledMap(result.rlsTables);
|
|
1595
|
+
const rlsPoliciesByTable = buildRlsPoliciesMap(result.rlsPolicies);
|
|
1596
|
+
const checksByTable = buildCheckConstraintsMap(result.checkConstraints);
|
|
1597
|
+
const triggersByTable = buildTriggersMap(result.triggers);
|
|
1598
|
+
const pkByTable = buildPrimaryKeyMap(result.indexes);
|
|
1599
|
+
return result.tables.map((table) => {
|
|
1600
|
+
const qualifiedName = `${table.schema}.${table.name}`;
|
|
1601
|
+
const primaryKeyColumns = pkByTable.get(qualifiedName) ?? [];
|
|
1602
|
+
const columns = table.columns.map((col) => ({
|
|
1603
|
+
name: col.name,
|
|
1604
|
+
type: col.type,
|
|
1605
|
+
notNull: !col.nullable,
|
|
1606
|
+
hasDefault: col.default !== null && col.default !== void 0,
|
|
1607
|
+
isPrimaryKey: primaryKeyColumns.includes(col.name)
|
|
1608
|
+
}));
|
|
1609
|
+
return {
|
|
1610
|
+
schema: table.schema,
|
|
1611
|
+
name: table.name,
|
|
1612
|
+
qualifiedName,
|
|
1613
|
+
semanticName: "",
|
|
1614
|
+
// Will be set by semantic mapper
|
|
1615
|
+
sourceFile: "",
|
|
1616
|
+
// Will be set by SQL file mapping
|
|
1617
|
+
columns,
|
|
1618
|
+
primaryKey: pkByTable.get(qualifiedName) ?? table.primaryKey,
|
|
1619
|
+
foreignKeys: fksByTable.get(qualifiedName) ?? [],
|
|
1620
|
+
indexes: indexesByTable.get(qualifiedName) ?? [],
|
|
1621
|
+
hasRls: rlsByTable.get(qualifiedName) ?? false,
|
|
1622
|
+
rlsPolicies: rlsPoliciesByTable.get(qualifiedName) ?? [],
|
|
1623
|
+
checkConstraints: checksByTable.get(qualifiedName) ?? [],
|
|
1624
|
+
triggers: triggersByTable.get(qualifiedName) ?? []
|
|
1625
|
+
};
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
function normalizeOnAction2(action) {
|
|
1629
|
+
if (!action) return void 0;
|
|
1630
|
+
const upper = action.toUpperCase().replace(/\s+/g, " ").trim();
|
|
1631
|
+
switch (upper) {
|
|
1632
|
+
case "CASCADE":
|
|
1633
|
+
return "CASCADE";
|
|
1634
|
+
case "SET NULL":
|
|
1635
|
+
return "SET NULL";
|
|
1636
|
+
case "SET DEFAULT":
|
|
1637
|
+
return "SET DEFAULT";
|
|
1638
|
+
case "RESTRICT":
|
|
1639
|
+
return "RESTRICT";
|
|
1640
|
+
case "NO ACTION":
|
|
1641
|
+
return "NO ACTION";
|
|
1642
|
+
default:
|
|
1643
|
+
return void 0;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
function shouldSkipDrizzleExport(exportName) {
|
|
1647
|
+
return exportName.endsWith("Schema") || exportName.endsWith("Enum") || exportName.endsWith("Relations");
|
|
1648
|
+
}
|
|
1649
|
+
function extractDrizzleTableNames(schemaModule) {
|
|
1650
|
+
const drizzleTables = /* @__PURE__ */ new Set();
|
|
1651
|
+
for (const [exportName, exportValue] of Object.entries(schemaModule)) {
|
|
1652
|
+
if (shouldSkipDrizzleExport(exportName)) continue;
|
|
1653
|
+
const maybeTable = exportValue;
|
|
1654
|
+
if (maybeTable?._?.name) {
|
|
1655
|
+
const fullName = maybeTable._.schema ? `${maybeTable._.schema}.${maybeTable._.name}` : maybeTable._.name;
|
|
1656
|
+
drizzleTables.add(fullName);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return drizzleTables;
|
|
1660
|
+
}
|
|
1661
|
+
function compareTables(sqlTables, drizzleTables) {
|
|
1662
|
+
const matched = [];
|
|
1663
|
+
const sqlOnly = [];
|
|
1664
|
+
const sqlFullNames = new Set(sqlTables.map((t) => t.qualifiedName));
|
|
1665
|
+
for (const table of sqlTables) {
|
|
1666
|
+
if (drizzleTables.has(table.qualifiedName)) {
|
|
1667
|
+
matched.push(table);
|
|
1668
|
+
} else {
|
|
1669
|
+
sqlOnly.push(table);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
const drizzleOnly = [...drizzleTables].filter((name) => !sqlFullNames.has(name));
|
|
1673
|
+
return { matched, sqlOnly, drizzleOnly };
|
|
1674
|
+
}
|
|
1675
|
+
async function crossCheckWithDrizzle(sqlTables, drizzleSchemaPath) {
|
|
1676
|
+
if (!existsSync(drizzleSchemaPath)) {
|
|
1677
|
+
return { matched: [], sqlOnly: sqlTables, drizzleOnly: [] };
|
|
1678
|
+
}
|
|
1679
|
+
try {
|
|
1680
|
+
const schemaModule = await import(drizzleSchemaPath);
|
|
1681
|
+
const drizzleTables = extractDrizzleTableNames(schemaModule);
|
|
1682
|
+
return compareTables(sqlTables, drizzleTables);
|
|
1683
|
+
} catch {
|
|
1684
|
+
return { matched: [], sqlOnly: sqlTables, drizzleOnly: [] };
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
// src/commands/db/utils/table-registry.ts
|
|
1689
|
+
var MANIFEST_VERSION = 2;
|
|
1690
|
+
var GENERATOR_VERSION = "1.0.0";
|
|
1691
|
+
var DEFAULT_IDEMPOTENT_SQL_DIR = "supabase/schemas/idempotent";
|
|
1692
|
+
var KNOWN_EXTENSION_SYSTEM_TABLES = /* @__PURE__ */ new Set([
|
|
1693
|
+
"public.spatial_ref_sys",
|
|
1694
|
+
"public.geometry_columns",
|
|
1695
|
+
"public.geography_columns"
|
|
1696
|
+
]);
|
|
1697
|
+
var SUPABASE_SYSTEM_SCHEMA_SET = new Set(SUPABASE_SYSTEM_SCHEMAS);
|
|
1698
|
+
function toRelativeSourcePath(projectRoot, sourceFile) {
|
|
1699
|
+
let relativeSource = relative(projectRoot, sourceFile);
|
|
1700
|
+
if (relativeSource.startsWith("/") || relativeSource.startsWith("..")) {
|
|
1701
|
+
const schemaMatch = sourceFile.match(/supabase\/schemas\/[^/]+\/[^/]+$/);
|
|
1702
|
+
relativeSource = schemaMatch ? schemaMatch[0] : sourceFile;
|
|
1703
|
+
}
|
|
1704
|
+
return relativeSource;
|
|
1705
|
+
}
|
|
1706
|
+
function resolveSourceConfig(projectRoot, options) {
|
|
1707
|
+
let idempotentSqlDir = options.idempotentSqlDir ?? DEFAULT_IDEMPOTENT_SQL_DIR;
|
|
1708
|
+
const exclusions = new Set(options.excludeFromOrphanDetection ?? []);
|
|
1709
|
+
try {
|
|
1710
|
+
const config = loadRunaConfig(projectRoot);
|
|
1711
|
+
const pgSchemaDiff = config.database?.pgSchemaDiff;
|
|
1712
|
+
if (!options.idempotentSqlDir && pgSchemaDiff?.idempotentSqlDir) {
|
|
1713
|
+
idempotentSqlDir = pgSchemaDiff.idempotentSqlDir;
|
|
1714
|
+
}
|
|
1715
|
+
if (pgSchemaDiff?.excludeFromOrphanDetection) {
|
|
1716
|
+
for (const pattern of pgSchemaDiff.excludeFromOrphanDetection) {
|
|
1717
|
+
exclusions.add(pattern);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
} catch {
|
|
1721
|
+
}
|
|
1722
|
+
return {
|
|
1723
|
+
idempotentSqlDir: isAbsolute(idempotentSqlDir) ? idempotentSqlDir : join(projectRoot, idempotentSqlDir),
|
|
1724
|
+
excludeFromOrphanDetection: [...exclusions].sort((a, b) => a.localeCompare(b))
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
async function fetchMissingSourceMetadata(params) {
|
|
1728
|
+
const { databaseUrl, schemas } = params;
|
|
1729
|
+
if (schemas.length === 0) {
|
|
1730
|
+
return {
|
|
1731
|
+
extensionManagedTables: /* @__PURE__ */ new Map(),
|
|
1732
|
+
partitionParentMap: /* @__PURE__ */ new Map()
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
const isRemoteSupabase = databaseUrl.includes(".supabase.co");
|
|
1736
|
+
const sql = postgres2(databaseUrl, {
|
|
1737
|
+
...isRemoteSupabase && { ssl: "require" }
|
|
1738
|
+
});
|
|
1739
|
+
try {
|
|
1740
|
+
const schemaList = buildSafeSchemaInClause2(schemas);
|
|
1741
|
+
const [extensionRows, partitionRows] = await Promise.all([
|
|
1742
|
+
sql`
|
|
1743
|
+
SELECT
|
|
1744
|
+
n.nspname AS schema_name,
|
|
1745
|
+
c.relname AS table_name,
|
|
1746
|
+
ext.extname AS extension_name
|
|
1747
|
+
FROM pg_class c
|
|
1748
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1749
|
+
JOIN pg_depend d
|
|
1750
|
+
ON d.classid = 'pg_class'::regclass
|
|
1751
|
+
AND d.objid = c.oid
|
|
1752
|
+
AND d.refclassid = 'pg_extension'::regclass
|
|
1753
|
+
AND d.deptype = 'e'
|
|
1754
|
+
JOIN pg_extension ext ON ext.oid = d.refobjid
|
|
1755
|
+
WHERE c.relkind IN ('r', 'p')
|
|
1756
|
+
AND n.nspname IN (${sql.unsafe(schemaList)})
|
|
1757
|
+
`,
|
|
1758
|
+
sql`
|
|
1759
|
+
SELECT
|
|
1760
|
+
child_ns.nspname AS child_schema,
|
|
1761
|
+
child.relname AS child_table,
|
|
1762
|
+
parent_ns.nspname AS parent_schema,
|
|
1763
|
+
parent.relname AS parent_table
|
|
1764
|
+
FROM pg_inherits i
|
|
1765
|
+
JOIN pg_class child ON child.oid = i.inhrelid
|
|
1766
|
+
JOIN pg_namespace child_ns ON child_ns.oid = child.relnamespace
|
|
1767
|
+
JOIN pg_class parent ON parent.oid = i.inhparent
|
|
1768
|
+
JOIN pg_namespace parent_ns ON parent_ns.oid = parent.relnamespace
|
|
1769
|
+
WHERE child.relkind IN ('r', 'p')
|
|
1770
|
+
AND child_ns.nspname IN (${sql.unsafe(schemaList)})
|
|
1771
|
+
`
|
|
1772
|
+
]);
|
|
1773
|
+
const extensionManagedTables = /* @__PURE__ */ new Map();
|
|
1774
|
+
for (const row of extensionRows) {
|
|
1775
|
+
extensionManagedTables.set(
|
|
1776
|
+
`${String(row.schema_name)}.${String(row.table_name)}`,
|
|
1777
|
+
String(row.extension_name)
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
const partitionParentMap = /* @__PURE__ */ new Map();
|
|
1781
|
+
for (const row of partitionRows) {
|
|
1782
|
+
partitionParentMap.set(
|
|
1783
|
+
`${String(row.child_schema)}.${String(row.child_table)}`,
|
|
1784
|
+
`${String(row.parent_schema)}.${String(row.parent_table)}`
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
return { extensionManagedTables, partitionParentMap };
|
|
1788
|
+
} finally {
|
|
1789
|
+
await sql.end();
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function formatMissingSourceItems(items) {
|
|
1793
|
+
return items.map((item) => item.detail ? `${item.qualifiedName} (${item.detail})` : item.qualifiedName).join(", ");
|
|
1794
|
+
}
|
|
1795
|
+
function logMissingSourceClassification(classification) {
|
|
1796
|
+
const total = classification.definedInIdempotentDynamicDdl.length + classification.extensionManagedOrSystemTable.length + classification.trulyOrphaned.length;
|
|
1797
|
+
if (total === 0) return;
|
|
1798
|
+
console.warn(`[tables-manifest] \u26A0 ${total} table(s) exist in DB but not in SQL files.`);
|
|
1799
|
+
if (classification.definedInIdempotentDynamicDdl.length > 0) {
|
|
1800
|
+
console.log(
|
|
1801
|
+
`[tables-manifest] info: defined_in_idempotent_dynamic_ddl (${classification.definedInIdempotentDynamicDdl.length})`
|
|
1802
|
+
);
|
|
1803
|
+
console.log(` ${formatMissingSourceItems(classification.definedInIdempotentDynamicDdl)}`);
|
|
1804
|
+
}
|
|
1805
|
+
if (classification.extensionManagedOrSystemTable.length > 0) {
|
|
1806
|
+
console.log(
|
|
1807
|
+
`[tables-manifest] info: extension_managed/system_table (${classification.extensionManagedOrSystemTable.length})`
|
|
1808
|
+
);
|
|
1809
|
+
console.log(` ${formatMissingSourceItems(classification.extensionManagedOrSystemTable)}`);
|
|
1810
|
+
}
|
|
1811
|
+
if (classification.trulyOrphaned.length > 0) {
|
|
1812
|
+
console.warn(`[tables-manifest] warn: truly_orphaned (${classification.trulyOrphaned.length})`);
|
|
1813
|
+
console.warn(` ${classification.trulyOrphaned.join(", ")}`);
|
|
1814
|
+
console.warn(
|
|
1815
|
+
" \u2192 Add declarative/idempotent SQL definitions or allowlist via database.pgSchemaDiff.excludeFromOrphanDetection."
|
|
1816
|
+
);
|
|
1817
|
+
} else {
|
|
1818
|
+
console.log("[tables-manifest] info: no truly_orphaned tables detected.");
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
async function logDrizzleCrossCheck(tables, drizzleSchemaPath) {
|
|
1822
|
+
const result = await crossCheckWithDrizzle(tables, drizzleSchemaPath);
|
|
1823
|
+
if (result.sqlOnly.length === 0 && result.drizzleOnly.length === 0) return;
|
|
1824
|
+
console.warn("[tables-manifest] SQL\u2194Drizzle discrepancies found:");
|
|
1825
|
+
if (result.sqlOnly.length > 0) {
|
|
1826
|
+
console.warn(" SQL only:", result.sqlOnly.map((t) => t.qualifiedName).join(", "));
|
|
1827
|
+
}
|
|
1828
|
+
if (result.drizzleOnly.length > 0) {
|
|
1829
|
+
console.warn(" Drizzle only:", result.drizzleOnly.join(", "));
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
function logMappingConflicts(conflicts) {
|
|
1833
|
+
if (conflicts.length === 0) return;
|
|
1834
|
+
console.warn("[tables-manifest] Semantic name conflicts detected:");
|
|
1835
|
+
for (const conflict of conflicts) {
|
|
1836
|
+
console.warn(` '${conflict.semanticName}': ${conflict.tables.join(", ")}`);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
async function generateTablesManifest(projectRoot, options = {}) {
|
|
1840
|
+
const {
|
|
1841
|
+
sqlDir = join(projectRoot, "supabase/schemas/declarative"),
|
|
1842
|
+
drizzleSchemaPath = join(projectRoot, "packages/database/src/schema/index.js"),
|
|
1843
|
+
outputPath = join(projectRoot, ".runa/manifests/tables.json"),
|
|
1844
|
+
crossCheck = true,
|
|
1845
|
+
databaseUrl,
|
|
1846
|
+
mappingOptions = { conflictStrategy: "prefix" },
|
|
1847
|
+
// includeMetadata is defined in options but not yet used
|
|
1848
|
+
// Reserved for future metadata filtering feature
|
|
1849
|
+
includeMetadata: _includeMetadata = true
|
|
1850
|
+
} = options;
|
|
1851
|
+
const sourceConfig = resolveSourceConfig(projectRoot, options);
|
|
1852
|
+
let tables = [];
|
|
1853
|
+
const source = "introspection";
|
|
1854
|
+
const declarativeTables = await extractTablesFromSqlDir(sqlDir, {
|
|
1855
|
+
includeColumns: false,
|
|
1856
|
+
// Don't need columns from SQL (DB introspection is more accurate)
|
|
1857
|
+
includeForeignKeys: false,
|
|
1858
|
+
includeIndexes: false,
|
|
1859
|
+
includeRlsPolicies: false
|
|
1860
|
+
});
|
|
1861
|
+
const idempotentTablesForSource = await extractTablesFromSqlDir(sourceConfig.idempotentSqlDir, {
|
|
1862
|
+
includeColumns: false,
|
|
1863
|
+
includeForeignKeys: false,
|
|
1864
|
+
includeIndexes: false,
|
|
1865
|
+
includeRlsPolicies: false
|
|
1866
|
+
});
|
|
1867
|
+
const idempotentTablesFromRegex = extractTablesFromIdempotentSql(
|
|
1868
|
+
sourceConfig.idempotentSqlDir,
|
|
1869
|
+
projectRoot
|
|
1870
|
+
);
|
|
1871
|
+
const idempotentManagedTables = /* @__PURE__ */ new Set([
|
|
1872
|
+
...idempotentTablesFromRegex,
|
|
1873
|
+
...idempotentTablesForSource.map((t) => t.qualifiedName)
|
|
1874
|
+
]);
|
|
1875
|
+
const sourceFileMap = /* @__PURE__ */ new Map();
|
|
1876
|
+
const sourceTables = [...declarativeTables, ...idempotentTablesForSource];
|
|
1877
|
+
for (const t of sourceTables) {
|
|
1878
|
+
if (sourceFileMap.has(t.qualifiedName)) {
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
sourceFileMap.set(t.qualifiedName, {
|
|
1882
|
+
sourceFile: toRelativeSourcePath(projectRoot, t.sourceFile),
|
|
1883
|
+
lineNumber: t.lineNumber
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
if (!databaseUrl) {
|
|
1887
|
+
throw new Error(
|
|
1888
|
+
"[tables-manifest] databaseUrl is required.\nDB introspection is the primary source for accurate table metadata.\nEnsure database is running and DATABASE_URL is set.\nRun: runa db sync (which starts the database automatically)"
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
console.log("[tables-manifest] Using DB introspection (PostgreSQL system catalogs)...");
|
|
1892
|
+
tables = await introspectTablesFromDb(databaseUrl);
|
|
1893
|
+
tables = tables.map((t) => {
|
|
1894
|
+
const fileInfo = sourceFileMap.get(t.qualifiedName);
|
|
1895
|
+
return {
|
|
1896
|
+
...t,
|
|
1897
|
+
sourceFile: fileInfo?.sourceFile ?? "",
|
|
1898
|
+
lineNumber: fileInfo?.lineNumber
|
|
1899
|
+
};
|
|
1900
|
+
});
|
|
1901
|
+
console.log(`[tables-manifest] \u2713 Introspected ${tables.length} tables from database`);
|
|
1902
|
+
const tablesWithoutSource = tables.filter((t) => !t.sourceFile);
|
|
1903
|
+
if (tablesWithoutSource.length > 0) {
|
|
1904
|
+
const missingSourceQualifiedNames = tablesWithoutSource.map((t) => t.qualifiedName);
|
|
1905
|
+
const missingSchemas = [...new Set(tablesWithoutSource.map((t) => t.schema))];
|
|
1906
|
+
let extensionManagedTables = /* @__PURE__ */ new Map();
|
|
1907
|
+
let partitionParentMap = /* @__PURE__ */ new Map();
|
|
1908
|
+
try {
|
|
1909
|
+
const metadata = await fetchMissingSourceMetadata({
|
|
1910
|
+
databaseUrl,
|
|
1911
|
+
schemas: missingSchemas
|
|
1912
|
+
});
|
|
1913
|
+
extensionManagedTables = metadata.extensionManagedTables;
|
|
1914
|
+
partitionParentMap = metadata.partitionParentMap;
|
|
1915
|
+
} catch (error) {
|
|
1916
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1917
|
+
console.warn(`[tables-manifest] Failed to classify extension/partition metadata: ${message}`);
|
|
1918
|
+
}
|
|
1919
|
+
const classification = classifyMissingSourceTables({
|
|
1920
|
+
tablesWithoutSource: missingSourceQualifiedNames,
|
|
1921
|
+
idempotentManagedTables,
|
|
1922
|
+
extensionManagedTables,
|
|
1923
|
+
partitionParentMap,
|
|
1924
|
+
excludeFromOrphanDetection: sourceConfig.excludeFromOrphanDetection,
|
|
1925
|
+
systemSchemas: SUPABASE_SYSTEM_SCHEMA_SET,
|
|
1926
|
+
knownSystemTables: KNOWN_EXTENSION_SYSTEM_TABLES
|
|
1927
|
+
});
|
|
1928
|
+
logMissingSourceClassification(classification);
|
|
1929
|
+
}
|
|
1930
|
+
if (crossCheck && existsSync(drizzleSchemaPath)) {
|
|
1931
|
+
await logDrizzleCrossCheck(tables, drizzleSchemaPath);
|
|
1932
|
+
}
|
|
1933
|
+
const mappingResult = generateMapping(tables, mappingOptions);
|
|
1934
|
+
logMappingConflicts(mappingResult.conflicts);
|
|
1935
|
+
tables = applyMappingToTables(tables, mappingResult.mapping);
|
|
1936
|
+
const now = /* @__PURE__ */ new Date();
|
|
1937
|
+
const jstOffset = 9 * 60 * 60 * 1e3;
|
|
1938
|
+
const jst = new Date(now.getTime() + jstOffset);
|
|
1939
|
+
const generatedAtJST = `${jst.getUTCFullYear()}-${String(jst.getUTCMonth() + 1).padStart(2, "0")}-${String(jst.getUTCDate()).padStart(2, "0")}T${String(jst.getUTCHours()).padStart(2, "0")}:${String(jst.getUTCMinutes()).padStart(2, "0")}:${String(jst.getUTCSeconds()).padStart(2, "0")}+09:00`;
|
|
1940
|
+
const manifest = {
|
|
1941
|
+
version: MANIFEST_VERSION,
|
|
1942
|
+
source,
|
|
1943
|
+
generatedAt: generatedAtJST,
|
|
1944
|
+
generatorVersion: GENERATOR_VERSION,
|
|
1945
|
+
tables,
|
|
1946
|
+
mapping: mappingResult.mapping
|
|
1947
|
+
};
|
|
1948
|
+
const outputDir = join(outputPath, "..");
|
|
1949
|
+
if (!existsSync(outputDir)) {
|
|
1950
|
+
mkdirSync(outputDir, { recursive: true });
|
|
1951
|
+
}
|
|
1952
|
+
writeFileSync(outputPath, `${JSON.stringify(manifest, null, 2)}
|
|
1953
|
+
`);
|
|
1954
|
+
logManifestSummary(manifest, mappingResult.conflicts);
|
|
1955
|
+
return manifest;
|
|
1956
|
+
}
|
|
1957
|
+
function logManifestSummary(manifest, conflicts) {
|
|
1958
|
+
const tableCount = manifest.tables.length;
|
|
1959
|
+
const schemas = [...new Set(manifest.tables.map((t) => t.schema))];
|
|
1960
|
+
const mappingCount = Object.keys(manifest.mapping).length;
|
|
1961
|
+
const checkCount = manifest.tables.reduce(
|
|
1962
|
+
(sum, t) => sum + (t.checkConstraints?.length ?? 0),
|
|
1963
|
+
0
|
|
1964
|
+
);
|
|
1965
|
+
const triggerCount = manifest.tables.reduce(
|
|
1966
|
+
(sum, t) => sum + (t.triggers?.length ?? 0),
|
|
1967
|
+
0
|
|
1968
|
+
);
|
|
1969
|
+
console.log("\n\u2713 Tables manifest generated");
|
|
1970
|
+
console.log(` - Source: DB introspection (PostgreSQL system catalogs)`);
|
|
1971
|
+
console.log(` - ${tableCount} tables extracted`);
|
|
1972
|
+
console.log(` - ${schemas.length} schemas: ${schemas.join(", ")}`);
|
|
1973
|
+
console.log(` - ${mappingCount} semantic names mapped`);
|
|
1974
|
+
if (checkCount > 0) {
|
|
1975
|
+
console.log(` - ${checkCount} CHECK constraints detected`);
|
|
1976
|
+
}
|
|
1977
|
+
if (triggerCount > 0) {
|
|
1978
|
+
console.log(` - ${triggerCount} triggers detected`);
|
|
1979
|
+
}
|
|
1980
|
+
const mappingEntries = Object.entries(manifest.mapping).slice(0, 5);
|
|
1981
|
+
if (mappingEntries.length > 0) {
|
|
1982
|
+
console.log(` - Mappings: ${mappingEntries.map(([k, v]) => `${k}\u2192${v}`).join(", ")}`);
|
|
1983
|
+
if (Object.keys(manifest.mapping).length > 5) {
|
|
1984
|
+
console.log(` ... and ${Object.keys(manifest.mapping).length - 5} more`);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
const warnings = [];
|
|
1988
|
+
if (conflicts.length > 0) {
|
|
1989
|
+
for (const conflict of conflicts) {
|
|
1990
|
+
warnings.push(`Naming conflict '${conflict.semanticName}': ${conflict.tables.join(", ")}`);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
const expectedTables = ["clients", "projects", "repositories", "workflows", "deployments"];
|
|
1994
|
+
const missingTables = expectedTables.filter((name) => !manifest.mapping[name]);
|
|
1995
|
+
if (missingTables.length > 0) {
|
|
1996
|
+
warnings.push(
|
|
1997
|
+
`Optional SDK tables not found: ${missingTables.join(", ")}
|
|
1998
|
+
(This is OK if your schema uses different names)`
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
if (warnings.length > 0) {
|
|
2002
|
+
console.log("\n\u26A0\uFE0F Warnings:");
|
|
2003
|
+
for (const warning of warnings) {
|
|
2004
|
+
console.log(` - ${warning}`);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
console.log("\n\u{1F4A1} Tip: SDK will auto-load this manifest. No additional setup required.");
|
|
2008
|
+
console.log(" Path: .runa/manifests/tables.json\n");
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
export { diffSchema, extractSchemaTablesAndEnums, extractTablesFromIdempotentSql, fetchDbTablesAndEnums, generateTablesManifest, removeEnvLocalBridge, writeEnvLocalBridge };
|