diff --git a/hub/deploy/deploy_platform.sh b/hub/deploy/deploy_platform.sh index 01bebf5..80a7a16 100755 --- a/hub/deploy/deploy_platform.sh +++ b/hub/deploy/deploy_platform.sh @@ -60,9 +60,10 @@ if [ "$release_ready" = false ]; then -e "ssh ${SSH_OPTS[*]}" \ "$REPO_ROOT/hub/" "$DEPLOY_USER@$HOST:$HUB_DIR/" - # 2. Install deps, audit and build, then atomically mark the release complete. + # 2. Install deps (hub + admin-web), audit hub prod, build tsc + SPA, mark complete. + # `npm run build` → tsc then admin:build → admin-web/build for registerStaticSpa. ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" \ - "cd '$HUB_DIR' && npm ci && npm run audit:production && npm run build && touch '$RELEASE_DIR/.complete'" + "cd '$HUB_DIR' && npm ci && npm ci --prefix admin-web && npm run audit:production && npm run build && touch '$RELEASE_DIR/.complete'" fi # 3. Ensure the service is installed (idempotent), then restart. diff --git a/hub/package.json b/hub/package.json index e2c2116..f21b044 100644 --- a/hub/package.json +++ b/hub/package.json @@ -30,7 +30,7 @@ "description": "Curriculum Project Hub — org-scoped Feishu collaboration and confined Agent runtime. Aligns to spec/System through ADR-0024.", "scripts": { "dev": "npm run prisma:migrate && tsx watch src/server.ts", - "build": "tsc -p tsconfig.json", + "build": "tsc -p tsconfig.json && npm run admin:build", "start": "npm run prisma:migrate && node dist/server.js", "check": "tsc -p tsconfig.json --noEmit", "audit:production": "npm audit --omit=dev --audit-level=high", diff --git a/hub/src/admin/plugin.ts b/hub/src/admin/plugin.ts index 5115942..744600c 100644 --- a/hub/src/admin/plugin.ts +++ b/hub/src/admin/plugin.ts @@ -1,11 +1,12 @@ /** - * Registers org-admin HTTP surface: auth, org APIs, (later) static SPA. + * Registers org-admin HTTP surface: auth, org APIs, and the static SPA shell. */ import cookie from "@fastify/cookie"; import type { PrismaClient } from "@prisma/client"; import type { FastifyInstance } from "fastify"; import { registerAuthRoutes } from "./routes/authRoutes.js"; import { registerOrgRoutes } from "./routes/orgRoutes.js"; +import { registerStaticSpa } from "./static.js"; import type { LocalSecretEnvelope } from "../security/secretEnvelope.js"; import type { ProviderReadinessProbe } from "../connections/providerReadiness.js"; import type { FeishuReadinessProbe } from "../connections/feishuReadiness.js"; @@ -62,4 +63,7 @@ export async function registerAdminPlugin( ? { feishuConnectionReadinessProbe: config.feishuConnectionReadinessProbe } : {}), }); + + // After API + /admin/login so SPA fallback does not shadow auth routes. + await registerStaticSpa(app); } diff --git a/hub/src/admin/static.ts b/hub/src/admin/static.ts index 024e2c7..92fbbd8 100644 --- a/hub/src/admin/static.ts +++ b/hub/src/admin/static.ts @@ -70,9 +70,9 @@ export async function registerStaticSpa(app: FastifyInstance): Promise { } }); - // SPA client-side route fallback. `/admin/login` is registered earlier as a - // static route and takes precedence; everything else under /admin/* serves - // the index so SvelteKit's client-side router can resolve the view. + // SPA client-side route fallback. Auth registers `/admin/login` first so it + // takes precedence; everything else under /admin/* serves the index so + // SvelteKit's client-side router can resolve the view. app.get("/admin", async (_request, reply) => { return reply.type("text/html; charset=utf-8").send(indexHtml); });