forked from EduCraft/curriculum-project-hub
fix(admin): serve built SPA and include it in release builds
registerStaticSpa was never mounted, so production only exposed org-admin APIs. Wire it after auth/API routes, fold admin:build into npm run build, and install admin-web deps during deploy so admin-web/build ships with the release for same-origin /admin/*.
This commit is contained in:
@@ -60,9 +60,10 @@ if [ "$release_ready" = false ]; then
|
|||||||
-e "ssh ${SSH_OPTS[*]}" \
|
-e "ssh ${SSH_OPTS[*]}" \
|
||||||
"$REPO_ROOT/hub/" "$DEPLOY_USER@$HOST:$HUB_DIR/"
|
"$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" \
|
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
|
fi
|
||||||
|
|
||||||
# 3. Ensure the service is installed (idempotent), then restart.
|
# 3. Ensure the service is installed (idempotent), then restart.
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@
|
|||||||
"description": "Curriculum Project Hub — org-scoped Feishu collaboration and confined Agent runtime. Aligns to spec/System through ADR-0024.",
|
"description": "Curriculum Project Hub — org-scoped Feishu collaboration and confined Agent runtime. Aligns to spec/System through ADR-0024.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run prisma:migrate && tsx watch src/server.ts",
|
"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",
|
"start": "npm run prisma:migrate && node dist/server.js",
|
||||||
"check": "tsc -p tsconfig.json --noEmit",
|
"check": "tsc -p tsconfig.json --noEmit",
|
||||||
"audit:production": "npm audit --omit=dev --audit-level=high",
|
"audit:production": "npm audit --omit=dev --audit-level=high",
|
||||||
|
|||||||
@@ -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 cookie from "@fastify/cookie";
|
||||||
import type { PrismaClient } from "@prisma/client";
|
import type { PrismaClient } from "@prisma/client";
|
||||||
import type { FastifyInstance } from "fastify";
|
import type { FastifyInstance } from "fastify";
|
||||||
import { registerAuthRoutes } from "./routes/authRoutes.js";
|
import { registerAuthRoutes } from "./routes/authRoutes.js";
|
||||||
import { registerOrgRoutes } from "./routes/orgRoutes.js";
|
import { registerOrgRoutes } from "./routes/orgRoutes.js";
|
||||||
|
import { registerStaticSpa } from "./static.js";
|
||||||
import type { LocalSecretEnvelope } from "../security/secretEnvelope.js";
|
import type { LocalSecretEnvelope } from "../security/secretEnvelope.js";
|
||||||
import type { ProviderReadinessProbe } from "../connections/providerReadiness.js";
|
import type { ProviderReadinessProbe } from "../connections/providerReadiness.js";
|
||||||
import type { FeishuReadinessProbe } from "../connections/feishuReadiness.js";
|
import type { FeishuReadinessProbe } from "../connections/feishuReadiness.js";
|
||||||
@@ -62,4 +63,7 @@ export async function registerAdminPlugin(
|
|||||||
? { feishuConnectionReadinessProbe: config.feishuConnectionReadinessProbe }
|
? { feishuConnectionReadinessProbe: config.feishuConnectionReadinessProbe }
|
||||||
: {}),
|
: {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// After API + /admin/login so SPA fallback does not shadow auth routes.
|
||||||
|
await registerStaticSpa(app);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ export async function registerStaticSpa(app: FastifyInstance): Promise<void> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// SPA client-side route fallback. `/admin/login` is registered earlier as a
|
// SPA client-side route fallback. Auth registers `/admin/login` first so it
|
||||||
// static route and takes precedence; everything else under /admin/* serves
|
// takes precedence; everything else under /admin/* serves the index so
|
||||||
// the index so SvelteKit's client-side router can resolve the view.
|
// SvelteKit's client-side router can resolve the view.
|
||||||
app.get("/admin", async (_request, reply) => {
|
app.get("/admin", async (_request, reply) => {
|
||||||
return reply.type("text/html; charset=utf-8").send(indexHtml);
|
return reply.type("text/html; charset=utf-8").send(indexHtml);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user