@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.
- package/bin/mostajs.sh +282 -10
- 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/
|
|
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
|
-
-
|
|
4082
|
-
-
|
|
4083
|
-
-
|
|
4084
|
-
- Open http://localhost:3000
|
|
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.
|
|
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",
|