@supabase/pg-delta 1.0.0-alpha.4 → 1.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -23
- package/dist/cli/app.js +26 -3
- package/dist/cli/bin/cli.js +5 -0
- package/dist/cli/commands/catalog-export.d.ts +5 -0
- package/dist/cli/commands/catalog-export.js +64 -0
- package/dist/cli/commands/declarative-apply.d.ts +6 -0
- package/dist/cli/commands/declarative-apply.js +288 -0
- package/dist/cli/commands/declarative-export.d.ts +5 -0
- package/dist/cli/commands/declarative-export.js +245 -0
- package/dist/cli/commands/plan.js +19 -6
- package/dist/cli/exit-code.d.ts +2 -0
- package/dist/cli/exit-code.js +7 -0
- package/dist/cli/formatters/tree/tree.js +3 -2
- package/dist/cli/utils/apply-display.d.ts +52 -0
- package/dist/cli/utils/apply-display.js +183 -0
- package/dist/cli/utils/export-display.d.ts +43 -0
- package/dist/cli/utils/export-display.js +202 -0
- package/dist/cli/utils/resolve-input.d.ts +7 -0
- package/dist/cli/utils/resolve-input.js +13 -0
- package/dist/core/catalog-export/index.d.ts +11 -0
- package/dist/core/catalog-export/index.js +10 -0
- package/dist/core/catalog.diff.d.ts +1 -0
- package/dist/core/catalog.diff.js +64 -48
- package/dist/core/catalog.model.d.ts +14 -1
- package/dist/core/catalog.model.js +103 -1
- package/dist/core/catalog.snapshot.d.ts +66 -0
- package/dist/core/catalog.snapshot.js +206 -0
- package/dist/core/declarative-apply/discover-sql.d.ts +18 -0
- package/dist/core/declarative-apply/discover-sql.js +86 -0
- package/dist/core/declarative-apply/extract-catalog-providers.d.ts +23 -0
- package/dist/core/declarative-apply/extract-catalog-providers.js +159 -0
- package/dist/core/declarative-apply/index.d.ts +49 -0
- package/dist/core/declarative-apply/index.js +134 -0
- package/dist/core/declarative-apply/round-apply.d.ts +100 -0
- package/dist/core/declarative-apply/round-apply.js +378 -0
- package/dist/core/export/file-mapper.d.ts +71 -0
- package/dist/core/export/file-mapper.js +474 -0
- package/dist/core/export/grouper.d.ts +13 -0
- package/dist/core/export/grouper.js +76 -0
- package/dist/core/export/index.d.ts +45 -0
- package/dist/core/export/index.js +63 -0
- package/dist/core/export/types.d.ts +84 -0
- package/dist/core/export/types.js +25 -0
- package/dist/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
- package/dist/core/integrations/filter/dsl.d.ts +38 -1
- package/dist/core/integrations/filter/dsl.js +20 -2
- package/dist/core/integrations/filter/extractors.js +42 -0
- package/dist/core/integrations/integration-dsl.d.ts +10 -0
- package/dist/core/integrations/supabase.d.ts +8 -0
- package/dist/core/integrations/supabase.js +9 -0
- package/dist/core/objects/aggregate/aggregate.diff.d.ts +2 -8
- package/dist/core/objects/aggregate/aggregate.diff.js +16 -70
- package/dist/core/objects/aggregate/aggregate.model.d.ts +8 -8
- package/dist/core/objects/aggregate/aggregate.model.js +1 -1
- package/dist/core/objects/aggregate/changes/aggregate.create.js +1 -1
- package/dist/core/objects/aggregate/changes/aggregate.drop.js +1 -1
- package/dist/core/objects/base.privilege-diff.d.ts +38 -13
- package/dist/core/objects/base.privilege-diff.js +104 -22
- package/dist/core/objects/base.privilege.d.ts +1 -0
- package/dist/core/objects/base.privilege.js +9 -2
- package/dist/core/objects/collation/collation.diff.d.ts +2 -3
- package/dist/core/objects/diff-context.d.ts +15 -0
- package/dist/core/objects/diff-context.js +1 -0
- package/dist/core/objects/domain/changes/domain.create.js +4 -2
- package/dist/core/objects/domain/domain.diff.d.ts +2 -8
- package/dist/core/objects/domain/domain.diff.js +16 -77
- package/dist/core/objects/domain/domain.model.js +1 -1
- package/dist/core/objects/event-trigger/event-trigger.diff.d.ts +2 -3
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.js +13 -77
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.js +2 -2
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -77
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +1 -1
- package/dist/core/objects/foreign-data-wrapper/server/server.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/server/server.diff.js +13 -77
- package/dist/core/objects/language/language.diff.d.ts +2 -5
- package/dist/core/objects/language/language.diff.js +7 -39
- package/dist/core/objects/materialized-view/materialized-view.diff.d.ts +2 -8
- package/dist/core/objects/materialized-view/materialized-view.diff.js +16 -158
- package/dist/core/objects/materialized-view/materialized-view.model.d.ts +3 -3
- package/dist/core/objects/materialized-view/materialized-view.model.js +1 -1
- package/dist/core/objects/procedure/changes/procedure.alter.js +12 -12
- package/dist/core/objects/procedure/procedure.diff.d.ts +2 -8
- package/dist/core/objects/procedure/procedure.diff.js +16 -77
- package/dist/core/objects/procedure/procedure.model.d.ts +9 -9
- package/dist/core/objects/procedure/procedure.model.js +1 -1
- package/dist/core/objects/publication/changes/publication.alter.d.ts +0 -9
- package/dist/core/objects/publication/changes/publication.alter.js +0 -14
- package/dist/core/objects/publication/changes/publication.types.d.ts +2 -2
- package/dist/core/objects/publication/publication.diff.d.ts +2 -3
- package/dist/core/objects/publication/publication.diff.js +8 -13
- package/dist/core/objects/rls-policy/changes/rls-policy.alter.js +3 -3
- package/dist/core/objects/rls-policy/rls-policy.model.d.ts +2 -2
- package/dist/core/objects/role/role.diff.js +22 -1
- package/dist/core/objects/role/role.model.d.ts +4 -3
- package/dist/core/objects/role/role.model.js +118 -12
- package/dist/core/objects/rule/rule.model.d.ts +1 -1
- package/dist/core/objects/schema/schema.diff.d.ts +2 -8
- package/dist/core/objects/schema/schema.diff.js +16 -77
- package/dist/core/objects/schema/schema.model.js +1 -1
- package/dist/core/objects/sequence/sequence.diff.d.ts +2 -8
- package/dist/core/objects/sequence/sequence.diff.js +16 -79
- package/dist/core/objects/sequence/sequence.model.js +1 -1
- package/dist/core/objects/subscription/subscription.diff.d.ts +2 -3
- package/dist/core/objects/table/changes/table.create.js +3 -0
- package/dist/core/objects/table/table.diff.d.ts +2 -8
- package/dist/core/objects/table/table.diff.js +26 -157
- package/dist/core/objects/table/table.model.d.ts +23 -22
- package/dist/core/objects/table/table.model.js +1 -1
- package/dist/core/objects/trigger/changes/trigger.create.js +2 -4
- package/dist/core/objects/trigger/trigger.model.d.ts +8 -0
- package/dist/core/objects/trigger/trigger.model.js +11 -0
- package/dist/core/objects/type/composite-type/composite-type.diff.d.ts +2 -8
- package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -77
- package/dist/core/objects/type/composite-type/composite-type.model.d.ts +3 -3
- package/dist/core/objects/type/composite-type/composite-type.model.js +2 -1
- package/dist/core/objects/type/enum/enum.diff.d.ts +2 -8
- package/dist/core/objects/type/enum/enum.diff.js +25 -112
- package/dist/core/objects/type/enum/enum.model.js +1 -1
- package/dist/core/objects/type/range/changes/range.create.js +6 -3
- package/dist/core/objects/type/range/range.diff.d.ts +2 -8
- package/dist/core/objects/type/range/range.diff.js +16 -77
- package/dist/core/objects/type/range/range.model.js +1 -1
- package/dist/core/objects/view/view.diff.d.ts +2 -8
- package/dist/core/objects/view/view.diff.js +16 -158
- package/dist/core/objects/view/view.model.d.ts +18 -4
- package/dist/core/objects/view/view.model.js +3 -13
- package/dist/core/plan/apply.js +9 -26
- package/dist/core/plan/create.d.ts +19 -6
- package/dist/core/plan/create.js +134 -174
- package/dist/core/plan/serialize.js +16 -4
- package/dist/core/plan/sql-format/fixtures.js +3 -5
- package/dist/core/plan/sql-format/keyword-case.js +26 -1
- package/dist/core/plan/ssl-config.d.ts +32 -0
- package/dist/core/plan/ssl-config.js +115 -0
- package/dist/core/plan/types.d.ts +6 -0
- package/dist/core/postgres-config.d.ts +14 -0
- package/dist/core/postgres-config.js +53 -2
- package/dist/core/sort/graph-builder.js +10 -0
- package/dist/core/sort/logical-sort.js +31 -23
- package/dist/core/test-utils/assert-valid-sql.d.ts +10 -0
- package/dist/core/test-utils/assert-valid-sql.js +19 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -1
- package/package.json +21 -4
- package/src/cli/app.ts +27 -3
- package/src/cli/bin/cli.ts +6 -0
- package/src/cli/commands/catalog-export.ts +78 -0
- package/src/cli/commands/declarative-apply.diagnostics.test.ts +77 -0
- package/src/cli/commands/declarative-apply.ts +380 -0
- package/src/cli/commands/declarative-export.ts +330 -0
- package/src/cli/commands/plan.ts +28 -7
- package/src/cli/exit-code.test.ts +19 -0
- package/src/cli/exit-code.ts +7 -0
- package/src/cli/formatters/tree/tree.ts +3 -2
- package/src/cli/utils/apply-display.test.ts +348 -0
- package/src/cli/utils/apply-display.ts +238 -0
- package/src/cli/utils/export-display.test.ts +103 -0
- package/src/cli/utils/export-display.ts +275 -0
- package/src/cli/utils/integrations.test.ts +44 -0
- package/src/cli/utils/resolve-input.test.ts +38 -0
- package/src/cli/utils/resolve-input.ts +17 -0
- package/src/core/catalog-export/index.ts +20 -0
- package/src/core/catalog.diff.ts +79 -78
- package/src/core/catalog.model.test.ts +122 -0
- package/src/core/catalog.model.ts +127 -1
- package/src/core/catalog.snapshot.test.ts +464 -0
- package/src/core/catalog.snapshot.ts +289 -0
- package/src/core/declarative-apply/discover-sql.test.ts +103 -0
- package/src/core/declarative-apply/discover-sql.ts +107 -0
- package/src/core/declarative-apply/extract-catalog-providers.ts +220 -0
- package/src/core/declarative-apply/index.test.ts +67 -0
- package/src/core/declarative-apply/index.ts +205 -0
- package/src/core/declarative-apply/round-apply.test.ts +504 -0
- package/src/core/declarative-apply/round-apply.ts +562 -0
- package/src/core/expand-replace-dependencies.test.ts +70 -0
- package/src/core/export/file-mapper.test.ts +816 -0
- package/src/core/export/file-mapper.ts +574 -0
- package/src/core/export/grouper.ts +108 -0
- package/src/core/export/index.ts +129 -0
- package/src/core/export/types.ts +104 -0
- package/src/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
- package/src/core/integrations/filter/dsl.test.ts +211 -0
- package/src/core/integrations/filter/dsl.ts +65 -3
- package/src/core/integrations/filter/extractors.test.ts +244 -0
- package/src/core/integrations/filter/extractors.ts +42 -0
- package/src/core/integrations/integration-dsl.ts +10 -0
- package/src/core/integrations/serialize/dsl.test.ts +91 -0
- package/src/core/integrations/supabase.ts +9 -0
- package/src/core/objects/aggregate/aggregate.diff.ts +39 -95
- package/src/core/objects/aggregate/aggregate.model.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.alter.test.ts +3 -1
- package/src/core/objects/aggregate/changes/aggregate.comment.test.ts +5 -2
- package/src/core/objects/aggregate/changes/aggregate.create.test.ts +6 -3
- package/src/core/objects/aggregate/changes/aggregate.create.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.drop.test.ts +7 -3
- package/src/core/objects/aggregate/changes/aggregate.drop.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.privilege.test.ts +9 -3
- package/src/core/objects/base.privilege-diff.ts +178 -30
- package/src/core/objects/base.privilege.ts +9 -2
- package/src/core/objects/collation/changes/collation.alter.test.ts +7 -2
- package/src/core/objects/collation/changes/collation.create.test.ts +7 -2
- package/src/core/objects/collation/changes/collation.drop.test.ts +4 -1
- package/src/core/objects/collation/collation.diff.test.ts +9 -12
- package/src/core/objects/collation/collation.diff.ts +2 -1
- package/src/core/objects/diff-context.ts +16 -0
- package/src/core/objects/domain/changes/domain.alter.test.ts +28 -9
- package/src/core/objects/domain/changes/domain.create.test.ts +32 -2
- package/src/core/objects/domain/changes/domain.create.ts +7 -1
- package/src/core/objects/domain/changes/domain.drop.test.ts +4 -1
- package/src/core/objects/domain/domain.diff.ts +39 -102
- package/src/core/objects/domain/domain.model.ts +1 -1
- package/src/core/objects/event-trigger/changes/event-trigger.alter.test.ts +10 -3
- package/src/core/objects/event-trigger/changes/event-trigger.create.test.ts +4 -1
- package/src/core/objects/event-trigger/changes/event-trigger.drop.test.ts +4 -1
- package/src/core/objects/event-trigger/event-trigger.diff.test.ts +12 -7
- package/src/core/objects/event-trigger/event-trigger.diff.ts +2 -1
- package/src/core/objects/extension/changes/extension.alter.test.ts +7 -2
- package/src/core/objects/extension/changes/extension.create.test.ts +4 -1
- package/src/core/objects/extension/changes/extension.drop.test.ts +4 -1
- package/src/core/objects/extension/extension.model.test.ts +98 -0
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.test.ts +16 -5
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.test.ts +51 -16
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.test.ts +111 -4
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.ts +31 -101
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.ts +2 -2
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.test.ts +46 -15
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.test.ts +13 -4
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +39 -102
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +1 -1
- package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.test.ts +22 -7
- package/src/core/objects/foreign-data-wrapper/server/changes/server.create.test.ts +19 -6
- package/src/core/objects/foreign-data-wrapper/server/changes/server.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/server/server.diff.test.ts +95 -0
- package/src/core/objects/foreign-data-wrapper/server/server.diff.ts +31 -101
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.test.ts +13 -4
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.test.ts +16 -5
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.drop.test.ts +10 -3
- package/src/core/objects/index/changes/index.alter.test.ts +13 -4
- package/src/core/objects/index/changes/index.create.test.ts +4 -1
- package/src/core/objects/index/changes/index.drop.test.ts +4 -1
- package/src/core/objects/language/changes/language.alter.test.ts +4 -1
- package/src/core/objects/language/changes/language.create.test.ts +4 -1
- package/src/core/objects/language/changes/language.drop.test.ts +4 -1
- package/src/core/objects/language/language.diff.test.ts +86 -4
- package/src/core/objects/language/language.diff.ts +17 -49
- package/src/core/objects/materialized-view/changes/materialized-view.alter.test.ts +10 -3
- package/src/core/objects/materialized-view/changes/materialized-view.create.test.ts +7 -2
- package/src/core/objects/materialized-view/changes/materialized-view.drop.test.ts +4 -1
- package/src/core/objects/materialized-view/materialized-view.diff.test.ts +162 -0
- package/src/core/objects/materialized-view/materialized-view.diff.ts +41 -191
- package/src/core/objects/materialized-view/materialized-view.model.ts +1 -1
- package/src/core/objects/procedure/changes/procedure.alter.test.ts +121 -49
- package/src/core/objects/procedure/changes/procedure.alter.ts +15 -12
- package/src/core/objects/procedure/changes/procedure.create.test.ts +4 -1
- package/src/core/objects/procedure/changes/procedure.drop.test.ts +7 -2
- package/src/core/objects/procedure/procedure.diff.ts +39 -102
- package/src/core/objects/procedure/procedure.model.ts +1 -1
- package/src/core/objects/publication/changes/publication.alter.test.ts +15 -21
- package/src/core/objects/publication/changes/publication.alter.ts +0 -18
- package/src/core/objects/publication/changes/publication.comment.test.ts +5 -2
- package/src/core/objects/publication/changes/publication.create.test.ts +5 -2
- package/src/core/objects/publication/changes/publication.drop.test.ts +3 -1
- package/src/core/objects/publication/changes/publication.types.ts +0 -2
- package/src/core/objects/publication/publication.diff.test.ts +24 -19
- package/src/core/objects/publication/publication.diff.ts +9 -15
- package/src/core/objects/rls-policy/changes/rls-policy.alter.test.ts +31 -14
- package/src/core/objects/rls-policy/changes/rls-policy.alter.ts +3 -3
- package/src/core/objects/rls-policy/changes/rls-policy.create.test.ts +10 -3
- package/src/core/objects/rls-policy/changes/rls-policy.drop.test.ts +4 -1
- package/src/core/objects/role/changes/role.alter.test.ts +31 -15
- package/src/core/objects/role/changes/role.create.test.ts +6 -2
- package/src/core/objects/role/changes/role.drop.test.ts +4 -1
- package/src/core/objects/role/role.diff.test.ts +235 -0
- package/src/core/objects/role/role.diff.ts +21 -1
- package/src/core/objects/role/role.model.ts +122 -14
- package/src/core/objects/rule/changes/rule.alter.test.ts +7 -3
- package/src/core/objects/rule/changes/rule.comment.test.ts +5 -2
- package/src/core/objects/rule/changes/rule.create.test.ts +6 -2
- package/src/core/objects/rule/changes/rule.drop.test.ts +3 -1
- package/src/core/objects/schema/changes/schema.alter.test.ts +4 -1
- package/src/core/objects/schema/changes/schema.create.test.ts +4 -1
- package/src/core/objects/schema/changes/schema.drop.test.ts +4 -1
- package/src/core/objects/schema/schema.diff.ts +39 -102
- package/src/core/objects/schema/schema.model.ts +1 -1
- package/src/core/objects/sequence/changes/sequence.alter.test.ts +11 -5
- package/src/core/objects/sequence/changes/sequence.create.test.ts +8 -3
- package/src/core/objects/sequence/changes/sequence.drop.test.ts +4 -1
- package/src/core/objects/sequence/sequence.diff.test.ts +114 -0
- package/src/core/objects/sequence/sequence.diff.ts +39 -104
- package/src/core/objects/sequence/sequence.model.ts +1 -1
- package/src/core/objects/subscription/changes/subscription.alter.test.ts +15 -5
- package/src/core/objects/subscription/changes/subscription.comment.test.ts +5 -2
- package/src/core/objects/subscription/changes/subscription.create.test.ts +5 -2
- package/src/core/objects/subscription/changes/subscription.drop.test.ts +3 -1
- package/src/core/objects/subscription/subscription.diff.test.ts +16 -11
- package/src/core/objects/subscription/subscription.diff.ts +2 -1
- package/src/core/objects/table/changes/table.alter.test.ts +38 -15
- package/src/core/objects/table/changes/table.create.test.ts +41 -3
- package/src/core/objects/table/changes/table.create.ts +4 -0
- package/src/core/objects/table/changes/table.drop.test.ts +3 -1
- package/src/core/objects/table/table.diff.test.ts +157 -0
- package/src/core/objects/table/table.diff.ts +54 -190
- package/src/core/objects/table/table.model.ts +1 -1
- package/src/core/objects/trigger/changes/trigger.alter.test.ts +8 -4
- package/src/core/objects/trigger/changes/trigger.create.test.ts +5 -1
- package/src/core/objects/trigger/changes/trigger.create.ts +7 -4
- package/src/core/objects/trigger/changes/trigger.drop.test.ts +5 -1
- package/src/core/objects/trigger/trigger.diff.test.ts +1 -0
- package/src/core/objects/trigger/trigger.model.ts +12 -0
- package/src/core/objects/type/composite-type/changes/composite-type.alter.test.ts +10 -4
- package/src/core/objects/type/composite-type/changes/composite-type.create.test.ts +7 -2
- package/src/core/objects/type/composite-type/changes/composite-type.drop.test.ts +4 -1
- package/src/core/objects/type/composite-type/composite-type.diff.test.ts +78 -0
- package/src/core/objects/type/composite-type/composite-type.diff.ts +39 -101
- package/src/core/objects/type/composite-type/composite-type.model.ts +2 -1
- package/src/core/objects/type/enum/changes/enum.alter.test.ts +14 -5
- package/src/core/objects/type/enum/changes/enum.create.test.ts +4 -1
- package/src/core/objects/type/enum/changes/enum.drop.test.ts +4 -1
- package/src/core/objects/type/enum/enum.diff.test.ts +181 -0
- package/src/core/objects/type/enum/enum.diff.ts +58 -146
- package/src/core/objects/type/enum/enum.model.ts +1 -1
- package/src/core/objects/type/range/changes/range.alter.test.ts +3 -1
- package/src/core/objects/type/range/changes/range.create.test.ts +5 -2
- package/src/core/objects/type/range/changes/range.create.ts +6 -2
- package/src/core/objects/type/range/changes/range.drop.test.ts +3 -1
- package/src/core/objects/type/range/range.diff.test.ts +77 -0
- package/src/core/objects/type/range/range.diff.ts +39 -101
- package/src/core/objects/type/range/range.model.ts +1 -1
- package/src/core/objects/view/changes/view.alter.test.ts +8 -3
- package/src/core/objects/view/changes/view.create.test.ts +7 -2
- package/src/core/objects/view/changes/view.drop.test.ts +4 -1
- package/src/core/objects/view/view.diff.test.ts +82 -0
- package/src/core/objects/view/view.diff.ts +41 -191
- package/src/core/objects/view/view.model.ts +3 -17
- package/src/core/plan/apply.ts +9 -27
- package/src/core/plan/create.ts +173 -237
- package/src/core/plan/serialize.test.ts +317 -0
- package/src/core/plan/serialize.ts +18 -4
- package/src/core/plan/sql-format/fixtures.ts +2 -5
- package/src/core/plan/sql-format/format-lowercase-coverage.test.ts +52 -0
- package/src/core/plan/sql-format/format-off.test.ts +14 -17
- package/src/core/plan/sql-format/format-pretty-lower-leading.test.ts +27 -22
- package/src/core/plan/sql-format/format-pretty-narrow.test.ts +17 -21
- package/src/core/plan/sql-format/format-pretty-preserve.test.ts +25 -20
- package/src/core/plan/sql-format/format-pretty-upper.test.ts +23 -20
- package/src/core/plan/sql-format/keyword-case.ts +36 -1
- package/src/core/plan/ssl-config.ts +172 -0
- package/src/core/plan/types.ts +6 -0
- package/src/core/postgres-config.ts +71 -2
- package/src/core/sort/graph-builder.ts +12 -0
- package/src/core/sort/logical-sort.test.ts +371 -0
- package/src/core/sort/logical-sort.ts +32 -25
- package/src/core/sort/topological-sort.test.ts +275 -0
- package/src/core/test-utils/assert-valid-sql.ts +20 -0
- package/src/index.ts +26 -2
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative export command - export a declarative SQL schema from a database diff.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { buildCommand, type CommandContext } from "@stricli/core";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import type { CatalogSnapshot } from "../../core/catalog.snapshot.ts";
|
|
10
|
+
import { exportDeclarativeSchema } from "../../core/export/index.ts";
|
|
11
|
+
import type { Grouping, GroupingPattern } from "../../core/export/types.ts";
|
|
12
|
+
import type { FilterDSL } from "../../core/integrations/filter/dsl.ts";
|
|
13
|
+
import type { ChangeFilter } from "../../core/integrations/filter/filter.types.ts";
|
|
14
|
+
import type { SerializeDSL } from "../../core/integrations/serialize/dsl.ts";
|
|
15
|
+
import type { ChangeSerializer } from "../../core/integrations/serialize/serialize.types.ts";
|
|
16
|
+
import { createPlan } from "../../core/plan/index.ts";
|
|
17
|
+
import type { SqlFormatOptions } from "../../core/plan/sql-format.ts";
|
|
18
|
+
import {
|
|
19
|
+
assertSafePath,
|
|
20
|
+
buildFileTree,
|
|
21
|
+
computeFileDiff,
|
|
22
|
+
formatExportSummary,
|
|
23
|
+
} from "../utils/export-display.ts";
|
|
24
|
+
import { loadIntegrationDSL } from "../utils/integrations.ts";
|
|
25
|
+
import { isPostgresUrl, loadCatalogFromFile } from "../utils/resolve-input.ts";
|
|
26
|
+
|
|
27
|
+
function parseJsonFlag<T>(label: string, value: string): T {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(value) as T;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Invalid ${label} JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const declarativeExportCommand = buildCommand({
|
|
38
|
+
parameters: {
|
|
39
|
+
flags: {
|
|
40
|
+
source: {
|
|
41
|
+
kind: "parsed",
|
|
42
|
+
brief:
|
|
43
|
+
"Source (current state): postgres URL or catalog snapshot file path. Omit to export all objects from target.",
|
|
44
|
+
parse: String,
|
|
45
|
+
optional: true,
|
|
46
|
+
},
|
|
47
|
+
target: {
|
|
48
|
+
kind: "parsed",
|
|
49
|
+
brief:
|
|
50
|
+
"Target (desired state): postgres URL or catalog snapshot file path",
|
|
51
|
+
parse: String,
|
|
52
|
+
},
|
|
53
|
+
output: {
|
|
54
|
+
kind: "parsed",
|
|
55
|
+
brief: "Output directory path for declarative schema files",
|
|
56
|
+
parse: String,
|
|
57
|
+
},
|
|
58
|
+
integration: {
|
|
59
|
+
kind: "parsed",
|
|
60
|
+
brief:
|
|
61
|
+
"Integration name (e.g., 'supabase') or path to integration JSON file",
|
|
62
|
+
parse: String,
|
|
63
|
+
optional: true,
|
|
64
|
+
},
|
|
65
|
+
filter: {
|
|
66
|
+
kind: "parsed",
|
|
67
|
+
brief: 'Filter DSL as inline JSON (e.g., \'{"schema":"public"}\')',
|
|
68
|
+
parse: (v: string) => parseJsonFlag<FilterDSL>("filter", v),
|
|
69
|
+
optional: true,
|
|
70
|
+
},
|
|
71
|
+
serialize: {
|
|
72
|
+
kind: "parsed",
|
|
73
|
+
brief:
|
|
74
|
+
'Serialize DSL as inline JSON array (e.g., \'[{"when":{"type":"schema"},"options":{"skipAuthorization":true}}]\')',
|
|
75
|
+
parse: (v: string) => parseJsonFlag<SerializeDSL>("serialize", v),
|
|
76
|
+
optional: true,
|
|
77
|
+
},
|
|
78
|
+
"grouping-mode": {
|
|
79
|
+
kind: "enum",
|
|
80
|
+
brief: "How grouped entities are organized on disk",
|
|
81
|
+
values: ["single-file", "subdirectory"] as const,
|
|
82
|
+
optional: true,
|
|
83
|
+
},
|
|
84
|
+
"group-patterns": {
|
|
85
|
+
kind: "parsed",
|
|
86
|
+
brief:
|
|
87
|
+
'JSON array of {pattern, name} objects (e.g., \'[{"pattern":"^auth","name":"auth"}]\')',
|
|
88
|
+
parse: (v: string) => {
|
|
89
|
+
const parsed = parseJsonFlag<GroupingPattern[]>("group-patterns", v);
|
|
90
|
+
if (!Array.isArray(parsed)) {
|
|
91
|
+
throw new Error("group-patterns must be a JSON array");
|
|
92
|
+
}
|
|
93
|
+
return parsed;
|
|
94
|
+
},
|
|
95
|
+
optional: true,
|
|
96
|
+
},
|
|
97
|
+
"flat-schemas": {
|
|
98
|
+
kind: "parsed",
|
|
99
|
+
brief:
|
|
100
|
+
"Comma-separated list of schemas to flatten (e.g., partman,pgboss,audit)",
|
|
101
|
+
parse: String,
|
|
102
|
+
optional: true,
|
|
103
|
+
},
|
|
104
|
+
"format-options": {
|
|
105
|
+
kind: "parsed",
|
|
106
|
+
brief:
|
|
107
|
+
'SQL format options as inline JSON (e.g., \'{"keywordCase":"lower","maxWidth":180}\')',
|
|
108
|
+
parse: (v: string) =>
|
|
109
|
+
parseJsonFlag<SqlFormatOptions>("format-options", v),
|
|
110
|
+
optional: true,
|
|
111
|
+
},
|
|
112
|
+
force: {
|
|
113
|
+
kind: "boolean",
|
|
114
|
+
brief: "Remove entire output directory before writing",
|
|
115
|
+
optional: true,
|
|
116
|
+
},
|
|
117
|
+
"dry-run": {
|
|
118
|
+
kind: "boolean",
|
|
119
|
+
brief: "Show tree and summary without writing files",
|
|
120
|
+
optional: true,
|
|
121
|
+
},
|
|
122
|
+
"diff-focus": {
|
|
123
|
+
kind: "boolean",
|
|
124
|
+
brief:
|
|
125
|
+
"Show only files that changed (created/updated/deleted) in the tree",
|
|
126
|
+
optional: true,
|
|
127
|
+
},
|
|
128
|
+
verbose: {
|
|
129
|
+
kind: "boolean",
|
|
130
|
+
brief: "Show detailed output",
|
|
131
|
+
optional: true,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
aliases: {
|
|
135
|
+
s: "source",
|
|
136
|
+
t: "target",
|
|
137
|
+
o: "output",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
docs: {
|
|
141
|
+
brief: "Export a declarative schema from a database diff",
|
|
142
|
+
fullDescription: `
|
|
143
|
+
Export a declarative SQL schema by comparing two databases (source → target).
|
|
144
|
+
Writes .sql files to the output directory, grouped by object type and optional
|
|
145
|
+
grouping rules.
|
|
146
|
+
|
|
147
|
+
When --source is omitted, all objects from the target database are exported
|
|
148
|
+
(equivalent to diffing from an empty database).
|
|
149
|
+
|
|
150
|
+
Flags:
|
|
151
|
+
source - Source database connection URL (optional; omit for full export)
|
|
152
|
+
target - Target database connection URL (desired state)
|
|
153
|
+
output - Directory path for generated .sql files
|
|
154
|
+
integration - Integration name or path (e.g., supabase) for filter/serialize
|
|
155
|
+
filter - Filter DSL as JSON to include/exclude changes
|
|
156
|
+
serialize - Serialize DSL as JSON array for custom SQL generation
|
|
157
|
+
grouping-mode - single-file or subdirectory for grouped entities
|
|
158
|
+
group-patterns - JSON array of {pattern, name} for name-based grouping
|
|
159
|
+
flat-schemas - Comma-separated schemas to merge into one file per category
|
|
160
|
+
format-options - SQL format options as JSON
|
|
161
|
+
force - Remove output directory before writing (full replace)
|
|
162
|
+
dry-run - Show tree and summary only, do not write files
|
|
163
|
+
diff-focus - Show only changed files (created/updated/deleted) in the tree
|
|
164
|
+
verbose - Show detailed output
|
|
165
|
+
|
|
166
|
+
After export, a tip is printed with the command to apply the schema to an empty database.
|
|
167
|
+
`.trim(),
|
|
168
|
+
},
|
|
169
|
+
async func(
|
|
170
|
+
this: CommandContext,
|
|
171
|
+
flags: {
|
|
172
|
+
source?: string;
|
|
173
|
+
target: string;
|
|
174
|
+
output: string;
|
|
175
|
+
integration?: string;
|
|
176
|
+
filter?: FilterDSL;
|
|
177
|
+
serialize?: SerializeDSL;
|
|
178
|
+
"grouping-mode"?: "single-file" | "subdirectory";
|
|
179
|
+
"group-patterns"?: GroupingPattern[];
|
|
180
|
+
"flat-schemas"?: string;
|
|
181
|
+
"format-options"?: SqlFormatOptions;
|
|
182
|
+
force?: boolean;
|
|
183
|
+
"dry-run"?: boolean;
|
|
184
|
+
"diff-focus"?: boolean;
|
|
185
|
+
verbose?: boolean;
|
|
186
|
+
},
|
|
187
|
+
) {
|
|
188
|
+
const { compileSerializeDSL } = await import(
|
|
189
|
+
"../../core/integrations/serialize/dsl.ts"
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
let filterOption: FilterDSL | ChangeFilter | undefined = flags.filter;
|
|
193
|
+
let serializeOption: SerializeDSL | ChangeSerializer | undefined =
|
|
194
|
+
flags.serialize;
|
|
195
|
+
let integrationEmptyCatalog: CatalogSnapshot | undefined;
|
|
196
|
+
if (flags.integration) {
|
|
197
|
+
const integrationDSL = await loadIntegrationDSL(flags.integration);
|
|
198
|
+
filterOption = filterOption ?? integrationDSL.filter;
|
|
199
|
+
serializeOption = serializeOption ?? integrationDSL.serialize;
|
|
200
|
+
integrationEmptyCatalog = integrationDSL.emptyCatalog;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const resolvedSource = flags.source
|
|
204
|
+
? isPostgresUrl(flags.source)
|
|
205
|
+
? flags.source
|
|
206
|
+
: await loadCatalogFromFile(flags.source)
|
|
207
|
+
: integrationEmptyCatalog
|
|
208
|
+
? (await import("../../core/catalog.snapshot.ts")).deserializeCatalog(
|
|
209
|
+
integrationEmptyCatalog,
|
|
210
|
+
)
|
|
211
|
+
: null;
|
|
212
|
+
|
|
213
|
+
const resolvedTarget = isPostgresUrl(flags.target)
|
|
214
|
+
? flags.target
|
|
215
|
+
: await loadCatalogFromFile(flags.target);
|
|
216
|
+
|
|
217
|
+
// Pass raw DSL to createPlan (not pre-compiled functions).
|
|
218
|
+
// createPlan compiles them internally and uses the DSL type to correctly
|
|
219
|
+
// determine cascade behavior: DSL filters disable cascading by default
|
|
220
|
+
// (unless cascade:true is set), preventing unintended exclusion of
|
|
221
|
+
// changes that depend on filtered objects (e.g. RLS policies that
|
|
222
|
+
// reference auth.uid() when the auth schema is filtered out).
|
|
223
|
+
const planResult = await createPlan(resolvedSource, resolvedTarget, {
|
|
224
|
+
filter: filterOption,
|
|
225
|
+
serialize: serializeOption,
|
|
226
|
+
skipDefaultPrivilegeSubtraction: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (!planResult) {
|
|
230
|
+
this.process.stdout.write("No changes detected.\n");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const hasGrouping =
|
|
235
|
+
flags["grouping-mode"] !== undefined ||
|
|
236
|
+
(flags["group-patterns"] !== undefined &&
|
|
237
|
+
flags["group-patterns"].length > 0) ||
|
|
238
|
+
(flags["flat-schemas"] !== undefined && flags["flat-schemas"].length > 0);
|
|
239
|
+
|
|
240
|
+
let grouping: Grouping | undefined;
|
|
241
|
+
if (hasGrouping) {
|
|
242
|
+
grouping = {
|
|
243
|
+
mode: flags["grouping-mode"] ?? "single-file",
|
|
244
|
+
groupPatterns: flags["group-patterns"],
|
|
245
|
+
autoGroupPartitions: true,
|
|
246
|
+
flatSchemas:
|
|
247
|
+
flags["flat-schemas"] !== undefined
|
|
248
|
+
? flags["flat-schemas"]
|
|
249
|
+
.split(",")
|
|
250
|
+
.map((s) => s.trim())
|
|
251
|
+
.filter(Boolean)
|
|
252
|
+
: undefined,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const serializeFn =
|
|
257
|
+
serializeOption !== undefined
|
|
258
|
+
? compileSerializeDSL(serializeOption)
|
|
259
|
+
: undefined;
|
|
260
|
+
|
|
261
|
+
const output = exportDeclarativeSchema(planResult, {
|
|
262
|
+
integration:
|
|
263
|
+
serializeFn !== undefined ? { serialize: serializeFn } : undefined,
|
|
264
|
+
formatOptions: flags["format-options"] ?? undefined,
|
|
265
|
+
grouping,
|
|
266
|
+
onWarning: (msg) => {
|
|
267
|
+
this.process.stderr.write(chalk.yellow(`Warning: ${msg}\n`));
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const outputDir = path.resolve(flags.output);
|
|
272
|
+
const applyTip = (dir: string) =>
|
|
273
|
+
`\nTip: To apply this schema to an empty database, run:\n pgdelta declarative apply --path ${dir} --target <database_url>\n`;
|
|
274
|
+
const diff = await computeFileDiff(outputDir, output.files);
|
|
275
|
+
|
|
276
|
+
this.process.stdout.write("\n");
|
|
277
|
+
this.process.stdout.write(
|
|
278
|
+
`${buildFileTree(
|
|
279
|
+
output.files.map((f) => f.path),
|
|
280
|
+
path.basename(outputDir) || outputDir,
|
|
281
|
+
{ diff, diffFocus: !!flags["diff-focus"] },
|
|
282
|
+
)}\n`,
|
|
283
|
+
);
|
|
284
|
+
this.process.stdout.write("\n");
|
|
285
|
+
this.process.stdout.write(
|
|
286
|
+
`${chalk.green("+")} created ${chalk.yellow("~")} updated ${chalk.red("-")} deleted\n`,
|
|
287
|
+
);
|
|
288
|
+
this.process.stdout.write("\n");
|
|
289
|
+
|
|
290
|
+
const summary = formatExportSummary(diff, !!flags["dry-run"]);
|
|
291
|
+
if (summary) {
|
|
292
|
+
this.process.stdout.write(`${summary}\n`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const totalChanges = planResult.sortedChanges.length;
|
|
296
|
+
const totalStatements = output.files.reduce((s, f) => s + f.statements, 0);
|
|
297
|
+
this.process.stdout.write(
|
|
298
|
+
`Changes: ${totalChanges} | Files: ${output.files.length} | Statements: ${totalStatements}\n`,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
if (flags["dry-run"]) {
|
|
302
|
+
this.process.stdout.write(chalk.dim("\n(dry-run: no files written)\n"));
|
|
303
|
+
this.process.stdout.write(chalk.cyan(applyTip(outputDir)));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (flags.force) {
|
|
308
|
+
await rm(outputDir, { recursive: true, force: true });
|
|
309
|
+
await mkdir(outputDir, { recursive: true });
|
|
310
|
+
} else if (diff.deleted.length > 0) {
|
|
311
|
+
this.process.stderr.write(
|
|
312
|
+
chalk.yellow(
|
|
313
|
+
`Warning: ${diff.deleted.length} existing file(s) will no longer be present. Use --force to replace the output directory.\n`,
|
|
314
|
+
),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
for (const file of output.files) {
|
|
319
|
+
assertSafePath(file.path, outputDir);
|
|
320
|
+
const filePath = path.join(outputDir, file.path);
|
|
321
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
322
|
+
await writeFile(filePath, file.sql);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.process.stdout.write(
|
|
326
|
+
chalk.green(`Wrote ${output.files.length} file(s) to ${outputDir}\n`),
|
|
327
|
+
);
|
|
328
|
+
this.process.stdout.write(chalk.cyan(applyTip(outputDir)));
|
|
329
|
+
},
|
|
330
|
+
});
|
package/src/cli/commands/plan.ts
CHANGED
|
@@ -10,7 +10,9 @@ import type { SerializeDSL } from "../../core/integrations/serialize/dsl.ts";
|
|
|
10
10
|
import type { ChangeSerializer } from "../../core/integrations/serialize/serialize.types.ts";
|
|
11
11
|
import { createPlan } from "../../core/plan/index.ts";
|
|
12
12
|
import type { SqlFormatOptions } from "../../core/plan/sql-format.ts";
|
|
13
|
+
import { setCommandExitCode } from "../exit-code.ts";
|
|
13
14
|
import { loadIntegrationDSL } from "../utils/integrations.ts";
|
|
15
|
+
import { isPostgresUrl, loadCatalogFromFile } from "../utils/resolve-input.ts";
|
|
14
16
|
import { formatPlanForDisplay } from "../utils.ts";
|
|
15
17
|
|
|
16
18
|
export const planCommand = buildCommand({
|
|
@@ -18,12 +20,15 @@ export const planCommand = buildCommand({
|
|
|
18
20
|
flags: {
|
|
19
21
|
source: {
|
|
20
22
|
kind: "parsed",
|
|
21
|
-
brief:
|
|
23
|
+
brief:
|
|
24
|
+
"Source (current state): postgres URL or catalog snapshot file path. Omit for empty baseline.",
|
|
22
25
|
parse: String,
|
|
26
|
+
optional: true,
|
|
23
27
|
},
|
|
24
28
|
target: {
|
|
25
29
|
kind: "parsed",
|
|
26
|
-
brief:
|
|
30
|
+
brief:
|
|
31
|
+
"Target (desired state): postgres URL or catalog snapshot file path",
|
|
27
32
|
parse: String,
|
|
28
33
|
},
|
|
29
34
|
format: {
|
|
@@ -121,7 +126,7 @@ json/sql outputs are available for artifacts or piping.
|
|
|
121
126
|
async func(
|
|
122
127
|
this: CommandContext,
|
|
123
128
|
flags: {
|
|
124
|
-
source
|
|
129
|
+
source?: string;
|
|
125
130
|
target: string;
|
|
126
131
|
format?: "json" | "sql";
|
|
127
132
|
output?: string;
|
|
@@ -133,18 +138,34 @@ json/sql outputs are available for artifacts or piping.
|
|
|
133
138
|
"sql-format-options"?: SqlFormatOptions;
|
|
134
139
|
},
|
|
135
140
|
) {
|
|
136
|
-
// Load integration if provided and extract filter/serialize DSL
|
|
137
141
|
let filterOption: FilterDSL | ChangeFilter | undefined = flags.filter;
|
|
138
142
|
let serializeOption: SerializeDSL | ChangeSerializer | undefined =
|
|
139
143
|
flags.serialize;
|
|
144
|
+
let integrationEmptyCatalog:
|
|
145
|
+
| import("../../core/catalog.snapshot.ts").CatalogSnapshot
|
|
146
|
+
| undefined;
|
|
140
147
|
if (flags.integration) {
|
|
141
148
|
const integrationDSL = await loadIntegrationDSL(flags.integration);
|
|
142
|
-
// Use integration DSL if explicit flags not provided
|
|
143
149
|
filterOption = filterOption ?? integrationDSL.filter;
|
|
144
150
|
serializeOption = serializeOption ?? integrationDSL.serialize;
|
|
151
|
+
integrationEmptyCatalog = integrationDSL.emptyCatalog;
|
|
145
152
|
}
|
|
146
153
|
|
|
147
|
-
const
|
|
154
|
+
const resolvedSource = flags.source
|
|
155
|
+
? isPostgresUrl(flags.source)
|
|
156
|
+
? flags.source
|
|
157
|
+
: await loadCatalogFromFile(flags.source)
|
|
158
|
+
: integrationEmptyCatalog
|
|
159
|
+
? (await import("../../core/catalog.snapshot.ts")).deserializeCatalog(
|
|
160
|
+
integrationEmptyCatalog,
|
|
161
|
+
)
|
|
162
|
+
: null;
|
|
163
|
+
|
|
164
|
+
const resolvedTarget = isPostgresUrl(flags.target)
|
|
165
|
+
? flags.target
|
|
166
|
+
: await loadCatalogFromFile(flags.target);
|
|
167
|
+
|
|
168
|
+
const planResult = await createPlan(resolvedSource, resolvedTarget, {
|
|
148
169
|
role: flags.role,
|
|
149
170
|
filter: filterOption,
|
|
150
171
|
serialize: serializeOption,
|
|
@@ -190,6 +211,6 @@ json/sql outputs are available for artifacts or piping.
|
|
|
190
211
|
}
|
|
191
212
|
|
|
192
213
|
// Exit code 2 indicates changes were detected
|
|
193
|
-
|
|
214
|
+
setCommandExitCode(2);
|
|
194
215
|
},
|
|
195
216
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { getCommandExitCode, setCommandExitCode } from "./exit-code.ts";
|
|
3
|
+
|
|
4
|
+
describe("exit-code", () => {
|
|
5
|
+
test("getCommandExitCode returns undefined when never set", () => {
|
|
6
|
+
expect(getCommandExitCode()).toBeUndefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("setCommandExitCode then getCommandExitCode returns the value", () => {
|
|
10
|
+
setCommandExitCode(2);
|
|
11
|
+
expect(getCommandExitCode()).toBe(2);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("setCommandExitCode overwrites previous value", () => {
|
|
15
|
+
setCommandExitCode(2);
|
|
16
|
+
setCommandExitCode(0);
|
|
17
|
+
expect(getCommandExitCode()).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -18,7 +18,7 @@ export function formatTree(plan: HierarchicalPlan): string {
|
|
|
18
18
|
lines.push(
|
|
19
19
|
chalk.bold(`📋 Migration Plan: ${total} change${total !== 1 ? "s" : ""}`),
|
|
20
20
|
);
|
|
21
|
-
const summary =
|
|
21
|
+
const summary = buildPlanSummaryTable(plan);
|
|
22
22
|
if (summary) {
|
|
23
23
|
lines.push("");
|
|
24
24
|
lines.push(summary);
|
|
@@ -54,8 +54,9 @@ function countTotalChanges(plan: HierarchicalPlan): number {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Build summary as a table showing counts by entity type and operation.
|
|
57
|
+
* Exported for use by declarative-export to show the same summary style.
|
|
57
58
|
*/
|
|
58
|
-
function
|
|
59
|
+
function buildPlanSummaryTable(plan: HierarchicalPlan): string {
|
|
59
60
|
// Count by object type
|
|
60
61
|
const byType: Record<
|
|
61
62
|
string,
|