@mostajs/orm-cli 0.5.16 → 0.6.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 +254 -10
- package/package.json +1 -1
package/bin/mostajs.sh
CHANGED
|
@@ -1499,7 +1499,9 @@ menu_services() {
|
|
|
1499
1499
|
echo -e " ${CYAN}4${RESET}) Status"
|
|
1500
1500
|
echo -e " ${CYAN}5${RESET}) Show access URLs"
|
|
1501
1501
|
echo -e " ${CYAN}6${RESET}) ${BOLD}Setup services${RESET} — scaffold replicator + monitor + config.env + dev:all"
|
|
1502
|
-
echo -e " ${CYAN}7${RESET}) Start all (
|
|
1502
|
+
echo -e " ${CYAN}7${RESET}) ${BOLD}Start all${RESET} (replicator + monitor in background, return to menu)"
|
|
1503
|
+
echo -e " ${CYAN}8${RESET}) ${BOLD}Stop all background${RESET} (stop dev:all)"
|
|
1504
|
+
echo -e " ${CYAN}9${RESET}) ${BOLD}Background status${RESET} (dev:all PID + last logs)"
|
|
1503
1505
|
echo
|
|
1504
1506
|
echo -e " ${CYAN}b${RESET}) Back"
|
|
1505
1507
|
echo
|
|
@@ -1512,6 +1514,8 @@ menu_services() {
|
|
|
1512
1514
|
5) show_urls;;
|
|
1513
1515
|
6) action_setup_services;;
|
|
1514
1516
|
7) svc_start_dev_all;;
|
|
1517
|
+
8) svc_stop_dev_all;;
|
|
1518
|
+
9) svc_dev_all_status;;
|
|
1515
1519
|
b|B) return;;
|
|
1516
1520
|
*) warn Unknown;;
|
|
1517
1521
|
esac
|
|
@@ -1546,9 +1550,57 @@ svc_start_dev_all() {
|
|
|
1546
1550
|
info "Run menu 5 → 6 (Setup services) first to scaffold + patch."
|
|
1547
1551
|
return
|
|
1548
1552
|
fi
|
|
1549
|
-
info "Starting dev:all
|
|
1550
|
-
|
|
1551
|
-
|
|
1553
|
+
info "Starting dev:all in background..."
|
|
1554
|
+
nohup "$PKG_MANAGER" run dev:all > "$LOG_DIR/dev-all.log" 2>&1 &
|
|
1555
|
+
local pid=$!
|
|
1556
|
+
echo "$pid" > "$LOG_DIR/dev-all.pid"
|
|
1557
|
+
sleep 2
|
|
1558
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
1559
|
+
ok "dev:all running (PID $pid)"
|
|
1560
|
+
ok "Logs : tail -f $LOG_DIR/dev-all.log"
|
|
1561
|
+
# Show detected ports from log
|
|
1562
|
+
grep -oE '(localhost|127\.0\.0\.1):[0-9]+' "$LOG_DIR/dev-all.log" 2>/dev/null | sort -u | while read url; do
|
|
1563
|
+
echo -e " ${CYAN}→ http://$url${RESET}"
|
|
1564
|
+
done
|
|
1565
|
+
else
|
|
1566
|
+
err "dev:all failed to start — check $LOG_DIR/dev-all.log"
|
|
1567
|
+
tail -5 "$LOG_DIR/dev-all.log" 2>/dev/null
|
|
1568
|
+
fi
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
svc_stop_dev_all() {
|
|
1572
|
+
local pidfile="$LOG_DIR/dev-all.pid"
|
|
1573
|
+
if [[ ! -f "$pidfile" ]]; then
|
|
1574
|
+
warn "No dev:all PID file found — not running?"
|
|
1575
|
+
return
|
|
1576
|
+
fi
|
|
1577
|
+
local pid
|
|
1578
|
+
pid=$(cat "$pidfile")
|
|
1579
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
1580
|
+
# Kill the process group (concurrently + children)
|
|
1581
|
+
kill -- -"$pid" 2>/dev/null || kill "$pid" 2>/dev/null
|
|
1582
|
+
sleep 1
|
|
1583
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
1584
|
+
kill -9 -- -"$pid" 2>/dev/null || kill -9 "$pid" 2>/dev/null
|
|
1585
|
+
fi
|
|
1586
|
+
ok "dev:all stopped (was PID $pid)"
|
|
1587
|
+
else
|
|
1588
|
+
info "dev:all already stopped (PID $pid)"
|
|
1589
|
+
fi
|
|
1590
|
+
rm -f "$pidfile"
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
svc_dev_all_status() {
|
|
1594
|
+
local pidfile="$LOG_DIR/dev-all.pid"
|
|
1595
|
+
if [[ -f "$pidfile" ]] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
|
|
1596
|
+
local pid=$(cat "$pidfile")
|
|
1597
|
+
ok "dev:all running (PID $pid)"
|
|
1598
|
+
ok "Logs : $LOG_DIR/dev-all.log"
|
|
1599
|
+
echo -e " Last 5 lines :"
|
|
1600
|
+
tail -5 "$LOG_DIR/dev-all.log" 2>/dev/null | sed 's/^/ /'
|
|
1601
|
+
else
|
|
1602
|
+
info "dev:all not running"
|
|
1603
|
+
fi
|
|
1552
1604
|
}
|
|
1553
1605
|
|
|
1554
1606
|
svc_start_dev() {
|
|
@@ -4016,20 +4068,212 @@ CFG
|
|
|
4016
4068
|
exit 1
|
|
4017
4069
|
fi
|
|
4018
4070
|
|
|
4019
|
-
# ─── Step 4 ───
|
|
4020
|
-
echo -e "\n\e[1m▶ Step 4/
|
|
4071
|
+
# ─── Step 4 : Data migration ───
|
|
4072
|
+
echo -e "\n\e[1m▶ Step 4/6 : Data migration\e[0m"
|
|
4073
|
+
echo
|
|
4074
|
+
echo -e " Tables created on ${CYAN}$DB_DIALECT${RESET} (${DIM}$SGBD_URI${RESET})."
|
|
4075
|
+
echo -e " How do you want to populate them?"
|
|
4076
|
+
echo
|
|
4077
|
+
echo -e " ${CYAN}1${RESET}) Apply seed files ${DIM}(.mostajs/seeds/*.json → mostajs seed)${RESET}"
|
|
4078
|
+
echo -e " ${CYAN}2${RESET}) Replicate from old DB ${DIM}(old Prisma DB = master → new DB = slave)${RESET}"
|
|
4079
|
+
echo -e " ${CYAN}3${RESET}) Start with empty tables ${DIM}(app handles its own data)${RESET}"
|
|
4080
|
+
echo -e " ${CYAN}4${RESET}) Auto-replicate old → new ${DIM}(init schema + snapshot all data)${RESET}"
|
|
4081
|
+
echo
|
|
4082
|
+
local dm_choice
|
|
4083
|
+
dm_choice=$(ask "Choice" "3")
|
|
4084
|
+
|
|
4085
|
+
case "$dm_choice" in
|
|
4086
|
+
1)
|
|
4087
|
+
info " Run menu S to upload + apply seeds, or place JSON files in .mostajs/seeds/"
|
|
4088
|
+
;;
|
|
4089
|
+
3)
|
|
4090
|
+
ok " Empty tables — ready for your app to populate."
|
|
4091
|
+
;;
|
|
4092
|
+
2|4)
|
|
4093
|
+
# ─── Replication from old DB ───
|
|
4094
|
+
echo
|
|
4095
|
+
echo -e "${BOLD}▶ Replication setup : old DB → new DB${RESET}"
|
|
4096
|
+
|
|
4097
|
+
# Detect old Prisma DATABASE_URL
|
|
4098
|
+
local old_uri=""
|
|
4099
|
+
if [[ -f "$PROJECT_ROOT/.env" ]]; then
|
|
4100
|
+
old_uri=$(grep -E '^DATABASE_URL=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | sed 's/^DATABASE_URL=//' | tr -d '"' | tr -d "'")
|
|
4101
|
+
fi
|
|
4102
|
+
if [[ -z "$old_uri" ]]; then
|
|
4103
|
+
old_uri=$(ask "Old Prisma DATABASE_URL (source)" "postgresql://user:pass@localhost:5432/olddb")
|
|
4104
|
+
else
|
|
4105
|
+
info " Detected old DATABASE_URL from .env"
|
|
4106
|
+
echo -e " ${DIM}$old_uri${RESET}"
|
|
4107
|
+
if ! confirm "Use this as source?"; then
|
|
4108
|
+
old_uri=$(ask "Old DATABASE_URL" "$old_uri")
|
|
4109
|
+
fi
|
|
4110
|
+
fi
|
|
4111
|
+
|
|
4112
|
+
# Detect old dialect from URI
|
|
4113
|
+
local old_dialect="postgres"
|
|
4114
|
+
case "$old_uri" in
|
|
4115
|
+
postgresql://*|postgres://*) old_dialect="postgres" ;;
|
|
4116
|
+
mysql://*) old_dialect="mysql" ;;
|
|
4117
|
+
mongodb://*) old_dialect="mongodb" ;;
|
|
4118
|
+
mariadb://*) old_dialect="mariadb" ;;
|
|
4119
|
+
oracle://*) old_dialect="oracle" ;;
|
|
4120
|
+
mssql://*) old_dialect="mssql" ;;
|
|
4121
|
+
*sqlite*|*.db|*.sqlite) old_dialect="sqlite" ;;
|
|
4122
|
+
esac
|
|
4123
|
+
info " Source dialect : $old_dialect"
|
|
4124
|
+
info " Target dialect : $DB_DIALECT"
|
|
4125
|
+
|
|
4126
|
+
# Install replicator + monitor deps
|
|
4127
|
+
echo
|
|
4128
|
+
echo -e "${CYAN}▶ Installing replicator + monitor deps${RESET}"
|
|
4129
|
+
ensure_pkg "@mostajs/replicator" "@mostajs/replica-monitor" "@mostajs/mproject" || {
|
|
4130
|
+
err "Failed to install replicator deps"
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
# Write replicator-tree.json
|
|
4134
|
+
echo
|
|
4135
|
+
echo -e "${CYAN}▶ Creating replicator tree (old → new)${RESET}"
|
|
4136
|
+
local tree_file="$CONFIG_DIR/replicator-tree.json"
|
|
4137
|
+
cat > "$tree_file" <<TREE
|
|
4138
|
+
{
|
|
4139
|
+
"replicas": {
|
|
4140
|
+
"old-db": {
|
|
4141
|
+
"source": {
|
|
4142
|
+
"role": "master",
|
|
4143
|
+
"dialect": "$old_dialect",
|
|
4144
|
+
"uri": "$old_uri",
|
|
4145
|
+
"schemaStrategy": "none"
|
|
4146
|
+
}
|
|
4147
|
+
},
|
|
4148
|
+
"new-db": {
|
|
4149
|
+
"target": {
|
|
4150
|
+
"role": "master",
|
|
4151
|
+
"dialect": "$DB_DIALECT",
|
|
4152
|
+
"uri": "$SGBD_URI",
|
|
4153
|
+
"schemaStrategy": "update"
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
},
|
|
4157
|
+
"rules": {
|
|
4158
|
+
"migrate-all": {
|
|
4159
|
+
"source": "old-db",
|
|
4160
|
+
"target": "new-db",
|
|
4161
|
+
"mode": "snapshot",
|
|
4162
|
+
"collections": ["*"],
|
|
4163
|
+
"conflictResolution": "source-wins",
|
|
4164
|
+
"enabled": true
|
|
4165
|
+
}
|
|
4166
|
+
},
|
|
4167
|
+
"routing": {}
|
|
4168
|
+
}
|
|
4169
|
+
TREE
|
|
4170
|
+
ok " Tree written : $tree_file"
|
|
4171
|
+
|
|
4172
|
+
# Scaffold services
|
|
4173
|
+
echo
|
|
4174
|
+
echo -e "${CYAN}▶ Scaffolding services${RESET}"
|
|
4175
|
+
action_rep_scaffold_services
|
|
4176
|
+
|
|
4177
|
+
# If option 4 : run one-shot sync now
|
|
4178
|
+
if [[ "$dm_choice" == "4" ]]; then
|
|
4179
|
+
echo
|
|
4180
|
+
echo -e "${BOLD}▶ Running one-shot data migration (snapshot sync)${RESET}"
|
|
4181
|
+
TREE_FILE="$tree_file" RUNTIME_ROOT="$PROJECT_ROOT" \
|
|
4182
|
+
node --input-type=module -e "
|
|
4183
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
4184
|
+
import { resolve } from 'node:path';
|
|
4185
|
+
|
|
4186
|
+
const orm = await import(resolve(process.env.RUNTIME_ROOT, 'node_modules/@mostajs/orm/dist/index.js'));
|
|
4187
|
+
const { ReplicationManager } = await import(resolve(process.env.RUNTIME_ROOT, 'node_modules/@mostajs/replicator/dist/index.js'));
|
|
4188
|
+
|
|
4189
|
+
// Load schemas
|
|
4190
|
+
const entPath = resolve(process.env.RUNTIME_ROOT, '.mostajs/generated/entities.json');
|
|
4191
|
+
let schemas = [];
|
|
4192
|
+
if (existsSync(entPath)) {
|
|
4193
|
+
schemas = JSON.parse(readFileSync(entPath, 'utf8'));
|
|
4194
|
+
orm.registerSchemas(schemas);
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
const tree = JSON.parse(readFileSync(process.env.TREE_FILE, 'utf8'));
|
|
4198
|
+
const rm = new ReplicationManager();
|
|
4199
|
+
|
|
4200
|
+
// Connect source + target
|
|
4201
|
+
for (const [project, reps] of Object.entries(tree.replicas)) {
|
|
4202
|
+
for (const [name, cfg] of Object.entries(reps)) {
|
|
4203
|
+
try {
|
|
4204
|
+
await rm.addReplica(project, {
|
|
4205
|
+
name, role: cfg.role, dialect: cfg.dialect,
|
|
4206
|
+
uri: cfg.uri, schemaStrategy: cfg.schemaStrategy ?? 'update',
|
|
4207
|
+
}, schemas);
|
|
4208
|
+
console.log(' ✓ connected ' + project + '/' + name + ' [' + cfg.dialect + ']');
|
|
4209
|
+
} catch (e) {
|
|
4210
|
+
console.error(' ✗ ' + project + '/' + name + ' : ' + e.message);
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
// Resolve wildcard + sync
|
|
4216
|
+
for (const [ruleName, rule] of Object.entries(tree.rules)) {
|
|
4217
|
+
try {
|
|
4218
|
+
// Resolve entity names from schemas
|
|
4219
|
+
let collections = rule.collections;
|
|
4220
|
+
if (collections.includes('*')) {
|
|
4221
|
+
collections = schemas.map(s => s.name);
|
|
4222
|
+
console.log(' → expanded * → ' + collections.length + ' entities');
|
|
4223
|
+
}
|
|
4224
|
+
rm.addReplicationRule({ name: ruleName, ...rule, collections });
|
|
4225
|
+
const stats = await rm.sync(ruleName);
|
|
4226
|
+
const ins = stats.recordsSynced ?? 0;
|
|
4227
|
+
const fail = stats.errors ?? 0;
|
|
4228
|
+
console.log(' ✓ ' + ruleName + ' : ' + ins + ' records synced, ' + fail + ' errors (' + (stats.duration ?? 0) + 'ms)');
|
|
4229
|
+
if (stats.details) {
|
|
4230
|
+
for (const d of stats.details) {
|
|
4231
|
+
console.log(' ' + d.collection + ' : ' + d.created + '↑ ' + d.updated + '⟳ ' + d.deleted + '↓ ' + d.errors + '✗');
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
} catch (e) {
|
|
4235
|
+
console.error(' ✗ ' + ruleName + ' : ' + e.message);
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
await rm.disconnectAll();
|
|
4240
|
+
" 2>&1 | sed 's/^/ /'
|
|
4241
|
+
else
|
|
4242
|
+
info " Replication configured. Run 'npm run dev:all' to start continuous sync."
|
|
4243
|
+
info " Or run a one-shot sync : npm run sync-once"
|
|
4244
|
+
fi
|
|
4245
|
+
;;
|
|
4246
|
+
*)
|
|
4247
|
+
warn " Invalid choice — defaulting to empty tables."
|
|
4248
|
+
;;
|
|
4249
|
+
esac
|
|
4250
|
+
|
|
4251
|
+
# ─── Step 5 : Summary ───
|
|
4252
|
+
echo -e "\n\e[1m▶ Step 5/6 : Summary\e[0m"
|
|
4021
4253
|
if (( BS_OK_CODEMOD && BS_OK_DEPS && BS_OK_CONVERT && BS_OK_DDL )); then
|
|
4022
4254
|
cat <<DONE
|
|
4023
4255
|
|
|
4024
4256
|
✓ Bridge installed in-place. Original files backed up as *.prisma.bak
|
|
4025
4257
|
✓ Schema converted : $GENERATED_DIR/entities.json
|
|
4026
4258
|
✓ DDL applied (DB_DIALECT=$DB_DIALECT SGBD_URI=$SGBD_URI)
|
|
4259
|
+
DONE
|
|
4260
|
+
if [[ "$dm_choice" == "2" || "$dm_choice" == "4" ]]; then
|
|
4261
|
+
cat <<DONE
|
|
4262
|
+
✓ Replicator configured : old ($old_dialect) → new ($DB_DIALECT)
|
|
4263
|
+
✓ Services scaffolded : services/replicator.mjs + services/monitor.mjs
|
|
4264
|
+
✓ package.json patched : npm run replicator / monitor / dev:all
|
|
4265
|
+
DONE
|
|
4266
|
+
fi
|
|
4267
|
+
if [[ "$dm_choice" == "4" ]]; then
|
|
4268
|
+
echo " ✓ Data migrated via snapshot sync"
|
|
4269
|
+
fi
|
|
4270
|
+
cat <<DONE
|
|
4027
4271
|
|
|
4028
4272
|
Next :
|
|
4029
|
-
-
|
|
4030
|
-
-
|
|
4031
|
-
-
|
|
4032
|
-
- Open http://localhost:3000
|
|
4273
|
+
- npm run dev # start your app
|
|
4274
|
+
- npm run dev:all # start app + replicator + monitor
|
|
4275
|
+
- $CLI_NAME # interactive CLI for seeds, replicas, CDC
|
|
4276
|
+
- Open http://localhost:3000
|
|
4033
4277
|
|
|
4034
4278
|
To undo the codemod :
|
|
4035
4279
|
$CLI_NAME install-bridge --restore --apply
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/orm-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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",
|