@mostajs/orm-cli 0.4.6 → 0.5.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 +456 -2
  2. package/package.json +1 -1
package/bin/mostajs.sh CHANGED
@@ -27,7 +27,7 @@ CLI_NAME="mostajs"
27
27
  # PATHS — relative to the CALLER's CWD, not the script
28
28
  # ============================================================
29
29
 
30
- PROJECT_ROOT="$(pwd)"
30
+ PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}"
31
31
  CONFIG_DIR="$PROJECT_ROOT/.mostajs"
32
32
  CONFIG_FILE="$CONFIG_DIR/config.env"
33
33
  LOG_DIR="$CONFIG_DIR/logs"
@@ -214,7 +214,16 @@ header() {
214
214
  # ============================================================
215
215
 
216
216
  load_env() {
217
- [[ -f "$CONFIG_FILE" ]] && { set -a; source "$CONFIG_FILE"; set +a; }
217
+ # Source .mostajs/config.env WITHOUT overriding env vars that are already
218
+ # set (CLI invocation with DB_DIALECT=... mostajs ... takes precedence).
219
+ [[ -f "$CONFIG_FILE" ]] || return
220
+ while IFS='=' read -r key value; do
221
+ [[ -z "$key" || "$key" =~ ^# ]] && continue
222
+ # Trim whitespace from key and strip surrounding quotes from value
223
+ key="${key// /}"
224
+ value="${value%\"}"; value="${value#\"}"
225
+ [[ -z "${!key+x}" ]] && export "$key=$value"
226
+ done < "$CONFIG_FILE"
218
227
  }
219
228
 
220
229
  save_var() {
@@ -457,6 +466,7 @@ menu_main() {
457
466
  echo -e " ${CYAN}8${RESET}) Health checks"
458
467
  echo -e " ${CYAN}9${RESET}) Generate boilerplate (src/db.ts with bridge)"
459
468
  echo -e " ${CYAN}s${RESET}) ${BOLD}Seeding${RESET} (upload / validate / apply seed data)"
469
+ echo -e " ${CYAN}e${RESET}) ${BOLD}Export entities${RESET} → Prisma / JSON Schema / OpenAPI / Native"
460
470
  echo -e " ${GREEN}b${RESET}) ${BOLD}Bootstrap${RESET} — one-shot migration of a Prisma project"
461
471
  echo -e " ${GREEN}i${RESET}) ${BOLD}Install bridge${RESET} — codemod PrismaClient → bridge (dry-run / apply / restore)"
462
472
  echo -e " ${CYAN}0${RESET}) About / Help"
@@ -476,6 +486,7 @@ menu_main() {
476
486
  8) action_healthcheck ;;
477
487
  9) action_generate_boilerplate ;;
478
488
  s|S) menu_seeding ;;
489
+ e|E) action_export_entities ;;
479
490
  b|B) menu_bootstrap ;;
480
491
  i|I) menu_install_bridge ;;
481
492
  0) action_about ;;
@@ -1700,6 +1711,71 @@ menu_seeding() {
1700
1711
  menu_seeding
1701
1712
  }
1702
1713
 
1714
+ # ------------------------------------------------------------
1715
+ # Export entities → Prisma / JSON Schema / OpenAPI / Native TS
1716
+ # ------------------------------------------------------------
1717
+ action_export_entities() {
1718
+ header
1719
+ echo -e "${BOLD}${MAGENTA}▶ Export entities to another schema format${RESET}"
1720
+ echo
1721
+ local ent_json="$GENERATED_DIR/entities.json"
1722
+ if [[ ! -f "$ent_json" ]]; then
1723
+ err "No entities.json found — run menu 1 (Convert) first."
1724
+ pause; return
1725
+ fi
1726
+ local count
1727
+ count=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$ent_json','utf8')).length)" 2>/dev/null || echo 0)
1728
+ echo -e " Source : ${DIM}${ent_json}${RESET} (${CYAN}${count}${RESET} entities)"
1729
+ echo
1730
+ echo -e " ${CYAN}1${RESET}) Prisma → ${DIM}schema.prisma${RESET}"
1731
+ echo -e " ${CYAN}2${RESET}) JSON Schema → ${DIM}schema.json${RESET} (2020-12)"
1732
+ echo -e " ${CYAN}3${RESET}) OpenAPI 3.1 → ${DIM}openapi.json${RESET}"
1733
+ echo -e " ${CYAN}4${RESET}) Native (TS) → ${DIM}src/schemas.ts${RESET}"
1734
+ echo
1735
+ echo -e " ${CYAN}b${RESET}) Back"
1736
+ echo
1737
+ local choice
1738
+ choice=$(ask "Format" "1")
1739
+ local format="" default_out=""
1740
+ case "$choice" in
1741
+ 1) format="prisma"; default_out="$PROJECT_ROOT/prisma/schema.prisma" ;;
1742
+ 2) format="jsonschema"; default_out="$PROJECT_ROOT/schema.json" ;;
1743
+ 3) format="openapi"; default_out="$PROJECT_ROOT/openapi.json" ;;
1744
+ 4) format="native"; default_out="$PROJECT_ROOT/src/schemas.ts" ;;
1745
+ b|B) return ;;
1746
+ *) warn "Unknown"; pause; return ;;
1747
+ esac
1748
+ local out
1749
+ out=$(ask "Output file" "$default_out")
1750
+ mkdir -p "$(dirname "$out")"
1751
+
1752
+ # Spawn Node to run the adapter's fromEntitySchema
1753
+ node --input-type=module -e "
1754
+ import('@mostajs/orm-adapter').then(async ({ PrismaAdapter, JsonSchemaAdapter, OpenApiAdapter, NativeAdapter }) => {
1755
+ const entities = JSON.parse(await (await import('fs/promises')).readFile('$ent_json', 'utf8'));
1756
+ let out;
1757
+ switch ('$format') {
1758
+ case 'prisma': out = await new PrismaAdapter().fromEntitySchema(entities); break;
1759
+ case 'jsonschema': out = JSON.stringify(await new JsonSchemaAdapter().fromEntitySchema(entities), null, 2); break;
1760
+ case 'openapi': out = JSON.stringify(await new OpenApiAdapter().fromEntitySchema(entities), null, 2); break;
1761
+ case 'native':
1762
+ out = '// Auto-generated by mostajs export → Native (EntitySchema TS)\n'
1763
+ + '// Author: @mostajs/orm-cli\n\n'
1764
+ + \"import type { EntitySchema } from '@mostajs/orm';\n\n\"
1765
+ + 'export const schemas: EntitySchema[] = '
1766
+ + JSON.stringify(entities, null, 2) + ';\n\n'
1767
+ + '// Named exports for convenience\n'
1768
+ + entities.map(e => 'export const ' + e.name + 'Schema: EntitySchema = schemas.find(s => s.name === \"' + e.name + '\")!;').join('\n')
1769
+ + '\n';
1770
+ break;
1771
+ }
1772
+ await (await import('fs/promises')).writeFile('$out', out);
1773
+ console.log(' ✓ written ' + '$out' + ' (' + (out.length/1024).toFixed(1) + ' KB)');
1774
+ }).catch(e => { console.error(' ✗', e.message); process.exit(1); });
1775
+ " 2>&1
1776
+ pause
1777
+ }
1778
+
1703
1779
  # ------------------------------------------------------------
1704
1780
  # seed : drop tables (interactive picker)
1705
1781
  # ------------------------------------------------------------
@@ -2569,6 +2645,364 @@ EOF
2569
2645
  pause
2570
2646
  }
2571
2647
 
2648
+ # ============================================================
2649
+ # `mostajs init` — scaffold a new project
2650
+ # ============================================================
2651
+ #
2652
+ # Creates every file a fresh project needs to run on the bridge :
2653
+ # - .env with PORT / DB_DIALECT / SGBD_URI / AUTH_SECRET
2654
+ # - prisma/schema.prisma (minimal User model — starting point)
2655
+ # - src/lib/db.ts (createPrismaLikeDb)
2656
+ # - .mostajs/config.env (mirrors .env for the seed-runner)
2657
+ # - .mostajs/generated/entities.json (empty array, filled by menu 1)
2658
+ #
2659
+ # Dialect defaults to sqlite ./data.sqlite. Pass --dialect=postgres etc.
2660
+ # Refuses to overwrite existing files unless --force.
2661
+
2662
+ action_cli_init() {
2663
+ local dialect="sqlite"
2664
+ local uri=""
2665
+ local force=0
2666
+ while [[ $# -gt 0 ]]; do
2667
+ case "$1" in
2668
+ --dialect) dialect="$2"; shift 2 ;;
2669
+ --dialect=*) dialect="${1#*=}"; shift ;;
2670
+ --uri) uri="$2"; shift 2 ;;
2671
+ --uri=*) uri="${1#*=}"; shift ;;
2672
+ --force|-f) force=1; shift ;;
2673
+ *) warn "Unknown flag: $1"; shift ;;
2674
+ esac
2675
+ done
2676
+
2677
+ # Default URIs per dialect
2678
+ if [[ -z "$uri" ]]; then
2679
+ case "$dialect" in
2680
+ sqlite) uri="./data.sqlite" ;;
2681
+ postgres) uri="postgres://user:pass@localhost:5432/mydb" ;;
2682
+ mysql) uri="mysql://user:pass@localhost:3306/mydb" ;;
2683
+ mariadb) uri="mariadb://user:pass@localhost:3306/mydb" ;;
2684
+ mongodb) uri="mongodb://user:pass@localhost:27017/mydb" ;;
2685
+ oracle) uri="oracle://user:pass@localhost:1521/XE" ;;
2686
+ mssql) uri="mssql://user:pass@localhost:1433/mydb" ;;
2687
+ cockroachdb) uri="postgresql://user:pass@localhost:26257/mydb?sslmode=disable" ;;
2688
+ *) uri="./data.sqlite"; dialect="sqlite" ;;
2689
+ esac
2690
+ fi
2691
+
2692
+ header
2693
+ echo -e "${BOLD}${MAGENTA}▶ mostajs init — scaffold a bridge-ready project${RESET}"
2694
+ echo
2695
+ echo -e " Dialect : ${CYAN}${dialect}${RESET}"
2696
+ echo -e " URI : ${DIM}${uri}${RESET}"
2697
+ echo -e " Root : ${DIM}${PROJECT_ROOT}${RESET}"
2698
+ echo
2699
+
2700
+ local created=0 skipped=0
2701
+
2702
+ write_if_missing() {
2703
+ local path="$1"; local content="$2"
2704
+ if [[ -f "$PROJECT_ROOT/$path" && $force -eq 0 ]]; then
2705
+ dim " - skip $path (exists — use --force to overwrite)"
2706
+ ((skipped++))
2707
+ return
2708
+ fi
2709
+ mkdir -p "$(dirname "$PROJECT_ROOT/$path")"
2710
+ printf '%s' "$content" > "$PROJECT_ROOT/$path"
2711
+ ok "created $path"
2712
+ ((created++))
2713
+ }
2714
+
2715
+ # --- .env ---
2716
+ local secret
2717
+ secret=$(node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" 2>/dev/null || echo 'CHANGE-ME-IN-PROD')
2718
+ write_if_missing ".env" "\
2719
+ # Port — used by next dev / start (reads PORT from here)
2720
+ PORT=3000
2721
+
2722
+ # Database — consumed by @mostajs/orm-bridge (createPrismaLikeDb)
2723
+ DB_DIALECT=${dialect}
2724
+ SGBD_URI=${uri}
2725
+ DB_SCHEMA_STRATEGY=update
2726
+
2727
+ # NextAuth (if you use it)
2728
+ NEXTAUTH_URL=http://localhost:3000
2729
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
2730
+ AUTH_SECRET=${secret}
2731
+ "
2732
+
2733
+ # --- .mostajs/config.env (mirror for the seed-runner) ---
2734
+ write_if_missing ".mostajs/config.env" "\
2735
+ DB_DIALECT=${dialect}
2736
+ SGBD_URI=${uri}
2737
+ DB_SCHEMA_STRATEGY=update
2738
+ APP_PORT=3000
2739
+ "
2740
+
2741
+ # --- .mostajs/generated/entities.json (empty — filled by menu 1) ---
2742
+ write_if_missing ".mostajs/generated/entities.json" "[]
2743
+ "
2744
+
2745
+ # --- prisma/schema.prisma (minimal starter) ---
2746
+ # Prisma's valid providers : sqlite, postgresql, mysql, mongodb, sqlserver, cockroachdb
2747
+ local provider="$dialect"
2748
+ case "$dialect" in
2749
+ postgres|postgresql) provider="postgresql" ;;
2750
+ mssql) provider="sqlserver" ;;
2751
+ mariadb) provider="mysql" ;;
2752
+ oracle|db2|hana|hsqldb|spanner|sybase) provider="sqlite" ;; # Prisma has no native provider — keep sqlite placeholder
2753
+ esac
2754
+ write_if_missing "prisma/schema.prisma" "\
2755
+ // Minimal starter — edit freely. Run 'mostajs' menu 1 to convert to EntitySchema.
2756
+ generator client {
2757
+ provider = \"prisma-client-js\"
2758
+ }
2759
+
2760
+ datasource db {
2761
+ provider = \"${provider}\"
2762
+ url = env(\"DATABASE_URL\")
2763
+ }
2764
+
2765
+ model User {
2766
+ id String @id @default(uuid())
2767
+ email String @unique
2768
+ password String
2769
+ name String?
2770
+ createdAt DateTime @default(now())
2771
+ updatedAt DateTime @updatedAt
2772
+ }
2773
+ "
2774
+
2775
+ # --- src/lib/db.ts (createPrismaLikeDb) ---
2776
+ write_if_missing "src/lib/db.ts" "\
2777
+ // Generated by 'mostajs init' — @mostajs/orm-bridge entry point.
2778
+ // Every Prisma-style db.User.findUnique(...) call below is routed to
2779
+ // @mostajs/orm (13 dialects). Edit DB_DIALECT / SGBD_URI in .env to switch.
2780
+ import { createPrismaLikeDb } from '@mostajs/orm-bridge/prisma-client'
2781
+
2782
+ export const db = createPrismaLikeDb()
2783
+ "
2784
+
2785
+ echo
2786
+ echo -e " ${BOLD}${created}${RESET} file(s) created, ${DIM}${skipped}${RESET} skipped"
2787
+ echo
2788
+ echo -e " ${BOLD}Next steps${RESET} :"
2789
+ echo -e " ${CYAN}1.${RESET} npm install @mostajs/orm @mostajs/orm-bridge @mostajs/orm-cli --legacy-peer-deps"
2790
+ echo -e " ${CYAN}2.${RESET} Edit ${DIM}prisma/schema.prisma${RESET} — add your models"
2791
+ echo -e " ${CYAN}3.${RESET} ${CYAN}mostajs${RESET} → menu 1 (Convert) → menu 3 (init DDL)"
2792
+ echo -e " ${CYAN}4.${RESET} ${CYAN}mostajs${RESET} → menu S (Seeds) — populate, hash, apply"
2793
+ echo -e " ${CYAN}5.${RESET} ${CYAN}npm run dev${RESET}"
2794
+ }
2795
+
2796
+ # ============================================================
2797
+ # `mostajs migrate` — incremental DDL diff / apply / status
2798
+ # ============================================================
2799
+ #
2800
+ # Subcommands :
2801
+ # diff — list ALTERs needed to make the live DB match entities.json
2802
+ # apply — execute those ALTERs (prompts for confirmation, --yes to skip)
2803
+ # status — show entities.json count + live tables count + missing columns
2804
+
2805
+ action_cli_migrate() {
2806
+ local sub="${1:-}"
2807
+ [[ -z "$sub" ]] && { action_migrate_help; return; }
2808
+ shift
2809
+ case "$sub" in
2810
+ diff|d) action_migrate_diff "$@" ;;
2811
+ apply|a) action_migrate_apply "$@" ;;
2812
+ status|s) action_migrate_status "$@" ;;
2813
+ help|h|--help) action_migrate_help ;;
2814
+ *) err "Unknown migrate subcommand: $sub"; action_migrate_help; return 1 ;;
2815
+ esac
2816
+ }
2817
+
2818
+ action_migrate_help() {
2819
+ cat <<EOF
2820
+
2821
+ ${BOLD}mostajs migrate${RESET} — incremental schema migration
2822
+
2823
+ ${CYAN}diff${RESET} show ALTER statements the DB needs to match entities.json
2824
+ ${CYAN}apply${RESET} execute those ALTERs (prompts for confirmation)
2825
+ flags : --yes (skip confirmation)
2826
+ ${CYAN}status${RESET} show live-vs-schema summary per entity
2827
+
2828
+ Every subcommand honors DB_DIALECT + SGBD_URI from ${DIM}.mostajs/config.env${RESET}.
2829
+
2830
+ EOF
2831
+ }
2832
+
2833
+ # Node helper : compare live columns vs schema.fields and emit ALTER plan as JSON.
2834
+ # Outputs to stdout : { changes: [{ table, column, sql }], ok: bool }
2835
+ _migrate_compute_plan() {
2836
+ load_env
2837
+ local entities_json="$GENERATED_DIR/entities.json"
2838
+ if [[ ! -f "$entities_json" ]]; then
2839
+ err "No entities.json — run menu 1 (Convert) first."
2840
+ return 1
2841
+ fi
2842
+ ENT_PATH="$entities_json" DIALECT="$DB_DIALECT" URI="$SGBD_URI" \
2843
+ node --input-type=module -e "
2844
+ import { readFileSync } from 'node:fs';
2845
+ import { getDialect } from '${PROJECT_ROOT}/node_modules/@mostajs/orm/dist/index.js';
2846
+ const entities = JSON.parse(readFileSync(process.env.ENT_PATH, 'utf8'));
2847
+ const d = await getDialect({ dialect: process.env.DIALECT, uri: process.env.URI, schemaStrategy: 'none' });
2848
+
2849
+ // Use the dialect's own introspection — protected method, exposed via cast
2850
+ const changes = [];
2851
+ for (const e of entities) {
2852
+ let live;
2853
+ try {
2854
+ live = await (d).getExistingColumns(e.collection);
2855
+ } catch {
2856
+ changes.push({ table: e.collection, column: '*', sql: '-- (cannot introspect — run menu 3 first)' });
2857
+ continue;
2858
+ }
2859
+ const hasCol = (name) => {
2860
+ const lc = name.toLowerCase();
2861
+ for (const c of live) if (c.toLowerCase() === lc) return true;
2862
+ return false;
2863
+ };
2864
+ // Field columns
2865
+ for (const [name, f] of Object.entries(e.fields || {})) {
2866
+ if (name === '_id') continue;
2867
+ if (hasCol(name)) continue;
2868
+ // Reconstruct the ALTER — d has fieldToSqlType + getIdColumnType + quoteIdentifier
2869
+ const q = (n) => (d).quoteIdentifier(n);
2870
+ let sql;
2871
+ if (name === 'id') {
2872
+ sql = 'ALTER TABLE ' + q(e.collection) + ' ADD ' + q('id') + ' ' + (d).getIdColumnType();
2873
+ } else {
2874
+ sql = 'ALTER TABLE ' + q(e.collection) + ' ADD ' + q(name) + ' ' + (d).fieldToSqlType(f);
2875
+ }
2876
+ changes.push({ table: e.collection, column: name, sql });
2877
+ }
2878
+ // Relation FK columns
2879
+ for (const [rname, rel] of Object.entries(e.relations || {})) {
2880
+ if (rel.type !== 'many-to-one' && rel.type !== 'one-to-one') continue;
2881
+ const colName = rel.joinColumn || (rname + 'Id');
2882
+ if (hasCol(colName)) continue;
2883
+ const q = (n) => (d).quoteIdentifier(n);
2884
+ changes.push({
2885
+ table: e.collection, column: colName,
2886
+ sql: 'ALTER TABLE ' + q(e.collection) + ' ADD ' + q(colName) + ' ' + (d).getIdColumnType(),
2887
+ });
2888
+ }
2889
+ }
2890
+ await d.disconnect();
2891
+ console.log(JSON.stringify({ ok: true, changes }));
2892
+ "
2893
+ }
2894
+
2895
+ action_migrate_diff() {
2896
+ header
2897
+ echo -e "${BOLD}${MAGENTA}▶ mostajs migrate diff${RESET}"
2898
+ echo
2899
+ local plan_json
2900
+ plan_json=$(_migrate_compute_plan) || { pause; return 1; }
2901
+ local count
2902
+ count=$(echo "$plan_json" | node -e "process.stdin.on('data',d=>{console.log(JSON.parse(d).changes.length)})" 2>/dev/null || echo '?')
2903
+ if [[ "$count" == "0" ]]; then
2904
+ ok "Schema is up to date — nothing to ALTER."
2905
+ return 0
2906
+ fi
2907
+ echo -e " ${BOLD}${count}${RESET} pending change(s) :"
2908
+ echo
2909
+ echo "$plan_json" | node -e "
2910
+ let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{
2911
+ const p = JSON.parse(d);
2912
+ for (const ch of p.changes) console.log(' ' + ch.sql + ';');
2913
+ });
2914
+ "
2915
+ echo
2916
+ echo -e " Run ${CYAN}mostajs migrate apply${RESET} to execute these statements."
2917
+ }
2918
+
2919
+ action_migrate_apply() {
2920
+ local auto_yes=0
2921
+ [[ "${1:-}" == "--yes" || "${1:-}" == "-y" ]] && auto_yes=1
2922
+ header
2923
+ echo -e "${BOLD}${MAGENTA}▶ mostajs migrate apply${RESET}"
2924
+ echo
2925
+ local plan_json
2926
+ plan_json=$(_migrate_compute_plan) || return 1
2927
+ local count
2928
+ count=$(echo "$plan_json" | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>console.log(JSON.parse(d).changes.length))" 2>/dev/null || echo 0)
2929
+ if [[ "$count" == "0" ]]; then
2930
+ ok "Schema is up to date — nothing to ALTER."
2931
+ return 0
2932
+ fi
2933
+ echo " Pending : ${BOLD}${count}${RESET} statement(s)"
2934
+ echo
2935
+ echo "$plan_json" | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{for(const ch of JSON.parse(d).changes) console.log(' ' + ch.sql + ';')})"
2936
+ echo
2937
+ if [[ $auto_yes -eq 0 ]]; then
2938
+ if ! confirm "Execute these ALTER statements?"; then
2939
+ dim " Aborted."
2940
+ return
2941
+ fi
2942
+ fi
2943
+
2944
+ # Execute
2945
+ load_env
2946
+ PLAN="$plan_json" DIALECT="$DB_DIALECT" URI="$SGBD_URI" \
2947
+ node --input-type=module -e "
2948
+ import { getDialect } from '${PROJECT_ROOT}/node_modules/@mostajs/orm/dist/index.js';
2949
+ const plan = JSON.parse(process.env.PLAN);
2950
+ const d = await getDialect({ dialect: process.env.DIALECT, uri: process.env.URI, schemaStrategy: 'none' });
2951
+ let ok = 0, fail = 0;
2952
+ for (const ch of plan.changes) {
2953
+ try {
2954
+ await d.executeRun(ch.sql, []);
2955
+ console.log(' ✓ ' + ch.table + '.' + ch.column);
2956
+ ok++;
2957
+ } catch (e) {
2958
+ console.error(' ✗ ' + ch.table + '.' + ch.column + ' : ' + e.message);
2959
+ fail++;
2960
+ }
2961
+ }
2962
+ await d.disconnect();
2963
+ console.log('\nApplied : ' + ok + ' ok, ' + fail + ' failed');
2964
+ process.exit(fail > 0 ? 1 : 0);
2965
+ "
2966
+ }
2967
+
2968
+ action_migrate_status() {
2969
+ header
2970
+ echo -e "${BOLD}${MAGENTA}▶ mostajs migrate status${RESET}"
2971
+ echo
2972
+ load_env
2973
+ local ent_json="$GENERATED_DIR/entities.json"
2974
+ if [[ ! -f "$ent_json" ]]; then
2975
+ err "No entities.json — run menu 1 (Convert) first."
2976
+ return 1
2977
+ fi
2978
+ ENT_PATH="$ent_json" DIALECT="$DB_DIALECT" URI="$SGBD_URI" \
2979
+ node --input-type=module -e "
2980
+ import { readFileSync } from 'node:fs';
2981
+ import { getDialect } from '${PROJECT_ROOT}/node_modules/@mostajs/orm/dist/index.js';
2982
+ const entities = JSON.parse(readFileSync(process.env.ENT_PATH, 'utf8'));
2983
+ const d = await getDialect({ dialect: process.env.DIALECT, uri: process.env.URI, schemaStrategy: 'none' });
2984
+ let existing = 0, missing = 0, lagging = 0;
2985
+ for (const e of entities) {
2986
+ let live;
2987
+ try { live = await (d).getExistingColumns(e.collection); }
2988
+ catch { live = new Set(); }
2989
+ if (!live || live.size === 0) { console.log(' ✗ ' + e.collection + ' — table not found'); missing++; continue; }
2990
+ const hasCol = (n) => { const lc = n.toLowerCase(); for (const c of live) if (c.toLowerCase() === lc) return true; return false; };
2991
+ const schemaCols = Object.keys(e.fields || {});
2992
+ const need = schemaCols.filter(c => !hasCol(c));
2993
+ if (need.length) {
2994
+ console.log(' ⚠ ' + e.collection + ' — missing ' + need.length + ' column(s) : ' + need.join(', '));
2995
+ lagging++;
2996
+ } else {
2997
+ console.log(' ✓ ' + e.collection + ' (' + live.size + ' cols live, ' + schemaCols.length + ' in schema)');
2998
+ existing++;
2999
+ }
3000
+ }
3001
+ await d.disconnect();
3002
+ console.log('\n ' + existing + ' up-to-date · ' + lagging + ' need migrate · ' + missing + ' missing');
3003
+ "
3004
+ }
3005
+
2572
3006
  # ============================================================
2573
3007
  # CLI SUBCOMMANDS (non-interactive)
2574
3008
  # ============================================================
@@ -2654,6 +3088,26 @@ run_subcommand() {
2654
3088
  health|h)
2655
3089
  action_healthcheck
2656
3090
  ;;
3091
+ init)
3092
+ # mostajs init [--dialect sqlite|postgres|mongodb|...] [--force]
3093
+ # Scaffold a fresh project with bridge-ready layout :
3094
+ # .env (PORT, DB_DIALECT, SGBD_URI, AUTH_SECRET)
3095
+ # prisma/schema.prisma (minimal — User model only)
3096
+ # src/lib/db.ts (createPrismaLikeDb)
3097
+ # .mostajs/config.env (mirrors .env for the runner)
3098
+ # .mostajs/generated/entities.json (empty array)
3099
+ shift
3100
+ action_cli_init "$@"
3101
+ ;;
3102
+ migrate|mig|m)
3103
+ # mostajs migrate <subcommand> [options]
3104
+ # Subcommands :
3105
+ # diff — show ALTER statements the target DB needs to match entities.json
3106
+ # apply — execute those ALTERs (with confirmation)
3107
+ # status — show what's in entities.json vs what's live in the DB
3108
+ shift
3109
+ action_cli_migrate "$@"
3110
+ ;;
2657
3111
  install-bridge|ib)
2658
3112
  # mostajs install-bridge [--apply] [--file X] [--project P] [--restore]
2659
3113
  # Codemod : scans the project for `new PrismaClient(...)` sites and rewrites
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/orm-cli",
3
- "version": "0.4.6",
3
+ "version": "0.5.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",