@pasosdejesus/m 0.15.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/blockchain/deployments.d.ts +40 -0
- package/dist/blockchain/deployments.d.ts.map +1 -0
- package/dist/blockchain/deployments.js +64 -0
- package/dist/debug/DebugConsole.d.ts +22 -0
- package/dist/debug/DebugConsole.d.ts.map +1 -0
- package/dist/debug/DebugConsole.js +79 -0
- package/dist/debug/Logger.d.ts +38 -0
- package/dist/debug/Logger.d.ts.map +1 -0
- package/dist/debug/Logger.js +94 -0
- package/dist/debug/index.d.ts +6 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/index.js +4 -0
- package/dist/debug/useLogger.d.ts +14 -0
- package/dist/debug/useLogger.d.ts.map +1 -0
- package/dist/debug/useLogger.js +23 -0
- package/dist/debug/utils.d.ts +10 -0
- package/dist/debug/utils.d.ts.map +1 -0
- package/dist/debug/utils.js +53 -0
- package/dist/e2e/assertions.d.ts +5 -0
- package/dist/e2e/assertions.d.ts.map +1 -0
- package/dist/e2e/assertions.js +18 -0
- package/dist/e2e/browser.d.ts +37 -0
- package/dist/e2e/browser.d.ts.map +1 -0
- package/dist/e2e/browser.js +97 -0
- package/dist/e2e/env.d.ts +18 -0
- package/dist/e2e/env.d.ts.map +1 -0
- package/dist/e2e/env.js +57 -0
- package/dist/e2e/index.d.ts +11 -0
- package/dist/e2e/index.d.ts.map +1 -0
- package/dist/e2e/index.js +8 -0
- package/dist/e2e/navigation.d.ts +4 -0
- package/dist/e2e/navigation.d.ts.map +1 -0
- package/dist/e2e/navigation.js +32 -0
- package/dist/e2e/siwe-mock.d.ts +11 -0
- package/dist/e2e/siwe-mock.d.ts.map +1 -0
- package/dist/e2e/siwe-mock.js +112 -0
- package/dist/e2e/siwe.d.ts +15 -0
- package/dist/e2e/siwe.d.ts.map +1 -0
- package/dist/e2e/siwe.js +85 -0
- package/dist/e2e/wallet-mock.d.ts +13 -0
- package/dist/e2e/wallet-mock.d.ts.map +1 -0
- package/dist/e2e/wallet-mock.js +91 -0
- package/dist/i18n/common-cli.d.ts +11 -0
- package/dist/i18n/common-cli.d.ts.map +1 -0
- package/dist/i18n/common-cli.js +22 -0
- package/dist/i18n/createTranslator.d.ts +28 -0
- package/dist/i18n/createTranslator.d.ts.map +1 -0
- package/dist/i18n/createTranslator.js +48 -0
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/locales/cli.d.ts +3 -0
- package/dist/i18n/locales/cli.d.ts.map +1 -0
- package/dist/i18n/locales/cli.js +180 -0
- package/dist/i18n/types.d.ts +3 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +10 -0
- package/dist/m.d.ts +2 -0
- package/dist/m.d.ts.map +1 -0
- package/dist/m.js +334 -0
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +7 -0
- package/dist/shadcn_components/cn.d.ts +13 -0
- package/dist/shadcn_components/cn.d.ts.map +1 -0
- package/dist/shadcn_components/cn.js +15 -0
- package/dist/shadcn_components/ui/accordion.d.ts +8 -0
- package/dist/shadcn_components/ui/accordion.d.ts.map +1 -0
- package/dist/shadcn_components/ui/accordion.js +14 -0
- package/dist/shadcn_components/ui/alert-dialog.d.ts +21 -0
- package/dist/shadcn_components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/shadcn_components/ui/alert-dialog.js +27 -0
- package/dist/shadcn_components/ui/alert.d.ts +9 -0
- package/dist/shadcn_components/ui/alert.d.ts.map +1 -0
- package/dist/shadcn_components/ui/alert.js +22 -0
- package/dist/shadcn_components/ui/avatar.d.ts +7 -0
- package/dist/shadcn_components/ui/avatar.d.ts.map +1 -0
- package/dist/shadcn_components/ui/avatar.js +12 -0
- package/dist/shadcn_components/ui/badge.d.ts +10 -0
- package/dist/shadcn_components/ui/badge.d.ts.map +1 -0
- package/dist/shadcn_components/ui/badge.js +22 -0
- package/dist/shadcn_components/ui/button.d.ts +11 -0
- package/dist/shadcn_components/ui/button.d.ts.map +1 -0
- package/dist/shadcn_components/ui/button.js +31 -0
- package/dist/shadcn_components/ui/card.d.ts +10 -0
- package/dist/shadcn_components/ui/card.d.ts.map +1 -0
- package/dist/shadcn_components/ui/card.js +24 -0
- package/dist/shadcn_components/ui/checkbox.d.ts +5 -0
- package/dist/shadcn_components/ui/checkbox.d.ts.map +1 -0
- package/dist/shadcn_components/ui/checkbox.js +9 -0
- package/dist/shadcn_components/ui/dialog.d.ts +20 -0
- package/dist/shadcn_components/ui/dialog.d.ts.map +1 -0
- package/dist/shadcn_components/ui/dialog.js +23 -0
- package/dist/shadcn_components/ui/dropdown-menu.d.ts +28 -0
- package/dist/shadcn_components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/shadcn_components/ui/dropdown-menu.js +36 -0
- package/dist/shadcn_components/ui/form.d.ts +24 -0
- package/dist/shadcn_components/ui/form.d.ts.map +1 -0
- package/dist/shadcn_components/ui/form.js +63 -0
- package/dist/shadcn_components/ui/input.d.ts +4 -0
- package/dist/shadcn_components/ui/input.d.ts.map +1 -0
- package/dist/shadcn_components/ui/input.js +6 -0
- package/dist/shadcn_components/ui/label.d.ts +6 -0
- package/dist/shadcn_components/ui/label.d.ts.map +1 -0
- package/dist/shadcn_components/ui/label.js +10 -0
- package/dist/shadcn_components/ui/menubar.d.ts +29 -0
- package/dist/shadcn_components/ui/menubar.d.ts.map +1 -0
- package/dist/shadcn_components/ui/menubar.js +46 -0
- package/dist/shadcn_components/ui/popover.d.ts +7 -0
- package/dist/shadcn_components/ui/popover.d.ts.map +1 -0
- package/dist/shadcn_components/ui/popover.js +10 -0
- package/dist/shadcn_components/ui/progress.d.ts +5 -0
- package/dist/shadcn_components/ui/progress.d.ts.map +1 -0
- package/dist/shadcn_components/ui/progress.js +8 -0
- package/dist/shadcn_components/ui/radio-group.d.ts +6 -0
- package/dist/shadcn_components/ui/radio-group.d.ts.map +1 -0
- package/dist/shadcn_components/ui/radio-group.js +15 -0
- package/dist/shadcn_components/ui/scroll-area.d.ts +6 -0
- package/dist/shadcn_components/ui/scroll-area.d.ts.map +1 -0
- package/dist/shadcn_components/ui/scroll-area.js +12 -0
- package/dist/shadcn_components/ui/select.d.ts +14 -0
- package/dist/shadcn_components/ui/select.d.ts.map +1 -0
- package/dist/shadcn_components/ui/select.js +27 -0
- package/dist/shadcn_components/ui/separator.d.ts +5 -0
- package/dist/shadcn_components/ui/separator.d.ts.map +1 -0
- package/dist/shadcn_components/ui/separator.js +8 -0
- package/dist/shadcn_components/ui/sheet.d.ts +26 -0
- package/dist/shadcn_components/ui/sheet.d.ts.map +1 -0
- package/dist/shadcn_components/ui/sheet.js +37 -0
- package/dist/shadcn_components/ui/skeleton.d.ts +3 -0
- package/dist/shadcn_components/ui/skeleton.d.ts.map +1 -0
- package/dist/shadcn_components/ui/skeleton.js +6 -0
- package/dist/shadcn_components/ui/slider.d.ts +5 -0
- package/dist/shadcn_components/ui/slider.d.ts.map +1 -0
- package/dist/shadcn_components/ui/slider.js +8 -0
- package/dist/shadcn_components/ui/switch.d.ts +5 -0
- package/dist/shadcn_components/ui/switch.d.ts.map +1 -0
- package/dist/shadcn_components/ui/switch.js +8 -0
- package/dist/shadcn_components/ui/table.d.ts +11 -0
- package/dist/shadcn_components/ui/table.d.ts.map +1 -0
- package/dist/shadcn_components/ui/table.js +20 -0
- package/dist/shadcn_components/ui/tabs.d.ts +8 -0
- package/dist/shadcn_components/ui/tabs.d.ts.map +1 -0
- package/dist/shadcn_components/ui/tabs.js +13 -0
- package/dist/shadcn_components/ui/textarea.d.ts +4 -0
- package/dist/shadcn_components/ui/textarea.d.ts.map +1 -0
- package/dist/shadcn_components/ui/textarea.js +8 -0
- package/dist/shadcn_components/ui/toast.d.ts +16 -0
- package/dist/shadcn_components/ui/toast.d.ts.map +1 -0
- package/dist/shadcn_components/ui/toast.js +34 -0
- package/dist/shadcn_components/ui/toaster.d.ts +2 -0
- package/dist/shadcn_components/ui/toaster.d.ts.map +1 -0
- package/dist/shadcn_components/ui/toaster.js +10 -0
- package/dist/shadcn_components/ui/tooltip.d.ts +8 -0
- package/dist/shadcn_components/ui/tooltip.d.ts.map +1 -0
- package/dist/shadcn_components/ui/tooltip.js +11 -0
- package/dist/shadcn_components/ui/use-toast.d.ts +45 -0
- package/dist/shadcn_components/ui/use-toast.d.ts.map +1 -0
- package/dist/shadcn_components/ui/use-toast.js +126 -0
- package/dist/tasks/context.d.ts +13 -0
- package/dist/tasks/context.d.ts.map +1 -0
- package/dist/tasks/context.js +310 -0
- package/dist/tasks/create.d.ts +2 -0
- package/dist/tasks/create.d.ts.map +1 -0
- package/dist/tasks/create.js +91 -0
- package/dist/tasks/db.d.ts +19 -0
- package/dist/tasks/db.d.ts.map +1 -0
- package/dist/tasks/db.js +791 -0
- package/dist/tasks/e2e.d.ts +2 -0
- package/dist/tasks/e2e.d.ts.map +1 -0
- package/dist/tasks/e2e.js +124 -0
- package/dist/tasks/edit.d.ts +7 -0
- package/dist/tasks/edit.d.ts.map +1 -0
- package/dist/tasks/edit.js +471 -0
- package/dist/tasks/engine.d.ts +2 -0
- package/dist/tasks/engine.d.ts.map +1 -0
- package/dist/tasks/engine.js +149 -0
- package/dist/tasks/env-diff.d.ts +2 -0
- package/dist/tasks/env-diff.d.ts.map +1 -0
- package/dist/tasks/env-diff.js +140 -0
- package/dist/tasks/fuzzy.d.ts +9 -0
- package/dist/tasks/fuzzy.d.ts.map +1 -0
- package/dist/tasks/fuzzy.js +153 -0
- package/dist/tasks/glob.d.ts +5 -0
- package/dist/tasks/glob.d.ts.map +1 -0
- package/dist/tasks/glob.js +57 -0
- package/dist/tasks/grep.d.ts +9 -0
- package/dist/tasks/grep.d.ts.map +1 -0
- package/dist/tasks/grep.js +93 -0
- package/dist/tasks/readfile.d.ts +2 -0
- package/dist/tasks/readfile.d.ts.map +1 -0
- package/dist/tasks/readfile.js +177 -0
- package/dist/tasks/update.d.ts +5 -0
- package/dist/tasks/update.d.ts.map +1 -0
- package/dist/tasks/update.js +200 -0
- package/dist/tasks/util.d.ts +12 -0
- package/dist/tasks/util.d.ts.map +1 -0
- package/dist/tasks/util.js +42 -0
- package/dist/tasks/wallet.d.ts +2 -0
- package/dist/tasks/wallet.d.ts.map +1 -0
- package/dist/tasks/wallet.js +697 -0
- package/dist/tasks/writefile.d.ts +2 -0
- package/dist/tasks/writefile.d.ts.map +1 -0
- package/dist/tasks/writefile.js +66 -0
- package/dist/templates/AGENTS.md.hbs +81 -0
- package/dist/templates/ARCHITECTURE.md.hbs +146 -0
- package/dist/templates/CONTRIBUTING.md.hbs +83 -0
- package/dist/templates/LICENCIA.md +21 -0
- package/dist/templates/LICENSE.md +15 -0
- package/dist/templates/Makefile +16 -0
- package/dist/templates/README.md.hbs +111 -0
- package/dist/templates/apps/.env.example.hbs +75 -0
- package/dist/templates/apps/hardhat/.prettierrc +16 -0
- package/dist/templates/apps/hardhat/MOCK_USDT_README.md +20 -0
- package/dist/templates/apps/hardhat/Makefile +13 -0
- package/dist/templates/apps/hardhat/README.md +129 -0
- package/dist/templates/apps/hardhat/bin/MusdtVerification +4 -0
- package/dist/templates/apps/hardhat/bin/approveDepositOwner +7 -0
- package/dist/templates/apps/hardhat/bin/contractVerification +9 -0
- package/dist/templates/apps/hardhat/bin/deployMockUSDT +7 -0
- package/dist/templates/apps/hardhat/bin/prepadJ.sh +14 -0
- package/dist/templates/apps/hardhat/bin/verifyMockUSDT +7 -0
- package/dist/templates/apps/hardhat/contracts/MockUSDT.sol +47 -0
- package/dist/templates/apps/hardhat/hardhat.config.ts +102 -0
- package/dist/templates/apps/hardhat/package.json +55 -0
- package/dist/templates/apps/hardhat/scripts/approveDepositOwner.ts +22 -0
- package/dist/templates/apps/hardhat/scripts/deployMockUSDT.ts +49 -0
- package/dist/templates/apps/hardhat/scripts/verifyDeployment.ts +93 -0
- package/dist/templates/apps/hardhat/scripts/verifyMockUSDT.ts +52 -0
- package/dist/templates/apps/hardhat/sync-abis.mjs +58 -0
- package/dist/templates/apps/hardhat/test/MockUSDT.test.ts +40 -0
- package/dist/templates/apps/hardhat/tsconfig.json +27 -0
- package/dist/templates/apps/nextjs/.config/kysely.config.ts +36 -0
- package/dist/templates/apps/nextjs/Makefile +24 -0
- package/dist/templates/apps/nextjs/README.md.hbs +76 -0
- package/dist/templates/apps/nextjs/abis/IERC20.json +74 -0
- package/dist/templates/apps/nextjs/abis/MockUSDT.json +242 -0
- package/dist/templates/apps/nextjs/abis/ReentrancyGuard.json +1 -0
- package/dist/templates/apps/nextjs/app/globals.css +3 -0
- package/dist/templates/apps/nextjs/app/layout.tsx.hbs +26 -0
- package/dist/templates/apps/nextjs/app/page.tsx.hbs +225 -0
- package/dist/templates/apps/nextjs/app/testui/page.tsx +371 -0
- package/dist/templates/apps/nextjs/bin/m +8 -0
- package/dist/templates/apps/nextjs/components.json +21 -0
- package/dist/templates/apps/nextjs/next-env.d.ts +5 -0
- package/dist/templates/apps/nextjs/next.config.ts +10 -0
- package/dist/templates/apps/nextjs/package.json.hbs +48 -0
- package/dist/templates/apps/nextjs/pnpm-workspace.yaml +13 -0
- package/dist/templates/apps/nextjs/postcss.config.mjs +6 -0
- package/dist/templates/apps/nextjs/tailwind.config.js +11 -0
- package/dist/templates/apps/nextjs/tests/helpers.ts +31 -0
- package/dist/templates/apps/nextjs/tests/index.test.ts +7 -0
- package/dist/templates/apps/nextjs/tests/setup-env.ts.hbs +8 -0
- package/dist/templates/apps/nextjs/tests/test-utils.example.test.ts +168 -0
- package/dist/templates/apps/nextjs/tsconfig.json +44 -0
- package/dist/templates/apps/nextjs/tsconfig.test.json +38 -0
- package/dist/templates/apps/nextjs/vitest.config.ts +13 -0
- package/dist/test-utils/fs-mocks.d.ts +32 -0
- package/dist/test-utils/fs-mocks.d.ts.map +1 -0
- package/dist/test-utils/fs-mocks.js +63 -0
- package/dist/test-utils/i18n-mock.d.ts +21 -0
- package/dist/test-utils/i18n-mock.d.ts.map +1 -0
- package/dist/test-utils/i18n-mock.js +29 -0
- package/dist/test-utils/index.d.ts +12 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/index.js +11 -0
- package/dist/test-utils/kysely-mocks.d.ts +298 -0
- package/dist/test-utils/kysely-mocks.d.ts.map +1 -0
- package/dist/test-utils/kysely-mocks.js +173 -0
- package/dist/test-utils/radix-mocks.d.ts +134 -0
- package/dist/test-utils/radix-mocks.d.ts.map +1 -0
- package/dist/test-utils/radix-mocks.js +175 -0
- package/dist/test-utils/rainbowkit-mocks.d.ts +80 -0
- package/dist/test-utils/rainbowkit-mocks.d.ts.map +1 -0
- package/dist/test-utils/rainbowkit-mocks.js +247 -0
- package/dist/test-utils/viem-mocks.d.ts +86 -0
- package/dist/test-utils/viem-mocks.d.ts.map +1 -0
- package/dist/test-utils/viem-mocks.js +54 -0
- package/dist/util/cli-parser.d.ts +20 -0
- package/dist/util/cli-parser.d.ts.map +1 -0
- package/dist/util/cli-parser.js +38 -0
- package/dist/util/colors.d.ts +5 -0
- package/dist/util/colors.d.ts.map +1 -0
- package/dist/util/colors.js +5 -0
- package/dist/util/locale.d.ts +3 -0
- package/dist/util/locale.d.ts.map +1 -0
- package/dist/util/locale.js +10 -0
- package/dist/wba/client.d.ts +19 -0
- package/dist/wba/client.d.ts.map +1 -0
- package/dist/wba/client.js +31 -0
- package/dist/wba/index.d.ts +4 -0
- package/dist/wba/index.d.ts.map +1 -0
- package/dist/wba/index.js +2 -0
- package/dist/wba/routes/event.d.ts +32 -0
- package/dist/wba/routes/event.d.ts.map +1 -0
- package/dist/wba/routes/event.js +42 -0
- package/dist/wba/routes/index.d.ts +3 -0
- package/dist/wba/routes/index.d.ts.map +1 -0
- package/dist/wba/routes/index.js +2 -0
- package/dist/wba/routes/summary.d.ts +44 -0
- package/dist/wba/routes/summary.d.ts.map +1 -0
- package/dist/wba/routes/summary.js +146 -0
- package/dist/wba/server.d.ts +18 -0
- package/dist/wba/server.d.ts.map +1 -0
- package/dist/wba/server.js +29 -0
- package/dist/wba/types.d.ts +68 -0
- package/dist/wba/types.d.ts.map +1 -0
- package/dist/wba/types.js +6 -0
- package/package.json +223 -0
- package/src/blockchain/README.md +80 -0
- package/src/debug/README.md +94 -0
- package/src/e2e/README.md +105 -0
- package/src/i18n/README.md +143 -0
- package/src/shadcn_components/README.md +125 -0
- package/src/tasks/README.md +69 -0
- package/src/test-utils/README.md +638 -0
- package/src/util/README.md +18 -0
- package/src/wba/README.md +175 -0
package/dist/tasks/db.js
ADDED
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { spawn, spawnSync } from 'child_process';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { problem, success, info, warn } from '../util/colors.js';
|
|
7
|
+
import { createTranslator } from '../i18n/createTranslator.js';
|
|
8
|
+
import { detectLocale } from '../util/locale.js';
|
|
9
|
+
import commonCli from '../i18n/common-cli.js';
|
|
10
|
+
const dbT = createTranslator(detectLocale(), {
|
|
11
|
+
en: {
|
|
12
|
+
'db.create_success': 'Database "{{0}}" created successfully.',
|
|
13
|
+
'db.create_failed': 'Failed to create database "{{0}}": {{1}}',
|
|
14
|
+
'db.drop_success': 'Database "{{0}}" dropped successfully.',
|
|
15
|
+
'db.drop_failed': 'Failed to drop database "{{0}}": {{1}}',
|
|
16
|
+
'db.super.credentials_missing': 'PGUSER or PGPASSWORD not defined in environment (.env)',
|
|
17
|
+
'db.super.superuser_env_missing': 'PG_SUPERUSER and PG_SUPERUSER_PASSWORD must be set in .env or .env.test.\n Without superuser credentials, db:super:createuser cannot authenticate.\n Add to apps/.env.test:\n PG_SUPERUSER=postgres\n PG_SUPERUSER_PASSWORD=\n On OpenBSD/adJ the password is typically empty and authentication is via Unix socket + doas.',
|
|
18
|
+
'db.super.invalid_username': 'Username "{{0}}" contains invalid character "-". Use only letters, numbers, and underscores.',
|
|
19
|
+
'db.super.trying_mode': '[db:super:createuser] attempting mode={{0}}',
|
|
20
|
+
'db.super.role_exists': 'PostgreSQL role \'{{0}}\' already exists (continuing).',
|
|
21
|
+
'db.super.create_failed_mode': 'Creation failed in mode {{0}}: {{1}}',
|
|
22
|
+
'db.super.create_success': 'Superuser role \'{{0}}\' created.',
|
|
23
|
+
'db.super.password_failed_mode': 'Password change failed in mode {{0}}: {{1}}',
|
|
24
|
+
'db.super.password_success': 'Password for \'{{0}}\' set.',
|
|
25
|
+
'db.super.final_failure': 'Could not create superuser: {{0}}',
|
|
26
|
+
'db.console_starting': 'Opening console for database "{{0}}"...',
|
|
27
|
+
'db.console_querying': 'Executing query on database "{{0}}"...',
|
|
28
|
+
'db.console_closed': 'Console closed.',
|
|
29
|
+
'db.console_failed': 'Console failed: {{0}}',
|
|
30
|
+
'db.test_connection.start': 'Testing database connection...',
|
|
31
|
+
'db.test_connection.success': 'Database test successful.',
|
|
32
|
+
'db.test_connection.failed': 'Database test failed: {{0}}',
|
|
33
|
+
'db.structure.dump_start': 'Dumping structure to {{0}}...',
|
|
34
|
+
'db.structure.dump_success': 'Database structure dumped to {{0}}.',
|
|
35
|
+
'db.structure.dump_failed': 'Failed to dump structure: {{0}}: {{1}}',
|
|
36
|
+
'db.structure.load_not_found': 'Structure file not found: {{0}}',
|
|
37
|
+
'db.structure.load_start': 'Loading structure from {{0}}...',
|
|
38
|
+
'db.dropping_for_load': 'Dropping database "{{0}}" before loading structure...',
|
|
39
|
+
'db.creating_for_load': 'Creating database "{{0}}" before loading structure...',
|
|
40
|
+
'db.structure.load_success': 'Database structure loaded from {{0}}.',
|
|
41
|
+
'db.structure.load_failed': 'Failed to load structure: {{0}}: {{1}}',
|
|
42
|
+
'db.mig.make_start': 'Creating migration file {{0}}...',
|
|
43
|
+
'db.mig.make_success': 'Migration file created: {{0}}',
|
|
44
|
+
'db.mig.make_failed': 'Failed to create migration: {{0}}',
|
|
45
|
+
'db.migrate.start': 'Running migrations...',
|
|
46
|
+
'db.migrate.success': 'Migrations completed successfully.',
|
|
47
|
+
'db.migrate.failed': 'Migration failed: {{0}}',
|
|
48
|
+
'db.rollback.start': 'Rolling back last migration...',
|
|
49
|
+
'db.rollback.success': 'Migration rolled back successfully.',
|
|
50
|
+
'db.rollback.failed': 'Rollback failed: {{0}}',
|
|
51
|
+
'db.rollback.step': 'Rolling back {{0}} migration(s)...',
|
|
52
|
+
'db.rollback.step_success': '{{0}} migration(s) rolled back successfully.',
|
|
53
|
+
'db.dump.start': 'Backing up database to {{0}}...',
|
|
54
|
+
'db.dump.success': 'Database backed up to {{0}}.',
|
|
55
|
+
'db.dump.failed': 'Database backup failed: {{0}}: {{1}}',
|
|
56
|
+
'db.restore.start': 'Restoring database from {{0}}...',
|
|
57
|
+
'db.restore.success': 'Database restored from {{0}}.',
|
|
58
|
+
'db.restore.failed': 'Database restoration failed: {{0}}: {{1}}',
|
|
59
|
+
'db.restore.backup_not_found': 'Backup not found: {{0}}',
|
|
60
|
+
'db.restore.no_backups': 'No backups found in {{0}}.',
|
|
61
|
+
'db.restore.available': 'Available backups:',
|
|
62
|
+
'db.restore.list_item': ' {{0}} ({{1}})',
|
|
63
|
+
'db.types.gen_start': 'Generating types...',
|
|
64
|
+
'db.types.gen_success': 'Types generated.',
|
|
65
|
+
'db.types.gen_failed': 'Type generation failed: {{0}}',
|
|
66
|
+
},
|
|
67
|
+
es: {
|
|
68
|
+
'db.create_success': 'Base de datos "{{0}}" creada exitosamente.',
|
|
69
|
+
'db.create_failed': 'Falló la creación de la base de datos "{{0}}": {{1}}',
|
|
70
|
+
'db.drop_success': 'Base de datos "{{0}}" eliminada exitosamente.',
|
|
71
|
+
'db.drop_failed': 'Falló la eliminación de la base de datos "{{0}}": {{1}}',
|
|
72
|
+
'db.super.credentials_missing': 'PGUSER o PGPASSWORD no están definidos en el entorno (.env)',
|
|
73
|
+
'db.super.superuser_env_missing': 'PG_SUPERUSER y PG_SUPERUSER_PASSWORD deben estar definidos en .env o .env.test.\n Sin credenciales de superusuario, db:super:createuser no puede autenticarse.\n Agregue en apps/.env.test:\n PG_SUPERUSER=postgres\n PG_SUPERUSER_PASSWORD=\n En OpenBSD/adJ la contraseña típicamente es vacía y la autenticación es vía socket Unix + doas.',
|
|
74
|
+
'db.super.invalid_username': 'El nombre de usuario "{{0}}" contiene el carácter inválido "-". Usa solo letras, números y guiones bajos.',
|
|
75
|
+
'db.super.trying_mode': '[db:super:createuser] intentando modo={{0}}',
|
|
76
|
+
'db.super.role_exists': 'El rol de PostgreSQL \'{{0}}\' ya existe (continuando).',
|
|
77
|
+
'db.super.create_failed_mode': 'Falló la creación en modo {{0}}: {{1}}',
|
|
78
|
+
'db.super.create_success': 'Rol de superusuario \'{{0}}\' creado.',
|
|
79
|
+
'db.super.password_failed_mode': 'Falló el cambio de contraseña en modo {{0}}: {{1}}',
|
|
80
|
+
'db.super.password_success': 'Contraseña para \'{{0}}\' establecida.',
|
|
81
|
+
'db.super.final_failure': 'No se pudo crear el superusuario: {{0}}',
|
|
82
|
+
'db.console_starting': 'Abriendo consola para la base de datos "{{0}}"...',
|
|
83
|
+
'db.console_querying': 'Ejecutando consulta en la base de datos "{{0}}"...',
|
|
84
|
+
'db.console_closed': 'Consola cerrada.',
|
|
85
|
+
'db.console_failed': 'Falló la consola: {{0}}',
|
|
86
|
+
'db.test_connection.start': 'Probando la conexión a la base de datos...',
|
|
87
|
+
'db.test_connection.success': 'Prueba de base de datos exitosa.',
|
|
88
|
+
'db.test_connection.failed': 'Falló la prueba de la base de datos: {{0}}',
|
|
89
|
+
'db.structure.dump_start': 'Volcando estructura a {{0}}...',
|
|
90
|
+
'db.structure.dump_success': 'Estructura de la base de datos volcada a {{0}}.',
|
|
91
|
+
'db.structure.dump_failed': 'Falló el volcado de la estructura: {{0}}: {{1}}',
|
|
92
|
+
'db.structure.load_not_found': 'Archivo de estructura no encontrado: {{0}}',
|
|
93
|
+
'db.structure.load_start': 'Cargando estructura desde {{0}}...',
|
|
94
|
+
'db.dropping_for_load': 'Eliminando la base de datos "{{0}}" antes de cargar la estructura...',
|
|
95
|
+
'db.creating_for_load': 'Creando la base de datos "{{0}}" antes de cargar la estructura...',
|
|
96
|
+
'db.structure.load_success': 'Estructura de la base de datos cargada desde {{0}}.',
|
|
97
|
+
'db.structure.load_failed': 'Falló la carga de la estructura: {{0}}: {{1}}',
|
|
98
|
+
'db.mig.make_start': 'Creando archivo de migración {{0}}...',
|
|
99
|
+
'db.mig.make_success': 'Archivo de migración creado: {{0}}',
|
|
100
|
+
'db.mig.make_failed': 'Falló la creación de la migración: {{0}}',
|
|
101
|
+
'db.migrate.start': 'Ejecutando migraciones...',
|
|
102
|
+
'db.migrate.success': 'Migraciones completadas exitosamente.',
|
|
103
|
+
'db.migrate.failed': 'Falló la migración: {{0}}',
|
|
104
|
+
'db.rollback.start': 'Revirtiendo la última migración...',
|
|
105
|
+
'db.rollback.success': 'Migraciones revertidas exitosamente.',
|
|
106
|
+
'db.rollback.failed': 'Falló la reversión: {{0}}',
|
|
107
|
+
'db.rollback.step': 'Revirtiendo {{0}} migración(es)...',
|
|
108
|
+
'db.rollback.step_success': '{{0}} migración(es) revertidas exitosamente.',
|
|
109
|
+
'db.types.gen_start': 'Generando tipos...',
|
|
110
|
+
'db.types.gen_success': 'Tipos generados.',
|
|
111
|
+
'db.types.gen_failed': 'Falló la generación de tipos: {{0}}',
|
|
112
|
+
},
|
|
113
|
+
}, commonCli);
|
|
114
|
+
function pgConfig(withSuperUser = false) {
|
|
115
|
+
let h = {
|
|
116
|
+
database: process.env.PGDATABASE || 'msipn_dev',
|
|
117
|
+
host: process.env.PGHOST,
|
|
118
|
+
port: process.env.PGPORT,
|
|
119
|
+
password: withSuperUser ? process.env.PG_SUPERUSER_PASSWORD : process.env.PGPASSWORD,
|
|
120
|
+
user: withSuperUser ? process.env.PG_SUPERUSER : process.env.PGUSER
|
|
121
|
+
};
|
|
122
|
+
return h;
|
|
123
|
+
}
|
|
124
|
+
function buildDatabaseUrl(cfg) {
|
|
125
|
+
const user = cfg.user || process.env.PGUSER || 'postgres';
|
|
126
|
+
const password = cfg.password || process.env.PGPASSWORD || '';
|
|
127
|
+
const host = cfg.host || process.env.PGHOST || 'localhost';
|
|
128
|
+
const port = cfg.port || process.env.PGPORT || '5432';
|
|
129
|
+
const database = cfg.database || process.env.PGDATABASE;
|
|
130
|
+
if (!host || host.trim() === '') {
|
|
131
|
+
throw new Error('PGHOST vacío — configure PGHOST en .env.test (ej: /var/run/postgresql para socket Unix, o localhost para TCP)');
|
|
132
|
+
}
|
|
133
|
+
let url;
|
|
134
|
+
if (host.startsWith('/') || host.startsWith('./')) {
|
|
135
|
+
// Unix socket: encode the directory path as the host component.
|
|
136
|
+
// pg (node-postgres) interprets a path starting with / in the host as a socket dir.
|
|
137
|
+
url = `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${encodeURIComponent(host)}/${database}`;
|
|
138
|
+
}
|
|
139
|
+
else if (/^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$/.test(host) || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host)) {
|
|
140
|
+
// TCP/IP hostname or IPv4
|
|
141
|
+
url = `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
throw new Error(`PGHOST="${host}" no es un formato reconocido. Use ruta de socket Unix (/var/run/postgresql), nombre de host (localhost) o dirección IP.`);
|
|
145
|
+
}
|
|
146
|
+
// Validate the URL starts correctly (catch encoding bugs early)
|
|
147
|
+
if (!url.startsWith('postgresql://')) {
|
|
148
|
+
throw new Error(`DATABASE_URL mal formada: "${url.substring(0, 60)}..." — revise PGHOST, PGUSER, PGPASSWORD.`);
|
|
149
|
+
}
|
|
150
|
+
return url;
|
|
151
|
+
}
|
|
152
|
+
function buildEnvArgs(cfg) {
|
|
153
|
+
const env = {
|
|
154
|
+
PATH: process.env.PATH || '',
|
|
155
|
+
};
|
|
156
|
+
if (cfg.database)
|
|
157
|
+
env.PGDATABASE = cfg.database;
|
|
158
|
+
if (cfg.host)
|
|
159
|
+
env.PGHOST = cfg.host;
|
|
160
|
+
if (cfg.port)
|
|
161
|
+
env.PGPORT = cfg.port;
|
|
162
|
+
if (cfg.user)
|
|
163
|
+
env.PGUSER = cfg.user;
|
|
164
|
+
if (cfg.password)
|
|
165
|
+
env.PGPASSWORD = cfg.password;
|
|
166
|
+
// Add DATABASE_URL which is often expected by Kysely and other tools
|
|
167
|
+
env.DATABASE_URL = buildDatabaseUrl(cfg);
|
|
168
|
+
return env;
|
|
169
|
+
}
|
|
170
|
+
function psqlArgs(cfg, extra = []) {
|
|
171
|
+
const args = [];
|
|
172
|
+
if (cfg.host) {
|
|
173
|
+
args.push('-h', cfg.host);
|
|
174
|
+
}
|
|
175
|
+
if (cfg.port) {
|
|
176
|
+
args.push('-p', cfg.port);
|
|
177
|
+
}
|
|
178
|
+
if (cfg.user) {
|
|
179
|
+
args.push('-U', cfg.user);
|
|
180
|
+
}
|
|
181
|
+
return [...args, ...extra];
|
|
182
|
+
}
|
|
183
|
+
function getSpawnTimeoutMs() {
|
|
184
|
+
// PG_TIMEOUT (seconds) → milliseconds; default 30s if not set
|
|
185
|
+
const sec = process.env.PG_TIMEOUT ? parseInt(process.env.PG_TIMEOUT) : 30;
|
|
186
|
+
if (isNaN(sec) || sec <= 0)
|
|
187
|
+
return undefined;
|
|
188
|
+
return sec * 1000;
|
|
189
|
+
}
|
|
190
|
+
function run(cmd, args, env, quiet = false) {
|
|
191
|
+
const res = spawnSync(cmd, args, { stdio: ['inherit', 'inherit', 'pipe'], env, timeout: getSpawnTimeoutMs() });
|
|
192
|
+
if (res.status !== 0) {
|
|
193
|
+
const errorMsg = res.stderr ?
|
|
194
|
+
res.stderr.toString().trim() : `exit code ${res.status}`;
|
|
195
|
+
throw new Error(`${cmd} failed: ${errorMsg}`);
|
|
196
|
+
}
|
|
197
|
+
return res;
|
|
198
|
+
}
|
|
199
|
+
export async function runDbCreate() {
|
|
200
|
+
const t = dbT;
|
|
201
|
+
const cfg = pgConfig();
|
|
202
|
+
const env = buildEnvArgs(cfg);
|
|
203
|
+
try {
|
|
204
|
+
run('createdb', [cfg.database], env);
|
|
205
|
+
success(t('db.create_success', cfg.database));
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
problem(t('db.create_failed', cfg.database, e.message));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
export async function runDbDrop() {
|
|
212
|
+
const t = dbT;
|
|
213
|
+
const cfg = pgConfig();
|
|
214
|
+
const env = buildEnvArgs(cfg);
|
|
215
|
+
try {
|
|
216
|
+
run('dropdb', ['--if-exists', cfg.database], env);
|
|
217
|
+
success(t('db.drop_success', cfg.database));
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
problem(t('db.drop_failed', cfg.database, e.message));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export async function runDbSuperCreateUser() {
|
|
224
|
+
const t = dbT;
|
|
225
|
+
const cfg = pgConfig();
|
|
226
|
+
const defaultSystemUser = os.platform() === 'openbsd' ?
|
|
227
|
+
'_postgresql' : 'postgres';
|
|
228
|
+
const pgsystemuser = process.env.PGSYSTEMUSER || defaultSystemUser;
|
|
229
|
+
const user = cfg.user;
|
|
230
|
+
const pass = cfg.password;
|
|
231
|
+
const host = cfg.host;
|
|
232
|
+
if (!user || pass === undefined) {
|
|
233
|
+
problem(t('db.super.credentials_missing'));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// On OpenBSD/adJ, PG_SUPERUSER_PASSWORD is often empty (Unix socket auth).
|
|
237
|
+
// If both PG_SUPERUSER and PG_SUPERUSER_PASSWORD are undefined, the command
|
|
238
|
+
// will hang waiting for a password prompt. Fail early with a diagnostic.
|
|
239
|
+
if (process.env.PG_SUPERUSER === undefined || process.env.PG_SUPERUSER_PASSWORD === undefined) {
|
|
240
|
+
problem(t('db.super.superuser_env_missing'));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (user.includes('-')) {
|
|
244
|
+
problem(t('db.super.invalid_username', user));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const hasCmd = (c) => spawnSync('which', [c], { stdio: 'ignore' }).status === 0;
|
|
248
|
+
const psqlSuperArgs = psqlArgs(pgConfig(true), []).join(' ');
|
|
249
|
+
const psqlConnArgs = psqlArgs(cfg, []).join(' ');
|
|
250
|
+
// Use psql + SQL instead of createuser wrapper (createuser may not be in PATH
|
|
251
|
+
// when executed via sudo -u postgres due to env_reset on Linux).
|
|
252
|
+
const createCmdForOpenBSD = `psql ${psqlSuperArgs} -d postgres -c \\\"CREATE ROLE ${user} WITH LOGIN CREATEDB;\\\"`.trim();
|
|
253
|
+
const createCmdForShell = `psql ${psqlSuperArgs} -d postgres -c "CREATE ROLE \"${user}\" WITH LOGIN CREATEDB;"`;
|
|
254
|
+
// No modificar la siguiente línea fruto de larga experimentación.
|
|
255
|
+
const alterCmdForOpenBSD = `psql ${psqlSuperArgs} -d postgres -c \\\"ALTER USER ${user} WITH PASSWORD '${pass}';\\\"`.trim();
|
|
256
|
+
const alterCmdForShell = `psql ${psqlConnArgs} -d postgres -c "ALTER USER \"${user}\" WITH PASSWORD '${pass}';"`;
|
|
257
|
+
const pgSuperUser = process.env.PG_SUPERUSER;
|
|
258
|
+
const pgSuperUserPassword = process.env.PG_SUPERUSER_PASSWORD;
|
|
259
|
+
const runPriv = (mode, cmd) => {
|
|
260
|
+
const superUserEnv = {
|
|
261
|
+
...process.env,
|
|
262
|
+
PGUSER: pgSuperUser,
|
|
263
|
+
PGPASSWORD: pgSuperUserPassword
|
|
264
|
+
};
|
|
265
|
+
switch (mode) {
|
|
266
|
+
case 'openbsd_su':
|
|
267
|
+
const finalCmd = `"${cmd}"`;
|
|
268
|
+
const spawnArgs = ['su', '-', '_postgresql', '-c', finalCmd];
|
|
269
|
+
const ret = spawnSync('/usr/bin/doas', spawnArgs, {
|
|
270
|
+
encoding: 'utf-8',
|
|
271
|
+
env: process.env,
|
|
272
|
+
shell: '/bin/sh'
|
|
273
|
+
});
|
|
274
|
+
return ret;
|
|
275
|
+
case 'superuser_env':
|
|
276
|
+
return spawnSync('sh', ['-c', cmd], { encoding: 'utf-8', env: superUserEnv });
|
|
277
|
+
case 'root-su':
|
|
278
|
+
return spawnSync('su', ['-', pgsystemuser, '-c', cmd], { encoding: 'utf-8' });
|
|
279
|
+
case 'doas':
|
|
280
|
+
return spawnSync('doas', ['-u', pgsystemuser, 'sh', '-c', cmd], { encoding: 'utf-8' });
|
|
281
|
+
case 'sudo':
|
|
282
|
+
return spawnSync('sudo', ['-u', pgsystemuser, 'sh', '-c', cmd], { encoding: 'utf-8' });
|
|
283
|
+
default: // 'direct'
|
|
284
|
+
return spawnSync('sh', ['-c', cmd], { encoding: 'utf-8' });
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const modes = [];
|
|
288
|
+
if (process.platform === 'openbsd') {
|
|
289
|
+
modes.push('openbsd_su');
|
|
290
|
+
}
|
|
291
|
+
if (pgSuperUser !== undefined && pgSuperUserPassword !== undefined) {
|
|
292
|
+
modes.push('superuser_env');
|
|
293
|
+
}
|
|
294
|
+
modes.push('direct');
|
|
295
|
+
if (!process.env.CI) {
|
|
296
|
+
if (process.getuid && process.getuid() === 0) {
|
|
297
|
+
modes.push('root-su');
|
|
298
|
+
}
|
|
299
|
+
if (hasCmd('doas')) {
|
|
300
|
+
modes.push('doas');
|
|
301
|
+
}
|
|
302
|
+
if (hasCmd('sudo')) {
|
|
303
|
+
modes.push('sudo');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
let opSuccess = false;
|
|
307
|
+
let lastErr;
|
|
308
|
+
for (const mode of modes) {
|
|
309
|
+
info(t('db.super.trying_mode', mode));
|
|
310
|
+
const createCmd = (mode === 'openbsd_su') ? createCmdForOpenBSD : createCmdForShell;
|
|
311
|
+
let res = runPriv(mode, createCmd);
|
|
312
|
+
if (res.status !== 0) {
|
|
313
|
+
const stderr = (res.stderr || '').toString();
|
|
314
|
+
if (/already exists/i.test(stderr)) {
|
|
315
|
+
info(t('db.super.role_exists', user));
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
lastErr = stderr || `Exit code ${res.status}`;
|
|
319
|
+
warn(t('db.super.create_failed_mode', mode, lastErr));
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
success(t('db.super.create_success', user));
|
|
325
|
+
}
|
|
326
|
+
const alterCmd = (mode === 'openbsd_su') ? alterCmdForOpenBSD : alterCmdForShell;
|
|
327
|
+
res = runPriv(mode, alterCmd);
|
|
328
|
+
if (res.status !== 0) {
|
|
329
|
+
const stderr = (res.stderr || '').toString();
|
|
330
|
+
lastErr = stderr || `Exit code ${res.status}`;
|
|
331
|
+
problem(t('db.super.password_failed_mode', mode, lastErr));
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
success(t('db.super.password_success', user));
|
|
336
|
+
opSuccess = true;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (!opSuccess) {
|
|
341
|
+
problem(t('db.super.final_failure', lastErr || 'unknown'));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
export async function runDbConsole(query) {
|
|
345
|
+
const t = dbT;
|
|
346
|
+
const cfg = pgConfig();
|
|
347
|
+
if (query) {
|
|
348
|
+
info(t('db.console_querying', cfg.database));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
info(t('db.console_starting', cfg.database));
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const env = {
|
|
355
|
+
...process.env,
|
|
356
|
+
...buildEnvArgs(cfg)
|
|
357
|
+
};
|
|
358
|
+
// Configurar variables de entorno para desactivar pager y asegurar visibilidad
|
|
359
|
+
env.PAGER = '';
|
|
360
|
+
env.PSGQL_PAGER = '';
|
|
361
|
+
env.PSQL_PAGER = '';
|
|
362
|
+
// Asegurar variable TERM para terminal interactivo
|
|
363
|
+
if (!env.TERM) {
|
|
364
|
+
env.TERM = 'xterm';
|
|
365
|
+
}
|
|
366
|
+
const psqlArgs = [];
|
|
367
|
+
if (query) {
|
|
368
|
+
psqlArgs.push('-c', query);
|
|
369
|
+
}
|
|
370
|
+
// Usar spawn asíncrono para modo interactivo real
|
|
371
|
+
const child = spawn('psql', psqlArgs, {
|
|
372
|
+
stdio: query ? ['inherit', 'inherit', 'inherit'] : [0, 1, 2],
|
|
373
|
+
env: env
|
|
374
|
+
});
|
|
375
|
+
// Esperar a que psql termine
|
|
376
|
+
return new Promise((resolve) => {
|
|
377
|
+
child.on('close', (code, signal) => {
|
|
378
|
+
if (code === 0) {
|
|
379
|
+
success(t('db.console_closed'));
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
problem(t('db.console_failed', signal ? `Señal: ${signal}` : `Código: ${code}`));
|
|
383
|
+
}
|
|
384
|
+
resolve();
|
|
385
|
+
});
|
|
386
|
+
child.on('error', (error) => {
|
|
387
|
+
problem(t('db.console_failed', error.message));
|
|
388
|
+
resolve();
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
catch (e) {
|
|
393
|
+
problem(t('db.console_failed', e.message));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
export async function runDbTestConnection() {
|
|
397
|
+
const t = dbT;
|
|
398
|
+
info(t('db.test_connection.start'));
|
|
399
|
+
try {
|
|
400
|
+
const cfg = pgConfig();
|
|
401
|
+
const env = buildEnvArgs(cfg);
|
|
402
|
+
const res = spawnSync('psql', ['-c', 'SELECT current_database();'], { encoding: 'utf-8', env: env });
|
|
403
|
+
if (res.error) {
|
|
404
|
+
throw new Error(res.error.message);
|
|
405
|
+
}
|
|
406
|
+
if (res.status !== 0) {
|
|
407
|
+
throw new Error(res.stderr);
|
|
408
|
+
}
|
|
409
|
+
success(t('db.test_connection.success'));
|
|
410
|
+
console.log(res.stdout);
|
|
411
|
+
}
|
|
412
|
+
catch (e) {
|
|
413
|
+
problem(t('db.test_connection.failed', e.message));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async function runPostMigrationTasks() {
|
|
417
|
+
await runDbStructureDump();
|
|
418
|
+
await runDbTypesGen();
|
|
419
|
+
// Execute post-migrate hook if configured via M_POST_MIGRATE env var.
|
|
420
|
+
// Useful for syncing sibling project schemas, e.g.:
|
|
421
|
+
// M_POST_MIGRATE="cd ../../sivel2 && bin/rails db:structure:dump"
|
|
422
|
+
const postMigrate = process.env.M_POST_MIGRATE;
|
|
423
|
+
if (postMigrate) {
|
|
424
|
+
info(`Post-migrate hook: ${postMigrate}`);
|
|
425
|
+
const res = spawnSync('sh', ['-c', postMigrate], { stdio: 'inherit', env: process.env });
|
|
426
|
+
if (res.status !== 0) {
|
|
427
|
+
problem(`Post-migrate hook failed with exit code ${res.status}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
export async function runDbStructureDump() {
|
|
432
|
+
const t = dbT;
|
|
433
|
+
const cfg = pgConfig();
|
|
434
|
+
const env = buildEnvArgs(cfg);
|
|
435
|
+
const structureFile = path.join(process.cwd(), 'db', 'structure.sql');
|
|
436
|
+
const structureDir = path.dirname(structureFile);
|
|
437
|
+
info(t('db.structure.dump_start', structureFile));
|
|
438
|
+
try {
|
|
439
|
+
if (!fs.existsSync(structureDir)) {
|
|
440
|
+
fs.mkdirSync(structureDir, { recursive: true });
|
|
441
|
+
}
|
|
442
|
+
const args = [
|
|
443
|
+
'--schema-only',
|
|
444
|
+
'--no-owner',
|
|
445
|
+
'--no-privileges',
|
|
446
|
+
'--no-comments',
|
|
447
|
+
'-d',
|
|
448
|
+
cfg.database,
|
|
449
|
+
];
|
|
450
|
+
const result = spawnSync('pg_dump', args, { stdio: 'pipe', env, timeout: getSpawnTimeoutMs() });
|
|
451
|
+
if (result.status !== 0) {
|
|
452
|
+
const errorMsg = result.stderr ?
|
|
453
|
+
result.stderr.toString().trim() : `exit code ${result.status}`;
|
|
454
|
+
throw new Error(`pg_dump failed: ${errorMsg}`);
|
|
455
|
+
}
|
|
456
|
+
// Normalize: remove session-specific hashes that change between dumps
|
|
457
|
+
const normalized = result.stdout?.toString()
|
|
458
|
+
.replace(/^\\restrict \S+$/gm, '\\restrict')
|
|
459
|
+
.replace(/^\\unrestrict \S+$/gm, '\\unrestrict');
|
|
460
|
+
fs.writeFileSync(structureFile, normalized);
|
|
461
|
+
success(t('db.structure.dump_success', structureFile));
|
|
462
|
+
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
problem(t('db.structure.dump_failed', structureFile, e.message));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
export async function runDbStructureLoad() {
|
|
468
|
+
const t = dbT;
|
|
469
|
+
const cfg = pgConfig();
|
|
470
|
+
const env = buildEnvArgs(cfg);
|
|
471
|
+
const structureFile = path.join(process.cwd(), 'db', 'structure.sql');
|
|
472
|
+
if (!fs.existsSync(structureFile)) {
|
|
473
|
+
problem(t('db.structure.load_not_found', structureFile));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
info(t('db.structure.load_start', structureFile));
|
|
477
|
+
try {
|
|
478
|
+
info(t('db.dropping_for_load', cfg.database));
|
|
479
|
+
run('dropdb', ['--if-exists', cfg.database], env, true /* quiet */);
|
|
480
|
+
info(t('db.creating_for_load', cfg.database));
|
|
481
|
+
run('createdb', [cfg.database], env, true /* quiet */);
|
|
482
|
+
const args = [
|
|
483
|
+
'-d',
|
|
484
|
+
cfg.database,
|
|
485
|
+
'-f',
|
|
486
|
+
structureFile,
|
|
487
|
+
];
|
|
488
|
+
run('psql', args, env, true /* quiet */);
|
|
489
|
+
success(t('db.structure.load_success', structureFile));
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
problem(t('db.structure.load_failed', structureFile, e.message));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
export async function runDbTypesGen() {
|
|
496
|
+
const t = dbT;
|
|
497
|
+
info(t('db.types.gen_start'));
|
|
498
|
+
try {
|
|
499
|
+
const cfg = pgConfig();
|
|
500
|
+
const env = buildEnvArgs(cfg);
|
|
501
|
+
const url = buildDatabaseUrl(cfg);
|
|
502
|
+
const outFile = path.join(process.cwd(), 'db', 'db.d.ts');
|
|
503
|
+
const args = ['--url', url, '--out-file', outFile];
|
|
504
|
+
run(path.join('node_modules', '.bin', 'kysely-codegen'), args, env);
|
|
505
|
+
success(t('db.types.gen_success'));
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
problem(t('db.types.gen_failed', e.message));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
export async function runDbMigrate() {
|
|
512
|
+
const t = dbT;
|
|
513
|
+
info(t('db.migrate.start'));
|
|
514
|
+
try {
|
|
515
|
+
const cfg = pgConfig();
|
|
516
|
+
const env = buildEnvArgs(cfg);
|
|
517
|
+
const dbUrl = buildDatabaseUrl(cfg);
|
|
518
|
+
console.error('[db:migrate] PGHOST=' + (env.PGHOST || '(default)') + ' URL=' + dbUrl.substring(0, 100) + ' timeout=' + (getSpawnTimeoutMs() || 'none') + 'ms');
|
|
519
|
+
const bin = path.join('node_modules', '.bin', 'kysely');
|
|
520
|
+
// Run one migration at a time so each is in its own transaction.
|
|
521
|
+
// If one fails, previously-applied migrations stay committed.
|
|
522
|
+
let applied = 0;
|
|
523
|
+
const migrated = [];
|
|
524
|
+
while (true) {
|
|
525
|
+
const result = spawnSync(bin, ['migrate', 'up'], { stdio: 'pipe', env, timeout: getSpawnTimeoutMs() });
|
|
526
|
+
const out = (result.stdout ? result.stdout.toString() : '') + (result.stderr ? result.stderr.toString() : '');
|
|
527
|
+
// Show output to user (but skip the "no new migrations" message since we break below)
|
|
528
|
+
const isSkipped = out.includes('no pending') || out.includes('no unmigrated') || out.includes('Migration skipped');
|
|
529
|
+
if (!isSkipped && out)
|
|
530
|
+
process.stdout.write(out);
|
|
531
|
+
if (result.status !== 0) {
|
|
532
|
+
if (isSkipped)
|
|
533
|
+
break;
|
|
534
|
+
if (out)
|
|
535
|
+
console.error(out);
|
|
536
|
+
throw new Error('Migration failed');
|
|
537
|
+
}
|
|
538
|
+
if (isSkipped)
|
|
539
|
+
break;
|
|
540
|
+
// Extract migration name from output like "✓ Migration 20260722_name"
|
|
541
|
+
const match = out.match(/Migration\s+(\S+)/);
|
|
542
|
+
if (match)
|
|
543
|
+
migrated.push(match[1]);
|
|
544
|
+
applied++;
|
|
545
|
+
}
|
|
546
|
+
if (applied > 0) {
|
|
547
|
+
for (const name of migrated)
|
|
548
|
+
console.log(' \u2713 ' + name);
|
|
549
|
+
console.log(' ' + applied + ' migration(s) applied');
|
|
550
|
+
}
|
|
551
|
+
else
|
|
552
|
+
info(t('db.migrate.none') || 'No pending migrations');
|
|
553
|
+
success(t('db.migrate.success'));
|
|
554
|
+
await runPostMigrationTasks();
|
|
555
|
+
}
|
|
556
|
+
catch (e) {
|
|
557
|
+
problem(t('db.migrate.failed', e.message));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
export async function runDbMigMake(name) {
|
|
561
|
+
const t = dbT;
|
|
562
|
+
const migrationsDir = path.join(process.cwd(), 'db', 'migrations');
|
|
563
|
+
const tstamp = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '');
|
|
564
|
+
const filename = `${tstamp}_${name}.ts`;
|
|
565
|
+
const filepath = path.join(migrationsDir, filename);
|
|
566
|
+
const template = `import { Kysely } from 'kysely';
|
|
567
|
+
|
|
568
|
+
export async function up(db: Kysely<any>): Promise<void> {
|
|
569
|
+
// Migration code
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export async function down(db: Kysely<any>): Promise<void> {
|
|
573
|
+
// Rollback code
|
|
574
|
+
}
|
|
575
|
+
`;
|
|
576
|
+
info(t('db.mig.make_start', filename));
|
|
577
|
+
try {
|
|
578
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
579
|
+
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
580
|
+
}
|
|
581
|
+
fs.writeFileSync(filepath, template);
|
|
582
|
+
success(t('db.mig.make_success', filename));
|
|
583
|
+
}
|
|
584
|
+
catch (e) {
|
|
585
|
+
problem(t('db.mig.make_failed', e.message));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
export async function runDbRollback() {
|
|
589
|
+
const t = dbT;
|
|
590
|
+
// Parse --step N from CLI args (e.g., bin/m db:rollback --step 2)
|
|
591
|
+
const stepIdx = process.argv.indexOf('--step');
|
|
592
|
+
const steps = stepIdx !== -1 && stepIdx + 1 < process.argv.length ? Math.max(1, parseInt(process.argv[stepIdx + 1]) || 1) : 1;
|
|
593
|
+
info(t(steps > 1 ? 'db.rollback.step' : 'db.rollback.start', String(steps)));
|
|
594
|
+
try {
|
|
595
|
+
const cfg = pgConfig();
|
|
596
|
+
const env = buildEnvArgs(cfg);
|
|
597
|
+
const dbUrl = buildDatabaseUrl(cfg);
|
|
598
|
+
console.error('[db:rollback] PGHOST=' + (env.PGHOST || '(default)') + ' URL=' + dbUrl.substring(0, 100) + ' timeout=' + (getSpawnTimeoutMs() || 'none') + 'ms');
|
|
599
|
+
for (let i = 0; i < steps; i++) {
|
|
600
|
+
run(path.join('node_modules', '.bin', 'kysely'), ['migrate', 'down'], env);
|
|
601
|
+
}
|
|
602
|
+
if (steps === 1) {
|
|
603
|
+
success('Migration rolled back successfully');
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
success(t('db.rollback.step_success', String(steps)));
|
|
607
|
+
}
|
|
608
|
+
await runPostMigrationTasks();
|
|
609
|
+
}
|
|
610
|
+
catch (e) {
|
|
611
|
+
problem(t('db.rollback.failed', e.message));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
export async function runDbDump(name) {
|
|
615
|
+
const t = dbT;
|
|
616
|
+
const cfg = pgConfig();
|
|
617
|
+
const env = buildEnvArgs(cfg);
|
|
618
|
+
const backupsDir = path.join(process.cwd(), 'db', 'backups');
|
|
619
|
+
const dumpName = name || `${cfg.database}-${new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '')}`;
|
|
620
|
+
const dumpDir = path.join(backupsDir, dumpName);
|
|
621
|
+
info(t('db.dump.start', dumpDir));
|
|
622
|
+
try {
|
|
623
|
+
if (!fs.existsSync(backupsDir)) {
|
|
624
|
+
fs.mkdirSync(backupsDir, { recursive: true });
|
|
625
|
+
}
|
|
626
|
+
if (fs.existsSync(dumpDir)) {
|
|
627
|
+
fs.rmSync(dumpDir, { recursive: true });
|
|
628
|
+
}
|
|
629
|
+
const args = [
|
|
630
|
+
'-Fd', // custom directory format
|
|
631
|
+
'--no-owner',
|
|
632
|
+
'--no-privileges',
|
|
633
|
+
'-f', dumpDir,
|
|
634
|
+
'-d', cfg.database,
|
|
635
|
+
];
|
|
636
|
+
run('pg_dump', args, env, true /* quiet */);
|
|
637
|
+
success(t('db.dump.success', dumpDir));
|
|
638
|
+
}
|
|
639
|
+
catch (e) {
|
|
640
|
+
problem(t('db.dump.failed', dumpDir, e.message));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
export async function runDbRestore(name) {
|
|
644
|
+
const t = dbT;
|
|
645
|
+
const cfg = pgConfig();
|
|
646
|
+
const env = buildEnvArgs(cfg);
|
|
647
|
+
const backupsDir = path.join(process.cwd(), 'db', 'backups');
|
|
648
|
+
if (!name) {
|
|
649
|
+
// List available backups
|
|
650
|
+
if (!fs.existsSync(backupsDir)) {
|
|
651
|
+
info(t('db.restore.no_backups', backupsDir));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const entries = fs.readdirSync(backupsDir, { withFileTypes: true })
|
|
655
|
+
.filter(e => e.isDirectory())
|
|
656
|
+
.map(e => ({ name: e.name, mtime: fs.statSync(path.join(backupsDir, e.name)).mtime }))
|
|
657
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
658
|
+
if (entries.length === 0) {
|
|
659
|
+
info(t('db.restore.no_backups', backupsDir));
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
info(t('db.restore.available'));
|
|
663
|
+
for (const e of entries) {
|
|
664
|
+
console.log(t('db.restore.list_item', e.name, e.mtime.toISOString().slice(0, 19).replace('T', ' ')));
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const dumpDir = path.join(backupsDir, name);
|
|
669
|
+
if (!fs.existsSync(dumpDir)) {
|
|
670
|
+
problem(t('db.restore.backup_not_found', dumpDir));
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
info(t('db.restore.start', dumpDir));
|
|
674
|
+
try {
|
|
675
|
+
info(t('db.dropping_for_load', cfg.database));
|
|
676
|
+
run('dropdb', ['--if-exists', cfg.database], env, true /* quiet */);
|
|
677
|
+
info(t('db.creating_for_load', cfg.database));
|
|
678
|
+
run('createdb', [cfg.database], env, true /* quiet */);
|
|
679
|
+
const args = [
|
|
680
|
+
'--no-owner',
|
|
681
|
+
'-d', cfg.database,
|
|
682
|
+
dumpDir,
|
|
683
|
+
];
|
|
684
|
+
run('pg_restore', args, env, true /* quiet */);
|
|
685
|
+
success(t('db.restore.success', dumpDir));
|
|
686
|
+
}
|
|
687
|
+
catch (e) {
|
|
688
|
+
problem(t('db.restore.failed', dumpDir, e.message));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
export async function runDbSeed() { info('Command db:seed not implemented yet.'); }
|
|
692
|
+
/**
|
|
693
|
+
* db:check — verify expected DB schemas for installed modules.
|
|
694
|
+
*/
|
|
695
|
+
export async function runDbCheck() {
|
|
696
|
+
const { Kysely, PostgresDialect } = await import('kysely');
|
|
697
|
+
const { createRequire } = await import('node:module');
|
|
698
|
+
const { Pool } = createRequire(import.meta.url)('pg');
|
|
699
|
+
const db = new Kysely({
|
|
700
|
+
dialect: new PostgresDialect({
|
|
701
|
+
pool: new Pool({
|
|
702
|
+
host: process.env.PGHOST,
|
|
703
|
+
database: process.env.PGDATABASE,
|
|
704
|
+
user: process.env.PGUSER,
|
|
705
|
+
password: process.env.PGPASSWORD,
|
|
706
|
+
}),
|
|
707
|
+
}),
|
|
708
|
+
});
|
|
709
|
+
const modules = [
|
|
710
|
+
{
|
|
711
|
+
name: 'WBA',
|
|
712
|
+
tables: [
|
|
713
|
+
{ name: 'web_event', verify: async (d) => {
|
|
714
|
+
const { verifyWebEventSchema } = await import('../wba/routes/summary.js');
|
|
715
|
+
return verifyWebEventSchema(d);
|
|
716
|
+
} },
|
|
717
|
+
{ name: 'transaction', verify: async (d) => {
|
|
718
|
+
const { verifyTransactionSchema } = await import('../wba/routes/summary.js');
|
|
719
|
+
try {
|
|
720
|
+
await verifyTransactionSchema(d);
|
|
721
|
+
return { ok: true };
|
|
722
|
+
}
|
|
723
|
+
catch (e) {
|
|
724
|
+
return { ok: false, error: e.message };
|
|
725
|
+
}
|
|
726
|
+
} },
|
|
727
|
+
],
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: 'Credentials',
|
|
731
|
+
tables: [
|
|
732
|
+
{ name: 'credential_metadata', verify: async (_d) => {
|
|
733
|
+
return true; // schema verification provided by @pasosdejesus/mpdj plugin
|
|
734
|
+
} },
|
|
735
|
+
{ name: 'credential_emission', verify: async (d) => {
|
|
736
|
+
try {
|
|
737
|
+
const { sql } = await import('kysely');
|
|
738
|
+
await sql `SELECT 1 FROM credential_emission LIMIT 0`.execute(d);
|
|
739
|
+
return { ok: true };
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
} },
|
|
745
|
+
],
|
|
746
|
+
},
|
|
747
|
+
];
|
|
748
|
+
let errors = 0;
|
|
749
|
+
let active = 0;
|
|
750
|
+
let available = 0;
|
|
751
|
+
for (const mod of modules) {
|
|
752
|
+
console.log(`\n${mod.name}:`);
|
|
753
|
+
let modActive = false;
|
|
754
|
+
for (const t of mod.tables) {
|
|
755
|
+
try {
|
|
756
|
+
const result = await t.verify(db);
|
|
757
|
+
if (result === null) {
|
|
758
|
+
console.log(` ℹ️ ${t.name.padEnd(24)} — not installed (try eng:${mod.name.toLowerCase()})`);
|
|
759
|
+
}
|
|
760
|
+
else if (result.ok === false) {
|
|
761
|
+
console.log(` ⚠️ ${t.name.padEnd(24)} — ${result.error}`);
|
|
762
|
+
errors++;
|
|
763
|
+
modActive = true;
|
|
764
|
+
}
|
|
765
|
+
else if (result.missing && result.missing.length > 0) {
|
|
766
|
+
console.log(` ⚠️ ${t.name.padEnd(24)} — missing columns: ${result.missing.join(', ')}`);
|
|
767
|
+
errors++;
|
|
768
|
+
modActive = true;
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
const cols = result.columns || '?';
|
|
772
|
+
console.log(` ✅ ${t.name.padEnd(24)} — ${cols} columns`);
|
|
773
|
+
modActive = true;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
console.log(` ℹ️ ${t.name.padEnd(24)} — not installed (try eng:${mod.name.toLowerCase()})`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (modActive)
|
|
781
|
+
active++;
|
|
782
|
+
else
|
|
783
|
+
available++;
|
|
784
|
+
}
|
|
785
|
+
console.log(`\n${active} module(s) active, ${available} available.`);
|
|
786
|
+
if (errors > 0) {
|
|
787
|
+
console.log(`${errors} error(s) found.`);
|
|
788
|
+
process.exit(1);
|
|
789
|
+
}
|
|
790
|
+
await db.destroy();
|
|
791
|
+
}
|