@mostajs/orm-cli 0.5.2 → 0.5.4

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 +140 -4
  2. package/package.json +1 -1
package/bin/mostajs.sh CHANGED
@@ -1799,6 +1799,7 @@ menu_replicator() {
1799
1799
  echo -e " ${CYAN}8${RESET}) Run a CDC sync + show stats"
1800
1800
  echo -e " ${CYAN}9${RESET}) Remove a CDC rule"
1801
1801
  echo -e " ${CYAN}m${RESET}) ${BOLD}Open monitor${RESET} (live dashboard — localhost:14499)"
1802
+ echo -e " ${CYAN}s${RESET}) ${BOLD}Scaffold services${RESET} — services/replicator.mjs + services/monitor.mjs + package.json scripts"
1802
1803
  echo -e " ${CYAN}v${RESET}) View the raw tree file"
1803
1804
  echo -e " ${CYAN}c${RESET}) Clear (delete the tree file — DESTRUCTIVE)"
1804
1805
  echo
@@ -1817,6 +1818,7 @@ menu_replicator() {
1817
1818
  8) action_rep_sync ;;
1818
1819
  9) action_rep_remove_rule ;;
1819
1820
  m|M) action_rep_open_monitor ;;
1821
+ s|S) action_rep_scaffold_services ;;
1820
1822
  v|V) action_rep_view_tree ;;
1821
1823
  c|C) action_rep_clear ;;
1822
1824
  b|B) return ;;
@@ -1825,19 +1827,153 @@ menu_replicator() {
1825
1827
  menu_replicator
1826
1828
  }
1827
1829
 
1830
+ # ------------------------------------------------------------
1831
+ # Scaffold background services (replicator + monitor) + package.json patch
1832
+ # ------------------------------------------------------------
1833
+ action_rep_scaffold_services() {
1834
+ header
1835
+ echo -e "${BOLD}${MAGENTA}▶ Scaffold background services${RESET}"
1836
+ echo
1837
+ echo -e " This will :"
1838
+ echo -e " 1. ${CYAN}services/replicator.mjs${RESET} (from @mostajs/replicator)"
1839
+ echo -e " 2. ${CYAN}services/monitor.mjs${RESET} (from @mostajs/replica-monitor)"
1840
+ echo -e " 3. patch ${CYAN}package.json${RESET} : scripts.replicator / monitor / dev:all"
1841
+ echo -e " 4. install ${CYAN}concurrently${RESET} if missing"
1842
+ echo
1843
+ if ! confirm "Proceed?"; then return; fi
1844
+
1845
+ # Ensure the scaffolders' packages are installed locally
1846
+ ensure_pkg "@mostajs/replicator" "@mostajs/replica-monitor" || { pause; return; }
1847
+
1848
+ local force_arg=""
1849
+ if confirm "Overwrite existing services/*.mjs if present?"; then force_arg="--force"; fi
1850
+
1851
+ # Call each scaffolder (uses the lib's own emit logic — single source of truth)
1852
+ echo
1853
+ echo -e "${CYAN}▶ scaffoldReplicatorService${RESET}"
1854
+ node --input-type=module -e "
1855
+ const { scaffoldReplicatorService } = await import('${PROJECT_ROOT}/node_modules/@mostajs/replicator/dist/scaffold.js');
1856
+ const r = scaffoldReplicatorService({ projectDir: '${PROJECT_ROOT}', force: ${force_arg:+true}${force_arg:-false} });
1857
+ console.log(' ' + (r.wrote ? '✓' : '•') + ' ' + r.action + ' : ' + r.path);
1858
+ " 2>&1 | sed 's/^/ /'
1859
+
1860
+ echo
1861
+ echo -e "${CYAN}▶ scaffoldMonitorService${RESET}"
1862
+ node --input-type=module -e "
1863
+ const { scaffoldMonitorService } = await import('${PROJECT_ROOT}/node_modules/@mostajs/replica-monitor/dist/scaffold.js');
1864
+ const r = scaffoldMonitorService({ projectDir: '${PROJECT_ROOT}', force: ${force_arg:+true}${force_arg:-false} });
1865
+ console.log(' ' + (r.wrote ? '✓' : '•') + ' ' + r.action + ' : ' + r.path);
1866
+ " 2>&1 | sed 's/^/ /'
1867
+
1868
+ # Patch package.json scripts
1869
+ echo
1870
+ echo -e "${CYAN}▶ patching package.json scripts${RESET}"
1871
+ if [[ ! -f "$PROJECT_ROOT/package.json" ]]; then
1872
+ warn " no package.json found — skipping scripts patch"
1873
+ else
1874
+ PROJECT="$PROJECT_ROOT" node --input-type=module -e "
1875
+ const fs = await import('node:fs');
1876
+ const path = process.env.PROJECT + '/package.json';
1877
+ const pkg = JSON.parse(fs.readFileSync(path, 'utf8'));
1878
+ pkg.scripts = pkg.scripts || {};
1879
+ const add = (key, val) => {
1880
+ if (pkg.scripts[key]) {
1881
+ console.log(' • ' + key + ' already set (kept as-is)');
1882
+ } else {
1883
+ pkg.scripts[key] = val;
1884
+ console.log(' ✓ added ' + key);
1885
+ }
1886
+ };
1887
+ add('replicator', 'node services/replicator.mjs');
1888
+ add('monitor', 'node services/monitor.mjs');
1889
+ add('dev:all', 'concurrently --kill-others-on-fail --names next,rep,mon -c blue,magenta,cyan \"npm:dev\" \"npm:replicator\" \"npm:monitor\"');
1890
+ fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
1891
+ " 2>&1 | sed 's/^/ /'
1892
+ fi
1893
+
1894
+ # Ensure concurrently is installed (devDependency)
1895
+ echo
1896
+ echo -e "${CYAN}▶ checking concurrently${RESET}"
1897
+ if [[ -d "$PROJECT_ROOT/node_modules/concurrently" ]]; then
1898
+ ok " concurrently already installed"
1899
+ else
1900
+ if confirm "Install concurrently as a devDependency?"; then
1901
+ cd "$PROJECT_ROOT" || return 1
1902
+ local log_file="/tmp/mostajs-concurrently-$$.log"
1903
+ case "$PKG_MANAGER" in
1904
+ pnpm) pnpm add -D concurrently > "$log_file" 2>&1 & ;;
1905
+ yarn) yarn add -D concurrently > "$log_file" 2>&1 & ;;
1906
+ bun) bun add -d concurrently > "$log_file" 2>&1 & ;;
1907
+ *) npm install --save-dev concurrently --legacy-peer-deps > "$log_file" 2>&1 & ;;
1908
+ esac
1909
+ local pid=$! frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' tick=0
1910
+ while kill -0 "$pid" 2>/dev/null; do
1911
+ local f="${frames:$((tick % 10)):1}"
1912
+ printf "\r ${YELLOW}%s${RESET} installing concurrently ${DIM}(%ds)${RESET} " "$f" "$((tick / 5))"
1913
+ tick=$((tick + 1)); sleep 0.2
1914
+ done
1915
+ wait "$pid"; local rc=$?
1916
+ printf "\r%60s\r" ""
1917
+ if [[ $rc -eq 0 ]]; then ok " concurrently installed"
1918
+ else err " install failed (see $log_file)"; tail -3 "$log_file"
1919
+ fi
1920
+ rm -f "$log_file"
1921
+ fi
1922
+ fi
1923
+
1924
+ echo
1925
+ echo -e "${BOLD}${GREEN}✓ Scaffold complete.${RESET}"
1926
+ echo
1927
+ echo -e " ${BOLD}Next steps :${RESET}"
1928
+ echo -e " 1. ${CYAN}mostajs${RESET} → menu r → add replicas + CDC rules (writes replicator-tree.json)"
1929
+ echo -e " 2. ${CYAN}npm run dev:all${RESET} — starts Next + replicator + monitor in parallel"
1930
+ echo -e " or individually : ${CYAN}npm run replicator${RESET} / ${CYAN}npm run monitor${RESET}"
1931
+ echo -e " 3. Open ${CYAN}http://localhost:14499${RESET} for the live dashboard"
1932
+ pause
1933
+ }
1934
+
1828
1935
  action_rep_add_replica() {
1936
+ echo
1937
+ dim " A project is a logical group of replicas (e.g. 'fitzone', 'secuaccess')."
1938
+ dim " Use a SHORT IDENTIFIER here (NOT a database URI — the URI comes later)."
1939
+ echo
1829
1940
  local project name role dialect uri lag
1830
- project=$(ask "Project name" "default")
1831
- name=$(ask "Replica name" "master")
1941
+ project=$(ask "Project name (short id, e.g. 'fitzone')" "fitzone")
1942
+ # Basic validation : reject URIs or paths
1943
+ if [[ "$project" == *"://"* || "$project" == *"/"* ]]; then
1944
+ err " Project name looks like a URI or path. Use a short identifier (e.g. 'fitzone')."
1945
+ pause; return
1946
+ fi
1947
+
1948
+ echo
1949
+ dim " Replica name is a label inside the project (e.g. 'master-oracle', 'slave-pg')."
1950
+ name=$(ask "Replica name (label, e.g. 'master-oracle')" "master")
1951
+ if [[ "$name" == *"://"* ]]; then
1952
+ err " Replica name looks like a URI. Use a short label."
1953
+ pause; return
1954
+ fi
1955
+
1832
1956
  role=$(ask "Role (master|slave)" "master")
1833
- dialect=$(ask "Dialect" "${DB_DIALECT:-postgres}")
1834
- uri=$(ask "URI" "${SGBD_URI:-postgres://user:pass@localhost:5432/db}")
1957
+ dialect=$(ask "Dialect (sqlite|postgres|mysql|mongodb|oracle|mssql|mariadb|cockroachdb|db2|hana|hsqldb|spanner|sybase)" "${DB_DIALECT:-postgres}")
1958
+ uri=$(ask "Connection URI" "${SGBD_URI:-postgres://user:pass@localhost:5432/db}")
1835
1959
  if [[ "$role" == "slave" ]]; then
1836
1960
  lag=$(ask "Lag tolerance (ms)" "5000")
1837
1961
  else
1838
1962
  lag="0"
1839
1963
  fi
1840
1964
  _replicator_run "
1965
+ // Auto-register the project in ProjectManager if it's not already there —
1966
+ // addReplica() requires the project to exist. We use the replica's own
1967
+ // dialect/uri as the project config (it's typically the master).
1968
+ const existing = typeof pm.listProjects === 'function' ? pm.listProjects() : [];
1969
+ const known = Array.isArray(existing) && existing.some(p => (p.name ?? p) === '$project');
1970
+ if (!known) {
1971
+ const method = ['addProject','createProject','registerProject'].find(m => typeof pm[m] === 'function');
1972
+ if (method) {
1973
+ await pm[method]({ name: '$project', dialect: '$dialect', uri: '$uri', schemas: [] });
1974
+ console.log(' ✓ project \\'$project\\' registered in ProjectManager');
1975
+ }
1976
+ }
1841
1977
  await rm.addReplica('$project', {
1842
1978
  name: '$name', role: '$role', dialect: '$dialect', uri: '$uri',
1843
1979
  lagTolerance: $lag,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/orm-cli",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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",