From 015f1e501e2132d2abc6bbbcb8e6af074c326b47 Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 6 Aug 2026 21:17:19 +0800 Subject: [PATCH] fix(admin): declare users route runtime locally --- docs/BUG_HISTORY.md | 16 ++++++++++++++++ frontend/src/app/api/admin/users/route.ts | 4 +++- frontend/tests/admin-users-contract.test.ts | 6 ++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 3729bcac..e29ba7f0 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -2288,3 +2288,19 @@ - 相关记录:BUG-126、BUG-131 - 复发自:BUG-126 - 修复版本:待能力审计同步与 staging 验收 + +## BUG-133 | admin users Route Handler 重导出 runtime 导致 production build 失败 + +- 状态:investigating +- 首次发现:2026-08-06 +- 最近更新:2026-08-06 +- 影响面:`frontend/src/app/api/admin/users/route.ts`、Next.js production build、Gitea staging quality gate;customer handler 权限和业务逻辑未受改动。 +- 用户现象:PR gate run `1493` 的 Python 路由回归和 frontend 1472/1472 均通过,但 `next build` 报 `Next.js can't recognize the exported runtime field in route. It mustn't be reexported`,publish 被跳过,自动 deploy 未触发。 +- 触发条件:legacy `/api/admin/users` Route Handler 通过 `export { ..., runtime } from "../customers/route"` 同时重导出 handlers 和 route segment config。 +- 根因:Next.js 要求 `runtime` 等 route segment config 在当前 route 文件中可被静态解析,不允许从另一 Route Handler 重导出;既有精确合同测试反而固化了非法 re-export,且并发功能本地 production build 未闭环。 +- 修复:在 users route 本文件静态声明 `export const runtime = "nodejs"`,只重导出 DELETE/GET/PATCH/POST/PUT handlers;不复制 handler、不修改权限或客户数据逻辑。合同测试改为强制本地 runtime 常量并拒绝 runtime re-export。 +- 验证:待 admin users 目标合同、Next production build、完整 Gitea PR gate 和 staging gate;完成前不得标记 resolved。 +- 防复发:Route Handler 的 `runtime`、`dynamic`、`revalidate` 等 segment config 必须本地静态声明;handler 可复用,但 segment config 不得 re-export。新增 alias route 必须经过 production build,而不只运行文本合同测试。 +- 相关记录:BUG-131、BUG-132 +- 复发自:无 +- 修复版本:待 Route Handler 静态 config 修复与 staging 验收 diff --git a/frontend/src/app/api/admin/users/route.ts b/frontend/src/app/api/admin/users/route.ts index f5a5737b..a12fc55f 100644 --- a/frontend/src/app/api/admin/users/route.ts +++ b/frontend/src/app/api/admin/users/route.ts @@ -1 +1,3 @@ -export { DELETE, GET, PATCH, POST, PUT, runtime } from "../customers/route"; +export const runtime = "nodejs"; + +export { DELETE, GET, PATCH, POST, PUT } from "../customers/route"; diff --git a/frontend/tests/admin-users-contract.test.ts b/frontend/tests/admin-users-contract.test.ts index d42941fc..bedbe96d 100644 --- a/frontend/tests/admin-users-contract.test.ts +++ b/frontend/tests/admin-users-contract.test.ts @@ -38,6 +38,8 @@ test("admin_users migration is service-role-only and auditable", () => { assert.match(migration, /grant select, insert, update on table public\.admin_users to service_role/); }); -test("legacy admin users route aliases the guarded customer resource", () => { - assert.match(usersSource, /export \{ DELETE, GET, PATCH, POST, PUT, runtime \} from "\.\.\/customers\/route"/); +test("legacy admin users route aliases handlers but declares runtime statically", () => { + assert.match(usersSource, /export const runtime = "nodejs"/); + assert.match(usersSource, /export \{ DELETE, GET, PATCH, POST, PUT \} from "\.\.\/customers\/route"/); + assert.doesNotMatch(usersSource, /export \{[^}]*runtime[^}]*\} from/); });