@mostajs/orm-cli 0.1.0 → 0.2.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/mostajs.sh +586 -100
- package/package.json +1 -1
package/bin/mostajs.sh
CHANGED
|
@@ -53,6 +53,110 @@ warn() { echo -e " ${YELLOW}⚠${RESET} $*"; }
|
|
|
53
53
|
err() { echo -e " ${RED}✗${RESET} $*"; }
|
|
54
54
|
dim() { echo -e " ${DIM}$*${RESET}"; }
|
|
55
55
|
|
|
56
|
+
# ============================================================
|
|
57
|
+
# ERROR HANDLING + AUTO-INSTALL
|
|
58
|
+
# ============================================================
|
|
59
|
+
|
|
60
|
+
# Run a node command with clean error reporting.
|
|
61
|
+
# Usage: run_node <script_path> [env=val ...]
|
|
62
|
+
run_node() {
|
|
63
|
+
local script="$1"; shift
|
|
64
|
+
local envvars=("$@")
|
|
65
|
+
local rc=0
|
|
66
|
+
if [[ ${#envvars[@]} -gt 0 ]]; then
|
|
67
|
+
env "${envvars[@]}" node "$script" 2>&1
|
|
68
|
+
else
|
|
69
|
+
node "$script" 2>&1
|
|
70
|
+
fi
|
|
71
|
+
rc=${PIPESTATUS[0]}
|
|
72
|
+
if [[ $rc -ne 0 ]]; then
|
|
73
|
+
err "Script failed (exit $rc)"
|
|
74
|
+
return $rc
|
|
75
|
+
fi
|
|
76
|
+
return 0
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Check if a package is installed locally; if not, offer to install it.
|
|
80
|
+
# Usage: ensure_pkg <package-name> [<additional-pkg> ...]
|
|
81
|
+
ensure_pkg() {
|
|
82
|
+
local pkgs=("$@")
|
|
83
|
+
local missing=()
|
|
84
|
+
for pkg in "${pkgs[@]}"; do
|
|
85
|
+
# Try to resolve via node's require resolution (works for any layout)
|
|
86
|
+
if ! node -e "require.resolve('$pkg')" >/dev/null 2>&1; then
|
|
87
|
+
# Also check if there's a node_modules with it
|
|
88
|
+
if [[ ! -d "$PROJECT_ROOT/node_modules/$pkg" ]]; then
|
|
89
|
+
missing+=("$pkg")
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
|
|
94
|
+
if [[ ${#missing[@]} -eq 0 ]]; then
|
|
95
|
+
return 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
warn "Missing package(s): ${missing[*]}"
|
|
99
|
+
if confirm "Install now with $PKG_MANAGER?"; then
|
|
100
|
+
cd "$PROJECT_ROOT" || return 1
|
|
101
|
+
case "$PKG_MANAGER" in
|
|
102
|
+
pnpm) pnpm add "${missing[@]}" 2>&1 | tail -5 ;;
|
|
103
|
+
yarn) yarn add "${missing[@]}" 2>&1 | tail -5 ;;
|
|
104
|
+
bun) bun add "${missing[@]}" 2>&1 | tail -5 ;;
|
|
105
|
+
*) npm install --save "${missing[@]}" --legacy-peer-deps 2>&1 | tail -5 ;;
|
|
106
|
+
esac
|
|
107
|
+
local rc=${PIPESTATUS[0]}
|
|
108
|
+
if [[ $rc -ne 0 ]]; then
|
|
109
|
+
err "Install failed"
|
|
110
|
+
return $rc
|
|
111
|
+
fi
|
|
112
|
+
ok "Installed"
|
|
113
|
+
return 0
|
|
114
|
+
else
|
|
115
|
+
err "Cannot proceed without: ${missing[*]}"
|
|
116
|
+
return 1
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Resolve path to a specific installed package module file
|
|
121
|
+
# Usage: resolve_pkg <package>/dist/index.js
|
|
122
|
+
# Writes path to stdout; returns 0 on success, 1 on failure
|
|
123
|
+
resolve_pkg_path() {
|
|
124
|
+
local pkg="$1"
|
|
125
|
+
local result
|
|
126
|
+
result=$(node -e "
|
|
127
|
+
try {
|
|
128
|
+
const p = require.resolve('$pkg', { paths: [process.cwd(), '$PROJECT_ROOT'] });
|
|
129
|
+
console.log(p);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
" 2>/dev/null) || return 1
|
|
134
|
+
echo "$result"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Check if a driver is needed for the given dialect, and install it if missing.
|
|
138
|
+
# Usage: ensure_dialect_driver <dialect>
|
|
139
|
+
ensure_dialect_driver() {
|
|
140
|
+
local dialect="$1"
|
|
141
|
+
local driver=""
|
|
142
|
+
case "$dialect" in
|
|
143
|
+
sqlite) driver="better-sqlite3" ;;
|
|
144
|
+
postgres|cockroachdb) driver="pg" ;;
|
|
145
|
+
mysql) driver="mysql2" ;;
|
|
146
|
+
mariadb) driver="mariadb" ;;
|
|
147
|
+
mssql) driver="mssql" ;;
|
|
148
|
+
oracle) driver="oracledb" ;;
|
|
149
|
+
db2) driver="ibm_db" ;;
|
|
150
|
+
hana) driver="@sap/hana-client" ;;
|
|
151
|
+
spanner) driver="@google-cloud/spanner" ;;
|
|
152
|
+
sybase) driver="sybase" ;;
|
|
153
|
+
mongodb) driver="mongoose" ;;
|
|
154
|
+
*) return 0 ;;
|
|
155
|
+
esac
|
|
156
|
+
[[ -z "$driver" ]] && return 0
|
|
157
|
+
ensure_pkg "$driver"
|
|
158
|
+
}
|
|
159
|
+
|
|
56
160
|
pause() {
|
|
57
161
|
echo
|
|
58
162
|
read -n 1 -r -s -p "$(echo -e "${DIM}Press any key...${RESET}")" || true
|
|
@@ -154,29 +258,34 @@ run_adapter_convert() {
|
|
|
154
258
|
local input_file="$2"
|
|
155
259
|
local output_file="$3"
|
|
156
260
|
|
|
157
|
-
#
|
|
261
|
+
# Ensure @mostajs/orm-adapter is available — auto-install if missing
|
|
262
|
+
info "Checking @mostajs/orm-adapter..."
|
|
158
263
|
local adapter_path=""
|
|
264
|
+
|
|
265
|
+
# 1. Try local project install
|
|
159
266
|
if [[ -f "$PROJECT_ROOT/node_modules/@mostajs/orm-adapter/dist/index.js" ]]; then
|
|
160
267
|
adapter_path="$PROJECT_ROOT/node_modules/@mostajs/orm-adapter/dist/index.js"
|
|
161
|
-
|
|
268
|
+
ok "Using local install"
|
|
162
269
|
else
|
|
163
|
-
# Try
|
|
270
|
+
# 2. Try sibling (dev setup)
|
|
164
271
|
local cli_dir
|
|
165
272
|
cli_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
166
273
|
if [[ -f "$cli_dir/../mosta-orm-adapter/dist/index.js" ]]; then
|
|
167
274
|
adapter_path="$cli_dir/../mosta-orm-adapter/dist/index.js"
|
|
168
|
-
info "Using sibling
|
|
275
|
+
info "Using sibling dev install"
|
|
169
276
|
else
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
case "$PKG_MANAGER" in
|
|
174
|
-
pnpm) pnpm add -D @mostajs/orm-adapter @mostajs/orm ;;
|
|
175
|
-
yarn) yarn add -D @mostajs/orm-adapter @mostajs/orm ;;
|
|
176
|
-
bun) bun add -D @mostajs/orm-adapter @mostajs/orm ;;
|
|
177
|
-
*) npm install --save-dev @mostajs/orm-adapter @mostajs/orm --legacy-peer-deps ;;
|
|
178
|
-
esac
|
|
277
|
+
# 3. Offer auto-install
|
|
278
|
+
warn "Not installed locally"
|
|
279
|
+
if ensure_pkg "@mostajs/orm-adapter" "@mostajs/orm"; then
|
|
179
280
|
adapter_path="$PROJECT_ROOT/node_modules/@mostajs/orm-adapter/dist/index.js"
|
|
281
|
+
if [[ ! -f "$adapter_path" ]]; then
|
|
282
|
+
# Try via require.resolve
|
|
283
|
+
adapter_path=$(resolve_pkg_path "@mostajs/orm-adapter") || {
|
|
284
|
+
err "Install reported success but module cannot be resolved."
|
|
285
|
+
return 1
|
|
286
|
+
}
|
|
287
|
+
fi
|
|
288
|
+
ok "Installed"
|
|
180
289
|
else
|
|
181
290
|
err "Cannot proceed without the adapter."
|
|
182
291
|
return 1
|
|
@@ -194,13 +303,42 @@ run_adapter_convert() {
|
|
|
194
303
|
|
|
195
304
|
cat > "$CONFIG_DIR/convert.mjs" << EOF
|
|
196
305
|
import { readFileSync, writeFileSync } from 'fs';
|
|
197
|
-
import { $adapter_class } from '$adapter_path';
|
|
198
306
|
|
|
199
|
-
|
|
307
|
+
let adapterModule;
|
|
308
|
+
try {
|
|
309
|
+
adapterModule = await import('$adapter_path');
|
|
310
|
+
} catch (e) {
|
|
311
|
+
console.error('Failed to import adapter from $adapter_path');
|
|
312
|
+
console.error('Reason :', e.message);
|
|
313
|
+
process.exit(2);
|
|
314
|
+
}
|
|
315
|
+
const { $adapter_class } = adapterModule;
|
|
316
|
+
if (!$adapter_class) {
|
|
317
|
+
console.error('$adapter_class not exported from the adapter module');
|
|
318
|
+
process.exit(3);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let source;
|
|
322
|
+
try {
|
|
323
|
+
source = readFileSync('$input_file', 'utf8');
|
|
324
|
+
} catch (e) {
|
|
325
|
+
console.error('Cannot read input file : $input_file');
|
|
326
|
+
console.error('Reason :', e.message);
|
|
327
|
+
process.exit(4);
|
|
328
|
+
}
|
|
329
|
+
|
|
200
330
|
const adapter = new $adapter_class();
|
|
201
331
|
const warnings = [];
|
|
202
332
|
const input = '$input_type' === 'jsonschema' ? JSON.parse(source) : source;
|
|
203
|
-
|
|
333
|
+
|
|
334
|
+
let entities;
|
|
335
|
+
try {
|
|
336
|
+
entities = await adapter.toEntitySchema(input, { onWarning: w => warnings.push(w) });
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.error('Conversion failed :', e.message);
|
|
339
|
+
if (e.details) console.error('Details :', JSON.stringify(e.details, null, 2).slice(0, 500));
|
|
340
|
+
process.exit(5);
|
|
341
|
+
}
|
|
204
342
|
|
|
205
343
|
console.log('entities : ' + entities.length);
|
|
206
344
|
console.log('warnings : ' + warnings.length);
|
|
@@ -217,12 +355,28 @@ const code = header +
|
|
|
217
355
|
' entities.map(e => [e.name, e])\n' +
|
|
218
356
|
');\n';
|
|
219
357
|
|
|
220
|
-
|
|
221
|
-
|
|
358
|
+
try {
|
|
359
|
+
writeFileSync('$output_file', code);
|
|
360
|
+
// Also write .json (easier to load from ESM without TS support)
|
|
361
|
+
const jsonFile = '$output_file'.replace(/\.ts$/, '.json');
|
|
362
|
+
writeFileSync(jsonFile, JSON.stringify(entities, null, 2));
|
|
363
|
+
console.log('\u2713 Saved : $output_file');
|
|
364
|
+
console.log('\u2713 Saved : ' + jsonFile);
|
|
365
|
+
} catch (e) {
|
|
366
|
+
console.error('Cannot write output : ' + e.message);
|
|
367
|
+
process.exit(6);
|
|
368
|
+
}
|
|
222
369
|
EOF
|
|
223
370
|
|
|
371
|
+
local rc=0
|
|
224
372
|
node "$CONFIG_DIR/convert.mjs" 2>&1 | tee "$LOG_DIR/convert.log"
|
|
225
|
-
|
|
373
|
+
rc=${PIPESTATUS[0]}
|
|
374
|
+
if [[ $rc -ne 0 ]]; then
|
|
375
|
+
err "Conversion exited with code $rc"
|
|
376
|
+
info "See $LOG_DIR/convert.log for details"
|
|
377
|
+
return $rc
|
|
378
|
+
fi
|
|
379
|
+
return 0
|
|
226
380
|
}
|
|
227
381
|
|
|
228
382
|
# ============================================================
|
|
@@ -356,37 +510,43 @@ menu_databases() {
|
|
|
356
510
|
header
|
|
357
511
|
echo -e "${BOLD}${MAGENTA}▶ Database configuration${RESET}"
|
|
358
512
|
echo
|
|
359
|
-
echo -e "
|
|
360
|
-
echo
|
|
361
|
-
echo -e "
|
|
362
|
-
echo -e " ${CYAN}
|
|
363
|
-
echo -e " ${CYAN}
|
|
364
|
-
echo -e " ${CYAN}
|
|
365
|
-
echo -e " ${CYAN}
|
|
366
|
-
echo -e " ${CYAN}
|
|
367
|
-
echo -e " ${CYAN}9${RESET}) CockroachDB : ${DIM}${COCKROACH_URI:-<not set>}${RESET}"
|
|
513
|
+
echo -e "${DIM}Compatible with @mostajs/orm .env convention (see SecuAccessPro/.env.local)${RESET}"
|
|
514
|
+
echo
|
|
515
|
+
echo -e "${BOLD}Primary DB (single backend — 90% of apps) :${RESET}"
|
|
516
|
+
echo -e " ${CYAN}1${RESET}) DB_DIALECT : ${DIM}${DB_DIALECT:-<not set>}${RESET}"
|
|
517
|
+
echo -e " ${CYAN}2${RESET}) SGBD_URI : ${DIM}${SGBD_URI:-<not set>}${RESET}"
|
|
518
|
+
echo -e " ${CYAN}3${RESET}) DB_SCHEMA_STRATEGY : ${DIM}${DB_SCHEMA_STRATEGY:-update}${RESET}"
|
|
519
|
+
echo -e " ${CYAN}4${RESET}) DB_POOL_SIZE : ${DIM}${DB_POOL_SIZE:-20}${RESET}"
|
|
520
|
+
echo -e " ${CYAN}5${RESET}) DB_SHOW_SQL : ${DIM}${DB_SHOW_SQL:-false}${RESET}"
|
|
368
521
|
echo
|
|
369
|
-
echo -e "
|
|
370
|
-
echo -e " ${CYAN}
|
|
522
|
+
echo -e "${BOLD}Extra DBs (hybrid apps with Prisma Bridge) :${RESET}"
|
|
523
|
+
echo -e " ${CYAN}a${RESET}) Add extra binding ${DIM}(e.g. MongoDB for audit while PG is primary)${RESET}"
|
|
524
|
+
echo -e " ${CYAN}l${RESET}) List extra bindings : ${DIM}${EXTRA_BINDINGS:-<none>}${RESET}"
|
|
525
|
+
echo
|
|
526
|
+
echo -e "${BOLD}mosta-net + app :${RESET}"
|
|
527
|
+
echo -e " ${CYAN}u${RESET}) MOSTA_NET_URL : ${DIM}${MOSTA_NET_URL:-http://localhost:14488}${RESET}"
|
|
528
|
+
echo -e " ${CYAN}n${RESET}) MOSTA_NET_TRANSPORT : ${DIM}${MOSTA_NET_TRANSPORT:-rest}${RESET}"
|
|
529
|
+
echo -e " ${CYAN}p${RESET}) APP_PORT : ${DIM}${APP_PORT:-3000}${RESET}"
|
|
371
530
|
echo
|
|
372
531
|
echo -e " ${CYAN}t${RESET}) Test all connections"
|
|
532
|
+
echo -e " ${CYAN}e${RESET}) Export to .env.local in project"
|
|
373
533
|
echo -e " ${CYAN}r${RESET}) Reset config"
|
|
374
534
|
echo -e " ${CYAN}b${RESET}) Back"
|
|
375
535
|
echo
|
|
376
536
|
local choice; choice=$(ask "Choice" "1")
|
|
377
537
|
case "$choice" in
|
|
378
|
-
1)
|
|
379
|
-
2) save_var
|
|
380
|
-
3) save_var
|
|
381
|
-
4) save_var
|
|
382
|
-
5) save_var
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
p|P) save_var APP_PORT
|
|
388
|
-
n|N) save_var MOSTA_NET_PORT "$(ask 'mosta-net port' "${MOSTA_NET_PORT:-4447}")";;
|
|
538
|
+
1) prompt_dialect ;;
|
|
539
|
+
2) save_var SGBD_URI "$(ask 'SGBD_URI (path or connection string)' "${SGBD_URI:-./data.sqlite}")";;
|
|
540
|
+
3) save_var DB_SCHEMA_STRATEGY "$(ask 'DB_SCHEMA_STRATEGY (update|create|validate|none|create-drop)' "${DB_SCHEMA_STRATEGY:-update}")";;
|
|
541
|
+
4) save_var DB_POOL_SIZE "$(ask 'DB_POOL_SIZE' "${DB_POOL_SIZE:-20}")";;
|
|
542
|
+
5) save_var DB_SHOW_SQL "$(ask 'DB_SHOW_SQL (true|false)' "${DB_SHOW_SQL:-false}")";;
|
|
543
|
+
a|A) add_extra_binding ;;
|
|
544
|
+
l|L) list_extra_bindings; pause ;;
|
|
545
|
+
u|U) save_var MOSTA_NET_URL "$(ask 'MOSTA_NET_URL' "${MOSTA_NET_URL:-http://localhost:14488}")";;
|
|
546
|
+
n|N) save_var MOSTA_NET_TRANSPORT "$(ask 'MOSTA_NET_TRANSPORT (rest|sse|graphql|mcp|websocket|jsonrpc|grpc|odata)' "${MOSTA_NET_TRANSPORT:-rest}")";;
|
|
547
|
+
p|P) save_var APP_PORT "$(ask 'APP_PORT' "${APP_PORT:-3000}")";;
|
|
389
548
|
t|T) action_test_connections; return;;
|
|
549
|
+
e|E) export_env_local ;;
|
|
390
550
|
r|R) confirm "Really reset config?" && rm -f "$CONFIG_FILE" && ok "Reset";;
|
|
391
551
|
b|B) return;;
|
|
392
552
|
*) warn "Unknown";;
|
|
@@ -395,38 +555,235 @@ menu_databases() {
|
|
|
395
555
|
menu_databases
|
|
396
556
|
}
|
|
397
557
|
|
|
558
|
+
# Prompt user to choose a dialect from a numbered list
|
|
559
|
+
prompt_dialect() {
|
|
560
|
+
echo
|
|
561
|
+
echo "Pick a dialect:"
|
|
562
|
+
local i=1
|
|
563
|
+
local -a dialects=(sqlite postgres mysql mariadb mongodb mssql oracle db2 cockroachdb hana hsqldb spanner sybase)
|
|
564
|
+
for d in "${dialects[@]}"; do
|
|
565
|
+
echo -e " ${CYAN}$i${RESET}) $d"
|
|
566
|
+
i=$((i+1))
|
|
567
|
+
done
|
|
568
|
+
local num; num=$(ask "Number" 1)
|
|
569
|
+
local idx=$((num-1))
|
|
570
|
+
if [[ $idx -ge 0 && $idx -lt ${#dialects[@]} ]]; then
|
|
571
|
+
local d="${dialects[$idx]}"
|
|
572
|
+
save_var DB_DIALECT "$d"
|
|
573
|
+
# Suggest default URI for that dialect if SGBD_URI is empty
|
|
574
|
+
if [[ -z "${SGBD_URI:-}" ]]; then
|
|
575
|
+
local suggest
|
|
576
|
+
case "$d" in
|
|
577
|
+
sqlite) suggest="./data.sqlite" ;;
|
|
578
|
+
postgres) suggest="postgres://user:pw@localhost:5432/app" ;;
|
|
579
|
+
mysql|mariadb) suggest="mysql://user:pw@localhost:3306/app" ;;
|
|
580
|
+
mongodb) suggest="mongodb://localhost:27017/app" ;;
|
|
581
|
+
mssql) suggest="mssql://user:pw@localhost:1433/app" ;;
|
|
582
|
+
oracle) suggest="oracle://user:pw@localhost:1521/ORCLPDB" ;;
|
|
583
|
+
db2) suggest="db2://user:pw@localhost:50000/app" ;;
|
|
584
|
+
cockroachdb) suggest="postgres://user@localhost:26257/app" ;;
|
|
585
|
+
hana) suggest="hana://user:pw@localhost:39041" ;;
|
|
586
|
+
*) suggest="" ;;
|
|
587
|
+
esac
|
|
588
|
+
[[ -n "$suggest" ]] && save_var SGBD_URI "$(ask 'SGBD_URI' "$suggest")"
|
|
589
|
+
fi
|
|
590
|
+
else
|
|
591
|
+
warn "Invalid number"
|
|
592
|
+
fi
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
# Add an extra dialect binding for hybrid apps
|
|
596
|
+
add_extra_binding() {
|
|
597
|
+
local name; name=$(ask "Binding name (e.g. AuditLog, Reports) — used by the Prisma Bridge")
|
|
598
|
+
[[ -z "$name" ]] && return
|
|
599
|
+
local dialect; dialect=$(ask "Dialect for $name (sqlite|postgres|mongodb|oracle|...)")
|
|
600
|
+
[[ -z "$dialect" ]] && return
|
|
601
|
+
local uri; uri=$(ask "URI for $name")
|
|
602
|
+
[[ -z "$uri" ]] && return
|
|
603
|
+
local current="${EXTRA_BINDINGS:-}"
|
|
604
|
+
local new="${name}:${dialect}:${uri}"
|
|
605
|
+
if [[ -z "$current" ]]; then
|
|
606
|
+
save_var EXTRA_BINDINGS "$new"
|
|
607
|
+
else
|
|
608
|
+
save_var EXTRA_BINDINGS "${current};${new}"
|
|
609
|
+
fi
|
|
610
|
+
ok "Added: $name ($dialect @ $uri)"
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
list_extra_bindings() {
|
|
614
|
+
echo
|
|
615
|
+
if [[ -z "${EXTRA_BINDINGS:-}" ]]; then
|
|
616
|
+
dim " (none)"
|
|
617
|
+
return
|
|
618
|
+
fi
|
|
619
|
+
echo -e "${BOLD}Extra bindings:${RESET}"
|
|
620
|
+
local IFS=';'
|
|
621
|
+
for b in $EXTRA_BINDINGS; do
|
|
622
|
+
local name="${b%%:*}"
|
|
623
|
+
local rest="${b#*:}"
|
|
624
|
+
local dialect="${rest%%:*}"
|
|
625
|
+
local uri="${rest#*:}"
|
|
626
|
+
echo -e " ${CYAN}$name${RESET} → ${MAGENTA}$dialect${RESET} @ ${DIM}$uri${RESET}"
|
|
627
|
+
done
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
# Export a .env.local file compatible with @mostajs/orm convention
|
|
631
|
+
export_env_local() {
|
|
632
|
+
local target="$PROJECT_ROOT/.env.mostajs"
|
|
633
|
+
cat > "$target" <<EOF
|
|
634
|
+
# Generated by @mostajs/orm-cli on $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
635
|
+
# Primary database
|
|
636
|
+
DB_DIALECT=${DB_DIALECT:-sqlite}
|
|
637
|
+
SGBD_URI=${SGBD_URI:-./data.sqlite}
|
|
638
|
+
DB_SCHEMA_STRATEGY=${DB_SCHEMA_STRATEGY:-update}
|
|
639
|
+
DB_POOL_SIZE=${DB_POOL_SIZE:-20}
|
|
640
|
+
DB_SHOW_SQL=${DB_SHOW_SQL:-false}
|
|
641
|
+
|
|
642
|
+
# mosta-net server
|
|
643
|
+
MOSTA_NET_URL=${MOSTA_NET_URL:-http://localhost:14488}
|
|
644
|
+
MOSTA_NET_TRANSPORT=${MOSTA_NET_TRANSPORT:-rest}
|
|
645
|
+
|
|
646
|
+
# App
|
|
647
|
+
APP_PORT=${APP_PORT:-3000}
|
|
648
|
+
|
|
649
|
+
# Extra bindings for Prisma Bridge (hybrid apps)
|
|
650
|
+
# Format: EXTRA_BINDINGS="ModelName:dialect:uri;OtherModel:dialect:uri"
|
|
651
|
+
EXTRA_BINDINGS=${EXTRA_BINDINGS:-}
|
|
652
|
+
EOF
|
|
653
|
+
ok "Exported : $target"
|
|
654
|
+
info "Review, rename to .env.local, and commit to your .env.example (without secrets)"
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
# Auto-detect mosta-orm dialect from URI scheme
|
|
658
|
+
detect_dialect_from_uri() {
|
|
659
|
+
local uri="$1"
|
|
660
|
+
case "$uri" in
|
|
661
|
+
mongodb://*|mongodb+srv://*) echo "mongodb" ;;
|
|
662
|
+
postgres://*|postgresql://*) echo "postgres" ;;
|
|
663
|
+
mysql://*) echo "mysql" ;;
|
|
664
|
+
mariadb://*) echo "mariadb" ;;
|
|
665
|
+
mssql://*|sqlserver://*) echo "mssql" ;;
|
|
666
|
+
oracle://*) echo "oracle" ;;
|
|
667
|
+
db2://*) echo "db2" ;;
|
|
668
|
+
hana://*) echo "hana" ;;
|
|
669
|
+
cockroachdb://*) echo "cockroachdb" ;;
|
|
670
|
+
spanner://*) echo "spanner" ;;
|
|
671
|
+
sybase://*) echo "sybase" ;;
|
|
672
|
+
sqlite://*|sqlite:*|*.sqlite|*.db|:memory:) echo "sqlite" ;;
|
|
673
|
+
*) echo "unknown" ;;
|
|
674
|
+
esac
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
# Strip scheme prefix from URI (used for SQLite path)
|
|
678
|
+
strip_uri_scheme() {
|
|
679
|
+
local uri="$1"
|
|
680
|
+
case "$uri" in
|
|
681
|
+
sqlite://*) echo "${uri#sqlite://}" ;;
|
|
682
|
+
sqlite:*) echo "${uri#sqlite:}" ;;
|
|
683
|
+
*) echo "$uri" ;;
|
|
684
|
+
esac
|
|
685
|
+
}
|
|
686
|
+
|
|
398
687
|
action_test_connections() {
|
|
399
688
|
header
|
|
400
|
-
echo -e "${BOLD}${MAGENTA}▶ Testing connections${RESET}"
|
|
689
|
+
echo -e "${BOLD}${MAGENTA}▶ Testing connections (via @mostajs/orm)${RESET}"
|
|
401
690
|
echo
|
|
402
691
|
load_env
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
local
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
692
|
+
|
|
693
|
+
# Collect all configured URIs as (dialect, uri) pairs
|
|
694
|
+
local -a pairs=()
|
|
695
|
+
|
|
696
|
+
# Primary DB
|
|
697
|
+
if [[ -n "${DB_DIALECT:-}" && -n "${SGBD_URI:-}" ]]; then
|
|
698
|
+
pairs+=("${DB_DIALECT}|${SGBD_URI}")
|
|
699
|
+
fi
|
|
700
|
+
|
|
701
|
+
# Extra bindings (format: name:dialect:uri;name:dialect:uri)
|
|
702
|
+
if [[ -n "${EXTRA_BINDINGS:-}" ]]; then
|
|
703
|
+
local IFS=';'
|
|
704
|
+
for b in $EXTRA_BINDINGS; do
|
|
705
|
+
local rest="${b#*:}" # strip name
|
|
706
|
+
local dialect="${rest%%:*}"
|
|
707
|
+
local uri="${rest#*:}"
|
|
708
|
+
pairs+=("$dialect|$uri")
|
|
709
|
+
done
|
|
710
|
+
IFS=$' \t\n'
|
|
711
|
+
fi
|
|
712
|
+
|
|
713
|
+
if [[ ${#pairs[@]} -eq 0 ]]; then
|
|
714
|
+
warn "No URIs configured. Go to menu 2 first."
|
|
715
|
+
pause; return
|
|
716
|
+
fi
|
|
717
|
+
|
|
718
|
+
# Ensure @mostajs/orm is installed (test uses its native testConnection)
|
|
719
|
+
info "Checking @mostajs/orm installation..."
|
|
720
|
+
if ! ensure_pkg "@mostajs/orm"; then
|
|
721
|
+
err "Cannot test connections without @mostajs/orm"
|
|
722
|
+
pause; return
|
|
723
|
+
fi
|
|
724
|
+
|
|
725
|
+
# Ensure drivers for each dialect being tested
|
|
726
|
+
info "Checking drivers..."
|
|
727
|
+
for p in "${pairs[@]}"; do
|
|
728
|
+
local d="${p%%|*}"
|
|
729
|
+
ensure_dialect_driver "$d" || warn "Driver for $d may be missing"
|
|
730
|
+
done
|
|
731
|
+
|
|
732
|
+
local orm_path
|
|
733
|
+
orm_path=$(resolve_pkg_path "@mostajs/orm") || {
|
|
734
|
+
err "Cannot resolve @mostajs/orm"
|
|
735
|
+
pause; return
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
# Build a small node script that tests each connection
|
|
739
|
+
local -a args=()
|
|
740
|
+
for p in "${pairs[@]}"; do
|
|
741
|
+
args+=("$p")
|
|
427
742
|
done
|
|
428
743
|
|
|
429
|
-
|
|
744
|
+
cat > "$CONFIG_DIR/test-connections.mjs" <<EOF
|
|
745
|
+
import { getDialect } from '$orm_path';
|
|
746
|
+
|
|
747
|
+
const pairs = process.argv.slice(2).map(s => {
|
|
748
|
+
const i = s.indexOf('|');
|
|
749
|
+
return [s.slice(0, i), s.slice(i + 1)];
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
function stripScheme(uri) {
|
|
753
|
+
if (uri.startsWith('sqlite://')) return uri.slice(9);
|
|
754
|
+
if (uri.startsWith('sqlite:')) return uri.slice(7);
|
|
755
|
+
return uri;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
let ok = 0, fail = 0;
|
|
759
|
+
for (const [dialect, rawUri] of pairs) {
|
|
760
|
+
const uri = dialect === 'sqlite' ? stripScheme(rawUri) : rawUri;
|
|
761
|
+
process.stdout.write(dialect.padEnd(12) + ' ' + rawUri + '\n');
|
|
762
|
+
try {
|
|
763
|
+
const d = await getDialect({ dialect, uri });
|
|
764
|
+
const alive = await d.testConnection();
|
|
765
|
+
if (alive) {
|
|
766
|
+
console.log(' \u2713 reachable');
|
|
767
|
+
ok++;
|
|
768
|
+
} else {
|
|
769
|
+
console.log(' \u2717 testConnection returned false');
|
|
770
|
+
fail++;
|
|
771
|
+
}
|
|
772
|
+
await d.disconnect().catch(() => {});
|
|
773
|
+
} catch (e) {
|
|
774
|
+
console.error(' \u2717 ' + (e.message ?? e));
|
|
775
|
+
if (e.code) console.error(' code : ' + e.code);
|
|
776
|
+
fail++;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
console.log();
|
|
780
|
+
console.log('Results : ' + ok + ' reachable, ' + fail + ' failed');
|
|
781
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
782
|
+
EOF
|
|
783
|
+
|
|
784
|
+
cd "$PROJECT_ROOT"
|
|
785
|
+
node "$CONFIG_DIR/test-connections.mjs" "${args[@]}" 2>&1 | tee "$LOG_DIR/test-connections.log"
|
|
786
|
+
echo
|
|
430
787
|
pause
|
|
431
788
|
}
|
|
432
789
|
|
|
@@ -445,46 +802,139 @@ action_init_dialects() {
|
|
|
445
802
|
pause; return
|
|
446
803
|
fi
|
|
447
804
|
|
|
448
|
-
|
|
449
|
-
local
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
805
|
+
# Collect configured dialects from DB_DIALECT/SGBD_URI + EXTRA_BINDINGS
|
|
806
|
+
local -a configured=()
|
|
807
|
+
local -a dialect_names=()
|
|
808
|
+
|
|
809
|
+
if [[ -n "${DB_DIALECT:-}" && -n "${SGBD_URI:-}" ]]; then
|
|
810
|
+
configured+=("${DB_DIALECT}:${SGBD_URI}")
|
|
811
|
+
dialect_names+=("$DB_DIALECT")
|
|
812
|
+
fi
|
|
813
|
+
|
|
814
|
+
if [[ -n "${EXTRA_BINDINGS:-}" ]]; then
|
|
815
|
+
local IFS=';'
|
|
816
|
+
for b in $EXTRA_BINDINGS; do
|
|
817
|
+
local rest="${b#*:}"
|
|
818
|
+
local dialect="${rest%%:*}"
|
|
819
|
+
local uri="${rest#*:}"
|
|
820
|
+
configured+=("${dialect}:${uri}")
|
|
821
|
+
dialect_names+=("$dialect")
|
|
822
|
+
done
|
|
823
|
+
IFS=$' \t\n'
|
|
824
|
+
fi
|
|
825
|
+
|
|
826
|
+
if [[ ${#configured[@]} -eq 0 ]]; then
|
|
827
|
+
err "No URIs set. Menu 2 first."
|
|
828
|
+
pause; return
|
|
829
|
+
fi
|
|
830
|
+
|
|
831
|
+
info "Will attempt to initialize:"
|
|
832
|
+
for item in "${configured[@]}"; do
|
|
833
|
+
dim " ${item%%:*} → ${item#*:}"
|
|
453
834
|
done
|
|
454
|
-
|
|
835
|
+
echo
|
|
455
836
|
|
|
456
837
|
confirm "Proceed?" || return
|
|
457
838
|
|
|
458
|
-
|
|
839
|
+
# ---- Step 1 : ensure @mostajs/orm is installed ----
|
|
840
|
+
info "Step 1/3 : checking @mostajs/orm installation..."
|
|
841
|
+
if ! ensure_pkg "@mostajs/orm"; then
|
|
842
|
+
err "Cannot initialize dialects without @mostajs/orm"
|
|
843
|
+
pause; return
|
|
844
|
+
fi
|
|
845
|
+
ok "@mostajs/orm available"
|
|
846
|
+
|
|
847
|
+
# ---- Step 2 : ensure drivers for each dialect are installed ----
|
|
848
|
+
info "Step 2/3 : checking dialect drivers..."
|
|
849
|
+
for dialect in "${dialect_names[@]}"; do
|
|
850
|
+
ensure_dialect_driver "$dialect" || warn "Driver for $dialect may be missing"
|
|
851
|
+
done
|
|
852
|
+
ok "Drivers checked"
|
|
853
|
+
|
|
854
|
+
# ---- Step 3 : resolve absolute path to @mostajs/orm (avoids import resolution issues) ----
|
|
855
|
+
local orm_path
|
|
856
|
+
orm_path=$(resolve_pkg_path "@mostajs/orm") || {
|
|
857
|
+
err "Could not resolve @mostajs/orm path even after install"
|
|
858
|
+
pause; return
|
|
859
|
+
}
|
|
860
|
+
dim "Using @mostajs/orm at : $orm_path"
|
|
861
|
+
echo
|
|
862
|
+
|
|
863
|
+
# Pass each dialect:uri pair as argv to the node script
|
|
864
|
+
cat > "$CONFIG_DIR/init-all.mjs" <<EOF
|
|
865
|
+
// Auto-generated by mostajs-cli — runs from project root
|
|
459
866
|
import { readFileSync } from 'fs';
|
|
460
|
-
import { getDialect } from '
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
867
|
+
import { getDialect } from '$orm_path';
|
|
868
|
+
|
|
869
|
+
let entities;
|
|
870
|
+
try {
|
|
871
|
+
entities = JSON.parse(readFileSync('$GENERATED_DIR/entities.json', 'utf8'));
|
|
872
|
+
} catch (e) {
|
|
873
|
+
console.error('Cannot load entities.json — run menu 1 (Convert) first.');
|
|
874
|
+
console.error('Reason : ' + e.message);
|
|
875
|
+
process.exit(1);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function stripScheme(uri) {
|
|
879
|
+
if (uri.startsWith('sqlite://')) return uri.slice(9);
|
|
880
|
+
if (uri.startsWith('sqlite:')) return uri.slice(7);
|
|
881
|
+
return uri;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const pairs = process.argv.slice(2).map(s => {
|
|
885
|
+
const i = s.indexOf('|');
|
|
886
|
+
return [s.slice(0, i), s.slice(i + 1)];
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
let ok = 0, fail = 0;
|
|
890
|
+
const schemaStrategy = process.env.DB_SCHEMA_STRATEGY ?? 'update';
|
|
891
|
+
const poolSize = parseInt(process.env.DB_POOL_SIZE ?? '20', 10);
|
|
892
|
+
const showSql = process.env.DB_SHOW_SQL === 'true';
|
|
893
|
+
|
|
894
|
+
for (const [dialect, rawUri] of pairs) {
|
|
895
|
+
const uri = dialect === 'sqlite' ? stripScheme(rawUri) : rawUri;
|
|
896
|
+
process.stdout.write('→ ' + dialect.padEnd(12) + ' : ' + rawUri + '\n');
|
|
476
897
|
try {
|
|
477
|
-
const d = await getDialect({ dialect, uri, schemaStrategy
|
|
898
|
+
const d = await getDialect({ dialect, uri, schemaStrategy, poolSize, showSql });
|
|
478
899
|
await d.initSchema(entities);
|
|
479
|
-
console.log(
|
|
480
|
-
await d.disconnect();
|
|
900
|
+
console.log(' ✓ ' + dialect + ' ready (' + entities.length + ' entities)');
|
|
901
|
+
await d.disconnect().catch(() => {});
|
|
902
|
+
ok++;
|
|
481
903
|
} catch (e) {
|
|
482
|
-
console.error(
|
|
904
|
+
console.error(' ✗ ' + dialect + ' failed : ' + (e.message ?? e));
|
|
905
|
+
if (e.code) console.error(' code : ' + e.code);
|
|
906
|
+
fail++;
|
|
483
907
|
}
|
|
484
908
|
}
|
|
909
|
+
console.log();
|
|
910
|
+
console.log('Summary : ' + ok + ' succeeded, ' + fail + ' failed');
|
|
911
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
485
912
|
EOF
|
|
486
|
-
|
|
487
|
-
|
|
913
|
+
|
|
914
|
+
info "Step 3/3 : running initialization..."
|
|
915
|
+
cd "$PROJECT_ROOT" || return
|
|
916
|
+
|
|
917
|
+
# Pass each dialect:uri as an argv pair
|
|
918
|
+
local -a args=()
|
|
919
|
+
for item in "${configured[@]}"; do
|
|
920
|
+
local d="${item%%:*}"
|
|
921
|
+
local u="${item#*:}"
|
|
922
|
+
args+=("$d|$u")
|
|
923
|
+
done
|
|
924
|
+
|
|
925
|
+
if node "$CONFIG_DIR/init-all.mjs" "${args[@]}" 2>&1 | tee "$LOG_DIR/init.log"; then
|
|
926
|
+
echo
|
|
927
|
+
ok "Initialization complete"
|
|
928
|
+
else
|
|
929
|
+
echo
|
|
930
|
+
warn "One or more dialects failed — check the log above"
|
|
931
|
+
info "Log : $LOG_DIR/init.log"
|
|
932
|
+
echo
|
|
933
|
+
info "Common fixes :"
|
|
934
|
+
dim " - Verify the URI in menu 2 is reachable (menu 2 → T)"
|
|
935
|
+
dim " - Install missing driver : $PKG_MANAGER install <driver>"
|
|
936
|
+
dim " - Oracle/DB2/HANA need native libs installed on your system"
|
|
937
|
+
fi
|
|
488
938
|
pause
|
|
489
939
|
}
|
|
490
940
|
|
|
@@ -587,20 +1037,52 @@ action_curl_test() {
|
|
|
587
1037
|
header
|
|
588
1038
|
echo -e "${BOLD}${MAGENTA}▶ curl smoke test${RESET}"
|
|
589
1039
|
echo
|
|
1040
|
+
|
|
1041
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
1042
|
+
err "curl is not installed"
|
|
1043
|
+
info "Install with : sudo apt install curl"
|
|
1044
|
+
return 1
|
|
1045
|
+
fi
|
|
1046
|
+
|
|
1047
|
+
local any_reachable=0
|
|
590
1048
|
for url in \
|
|
591
1049
|
"http://localhost:${APP_PORT:-3000}/" \
|
|
592
1050
|
"http://localhost:${APP_PORT:-3000}/api/health" \
|
|
593
1051
|
"http://localhost:${MOSTA_NET_PORT:-4447}/" \
|
|
594
1052
|
"http://localhost:${MOSTA_NET_PORT:-4447}/mcp"; do
|
|
595
1053
|
info "GET $url"
|
|
596
|
-
|
|
1054
|
+
local output
|
|
1055
|
+
output=$(curl -s -o /dev/null -w " status=%{http_code} time=%{time_total}s" --max-time 5 "$url" 2>&1)
|
|
1056
|
+
local rc=$?
|
|
1057
|
+
if [[ $rc -eq 0 ]]; then
|
|
1058
|
+
echo "$output"
|
|
1059
|
+
any_reachable=1
|
|
1060
|
+
else
|
|
1061
|
+
err " unreachable (curl exit $rc — check service is running)"
|
|
1062
|
+
fi
|
|
597
1063
|
done
|
|
1064
|
+
[[ $any_reachable -eq 0 ]] && {
|
|
1065
|
+
echo
|
|
1066
|
+
warn "No endpoints reachable. Start services first (menu 5)."
|
|
1067
|
+
}
|
|
598
1068
|
}
|
|
599
1069
|
|
|
600
1070
|
run_in_project() {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
1071
|
+
local cmd="$1"
|
|
1072
|
+
cd "$PROJECT_ROOT" || { err "Cannot cd to project root"; return 1; }
|
|
1073
|
+
info "Running: $cmd"
|
|
1074
|
+
set +e
|
|
1075
|
+
eval "$cmd"
|
|
1076
|
+
local rc=$?
|
|
1077
|
+
set -e
|
|
1078
|
+
if [[ $rc -ne 0 ]]; then
|
|
1079
|
+
err "Command exited with code $rc"
|
|
1080
|
+
info "Check the output above, or common issues :"
|
|
1081
|
+
dim " - Tests failing → fix them, or skip with a flag"
|
|
1082
|
+
dim " - 'command not found' → install missing dev tools"
|
|
1083
|
+
dim " - Port conflict → stop other services first (menu 5 → 3)"
|
|
1084
|
+
fi
|
|
1085
|
+
return $rc
|
|
604
1086
|
}
|
|
605
1087
|
|
|
606
1088
|
# ============================================================
|
|
@@ -982,6 +1464,10 @@ EOF
|
|
|
982
1464
|
# MAIN
|
|
983
1465
|
# ============================================================
|
|
984
1466
|
|
|
1467
|
+
# If this script is being *sourced*, stop here — don't start the menu.
|
|
1468
|
+
# This lets other scripts (e.g. tests) reuse helper functions.
|
|
1469
|
+
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && return 0 2>/dev/null
|
|
1470
|
+
|
|
985
1471
|
# Non-interactive mode if args provided
|
|
986
1472
|
if [[ $# -gt 0 ]]; then
|
|
987
1473
|
run_subcommand "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/orm-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Universal CLI to integrate @mostajs/orm into any project — auto-detects Prisma, OpenAPI, JSON Schema. Interactive menu + subcommands. 13 databases.",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|