chore(platform): add local smoke script

This commit is contained in:
Saravana Achu Mac 2026-05-05 09:29:42 -07:00
parent dc5e79b1d5
commit a2053a70f1
4 changed files with 300 additions and 0 deletions

View File

@ -39,6 +39,16 @@ curl -sf http://localhost:4016/health
curl -sf http://localhost:4016/api/bootstrap
```
Local production-readiness smoke:
```bash
pnpm run smoke:local
# Use an already-running backend or skip shared service checks when isolating product behavior.
SMOKE_START_BACKEND=0 pnpm run smoke:local -- --no-start
pnpm run smoke:local -- --skip-platform
```
## Architecture
| Surface | Stack | Port |

View File

@ -172,6 +172,28 @@ Expected:
## 7. NoteLett Backend Checks
The automated local smoke entrypoint is:
```bash
pnpm run smoke:local
```
It follows the common-platform self-test convention of generating a short-lived local test JWT, then verifies:
- `GET /health`
- `GET /api/bootstrap`
- platform-service, extraction-service, and mcp-server health
- authenticated workspace create
- authenticated note create/read
- best-effort workspace/note cleanup
Useful variants:
```bash
pnpm run smoke:local -- --no-start
pnpm run smoke:local -- --skip-platform
```
Start backend in memory mode for local product smoke:
```bash

View File

@ -6,6 +6,7 @@
"typecheck": "pnpm --filter @notelett/backend run typecheck && pnpm --filter @notelett/web run typecheck && pnpm --filter @notelett/mobile run typecheck",
"test": "pnpm --filter @notelett/backend run test && pnpm --filter @notelett/web run test && pnpm --filter @notelett/mobile run test",
"build": "pnpm --filter @notelett/backend run build && pnpm --filter @notelett/web run build",
"smoke:local": "bash scripts/local-smoke.sh",
"verify": "pnpm run typecheck && pnpm run test && pnpm run build",
"prepare": "husky"
},

267
scripts/local-smoke.sh Executable file
View File

@ -0,0 +1,267 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMMON_PLAT_DIR="${COMMON_PLAT_DIR:-${ROOT_DIR}/../learning_ai/learning_ai_common_plat}"
NOTELETT_URL="${NOTELETT_URL:-http://localhost:4016}"
NOTELETT_API_URL="${NOTELETT_API_URL:-${NOTELETT_URL}/api}"
PLATFORM_URL="${PLATFORM_URL:-http://localhost:4003}"
EXTRACTION_URL="${EXTRACTION_URL:-http://localhost:4005}"
MCP_ORIGIN="${MCP_ORIGIN:-http://localhost:4007}"
JWT_SECRET="${JWT_SECRET:-dev-secret-do-not-use-in-prod}"
PRODUCT_ID="${PRODUCT_ID:-notelett}"
SMOKE_USER_ID="${SMOKE_USER_ID:-notelett-smoke-user}"
SMOKE_START_BACKEND="${SMOKE_START_BACKEND:-1}"
SMOKE_SKIP_PLATFORM="${SMOKE_SKIP_PLATFORM:-0}"
BACKEND_PID=""
BACKEND_LOG="${TMPDIR:-/tmp}/notelett-local-smoke-backend.log"
usage() {
cat <<'USAGE'
Usage: scripts/local-smoke.sh [--no-start] [--skip-platform] [--help]
Checks:
- NoteLett GET /health
- NoteLett GET /api/bootstrap
- platform-service, extraction-service, and mcp-server /health
- authenticated workspace + note create/read/delete flow in DB_PROVIDER=memory
Environment:
NOTELETT_URL=http://localhost:4016
PLATFORM_URL=http://localhost:4003
EXTRACTION_URL=http://localhost:4005
MCP_ORIGIN=http://localhost:4007
JWT_SECRET=dev-secret-do-not-use-in-prod
SMOKE_START_BACKEND=1
SMOKE_SKIP_PLATFORM=0
COMMON_PLAT_DIR=../learning_ai/learning_ai_common_plat
USAGE
}
for arg in "$@"; do
case "$arg" in
--)
;;
--no-start)
SMOKE_START_BACKEND=0
;;
--skip-platform)
SMOKE_SKIP_PLATFORM=1
;;
--help|-h)
usage
exit 0
;;
*)
echo "Unknown argument: $arg" >&2
usage >&2
exit 2
;;
esac
done
cleanup() {
if [[ -n "$BACKEND_PID" ]] && kill -0 "$BACKEND_PID" >/dev/null 2>&1; then
kill "$BACKEND_PID" >/dev/null 2>&1 || true
wait "$BACKEND_PID" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
curl_json() {
local method="$1"
local url="$2"
local body="${3:-}"
shift 3 || true
if [[ -n "$body" ]]; then
curl -fsS -X "$method" "$url" \
-H 'content-type: application/json' \
"$@" \
--data "$body"
else
curl -fsS -X "$method" "$url" "$@"
fi
}
healthy() {
curl -fsS "$NOTELETT_URL/health" >/dev/null 2>&1
}
ensure_common_platform_builds() {
if [[ ! -d "$COMMON_PLAT_DIR" ]]; then
echo "error: missing common-platform checkout at $COMMON_PLAT_DIR" >&2
return 1
fi
local palace_dist="$COMMON_PLAT_DIR/packages/palace/dist/index.js"
if [[ ! -f "$palace_dist" ]]; then
echo "info: building linked common-platform @bytelyst/palace package"
GITEA_NPM_TOKEN="${GITEA_NPM_TOKEN:-dummy}" \
pnpm -C "$COMMON_PLAT_DIR" --filter @bytelyst/palace run build
fi
local llm_dist="$COMMON_PLAT_DIR/packages/llm/dist/index.js"
if [[ ! -f "$llm_dist" ]] || ! grep -q 'buildVisionMessage' "$llm_dist"; then
echo "info: building linked common-platform @bytelyst/llm package"
GITEA_NPM_TOKEN="${GITEA_NPM_TOKEN:-dummy}" \
pnpm -C "$COMMON_PLAT_DIR" --filter @bytelyst/llm run build
fi
}
start_backend_if_needed() {
if healthy; then
echo "ok: NoteLett backend already healthy at $NOTELETT_URL"
return
fi
if [[ "$SMOKE_START_BACKEND" != "1" ]]; then
echo "error: NoteLett backend is not healthy at $NOTELETT_URL and SMOKE_START_BACKEND=0" >&2
return 1
fi
echo "info: starting NoteLett backend in memory mode"
ensure_common_platform_builds
(
cd "$ROOT_DIR"
DB_PROVIDER=memory \
NODE_ENV=development \
JWT_SECRET="$JWT_SECRET" \
PRODUCT_ID="$PRODUCT_ID" \
pnpm --filter @notelett/backend run dev
) >"$BACKEND_LOG" 2>&1 &
BACKEND_PID="$!"
for _ in {1..60}; do
if healthy; then
echo "ok: NoteLett backend started at $NOTELETT_URL"
return
fi
if ! kill -0 "$BACKEND_PID" >/dev/null 2>&1; then
echo "error: backend exited before becoming healthy; log follows" >&2
tail -n 80 "$BACKEND_LOG" >&2 || true
return 1
fi
sleep 1
done
echo "error: backend did not become healthy within 60 seconds; log follows" >&2
tail -n 80 "$BACKEND_LOG" >&2 || true
return 1
}
generate_token() {
(
cd "$ROOT_DIR/backend"
JWT_SECRET="$JWT_SECRET" PRODUCT_ID="$PRODUCT_ID" SMOKE_USER_ID="$SMOKE_USER_ID" node --input-type=module <<'NODE'
import { SignJWT } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const productId = process.env.PRODUCT_ID ?? 'notelett';
const sub = process.env.SMOKE_USER_ID ?? 'notelett-smoke-user';
const token = await new SignJWT({
email: 'smoke@notelett.local',
role: 'admin',
productId,
type: 'access',
})
.setProtectedHeader({ alg: 'HS256' })
.setSubject(sub)
.setIssuer('bytelyst-platform')
.setIssuedAt()
.setExpirationTime('15m')
.sign(secret);
process.stdout.write(token);
NODE
)
}
json_field_equals() {
local json="$1"
local field="$2"
local expected="$3"
JSON_INPUT="$json" FIELD="$field" EXPECTED="$expected" node --input-type=module <<'NODE'
const body = JSON.parse(process.env.JSON_INPUT ?? '{}');
const actual = body[process.env.FIELD];
if (actual !== process.env.EXPECTED) {
console.error(`Expected ${process.env.FIELD}=${process.env.EXPECTED}, got ${String(actual)}`);
process.exit(1);
}
NODE
}
check_platform_dependencies() {
if [[ "$SMOKE_SKIP_PLATFORM" == "1" ]]; then
echo "skip: platform dependency checks disabled"
return
fi
curl -fsS "$PLATFORM_URL/health" >/dev/null
echo "ok: platform-service health"
curl -fsS "$EXTRACTION_URL/health" >/dev/null
echo "ok: extraction-service health"
curl -fsS "$MCP_ORIGIN/health" >/dev/null
echo "ok: mcp-server health"
}
run_product_flow() {
local token="$1"
local run_id
run_id="smoke-$(date +%s)-$$"
local workspace_id="ws-$run_id"
local note_id="note-$run_id"
local auth_header="authorization: Bearer $token"
local workspace_body
workspace_body="{\"id\":\"$workspace_id\",\"name\":\"Smoke Workspace\",\"members\":[]}"
local workspace
workspace="$(curl_json POST "$NOTELETT_API_URL/workspaces" "$workspace_body" -H "$auth_header")"
json_field_equals "$workspace" id "$workspace_id"
echo "ok: authenticated workspace create"
local note_body
note_body="{\"id\":\"$note_id\",\"workspaceId\":\"$workspace_id\",\"title\":\"Smoke Note\",\"body\":\"<p>Local smoke note body.</p>\",\"tags\":[\"smoke\"],\"links\":[]}"
local note
note="$(curl_json POST "$NOTELETT_API_URL/notes" "$note_body" -H "$auth_header")"
json_field_equals "$note" id "$note_id"
echo "ok: authenticated note create"
local fetched
fetched="$(curl_json GET "$NOTELETT_API_URL/notes/$note_id?workspaceId=$workspace_id" "" -H "$auth_header")"
json_field_equals "$fetched" productId "$PRODUCT_ID"
json_field_equals "$fetched" id "$note_id"
echo "ok: authenticated note read"
curl_json DELETE "$NOTELETT_API_URL/notes/$note_id?workspaceId=$workspace_id" "" -H "$auth_header" >/dev/null 2>&1 || true
curl_json DELETE "$NOTELETT_API_URL/workspaces/$workspace_id" "" -H "$auth_header" >/dev/null 2>&1 || true
echo "ok: smoke cleanup attempted"
}
main() {
start_backend_if_needed
curl -fsS "$NOTELETT_URL/health" >/dev/null
echo "ok: NoteLett health"
local bootstrap
bootstrap="$(curl_json GET "$NOTELETT_API_URL/bootstrap" "")"
json_field_equals "$bootstrap" productId "$PRODUCT_ID"
echo "ok: NoteLett bootstrap"
check_platform_dependencies
local token
token="$(generate_token)"
run_product_flow "$token"
echo "ok: local production-readiness smoke passed"
}
main