@mostajs/orm-cli 0.4.2 → 0.4.5

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.
@@ -30,11 +30,13 @@ const argv = process.argv.slice(2);
30
30
  const flag = (name) => argv.includes(`--${name}`);
31
31
  const val = (name) => { const i = argv.indexOf(`--${name}`); return i >= 0 ? argv[i + 1] : null; };
32
32
 
33
- const APPLY = flag('apply');
34
- const RESTORE = flag('restore');
35
- const ONE_FILE = val('file');
36
- const ROOT = resolve(val('project') ?? process.cwd());
37
- const QUIET = flag('quiet');
33
+ const APPLY = flag('apply');
34
+ const RESTORE = flag('restore');
35
+ const RESTORE_SEEDS = flag('restore-seeds'); // NEW : restore only seed-like .prisma.bak files
36
+ const ONE_FILE = val('file');
37
+ const ROOT = resolve(val('project') ?? process.cwd());
38
+ const QUIET = flag('quiet');
39
+ const REWRITE_SEEDS = flag('rewrite-seeds'); // NEW : force rewriting seed scripts (legacy behavior)
38
40
 
39
41
  const log = (...a) => { if (!QUIET) console.log(...a); };
40
42
  const c = { cyan: s => `\x1b[36m${s}\x1b[0m`, yellow: s => `\x1b[33m${s}\x1b[0m`, green: s => `\x1b[32m${s}\x1b[0m`, red: s => `\x1b[31m${s}\x1b[0m`, bold: s => `\x1b[1m${s}\x1b[0m`, dim: s => `\x1b[2m${s}\x1b[0m` };
@@ -43,6 +45,17 @@ const c = { cyan: s => `\x1b[36m${s}\x1b[0m`, yellow: s => `\x1b[33m${s}\x1b[0
43
45
  const SKIP_DIRS = new Set(['node_modules', '.next', '.svelte-kit', 'dist', 'build', '.turbo', '.vercel', '.cache', 'coverage', '.git', '.vscode', '.idea']);
44
46
  const EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.js', '.jsx', '.mjs']);
45
47
 
48
+ // Paths that look like seed SCRIPTS (not db modules). These must NOT be
49
+ // rewritten — their `new PrismaClient()` is a local instance scoped to the
50
+ // script, not a reusable export. Rewriting them turns the script into a
51
+ // 2-line stub and destroys the seed logic.
52
+ //
53
+ // Heuristic : last path segment starts with `seed` or `seeder`, OR file sits
54
+ // inside a `seeds/` or `fixtures/` directory, OR the path is Prisma's
55
+ // canonical `prisma/seed.(ts|js)`.
56
+ const RX_SEED_PATH = /(^|[\\\/])(seeds?|fixtures|seeders?)[\\\/]|(^|[\\\/])(seed[^\\\/]*|seeder[^\\\/]*)\.(ts|tsx|mts|js|jsx|mjs)$|(^|[\\\/])prisma[\\\/]seed\.(ts|tsx|mts|js|jsx|mjs)$/i;
57
+ function looksLikeSeedScript(relPath) { return RX_SEED_PATH.test(relPath); }
58
+
46
59
  function* walk(dir) {
47
60
  let entries;
48
61
  try { entries = readdirSync(dir); } catch { return; }
@@ -52,7 +65,8 @@ function* walk(dir) {
52
65
  let st;
53
66
  try { st = statSync(p); } catch { continue; }
54
67
  if (st.isDirectory()) yield* walk(p);
55
- else if (EXTENSIONS.has(extname(name))) yield p;
68
+ // Source files we might rewrite, OR .prisma.bak we might restore.
69
+ else if (EXTENSIONS.has(extname(name)) || name.endsWith('.prisma.bak')) yield p;
56
70
  }
57
71
  }
58
72
 
@@ -113,16 +127,18 @@ function buildReplacement(shape) {
113
127
  }
114
128
 
115
129
  // ---------- Restore ----------
116
- function restoreBackups() {
130
+ function restoreBackups({ onlySeeds = false } = {}) {
117
131
  const restored = [];
118
132
  for (const p of walk(ROOT)) {
119
133
  if (!p.endsWith('.prisma.bak')) continue;
120
134
  const original = p.slice(0, -'.prisma.bak'.length);
135
+ const rel = relative(ROOT, original);
136
+ if (onlySeeds && !looksLikeSeedScript(rel)) continue;
121
137
  if (APPLY) {
122
138
  renameSync(p, original);
123
- log(` ${c.green('✓')} restored ${c.dim(relative(ROOT, original))}`);
139
+ log(` ${c.green('✓')} restored ${c.dim(rel)}`);
124
140
  } else {
125
- log(` ${c.yellow('•')} would restore ${c.dim(relative(ROOT, original))}`);
141
+ log(` ${c.yellow('•')} would restore ${c.dim(rel)}`);
126
142
  }
127
143
  restored.push(original);
128
144
  }
@@ -130,6 +146,12 @@ function restoreBackups() {
130
146
  }
131
147
 
132
148
  // ---------- Main ----------
149
+ if (RESTORE_SEEDS) {
150
+ log(c.bold('▶ Restoring seed-looking .prisma.bak files only' + (APPLY ? '' : c.yellow(' (dry-run, use --apply)'))));
151
+ const r = restoreBackups({ onlySeeds: true });
152
+ log(`\n${r.length} seed file(s) ${APPLY ? 'restored' : 'would be restored'}`);
153
+ process.exit(0);
154
+ }
133
155
  if (RESTORE) {
134
156
  log(c.bold('▶ Restoring .prisma.bak files' + (APPLY ? '' : c.yellow(' (dry-run, use --apply)'))));
135
157
  const r = restoreBackups();
@@ -141,6 +163,7 @@ log(c.bold(`▶ mostajs install-bridge — scanning ${c.cyan(ROOT)}`));
141
163
  log('');
142
164
 
143
165
  const candidates = [];
166
+ const skippedSeeds = [];
144
167
  const iter = ONE_FILE ? [resolve(ROOT, ONE_FILE)] : walk(ROOT);
145
168
  for (const p of iter) {
146
169
  let src;
@@ -151,12 +174,18 @@ for (const p of iter) {
151
174
  continue;
152
175
  }
153
176
  if (!RX_NEW.test(src)) continue; // imports only = not an instantiation site
177
+ const rel = relative(ROOT, p);
178
+ if (looksLikeSeedScript(rel) && !REWRITE_SEEDS) {
179
+ skippedSeeds.push(rel);
180
+ log(` ${c.yellow('⚠ skip seed script')} ${c.dim(rel)}`);
181
+ continue;
182
+ }
154
183
  const shape = detectExportShape(src);
155
184
  if (!shape) {
156
- log(` ${c.yellow('?')} ${relative(ROOT, p)} — PrismaClient detected but export shape unknown, skipping`);
185
+ log(` ${c.yellow('?')} ${rel} — PrismaClient detected but export shape unknown, skipping`);
157
186
  continue;
158
187
  }
159
- candidates.push({ path: p, rel: relative(ROOT, p), shape, src });
188
+ candidates.push({ path: p, rel, shape, src });
160
189
  }
161
190
 
162
191
  if (candidates.length === 0) {
@@ -171,6 +200,13 @@ log(c.bold(`Found ${candidates.length} PrismaClient instantiation site(s):`));
171
200
  for (const ca of candidates) {
172
201
  log(` ${c.green('→')} ${ca.rel} ${c.dim(`(${ca.shape.kind}${ca.shape.name ? ' ' + ca.shape.name : ''})`)}`);
173
202
  }
203
+ if (skippedSeeds.length) {
204
+ log('');
205
+ log(c.yellow(`${skippedSeeds.length} seed script(s) were skipped (preserved as-is).`));
206
+ log(c.dim(' These files keep their original content. To run them with the bridge,'));
207
+ log(c.dim(' either: (a) re-link @prisma/client, (b) rewrite them with --rewrite-seeds,'));
208
+ log(c.dim(' (c) use the mostajs seed system (menu S) with JSON fixtures.'));
209
+ }
174
210
  log('');
175
211
 
176
212
  if (!APPLY) {
@@ -201,4 +237,5 @@ log(` 2. ${c.cyan('npx @mostajs/orm-cli')} → menu 1 (Convert Prisma → ent
201
237
  log(` 3. Set ${c.cyan('DB_DIALECT')} + ${c.cyan('SGBD_URI')} in .env (or: menu 2 → i to import)`);
202
238
  log(` 4. ${c.cyan('npm run dev')} and test.`);
203
239
  log('');
204
- log(`To undo : ${c.cyan('mostajs install-bridge --restore --apply')}`);
240
+ log(`To undo all : ${c.cyan('mostajs install-bridge --restore --apply')}`);
241
+ log(`To restore seeds : ${c.cyan('mostajs install-bridge --restore-seeds --apply')}`);
package/bin/mostajs.sh CHANGED
@@ -1653,6 +1653,9 @@ menu_seeding() {
1653
1653
  echo -e " ${CYAN}8${RESET}) Clear the seeds directory"
1654
1654
  echo -e " ${CYAN}9${RESET}) Show a seed file"
1655
1655
  echo -e " ${CYAN}h${RESET}) ${BOLD}Hash plain-text passwords in seed files${RESET} (bcrypt)"
1656
+ echo -e " ${CYAN}r${RESET}) ${BOLD}Restore seed scripts${RESET} from ${DIM}*.prisma.bak${RESET} (undo install-bridge on seeds)"
1657
+ echo -e " ${CYAN}s${RESET}) ${BOLD}Run seed scripts${RESET} (${DIM}scripts/seed-*.ts | prisma/seed.ts${RESET}) via tsx"
1658
+ echo -e " ${RED}d${RESET}) ${BOLD}Drop table(s)${RESET} — pick one / many / all, then DROP TABLE (DESTRUCTIVE)"
1656
1659
  echo
1657
1660
  echo -e " ${CYAN}b${RESET}) Back"
1658
1661
  echo
@@ -1669,12 +1672,187 @@ menu_seeding() {
1669
1672
  8) action_seed_clear ;;
1670
1673
  9) action_seed_show ;;
1671
1674
  h|H) action_seed_hash_passwords ;;
1675
+ r|R) action_seed_restore_scripts ;;
1676
+ s|S) action_seed_run_scripts ;;
1677
+ d|D) action_drop_tables ;;
1672
1678
  b|B) return ;;
1673
1679
  *) warn "Unknown"; pause ;;
1674
1680
  esac
1675
1681
  menu_seeding
1676
1682
  }
1677
1683
 
1684
+ # ------------------------------------------------------------
1685
+ # seed : drop tables (interactive picker)
1686
+ # ------------------------------------------------------------
1687
+ action_drop_tables() {
1688
+ header
1689
+ echo -e "${BOLD}${RED}▶ Drop table(s) — DESTRUCTIVE${RESET}"
1690
+ echo
1691
+ load_env
1692
+ local seed_dir="$CONFIG_DIR/seeds"
1693
+ local entities_json="$GENERATED_DIR/entities.json"
1694
+ if [[ ! -f "$entities_json" ]]; then
1695
+ warn "No entities.json found at $entities_json — run menu 1 (Convert) first."
1696
+ pause; return
1697
+ fi
1698
+
1699
+ # Run a Node helper that lists live tables, lets the user pick, then drops via dialect
1700
+ node --input-type=module -e "
1701
+ import { readFileSync } from 'node:fs';
1702
+ import { createInterface } from 'node:readline/promises';
1703
+ import { stdin, stdout } from 'node:process';
1704
+ import { getDialect } from '${PROJECT_ROOT}/node_modules/@mostajs/orm/dist/index.js';
1705
+
1706
+ const env = readFileSync('${PROJECT_ROOT}/.mostajs/config.env', 'utf8');
1707
+ for (const line of env.split('\n')) {
1708
+ const [k, v] = line.split('=');
1709
+ if (k && v) process.env[k.trim()] = v.trim();
1710
+ }
1711
+
1712
+ const entities = JSON.parse(readFileSync('${PROJECT_ROOT}/.mostajs/generated/entities.json', 'utf8'));
1713
+ const tableSet = new Set(entities.map(e => e.collection));
1714
+ // Add junction tables (many-to-many.through)
1715
+ for (const e of entities) {
1716
+ for (const r of Object.values(e.relations || {})) {
1717
+ if (r && r.type === 'many-to-many' && r.through) tableSet.add(r.through);
1718
+ }
1719
+ }
1720
+
1721
+ const d = await getDialect({
1722
+ dialect: process.env.DB_DIALECT,
1723
+ uri: process.env.SGBD_URI,
1724
+ schemaStrategy: 'none',
1725
+ });
1726
+
1727
+ // Try to list live tables (dialects that expose getTableListQuery via internal call)
1728
+ let live = [];
1729
+ try {
1730
+ const sql = d.getTableListQuery && d.getTableListQuery();
1731
+ if (sql) {
1732
+ const rows = await d.executeQuery(sql, []);
1733
+ live = rows.map(r => r.name || r.TABLE_NAME || r.table_name || Object.values(r)[0]).filter(Boolean);
1734
+ }
1735
+ } catch {}
1736
+ // Intersect with schema-known tables (only show ones we own)
1737
+ const owned = (live.length ? live : Array.from(tableSet)).filter(t => tableSet.has(t)).sort();
1738
+
1739
+ if (owned.length === 0) {
1740
+ console.log(' No tables found that match this project\'s entities.');
1741
+ await d.disconnect();
1742
+ process.exit(0);
1743
+ }
1744
+
1745
+ console.log(' Live tables in this project :');
1746
+ owned.forEach((t, i) => console.log(' ' + (i + 1).toString().padStart(2) + ') ' + t));
1747
+ console.log(' a) ALL of the above');
1748
+ console.log(' q) Cancel');
1749
+
1750
+ const rl = createInterface({ input: stdin, output: stdout });
1751
+ const pick = (await rl.question(' Pick (number, comma-separated, or a) : ')).trim();
1752
+ if (!pick || pick.toLowerCase() === 'q') { rl.close(); await d.disconnect(); console.log(' Cancelled.'); process.exit(0); }
1753
+
1754
+ const targets = pick.toLowerCase() === 'a'
1755
+ ? owned
1756
+ : pick.split(',').map(s => s.trim()).map(s => owned[parseInt(s, 10) - 1]).filter(Boolean);
1757
+
1758
+ if (targets.length === 0) { rl.close(); await d.disconnect(); console.log(' Nothing to drop.'); process.exit(0); }
1759
+
1760
+ console.log(' About to DROP : ' + targets.join(', '));
1761
+ const confirm = (await rl.question(' Type DROP to confirm : ')).trim();
1762
+ rl.close();
1763
+ if (confirm !== 'DROP') { await d.disconnect(); console.log(' Aborted.'); process.exit(0); }
1764
+
1765
+ let ok = 0, fail = 0;
1766
+ for (const t of targets) {
1767
+ try {
1768
+ await d.dropTable(t);
1769
+ console.log(' ✓ dropped ' + t);
1770
+ ok++;
1771
+ } catch (e) {
1772
+ console.error(' ✗ ' + t + ' : ' + (e.message ?? e));
1773
+ fail++;
1774
+ }
1775
+ }
1776
+ await d.disconnect();
1777
+ console.log('\nDropped : ' + ok + ' · failed : ' + fail);
1778
+ " 2>&1
1779
+ echo
1780
+ pause
1781
+ }
1782
+
1783
+ # ------------------------------------------------------------
1784
+ # seed : restore TS seed scripts from .prisma.bak (undo install-bridge)
1785
+ # ------------------------------------------------------------
1786
+ action_seed_restore_scripts() {
1787
+ header
1788
+ echo -e "${BOLD}${MAGENTA}▶ Restore seed scripts from *.prisma.bak${RESET}"
1789
+ echo
1790
+ echo -e " ${DIM}Scans for seed-like .prisma.bak files that were rewritten by install-bridge${RESET}"
1791
+ echo -e " ${DIM}and moves each backup back to its original filename.${RESET}"
1792
+ echo
1793
+ local cli_dir
1794
+ cli_dir="$(cd "$(dirname "$0")/.." && pwd)"
1795
+ echo -e "${DIM}\$ node install-bridge.mjs --restore-seeds --project \"$PROJECT_ROOT\"${RESET}"
1796
+ node "$cli_dir/bin/install-bridge.mjs" --restore-seeds --project "$PROJECT_ROOT"
1797
+ echo
1798
+ if confirm "Apply the restoration above?"; then
1799
+ node "$cli_dir/bin/install-bridge.mjs" --restore-seeds --apply --project "$PROJECT_ROOT"
1800
+ else
1801
+ dim " Skipped."
1802
+ fi
1803
+ pause
1804
+ }
1805
+
1806
+ # ------------------------------------------------------------
1807
+ # seed : run TS seed scripts via tsx (or node --loader ts-node)
1808
+ # ------------------------------------------------------------
1809
+ action_seed_run_scripts() {
1810
+ header
1811
+ echo -e "${BOLD}${MAGENTA}▶ Run seed scripts${RESET}"
1812
+ echo
1813
+ # Candidate scripts : prisma/seed.ts, scripts/seed*.{ts,js}, scripts/seed.ts
1814
+ local -a candidates=()
1815
+ [[ -f "$PROJECT_ROOT/prisma/seed.ts" ]] && candidates+=("$PROJECT_ROOT/prisma/seed.ts")
1816
+ [[ -f "$PROJECT_ROOT/prisma/seed.js" ]] && candidates+=("$PROJECT_ROOT/prisma/seed.js")
1817
+ if [[ -d "$PROJECT_ROOT/scripts" ]]; then
1818
+ while IFS= read -r f; do candidates+=("$f"); done < <(
1819
+ find "$PROJECT_ROOT/scripts" -maxdepth 2 \( -name 'seed-*.ts' -o -name 'seed-*.js' -o -name 'seed.ts' -o -name 'seed.js' \) 2>/dev/null | sort
1820
+ )
1821
+ fi
1822
+ if [[ ${#candidates[@]} -eq 0 ]]; then
1823
+ warn "No seed scripts found under prisma/ or scripts/."
1824
+ dim " Expected : prisma/seed.ts | scripts/seed.ts | scripts/seed-*.ts (or .js)"
1825
+ pause
1826
+ return
1827
+ fi
1828
+ echo -e " Found ${CYAN}${#candidates[@]}${RESET} seed script(s):"
1829
+ local i=1
1830
+ for f in "${candidates[@]}"; do
1831
+ echo -e " ${CYAN}$i${RESET}) ${f#$PROJECT_ROOT/}"
1832
+ ((i++))
1833
+ done
1834
+ echo -e " ${CYAN}a${RESET}) Run ALL sequentially"
1835
+ echo
1836
+ local pick
1837
+ pick=$(ask "Choice" "a")
1838
+ local runner="npx --yes tsx"
1839
+ command -v tsx >/dev/null && runner="tsx"
1840
+ if [[ "$pick" == "a" || "$pick" == "A" ]]; then
1841
+ for f in "${candidates[@]}"; do
1842
+ echo -e "${CYAN}▶ ${runner} ${f#$PROJECT_ROOT/}${RESET}"
1843
+ (cd "$PROJECT_ROOT" && $runner "$f") || { warn "Script failed: $f"; pause; return; }
1844
+ done
1845
+ ok "All seed scripts ran successfully."
1846
+ elif [[ "$pick" =~ ^[0-9]+$ ]] && (( pick >= 1 && pick <= ${#candidates[@]} )); then
1847
+ local f="${candidates[$((pick-1))]}"
1848
+ echo -e "${CYAN}▶ ${runner} ${f#$PROJECT_ROOT/}${RESET}"
1849
+ (cd "$PROJECT_ROOT" && $runner "$f") && ok "Done." || warn "Script failed."
1850
+ else
1851
+ warn "Unknown choice"
1852
+ fi
1853
+ pause
1854
+ }
1855
+
1678
1856
  # ------------------------------------------------------------
1679
1857
  # seed : hash plain-text passwords
1680
1858
  # ------------------------------------------------------------
@@ -1995,6 +2173,15 @@ function validateRow(row, entity) {
1995
2173
  const errors = [];
1996
2174
  const fieldNames = new Set(Object.keys(entity.fields ?? {}));
1997
2175
  const relationNames = new Set(Object.keys(entity.relations ?? {}));
2176
+ // FK columns generated by relations (many-to-one / one-to-one joinColumn, plus
2177
+ // the conventional <relName>Id fallback). These appear in seed JSONs as direct
2178
+ // FK values ("userId": "user-admin") and must NOT be flagged as "unknown field".
2179
+ const relationFkColumns = new Set();
2180
+ for (const [relName, rel] of Object.entries(entity.relations ?? {})) {
2181
+ if (rel && (rel.type === 'many-to-one' || rel.type === 'one-to-one')) {
2182
+ relationFkColumns.add(rel.joinColumn || (relName + 'Id'));
2183
+ }
2184
+ }
1998
2185
  // Required fields
1999
2186
  for (const [k, def] of Object.entries(entity.fields ?? {})) {
2000
2187
  if (def.required && (row[k] === undefined || row[k] === null)) {
@@ -2017,7 +2204,7 @@ function validateRow(row, entity) {
2017
2204
  // Unknown fields (warn-level)
2018
2205
  const warnings = [];
2019
2206
  for (const k of Object.keys(row)) {
2020
- if (!fieldNames.has(k) && !relationNames.has(k) && k !== 'id' && k !== '_id') {
2207
+ if (!fieldNames.has(k) && !relationNames.has(k) && !relationFkColumns.has(k) && k !== 'id' && k !== '_id') {
2021
2208
  warnings.push('unknown field "' + k + '" (not in schema)');
2022
2209
  }
2023
2210
  }
@@ -2515,14 +2702,69 @@ run_subcommand() {
2515
2702
  fi
2516
2703
 
2517
2704
  mkdir -p "$CONFIG_DIR"
2518
- if [[ ! -f "$CONFIG_DIR/config.env" ]]; then
2705
+ # ── Database choice ──
2706
+ # Resolution order :
2707
+ # 1. existing .mostajs/config.env → honour it (user may have set it with `mostajs` menu 2)
2708
+ # 2. --dialect / --uri / --strategy CLI flags → non-interactive scripting
2709
+ # 3. $DB_DIALECT + $SGBD_URI env vars → non-interactive, no files on disk
2710
+ # 4. interactive prompt (default)
2711
+ if [[ -f "$CONFIG_DIR/config.env" ]]; then
2712
+ load_env
2713
+ info " using existing $CONFIG_DIR/config.env (dialect=$DB_DIALECT uri=$SGBD_URI)"
2714
+ else
2715
+ local bs_dialect="" bs_uri="" bs_strategy="update"
2716
+ for arg in "$@"; do
2717
+ case "$arg" in
2718
+ --dialect=*) bs_dialect="${arg#--dialect=}" ;;
2719
+ --uri=*) bs_uri="${arg#--uri=}" ;;
2720
+ --strategy=*) bs_strategy="${arg#--strategy=}" ;;
2721
+ esac
2722
+ done
2723
+ # Fall back to env vars
2724
+ [[ -z "$bs_dialect" ]] && bs_dialect="${DB_DIALECT:-}"
2725
+ [[ -z "$bs_uri" ]] && bs_uri="${SGBD_URI:-}"
2726
+
2727
+ if [[ -z "$bs_dialect" || -z "$bs_uri" ]]; then
2728
+ echo
2729
+ echo -e "${BOLD}▶ Database choice${RESET} ${DIM}(run \`$CLI_NAME\` then menu 2 beforehand to skip this prompt)${RESET}"
2730
+ echo
2731
+ echo " ${CYAN}1${RESET}) SQLite ${DIM}./data.sqlite (zero setup, recommended for trying out)${RESET}"
2732
+ echo " ${CYAN}2${RESET}) PostgreSQL ${DIM}postgres://user:pass@host:5432/db${RESET}"
2733
+ echo " ${CYAN}3${RESET}) MongoDB ${DIM}mongodb://user:pass@host:27017/db${RESET}"
2734
+ echo " ${CYAN}4${RESET}) MySQL ${DIM}mysql://user:pass@host:3306/db${RESET}"
2735
+ echo " ${CYAN}5${RESET}) MariaDB ${DIM}mariadb://user:pass@host:3306/db${RESET}"
2736
+ echo " ${CYAN}6${RESET}) Oracle ${DIM}oracle://user:pass@host:1521/XE${RESET}"
2737
+ echo " ${CYAN}7${RESET}) SQL Server ${DIM}mssql://user:pass@host:1433/db${RESET}"
2738
+ echo " ${CYAN}8${RESET}) CockroachDB ${DIM}postgresql://user:pass@host:26257/db?sslmode=disable${RESET}"
2739
+ echo " ${CYAN}9${RESET}) DB2 / HANA / HSQLDB / Spanner / Sybase / other (enter manually)"
2740
+ echo
2741
+ local c; c=$(ask "Choice" "1")
2742
+ case "$c" in
2743
+ 1) bs_dialect="sqlite"; bs_uri=$(ask "SQLite path" "./data.sqlite") ;;
2744
+ 2) bs_dialect="postgres"; bs_uri=$(ask "Postgres URI" "postgres://user:pass@localhost:5432/mydb") ;;
2745
+ 3) bs_dialect="mongodb"; bs_uri=$(ask "MongoDB URI" "mongodb://localhost:27017/mydb") ;;
2746
+ 4) bs_dialect="mysql"; bs_uri=$(ask "MySQL URI" "mysql://user:pass@localhost:3306/mydb") ;;
2747
+ 5) bs_dialect="mariadb"; bs_uri=$(ask "MariaDB URI" "mariadb://user:pass@localhost:3306/mydb") ;;
2748
+ 6) bs_dialect="oracle"; bs_uri=$(ask "Oracle URI" "oracle://user:pass@localhost:1521/XE") ;;
2749
+ 7) bs_dialect="mssql"; bs_uri=$(ask "SQL Server URI" "mssql://user:pass@localhost:1433/mydb") ;;
2750
+ 8) bs_dialect="cockroachdb"; bs_uri=$(ask "CockroachDB URI" "postgresql://root@localhost:26257/mydb?sslmode=disable") ;;
2751
+ 9) bs_dialect=$(ask "Dialect (db2|hana|hsqldb|spanner|sybase|other)" "")
2752
+ bs_uri=$(ask "URI" "") ;;
2753
+ *) err "Invalid choice"; exit 1 ;;
2754
+ esac
2755
+ [[ -z "$bs_dialect" || -z "$bs_uri" ]] && { err "dialect and uri are required"; exit 1; }
2756
+
2757
+ local chosen_strategy
2758
+ chosen_strategy=$(ask "Schema strategy (validate|update|create|create-drop|none)" "$bs_strategy")
2759
+ bs_strategy="$chosen_strategy"
2760
+ fi
2761
+
2519
2762
  cat > "$CONFIG_DIR/config.env" <<CFG
2520
- DB_DIALECT=sqlite
2521
- SGBD_URI=./data.sqlite
2522
- DB_SCHEMA_STRATEGY=update
2763
+ DB_DIALECT=$bs_dialect
2764
+ SGBD_URI=$bs_uri
2765
+ DB_SCHEMA_STRATEGY=$bs_strategy
2523
2766
  CFG
2524
- ok " wrote $CONFIG_DIR/config.env (defaults: sqlite ./data.sqlite)"
2525
- # Reload config so action_init_dialects picks up the new values
2767
+ ok " wrote $CONFIG_DIR/config.env (dialect=$bs_dialect)"
2526
2768
  load_env
2527
2769
  fi
2528
2770
 
@@ -2542,7 +2784,7 @@ CFG
2542
2784
 
2543
2785
  ✓ Bridge installed in-place. Original files backed up as *.prisma.bak
2544
2786
  ✓ Schema converted : $GENERATED_DIR/entities.json
2545
- ✓ DDL applied (DB_DIALECT=\$(grep ^DB_DIALECT $CONFIG_DIR/config.env | cut -d= -f2))
2787
+ ✓ DDL applied (DB_DIALECT=$DB_DIALECT SGBD_URI=$SGBD_URI)
2546
2788
 
2547
2789
  Next :
2548
2790
  - Add seeds to $CONFIG_DIR/seeds/*.json (one file per entity)
@@ -2566,8 +2808,11 @@ DONE
2566
2808
  cat <<EOF
2567
2809
  Usage :
2568
2810
  $CLI_NAME Interactive menu
2569
- $CLI_NAME bootstrap One-shot migration : codemod + deps + convert + DDL
2570
- $CLI_NAME install-bridge Codemod only (dry-run ; add --apply to write)
2811
+ $CLI_NAME bootstrap One-shot migration : codemod + deps + convert + DDL
2812
+ (interactive database picker unless config exists)
2813
+ $CLI_NAME bootstrap --dialect=postgres --uri=postgres://... --strategy=update
2814
+ Non-interactive bootstrap (for CI / scripts)
2815
+ $CLI_NAME install-bridge Codemod only (dry-run ; add --apply to write)
2571
2816
  $CLI_NAME install-bridge --apply Rewrite PrismaClient sites to use @mostajs/orm-bridge
2572
2817
  $CLI_NAME install-bridge --restore --apply Undo a prior install-bridge
2573
2818
  $CLI_NAME convert Run conversion (auto-detect schema type)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/orm-cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.5",
4
4
  "description": "Universal CLI to integrate @mostajs/orm into any project — one-shot `mostajs bootstrap` migrates a Prisma project (codemod + deps + schema convert + DDL) to 13 databases with zero code change.",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "AGPL-3.0-or-later",