@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.
- package/bin/install-bridge.mjs +198 -0
- package/bin/mostajs.sh +224 -0
- package/package.json +2 -2
|
@@ -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.
|
|
4
|
-
"description": "Universal CLI to integrate @mostajs/orm into any project —
|
|
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",
|