unoverse 0.1.23 → 0.1.25
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/unoverse.mjs +6 -2
- package/lib/create.mjs +1 -1
- package/operator/lib/init.sh +1 -1
- package/operator/lib/logs.sh +3 -1
- package/operator/operator.sh +2 -17
- package/package.json +1 -1
- package/operator/lib/dashboard.sh +0 -85
- package/operator/lib/update.sh +0 -357
package/bin/unoverse.mjs
CHANGED
|
@@ -27,7 +27,11 @@ import { create } from "../lib/create.mjs";
|
|
|
27
27
|
// file), so the code is global and the data is local.
|
|
28
28
|
const VENDORED = join(dirname(fileURLToPath(import.meta.url)), "../operator/operator.sh");
|
|
29
29
|
const IN_REPO = join(dirname(fileURLToPath(import.meta.url)), "../../../scripts/operator.sh");
|
|
30
|
-
|
|
30
|
+
// IN-REPO WINS. The vendored copy is a build artifact, refreshed only by prepack, so
|
|
31
|
+
// preferring it meant every edit to scripts/lib was invisible while developing here:
|
|
32
|
+
// the CLI kept running whatever the last `npm pack` froze. A published package has no
|
|
33
|
+
// ../../../scripts, so there the vendored copy is the only one and still wins.
|
|
34
|
+
const OPERATOR = existsSync(IN_REPO) ? IN_REPO : VENDORED;
|
|
31
35
|
|
|
32
36
|
function findUniverse() {
|
|
33
37
|
for (let dir = process.cwd(); ; ) {
|
|
@@ -56,7 +60,7 @@ const UNIVERSE = findUniverse();
|
|
|
56
60
|
const OPERATOR_COMMANDS = new Set([
|
|
57
61
|
"start", "stop", "check", "logs", "deploy",
|
|
58
62
|
// kept working, not advertised
|
|
59
|
-
"ground", "dev", "build", "publish", "init",
|
|
63
|
+
"ground", "dev", "build", "publish", "init",
|
|
60
64
|
]);
|
|
61
65
|
|
|
62
66
|
const [, , cmd, ...args] = process.argv;
|
package/lib/create.mjs
CHANGED
|
@@ -256,7 +256,7 @@ export async function create(nameArg) {
|
|
|
256
256
|
env: { ...process.env, UNOVERSE_DOCR_TOKEN: token },
|
|
257
257
|
});
|
|
258
258
|
if (r.status !== 0) {
|
|
259
|
-
fail(`setup did not finish. Run 'unoverse
|
|
259
|
+
fail(`setup did not finish. Run 'unoverse start'${name === "." ? "" : ` in ./${name}`} to pick it back up`);
|
|
260
260
|
process.exit(r.status ?? 1);
|
|
261
261
|
}
|
|
262
262
|
console.log(`
|
package/operator/lib/init.sh
CHANGED
package/operator/lib/logs.sh
CHANGED
|
@@ -18,6 +18,8 @@ cmd_logs() {
|
|
|
18
18
|
fi
|
|
19
19
|
info "Tip: ${BOLD}unoverse logs <service>${NC} to stream one service in the terminal"
|
|
20
20
|
echo ""
|
|
21
|
-
|
|
21
|
+
if command -v open &>/dev/null; then open "http://localhost:8080"
|
|
22
|
+
elif command -v xdg-open &>/dev/null; then xdg-open "http://localhost:8080"
|
|
23
|
+
fi
|
|
22
24
|
fi
|
|
23
25
|
}
|
package/operator/operator.sh
CHANGED
|
@@ -37,11 +37,9 @@ source "$GRAVITY_LIB/init.sh"
|
|
|
37
37
|
source "$GRAVITY_LIB/start.sh"
|
|
38
38
|
source "$GRAVITY_LIB/stop.sh"
|
|
39
39
|
source "$GRAVITY_LIB/logs.sh"
|
|
40
|
-
source "$GRAVITY_LIB/update.sh"
|
|
41
40
|
source "$GRAVITY_LIB/doctor.sh"
|
|
42
41
|
source "$GRAVITY_LIB/dev.sh"
|
|
43
42
|
source "$GRAVITY_LIB/check.sh"
|
|
44
|
-
source "$GRAVITY_LIB/dashboard.sh"
|
|
45
43
|
source "$GRAVITY_LIB/help.sh"
|
|
46
44
|
source "$GRAVITY_LIB/db-setup.sh"
|
|
47
45
|
source "$GRAVITY_LIB/db-verify.sh"
|
|
@@ -56,27 +54,15 @@ source "$GRAVITY_LIB/ground.sh"
|
|
|
56
54
|
# (The old "studio mode" gate is gone — 2026-07-28. This CLI has ONE job:
|
|
57
55
|
# operate a universe. Studio is a separate app; authoring happens there.)
|
|
58
56
|
case "${1:-}" in
|
|
59
|
-
init) cmd_init ;;
|
|
57
|
+
init) cmd_init ;; # not advertised: create configures, start resumes
|
|
60
58
|
start) shift; cmd_start "$@" ;;
|
|
61
59
|
stop) cmd_stop ;;
|
|
62
|
-
status) cmd_check ;; # alias — status merged into check 2026-07-28
|
|
63
60
|
logs) cmd_logs "${2:-}" ;;
|
|
64
|
-
update)
|
|
65
|
-
if [ "${2:-}" = "nodes" ]; then
|
|
66
|
-
cmd_update_nodes
|
|
67
|
-
else
|
|
68
|
-
cmd_update
|
|
69
|
-
fi
|
|
70
|
-
;;
|
|
71
61
|
# ONE question, one command. `check` runs the health check, then the schema check,
|
|
72
62
|
# then the deeper environment diagnosis — the three things that used to be `check`,
|
|
73
63
|
# `db-verify` and `doctor`, which nobody could pick between.
|
|
74
64
|
check) cmd_check && cmd_db_verify && cmd_doctor ;;
|
|
75
|
-
doctor|db-verify|db-setup)
|
|
76
|
-
echo " '$1' folded into 'unoverse check' (db-setup runs inside create and deploy)"
|
|
77
|
-
exit 1 ;;
|
|
78
65
|
dev) cmd_dev ;;
|
|
79
|
-
_db-setup) cmd_db_setup ;; # internal: called by create and deploy
|
|
80
66
|
ground) shift; cmd_ground "$@" ;;
|
|
81
67
|
deploy)
|
|
82
68
|
# `deploy marketplace` is PLATFORM-OWNER only: it publishes the marketplace package
|
|
@@ -105,9 +91,8 @@ case "${1:-}" in
|
|
|
105
91
|
fi
|
|
106
92
|
;;
|
|
107
93
|
build) cmd_build "${2:-}" ;;
|
|
108
|
-
open) echo " 'open' folded into 'unoverse where'"; exit 1 ;;
|
|
109
94
|
help|--help|-h) cmd_help ;;
|
|
110
|
-
"")
|
|
95
|
+
"") cmd_help ;;
|
|
111
96
|
*)
|
|
112
97
|
echo "Unknown command: $1"
|
|
113
98
|
echo "Run unoverse help for usage"
|
package/package.json
CHANGED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# unoverse dashboard (default command when no args)
|
|
3
|
-
|
|
4
|
-
cmd_dashboard() {
|
|
5
|
-
echo ""
|
|
6
|
-
echo -e " ${BOLD}${CYAN}⬡ Unoverse Platform${NC} ${DIM}v${GRAVITY_VERSION}${NC}"
|
|
7
|
-
echo -e " ${DIM}─────────────────────────────────${NC}"
|
|
8
|
-
|
|
9
|
-
# Check if .env exists
|
|
10
|
-
if [ ! -f "$ROOT/.env" ]; then
|
|
11
|
-
echo ""
|
|
12
|
-
echo -e " ${YELLOW}${BOLD}First time?${NC} Run ${GREEN}${BOLD}unoverse init${NC} to get started."
|
|
13
|
-
echo ""
|
|
14
|
-
return
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
# Quick status — use -a to see Created containers too
|
|
18
|
-
local ps
|
|
19
|
-
ps=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Name}}\t{{.Status}}" 2>/dev/null) || true
|
|
20
|
-
|
|
21
|
-
if [ -n "$ps" ]; then
|
|
22
|
-
local total running created
|
|
23
|
-
total=$(echo "$ps" | wc -l | tr -d ' ')
|
|
24
|
-
running=$(echo "$ps" | grep -ci "up" || echo "0")
|
|
25
|
-
created=$(echo "$ps" | grep -ci "created" || echo "0")
|
|
26
|
-
|
|
27
|
-
echo ""
|
|
28
|
-
if [ "$running" -eq "$total" ] && [ "$total" -gt 0 ]; then
|
|
29
|
-
echo -e " ${GREEN}●${NC} ${BOLD}Platform running${NC} ${DIM}($running services)${NC}"
|
|
30
|
-
echo ""
|
|
31
|
-
print_access_urls
|
|
32
|
-
elif [ "$created" -gt 0 ] && [ "$running" -eq 0 ]; then
|
|
33
|
-
echo -e " ${RED}●${NC} ${BOLD}$total containers stuck in Created state${NC}"
|
|
34
|
-
echo ""
|
|
35
|
-
echo -e " Containers were created but never started."
|
|
36
|
-
echo -e " Run ${GREEN}${BOLD}unoverse check${NC} to diagnose"
|
|
37
|
-
elif [ "$running" -gt 0 ]; then
|
|
38
|
-
echo -e " ${YELLOW}●${NC} ${BOLD}$running/$total services up${NC}"
|
|
39
|
-
echo ""
|
|
40
|
-
print_access_urls
|
|
41
|
-
else
|
|
42
|
-
echo -e " ${RED}●${NC} ${BOLD}Platform not running${NC} ${DIM}($total containers stopped)${NC}"
|
|
43
|
-
echo ""
|
|
44
|
-
echo -e " Run ${GREEN}${BOLD}unoverse start${NC} to launch"
|
|
45
|
-
fi
|
|
46
|
-
else
|
|
47
|
-
echo ""
|
|
48
|
-
echo -e " ${DIM}●${NC} ${BOLD}Platform stopped${NC}"
|
|
49
|
-
echo ""
|
|
50
|
-
echo -e " Run ${GREEN}${BOLD}unoverse start${NC} to launch"
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
echo ""
|
|
54
|
-
echo -e " ${DIM}Run ${NC}${BOLD}unoverse help${NC}${DIM} for all commands${NC}"
|
|
55
|
-
echo ""
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
# ── unoverse open — browser shortcut (merged from open.sh 2026-07-28) ──
|
|
59
|
-
|
|
60
|
-
cmd_open() {
|
|
61
|
-
local target="${1:-canvas}"
|
|
62
|
-
local url
|
|
63
|
-
|
|
64
|
-
case "$target" in
|
|
65
|
-
canvas) url="http://localhost:3001" ;;
|
|
66
|
-
api) url="http://localhost:4105" ;;
|
|
67
|
-
logs|dozzle) url="http://localhost:8080" ;;
|
|
68
|
-
*)
|
|
69
|
-
fail "Unknown target: $target"
|
|
70
|
-
info "Options: canvas, api, logs"
|
|
71
|
-
return
|
|
72
|
-
;;
|
|
73
|
-
esac
|
|
74
|
-
|
|
75
|
-
ok "Opening $target → ${UNDERLINE}$url${NC}"
|
|
76
|
-
|
|
77
|
-
# Cross-platform open
|
|
78
|
-
if command -v open &>/dev/null; then
|
|
79
|
-
open "$url"
|
|
80
|
-
elif command -v xdg-open &>/dev/null; then
|
|
81
|
-
xdg-open "$url"
|
|
82
|
-
else
|
|
83
|
-
info "Open in your browser: $url"
|
|
84
|
-
fi
|
|
85
|
-
}
|
package/operator/lib/update.sh
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# unoverse update + gravity update nodes
|
|
3
|
-
|
|
4
|
-
cmd_update() {
|
|
5
|
-
echo ""
|
|
6
|
-
echo -e " ${BOLD}Updating Unoverse Platform${NC}"
|
|
7
|
-
echo ""
|
|
8
|
-
timer_start
|
|
9
|
-
|
|
10
|
-
# ── Refuse before doing half the job ───────────────────────────────────────────
|
|
11
|
-
# This used to run against an unconfigured folder with Docker down: compose warned
|
|
12
|
-
# about thirty blank variables, then failed on the daemon, after reporting that the
|
|
13
|
-
# code had updated. Ask first.
|
|
14
|
-
if [ ! -f "$ROOT/.env" ]; then
|
|
15
|
-
fail "This universe is not configured yet"
|
|
16
|
-
info "Run ${BOLD}unoverse init${NC} first"
|
|
17
|
-
exit 1
|
|
18
|
-
fi
|
|
19
|
-
if ! docker info >/dev/null 2>&1; then
|
|
20
|
-
fail "Docker is not running. Update pulls images, so it needs the daemon"
|
|
21
|
-
info "Start Docker Desktop, then run ${BOLD}unoverse update${NC} again"
|
|
22
|
-
exit 1
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
# ── Developer work is SAFE, by design ──────────────────────────────────────────
|
|
26
|
-
# `update` refreshes the PLATFORM (its own tracked files) and NEVER touches a developer's
|
|
27
|
-
# own work. Custom nodes live as UNTRACKED files (apps/unoverse/nodes/<yours>) — `git reset
|
|
28
|
-
# --hard` ignores untracked files, and we no longer `git clean` them (see Step 1). So a
|
|
29
|
-
# developer can create as many nodes as they want and `update` leaves them completely alone —
|
|
30
|
-
# no stashing, no ceremony. The ONLY thing update overwrites is tracked PLATFORM files, so we
|
|
31
|
-
# guard just those (a dev who edited framework code shouldn't lose it silently).
|
|
32
|
-
# UNOVERSE_UPDATE_FORCE=1 overrides even that.
|
|
33
|
-
if [ "${UNOVERSE_UPDATE_FORCE:-}" != "1" ] && [ -n "$(git -C "$ROOT" status --porcelain --untracked-files=no 2>/dev/null)" ]; then
|
|
34
|
-
fail "You have uncommitted edits to PLATFORM files that update would overwrite."
|
|
35
|
-
echo ""
|
|
36
|
-
echo -e " ${DIM}Tracked files with local edits (your own untracked nodes are NOT affected):${NC}"
|
|
37
|
-
git -C "$ROOT" status --short --untracked-files=no | sed 's/^/ /' | head -30
|
|
38
|
-
echo ""
|
|
39
|
-
info "Commit them: git add -A && git commit -m 'wip'"
|
|
40
|
-
info "Or overwrite on purpose: UNOVERSE_UPDATE_FORCE=1 unoverse update"
|
|
41
|
-
echo ""
|
|
42
|
-
exit 1
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# Step 1: Code — pull from customer's fork.
|
|
46
|
-
#
|
|
47
|
-
# NOT EVERY UNIVERSE IS A GIT CLONE. `unoverse create` extracts a tarball, so there is
|
|
48
|
-
# no remote to pull from and never was. That is not a failure: the images carry the
|
|
49
|
-
# platform, and the files here are yours. Skip the step and say which mode you are in,
|
|
50
|
-
# rather than running git commands that all fail and then reporting "Code updated".
|
|
51
|
-
if ! git -C "$ROOT" rev-parse --git-dir >/dev/null 2>&1; then
|
|
52
|
-
info "Not a git checkout, so there is no code to pull. Updating images only"
|
|
53
|
-
UNOVERSE_SKIP_GIT=1
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
if [ "${UNOVERSE_SKIP_GIT:-}" != "1" ]; then
|
|
57
|
-
printf " ${DIM}●${NC} Pulling latest code..."
|
|
58
|
-
local git_ok=true
|
|
59
|
-
local git_log
|
|
60
|
-
git_log=$(mktemp)
|
|
61
|
-
(
|
|
62
|
-
cd "$ROOT"
|
|
63
|
-
# Abort any stuck rebase/merge from a previous failed pull
|
|
64
|
-
git rebase --abort >/dev/null 2>&1 || true
|
|
65
|
-
git merge --abort >/dev/null 2>&1 || true
|
|
66
|
-
# Reset generated files (e.g. marketplace templates from deploy.sh rebuild)
|
|
67
|
-
# This is safe: .env, production.yml, node_modules, package-lock.json are gitignored
|
|
68
|
-
git reset HEAD -- . >/dev/null 2>&1 || true
|
|
69
|
-
git checkout -- . >/dev/null 2>&1 || true
|
|
70
|
-
# Fetch latest from remote
|
|
71
|
-
git fetch origin >/dev/null 2>&1 || true
|
|
72
|
-
# Reset the PLATFORM's own tracked files to match remote exactly. This ignores UNTRACKED
|
|
73
|
-
# files, so a developer's custom nodes (apps/unoverse/nodes/<yours>) are left alone.
|
|
74
|
-
git reset --hard origin/$(git rev-parse --abbrev-ref HEAD) >/dev/null 2>&1 || true
|
|
75
|
-
# NB: intentionally NO `git clean -fd` here. It DELETES untracked files, and a developer's
|
|
76
|
-
# nodes are untracked — cleaning them was the "update deleted my node" bug. Marketplace
|
|
77
|
-
# packages install into a gitignored dir, so clean never removed those anyway.
|
|
78
|
-
) || git_ok=false
|
|
79
|
-
printf "\r\033[2K"
|
|
80
|
-
if $git_ok; then
|
|
81
|
-
ok "Code updated"
|
|
82
|
-
rm -f "$git_log"
|
|
83
|
-
else
|
|
84
|
-
warn "Code update failed. Attempting recovery"
|
|
85
|
-
local git_err
|
|
86
|
-
git_err=$(cat "$git_log" 2>/dev/null | head -5)
|
|
87
|
-
if [ -n "$git_err" ]; then
|
|
88
|
-
echo -e " ${DIM}Reason: $git_err${NC}"
|
|
89
|
-
fi
|
|
90
|
-
rm -f "$git_log"
|
|
91
|
-
|
|
92
|
-
# Recovery: download latest CLI script from GitHub, then force-sync
|
|
93
|
-
printf " ${DIM}●${NC} Downloading latest update script..."
|
|
94
|
-
local temp_update=$(mktemp)
|
|
95
|
-
if curl -fsSL "https://raw.githubusercontent.com/unoverse-platform/starter/main/scripts/lib/update.sh" -o "$temp_update" 2>/dev/null; then
|
|
96
|
-
cp "$temp_update" "$GRAVITY_LIB/update.sh"
|
|
97
|
-
rm -f "$temp_update"
|
|
98
|
-
printf "\r\033[2K"
|
|
99
|
-
ok "Update script refreshed"
|
|
100
|
-
|
|
101
|
-
# Now force-sync with the new script
|
|
102
|
-
printf " ${DIM}●${NC} Forcing sync with remote..."
|
|
103
|
-
(
|
|
104
|
-
cd "$ROOT"
|
|
105
|
-
git fetch origin >/dev/null 2>&1 || true
|
|
106
|
-
local branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
|
|
107
|
-
git reset --hard origin/$branch >/dev/null 2>&1 || true
|
|
108
|
-
# No `git clean -fd` — never delete a developer's untracked nodes (see Step 1).
|
|
109
|
-
)
|
|
110
|
-
printf "\r\033[2K"
|
|
111
|
-
ok "Forced sync completed"
|
|
112
|
-
else
|
|
113
|
-
printf "\r\033[2K"
|
|
114
|
-
fail "Recovery failed. Run: cd $ROOT && git reset --hard origin/main"
|
|
115
|
-
exit 1
|
|
116
|
-
fi
|
|
117
|
-
fi
|
|
118
|
-
fi # UNOVERSE_SKIP_GIT
|
|
119
|
-
|
|
120
|
-
# Step 2: Images — login to registry if DOCR_TOKEN is set
|
|
121
|
-
local docr_token
|
|
122
|
-
docr_token=$(grep "^DOCR_TOKEN=" "$ROOT/.env" 2>/dev/null | cut -d'=' -f2-)
|
|
123
|
-
if [ -n "$docr_token" ]; then
|
|
124
|
-
echo "$docr_token" | docker login registry.digitalocean.com -u "$docr_token" --password-stdin >/dev/null 2>&1 || true
|
|
125
|
-
fi
|
|
126
|
-
|
|
127
|
-
echo ""
|
|
128
|
-
info "Pulling images..."
|
|
129
|
-
echo ""
|
|
130
|
-
|
|
131
|
-
local pull_start=$(date +%s)
|
|
132
|
-
if docker compose -f "$ROOT/docker-compose.yml" pull; then
|
|
133
|
-
local total_elapsed=$(( $(date +%s) - pull_start ))
|
|
134
|
-
echo ""
|
|
135
|
-
ok "Images pulled ${DIM}(${total_elapsed}s)${NC}"
|
|
136
|
-
else
|
|
137
|
-
echo ""
|
|
138
|
-
fail "Image pull failed. Check network/registry and run ${BOLD}unoverse update${NC} again"
|
|
139
|
-
exit 1
|
|
140
|
-
fi
|
|
141
|
-
|
|
142
|
-
# Step 3: Build packages (requires Node.js — installed by install.yml)
|
|
143
|
-
local build_log
|
|
144
|
-
build_log=$(mktemp)
|
|
145
|
-
|
|
146
|
-
# Only run npm install if the dependency inputs changed or node_modules is
|
|
147
|
-
# missing. Hash every package.json alongside the lockfile: a pulled range
|
|
148
|
-
# bump (e.g. plugin-base ^1.2.0 → ^1.2.1) changes no lockfile — the starter
|
|
149
|
-
# doesn't ship one — so hashing only package-lock.json would skip the install
|
|
150
|
-
# that the bump exists to force.
|
|
151
|
-
dep_inputs_hash() {
|
|
152
|
-
cat "$ROOT/package-lock.json" "$ROOT"/package.json "$ROOT"/apps/unoverse/nodes/*/package.json 2>/dev/null \
|
|
153
|
-
| { md5 -q 2>/dev/null || md5sum 2>/dev/null | cut -d' ' -f1; }
|
|
154
|
-
}
|
|
155
|
-
local need_install=false
|
|
156
|
-
if [ ! -d "$ROOT/node_modules" ]; then
|
|
157
|
-
need_install=true
|
|
158
|
-
elif [ -f "$ROOT/.package-lock.hash" ]; then
|
|
159
|
-
local old_hash=$(cat "$ROOT/.package-lock.hash" 2>/dev/null || echo "")
|
|
160
|
-
local new_hash=$(dep_inputs_hash || echo "new")
|
|
161
|
-
[ "$old_hash" != "$new_hash" ] && need_install=true
|
|
162
|
-
else
|
|
163
|
-
need_install=true
|
|
164
|
-
fi
|
|
165
|
-
|
|
166
|
-
(
|
|
167
|
-
cd "$ROOT"
|
|
168
|
-
if $need_install; then
|
|
169
|
-
npm install --silent >/dev/null 2>&1 || true
|
|
170
|
-
# Save hash for next run
|
|
171
|
-
dep_inputs_hash > "$ROOT/.package-lock.hash" || true
|
|
172
|
-
fi
|
|
173
|
-
# Turbo handles build dependencies, no need to build plugin-base separately
|
|
174
|
-
npm run build --workspaces --if-present >> "$build_log" 2>&1 || true
|
|
175
|
-
# NOTE: component nodes are DEFINITION-BACKED (no generation): the universal
|
|
176
|
-
# node package (nodes/components, shipped pre-built) synthesizes one node per
|
|
177
|
-
# rx/components/* definition at boot. Component changes need only a restart —
|
|
178
|
-
# a restart does that (start/dev also rebuild the package if dist is stale).
|
|
179
|
-
) &
|
|
180
|
-
local build_pid=$!
|
|
181
|
-
local build_start=$(date +%s)
|
|
182
|
-
while kill -0 "$build_pid" 2>/dev/null || false; do
|
|
183
|
-
local c="${spin:si%${#spin}:1}"
|
|
184
|
-
si=$((si + 1))
|
|
185
|
-
local elapsed=$(( $(date +%s) - build_start ))
|
|
186
|
-
printf "\r ${CYAN}%s${NC} Building packages... ${DIM}(%ds)${NC} " "$c" "$elapsed"
|
|
187
|
-
sleep 0.1
|
|
188
|
-
done
|
|
189
|
-
local build_exit=0
|
|
190
|
-
wait "$build_pid" 2>/dev/null || build_exit=$?
|
|
191
|
-
printf "\r\033[2K"
|
|
192
|
-
if [ $build_exit -eq 0 ]; then
|
|
193
|
-
ok "Packages built"
|
|
194
|
-
else
|
|
195
|
-
warn "Build completed with warnings. Check output:"
|
|
196
|
-
cat "$build_log" | tail -20 | sed 's/^/ /'
|
|
197
|
-
fi
|
|
198
|
-
rm -f "$build_log"
|
|
199
|
-
|
|
200
|
-
# Step 4: Restart — with spinner
|
|
201
|
-
docker compose -f "$ROOT/docker-compose.yml" --env-file "$ROOT/.env" up -d --quiet-pull >/dev/null 2>&1 &
|
|
202
|
-
local restart_pid=$!
|
|
203
|
-
local restart_start=$(date +%s)
|
|
204
|
-
while kill -0 "$restart_pid" 2>/dev/null || false; do
|
|
205
|
-
local c="${spin:si%${#spin}:1}"
|
|
206
|
-
si=$((si + 1))
|
|
207
|
-
local elapsed=$(( $(date +%s) - restart_start ))
|
|
208
|
-
printf "\r ${CYAN}%s${NC} Restarting services... ${DIM}(%ds)${NC} " "$c" "$elapsed"
|
|
209
|
-
sleep 0.1
|
|
210
|
-
done
|
|
211
|
-
wait "$restart_pid" 2>/dev/null || true
|
|
212
|
-
printf "\r\033[2K"
|
|
213
|
-
|
|
214
|
-
# Verify services actually started (not stuck in Created)
|
|
215
|
-
sleep 2
|
|
216
|
-
local up_count=0 created_count=0 total_count=0
|
|
217
|
-
# `grep -c` already PRINTS "0" (and exits 1) on no match, so `|| echo "0"` printed a
|
|
218
|
-
# SECOND "0" → the var became "0\n0", which `[ -eq ]` rejects ("integer expression
|
|
219
|
-
# expected"). Use `|| true` to swallow grep's exit code without emitting a stray line.
|
|
220
|
-
total_count=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Name}}" 2>/dev/null | grep -c . || true)
|
|
221
|
-
up_count=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Status}}" 2>/dev/null | grep -ci "up\|running" || true)
|
|
222
|
-
created_count=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Status}}" 2>/dev/null | grep -ci "created" || true)
|
|
223
|
-
|
|
224
|
-
if [ "${up_count:-0}" -eq "${total_count:-0}" ] && [ "${total_count:-0}" -gt 0 ]; then
|
|
225
|
-
ok "All $up_count services running"
|
|
226
|
-
elif [ "${created_count:-0}" -gt 0 ]; then
|
|
227
|
-
warn "$up_count/$total_count services running: $created_count stuck in Created state"
|
|
228
|
-
info "Run ${BOLD}unoverse check${NC} to diagnose"
|
|
229
|
-
elif [ "${up_count:-0}" -gt 0 ]; then
|
|
230
|
-
warn "$up_count/$total_count services running"
|
|
231
|
-
info "Run ${BOLD}unoverse status${NC} to check"
|
|
232
|
-
else
|
|
233
|
-
fail "No services running after restart"
|
|
234
|
-
info "Run ${BOLD}unoverse check${NC} to diagnose"
|
|
235
|
-
fi
|
|
236
|
-
|
|
237
|
-
# Summary. Re-source the SHARED box helper first: `update` pulled fresh scripts a
|
|
238
|
-
# moment ago, but the copy loaded at startup is stale — reload so the summary reflects
|
|
239
|
-
# what we just pulled (fixes the "branded box / Studio line shows one run late" bug).
|
|
240
|
-
source "$GRAVITY_LIB/common.sh" 2>/dev/null || true
|
|
241
|
-
echo ""
|
|
242
|
-
echo -e " ${GREEN}${BOLD}Done${NC} ${DIM}in $(timer_elapsed)${NC}"
|
|
243
|
-
echo ""
|
|
244
|
-
print_access_urls
|
|
245
|
-
echo ""
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
cmd_update_nodes() {
|
|
249
|
-
echo ""
|
|
250
|
-
echo -e " ${BOLD}Updating Nodes${NC}"
|
|
251
|
-
echo ""
|
|
252
|
-
timer_start
|
|
253
|
-
|
|
254
|
-
# Step 1: Dependencies
|
|
255
|
-
printf " ${DIM}●${NC} Installing dependencies..."
|
|
256
|
-
(cd "$ROOT" && npm install --silent >/dev/null 2>&1) || true
|
|
257
|
-
printf "\r\033[2K"
|
|
258
|
-
ok "Dependencies installed"
|
|
259
|
-
|
|
260
|
-
# Step 2: Build
|
|
261
|
-
printf " ${DIM}●${NC} Building packages..."
|
|
262
|
-
(cd "$ROOT" && npm run build -w @unoverse-platform/plugin-base >/dev/null 2>&1) || true
|
|
263
|
-
local build_output
|
|
264
|
-
build_output=$(cd "$ROOT" && npm run build --workspaces --if-present 2>&1) || true
|
|
265
|
-
local pkg_count
|
|
266
|
-
pkg_count=$(echo "$build_output" | grep -c '> .* build' || echo "0")
|
|
267
|
-
printf "\r\033[2K"
|
|
268
|
-
ok "${pkg_count} packages built"
|
|
269
|
-
|
|
270
|
-
# Step 3 (retired): component nodes are DEFINITION-BACKED — no generation step.
|
|
271
|
-
# They synthesize from rx/components at boot; the universal package is built
|
|
272
|
-
# in-container on start/dev (and by `unoverse deploy packages` on the server).
|
|
273
|
-
|
|
274
|
-
# Step 4: Restart
|
|
275
|
-
printf " ${DIM}●${NC} Restarting unoverse..."
|
|
276
|
-
docker compose -f "$ROOT/docker-compose.yml" restart unoverse >/dev/null 2>&1 || true
|
|
277
|
-
printf "\r\033[2K"
|
|
278
|
-
ok "Node-service restarted"
|
|
279
|
-
|
|
280
|
-
# Summary
|
|
281
|
-
echo ""
|
|
282
|
-
echo -e " ${GREEN}${BOLD}Done${NC} ${DIM}in $(timer_elapsed)${NC}"
|
|
283
|
-
echo ""
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
# ── cmd_pull — INTERNAL helper (init + update use it). Not a user command since 2026-07-28. ──
|
|
287
|
-
|
|
288
|
-
cmd_pull() {
|
|
289
|
-
echo ""
|
|
290
|
-
read -r -p " Pull platform images now? (~1.2GB first time) [Y/n] " REPLY
|
|
291
|
-
echo ""
|
|
292
|
-
if [[ "$REPLY" =~ ^[Nn]$ ]]; then
|
|
293
|
-
info "Skipped. Run ${BOLD}unoverse start${NC} later."
|
|
294
|
-
return
|
|
295
|
-
fi
|
|
296
|
-
|
|
297
|
-
local IMAGES=(
|
|
298
|
-
"registry.digitalocean.com/gravity-repo/canvas:latest"
|
|
299
|
-
"registry.digitalocean.com/gravity-repo/umap:latest"
|
|
300
|
-
"registry.digitalocean.com/gravity-repo/unoverse:latest"
|
|
301
|
-
"registry.digitalocean.com/gravity-repo/memory:latest"
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
local total=${#IMAGES[@]}
|
|
305
|
-
local count=0
|
|
306
|
-
local failed=0
|
|
307
|
-
|
|
308
|
-
local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
|
309
|
-
|
|
310
|
-
echo ""
|
|
311
|
-
for img in "${IMAGES[@]}"; do
|
|
312
|
-
count=$((count + 1))
|
|
313
|
-
local short="${img##*/}" # gravity-server:latest
|
|
314
|
-
short="${short%%:*}" # gravity-server
|
|
315
|
-
|
|
316
|
-
# Start pull in background
|
|
317
|
-
docker pull "$img" &>/dev/null &
|
|
318
|
-
local pid=$!
|
|
319
|
-
|
|
320
|
-
# Animate spinner while pulling
|
|
321
|
-
local i=0
|
|
322
|
-
while kill -0 "$pid" 2>/dev/null || false; do
|
|
323
|
-
local c="${spin:i%${#spin}:1}"
|
|
324
|
-
i=$((i + 1))
|
|
325
|
-
printf "\r ${DIM}[%d/%d]${NC} ${CYAN}%s${NC} Pulling ${BOLD}%s${NC} " "$count" "$total" "$c" "$short"
|
|
326
|
-
sleep 0.1
|
|
327
|
-
done
|
|
328
|
-
|
|
329
|
-
# Check result
|
|
330
|
-
local pull_exit=0
|
|
331
|
-
wait "$pid" 2>/dev/null || pull_exit=$?
|
|
332
|
-
if [ $pull_exit -eq 0 ]; then
|
|
333
|
-
printf "\r ${DIM}[%d/%d]${NC} ${GREEN}✓${NC} Pulling ${BOLD}%s${NC} \n" "$count" "$total" "$short"
|
|
334
|
-
else
|
|
335
|
-
printf "\r ${DIM}[%d/%d]${NC} ${RED}✗${NC} Pulling ${BOLD}%s${NC} \n" "$count" "$total" "$short"
|
|
336
|
-
failed=$((failed + 1))
|
|
337
|
-
fi
|
|
338
|
-
done
|
|
339
|
-
echo ""
|
|
340
|
-
|
|
341
|
-
# Count how many gravity images we have now
|
|
342
|
-
local pulled
|
|
343
|
-
pulled=$(docker images -a --format "{{.Repository}}" | grep -c "gravity-repo" || true)
|
|
344
|
-
pulled=$(echo "$pulled" | tr -d '[:space:]')
|
|
345
|
-
pulled=${pulled:-0}
|
|
346
|
-
|
|
347
|
-
echo ""
|
|
348
|
-
if [ "$pulled" -ge 5 ]; then
|
|
349
|
-
ok "All $pulled images pulled"
|
|
350
|
-
elif [ "$pulled" -gt 0 ]; then
|
|
351
|
-
warn "$pulled images pulled (some may need retry)"
|
|
352
|
-
info "Run ${BOLD}unoverse pull${NC} to retry"
|
|
353
|
-
else
|
|
354
|
-
fail "No images pulled. Check your DOCR token and network"
|
|
355
|
-
exit 1
|
|
356
|
-
fi
|
|
357
|
-
}
|