@mostajs/orm-cli 0.5.4 → 0.5.6
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 +136 -59
- package/package.json +1 -1
package/bin/mostajs.sh
CHANGED
|
@@ -1938,7 +1938,8 @@ action_rep_add_replica() {
|
|
|
1938
1938
|
dim " Use a SHORT IDENTIFIER here (NOT a database URI — the URI comes later)."
|
|
1939
1939
|
echo
|
|
1940
1940
|
local project name role dialect uri lag
|
|
1941
|
-
project
|
|
1941
|
+
# Use the smart project picker (lists known projects, suggests first)
|
|
1942
|
+
project=$(_pick_project)
|
|
1942
1943
|
# Basic validation : reject URIs or paths
|
|
1943
1944
|
if [[ "$project" == *"://"* || "$project" == *"/"* ]]; then
|
|
1944
1945
|
err " Project name looks like a URI or path. Use a short identifier (e.g. 'fitzone')."
|
|
@@ -1961,48 +1962,113 @@ action_rep_add_replica() {
|
|
|
1961
1962
|
else
|
|
1962
1963
|
lag="0"
|
|
1963
1964
|
fi
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
}
|
|
1977
|
-
await rm.addReplica('$project', {
|
|
1978
|
-
name: '$name', role: '$role', dialect: '$dialect', uri: '$uri',
|
|
1979
|
-
lagTolerance: $lag,
|
|
1980
|
-
});
|
|
1981
|
-
await save();
|
|
1965
|
+
# Direct tree-JSON patch — preserves URI verbatim (no masking), no DB
|
|
1966
|
+
# connection needed. Validation of the URI happens later when replicator
|
|
1967
|
+
# service or sync action actually touches the DB.
|
|
1968
|
+
_tree_patch "
|
|
1969
|
+
tree.replicas['$project'] = tree.replicas['$project'] || {};
|
|
1970
|
+
tree.replicas['$project']['$name'] = {
|
|
1971
|
+
role: '$role',
|
|
1972
|
+
dialect: '$dialect',
|
|
1973
|
+
uri: '$uri',
|
|
1974
|
+
pool: { min: 2, max: 20 },
|
|
1975
|
+
...( '$role' === 'slave' ? { lagTolerance: $lag } : {} ),
|
|
1976
|
+
};
|
|
1982
1977
|
console.log(' ✓ replica added : $name (' + '$role' + ') on project $project');
|
|
1983
1978
|
"
|
|
1984
1979
|
pause
|
|
1985
1980
|
}
|
|
1986
1981
|
|
|
1982
|
+
# ----------------------------------------------------------------
|
|
1983
|
+
# Tree-file direct manipulation helpers
|
|
1984
|
+
# ----------------------------------------------------------------
|
|
1985
|
+
# The replicator's saveToFile() masks credentials (oracle://u:***@host)
|
|
1986
|
+
# which makes loadFromFile() unable to reconnect. For add/list/remove we
|
|
1987
|
+
# bypass the lib and patch the tree JSON directly, preserving the
|
|
1988
|
+
# original URI verbatim. The lib is still used for sync() / promote()
|
|
1989
|
+
# where it has logic beyond state tracking.
|
|
1990
|
+
|
|
1991
|
+
_tree_file() { _replicator_tree_file; }
|
|
1992
|
+
|
|
1993
|
+
_tree_read_json() {
|
|
1994
|
+
# Dump the tree JSON on stdout, empty-tree fallback if file missing.
|
|
1995
|
+
local tree_file
|
|
1996
|
+
tree_file=$(_tree_file)
|
|
1997
|
+
node -e "
|
|
1998
|
+
const fs = require('fs');
|
|
1999
|
+
const def = { replicas: {}, rules: {}, routing: {} };
|
|
2000
|
+
try { console.log(JSON.stringify(Object.assign(def, JSON.parse(fs.readFileSync('$tree_file','utf8'))))); }
|
|
2001
|
+
catch { console.log(JSON.stringify(def)); }
|
|
2002
|
+
"
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
# List known projects from the replicator-tree.json — if any — and propose
|
|
2006
|
+
# the first one as default. Echoes the chosen project name to stdout.
|
|
2007
|
+
_pick_project() {
|
|
2008
|
+
local projects=()
|
|
2009
|
+
while IFS= read -r p; do projects+=("$p"); done < <(
|
|
2010
|
+
_tree_read_json | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{for(const k of Object.keys(JSON.parse(d).replicas||{}))console.log(k)})"
|
|
2011
|
+
)
|
|
2012
|
+
local default_project="fitzone"
|
|
2013
|
+
if [[ ${#projects[@]} -gt 0 ]]; then
|
|
2014
|
+
default_project="${projects[0]}"
|
|
2015
|
+
if [[ ${#projects[@]} -gt 1 ]]; then
|
|
2016
|
+
echo >&2
|
|
2017
|
+
dim " Known projects : ${projects[*]}" >&2
|
|
2018
|
+
fi
|
|
2019
|
+
fi
|
|
2020
|
+
local picked
|
|
2021
|
+
picked=$(ask "Project name" "$default_project")
|
|
2022
|
+
echo "$picked"
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
# Patch the tree JSON : exec a Node snippet with `tree` in scope.
|
|
2026
|
+
# After the snippet runs, the mutated tree is written back.
|
|
2027
|
+
_tree_patch() {
|
|
2028
|
+
local snippet="$1"
|
|
2029
|
+
local tree_file
|
|
2030
|
+
tree_file=$(_tree_file)
|
|
2031
|
+
mkdir -p "$(dirname "$tree_file")"
|
|
2032
|
+
TREE_FILE="$tree_file" node --input-type=module -e "
|
|
2033
|
+
const { readFileSync, writeFileSync, existsSync } = await import('fs');
|
|
2034
|
+
const { mkdirSync } = await import('fs');
|
|
2035
|
+
const def = { replicas: {}, rules: {}, routing: {} };
|
|
2036
|
+
let tree = def;
|
|
2037
|
+
if (existsSync(process.env.TREE_FILE)) {
|
|
2038
|
+
try { tree = Object.assign(def, JSON.parse(readFileSync(process.env.TREE_FILE, 'utf8'))); }
|
|
2039
|
+
catch { /* start fresh */ }
|
|
2040
|
+
}
|
|
2041
|
+
try { $snippet }
|
|
2042
|
+
catch (e) { console.error(' ✗ ' + e.message); process.exit(1); }
|
|
2043
|
+
writeFileSync(process.env.TREE_FILE, JSON.stringify(tree, null, 2) + '\n');
|
|
2044
|
+
"
|
|
2045
|
+
}
|
|
2046
|
+
|
|
1987
2047
|
action_rep_list_replicas() {
|
|
1988
2048
|
local project
|
|
1989
|
-
project=$(
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2049
|
+
project=$(_pick_project)
|
|
2050
|
+
echo
|
|
2051
|
+
_tree_read_json | PROJECT="$project" node -e "
|
|
2052
|
+
let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{
|
|
2053
|
+
const tree = JSON.parse(d);
|
|
2054
|
+
const reps = tree.replicas[process.env.PROJECT] || {};
|
|
2055
|
+
const entries = Object.entries(reps);
|
|
2056
|
+
if (entries.length === 0) {
|
|
2057
|
+
console.log(' (no replicas registered for project ' + process.env.PROJECT + ')');
|
|
2058
|
+
return;
|
|
1997
2059
|
}
|
|
1998
|
-
|
|
2060
|
+
for (const [name, cfg] of entries) {
|
|
2061
|
+
const star = cfg.role === 'master' ? '\x1b[36m★\x1b[0m' : '•';
|
|
2062
|
+
console.log(' ' + star + ' ' + name + ' [' + cfg.role + '] ' + cfg.dialect + ' ' + (cfg.uri ?? '').replace(/:[^:@]+@/, ':***@'));
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
1999
2065
|
"
|
|
2000
2066
|
pause
|
|
2001
2067
|
}
|
|
2002
2068
|
|
|
2003
2069
|
action_rep_promote() {
|
|
2004
2070
|
local project name
|
|
2005
|
-
project=$(
|
|
2071
|
+
project=$(_pick_project)
|
|
2006
2072
|
name=$(ask "Slave to promote" "slave-1")
|
|
2007
2073
|
if ! confirm "Promote '$name' to master on project '$project'?"; then return; fi
|
|
2008
2074
|
_replicator_run "
|
|
@@ -2015,24 +2081,27 @@ action_rep_promote() {
|
|
|
2015
2081
|
|
|
2016
2082
|
action_rep_remove_replica() {
|
|
2017
2083
|
local project name
|
|
2018
|
-
project=$(
|
|
2084
|
+
project=$(_pick_project)
|
|
2019
2085
|
name=$(ask "Replica name" "slave-1")
|
|
2020
2086
|
if ! confirm "Remove replica '$name' from project '$project'?"; then return; fi
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2087
|
+
_tree_patch "
|
|
2088
|
+
if (!tree.replicas['$project'] || !tree.replicas['$project']['$name']) {
|
|
2089
|
+
console.log(' • not found : $project/$name');
|
|
2090
|
+
} else {
|
|
2091
|
+
delete tree.replicas['$project']['$name'];
|
|
2092
|
+
if (Object.keys(tree.replicas['$project']).length === 0) delete tree.replicas['$project'];
|
|
2093
|
+
console.log(' ✓ removed : $project/$name');
|
|
2094
|
+
}
|
|
2025
2095
|
"
|
|
2026
2096
|
pause
|
|
2027
2097
|
}
|
|
2028
2098
|
|
|
2029
2099
|
action_rep_set_routing() {
|
|
2030
2100
|
local project strategy
|
|
2031
|
-
project=$(
|
|
2101
|
+
project=$(_pick_project)
|
|
2032
2102
|
strategy=$(ask "Strategy (round-robin | least-lag | random)" "least-lag")
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
await save();
|
|
2103
|
+
_tree_patch "
|
|
2104
|
+
tree.routing['$project'] = '$strategy';
|
|
2036
2105
|
console.log(' ✓ read routing on $project = $strategy');
|
|
2037
2106
|
"
|
|
2038
2107
|
pause
|
|
@@ -2040,36 +2109,41 @@ action_rep_set_routing() {
|
|
|
2040
2109
|
|
|
2041
2110
|
action_rep_add_rule() {
|
|
2042
2111
|
local name source target mode colls conflict
|
|
2043
|
-
|
|
2044
|
-
source=$(
|
|
2045
|
-
|
|
2112
|
+
# Pick source/target from known projects
|
|
2113
|
+
source=$(_pick_project)
|
|
2114
|
+
local src_default="$source"
|
|
2115
|
+
name=$(ask "Rule name (short, e.g. 'pg-to-mongo')" "cdc-${src_default}")
|
|
2116
|
+
target=$(ask "Target project" "$src_default")
|
|
2046
2117
|
mode=$(ask "Mode (snapshot | cdc | bidirectional)" "cdc")
|
|
2047
2118
|
colls=$(ask "Collections (comma-separated)" "users,clients")
|
|
2048
2119
|
conflict=$(ask "Conflict resolution (source-wins | target-wins | timestamp)" "source-wins")
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2120
|
+
_tree_patch "
|
|
2121
|
+
tree.rules['$name'] = {
|
|
2122
|
+
source: '$source', target: '$target', mode: '$mode',
|
|
2052
2123
|
collections: '$colls'.split(',').map(s => s.trim()).filter(Boolean),
|
|
2053
2124
|
conflictResolution: '$conflict',
|
|
2054
2125
|
enabled: true,
|
|
2055
|
-
}
|
|
2056
|
-
await save();
|
|
2126
|
+
};
|
|
2057
2127
|
console.log(' ✓ rule added : $name ($source → $target, mode=$mode)');
|
|
2058
2128
|
"
|
|
2059
2129
|
pause
|
|
2060
2130
|
}
|
|
2061
2131
|
|
|
2062
2132
|
action_rep_list_rules() {
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2133
|
+
echo
|
|
2134
|
+
_tree_read_json | node -e "
|
|
2135
|
+
let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{
|
|
2136
|
+
const tree = JSON.parse(d);
|
|
2137
|
+
const rules = Object.entries(tree.rules || {});
|
|
2138
|
+
if (rules.length === 0) {
|
|
2139
|
+
console.log(' (no CDC rules registered)');
|
|
2140
|
+
return;
|
|
2071
2141
|
}
|
|
2072
|
-
|
|
2142
|
+
for (const [name, r] of rules) {
|
|
2143
|
+
const flag = r.enabled ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
2144
|
+
console.log(' ' + flag + ' ' + name + ' ' + r.source + ' → ' + r.target + ' [' + r.mode + '] ' + (r.collections||[]).join(','));
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2073
2147
|
"
|
|
2074
2148
|
pause
|
|
2075
2149
|
}
|
|
@@ -2093,10 +2167,13 @@ action_rep_remove_rule() {
|
|
|
2093
2167
|
local name
|
|
2094
2168
|
name=$(ask "Rule name" "pg-to-mongo")
|
|
2095
2169
|
if ! confirm "Remove CDC rule '$name'?"; then return; fi
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2170
|
+
_tree_patch "
|
|
2171
|
+
if (!tree.rules['$name']) {
|
|
2172
|
+
console.log(' • not found : $name');
|
|
2173
|
+
} else {
|
|
2174
|
+
delete tree.rules['$name'];
|
|
2175
|
+
console.log(' ✓ removed : $name');
|
|
2176
|
+
}
|
|
2100
2177
|
"
|
|
2101
2178
|
pause
|
|
2102
2179
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/orm-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
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",
|