@mostajs/orm-cli 0.4.3 → 0.4.6
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 +49 -12
- package/bin/mostajs.sh +212 -6
- package/package.json +1 -1
package/bin/install-bridge.mjs
CHANGED
|
@@ -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
|
|
34
|
-
const RESTORE
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
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
|
-
|
|
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(
|
|
139
|
+
log(` ${c.green('✓')} restored ${c.dim(rel)}`);
|
|
124
140
|
} else {
|
|
125
|
-
log(` ${c.yellow('•')} would restore ${c.dim(
|
|
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('?')} ${
|
|
185
|
+
log(` ${c.yellow('?')} ${rel} — PrismaClient detected but export shape unknown, skipping`);
|
|
157
186
|
continue;
|
|
158
187
|
}
|
|
159
|
-
candidates.push({ path: p, rel
|
|
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
|
@@ -98,13 +98,32 @@ ensure_pkg() {
|
|
|
98
98
|
warn "Missing package(s): ${missing[*]}"
|
|
99
99
|
if confirm "Install now with $PKG_MANAGER?"; then
|
|
100
100
|
cd "$PROJECT_ROOT" || return 1
|
|
101
|
+
local log_file="/tmp/mostajs-install-$$.log"
|
|
101
102
|
case "$PKG_MANAGER" in
|
|
102
|
-
pnpm) pnpm add "${missing[@]}" 2>&1
|
|
103
|
-
yarn) yarn add "${missing[@]}" 2>&1
|
|
104
|
-
bun) bun add "${missing[@]}" 2>&1
|
|
105
|
-
*) npm install --save "${missing[@]}" --legacy-peer-deps 2>&1
|
|
103
|
+
pnpm) pnpm add "${missing[@]}" >"$log_file" 2>&1 & ;;
|
|
104
|
+
yarn) yarn add "${missing[@]}" >"$log_file" 2>&1 & ;;
|
|
105
|
+
bun) bun add "${missing[@]}" >"$log_file" 2>&1 & ;;
|
|
106
|
+
*) npm install --save "${missing[@]}" --legacy-peer-deps >"$log_file" 2>&1 & ;;
|
|
106
107
|
esac
|
|
107
|
-
local
|
|
108
|
+
local install_pid=$!
|
|
109
|
+
# Braille spinner — visual feedback while the install runs in background
|
|
110
|
+
local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
|
111
|
+
local tick=0
|
|
112
|
+
while kill -0 "$install_pid" 2>/dev/null; do
|
|
113
|
+
local f="${frames:$((tick % 10)):1}"
|
|
114
|
+
local secs=$((tick / 5))
|
|
115
|
+
printf "\r ${YELLOW}%s${RESET} installing ${CYAN}%s${RESET} ${DIM}(%ds)${RESET} " \
|
|
116
|
+
"$f" "${missing[*]}" "$secs"
|
|
117
|
+
tick=$(( tick + 1 ))
|
|
118
|
+
sleep 0.2
|
|
119
|
+
done
|
|
120
|
+
wait "$install_pid"
|
|
121
|
+
local rc=$?
|
|
122
|
+
# Clear the spinner line
|
|
123
|
+
printf "\r%80s\r" ""
|
|
124
|
+
# Show the last lines of the install log (errors or summary)
|
|
125
|
+
tail -5 "$log_file"
|
|
126
|
+
rm -f "$log_file"
|
|
108
127
|
if [[ $rc -ne 0 ]]; then
|
|
109
128
|
err "Install failed"
|
|
110
129
|
return $rc
|
|
@@ -1653,6 +1672,9 @@ menu_seeding() {
|
|
|
1653
1672
|
echo -e " ${CYAN}8${RESET}) Clear the seeds directory"
|
|
1654
1673
|
echo -e " ${CYAN}9${RESET}) Show a seed file"
|
|
1655
1674
|
echo -e " ${CYAN}h${RESET}) ${BOLD}Hash plain-text passwords in seed files${RESET} (bcrypt)"
|
|
1675
|
+
echo -e " ${CYAN}r${RESET}) ${BOLD}Restore seed scripts${RESET} from ${DIM}*.prisma.bak${RESET} (undo install-bridge on seeds)"
|
|
1676
|
+
echo -e " ${CYAN}s${RESET}) ${BOLD}Run seed scripts${RESET} (${DIM}scripts/seed-*.ts | prisma/seed.ts${RESET}) via tsx"
|
|
1677
|
+
echo -e " ${RED}d${RESET}) ${BOLD}Drop table(s)${RESET} — pick one / many / all, then DROP TABLE (DESTRUCTIVE)"
|
|
1656
1678
|
echo
|
|
1657
1679
|
echo -e " ${CYAN}b${RESET}) Back"
|
|
1658
1680
|
echo
|
|
@@ -1669,12 +1691,187 @@ menu_seeding() {
|
|
|
1669
1691
|
8) action_seed_clear ;;
|
|
1670
1692
|
9) action_seed_show ;;
|
|
1671
1693
|
h|H) action_seed_hash_passwords ;;
|
|
1694
|
+
r|R) action_seed_restore_scripts ;;
|
|
1695
|
+
s|S) action_seed_run_scripts ;;
|
|
1696
|
+
d|D) action_drop_tables ;;
|
|
1672
1697
|
b|B) return ;;
|
|
1673
1698
|
*) warn "Unknown"; pause ;;
|
|
1674
1699
|
esac
|
|
1675
1700
|
menu_seeding
|
|
1676
1701
|
}
|
|
1677
1702
|
|
|
1703
|
+
# ------------------------------------------------------------
|
|
1704
|
+
# seed : drop tables (interactive picker)
|
|
1705
|
+
# ------------------------------------------------------------
|
|
1706
|
+
action_drop_tables() {
|
|
1707
|
+
header
|
|
1708
|
+
echo -e "${BOLD}${RED}▶ Drop table(s) — DESTRUCTIVE${RESET}"
|
|
1709
|
+
echo
|
|
1710
|
+
load_env
|
|
1711
|
+
local seed_dir="$CONFIG_DIR/seeds"
|
|
1712
|
+
local entities_json="$GENERATED_DIR/entities.json"
|
|
1713
|
+
if [[ ! -f "$entities_json" ]]; then
|
|
1714
|
+
warn "No entities.json found at $entities_json — run menu 1 (Convert) first."
|
|
1715
|
+
pause; return
|
|
1716
|
+
fi
|
|
1717
|
+
|
|
1718
|
+
# Run a Node helper that lists live tables, lets the user pick, then drops via dialect
|
|
1719
|
+
node --input-type=module -e "
|
|
1720
|
+
import { readFileSync } from 'node:fs';
|
|
1721
|
+
import { createInterface } from 'node:readline/promises';
|
|
1722
|
+
import { stdin, stdout } from 'node:process';
|
|
1723
|
+
import { getDialect } from '${PROJECT_ROOT}/node_modules/@mostajs/orm/dist/index.js';
|
|
1724
|
+
|
|
1725
|
+
const env = readFileSync('${PROJECT_ROOT}/.mostajs/config.env', 'utf8');
|
|
1726
|
+
for (const line of env.split('\n')) {
|
|
1727
|
+
const [k, v] = line.split('=');
|
|
1728
|
+
if (k && v) process.env[k.trim()] = v.trim();
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
const entities = JSON.parse(readFileSync('${PROJECT_ROOT}/.mostajs/generated/entities.json', 'utf8'));
|
|
1732
|
+
const tableSet = new Set(entities.map(e => e.collection));
|
|
1733
|
+
// Add junction tables (many-to-many.through)
|
|
1734
|
+
for (const e of entities) {
|
|
1735
|
+
for (const r of Object.values(e.relations || {})) {
|
|
1736
|
+
if (r && r.type === 'many-to-many' && r.through) tableSet.add(r.through);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const d = await getDialect({
|
|
1741
|
+
dialect: process.env.DB_DIALECT,
|
|
1742
|
+
uri: process.env.SGBD_URI,
|
|
1743
|
+
schemaStrategy: 'none',
|
|
1744
|
+
});
|
|
1745
|
+
|
|
1746
|
+
// Try to list live tables (dialects that expose getTableListQuery via internal call)
|
|
1747
|
+
let live = [];
|
|
1748
|
+
try {
|
|
1749
|
+
const sql = d.getTableListQuery && d.getTableListQuery();
|
|
1750
|
+
if (sql) {
|
|
1751
|
+
const rows = await d.executeQuery(sql, []);
|
|
1752
|
+
live = rows.map(r => r.name || r.TABLE_NAME || r.table_name || Object.values(r)[0]).filter(Boolean);
|
|
1753
|
+
}
|
|
1754
|
+
} catch {}
|
|
1755
|
+
// Intersect with schema-known tables (only show ones we own)
|
|
1756
|
+
const owned = (live.length ? live : Array.from(tableSet)).filter(t => tableSet.has(t)).sort();
|
|
1757
|
+
|
|
1758
|
+
if (owned.length === 0) {
|
|
1759
|
+
console.log(' No tables found that match this project\'s entities.');
|
|
1760
|
+
await d.disconnect();
|
|
1761
|
+
process.exit(0);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
console.log(' Live tables in this project :');
|
|
1765
|
+
owned.forEach((t, i) => console.log(' ' + (i + 1).toString().padStart(2) + ') ' + t));
|
|
1766
|
+
console.log(' a) ALL of the above');
|
|
1767
|
+
console.log(' q) Cancel');
|
|
1768
|
+
|
|
1769
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
1770
|
+
const pick = (await rl.question(' Pick (number, comma-separated, or a) : ')).trim();
|
|
1771
|
+
if (!pick || pick.toLowerCase() === 'q') { rl.close(); await d.disconnect(); console.log(' Cancelled.'); process.exit(0); }
|
|
1772
|
+
|
|
1773
|
+
const targets = pick.toLowerCase() === 'a'
|
|
1774
|
+
? owned
|
|
1775
|
+
: pick.split(',').map(s => s.trim()).map(s => owned[parseInt(s, 10) - 1]).filter(Boolean);
|
|
1776
|
+
|
|
1777
|
+
if (targets.length === 0) { rl.close(); await d.disconnect(); console.log(' Nothing to drop.'); process.exit(0); }
|
|
1778
|
+
|
|
1779
|
+
console.log(' About to DROP : ' + targets.join(', '));
|
|
1780
|
+
const confirm = (await rl.question(' Type DROP to confirm : ')).trim();
|
|
1781
|
+
rl.close();
|
|
1782
|
+
if (confirm !== 'DROP') { await d.disconnect(); console.log(' Aborted.'); process.exit(0); }
|
|
1783
|
+
|
|
1784
|
+
let ok = 0, fail = 0;
|
|
1785
|
+
for (const t of targets) {
|
|
1786
|
+
try {
|
|
1787
|
+
await d.dropTable(t);
|
|
1788
|
+
console.log(' ✓ dropped ' + t);
|
|
1789
|
+
ok++;
|
|
1790
|
+
} catch (e) {
|
|
1791
|
+
console.error(' ✗ ' + t + ' : ' + (e.message ?? e));
|
|
1792
|
+
fail++;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
await d.disconnect();
|
|
1796
|
+
console.log('\nDropped : ' + ok + ' · failed : ' + fail);
|
|
1797
|
+
" 2>&1
|
|
1798
|
+
echo
|
|
1799
|
+
pause
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
# ------------------------------------------------------------
|
|
1803
|
+
# seed : restore TS seed scripts from .prisma.bak (undo install-bridge)
|
|
1804
|
+
# ------------------------------------------------------------
|
|
1805
|
+
action_seed_restore_scripts() {
|
|
1806
|
+
header
|
|
1807
|
+
echo -e "${BOLD}${MAGENTA}▶ Restore seed scripts from *.prisma.bak${RESET}"
|
|
1808
|
+
echo
|
|
1809
|
+
echo -e " ${DIM}Scans for seed-like .prisma.bak files that were rewritten by install-bridge${RESET}"
|
|
1810
|
+
echo -e " ${DIM}and moves each backup back to its original filename.${RESET}"
|
|
1811
|
+
echo
|
|
1812
|
+
local cli_dir
|
|
1813
|
+
cli_dir="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1814
|
+
echo -e "${DIM}\$ node install-bridge.mjs --restore-seeds --project \"$PROJECT_ROOT\"${RESET}"
|
|
1815
|
+
node "$cli_dir/bin/install-bridge.mjs" --restore-seeds --project "$PROJECT_ROOT"
|
|
1816
|
+
echo
|
|
1817
|
+
if confirm "Apply the restoration above?"; then
|
|
1818
|
+
node "$cli_dir/bin/install-bridge.mjs" --restore-seeds --apply --project "$PROJECT_ROOT"
|
|
1819
|
+
else
|
|
1820
|
+
dim " Skipped."
|
|
1821
|
+
fi
|
|
1822
|
+
pause
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
# ------------------------------------------------------------
|
|
1826
|
+
# seed : run TS seed scripts via tsx (or node --loader ts-node)
|
|
1827
|
+
# ------------------------------------------------------------
|
|
1828
|
+
action_seed_run_scripts() {
|
|
1829
|
+
header
|
|
1830
|
+
echo -e "${BOLD}${MAGENTA}▶ Run seed scripts${RESET}"
|
|
1831
|
+
echo
|
|
1832
|
+
# Candidate scripts : prisma/seed.ts, scripts/seed*.{ts,js}, scripts/seed.ts
|
|
1833
|
+
local -a candidates=()
|
|
1834
|
+
[[ -f "$PROJECT_ROOT/prisma/seed.ts" ]] && candidates+=("$PROJECT_ROOT/prisma/seed.ts")
|
|
1835
|
+
[[ -f "$PROJECT_ROOT/prisma/seed.js" ]] && candidates+=("$PROJECT_ROOT/prisma/seed.js")
|
|
1836
|
+
if [[ -d "$PROJECT_ROOT/scripts" ]]; then
|
|
1837
|
+
while IFS= read -r f; do candidates+=("$f"); done < <(
|
|
1838
|
+
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
|
|
1839
|
+
)
|
|
1840
|
+
fi
|
|
1841
|
+
if [[ ${#candidates[@]} -eq 0 ]]; then
|
|
1842
|
+
warn "No seed scripts found under prisma/ or scripts/."
|
|
1843
|
+
dim " Expected : prisma/seed.ts | scripts/seed.ts | scripts/seed-*.ts (or .js)"
|
|
1844
|
+
pause
|
|
1845
|
+
return
|
|
1846
|
+
fi
|
|
1847
|
+
echo -e " Found ${CYAN}${#candidates[@]}${RESET} seed script(s):"
|
|
1848
|
+
local i=1
|
|
1849
|
+
for f in "${candidates[@]}"; do
|
|
1850
|
+
echo -e " ${CYAN}$i${RESET}) ${f#$PROJECT_ROOT/}"
|
|
1851
|
+
((i++))
|
|
1852
|
+
done
|
|
1853
|
+
echo -e " ${CYAN}a${RESET}) Run ALL sequentially"
|
|
1854
|
+
echo
|
|
1855
|
+
local pick
|
|
1856
|
+
pick=$(ask "Choice" "a")
|
|
1857
|
+
local runner="npx --yes tsx"
|
|
1858
|
+
command -v tsx >/dev/null && runner="tsx"
|
|
1859
|
+
if [[ "$pick" == "a" || "$pick" == "A" ]]; then
|
|
1860
|
+
for f in "${candidates[@]}"; do
|
|
1861
|
+
echo -e "${CYAN}▶ ${runner} ${f#$PROJECT_ROOT/}${RESET}"
|
|
1862
|
+
(cd "$PROJECT_ROOT" && $runner "$f") || { warn "Script failed: $f"; pause; return; }
|
|
1863
|
+
done
|
|
1864
|
+
ok "All seed scripts ran successfully."
|
|
1865
|
+
elif [[ "$pick" =~ ^[0-9]+$ ]] && (( pick >= 1 && pick <= ${#candidates[@]} )); then
|
|
1866
|
+
local f="${candidates[$((pick-1))]}"
|
|
1867
|
+
echo -e "${CYAN}▶ ${runner} ${f#$PROJECT_ROOT/}${RESET}"
|
|
1868
|
+
(cd "$PROJECT_ROOT" && $runner "$f") && ok "Done." || warn "Script failed."
|
|
1869
|
+
else
|
|
1870
|
+
warn "Unknown choice"
|
|
1871
|
+
fi
|
|
1872
|
+
pause
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1678
1875
|
# ------------------------------------------------------------
|
|
1679
1876
|
# seed : hash plain-text passwords
|
|
1680
1877
|
# ------------------------------------------------------------
|
|
@@ -1995,6 +2192,15 @@ function validateRow(row, entity) {
|
|
|
1995
2192
|
const errors = [];
|
|
1996
2193
|
const fieldNames = new Set(Object.keys(entity.fields ?? {}));
|
|
1997
2194
|
const relationNames = new Set(Object.keys(entity.relations ?? {}));
|
|
2195
|
+
// FK columns generated by relations (many-to-one / one-to-one joinColumn, plus
|
|
2196
|
+
// the conventional <relName>Id fallback). These appear in seed JSONs as direct
|
|
2197
|
+
// FK values ("userId": "user-admin") and must NOT be flagged as "unknown field".
|
|
2198
|
+
const relationFkColumns = new Set();
|
|
2199
|
+
for (const [relName, rel] of Object.entries(entity.relations ?? {})) {
|
|
2200
|
+
if (rel && (rel.type === 'many-to-one' || rel.type === 'one-to-one')) {
|
|
2201
|
+
relationFkColumns.add(rel.joinColumn || (relName + 'Id'));
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
1998
2204
|
// Required fields
|
|
1999
2205
|
for (const [k, def] of Object.entries(entity.fields ?? {})) {
|
|
2000
2206
|
if (def.required && (row[k] === undefined || row[k] === null)) {
|
|
@@ -2017,7 +2223,7 @@ function validateRow(row, entity) {
|
|
|
2017
2223
|
// Unknown fields (warn-level)
|
|
2018
2224
|
const warnings = [];
|
|
2019
2225
|
for (const k of Object.keys(row)) {
|
|
2020
|
-
if (!fieldNames.has(k) && !relationNames.has(k) && k !== 'id' && k !== '_id') {
|
|
2226
|
+
if (!fieldNames.has(k) && !relationNames.has(k) && !relationFkColumns.has(k) && k !== 'id' && k !== '_id') {
|
|
2021
2227
|
warnings.push('unknown field "' + k + '" (not in schema)');
|
|
2022
2228
|
}
|
|
2023
2229
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/orm-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
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",
|