create-nextblock 0.13.9 → 0.13.11

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.
Files changed (260) hide show
  1. package/docker-template/.dockerignore +23 -23
  2. package/docker-template/.env.docker.example +69 -69
  3. package/docker-template/docker/db/init/99-jwt.sql +6 -6
  4. package/docker-template/docker/db/init/99-roles.sql +25 -25
  5. package/docker-template/docker/kong/kong.yml +112 -112
  6. package/docker-template/scripts/docker-setup.mjs +310 -310
  7. package/libs/db/tsconfig.lib.json +3 -3
  8. package/libs/editor/tsconfig.lib.json +3 -3
  9. package/libs/ui/tsconfig.lib.json +3 -3
  10. package/libs/utils/tsconfig.json +3 -3
  11. package/package.json +1 -1
  12. package/project.json +19 -19
  13. package/templates/nextblock-template/.browserslistrc +11 -11
  14. package/templates/nextblock-template/.dockerignore +23 -23
  15. package/templates/nextblock-template/README.md +34 -34
  16. package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
  17. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
  18. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
  19. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
  20. package/templates/nextblock-template/app/ToasterProvider.tsx +17 -17
  21. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
  22. package/templates/nextblock-template/app/actions/consent.ts +57 -57
  23. package/templates/nextblock-template/app/actions/postActions.ts +146 -146
  24. package/templates/nextblock-template/app/actions/productGridActions.ts +40 -40
  25. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
  26. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
  27. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
  28. package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
  29. package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
  30. package/templates/nextblock-template/app/api/cms/ecommerce/product-picker/route.ts +151 -151
  31. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
  32. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
  33. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3490 -3490
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5749 -5609
  35. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
  36. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
  37. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
  38. package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
  39. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
  40. package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
  41. package/templates/nextblock-template/app/api/search/route.ts +43 -43
  42. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
  43. package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
  44. package/templates/nextblock-template/app/cart/page.tsx +7 -7
  45. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +10 -1
  46. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +5 -1
  47. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
  48. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +55 -24
  49. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  50. package/templates/nextblock-template/app/cms/blocks/components/MultiEntityPicker.tsx +263 -251
  51. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
  52. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
  53. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
  54. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  55. package/templates/nextblock-template/app/cms/components/EcommerceActiveContext.tsx +27 -27
  56. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  57. package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
  58. package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
  59. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
  60. package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
  61. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
  62. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
  63. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
  64. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
  65. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
  66. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
  67. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
  68. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  69. package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
  70. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
  71. package/templates/nextblock-template/app/cms/interactions/EmailRecipientsInput.tsx +189 -189
  72. package/templates/nextblock-template/app/cms/layout.tsx +73 -73
  73. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  74. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  75. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  76. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
  77. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
  78. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  79. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
  80. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  81. package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
  82. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  83. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  84. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
  85. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
  86. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
  87. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -19
  88. package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
  89. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
  90. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
  91. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
  92. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +497 -497
  93. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
  94. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
  95. package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
  96. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  97. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  98. package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
  99. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageDetectionPanel.tsx +188 -188
  100. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  101. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  102. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
  103. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -20
  104. package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
  105. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
  106. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  107. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
  108. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
  109. package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
  110. package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
  111. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  112. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  113. package/templates/nextblock-template/app/cms/users/components/CreateUserForm.tsx +217 -217
  114. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  115. package/templates/nextblock-template/app/cms/users/new/page.tsx +44 -44
  116. package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
  117. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  118. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  119. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  120. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  121. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  122. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  123. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  124. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  125. package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
  126. package/templates/nextblock-template/app/setup/layout.tsx +13 -13
  127. package/templates/nextblock-template/app/setup/page.tsx +111 -111
  128. package/templates/nextblock-template/app/sitemap.ts +130 -130
  129. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  130. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  131. package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
  132. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  133. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  134. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
  135. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  136. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  137. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  138. package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
  139. package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
  140. package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
  141. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  142. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  143. package/templates/nextblock-template/components/blocks/ProductGridClient.tsx +114 -114
  144. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  145. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +8 -1
  146. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
  147. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +29 -23
  148. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  149. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  150. package/templates/nextblock-template/components/form-message.tsx +32 -32
  151. package/templates/nextblock-template/components/media/YouTubeFacade.tsx +69 -0
  152. package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -0
  153. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
  154. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
  155. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
  156. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
  157. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -464
  158. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  159. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  160. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
  161. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  162. package/templates/nextblock-template/context/language-rest-client.ts +32 -32
  163. package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
  164. package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
  165. package/templates/nextblock-template/docker/kong/kong.yml +112 -112
  166. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  167. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
  168. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
  169. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
  170. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
  171. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
  172. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
  173. package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
  174. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
  175. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
  176. package/templates/nextblock-template/docs/README.md +39 -39
  177. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  178. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  179. package/templates/nextblock-template/index.d.ts +7 -7
  180. package/templates/nextblock-template/lib/app-secrets.ts +39 -39
  181. package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
  182. package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
  183. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
  184. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  185. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +78 -78
  186. package/templates/nextblock-template/lib/blocks/README.md +13 -13
  187. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +21 -1
  188. package/templates/nextblock-template/lib/blocks/product-grid-data.ts +210 -210
  189. package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
  190. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
  191. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
  192. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
  193. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
  194. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
  195. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
  196. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
  197. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
  198. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
  199. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
  200. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
  201. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
  202. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
  203. package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
  204. package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
  205. package/templates/nextblock-template/lib/email/branding.ts +76 -76
  206. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
  207. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
  208. package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
  209. package/templates/nextblock-template/lib/i18n/country-languages.ts +247 -247
  210. package/templates/nextblock-template/lib/i18n/detection.test.ts +197 -197
  211. package/templates/nextblock-template/lib/i18n/detection.ts +192 -192
  212. package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
  213. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
  214. package/templates/nextblock-template/lib/media/youtube.ts +81 -0
  215. package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
  216. package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
  217. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  218. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
  219. package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
  220. package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
  221. package/templates/nextblock-template/lib/privacy/types.ts +69 -69
  222. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
  223. package/templates/nextblock-template/lib/promotions/server.ts +741 -741
  224. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
  225. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
  226. package/templates/nextblock-template/lib/search/types.ts +27 -27
  227. package/templates/nextblock-template/lib/setup/actions.ts +460 -460
  228. package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
  229. package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
  230. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +87 -82
  231. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
  232. package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
  233. package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
  234. package/templates/nextblock-template/lib/setup/types.ts +18 -18
  235. package/templates/nextblock-template/lib/site-url.ts +48 -48
  236. package/templates/nextblock-template/lib/storage/provider.ts +66 -66
  237. package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
  238. package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
  239. package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
  240. package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
  241. package/templates/nextblock-template/next-env.d.ts +1 -1
  242. package/templates/nextblock-template/package.json +1 -1
  243. package/templates/nextblock-template/postcss.config.js +6 -6
  244. package/templates/nextblock-template/scripts/backup.js +115 -115
  245. package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
  246. package/templates/nextblock-template/scripts/restore.js +385 -385
  247. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
  248. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
  249. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
  250. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
  251. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
  252. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
  253. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
  254. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
  255. package/templates/nextblock-template/tailwind.config.js +25 -25
  256. package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
  257. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  258. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  259. package/templates/nextblock-template/types/jsdom.d.ts +6 -6
  260. package/tsconfig.base.json +3 -3
@@ -1,293 +1,293 @@
1
1
  // apps/nextblock/scripts/restore.js
2
- const fs = require('fs');
3
- const path = require('path');
4
- const { spawn } = require('child_process');
5
- const readline = require('readline');
6
-
7
- function createPrefixSuppressor(regexes, label) {
8
- return (line) => {
9
- for (const regex of regexes) {
10
- if (regex.test(line)) {
11
- return label;
12
- }
13
- }
14
- return null;
15
- };
16
- }
17
-
18
- const stdoutSuppressors = [
19
- createPrefixSuppressor([/^SET\b/i, /^RESET\b/i], 'SET/RESET statements'),
20
- createPrefixSuppressor([/^CREATE\b/i], 'CREATE statements'),
21
- createPrefixSuppressor([/^ALTER\b/i], 'ALTER statements'),
22
- createPrefixSuppressor([/^DROP\b/i], 'DROP statements'),
23
- createPrefixSuppressor([/^COMMENT\b/i], 'COMMENT statements'),
24
- createPrefixSuppressor([/^GRANT\b/i], 'GRANT statements'),
25
- createPrefixSuppressor([/^REVOKE\b/i], 'REVOKE statements'),
26
- createPrefixSuppressor([/^COPY\b/i], 'COPY statements'),
27
- createPrefixSuppressor([/^SELECT\s+pg_catalog\.setval/i, /^\s*setval\b/i], 'setval outputs'),
28
- createPrefixSuppressor([/^\(\d+\s+rows?\)$/i, /^\(1 row\)$/i], 'row count outputs'),
29
- createPrefixSuppressor([/^\s*set_config\b/i], 'set_config outputs'),
30
- createPrefixSuppressor([/^\s*-{3,}\s*$/], 'table separators'),
31
- createPrefixSuppressor([/^\s*\d+\s*$/], 'numeric result lines'),
32
- ];
33
-
34
- function extractMultiplier(line) {
35
- const match = line.match(/\sx(\d+)$/i);
36
- return match ? parseInt(match[1], 10) : 1;
37
- }
38
-
39
- function createDuplicateCollapser(outputFn, options = {}) {
40
- const suppressors = options.suppressors || [];
41
- const suppressedCounts = new Map();
42
- let buffer = '';
43
- let lastLine = null;
44
- let repeatCount = 0;
45
- let finished = false;
46
-
47
- const flush = () => {
48
- if (lastLine === null) {
49
- return;
50
- }
51
- const baseLine = lastLine;
52
- const multiplier = extractMultiplier(baseLine);
53
- const occurrences = repeatCount * multiplier;
54
-
55
- for (const suppressor of suppressors) {
56
- const label = suppressor(baseLine);
57
- if (label) {
58
- suppressedCounts.set(label, (suppressedCounts.get(label) || 0) + occurrences);
59
- lastLine = null;
60
- repeatCount = 0;
61
- return;
62
- }
63
- }
64
-
65
- const line =
66
- repeatCount > 1 ? `${baseLine} x${repeatCount}` : baseLine;
67
- outputFn(line);
68
- lastLine = null;
69
- repeatCount = 0;
70
- };
71
-
72
- const handleLine = (line) => {
73
- const normalized = line.replace(/\r$/, '');
74
- if (!normalized.trim()) {
75
- return;
76
- }
77
- if (lastLine === null) {
78
- lastLine = normalized;
79
- repeatCount = 1;
80
- return;
81
- }
82
- if (normalized === lastLine) {
83
- repeatCount += 1;
84
- return;
85
- }
86
- flush();
87
- lastLine = normalized;
88
- repeatCount = 1;
89
- };
90
-
91
- return {
92
- write(chunk) {
93
- if (finished) {
94
- return;
95
- }
96
- buffer += chunk.toString();
97
- let newlineIndex;
98
- while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
99
- const line = buffer.slice(0, newlineIndex);
100
- handleLine(line);
101
- buffer = buffer.slice(newlineIndex + 1);
102
- }
103
- },
104
- finish() {
105
- if (finished) {
106
- return suppressedCounts;
107
- }
108
- if (buffer.length > 0) {
109
- handleLine(buffer);
110
- buffer = '';
111
- }
112
- flush();
113
- finished = true;
114
- return suppressedCounts;
115
- },
116
- };
117
- }
118
-
119
- function createErrorFilter(outputFn) {
120
- let buffer = '';
121
- const suppressedCounts = new Map();
122
- let finished = false;
123
-
124
- const ignorePatterns = [
125
- {
126
- regex: /ERROR:\s+permission denied to change default privileges/i,
127
- label: 'permission denied to change default privileges',
128
- },
129
- {
130
- regex: /ERROR:\s+Non-superuser owned event trigger must execute a non-superuser owned function/i,
131
- label: 'event trigger requires non-superuser function',
132
- },
133
- {
134
- regex: /DETAIL:\s+The current user "postgres" is not a superuser and the function "extensions\.[^"]+" is owned by a superuser/i,
135
- },
136
- {
137
- regex: /ERROR:\s+cannot drop function/i,
138
- label: 'function drop dependency issues',
139
- },
140
- {
141
- regex: /ERROR:\s+cannot drop schema/i,
142
- label: 'schema drop dependency issues',
143
- },
144
- {
145
- regex: /ERROR:\s+schema "[^"]+" already exists/i,
146
- label: 'schema exists warnings',
147
- },
148
- {
149
- regex: /ERROR:\s+type "[^"]+" already exists/i,
150
- label: 'type exists warnings',
151
- },
152
- {
153
- regex: /ERROR:\s+function "[^"]+" already exists/i,
154
- label: 'function exists warnings',
155
- },
156
- {
157
- regex: /ERROR:\s+relation "[^"]+" already exists/i,
158
- label: 'relation exists warnings',
159
- },
160
- {
161
- regex: /ERROR:\s+permission denied for schema/i,
162
- label: 'schema permission warnings',
163
- },
164
- {
165
- regex: /^(event trigger\s+|function\s+extensions\.)/i,
166
- label: 'dependency detail lines',
167
- },
168
- {
169
- regex: /ERROR:\s+must be able to SET ROLE/i,
170
- label: 'SET ROLE permission issues',
171
- },
172
- {
173
- regex: /ERROR:\s+must be owner of/i,
174
- label: 'ownership warnings',
175
- },
176
- {
177
- regex: /ERROR:\s+permission denied for table/i,
178
- label: 'table permission warnings',
179
- },
180
- {
181
- regex: /ERROR:\s+duplicate key value violates unique constraint/i,
182
- label: 'duplicate key conflicts',
183
- },
184
- {
185
- regex: /ERROR:\s+insert or update on table .* violates foreign key constraint/i,
186
- label: 'foreign key conflicts',
187
- },
188
- {
189
- regex: /ERROR:\s+grant options cannot be granted back to your own grantor/i,
190
- label: 'grant option warnings',
191
- },
192
- {
193
- regex: /ERROR:\s+function extensions\.pg_stat_statements_reset\(oid, oid, bigint\) does not exist/i,
194
- label: 'pg_stat_statements_reset missing',
195
- },
196
- {
197
- regex: /ERROR:\s+trailing junk after numeric literal/i,
198
- label: 'copy trailing junk errors',
199
- },
200
- {
201
- regex: /ERROR:\s+invalid command \\\S+/i,
202
- label: 'copy invalid command errors',
203
- },
204
- {
205
- regex: /WARNING:\s+no privileges were granted/i,
206
- label: 'privilege grant warnings',
207
- },
208
- {
209
- regex: /WARNING:\s+no privileges could be revoked/i,
210
- label: 'privilege revoke warnings',
211
- },
212
- {
213
- regex: /WARNING:\s+not all privileges were granted/i,
214
- label: 'partial privilege warnings',
215
- },
216
- {
217
- regex: /ERROR:\s+permission denied to set parameter/i,
218
- label: 'configuration permission warnings',
219
- },
220
- {
221
- regex: /^DETAIL:/i,
222
- label: 'suppressed detail lines',
223
- },
224
- {
225
- regex: /^HINT:/i,
226
- label: 'suppressed hint lines',
227
- },
228
- {
229
- regex: /^CONTEXT:\s+/i,
230
- label: 'suppressed context lines',
231
- },
232
- {
233
- regex: /^LINE \d+:/i,
234
- label: 'suppressed context lines',
235
- },
236
- {
237
- regex: /^\s*\^$/i,
238
- label: 'suppressed context lines',
239
- },
240
- ];
241
-
242
- const recordSuppressed = (label) => {
243
- if (!label) {
244
- return;
245
- }
246
- suppressedCounts.set(label, (suppressedCounts.get(label) || 0) + 1);
247
- };
248
-
249
- const handleLine = (line) => {
250
- const normalized = line.replace(/\r$/, '');
251
- if (!normalized.trim()) {
252
- return;
253
- }
254
- for (const { regex, label } of ignorePatterns) {
255
- if (regex.test(normalized)) {
256
- recordSuppressed(label);
257
- return;
258
- }
259
- }
260
- outputFn(normalized);
261
- };
262
-
263
- const finish = () => {
264
- if (finished) {
265
- return suppressedCounts;
266
- }
267
- if (buffer.length > 0) {
268
- handleLine(buffer);
269
- buffer = '';
270
- }
271
- finished = true;
272
- return suppressedCounts;
273
- };
274
-
275
- return {
276
- write(chunk) {
277
- if (finished) {
278
- return;
279
- }
280
- buffer += chunk.toString();
281
- let newlineIndex;
282
- while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
283
- const line = buffer.slice(0, newlineIndex);
284
- handleLine(line);
285
- buffer = buffer.slice(newlineIndex + 1);
286
- }
287
- },
288
- finish,
289
- };
290
- }
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { spawn } = require('child_process');
5
+ const readline = require('readline');
6
+
7
+ function createPrefixSuppressor(regexes, label) {
8
+ return (line) => {
9
+ for (const regex of regexes) {
10
+ if (regex.test(line)) {
11
+ return label;
12
+ }
13
+ }
14
+ return null;
15
+ };
16
+ }
17
+
18
+ const stdoutSuppressors = [
19
+ createPrefixSuppressor([/^SET\b/i, /^RESET\b/i], 'SET/RESET statements'),
20
+ createPrefixSuppressor([/^CREATE\b/i], 'CREATE statements'),
21
+ createPrefixSuppressor([/^ALTER\b/i], 'ALTER statements'),
22
+ createPrefixSuppressor([/^DROP\b/i], 'DROP statements'),
23
+ createPrefixSuppressor([/^COMMENT\b/i], 'COMMENT statements'),
24
+ createPrefixSuppressor([/^GRANT\b/i], 'GRANT statements'),
25
+ createPrefixSuppressor([/^REVOKE\b/i], 'REVOKE statements'),
26
+ createPrefixSuppressor([/^COPY\b/i], 'COPY statements'),
27
+ createPrefixSuppressor([/^SELECT\s+pg_catalog\.setval/i, /^\s*setval\b/i], 'setval outputs'),
28
+ createPrefixSuppressor([/^\(\d+\s+rows?\)$/i, /^\(1 row\)$/i], 'row count outputs'),
29
+ createPrefixSuppressor([/^\s*set_config\b/i], 'set_config outputs'),
30
+ createPrefixSuppressor([/^\s*-{3,}\s*$/], 'table separators'),
31
+ createPrefixSuppressor([/^\s*\d+\s*$/], 'numeric result lines'),
32
+ ];
33
+
34
+ function extractMultiplier(line) {
35
+ const match = line.match(/\sx(\d+)$/i);
36
+ return match ? parseInt(match[1], 10) : 1;
37
+ }
38
+
39
+ function createDuplicateCollapser(outputFn, options = {}) {
40
+ const suppressors = options.suppressors || [];
41
+ const suppressedCounts = new Map();
42
+ let buffer = '';
43
+ let lastLine = null;
44
+ let repeatCount = 0;
45
+ let finished = false;
46
+
47
+ const flush = () => {
48
+ if (lastLine === null) {
49
+ return;
50
+ }
51
+ const baseLine = lastLine;
52
+ const multiplier = extractMultiplier(baseLine);
53
+ const occurrences = repeatCount * multiplier;
54
+
55
+ for (const suppressor of suppressors) {
56
+ const label = suppressor(baseLine);
57
+ if (label) {
58
+ suppressedCounts.set(label, (suppressedCounts.get(label) || 0) + occurrences);
59
+ lastLine = null;
60
+ repeatCount = 0;
61
+ return;
62
+ }
63
+ }
64
+
65
+ const line =
66
+ repeatCount > 1 ? `${baseLine} x${repeatCount}` : baseLine;
67
+ outputFn(line);
68
+ lastLine = null;
69
+ repeatCount = 0;
70
+ };
71
+
72
+ const handleLine = (line) => {
73
+ const normalized = line.replace(/\r$/, '');
74
+ if (!normalized.trim()) {
75
+ return;
76
+ }
77
+ if (lastLine === null) {
78
+ lastLine = normalized;
79
+ repeatCount = 1;
80
+ return;
81
+ }
82
+ if (normalized === lastLine) {
83
+ repeatCount += 1;
84
+ return;
85
+ }
86
+ flush();
87
+ lastLine = normalized;
88
+ repeatCount = 1;
89
+ };
90
+
91
+ return {
92
+ write(chunk) {
93
+ if (finished) {
94
+ return;
95
+ }
96
+ buffer += chunk.toString();
97
+ let newlineIndex;
98
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
99
+ const line = buffer.slice(0, newlineIndex);
100
+ handleLine(line);
101
+ buffer = buffer.slice(newlineIndex + 1);
102
+ }
103
+ },
104
+ finish() {
105
+ if (finished) {
106
+ return suppressedCounts;
107
+ }
108
+ if (buffer.length > 0) {
109
+ handleLine(buffer);
110
+ buffer = '';
111
+ }
112
+ flush();
113
+ finished = true;
114
+ return suppressedCounts;
115
+ },
116
+ };
117
+ }
118
+
119
+ function createErrorFilter(outputFn) {
120
+ let buffer = '';
121
+ const suppressedCounts = new Map();
122
+ let finished = false;
123
+
124
+ const ignorePatterns = [
125
+ {
126
+ regex: /ERROR:\s+permission denied to change default privileges/i,
127
+ label: 'permission denied to change default privileges',
128
+ },
129
+ {
130
+ regex: /ERROR:\s+Non-superuser owned event trigger must execute a non-superuser owned function/i,
131
+ label: 'event trigger requires non-superuser function',
132
+ },
133
+ {
134
+ regex: /DETAIL:\s+The current user "postgres" is not a superuser and the function "extensions\.[^"]+" is owned by a superuser/i,
135
+ },
136
+ {
137
+ regex: /ERROR:\s+cannot drop function/i,
138
+ label: 'function drop dependency issues',
139
+ },
140
+ {
141
+ regex: /ERROR:\s+cannot drop schema/i,
142
+ label: 'schema drop dependency issues',
143
+ },
144
+ {
145
+ regex: /ERROR:\s+schema "[^"]+" already exists/i,
146
+ label: 'schema exists warnings',
147
+ },
148
+ {
149
+ regex: /ERROR:\s+type "[^"]+" already exists/i,
150
+ label: 'type exists warnings',
151
+ },
152
+ {
153
+ regex: /ERROR:\s+function "[^"]+" already exists/i,
154
+ label: 'function exists warnings',
155
+ },
156
+ {
157
+ regex: /ERROR:\s+relation "[^"]+" already exists/i,
158
+ label: 'relation exists warnings',
159
+ },
160
+ {
161
+ regex: /ERROR:\s+permission denied for schema/i,
162
+ label: 'schema permission warnings',
163
+ },
164
+ {
165
+ regex: /^(event trigger\s+|function\s+extensions\.)/i,
166
+ label: 'dependency detail lines',
167
+ },
168
+ {
169
+ regex: /ERROR:\s+must be able to SET ROLE/i,
170
+ label: 'SET ROLE permission issues',
171
+ },
172
+ {
173
+ regex: /ERROR:\s+must be owner of/i,
174
+ label: 'ownership warnings',
175
+ },
176
+ {
177
+ regex: /ERROR:\s+permission denied for table/i,
178
+ label: 'table permission warnings',
179
+ },
180
+ {
181
+ regex: /ERROR:\s+duplicate key value violates unique constraint/i,
182
+ label: 'duplicate key conflicts',
183
+ },
184
+ {
185
+ regex: /ERROR:\s+insert or update on table .* violates foreign key constraint/i,
186
+ label: 'foreign key conflicts',
187
+ },
188
+ {
189
+ regex: /ERROR:\s+grant options cannot be granted back to your own grantor/i,
190
+ label: 'grant option warnings',
191
+ },
192
+ {
193
+ regex: /ERROR:\s+function extensions\.pg_stat_statements_reset\(oid, oid, bigint\) does not exist/i,
194
+ label: 'pg_stat_statements_reset missing',
195
+ },
196
+ {
197
+ regex: /ERROR:\s+trailing junk after numeric literal/i,
198
+ label: 'copy trailing junk errors',
199
+ },
200
+ {
201
+ regex: /ERROR:\s+invalid command \\\S+/i,
202
+ label: 'copy invalid command errors',
203
+ },
204
+ {
205
+ regex: /WARNING:\s+no privileges were granted/i,
206
+ label: 'privilege grant warnings',
207
+ },
208
+ {
209
+ regex: /WARNING:\s+no privileges could be revoked/i,
210
+ label: 'privilege revoke warnings',
211
+ },
212
+ {
213
+ regex: /WARNING:\s+not all privileges were granted/i,
214
+ label: 'partial privilege warnings',
215
+ },
216
+ {
217
+ regex: /ERROR:\s+permission denied to set parameter/i,
218
+ label: 'configuration permission warnings',
219
+ },
220
+ {
221
+ regex: /^DETAIL:/i,
222
+ label: 'suppressed detail lines',
223
+ },
224
+ {
225
+ regex: /^HINT:/i,
226
+ label: 'suppressed hint lines',
227
+ },
228
+ {
229
+ regex: /^CONTEXT:\s+/i,
230
+ label: 'suppressed context lines',
231
+ },
232
+ {
233
+ regex: /^LINE \d+:/i,
234
+ label: 'suppressed context lines',
235
+ },
236
+ {
237
+ regex: /^\s*\^$/i,
238
+ label: 'suppressed context lines',
239
+ },
240
+ ];
241
+
242
+ const recordSuppressed = (label) => {
243
+ if (!label) {
244
+ return;
245
+ }
246
+ suppressedCounts.set(label, (suppressedCounts.get(label) || 0) + 1);
247
+ };
248
+
249
+ const handleLine = (line) => {
250
+ const normalized = line.replace(/\r$/, '');
251
+ if (!normalized.trim()) {
252
+ return;
253
+ }
254
+ for (const { regex, label } of ignorePatterns) {
255
+ if (regex.test(normalized)) {
256
+ recordSuppressed(label);
257
+ return;
258
+ }
259
+ }
260
+ outputFn(normalized);
261
+ };
262
+
263
+ const finish = () => {
264
+ if (finished) {
265
+ return suppressedCounts;
266
+ }
267
+ if (buffer.length > 0) {
268
+ handleLine(buffer);
269
+ buffer = '';
270
+ }
271
+ finished = true;
272
+ return suppressedCounts;
273
+ };
274
+
275
+ return {
276
+ write(chunk) {
277
+ if (finished) {
278
+ return;
279
+ }
280
+ buffer += chunk.toString();
281
+ let newlineIndex;
282
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
283
+ const line = buffer.slice(0, newlineIndex);
284
+ handleLine(line);
285
+ buffer = buffer.slice(newlineIndex + 1);
286
+ }
287
+ },
288
+ finish,
289
+ };
290
+ }
291
291
 
292
292
  // Load target database connection from env
293
293
  require('dotenv').config({ path: '.env.local' }); // ensure this has the new DB credentials
@@ -317,22 +317,22 @@ const user = connectionUrl.username;
317
317
  const password = connectionUrl.password;
318
318
  const sslMode = connectionUrl.searchParams.get('sslmode') || 'require';
319
319
 
320
- function formatBackupLabel(dirName) {
321
- const delimiter = '__';
322
- const idx = dirName.indexOf(delimiter);
323
- if (idx === -1) {
324
- return dirName;
325
- }
326
- const label = dirName.slice(idx + delimiter.length);
327
- return label || dirName;
328
- }
329
-
330
- // List available backup folders
331
- const backupsPath = path.join(__dirname, '../backups');
332
- if (!fs.existsSync(backupsPath)) {
333
- console.error("❌ Backups directory not found.");
334
- process.exit(1);
335
- }
320
+ function formatBackupLabel(dirName) {
321
+ const delimiter = '__';
322
+ const idx = dirName.indexOf(delimiter);
323
+ if (idx === -1) {
324
+ return dirName;
325
+ }
326
+ const label = dirName.slice(idx + delimiter.length);
327
+ return label || dirName;
328
+ }
329
+
330
+ // List available backup folders
331
+ const backupsPath = path.join(__dirname, '../backups');
332
+ if (!fs.existsSync(backupsPath)) {
333
+ console.error("❌ Backups directory not found.");
334
+ process.exit(1);
335
+ }
336
336
  const backupDirs = fs.readdirSync(backupsPath, { withFileTypes: true })
337
337
  .filter(dirent => dirent.isDirectory())
338
338
  .map(dirent => dirent.name);
@@ -344,27 +344,27 @@ if (backupDirs.length === 0) {
344
344
  process.exit(1);
345
345
  }
346
346
 
347
- // Show list of backups
348
- console.log("Available backups:");
349
- backupDirs.forEach((dir, index) => {
350
- const label = formatBackupLabel(dir);
351
- const prefix = index === 0 ? '(latest) ' : '';
352
- console.log(`${index + 1}. ${prefix}${label}`);
353
- });
354
-
355
- // Prompt user to choose a backup
356
- const rl = readline.createInterface({
357
- input: process.stdin,
358
- output: process.stdout
359
- });
360
- rl.question("Enter the number of the backup to restore (press Enter for latest): ", (answer) => {
361
- rl.close();
362
- const trimmed = answer.trim();
363
- const choice = trimmed === '' ? 1 : parseInt(trimmed, 10);
364
- if (isNaN(choice) || choice < 1 || choice > backupDirs.length) {
365
- console.error("❌ Invalid selection. Exiting.");
366
- process.exit(1);
367
- }
347
+ // Show list of backups
348
+ console.log("Available backups:");
349
+ backupDirs.forEach((dir, index) => {
350
+ const label = formatBackupLabel(dir);
351
+ const prefix = index === 0 ? '(latest) ' : '';
352
+ console.log(`${index + 1}. ${prefix}${label}`);
353
+ });
354
+
355
+ // Prompt user to choose a backup
356
+ const rl = readline.createInterface({
357
+ input: process.stdin,
358
+ output: process.stdout
359
+ });
360
+ rl.question("Enter the number of the backup to restore (press Enter for latest): ", (answer) => {
361
+ rl.close();
362
+ const trimmed = answer.trim();
363
+ const choice = trimmed === '' ? 1 : parseInt(trimmed, 10);
364
+ if (isNaN(choice) || choice < 1 || choice > backupDirs.length) {
365
+ console.error("❌ Invalid selection. Exiting.");
366
+ process.exit(1);
367
+ }
368
368
 
369
369
  const selectedDir = backupDirs[choice - 1];
370
370
  const dumpFile = path.join(backupsPath, selectedDir, 'dump.sql');
@@ -373,62 +373,62 @@ rl.question("Enter the number of the backup to restore (press Enter for latest):
373
373
  process.exit(1);
374
374
  }
375
375
 
376
- console.log(`🔄 Restoring database from backup "${selectedDir}"...`);
377
-
378
- // Spawn psql to execute the dump file
379
- const restoreArgs = [
380
- '-h', host,
381
- '-U', user,
382
- '-p', port,
383
- '-d', dbName,
384
- '-f', dumpFile
385
- ];
386
- const envVars = { ...process.env, PGPASSWORD: password, PGSSLMODE: sslMode };
387
- const psql = spawn('psql', restoreArgs, { env: envVars });
388
- const stdoutCollapser = createDuplicateCollapser(line => {
389
- process.stdout.write(`${line}\n`);
390
- }, { suppressors: stdoutSuppressors });
391
- const stderrFilter = createErrorFilter(line => {
392
- process.stderr.write(`${line}\n`);
393
- });
394
-
395
- psql.stdout.on('data', chunk => stdoutCollapser.write(chunk));
396
- psql.stderr.on('data', chunk => stderrFilter.write(chunk));
397
-
398
- psql.on('close', code => {
399
- const stdoutSuppressed = stdoutCollapser.finish();
400
- const stderrSuppressed = stderrFilter.finish();
401
-
402
- const formatSummary = (label, map) => {
403
- if (!map || map.size === 0) {
404
- return null;
405
- }
406
- const parts = [];
407
- let subtotal = 0;
408
- for (const [entryLabel, count] of map.entries()) {
409
- subtotal += count;
410
- parts.push(`${entryLabel} x${count}`);
411
- }
412
- return { label, subtotal, parts };
413
- };
414
-
415
- const summaries = [
416
- formatSummary('stdout', stdoutSuppressed),
417
- formatSummary('stderr', stderrSuppressed),
418
- ].filter(Boolean);
419
-
420
- if (summaries.length > 0) {
421
- const totalSuppressed = summaries.reduce((acc, { subtotal }) => acc + subtotal, 0);
422
- const detail = summaries
423
- .map(({ label, parts }) => `${label}: ${parts.join(', ')}`)
424
- .join(' | ');
425
- console.log(`ℹ️ Suppressed ${totalSuppressed} known restore messages (${detail}).`);
426
- }
427
-
428
- if (code === 0) {
429
- console.log("✅ Restore completed successfully.");
430
- } else {
431
- console.error(`❌ psql exited with code ${code}. The restore may have errors.`);
432
- }
433
- });
434
- });
376
+ console.log(`🔄 Restoring database from backup "${selectedDir}"...`);
377
+
378
+ // Spawn psql to execute the dump file
379
+ const restoreArgs = [
380
+ '-h', host,
381
+ '-U', user,
382
+ '-p', port,
383
+ '-d', dbName,
384
+ '-f', dumpFile
385
+ ];
386
+ const envVars = { ...process.env, PGPASSWORD: password, PGSSLMODE: sslMode };
387
+ const psql = spawn('psql', restoreArgs, { env: envVars });
388
+ const stdoutCollapser = createDuplicateCollapser(line => {
389
+ process.stdout.write(`${line}\n`);
390
+ }, { suppressors: stdoutSuppressors });
391
+ const stderrFilter = createErrorFilter(line => {
392
+ process.stderr.write(`${line}\n`);
393
+ });
394
+
395
+ psql.stdout.on('data', chunk => stdoutCollapser.write(chunk));
396
+ psql.stderr.on('data', chunk => stderrFilter.write(chunk));
397
+
398
+ psql.on('close', code => {
399
+ const stdoutSuppressed = stdoutCollapser.finish();
400
+ const stderrSuppressed = stderrFilter.finish();
401
+
402
+ const formatSummary = (label, map) => {
403
+ if (!map || map.size === 0) {
404
+ return null;
405
+ }
406
+ const parts = [];
407
+ let subtotal = 0;
408
+ for (const [entryLabel, count] of map.entries()) {
409
+ subtotal += count;
410
+ parts.push(`${entryLabel} x${count}`);
411
+ }
412
+ return { label, subtotal, parts };
413
+ };
414
+
415
+ const summaries = [
416
+ formatSummary('stdout', stdoutSuppressed),
417
+ formatSummary('stderr', stderrSuppressed),
418
+ ].filter(Boolean);
419
+
420
+ if (summaries.length > 0) {
421
+ const totalSuppressed = summaries.reduce((acc, { subtotal }) => acc + subtotal, 0);
422
+ const detail = summaries
423
+ .map(({ label, parts }) => `${label}: ${parts.join(', ')}`)
424
+ .join(' | ');
425
+ console.log(`ℹ️ Suppressed ${totalSuppressed} known restore messages (${detail}).`);
426
+ }
427
+
428
+ if (code === 0) {
429
+ console.log("✅ Restore completed successfully.");
430
+ } else {
431
+ console.error(`❌ psql exited with code ${code}. The restore may have errors.`);
432
+ }
433
+ });
434
+ });