ndomo 0.1.0 → 0.2.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.
- package/.env.example +4 -0
- package/README.es.md +29 -23
- package/README.md +64 -24
- package/bun.lock +447 -0
- package/docs/configuration.md +4 -4
- package/docs/installation.md +53 -34
- package/docs/installer.md +164 -0
- package/docs/integrations.md +1 -1
- package/docs/web-ui.md +124 -0
- package/package.json +43 -4
- package/scripts/install.sh +28 -0
- package/scripts/smoke-install.sh +47 -0
- package/scripts/smoke-web.sh +335 -0
- package/src/cli/__tests__/install.test.ts +733 -0
- package/src/cli/index.ts +8 -0
- package/src/cli/install.ts +1292 -0
- package/src/config/__tests__/schema.test.ts +223 -0
- package/src/config/schema.ts +129 -16
- package/src/http/__tests__/auth.test.ts +10 -10
- package/src/http/__tests__/spa.test.ts +296 -0
- package/src/http/auth.ts +8 -1
- package/src/http/server.ts +71 -2
- package/.bun-version +0 -1
- package/.dockerignore +0 -79
- package/.editorconfig +0 -18
- package/.github/CODEOWNERS +0 -8
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -62
- package/.github/ISSUE_TEMPLATE/config.yml +0 -2
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -34
- package/.github/dependabot.yml +0 -36
- package/.github/pull_request_template.md +0 -24
- package/.github/release.yml +0 -30
- package/.github/workflows/gitleaks.yml +0 -28
- package/.github/workflows/release-please.yml +0 -27
- package/.github/workflows/smoke.yml +0 -29
- package/.husky/commit-msg +0 -1
- package/CHANGELOG.md +0 -114
- package/Dockerfile +0 -32
- package/bin/ndomo-analyses.ts +0 -4
- package/bin/ndomo-status.ts +0 -4
- package/biome.json +0 -57
- package/commitlint.config.js +0 -3
- package/opencode.json +0 -5
- package/release-please-config.json +0 -11
- package/scripts/dev-bust-cache.sh +0 -164
- package/scripts/smoke-e2e.ts +0 -704
- package/scripts/smoke-hot.ts +0 -417
- package/scripts/smoke-v4.ts +0 -256
- package/scripts/smoke-v5.ts +0 -397
- package/scripts/uninstall.sh +0 -224
- package/src/index.ts +0 -37
- package/src/lib.ts +0 -65
- package/src/mem/scoped.ts +0 -65
- package/src/orchestrator/background.test.ts +0 -268
- package/src/orchestrator/background.ts +0 -293
- package/src/orchestrator/memory-hook.ts +0 -182
- package/src/orchestrator/reconciler.ts +0 -123
- package/src/orchestrator/scheduler.test.ts +0 -300
- package/src/orchestrator/scheduler.ts +0 -243
- package/src/plugin.test.ts +0 -2574
- package/src/plugin.ts +0 -1690
- package/src/worktrees/manager.ts +0 -236
- package/src/worktrees/state.ts +0 -87
- package/tests/integration/ranger-flow.test.ts +0 -257
- package/tsconfig.json +0 -31
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ndomo installer smoke test — verifies TS installer works end-to-end in a
|
|
3
|
+
# fresh tmp directory. Idempotent: cleans up tmp on exit (success or failure).
|
|
4
|
+
# Exit 0 if all smoke checks pass, exit 1 on any failure.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# ── Colors ────────────────────────────────────────────────────────────────────
|
|
8
|
+
readonly GREEN='\033[0;32m'
|
|
9
|
+
readonly RED='\033[0;31m'
|
|
10
|
+
readonly YELLOW='\033[0;33m'
|
|
11
|
+
readonly BLUE='\033[0;34m'
|
|
12
|
+
readonly NC='\033[0m'
|
|
13
|
+
|
|
14
|
+
# ── Paths ─────────────────────────────────────────────────────────────────────
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
17
|
+
WORKTREE_ROOT="$PROJECT_ROOT"
|
|
18
|
+
|
|
19
|
+
# ── Isolated tmp dir + cleanup trap ───────────────────────────────────────────
|
|
20
|
+
TMPDIR="$(mktemp -d -t ndomo-smoke-install-XXXXXX)"
|
|
21
|
+
export XDG_CONFIG_HOME="$TMPDIR/config"
|
|
22
|
+
|
|
23
|
+
cleanup() {
|
|
24
|
+
local exit_code=$?
|
|
25
|
+
rm -rf "$TMPDIR"
|
|
26
|
+
if [[ $exit_code -eq 0 ]]; then
|
|
27
|
+
echo -e "${GREEN}[smoke-install]${NC} tmp cleaned: $TMPDIR"
|
|
28
|
+
else
|
|
29
|
+
echo -e "${RED}[smoke-install]${NC} tmp cleaned (after failure): $TMPDIR"
|
|
30
|
+
fi
|
|
31
|
+
exit $exit_code
|
|
32
|
+
}
|
|
33
|
+
trap cleanup EXIT
|
|
34
|
+
|
|
35
|
+
echo -e "${BLUE}[smoke-install]${NC} tmp dir: $TMPDIR"
|
|
36
|
+
echo -e "${BLUE}[smoke-install]${NC} XDG_CONFIG_HOME=$XDG_CONFIG_HOME"
|
|
37
|
+
echo -e "${YELLOW}[smoke-install]${NC} running: bun run src/cli/install.ts --dry-run --preset=default --enable-http"
|
|
38
|
+
|
|
39
|
+
# ── Run installer (dry-run, safe — does not write to filesystem) ──────────────
|
|
40
|
+
cd "$PROJECT_ROOT"
|
|
41
|
+
if bun run src/cli/install.ts --dry-run --preset=default --enable-http; then
|
|
42
|
+
echo -e "${GREEN}[smoke-install] PASS:${NC} bunx ndomo install dry-run completed"
|
|
43
|
+
exit 0
|
|
44
|
+
else
|
|
45
|
+
echo -e "${RED}[smoke-install] FAIL:${NC} install.ts dry-run exited non-zero" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ndomo I3 web UI smoke test — verifies Elysia single-port SPA topology.
|
|
3
|
+
#
|
|
4
|
+
# Builds the Vue SPA via `bun run web:build`, boots the HTTP server, then runs
|
|
5
|
+
# a battery of curl assertions:
|
|
6
|
+
# - GET / → 200 text/html (SPA index)
|
|
7
|
+
# - GET /api/health → 200 JSON
|
|
8
|
+
# - GET /api/plans without auth → 401 + WWW-Authenticate
|
|
9
|
+
# - GET /api/plans with auth → 200 JSON array
|
|
10
|
+
# - GET /plans/<random-uuid> → 200 text/html (SPA fallback)
|
|
11
|
+
# - GET /api/plans/<bad-uuid> → 404 JSON
|
|
12
|
+
# - GET /assets/<hashed>.js → 200 application/javascript
|
|
13
|
+
# - GET /assets/<hashed>.css → 200 text/css
|
|
14
|
+
#
|
|
15
|
+
# Plus suite-level checks at the end:
|
|
16
|
+
# - bun run typecheck (0 errors)
|
|
17
|
+
# - bun test (full suite, no regressions)
|
|
18
|
+
# - bun run web:typecheck (0 errors)
|
|
19
|
+
#
|
|
20
|
+
# Usage:
|
|
21
|
+
# bash scripts/smoke-web.sh
|
|
22
|
+
#
|
|
23
|
+
# Env overrides:
|
|
24
|
+
# SMOKE_PORT TCP port for the server (default: 4097)
|
|
25
|
+
# SMOKE_PASSWORD HTTP Basic password (default: smoke-test-password)
|
|
26
|
+
# SMOKE_TIMEOUT Health-check wait in seconds (default: 10)
|
|
27
|
+
# SKIP_BUILD Skip `bun run web:build` if artifacts already present
|
|
28
|
+
#
|
|
29
|
+
# Exit 0 on all assertions pass, exit 1 on any failure.
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
|
|
32
|
+
# ─── Banner ──────────────────────────────────────────────────────────────────
|
|
33
|
+
echo "┌──────────────────────────────────────┐"
|
|
34
|
+
echo "│ I3 web UI SPA smoke test │"
|
|
35
|
+
echo "└──────────────────────────────────────┘"
|
|
36
|
+
|
|
37
|
+
# ─── Resolve project root (this script's parent dir) ─────────────────────────
|
|
38
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
39
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
40
|
+
cd "${PROJECT_ROOT}"
|
|
41
|
+
|
|
42
|
+
# ─── Pre-flight checks ───────────────────────────────────────────────────────
|
|
43
|
+
command -v bun >/dev/null 2>&1 || {
|
|
44
|
+
echo "[FAIL] bun not found in PATH — install from https://bun.sh" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
}
|
|
47
|
+
command -v curl >/dev/null 2>&1 || {
|
|
48
|
+
echo "[FAIL] curl not found in PATH" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ─── Config ──────────────────────────────────────────────────────────────────
|
|
53
|
+
SMOKE_PORT="${SMOKE_PORT:-4097}"
|
|
54
|
+
SMOKE_PASSWORD="${SMOKE_PASSWORD:-smoke-test-password}"
|
|
55
|
+
SMOKE_TIMEOUT="${SMOKE_TIMEOUT:-10}"
|
|
56
|
+
SKIP_BUILD="${SKIP_BUILD:-}"
|
|
57
|
+
|
|
58
|
+
# Validate port range
|
|
59
|
+
if ! [[ "${SMOKE_PORT}" =~ ^[0-9]+$ ]] || [ "${SMOKE_PORT}" -lt 1 ] || [ "${SMOKE_PORT}" -gt 65535 ]; then
|
|
60
|
+
echo "[FAIL] invalid SMOKE_PORT: ${SMOKE_PORT}" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# ─── Port fallback helper ────────────────────────────────────────────────────
|
|
65
|
+
# If SMOKE_PORT is in use, fall back to the next free port up to +10.
|
|
66
|
+
find_free_port() {
|
|
67
|
+
local start="$1"
|
|
68
|
+
for offset in $(seq 0 10); do
|
|
69
|
+
local candidate=$((start + offset))
|
|
70
|
+
if ! ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "(^|:)${candidate}$"; then
|
|
71
|
+
echo "${candidate}"
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
PORT="$(find_free_port "${SMOKE_PORT}")" || {
|
|
79
|
+
echo "[FAIL] no free port in range ${SMOKE_PORT}-$((SMOKE_PORT + 10))" >&2
|
|
80
|
+
exit 1
|
|
81
|
+
}
|
|
82
|
+
if [ "${PORT}" != "${SMOKE_PORT}" ]; then
|
|
83
|
+
echo "[info] SMOKE_PORT ${SMOKE_PORT} busy — falling back to ${PORT}"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# ─── Build SPA (Vite → src/http/web/) ────────────────────────────────────────
|
|
87
|
+
WEB_DIST="${PROJECT_ROOT}/src/http/web"
|
|
88
|
+
if [ -n "${SKIP_BUILD}" ] && [ -f "${WEB_DIST}/index.html" ]; then
|
|
89
|
+
echo "[setup] SKIP_BUILD set + artifacts present — skipping web:build"
|
|
90
|
+
else
|
|
91
|
+
echo "[setup] building Vue SPA (bun run web:build)..."
|
|
92
|
+
bun run web:build || {
|
|
93
|
+
echo "[FAIL] bun run web:build failed" >&2
|
|
94
|
+
exit 1
|
|
95
|
+
}
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
if [ ! -f "${WEB_DIST}/index.html" ]; then
|
|
99
|
+
echo "[FAIL] ${WEB_DIST}/index.html missing after build" >&2
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# ─── Bootstrap .ndomo/state.db if missing ────────────────────────────────────
|
|
104
|
+
NDOMO_DB="${PROJECT_ROOT}/.ndomo/state.db"
|
|
105
|
+
if [ ! -f "${NDOMO_DB}" ]; then
|
|
106
|
+
echo "[setup] bootstrapping .ndomo/state.db..."
|
|
107
|
+
bun -e '
|
|
108
|
+
import { Database } from "bun:sqlite";
|
|
109
|
+
import { mkdirSync } from "node:fs";
|
|
110
|
+
import { join } from "node:path";
|
|
111
|
+
const dir = join(process.cwd(), ".ndomo");
|
|
112
|
+
mkdirSync(dir, { recursive: true });
|
|
113
|
+
const db = new Database(join(dir, "state.db"), { create: true });
|
|
114
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
115
|
+
db.exec("PRAGMA auto_vacuum = INCREMENTAL");
|
|
116
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
117
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
118
|
+
const { runMigrations } = await import("./src/db/migrations.ts");
|
|
119
|
+
runMigrations(db);
|
|
120
|
+
db.close();
|
|
121
|
+
console.log("[setup] db initialized");
|
|
122
|
+
' || {
|
|
123
|
+
echo "[FAIL] failed to bootstrap .ndomo/state.db" >&2
|
|
124
|
+
exit 1
|
|
125
|
+
}
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# ─── Export server env ───────────────────────────────────────────────────────
|
|
129
|
+
export NDOMO_HTTP_ENABLED=true
|
|
130
|
+
export NDOMO_HTTP_PORT="${PORT}"
|
|
131
|
+
export NDOMO_HTTP_AUTH_REQUIRED=true
|
|
132
|
+
export NDOMO_HTTP_CORS_ORIGINS='*'
|
|
133
|
+
export OPENCODE_SERVER_PASSWORD="${SMOKE_PASSWORD}"
|
|
134
|
+
export OPENCODE_SERVER_URL="${OPENCODE_SERVER_URL:-http://localhost:4096}"
|
|
135
|
+
|
|
136
|
+
# ─── Start server in background ─────────────────────────────────────────────
|
|
137
|
+
LOG="$(mktemp -t smoke-web-XXXXXX.log)"
|
|
138
|
+
echo "[setup] starting server on port ${PORT} (log: ${LOG})"
|
|
139
|
+
|
|
140
|
+
bun run src/cli/serve.ts --port "${PORT}" --cors '*' >"${LOG}" 2>&1 &
|
|
141
|
+
SERVER_PID=$!
|
|
142
|
+
|
|
143
|
+
# Register cleanup trap — runs even on assertion failure
|
|
144
|
+
cleanup() {
|
|
145
|
+
local exit_code=$?
|
|
146
|
+
if kill -0 "${SERVER_PID}" 2>/dev/null; then
|
|
147
|
+
echo "[cleanup] killing server pid ${SERVER_PID}"
|
|
148
|
+
kill "${SERVER_PID}" 2>/dev/null || true
|
|
149
|
+
for _ in 1 2 3 4 5 6; do
|
|
150
|
+
kill -0 "${SERVER_PID}" 2>/dev/null || break
|
|
151
|
+
sleep 0.5
|
|
152
|
+
done
|
|
153
|
+
kill -9 "${SERVER_PID}" 2>/dev/null || true
|
|
154
|
+
wait "${SERVER_PID}" 2>/dev/null || true
|
|
155
|
+
fi
|
|
156
|
+
rm -f "${LOG}"
|
|
157
|
+
exit "${exit_code}"
|
|
158
|
+
}
|
|
159
|
+
trap cleanup EXIT INT TERM
|
|
160
|
+
|
|
161
|
+
# ─── Wait for /health (up to SMOKE_TIMEOUT seconds, 200ms backoff) ────────────
|
|
162
|
+
echo "[wait] polling /health (timeout ${SMOKE_TIMEOUT}s)..."
|
|
163
|
+
ready=false
|
|
164
|
+
deadline=$(( $(date +%s) + SMOKE_TIMEOUT ))
|
|
165
|
+
while [ "$(date +%s)" -lt "${deadline}" ]; do
|
|
166
|
+
if ! kill -0 "${SERVER_PID}" 2>/dev/null; then
|
|
167
|
+
echo "[FAIL] server died before becoming ready. Log:"
|
|
168
|
+
cat "${LOG}" >&2
|
|
169
|
+
exit 1
|
|
170
|
+
fi
|
|
171
|
+
if curl -fsS -o /dev/null "localhost:${PORT}/health" 2>/dev/null; then
|
|
172
|
+
ready=true
|
|
173
|
+
break
|
|
174
|
+
fi
|
|
175
|
+
sleep 0.2
|
|
176
|
+
done
|
|
177
|
+
if [ "${ready}" != "true" ]; then
|
|
178
|
+
echo "[FAIL] /health did not respond within ${SMOKE_TIMEOUT}s. Log:"
|
|
179
|
+
cat "${LOG}" >&2
|
|
180
|
+
exit 1
|
|
181
|
+
fi
|
|
182
|
+
echo "[ready] server up on port ${PORT}"
|
|
183
|
+
|
|
184
|
+
# ─── Assertion harness ───────────────────────────────────────────────────────
|
|
185
|
+
PASS=0
|
|
186
|
+
FAIL=0
|
|
187
|
+
FAILED_NAMES=()
|
|
188
|
+
|
|
189
|
+
assert() {
|
|
190
|
+
local name="$1"
|
|
191
|
+
local cmd="$2"
|
|
192
|
+
if eval "${cmd}" >/dev/null 2>&1; then
|
|
193
|
+
echo "[PASS] ${name}"
|
|
194
|
+
PASS=$((PASS + 1))
|
|
195
|
+
else
|
|
196
|
+
echo "[FAIL] ${name}"
|
|
197
|
+
FAIL=$((FAIL + 1))
|
|
198
|
+
FAILED_NAMES+=("${name}")
|
|
199
|
+
fi
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# ─── SPA index served at / (PUBLIC — no auth required) ──────────────────────
|
|
203
|
+
# SPA root + static assets + history fallback are exempt from auth. Only /api/*
|
|
204
|
+
# requires the OPENCODE_SERVER_PASSWORD.
|
|
205
|
+
curl -fsS -o /tmp/smoke-index.html "localhost:${PORT}/" || true
|
|
206
|
+
JS_ASSET_PATH="$(grep -oE 'assets/[A-Za-z0-9_./-]+\.js' /tmp/smoke-index.html | head -1 || true)"
|
|
207
|
+
CSS_ASSET_PATH="$(grep -oE 'assets/[A-Za-z0-9_./-]+\.css' /tmp/smoke-index.html | head -1 || true)"
|
|
208
|
+
|
|
209
|
+
assert "SPA index served: GET / → 200 text/html (no auth, public)" \
|
|
210
|
+
"[ \"\$(curl -s -o /dev/null -w '%{http_code}' localhost:${PORT}/)\" = '200' ] && [ \"\$(curl -s -o /dev/null -w '%{content_type}' localhost:${PORT}/ | cut -d';' -f1)\" = 'text/html' ]"
|
|
211
|
+
|
|
212
|
+
assert "SPA index body references hashed asset" \
|
|
213
|
+
"[ -n \"${JS_ASSET_PATH}\" ]"
|
|
214
|
+
|
|
215
|
+
# ─── /health still works (no auth required) ─────────────────────────────────
|
|
216
|
+
assert "API: GET /health → 200 JSON status=ok (no auth)" \
|
|
217
|
+
"[ \"\$(curl -fsS -o /tmp/smoke-health.json -w '%{http_code}' localhost:${PORT}/health)\" = '200' ] && [ \"\$(grep -o '\"status\":\"[^\"]*\"' /tmp/smoke-health.json | head -1 | cut -d'\"' -f4)\" = 'ok' ]"
|
|
218
|
+
|
|
219
|
+
# ─── /api/plans auth required ───────────────────────────────────────────────
|
|
220
|
+
assert "API: GET /api/plans without auth → 401 + WWW-Authenticate" \
|
|
221
|
+
"[ \"\$(curl -s -o /dev/null -w '%{http_code}' localhost:${PORT}/api/plans)\" = '401' ] && [ -n \"\$(curl -s -i localhost:${PORT}/api/plans | grep -i '^www-authenticate:')\" ]"
|
|
222
|
+
|
|
223
|
+
# ─── /api/plans auth OK ──────────────────────────────────────────────────────
|
|
224
|
+
assert "API: GET /api/plans with auth → 200 JSON array" \
|
|
225
|
+
"[ \"\$(curl -fsS -o /tmp/smoke-plans.json -w '%{http_code}' -u \"user:${SMOKE_PASSWORD}\" localhost:${PORT}/api/plans)\" = '200' ] && head -c 1 /tmp/smoke-plans.json | grep -q '\\['"
|
|
226
|
+
|
|
227
|
+
# ─── SPA history fallback (non-/api path → index.html, NOT 404) ─────────────
|
|
228
|
+
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
|
229
|
+
curl -s -o /tmp/smoke-spa.html -w '%{http_code}|%{content_type}' "localhost:${PORT}/plans/${RANDOM_UUID}" > /tmp/smoke-spa-meta.txt || true
|
|
230
|
+
assert "SPA fallback: GET /plans/<random-uuid> → 200 text/html (not 404, no auth)" \
|
|
231
|
+
"[ \"\$(cut -d'|' -f1 /tmp/smoke-spa-meta.txt)\" = '200' ] && [ \"\$(cut -d'|' -f2 /tmp/smoke-spa-meta.txt | cut -d';' -f1)\" = 'text/html' ] && grep -q '<div id=\"app\"' /tmp/smoke-spa.html"
|
|
232
|
+
|
|
233
|
+
# ─── /api/* unknown → 404 JSON (SPA fallback does NOT swallow /api) ─────────
|
|
234
|
+
assert "API: GET /api/plans/<bad-uuid> → 404 JSON error" \
|
|
235
|
+
"[ \"\$(curl -s -o /tmp/smoke-404.json -w '%{http_code}' -u \"user:${SMOKE_PASSWORD}\" localhost:${PORT}/api/plans/does-not-exist-zzz)\" = '404' ] && [ \"\$(grep -o '\"[^\"]*\"' /tmp/smoke-404.json | head -1 | cut -d'\"' -f2)\" = 'error' ]"
|
|
236
|
+
|
|
237
|
+
# ─── Static JS asset served (PUBLIC, no auth) ───────────────────────────────
|
|
238
|
+
if [ -n "${JS_ASSET_PATH}" ]; then
|
|
239
|
+
assert "Static: GET /${JS_ASSET_PATH} → 200 application/javascript (no auth)" \
|
|
240
|
+
"[ \"\$(curl -fsS -o /tmp/smoke-asset.js -w '%{http_code}' localhost:${PORT}/${JS_ASSET_PATH})\" = '200' ] && [ \"\$(curl -s -o /dev/null -w '%{content_type}' localhost:${PORT}/${JS_ASSET_PATH} | cut -d';' -f1)\" = 'application/javascript' ] && [ \"\$(wc -c < /tmp/smoke-asset.js)\" -gt 100 ]"
|
|
241
|
+
else
|
|
242
|
+
assert "Static: GET /assets/<hashed>.js → 200 application/javascript" "false"
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# ─── Static CSS asset served (PUBLIC, no auth) ──────────────────────────────
|
|
246
|
+
if [ -n "${CSS_ASSET_PATH}" ]; then
|
|
247
|
+
assert "Static: GET /${CSS_ASSET_PATH} → 200 text/css (no auth)" \
|
|
248
|
+
"[ \"\$(curl -fsS -o /tmp/smoke-style.css -w '%{http_code}' localhost:${PORT}/${CSS_ASSET_PATH})\" = '200' ] && [ \"\$(curl -s -o /dev/null -w '%{content_type}' localhost:${PORT}/${CSS_ASSET_PATH} | cut -d';' -f1)\" = 'text/css' ]"
|
|
249
|
+
else
|
|
250
|
+
# Vue SPA often inlines CSS — skip if no external CSS asset.
|
|
251
|
+
assert "Static: GET /assets/<hashed>.css → 200 text/css (skipped: no external CSS asset in build)" "true"
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
# ─── Stop server before suite-level checks ──────────────────────────────────
|
|
255
|
+
echo ""
|
|
256
|
+
echo "[cleanup] stopping server before suite-level checks..."
|
|
257
|
+
kill "${SERVER_PID}" 2>/dev/null || true
|
|
258
|
+
for _ in 1 2 3 4 5 6; do
|
|
259
|
+
kill -0 "${SERVER_PID}" 2>/dev/null || break
|
|
260
|
+
sleep 0.5
|
|
261
|
+
done
|
|
262
|
+
kill -9 "${SERVER_PID}" 2>/dev/null || true
|
|
263
|
+
wait "${SERVER_PID}" 2>/dev/null || true
|
|
264
|
+
trap - EXIT INT TERM
|
|
265
|
+
|
|
266
|
+
# ─── Suite-level checks ─────────────────────────────────────────────────────
|
|
267
|
+
echo ""
|
|
268
|
+
echo "[suite] running bun run typecheck..."
|
|
269
|
+
if bun run typecheck >/tmp/smoke-typecheck.log 2>&1; then
|
|
270
|
+
echo "[PASS] typecheck: 0 errors"
|
|
271
|
+
PASS=$((PASS + 1))
|
|
272
|
+
else
|
|
273
|
+
echo "[FAIL] typecheck had errors:" >&2
|
|
274
|
+
tail -30 /tmp/smoke-typecheck.log >&2
|
|
275
|
+
FAIL=$((FAIL + 1))
|
|
276
|
+
FAILED_NAMES+=("typecheck")
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
echo "[suite] running bun test (src/)..."
|
|
280
|
+
# Unset smoke env vars so they don't pollute tests that check default config behavior.
|
|
281
|
+
unset NDOMO_HTTP_ENABLED
|
|
282
|
+
unset NDOMO_HTTP_PORT
|
|
283
|
+
unset NDOMO_HTTP_AUTH_REQUIRED
|
|
284
|
+
unset NDOMO_HTTP_CORS_ORIGINS
|
|
285
|
+
unset OPENCODE_SERVER_PASSWORD
|
|
286
|
+
unset OPENCODE_SERVER_URL
|
|
287
|
+
# web/__tests__/ uses vitest (vi.mocked etc.) — not bun:test. Scope bun test to src/.
|
|
288
|
+
if bun test src/ >/tmp/smoke-buntest.log 2>&1; then
|
|
289
|
+
TLINE="$(grep -oE '[0-9]+ pass' /tmp/smoke-buntest.log | head -1 || echo 'all pass')"
|
|
290
|
+
echo "[PASS] bun test src/: ${TLINE}"
|
|
291
|
+
PASS=$((PASS + 1))
|
|
292
|
+
else
|
|
293
|
+
echo "[FAIL] bun test src/ had failures:" >&2
|
|
294
|
+
tail -40 /tmp/smoke-buntest.log >&2
|
|
295
|
+
FAIL=$((FAIL + 1))
|
|
296
|
+
FAILED_NAMES+=("bun test src/")
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
echo "[suite] running bun run web:test (vitest)..."
|
|
300
|
+
if bun run web:test >/tmp/smoke-webtest.log 2>&1; then
|
|
301
|
+
TLINE="$(grep -oE 'Tests *[0-9]+ passed' /tmp/smoke-webtest.log | head -1 || echo 'all pass')"
|
|
302
|
+
echo "[PASS] web:test (vitest): ${TLINE}"
|
|
303
|
+
PASS=$((PASS + 1))
|
|
304
|
+
else
|
|
305
|
+
echo "[FAIL] web:test (vitest) had failures:" >&2
|
|
306
|
+
tail -40 /tmp/smoke-webtest.log >&2
|
|
307
|
+
FAIL=$((FAIL + 1))
|
|
308
|
+
FAILED_NAMES+=("web:test")
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
echo "[suite] running bun run web:typecheck..."
|
|
312
|
+
if bun run web:typecheck >/tmp/smoke-webtypecheck.log 2>&1; then
|
|
313
|
+
echo "[PASS] web:typecheck: 0 errors"
|
|
314
|
+
PASS=$((PASS + 1))
|
|
315
|
+
else
|
|
316
|
+
echo "[FAIL] web:typecheck had errors:" >&2
|
|
317
|
+
tail -30 /tmp/smoke-webtypecheck.log >&2
|
|
318
|
+
FAIL=$((FAIL + 1))
|
|
319
|
+
FAILED_NAMES+=("web:typecheck")
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# ─── Report ──────────────────────────────────────────────────────────────────
|
|
323
|
+
echo ""
|
|
324
|
+
echo "────────────────────────────────────────"
|
|
325
|
+
TOTAL=$((PASS + FAIL))
|
|
326
|
+
echo "PASS: ${PASS}/${TOTAL}"
|
|
327
|
+
if [ "${FAIL}" -gt 0 ]; then
|
|
328
|
+
echo "FAILED assertions:"
|
|
329
|
+
for name in "${FAILED_NAMES[@]}"; do
|
|
330
|
+
echo " - ${name}"
|
|
331
|
+
done
|
|
332
|
+
exit 1
|
|
333
|
+
fi
|
|
334
|
+
echo "PASS: ${PASS}/${PASS}"
|
|
335
|
+
exit 0
|