@mostajs/orm-cli 0.5.17 → 0.6.1

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 +282 -10
  2. package/package.json +1 -1
package/bin/mostajs.sh CHANGED
@@ -579,10 +579,10 @@ menu_install_bridge() {
579
579
  header
580
580
  echo -e "${BOLD}${MAGENTA}▶ Install bridge — PrismaClient codemod${RESET}"
581
581
  echo
582
- echo " ${CYAN}1${RESET}) Dry-run : list files that would be rewritten (default)"
583
- echo " ${CYAN}2${RESET}) Apply : rewrite PrismaClient sites to createPrismaLikeDb()"
584
- echo " ${CYAN}3${RESET}) Restore : revert .prisma.bak files (dry-run)"
585
- echo " ${CYAN}4${RESET}) Restore : revert .prisma.bak files (apply)"
582
+ echo -e " ${CYAN}1${RESET}) Dry-run : list files that would be rewritten (default)"
583
+ echo -e " ${CYAN}2${RESET}) Apply : rewrite PrismaClient sites to createPrismaLikeDb()"
584
+ echo -e " ${CYAN}3${RESET}) Restore : revert .prisma.bak files (dry-run)"
585
+ echo -e " ${CYAN}4${RESET}) Restore : revert .prisma.bak files (apply)"
586
586
  echo " ${RED}0${RESET}) Back"
587
587
  echo
588
588
  local cli_dir
@@ -4068,20 +4068,292 @@ CFG
4068
4068
  exit 1
4069
4069
  fi
4070
4070
 
4071
- # ─── Step 4 ───
4072
- 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, continuous)${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}(one-shot snapshot + scaffold replicator)${RESET}"
4081
+ echo -e " ${CYAN}5${RESET}) Simple data copy ${DIM}(one-shot copy, no replicator setup)${RESET}"
4082
+ echo
4083
+ local dm_choice
4084
+ dm_choice=$(ask "Choice" "3")
4085
+
4086
+ case "$dm_choice" in
4087
+ 1)
4088
+ info " Run menu S to upload + apply seeds, or place JSON files in .mostajs/seeds/"
4089
+ ;;
4090
+ 3)
4091
+ ok " Empty tables — ready for your app to populate."
4092
+ ;;
4093
+ 5)
4094
+ # ─── Simple one-shot data copy (no replicator) ───
4095
+ echo
4096
+ echo -e "${BOLD}▶ Simple data copy : old DB → new DB${RESET}"
4097
+
4098
+ local old_uri_simple=""
4099
+ if [[ -f "$PROJECT_ROOT/.env" ]]; then
4100
+ old_uri_simple=$(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_simple" ]]; then
4103
+ old_uri_simple=$(ask "Old DATABASE_URL (source)" "postgresql://user:pass@localhost:5432/olddb")
4104
+ else
4105
+ info " Detected old DATABASE_URL : ${old_uri_simple:0:50}..."
4106
+ if ! confirm "Use this as source?"; then
4107
+ old_uri_simple=$(ask "Old DATABASE_URL" "$old_uri_simple")
4108
+ fi
4109
+ fi
4110
+
4111
+ local old_d_simple="postgres"
4112
+ case "$old_uri_simple" in
4113
+ postgresql://*|postgres://*) old_d_simple="postgres" ;;
4114
+ mysql://*) old_d_simple="mysql" ;;
4115
+ mongodb://*) old_d_simple="mongodb" ;;
4116
+ mariadb://*) old_d_simple="mariadb" ;;
4117
+ oracle://*) old_d_simple="oracle" ;;
4118
+ mssql://*) old_d_simple="mssql" ;;
4119
+ *sqlite*|*.db|*.sqlite) old_d_simple="sqlite" ;;
4120
+ esac
4121
+
4122
+ echo
4123
+ info " Source : $old_d_simple → Target : $DB_DIALECT"
4124
+ info " Copying data (one-shot, no replicator setup)..."
4125
+
4126
+ RUNTIME_ROOT="$PROJECT_ROOT" OLD_URI="$old_uri_simple" OLD_DIALECT="$old_d_simple" \
4127
+ NEW_URI="$SGBD_URI" NEW_DIALECT="$DB_DIALECT" \
4128
+ node --input-type=module -e "
4129
+ import { readFileSync, existsSync } from 'node:fs';
4130
+ import { resolve } from 'node:path';
4131
+
4132
+ const orm = await import(resolve(process.env.RUNTIME_ROOT, 'node_modules/@mostajs/orm/dist/index.js'));
4133
+
4134
+ const entPath = resolve(process.env.RUNTIME_ROOT, '.mostajs/generated/entities.json');
4135
+ const schemas = existsSync(entPath) ? JSON.parse(readFileSync(entPath, 'utf8')) : [];
4136
+ orm.registerSchemas(schemas);
4137
+
4138
+ const src = await orm.createIsolatedDialect(
4139
+ { dialect: process.env.OLD_DIALECT, uri: process.env.OLD_URI, schemaStrategy: 'none' }, schemas);
4140
+ const dst = await orm.createIsolatedDialect(
4141
+ { dialect: process.env.NEW_DIALECT, uri: process.env.NEW_URI, schemaStrategy: 'update' }, schemas);
4142
+ await dst.initSchema(schemas);
4143
+
4144
+ let totalCopied = 0, totalErrors = 0;
4145
+ for (const schema of schemas) {
4146
+ try {
4147
+ const rows = await src.find(schema, {});
4148
+ let copied = 0, errors = 0;
4149
+ for (const row of rows) {
4150
+ try {
4151
+ const { createdAt, updatedAt, ...data } = row;
4152
+ await dst.create(schema, data);
4153
+ copied++;
4154
+ } catch (e) {
4155
+ errors++;
4156
+ }
4157
+ }
4158
+ console.log(' ' + schema.name + ' : ' + copied + ' copied, ' + errors + ' errors');
4159
+ totalCopied += copied;
4160
+ totalErrors += errors;
4161
+ } catch (e) {
4162
+ console.log(' ' + schema.name + ' : ERROR ' + e.message.slice(0,80));
4163
+ totalErrors++;
4164
+ }
4165
+ }
4166
+ console.log('');
4167
+ console.log(' Total : ' + totalCopied + ' records copied, ' + totalErrors + ' errors');
4168
+ await src.disconnect();
4169
+ await dst.disconnect();
4170
+ " 2>&1 | sed 's/^/ /'
4171
+ ;;
4172
+ 2|4)
4173
+ # ─── Replication from old DB ───
4174
+ echo
4175
+ echo -e "${BOLD}▶ Replication setup : old DB → new DB${RESET}"
4176
+
4177
+ # Detect old Prisma DATABASE_URL
4178
+ local old_uri=""
4179
+ if [[ -f "$PROJECT_ROOT/.env" ]]; then
4180
+ old_uri=$(grep -E '^DATABASE_URL=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | sed 's/^DATABASE_URL=//' | tr -d '"' | tr -d "'")
4181
+ fi
4182
+ if [[ -z "$old_uri" ]]; then
4183
+ old_uri=$(ask "Old Prisma DATABASE_URL (source)" "postgresql://user:pass@localhost:5432/olddb")
4184
+ else
4185
+ info " Detected old DATABASE_URL from .env"
4186
+ echo -e " ${DIM}$old_uri${RESET}"
4187
+ if ! confirm "Use this as source?"; then
4188
+ old_uri=$(ask "Old DATABASE_URL" "$old_uri")
4189
+ fi
4190
+ fi
4191
+
4192
+ # Detect old dialect from URI
4193
+ local old_dialect="postgres"
4194
+ case "$old_uri" in
4195
+ postgresql://*|postgres://*) old_dialect="postgres" ;;
4196
+ mysql://*) old_dialect="mysql" ;;
4197
+ mongodb://*) old_dialect="mongodb" ;;
4198
+ mariadb://*) old_dialect="mariadb" ;;
4199
+ oracle://*) old_dialect="oracle" ;;
4200
+ mssql://*) old_dialect="mssql" ;;
4201
+ *sqlite*|*.db|*.sqlite) old_dialect="sqlite" ;;
4202
+ esac
4203
+ info " Source dialect : $old_dialect"
4204
+ info " Target dialect : $DB_DIALECT"
4205
+
4206
+ # Install replicator + monitor deps
4207
+ echo
4208
+ echo -e "${CYAN}▶ Installing replicator + monitor deps${RESET}"
4209
+ ensure_pkg "@mostajs/replicator" "@mostajs/replica-monitor" "@mostajs/mproject" || {
4210
+ err "Failed to install replicator deps"
4211
+ }
4212
+
4213
+ # Write replicator-tree.json
4214
+ echo
4215
+ echo -e "${CYAN}▶ Creating replicator tree (old → new)${RESET}"
4216
+ local tree_file="$CONFIG_DIR/replicator-tree.json"
4217
+ cat > "$tree_file" <<TREE
4218
+ {
4219
+ "replicas": {
4220
+ "old-db": {
4221
+ "source": {
4222
+ "role": "master",
4223
+ "dialect": "$old_dialect",
4224
+ "uri": "$old_uri",
4225
+ "schemaStrategy": "none"
4226
+ }
4227
+ },
4228
+ "new-db": {
4229
+ "target": {
4230
+ "role": "master",
4231
+ "dialect": "$DB_DIALECT",
4232
+ "uri": "$SGBD_URI",
4233
+ "schemaStrategy": "update"
4234
+ }
4235
+ }
4236
+ },
4237
+ "rules": {
4238
+ "migrate-all": {
4239
+ "source": "old-db",
4240
+ "target": "new-db",
4241
+ "mode": "snapshot",
4242
+ "collections": ["*"],
4243
+ "conflictResolution": "source-wins",
4244
+ "enabled": true
4245
+ }
4246
+ },
4247
+ "routing": {}
4248
+ }
4249
+ TREE
4250
+ ok " Tree written : $tree_file"
4251
+
4252
+ # Scaffold services
4253
+ echo
4254
+ echo -e "${CYAN}▶ Scaffolding services${RESET}"
4255
+ action_rep_scaffold_services
4256
+
4257
+ # If option 4 : run one-shot sync now
4258
+ if [[ "$dm_choice" == "4" ]]; then
4259
+ echo
4260
+ echo -e "${BOLD}▶ Running one-shot data migration (snapshot sync)${RESET}"
4261
+ TREE_FILE="$tree_file" RUNTIME_ROOT="$PROJECT_ROOT" \
4262
+ node --input-type=module -e "
4263
+ import { readFileSync, existsSync } from 'node:fs';
4264
+ import { resolve } from 'node:path';
4265
+
4266
+ const orm = await import(resolve(process.env.RUNTIME_ROOT, 'node_modules/@mostajs/orm/dist/index.js'));
4267
+ const { ReplicationManager } = await import(resolve(process.env.RUNTIME_ROOT, 'node_modules/@mostajs/replicator/dist/index.js'));
4268
+
4269
+ // Load schemas
4270
+ const entPath = resolve(process.env.RUNTIME_ROOT, '.mostajs/generated/entities.json');
4271
+ let schemas = [];
4272
+ if (existsSync(entPath)) {
4273
+ schemas = JSON.parse(readFileSync(entPath, 'utf8'));
4274
+ orm.registerSchemas(schemas);
4275
+ }
4276
+
4277
+ const tree = JSON.parse(readFileSync(process.env.TREE_FILE, 'utf8'));
4278
+ const rm = new ReplicationManager();
4279
+
4280
+ // Connect source + target
4281
+ for (const [project, reps] of Object.entries(tree.replicas)) {
4282
+ for (const [name, cfg] of Object.entries(reps)) {
4283
+ try {
4284
+ await rm.addReplica(project, {
4285
+ name, role: cfg.role, dialect: cfg.dialect,
4286
+ uri: cfg.uri, schemaStrategy: cfg.schemaStrategy ?? 'update',
4287
+ }, schemas);
4288
+ console.log(' ✓ connected ' + project + '/' + name + ' [' + cfg.dialect + ']');
4289
+ } catch (e) {
4290
+ console.error(' ✗ ' + project + '/' + name + ' : ' + e.message);
4291
+ }
4292
+ }
4293
+ }
4294
+
4295
+ // Resolve wildcard + sync
4296
+ for (const [ruleName, rule] of Object.entries(tree.rules)) {
4297
+ try {
4298
+ // Resolve entity names from schemas
4299
+ let collections = rule.collections;
4300
+ if (collections.includes('*')) {
4301
+ collections = schemas.map(s => s.name);
4302
+ console.log(' → expanded * → ' + collections.length + ' entities');
4303
+ }
4304
+ rm.addReplicationRule({ name: ruleName, ...rule, collections });
4305
+ const stats = await rm.sync(ruleName);
4306
+ const ins = stats.recordsSynced ?? 0;
4307
+ const fail = stats.errors ?? 0;
4308
+ console.log(' ✓ ' + ruleName + ' : ' + ins + ' records synced, ' + fail + ' errors (' + (stats.duration ?? 0) + 'ms)');
4309
+ if (stats.details) {
4310
+ for (const d of stats.details) {
4311
+ console.log(' ' + d.collection + ' : ' + d.created + '↑ ' + d.updated + '⟳ ' + d.deleted + '↓ ' + d.errors + '✗');
4312
+ }
4313
+ }
4314
+ } catch (e) {
4315
+ console.error(' ✗ ' + ruleName + ' : ' + e.message);
4316
+ }
4317
+ }
4318
+
4319
+ await rm.disconnectAll();
4320
+ " 2>&1 | sed 's/^/ /'
4321
+ else
4322
+ info " Replication configured. Run 'npm run dev:all' to start continuous sync."
4323
+ info " Or run a one-shot sync : npm run sync-once"
4324
+ fi
4325
+ ;;
4326
+ *)
4327
+ warn " Invalid choice — defaulting to empty tables."
4328
+ ;;
4329
+ esac
4330
+
4331
+ # ─── Step 5 : Summary ───
4332
+ echo -e "\n\e[1m▶ Step 5/6 : Summary\e[0m"
4073
4333
  if (( BS_OK_CODEMOD && BS_OK_DEPS && BS_OK_CONVERT && BS_OK_DDL )); then
4074
4334
  cat <<DONE
4075
4335
 
4076
4336
  ✓ Bridge installed in-place. Original files backed up as *.prisma.bak
4077
4337
  ✓ Schema converted : $GENERATED_DIR/entities.json
4078
4338
  ✓ DDL applied (DB_DIALECT=$DB_DIALECT SGBD_URI=$SGBD_URI)
4339
+ DONE
4340
+ if [[ "$dm_choice" == "2" || "$dm_choice" == "4" ]]; then
4341
+ cat <<DONE
4342
+ ✓ Replicator configured : old ($old_dialect) → new ($DB_DIALECT)
4343
+ ✓ Services scaffolded : services/replicator.mjs + services/monitor.mjs
4344
+ ✓ package.json patched : npm run replicator / monitor / dev:all
4345
+ DONE
4346
+ fi
4347
+ if [[ "$dm_choice" == "4" ]]; then
4348
+ echo " ✓ Data migrated via snapshot sync"
4349
+ fi
4350
+ cat <<DONE
4079
4351
 
4080
4352
  Next :
4081
- - Add seeds to $CONFIG_DIR/seeds/*.json (one file per entity)
4082
- - $CLI_NAME # menu S h (hash) 4 (apply)
4083
- - npm run dev
4084
- - Open http://localhost:3000/login
4353
+ - npm run dev # start your app
4354
+ - npm run dev:all # start app + replicator + monitor
4355
+ - $CLI_NAME # interactive CLI for seeds, replicas, CDC
4356
+ - Open http://localhost:3000
4085
4357
 
4086
4358
  To undo the codemod :
4087
4359
  $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.17",
3
+ "version": "0.6.1",
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",