@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.
Files changed (2) hide show
  1. package/bin/mostajs.sh +254 -10
  2. 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 (dev + replicator + monitor via ${DIM}npm run dev:all${RESET})"
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 (logs → terminal, Ctrl+C to stop)"
1550
- echo
1551
- "$PKG_MANAGER" run dev:all
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/4 : done\e[0m"
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
- - Add seeds to $CONFIG_DIR/seeds/*.json (one file per entity)
4030
- - $CLI_NAME # menu S h (hash) 4 (apply)
4031
- - npm run dev
4032
- - Open http://localhost:3000/login
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.5.16",
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",