@mostajs/orm-cli 0.2.3 → 0.3.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 +502 -4
  2. package/package.json +1 -1
package/bin/mostajs.sh CHANGED
@@ -437,6 +437,7 @@ menu_main() {
437
437
  echo -e " ${CYAN}7${RESET}) View logs"
438
438
  echo -e " ${CYAN}8${RESET}) Health checks"
439
439
  echo -e " ${CYAN}9${RESET}) Generate boilerplate (src/db.ts with bridge)"
440
+ echo -e " ${CYAN}s${RESET}) ${BOLD}Seeding${RESET} (upload / validate / apply seed data)"
440
441
  echo -e " ${CYAN}0${RESET}) About / Help"
441
442
  echo
442
443
  echo -e " ${RED}q${RESET}) Quit"
@@ -453,6 +454,7 @@ menu_main() {
453
454
  7) action_logs ;;
454
455
  8) action_healthcheck ;;
455
456
  9) action_generate_boilerplate ;;
457
+ s|S) menu_seeding ;;
456
458
  0) action_about ;;
457
459
  q|Q) exit 0 ;;
458
460
  *) warn "Unknown choice"; pause ;;
@@ -1365,12 +1367,23 @@ show_urls() {
1365
1367
  load_env
1366
1368
  local ip
1367
1369
  ip=$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost)
1370
+
1371
+ # Parse mosta-net URL from config (full URL, not just port)
1372
+ local mosta_url="${MOSTA_NET_URL:-http://localhost:14488}"
1373
+ local mosta_host="${mosta_url#*://}"
1374
+ mosta_host="${mosta_host%%/*}"
1375
+
1376
+ # App URL : derive from APP_PORT
1377
+ local app_port="${APP_PORT:-3000}"
1378
+
1368
1379
  echo
1369
1380
  echo -e "${BOLD}Access URLs${RESET}"
1370
- echo -e " Dev server (local) : ${CYAN}http://localhost:${APP_PORT:-3000}${RESET}"
1371
- echo -e " Dev server (mobile) : ${CYAN}http://${ip}:${APP_PORT:-3000}${RESET}"
1372
- echo -e " mosta-net : ${CYAN}http://localhost:${MOSTA_NET_PORT:-4447}${RESET}"
1373
- echo -e " MCP endpoint (AI) : ${CYAN}http://localhost:${MOSTA_NET_PORT:-4447}/mcp${RESET}"
1381
+ echo -e " Dev server (local) : ${CYAN}http://localhost:${app_port}${RESET}"
1382
+ echo -e " Dev server (mobile) : ${CYAN}http://${ip}:${app_port}${RESET}"
1383
+ echo -e " mosta-net base : ${CYAN}${mosta_url}${RESET}"
1384
+ echo -e " REST CRUD : ${CYAN}${mosta_url}/api/v1/<collection>${RESET}"
1385
+ echo -e " MCP endpoint (AI) : ${CYAN}${mosta_url}/mcp${RESET}"
1386
+ echo -e " LAN (mobile) : ${CYAN}http://${ip}${mosta_host#localhost}${RESET}"
1374
1387
  }
1375
1388
 
1376
1389
  # ============================================================
@@ -1464,6 +1477,491 @@ action_healthcheck() {
1464
1477
  pause
1465
1478
  }
1466
1479
 
1480
+ # ============================================================
1481
+ # MENU S : SEEDING
1482
+ # ============================================================
1483
+
1484
+ menu_seeding() {
1485
+ load_env
1486
+ header
1487
+ echo -e "${BOLD}${MAGENTA}▶ Seeding — populate databases with test / initial data${RESET}"
1488
+ echo
1489
+ local seed_dir="$CONFIG_DIR/seeds"
1490
+ mkdir -p "$seed_dir"
1491
+ local count
1492
+ count=$(ls -1 "$seed_dir"/*.json 2>/dev/null | wc -l)
1493
+ echo -e " Seed directory : ${DIM}${seed_dir}${RESET}"
1494
+ echo -e " Seed files : ${DIM}${count} .json${RESET}"
1495
+ [[ $count -gt 0 ]] && ls "$seed_dir"/*.json 2>/dev/null | while read f; do
1496
+ local rows; rows=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$f','utf8')).length)}catch{console.log('?')}" 2>/dev/null)
1497
+ dim " - $(basename "$f") (${rows} rows)"
1498
+ done
1499
+ echo
1500
+ echo -e "${BOLD}━━━ SEEDING MENU ━━━${RESET}"
1501
+ echo
1502
+ echo -e " ${CYAN}1${RESET}) Upload / import seed file(s)"
1503
+ echo -e " ${CYAN}2${RESET}) Generate seed templates (one empty .json per entity)"
1504
+ echo -e " ${CYAN}3${RESET}) Validate seeds against schema (dry-run, no DB writes)"
1505
+ echo -e " ${CYAN}4${RESET}) Apply seeds to primary DB"
1506
+ echo -e " ${CYAN}5${RESET}) Apply seeds with upsert (insert or update by id)"
1507
+ echo -e " ${CYAN}6${RESET}) Truncate + apply (DESTRUCTIVE — wipes tables first)"
1508
+ echo -e " ${CYAN}7${RESET}) Dump current DB rows → .mostajs/seeds-dump/"
1509
+ echo -e " ${CYAN}8${RESET}) Clear the seeds directory"
1510
+ echo -e " ${CYAN}9${RESET}) Show a seed file"
1511
+ echo
1512
+ echo -e " ${CYAN}b${RESET}) Back"
1513
+ echo
1514
+ local choice
1515
+ choice=$(ask "Choice" "1")
1516
+ case "$choice" in
1517
+ 1) action_seed_upload ;;
1518
+ 2) action_seed_generate_templates ;;
1519
+ 3) action_seed_apply "validate" ;;
1520
+ 4) action_seed_apply "apply" ;;
1521
+ 5) action_seed_apply "upsert" ;;
1522
+ 6) action_seed_apply "truncate-apply" ;;
1523
+ 7) action_seed_dump ;;
1524
+ 8) action_seed_clear ;;
1525
+ 9) action_seed_show ;;
1526
+ b|B) return ;;
1527
+ *) warn "Unknown"; pause ;;
1528
+ esac
1529
+ menu_seeding
1530
+ }
1531
+
1532
+ # ------------------------------------------------------------
1533
+ # seed : upload / import
1534
+ # ------------------------------------------------------------
1535
+
1536
+ action_seed_upload() {
1537
+ header
1538
+ echo -e "${BOLD}${MAGENTA}▶ Upload seed file(s)${RESET}"
1539
+ echo
1540
+ local seed_dir="$CONFIG_DIR/seeds"
1541
+ mkdir -p "$seed_dir"
1542
+
1543
+ echo "Supported forms :"
1544
+ dim " (a) Single file with all collections : seeds.json → { users: [...], posts: [...] }"
1545
+ dim " (b) One file per collection (preferred): seeds/users.json = [...rows...]"
1546
+ dim " (c) CSV (header row = field names) : seeds/users.csv"
1547
+ echo
1548
+ local src
1549
+ src=$(ask "Path to seed file or directory (tilde OK)")
1550
+ [[ -z "$src" ]] && return
1551
+ src="${src/#\~/$HOME}"
1552
+ [[ ! -e "$src" ]] && { err "Path does not exist : $src"; pause; return; }
1553
+
1554
+ if [[ -d "$src" ]]; then
1555
+ local n=0
1556
+ for f in "$src"/*.{json,csv}; do
1557
+ [[ -f "$f" ]] || continue
1558
+ cp "$f" "$seed_dir/" && n=$((n+1))
1559
+ done
1560
+ ok "Imported $n file(s) from $src → $seed_dir"
1561
+ elif [[ "$src" =~ \.json$ ]]; then
1562
+ # Case (a) or (b) — detect
1563
+ local is_map
1564
+ is_map=$(node -e "
1565
+ const d = JSON.parse(require('fs').readFileSync('$src','utf8'));
1566
+ console.log(!Array.isArray(d) && typeof d === 'object' ? 'yes' : 'no');
1567
+ " 2>/dev/null)
1568
+ if [[ "$is_map" == "yes" ]]; then
1569
+ # Split into per-collection files
1570
+ node -e "
1571
+ const { writeFileSync } = require('fs');
1572
+ const d = JSON.parse(require('fs').readFileSync('$src','utf8'));
1573
+ let n = 0;
1574
+ for (const [coll, rows] of Object.entries(d)) {
1575
+ if (!Array.isArray(rows)) continue;
1576
+ writeFileSync('$seed_dir/' + coll + '.json', JSON.stringify(rows, null, 2));
1577
+ console.log(' wrote ' + coll + '.json (' + rows.length + ' rows)');
1578
+ n++;
1579
+ }
1580
+ console.log('split ' + n + ' collections');
1581
+ " 2>&1 | tee -a "$LOG_DIR/seed.log"
1582
+ else
1583
+ # Single-collection array
1584
+ local base; base=$(basename "$src")
1585
+ cp "$src" "$seed_dir/$base"
1586
+ ok "Copied → $seed_dir/$base"
1587
+ fi
1588
+ elif [[ "$src" =~ \.csv$ ]]; then
1589
+ local base; base=$(basename "$src" .csv)
1590
+ # Convert CSV to JSON (minimal — header row + comma-separated values)
1591
+ node -e "
1592
+ const { readFileSync, writeFileSync } = require('fs');
1593
+ const lines = readFileSync('$src','utf8').split(/\r?\n/).filter(Boolean);
1594
+ if (lines.length < 2) { console.error('CSV too small'); process.exit(1); }
1595
+ const headers = lines[0].split(',');
1596
+ const rows = lines.slice(1).map(line => {
1597
+ const vals = line.split(',');
1598
+ const o = {};
1599
+ headers.forEach((h, i) => { o[h.trim()] = vals[i]?.trim() ?? null; });
1600
+ return o;
1601
+ });
1602
+ writeFileSync('$seed_dir/${base}.json', JSON.stringify(rows, null, 2));
1603
+ console.log('Converted ' + rows.length + ' rows → $seed_dir/${base}.json');
1604
+ " 2>&1 | tee -a "$LOG_DIR/seed.log"
1605
+ else
1606
+ err "Unsupported file type — use .json or .csv"
1607
+ fi
1608
+ pause
1609
+ }
1610
+
1611
+ # ------------------------------------------------------------
1612
+ # seed : generate empty templates
1613
+ # ------------------------------------------------------------
1614
+
1615
+ action_seed_generate_templates() {
1616
+ header
1617
+ echo -e "${BOLD}${MAGENTA}▶ Generate empty seed templates${RESET}"
1618
+ echo
1619
+
1620
+ if [[ ! -f "$GENERATED_DIR/entities.json" ]]; then
1621
+ err "No entities.json — run menu 1 (Convert) first"
1622
+ pause; return
1623
+ fi
1624
+
1625
+ local seed_dir="$CONFIG_DIR/seeds"
1626
+ mkdir -p "$seed_dir"
1627
+
1628
+ if ! confirm "Generate a template .json per entity (will NOT overwrite existing files)?"; then
1629
+ return
1630
+ fi
1631
+
1632
+ node -e "
1633
+ const { readFileSync, writeFileSync, existsSync } = require('fs');
1634
+ const entities = JSON.parse(readFileSync('$GENERATED_DIR/entities.json','utf8'));
1635
+ let created = 0, skipped = 0;
1636
+ for (const e of entities) {
1637
+ const file = '$seed_dir/' + e.collection + '.json';
1638
+ if (existsSync(file)) { skipped++; continue; }
1639
+
1640
+ // Build a sample row from field defaults
1641
+ const sample = {};
1642
+ for (const [k, def] of Object.entries(e.fields ?? {})) {
1643
+ if (def.default !== undefined && typeof def.default !== 'object') sample[k] = def.default;
1644
+ else if (def.type === 'string' && def.enum) sample[k] = def.enum[0] ?? '';
1645
+ else if (def.type === 'string') sample[k] = 'example';
1646
+ else if (def.type === 'number') sample[k] = 0;
1647
+ else if (def.type === 'boolean') sample[k] = false;
1648
+ else if (def.type === 'date') sample[k] = new Date().toISOString();
1649
+ else if (def.type === 'json') sample[k] = {};
1650
+ else if (def.type === 'array') sample[k] = [];
1651
+ }
1652
+ writeFileSync(file, JSON.stringify([sample], null, 2));
1653
+ created++;
1654
+ }
1655
+ console.log('Created ' + created + ', skipped ' + skipped + ' (already existed)');
1656
+ " 2>&1 | tee -a "$LOG_DIR/seed.log"
1657
+ pause
1658
+ }
1659
+
1660
+ # ------------------------------------------------------------
1661
+ # seed : validate + apply
1662
+ # ------------------------------------------------------------
1663
+
1664
+ action_seed_apply() {
1665
+ local mode="$1" # validate | apply | upsert | truncate-apply
1666
+ header
1667
+ local title="Validate seeds (dry-run)"
1668
+ case "$mode" in
1669
+ apply) title="Apply seeds (insert)" ;;
1670
+ upsert) title="Apply seeds (upsert by id)" ;;
1671
+ truncate-apply) title="DESTRUCTIVE: truncate + apply" ;;
1672
+ esac
1673
+ echo -e "${BOLD}${MAGENTA}▶ $title${RESET}"
1674
+ echo
1675
+
1676
+ load_env
1677
+
1678
+ if [[ ! -f "$GENERATED_DIR/entities.json" ]]; then
1679
+ err "No entities.json — run menu 1 (Convert) first"
1680
+ pause; return
1681
+ fi
1682
+
1683
+ local seed_dir="$CONFIG_DIR/seeds"
1684
+ local file_count
1685
+ file_count=$(ls -1 "$seed_dir"/*.json 2>/dev/null | wc -l)
1686
+ if [[ $file_count -eq 0 ]]; then
1687
+ err "No seed files in $seed_dir — use menu S → 1 or 2"
1688
+ pause; return
1689
+ fi
1690
+
1691
+ if [[ "$mode" == "truncate-apply" ]]; then
1692
+ warn "This will TRUNCATE all tables listed in seeds before inserting."
1693
+ confirm "Really wipe the tables ?" || return
1694
+ if ! confirm "Are you SURE ? This cannot be undone." ; then return; fi
1695
+ fi
1696
+
1697
+ if [[ "$mode" != "validate" ]]; then
1698
+ if [[ -z "${DB_DIALECT:-}" || -z "${SGBD_URI:-}" ]]; then
1699
+ err "No DB configured (menu 2 first)"
1700
+ pause; return
1701
+ fi
1702
+ # Dialect specific driver
1703
+ ensure_pkg "@mostajs/orm" || return
1704
+ ensure_dialect_driver "$DB_DIALECT" || warn "Driver for $DB_DIALECT may be missing"
1705
+ fi
1706
+
1707
+ local orm_path
1708
+ orm_path=$(resolve_pkg_path "@mostajs/orm" 2>/dev/null)
1709
+ if [[ "$mode" != "validate" ]] && [[ -z "$orm_path" ]]; then
1710
+ err "Cannot resolve @mostajs/orm — install it first"
1711
+ pause; return
1712
+ fi
1713
+
1714
+ cat > "$CONFIG_DIR/seed-runner.mjs" <<EOF
1715
+ import { readFileSync, readdirSync } from 'fs';
1716
+ import { join } from 'path';
1717
+
1718
+ const MODE = process.argv[2] ?? 'validate';
1719
+ const SEEDDIR = process.argv[3];
1720
+ const ENTPATH = process.argv[4];
1721
+ const DIALECT = process.env.DB_DIALECT ?? '';
1722
+ const URI = process.env.SGBD_URI ?? '';
1723
+
1724
+ function stripScheme(u) {
1725
+ if (u.startsWith('sqlite://')) return u.slice(9);
1726
+ if (u.startsWith('sqlite:')) return u.slice(7);
1727
+ return u;
1728
+ }
1729
+
1730
+ const entities = JSON.parse(readFileSync(ENTPATH, 'utf8'));
1731
+ const entityByCollection = Object.fromEntries(entities.map(e => [e.collection, e]));
1732
+ const entityByName = Object.fromEntries(entities.map(e => [e.name, e]));
1733
+
1734
+ // ---------- Load seed files ----------
1735
+ const seeds = {}; // collection -> rows
1736
+ for (const f of readdirSync(SEEDDIR).filter(x => x.endsWith('.json'))) {
1737
+ const coll = f.replace(/\\.json$/, '');
1738
+ let data;
1739
+ try {
1740
+ data = JSON.parse(readFileSync(join(SEEDDIR, f), 'utf8'));
1741
+ } catch (e) {
1742
+ console.error(' \u2717 ' + f + ' : invalid JSON (' + e.message + ')');
1743
+ continue;
1744
+ }
1745
+ if (!Array.isArray(data)) {
1746
+ console.error(' \u2717 ' + f + ' : file is not an array of rows');
1747
+ continue;
1748
+ }
1749
+ seeds[coll] = data;
1750
+ }
1751
+
1752
+ // ---------- Validate ----------
1753
+ function validateRow(row, entity) {
1754
+ const errors = [];
1755
+ const fieldNames = new Set(Object.keys(entity.fields ?? {}));
1756
+ const relationNames = new Set(Object.keys(entity.relations ?? {}));
1757
+ // Required fields
1758
+ for (const [k, def] of Object.entries(entity.fields ?? {})) {
1759
+ if (def.required && (row[k] === undefined || row[k] === null)) {
1760
+ errors.push('missing required field "' + k + '"');
1761
+ }
1762
+ // Enum
1763
+ if (def.enum && row[k] !== undefined && !def.enum.includes(row[k])) {
1764
+ errors.push('field "' + k + '" not in enum ' + JSON.stringify(def.enum) + ' (got: ' + JSON.stringify(row[k]) + ')');
1765
+ }
1766
+ // Type basic check
1767
+ if (row[k] !== undefined && row[k] !== null) {
1768
+ const val = row[k];
1769
+ const t = def.type;
1770
+ if (t === 'number' && typeof val !== 'number') errors.push('field "' + k + '" expected number, got ' + typeof val);
1771
+ if (t === 'boolean' && typeof val !== 'boolean') errors.push('field "' + k + '" expected boolean, got ' + typeof val);
1772
+ if (t === 'string' && typeof val !== 'string') errors.push('field "' + k + '" expected string, got ' + typeof val);
1773
+ if (t === 'date' && typeof val !== 'string' && !(val instanceof Date)) errors.push('field "' + k + '" expected date, got ' + typeof val);
1774
+ }
1775
+ }
1776
+ // Unknown fields (warn-level)
1777
+ const warnings = [];
1778
+ for (const k of Object.keys(row)) {
1779
+ if (!fieldNames.has(k) && !relationNames.has(k) && k !== 'id' && k !== '_id') {
1780
+ warnings.push('unknown field "' + k + '" (not in schema)');
1781
+ }
1782
+ }
1783
+ return { errors, warnings };
1784
+ }
1785
+
1786
+ let totalRows = 0;
1787
+ let totalErrors = 0;
1788
+ let totalWarnings = 0;
1789
+ const reports = [];
1790
+
1791
+ for (const [coll, rows] of Object.entries(seeds)) {
1792
+ const entity = entityByCollection[coll] ?? entityByName[coll];
1793
+ if (!entity) {
1794
+ console.error(' \u2717 ' + coll + ' : no matching entity (collection or name)');
1795
+ continue;
1796
+ }
1797
+ let collErrs = 0, collWarns = 0;
1798
+ rows.forEach((row, i) => {
1799
+ const { errors, warnings } = validateRow(row, entity);
1800
+ if (errors.length) {
1801
+ collErrs += errors.length;
1802
+ for (const e of errors) console.error(' \u2717 ' + coll + '[' + i + '] : ' + e);
1803
+ }
1804
+ collWarns += warnings.length;
1805
+ for (const w of warnings) console.warn(' \u26A0 ' + coll + '[' + i + '] : ' + w);
1806
+ });
1807
+ const mark = collErrs === 0 ? '\u2713' : '\u2717';
1808
+ console.log(' ' + mark + ' ' + coll + ' : ' + rows.length + ' rows, ' + collErrs + ' errors, ' + collWarns + ' warnings');
1809
+ reports.push({ coll, rows: rows.length, errors: collErrs, warnings: collWarns });
1810
+ totalRows += rows.length;
1811
+ totalErrors += collErrs;
1812
+ totalWarnings += collWarns;
1813
+ }
1814
+
1815
+ console.log();
1816
+ console.log('Validation : ' + totalRows + ' rows · ' + totalErrors + ' errors · ' + totalWarnings + ' warnings');
1817
+
1818
+ if (MODE === 'validate') {
1819
+ process.exit(totalErrors > 0 ? 1 : 0);
1820
+ }
1821
+
1822
+ if (totalErrors > 0) {
1823
+ console.error('Refusing to apply : fix validation errors first (run menu S → 3).');
1824
+ process.exit(2);
1825
+ }
1826
+
1827
+ // ---------- Apply ----------
1828
+ const { getDialect } = await import('$orm_path');
1829
+ const uri = DIALECT === 'sqlite' ? stripScheme(URI) : URI;
1830
+ const d = await getDialect({ dialect: DIALECT, uri, schemaStrategy: 'update' });
1831
+ await d.initSchema(entities);
1832
+
1833
+ let inserted = 0, failed = 0;
1834
+
1835
+ for (const [coll, rows] of Object.entries(seeds)) {
1836
+ const entity = entityByCollection[coll] ?? entityByName[coll];
1837
+ if (!entity) continue;
1838
+
1839
+ if (MODE === 'truncate-apply') {
1840
+ try {
1841
+ await d.deleteMany(entity, {});
1842
+ console.log(' \u2205 truncated ' + coll);
1843
+ } catch (e) {
1844
+ console.error(' \u2717 truncate ' + coll + ' : ' + (e.message ?? e));
1845
+ }
1846
+ }
1847
+
1848
+ for (const row of rows) {
1849
+ try {
1850
+ if (MODE === 'upsert' && (row.id || row._id)) {
1851
+ const id = row.id ?? row._id;
1852
+ const existing = await d.findById(entity, String(id)).catch(() => null);
1853
+ if (existing) {
1854
+ await d.update(entity, String(id), row);
1855
+ } else {
1856
+ await d.create(entity, row);
1857
+ }
1858
+ } else {
1859
+ await d.create(entity, row);
1860
+ }
1861
+ inserted++;
1862
+ } catch (e) {
1863
+ failed++;
1864
+ console.error(' \u2717 ' + coll + ' : ' + (e.message ?? e));
1865
+ }
1866
+ }
1867
+ console.log(' \u2713 ' + coll + ' done');
1868
+ }
1869
+
1870
+ console.log();
1871
+ console.log('Applied : ' + inserted + ' inserted · ' + failed + ' failed');
1872
+ await d.disconnect().catch(() => {});
1873
+ process.exit(failed > 0 ? 1 : 0);
1874
+ EOF
1875
+
1876
+ cd "$PROJECT_ROOT" || return
1877
+ export DB_DIALECT="${DB_DIALECT:-}"
1878
+ export SGBD_URI="${SGBD_URI:-}"
1879
+ node "$CONFIG_DIR/seed-runner.mjs" "$mode" "$seed_dir" "$GENERATED_DIR/entities.json" 2>&1 | tee "$LOG_DIR/seed-${mode}.log"
1880
+ echo
1881
+ pause
1882
+ }
1883
+
1884
+ # ------------------------------------------------------------
1885
+ # seed : dump current DB → seed files
1886
+ # ------------------------------------------------------------
1887
+
1888
+ action_seed_dump() {
1889
+ header
1890
+ echo -e "${BOLD}${MAGENTA}▶ Dump current DB rows${RESET}"
1891
+ echo
1892
+ load_env
1893
+ if [[ -z "${DB_DIALECT:-}" || -z "${SGBD_URI:-}" ]]; then
1894
+ err "No DB configured (menu 2 first)"
1895
+ pause; return
1896
+ fi
1897
+ if [[ ! -f "$GENERATED_DIR/entities.json" ]]; then
1898
+ err "No entities.json — run menu 1 (Convert) first"
1899
+ pause; return
1900
+ fi
1901
+ ensure_pkg "@mostajs/orm" || return
1902
+ local orm_path
1903
+ orm_path=$(resolve_pkg_path "@mostajs/orm") || return
1904
+ local dump_dir="$CONFIG_DIR/seeds-dump"
1905
+ mkdir -p "$dump_dir"
1906
+
1907
+ cat > "$CONFIG_DIR/seed-dump.mjs" <<EOF
1908
+ import { readFileSync, writeFileSync } from 'fs';
1909
+ import { getDialect } from '$orm_path';
1910
+
1911
+ const entities = JSON.parse(readFileSync('$GENERATED_DIR/entities.json','utf8'));
1912
+ const DIALECT = process.env.DB_DIALECT;
1913
+ let URI = process.env.SGBD_URI;
1914
+ if (DIALECT === 'sqlite') {
1915
+ if (URI.startsWith('sqlite://')) URI = URI.slice(9);
1916
+ else if (URI.startsWith('sqlite:')) URI = URI.slice(7);
1917
+ }
1918
+ const d = await getDialect({ dialect: DIALECT, uri: URI });
1919
+ await d.initSchema(entities);
1920
+
1921
+ for (const e of entities) {
1922
+ const rows = await d.find(e, {}, { limit: 10000 });
1923
+ writeFileSync('$dump_dir/' + e.collection + '.json', JSON.stringify(rows, null, 2));
1924
+ console.log(' \u2713 ' + e.collection + ' : ' + rows.length + ' rows');
1925
+ }
1926
+ await d.disconnect().catch(() => {});
1927
+ EOF
1928
+ cd "$PROJECT_ROOT"
1929
+ DB_DIALECT="$DB_DIALECT" SGBD_URI="$SGBD_URI" node "$CONFIG_DIR/seed-dump.mjs" 2>&1 | tee "$LOG_DIR/seed-dump.log"
1930
+ echo
1931
+ ok "Dump written to $dump_dir"
1932
+ pause
1933
+ }
1934
+
1935
+ action_seed_clear() {
1936
+ header
1937
+ local seed_dir="$CONFIG_DIR/seeds"
1938
+ local count; count=$(ls -1 "$seed_dir"/*.json 2>/dev/null | wc -l)
1939
+ [[ $count -eq 0 ]] && { warn "Already empty"; pause; return; }
1940
+ if confirm "Delete all $count seed files in $seed_dir ?"; then
1941
+ rm -f "$seed_dir"/*.json
1942
+ ok "Cleared"
1943
+ fi
1944
+ pause
1945
+ }
1946
+
1947
+ action_seed_show() {
1948
+ header
1949
+ local seed_dir="$CONFIG_DIR/seeds"
1950
+ local files=()
1951
+ for f in "$seed_dir"/*.json; do [[ -f "$f" ]] && files+=("$f"); done
1952
+ [[ ${#files[@]} -eq 0 ]] && { warn "No seed files"; pause; return; }
1953
+ echo "Pick a file to display :"
1954
+ local i=1
1955
+ for f in "${files[@]}"; do
1956
+ echo -e " ${CYAN}$i${RESET}) $(basename "$f")"
1957
+ i=$((i+1))
1958
+ done
1959
+ local num; num=$(ask "Number" 1)
1960
+ local idx=$((num-1))
1961
+ [[ $idx -ge 0 && $idx -lt ${#files[@]} ]] && ${PAGER:-less} "${files[$idx]}"
1962
+ pause
1963
+ }
1964
+
1467
1965
  # ============================================================
1468
1966
  # ACTION 9 : GENERATE BOILERPLATE
1469
1967
  # ============================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/orm-cli",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Universal CLI to integrate @mostajs/orm into any project — auto-detects Prisma, OpenAPI, JSON Schema. Interactive menu + subcommands. 13 databases.",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "AGPL-3.0-or-later",