@mostajs/orm-cli 0.3.1 → 0.4.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.
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ // install-bridge.mjs — one-shot codemod : scan a Prisma project, find every
3
+ // file that instantiates PrismaClient, and rewrite it to use
4
+ // createPrismaLikeDb() from @mostajs/orm-bridge.
5
+ //
6
+ // Author: Dr Hamid MADANI drmdh@msn.com
7
+ // License: AGPL-3.0-or-later
8
+ //
9
+ // Usage (invoked by bin/mostajs.sh as `mostajs install-bridge`) :
10
+ //
11
+ // node install-bridge.mjs # dry-run (default, safe)
12
+ // node install-bridge.mjs --apply # actually write files
13
+ // node install-bridge.mjs --file X # restrict to a single file
14
+ // node install-bridge.mjs --project P # root to scan (default: cwd)
15
+ // node install-bridge.mjs --restore # restore from .prisma.bak backups
16
+ //
17
+ // The rewriter is deliberately conservative :
18
+ // - It only touches files that CREATE a PrismaClient (`new PrismaClient(...)`).
19
+ // - It preserves the original export name (`prisma`, `db`, `client`, `default`)
20
+ // so none of the 10 to 10000 call-sites elsewhere in the codebase need to change.
21
+ // - Original file is saved as <path>.prisma.bak (never overwritten).
22
+ // - Re-runs are idempotent : if the file already uses createPrismaLikeDb,
23
+ // the codemod skips it.
24
+
25
+ import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, renameSync, copyFileSync } from 'node:fs';
26
+ import { join, resolve, relative, extname } from 'node:path';
27
+
28
+ // ---------- CLI ----------
29
+ const argv = process.argv.slice(2);
30
+ const flag = (name) => argv.includes(`--${name}`);
31
+ const val = (name) => { const i = argv.indexOf(`--${name}`); return i >= 0 ? argv[i + 1] : null; };
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');
38
+
39
+ const log = (...a) => { if (!QUIET) console.log(...a); };
40
+ 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` };
41
+
42
+ // ---------- Walk ----------
43
+ const SKIP_DIRS = new Set(['node_modules', '.next', '.svelte-kit', 'dist', 'build', '.turbo', '.vercel', '.cache', 'coverage', '.git', '.vscode', '.idea']);
44
+ const EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.js', '.jsx', '.mjs']);
45
+
46
+ function* walk(dir) {
47
+ let entries;
48
+ try { entries = readdirSync(dir); } catch { return; }
49
+ for (const name of entries) {
50
+ if (SKIP_DIRS.has(name)) continue;
51
+ const p = join(dir, name);
52
+ let st;
53
+ try { st = statSync(p); } catch { continue; }
54
+ if (st.isDirectory()) yield* walk(p);
55
+ else if (EXTENSIONS.has(extname(name))) yield p;
56
+ }
57
+ }
58
+
59
+ // ---------- Detection ----------
60
+ // Patterns we consider "PrismaClient instantiation sites"
61
+ const RX_IMPORT = /import\s*(?:\{[^}]*PrismaClient[^}]*\}|[^;]*?)\s*from\s*['"]@prisma\/client['"]/;
62
+ const RX_NEW = /new\s+PrismaClient\s*\(/;
63
+ const RX_ALREADY = /@mostajs\/orm-bridge\/prisma-client/;
64
+
65
+ // Detect the export shape to preserve the name the codebase depends on.
66
+ // export const db = new PrismaClient(...) → named, "db"
67
+ // export const prisma = new PrismaClient(...) → named, "prisma"
68
+ // export default new PrismaClient(...) → default
69
+ // const prisma = ...; export { prisma } → named, "prisma"
70
+ function detectExportShape(source) {
71
+ // Singleton pattern commonly used in Next.js :
72
+ // const g = globalThis as ... { prisma: ... }
73
+ // export const db = g.prisma ?? new PrismaClient()
74
+ const mNamed = source.match(/export\s+const\s+(\w+)\s*=\s*(?:[^;]*?)\bnew\s+PrismaClient/);
75
+ if (mNamed) return { kind: 'const', name: mNamed[1] };
76
+
77
+ const mLet = source.match(/export\s+let\s+(\w+)\s*=\s*(?:[^;]*?)\bnew\s+PrismaClient/);
78
+ if (mLet) return { kind: 'let', name: mLet[1] };
79
+
80
+ const mDefault = source.match(/export\s+default\s+(?:[^;]*?)\bnew\s+PrismaClient/);
81
+ if (mDefault) return { kind: 'default', name: null };
82
+
83
+ // Bare global : const prisma = new PrismaClient(); followed somewhere by `export { prisma }` or not
84
+ const mBare = source.match(/(?:^|\n)\s*(?:const|let)\s+(\w+)\s*=\s*(?:[^;]*?)\bnew\s+PrismaClient/);
85
+ if (mBare) {
86
+ const name = mBare[1];
87
+ if (new RegExp(`export\\s*\\{[^}]*\\b${name}\\b[^}]*\\}`).test(source)) {
88
+ return { kind: 'export-block', name };
89
+ }
90
+ return { kind: 'const', name }; // assume user wants to export it ; safer default
91
+ }
92
+
93
+ return null;
94
+ }
95
+
96
+ // ---------- Rewrite ----------
97
+ function buildReplacement(shape) {
98
+ const header = `// Auto-generated by \`mostajs install-bridge\` on ${new Date().toISOString()}\n// Original file backed up as <this-file>.prisma.bak\n// Every db/prisma/client call is now routed to @mostajs/orm (13 dialects).\nimport 'server-only'\nimport { createPrismaLikeDb } from '@mostajs/orm-bridge/prisma-client'\n`;
99
+ if (shape.kind === 'default') {
100
+ return `${header}\nexport default createPrismaLikeDb()\n`;
101
+ }
102
+ const kw = shape.kind === 'let' ? 'let' : 'const';
103
+ if (shape.kind === 'export-block') {
104
+ return `${header}\n${kw} ${shape.name} = createPrismaLikeDb()\nexport { ${shape.name} }\n`;
105
+ }
106
+ return `${header}\nexport ${kw} ${shape.name} = createPrismaLikeDb()\n`;
107
+ }
108
+
109
+ // ---------- Restore ----------
110
+ function restoreBackups() {
111
+ const restored = [];
112
+ for (const p of walk(ROOT)) {
113
+ if (!p.endsWith('.prisma.bak')) continue;
114
+ const original = p.slice(0, -'.prisma.bak'.length);
115
+ if (APPLY) {
116
+ renameSync(p, original);
117
+ log(` ${c.green('✓')} restored ${c.dim(relative(ROOT, original))}`);
118
+ } else {
119
+ log(` ${c.yellow('•')} would restore ${c.dim(relative(ROOT, original))}`);
120
+ }
121
+ restored.push(original);
122
+ }
123
+ return restored;
124
+ }
125
+
126
+ // ---------- Main ----------
127
+ if (RESTORE) {
128
+ log(c.bold('▶ Restoring .prisma.bak files' + (APPLY ? '' : c.yellow(' (dry-run, use --apply)'))));
129
+ const r = restoreBackups();
130
+ log(`\n${r.length} file(s) ${APPLY ? 'restored' : 'would be restored'}`);
131
+ process.exit(0);
132
+ }
133
+
134
+ log(c.bold(`▶ mostajs install-bridge — scanning ${c.cyan(ROOT)}`));
135
+ log('');
136
+
137
+ const candidates = [];
138
+ const iter = ONE_FILE ? [resolve(ROOT, ONE_FILE)] : walk(ROOT);
139
+ for (const p of iter) {
140
+ let src;
141
+ try { src = readFileSync(p, 'utf8'); } catch { continue; }
142
+ if (!RX_IMPORT.test(src) && !RX_NEW.test(src)) continue;
143
+ if (RX_ALREADY.test(src)) {
144
+ log(` ${c.dim('— skip (already bridged) ' + relative(ROOT, p))}`);
145
+ continue;
146
+ }
147
+ if (!RX_NEW.test(src)) continue; // imports only = not an instantiation site
148
+ const shape = detectExportShape(src);
149
+ if (!shape) {
150
+ log(` ${c.yellow('?')} ${relative(ROOT, p)} — PrismaClient detected but export shape unknown, skipping`);
151
+ continue;
152
+ }
153
+ candidates.push({ path: p, rel: relative(ROOT, p), shape, src });
154
+ }
155
+
156
+ if (candidates.length === 0) {
157
+ log(c.yellow(' No instantiation sites found.'));
158
+ log('');
159
+ log(` Did you remove \`new PrismaClient()\` already ? The bridge may already be in place.`);
160
+ log(` Re-scan with: ${c.cyan('mostajs install-bridge --restore --apply')} to undo a prior run.`);
161
+ process.exit(0);
162
+ }
163
+
164
+ log(c.bold(`Found ${candidates.length} PrismaClient instantiation site(s):`));
165
+ for (const ca of candidates) {
166
+ log(` ${c.green('→')} ${ca.rel} ${c.dim(`(${ca.shape.kind}${ca.shape.name ? ' ' + ca.shape.name : ''})`)}`);
167
+ }
168
+ log('');
169
+
170
+ if (!APPLY) {
171
+ log(c.yellow('Dry-run — no files written. Re-run with --apply to execute.'));
172
+ log('');
173
+ log(c.bold('Preview of rewrite for the first file :'));
174
+ log(c.dim('─────────────────────────────────────────'));
175
+ log(buildReplacement(candidates[0].shape));
176
+ log(c.dim('─────────────────────────────────────────'));
177
+ process.exit(0);
178
+ }
179
+
180
+ // ---------- Apply ----------
181
+ log(c.bold('Applying rewrites :'));
182
+ for (const ca of candidates) {
183
+ const bak = ca.path + '.prisma.bak';
184
+ if (!existsSync(bak)) copyFileSync(ca.path, bak);
185
+ writeFileSync(ca.path, buildReplacement(ca.shape));
186
+ log(` ${c.green('✓')} rewrote ${ca.rel} ${c.dim(`(backup: ${relative(ROOT, bak)})`)}`);
187
+ }
188
+
189
+ log('');
190
+ log(c.bold(c.green(`✓ Bridge installed in ${candidates.length} file(s).`)));
191
+ log('');
192
+ log('Next steps :');
193
+ log(` 1. ${c.cyan('npm i @mostajs/orm @mostajs/orm-bridge server-only --legacy-peer-deps')}`);
194
+ log(` 2. ${c.cyan('npx @mostajs/orm-cli')} → menu 1 (Convert Prisma → entities.json) → menu 3 (init DDL)`);
195
+ log(` 3. Set ${c.cyan('DB_DIALECT')} + ${c.cyan('SGBD_URI')} in .env (or: menu 2 → i to import)`);
196
+ log(` 4. ${c.cyan('npm run dev')} and test.`);
197
+ log('');
198
+ log(`To undo : ${c.cyan('mostajs install-bridge --restore --apply')}`);
package/bin/mostajs.sh CHANGED
@@ -438,6 +438,8 @@ menu_main() {
438
438
  echo -e " ${CYAN}8${RESET}) Health checks"
439
439
  echo -e " ${CYAN}9${RESET}) Generate boilerplate (src/db.ts with bridge)"
440
440
  echo -e " ${CYAN}s${RESET}) ${BOLD}Seeding${RESET} (upload / validate / apply seed data)"
441
+ echo -e " ${GREEN}b${RESET}) ${BOLD}Bootstrap${RESET} — one-shot migration of a Prisma project"
442
+ echo -e " ${GREEN}i${RESET}) ${BOLD}Install bridge${RESET} — codemod PrismaClient → bridge (dry-run / apply / restore)"
441
443
  echo -e " ${CYAN}0${RESET}) About / Help"
442
444
  echo
443
445
  echo -e " ${RED}q${RESET}) Quit"
@@ -455,12 +457,63 @@ menu_main() {
455
457
  8) action_healthcheck ;;
456
458
  9) action_generate_boilerplate ;;
457
459
  s|S) menu_seeding ;;
460
+ b|B) menu_bootstrap ;;
461
+ i|I) menu_install_bridge ;;
458
462
  0) action_about ;;
459
463
  q|Q) exit 0 ;;
460
464
  *) warn "Unknown choice"; pause ;;
461
465
  esac
462
466
  }
463
467
 
468
+ # ------------------------------------------------------------
469
+ # Interactive wrapper for `install-bridge` codemod
470
+ # ------------------------------------------------------------
471
+ menu_install_bridge() {
472
+ header
473
+ echo -e "${BOLD}${MAGENTA}▶ Install bridge — PrismaClient codemod${RESET}"
474
+ echo
475
+ echo " ${CYAN}1${RESET}) Dry-run : list files that would be rewritten (default)"
476
+ echo " ${CYAN}2${RESET}) Apply : rewrite PrismaClient sites to createPrismaLikeDb()"
477
+ echo " ${CYAN}3${RESET}) Restore : revert .prisma.bak files (dry-run)"
478
+ echo " ${CYAN}4${RESET}) Restore : revert .prisma.bak files (apply)"
479
+ echo " ${RED}0${RESET}) Back"
480
+ echo
481
+ local cli_dir
482
+ cli_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
483
+ local choice; choice=$(ask "Choice" "1")
484
+ case "$choice" in
485
+ 1) node "$cli_dir/bin/install-bridge.mjs" ;;
486
+ 2) node "$cli_dir/bin/install-bridge.mjs" --apply ;;
487
+ 3) node "$cli_dir/bin/install-bridge.mjs" --restore ;;
488
+ 4) node "$cli_dir/bin/install-bridge.mjs" --restore --apply ;;
489
+ 0) return ;;
490
+ *) warn "Unknown" ;;
491
+ esac
492
+ pause
493
+ }
494
+
495
+ # ------------------------------------------------------------
496
+ # Interactive wrapper for `bootstrap` — one-shot full migration
497
+ # ------------------------------------------------------------
498
+ menu_bootstrap() {
499
+ header
500
+ echo -e "${BOLD}${GREEN}▶ Bootstrap — full Prisma → @mostajs/orm migration${RESET}"
501
+ echo
502
+ echo "This will, in ${BOLD}this project${RESET} :"
503
+ echo " 1. Rewrite every ${CYAN}new PrismaClient()${RESET} site to use ${CYAN}createPrismaLikeDb()${RESET}"
504
+ echo " (originals backed up as ${DIM}*.prisma.bak${RESET})"
505
+ echo " 2. Install ${CYAN}@mostajs/orm${RESET} + ${CYAN}@mostajs/orm-bridge${RESET} + ${CYAN}server-only${RESET}"
506
+ echo " 3. Convert ${CYAN}prisma/schema.prisma${RESET} → ${DIM}.mostajs/generated/entities.json${RESET}"
507
+ echo " 4. Write ${DIM}.mostajs/config.env${RESET} (default : sqlite ./data.sqlite) and init DDL"
508
+ echo
509
+ warn "Existing code changes will be backed up but NOT committed — review the diff before pushing."
510
+ echo
511
+ local go; go=$(ask "Proceed? (y/N)" "N")
512
+ [[ "$go" =~ ^[yY]$ ]] || { info "Cancelled"; return; }
513
+ run_subcommand bootstrap
514
+ pause
515
+ }
516
+
464
517
  # ============================================================
465
518
  # ACTION 1 : CONVERT
466
519
  # ============================================================
@@ -548,6 +601,7 @@ menu_databases() {
548
601
  echo -e " ${CYAN}p${RESET}) APP_PORT : ${DIM}${APP_PORT:-3000}${RESET}"
549
602
  echo
550
603
  echo -e " ${CYAN}t${RESET}) Test all connections"
604
+ echo -e " ${CYAN}i${RESET}) ${BOLD}Import from project .env${RESET} (auto-detect app's real DB)"
551
605
  echo -e " ${CYAN}e${RESET}) Export to .env.local in project"
552
606
  echo -e " ${CYAN}r${RESET}) Reset config"
553
607
  echo -e " ${CYAN}b${RESET}) Back"
@@ -565,6 +619,7 @@ menu_databases() {
565
619
  n|N) save_var MOSTA_NET_TRANSPORT "$(ask 'MOSTA_NET_TRANSPORT (rest|sse|graphql|mcp|websocket|jsonrpc|grpc|odata)' "${MOSTA_NET_TRANSPORT:-rest}")";;
566
620
  p|P) save_var APP_PORT "$(ask 'APP_PORT' "${APP_PORT:-3000}")";;
567
621
  t|T) action_test_connections; return;;
622
+ i|I) action_import_env ;;
568
623
  e|E) export_env_local ;;
569
624
  r|R) confirm "Really reset config?" && rm -f "$CONFIG_FILE" && ok "Reset";;
570
625
  b|B) return;;
@@ -647,6 +702,95 @@ list_extra_bindings() {
647
702
  done
648
703
  }
649
704
 
705
+ # Import DB config from the project's own .env file.
706
+ # This is CRUCIAL when your app (Prisma, NextAuth, ...) already has a
707
+ # DATABASE_URL / MONGODB_URI / POSTGRES_URL. Seeding the wrong DB leads to
708
+ # the classic "users are in mosta-net /api/v1/users but login fails" bug.
709
+ action_import_env() {
710
+ header
711
+ echo -e "${BOLD}${MAGENTA}▶ Import DB config from project .env${RESET}"
712
+ echo
713
+
714
+ local candidates=(".env.local" ".env" ".env.production" ".env.development")
715
+ local -a available=()
716
+ for f in "${candidates[@]}"; do
717
+ [[ -f "$PROJECT_ROOT/$f" ]] && available+=("$f")
718
+ done
719
+
720
+ if [[ ${#available[@]} -eq 0 ]]; then
721
+ err "No .env* file found in $PROJECT_ROOT"
722
+ pause; return
723
+ fi
724
+
725
+ echo "Found env files :"
726
+ local i=1
727
+ for f in "${available[@]}"; do
728
+ echo -e " ${CYAN}$i${RESET}) $f"
729
+ i=$((i+1))
730
+ done
731
+ echo
732
+ local choice; choice=$(ask "Number" 1)
733
+ local src_file="${available[$((choice-1))]}"
734
+ [[ -z "$src_file" ]] && return
735
+ local src_path="$PROJECT_ROOT/$src_file"
736
+
737
+ # Extract common DB URI variables — priority order matters
738
+ # MONGODB_URI > DATABASE_URL > POSTGRES_URL > MYSQL_URL > SGBD_URI
739
+ local found_uri="" found_var="" found_dialect=""
740
+ for var in SGBD_URI DATABASE_URL MONGODB_URI POSTGRES_URL POSTGRES_URI MYSQL_URL MYSQL_URI POSTGRESQL_URL; do
741
+ # Read value, skip commented lines, strip quotes
742
+ local val
743
+ val=$(grep -E "^${var}=" "$src_path" 2>/dev/null | grep -v "^#" | head -1 | sed "s/^${var}=//" | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\(.*\)'$/\1/")
744
+ if [[ -n "$val" ]]; then
745
+ found_uri="$val"
746
+ found_var="$var"
747
+ break
748
+ fi
749
+ done
750
+
751
+ if [[ -z "$found_uri" ]]; then
752
+ err "No DB URI found in $src_file"
753
+ info "Searched : SGBD_URI, DATABASE_URL, MONGODB_URI, POSTGRES_URL/URI, MYSQL_URL/URI"
754
+ pause; return
755
+ fi
756
+
757
+ # Detect dialect from URI scheme
758
+ found_dialect=$(detect_dialect_from_uri "$found_uri")
759
+ if [[ "$found_dialect" == "unknown" ]]; then
760
+ warn "Could not auto-detect dialect from URI: $found_uri"
761
+ found_dialect=$(ask "Dialect name (mongodb|postgres|mysql|...)" "")
762
+ [[ -z "$found_dialect" ]] && { pause; return; }
763
+ fi
764
+
765
+ info "Detected :"
766
+ echo -e " Variable : ${CYAN}$found_var${RESET}"
767
+ echo -e " URI : ${DIM}$found_uri${RESET}"
768
+ echo -e " Dialect : ${MAGENTA}$found_dialect${RESET}"
769
+ echo
770
+
771
+ # Special Prisma note
772
+ if [[ "$found_var" == "MONGODB_URI" ]] || [[ "$found_var" == "DATABASE_URL" ]]; then
773
+ dim " (This is likely the Prisma datasource — the same DB your app uses for login/auth.)"
774
+ echo
775
+ fi
776
+
777
+ if confirm "Save as DB_DIALECT + SGBD_URI ?"; then
778
+ save_var DB_DIALECT "$found_dialect"
779
+ save_var SGBD_URI "$found_uri"
780
+ # Suggest a strategy based on environment context
781
+ if [[ "$src_file" == ".env.production" ]]; then
782
+ save_var DB_SCHEMA_STRATEGY "validate"
783
+ info "Auto-set DB_SCHEMA_STRATEGY=validate (production)"
784
+ elif [[ -z "${DB_SCHEMA_STRATEGY:-}" ]]; then
785
+ save_var DB_SCHEMA_STRATEGY "update"
786
+ fi
787
+ ok "Config imported"
788
+ echo
789
+ info "Next step : menu 2 → t (test connection), then menu S → 4 (seed)"
790
+ fi
791
+ pause
792
+ }
793
+
650
794
  # Export a .env.local file compatible with @mostajs/orm convention
651
795
  export_env_local() {
652
796
  local target="$PROJECT_ROOT/.env.mostajs"
@@ -2225,6 +2369,14 @@ EOF
2225
2369
 
2226
2370
  run_subcommand() {
2227
2371
  case "$1" in
2372
+ diagnose|diag|d)
2373
+ # mostajs diagnose [email] [password]
2374
+ # Walks through: config vs project datasource mismatch, DB connection,
2375
+ # user lookup, isActive check, bcrypt verification.
2376
+ local email="${2:-}"
2377
+ local password="${3:-}"
2378
+ action_diagnose_login "$email" "$password"
2379
+ ;;
2228
2380
  hash|h)
2229
2381
  # mostajs hash <plaintext> [cost]
2230
2382
  local pw="${2:-}"
@@ -2296,6 +2448,71 @@ run_subcommand() {
2296
2448
  health|h)
2297
2449
  action_healthcheck
2298
2450
  ;;
2451
+ install-bridge|ib)
2452
+ # mostajs install-bridge [--apply] [--file X] [--project P] [--restore]
2453
+ # Codemod : scans the project for `new PrismaClient(...)` sites and rewrites
2454
+ # them in place to use createPrismaLikeDb() from @mostajs/orm-bridge.
2455
+ # Dry-run by default ; pass --apply to write the changes.
2456
+ local cli_dir
2457
+ cli_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
2458
+ shift
2459
+ node "$cli_dir/bin/install-bridge.mjs" "$@"
2460
+ ;;
2461
+ bootstrap|b)
2462
+ # mostajs bootstrap : the full zero-touch migration for a Prisma project.
2463
+ # 1. Run the codemod on the whole tree (install-bridge --apply)
2464
+ # 2. npm install @mostajs/orm @mostajs/orm-bridge server-only
2465
+ # 3. Convert prisma/schema.prisma → entities.json
2466
+ # 4. Write .mostajs/config.env + init SQLite DDL
2467
+ # 5. Tell the user what remains (.env, seeds, run dev).
2468
+ local cli_dir
2469
+ cli_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
2470
+ detect_project
2471
+ [[ ${#DETECTED_TYPES[@]} -eq 0 ]] && { err "No schema found. Bootstrap needs a prisma/schema.prisma (or OpenAPI/JSONSchema)."; exit 1; }
2472
+
2473
+ echo -e "\n\e[1m▶ Step 1/4 : rewrite PrismaClient sites\e[0m"
2474
+ node "$cli_dir/bin/install-bridge.mjs" --apply || { err "Codemod failed"; exit 1; }
2475
+
2476
+ echo -e "\n\e[1m▶ Step 2/4 : install runtime deps\e[0m"
2477
+ ( cd "$PROJECT_ROOT" && $PKG_MANAGER install @mostajs/orm @mostajs/orm-bridge server-only --legacy-peer-deps 2>&1 | tail -3 )
2478
+
2479
+ echo -e "\n\e[1m▶ Step 3/4 : convert schema + init DDL\e[0m"
2480
+ local type="${DETECTED_TYPES[0]}" input
2481
+ case "$type" in
2482
+ prisma) input="$PRISMA_SCHEMA" ;;
2483
+ openapi) input="$OPENAPI_FILE" ;;
2484
+ jsonschema) input="${JSON_SCHEMAS[0]}" ;;
2485
+ esac
2486
+ run_adapter_convert "$type" "$input" "$GENERATED_DIR/entities.ts"
2487
+
2488
+ mkdir -p "$CONFIG_DIR"
2489
+ if [[ ! -f "$CONFIG_DIR/config.env" ]]; then
2490
+ cat > "$CONFIG_DIR/config.env" <<CFG
2491
+ DB_DIALECT=sqlite
2492
+ SGBD_URI=./data.sqlite
2493
+ DB_SCHEMA_STRATEGY=update
2494
+ CFG
2495
+ ok " wrote $CONFIG_DIR/config.env (defaults: sqlite ./data.sqlite)"
2496
+ fi
2497
+ action_init_dialects || warn "DDL init returned non-zero — inspect and retry with menu 3"
2498
+
2499
+ echo -e "\n\e[1m▶ Step 4/4 : done\e[0m"
2500
+ cat <<DONE
2501
+
2502
+ ✓ Bridge installed in-place. Original files backed up as *.prisma.bak
2503
+ ✓ Schema converted : $GENERATED_DIR/entities.json
2504
+ ✓ SQLite DDL applied : $PROJECT_ROOT/data.sqlite
2505
+
2506
+ Next :
2507
+ - Add seeds to $CONFIG_DIR/seeds/*.json (one file per entity)
2508
+ - $CLI_NAME # menu S → h (hash) → 4 (apply)
2509
+ - npm run dev
2510
+
2511
+ To undo the codemod :
2512
+ $CLI_NAME install-bridge --restore --apply
2513
+
2514
+ DONE
2515
+ ;;
2299
2516
  version|-v|--version)
2300
2517
  echo "$CLI_NAME $VERSION"
2301
2518
  ;;
@@ -2303,14 +2520,21 @@ run_subcommand() {
2303
2520
  cat <<EOF
2304
2521
  Usage :
2305
2522
  $CLI_NAME Interactive menu
2523
+ $CLI_NAME bootstrap One-shot migration : codemod + deps + convert + DDL
2524
+ $CLI_NAME install-bridge Codemod only (dry-run ; add --apply to write)
2525
+ $CLI_NAME install-bridge --apply Rewrite PrismaClient sites to use @mostajs/orm-bridge
2526
+ $CLI_NAME install-bridge --restore --apply Undo a prior install-bridge
2306
2527
  $CLI_NAME convert Run conversion (auto-detect schema type)
2307
2528
  $CLI_NAME detect Print detected schemas
2308
2529
  $CLI_NAME health Run health checks
2309
2530
  $CLI_NAME hash <password> [cost] Hash a password with bcrypt (cost default 10)
2310
2531
  $CLI_NAME verify <password> <hash> Check if a plain password matches a bcrypt hash
2532
+ $CLI_NAME diagnose [email] [pw] Walk through login diagnostics
2311
2533
  $CLI_NAME version Print version
2312
2534
 
2313
2535
  Examples:
2536
+ $CLI_NAME bootstrap → zero-touch migrate a Prisma project to @mostajs/orm
2537
+ $CLI_NAME install-bridge → preview rewrites without touching files
2314
2538
  $CLI_NAME hash 'Admin@123456' → \$2b\$10\$N9qo8uLOickgx2ZMRZoMyeIjZA...
2315
2539
  $CLI_NAME verify 'Admin@123456' '\$2b\$10\$N9qo...'
2316
2540
  EOF
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mostajs/orm-cli",
3
- "version": "0.3.1",
4
- "description": "Universal CLI to integrate @mostajs/orm into any project — auto-detects Prisma, OpenAPI, JSON Schema. Interactive menu + subcommands. 13 databases.",
3
+ "version": "0.4.0",
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",
7
7
  "type": "module",