@mostajs/orm-cli 0.5.17 → 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 +198 -6
- package/package.json +1 -1
package/bin/mostajs.sh
CHANGED
|
@@ -4068,20 +4068,212 @@ 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)${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"
|
|
4073
4253
|
if (( BS_OK_CODEMOD && BS_OK_DEPS && BS_OK_CONVERT && BS_OK_DDL )); then
|
|
4074
4254
|
cat <<DONE
|
|
4075
4255
|
|
|
4076
4256
|
✓ Bridge installed in-place. Original files backed up as *.prisma.bak
|
|
4077
4257
|
✓ Schema converted : $GENERATED_DIR/entities.json
|
|
4078
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
|
|
4079
4271
|
|
|
4080
4272
|
Next :
|
|
4081
|
-
-
|
|
4082
|
-
-
|
|
4083
|
-
-
|
|
4084
|
-
- 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
|
|
4085
4277
|
|
|
4086
4278
|
To undo the codemod :
|
|
4087
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",
|