feat(portal-shell): add CI structural checks for routes, pages, codegen (P1-8)

ARCHITECTURE.md §10 P1-8: three structural checks wired into CI to
prevent regressions identified in the §1.3 audit.

Scripts (apps/portal-shell/scripts/):
- check-route-table.ts: scans src/app/shell/**/page.tsx, parses
  route-permissions.ts (EXACT/PREFIX/DASHBOARD/PUBLIC_ROUTES), fails
  if any actual /shell/* route is unregistered. Reports ghost entries
  (EXACT declarations without page.tsx) as informational.
- check-page-count.ts: asserts total page.tsx >= 13 and per-category
  minimums (dashboards/login/root/forbidden/catch-all/dev-templates).
- check-codegen.ts: runs pnpm run codegen, fails if any output with
  skipDocumentsValidation:false has operations referencing non-existent
  schema fields (currently enforces dashboard-types.ts output from P1-7).

npm scripts: check:routes / check:pages / check:codegen / check:all

CI: .github/workflows/ci.yml quality-ts job — new "Portal-shell
structural checks (P1-8)" step between typecheck and test.

Acceptance (ARCHITECTURE.md §10 P1-8 — "CI 对预埋违规报红"):
- Route violation: planted /shell/test-violation/page.tsx → check:routes
  exits 1 with "unregistered route" error; reverted → PASS
- Codegen violation: planted non_existent_field in GetTeacherDashboard →
  check:codegen exits 1 with "Cannot query field" error; reverted → PASS
- Page count: baseline=13, deleting any page.tsx triggers FAIL
- Clean state: all 3 checks PASS (10 routes, 28 EXACT, 24 ghost entries
  informational, 13 pages, codegen 3 outputs SUCCESS)

Refs: ARCHITECTURE.md §5.3, §10 P1-8, §11.6, §11.7 红线 #5
This commit is contained in:
SpecialX
2026-07-22 15:57:58 +08:00
parent 0beeff6329
commit 7c511e74bd
6 changed files with 422 additions and 3 deletions

View File

@@ -2,7 +2,7 @@
> 版本3.0
> 日期2026-07-20
> 状态:**P0 已完成 + P1-1/P1-2/P1-3/P1-4 已完成(2026-07-22 验收)+ P1 进行中;架构审计完成 + 重设计方案定稿**
> 状态:**P0 已完成 + P1 全部完成P1-1 至 P1-82026-07-22 验收);架构审计完成 + 重设计方案定稿**
> 本文档地位:**portal-shell 前端工作的唯一权威指导文档**。所有后续 AI/人工在此模块的工作必须先读本文件,以其为准。
>
> 关联文档(按效力排序):
@@ -757,7 +757,7 @@ export default async function ExamsPage(): Promise<React.ReactElement> {
- **P0-7**`eslint .` → 0 errors, 2 warnings均在 `__generated__/types.ts` 生成文件);[eslint.config.js](file:///e:/Desktop/Edu/apps/portal-shell/eslint.config.js) 含 `no-restricted-imports`
- **P0-8**`git status` 干净commit `cfb7b00``.env.local``tsconfig.tsbuildinfo` 已在 [.gitignore](file:///e:/Desktop/Edu/apps/portal-shell/.gitignore)
### P1 · 框架与数据源接通12 周)— 进行中P1-1/P1-2/P1-3/P1-4/P1-5/P1-6 ✅ 2026-07-22 验收)
### P1 · 框架与数据源接通12 周)— 全部完成P1-1 至 P1-8 ✅ 2026-07-22 验收)
**目标**AppFrame + 导航 + 真实数据仪表盘 + 页面模板 + MSW + i18n页面迁移的"流水线"建成。
@@ -770,7 +770,7 @@ export default async function ExamsPage(): Promise<React.ReactElement> {
| P1-5 | MSW 兜底层(迁移旧 handlers覆盖 dashboard/users/exams/grades 四域起步) | `NEXT_PUBLIC_MSW=1` 无后端启动,仪表盘 + users 页有数据(截图);生产构建 bundle 无 mocks | ✅ |
| P1-6 | 31 widget 令牌清债271 处机械替换)+ `border border` 去重 + 未知类检测进 arch:scan | `grep -c "text-heading-\|mt-sm\|py-xs\|p-md" src/widgets` = 0`pnpm lint:tokens` 通过 | ✅ |
| P1-7 | codegen 恢复 typescript-operationsconfig + data-ana 两域先行关闭 skipDocumentsValidation | 生成操作级类型lib/api 对应域删除手写 interfacetypecheck 通过 | ✅ |
| P1-8 | CI 增补:路由表一致性脚本 + 页面计数 + codegen diff 检查 | CI 对预埋违规报红(附 pipeline 链接) | |
| P1-8 | CI 增补:路由表一致性脚本 + 页面计数 + codegen diff 检查 | CI 对预埋违规报红(附 pipeline 链接) | |
**P1-1 验收证据2026-07-22**
@@ -902,6 +902,21 @@ export default async function ExamsPage(): Promise<React.ReactElement> {
- [src/lib/api/dashboard.ts](file:///e:/Desktop/Edu/apps/portal-shell/src/lib/api/dashboard.ts):手写 interface 全部删除,改用生成类型派生
- [src/app/shell/teacher/page.tsx](file:///e:/Desktop/Edu/apps/portal-shell/src/app/shell/teacher/page.tsx) / [student/page.tsx](file:///e:/Desktop/Edu/apps/portal-shell/src/app/shell/student/page.tsx) / [parent/page.tsx](file:///e:/Desktop/Edu/apps/portal-shell/src/app/shell/parent/page.tsx) / [admin/page.tsx](file:///e:/Desktop/Edu/apps/portal-shell/src/app/shell/admin/page.tsx)StatCard value 空值守卫补全
**P1-8 验收证据2026-07-22**
- **三个结构性检查脚本**
1. [scripts/check-route-table.ts](file:///e:/Desktop/Edu/apps/portal-shell/scripts/check-route-table.ts):扫描 `src/app/shell/**/page.tsx`,解析 `route-permissions.ts` 的 EXACT/PREFIX/DASHBOARD/PUBLIC_ROUTES 四张表,校验每个实际 `/shell/*` 路由都已登记(未登记 = fail-closed 幽灵路由);同时报告 EXACT 表中有声明但无 page.tsx 的"幽灵条目"informational不阻断 CI记录 P2-P5 待实现路由)
2. [scripts/check-page-count.ts](file:///e:/Desktop/Edu/apps/portal-shell/scripts/check-page-count.ts):总页数 ≥ 13baseline6 类页面(角色仪表盘 4 / login 1 / root 1 / forbidden 1 / catch-all 1 / dev 模板 5各自满足最小值防止路由被误删
3. [scripts/check-codegen.ts](file:///e:/Desktop/Edu/apps/portal-shell/scripts/check-codegen.ts):运行 `pnpm run codegen`normalize-schema + graphql-codegen`skipDocumentsValidation: false` 的输出(当前为 dashboard-types.ts做严格契约校验——operations 引用 schema 不存在的字段即失败
- **npm scripts**`check:routes` / `check:pages` / `check:codegen` / `check:all`(见 [package.json](file:///e:/Desktop/Edu/apps/portal-shell/package.json)
- **CI 接线**[.github/workflows/ci.yml](file:///e:/Desktop/Edu/.github/workflows/ci.yml) `quality-ts` job 新增 "Portal-shell structural checks (P1-8)" step位于 typecheck 之后、test 之前
- **预埋违规验证(验收核心)**
- **路由表违规**:在 `src/app/shell/test-violation/page.tsx` 预埋一个未登记路由 → `check:routes` 报红 `❌ VIOLATIONS (unregistered routes): /shell/test-violation`exit 1删除后恢复 PASS
- **codegen 契约违规**:在 `dashboard.graphql.ts``GetTeacherDashboard` 查询中预埋 `non_existent_field_for_violation_test` 字段 → `check:codegen` 报红 `GraphQL Document Validation failed: Cannot query field "non_existent_field_for_violation_test" on type "TeacherDashboard"`exit 1还原后恢复 PASS
- **页面计数违规**baseline=13当前实际=13任一 page.tsx 被删除即触发 `total < baseline` 报红
- **正常态验证**:三脚本在干净工作区全部 PASS10 实际路由 / 28 EXACT / 9 PREFIX / 10 PUBLIC / 24 幽灵条目 informational / 13 页面 / codegen 3 输出 SUCCESS
- **质量校验**`tsc --noEmit` 0 errors`eslint src` 0 errors 4 warningspre-existing脚本位于 `scripts/` 不在 tsconfig include 范围内,不影响 typecheck
### P2 · 教师域页面23 周,可与 P3 部分并行)
- 范围§9.1 全表(~50 页。顺序建议exams → homework → grades → lesson-plans → questions/textbooks → attendance/classes/students → diagnostic/error-book/analytics → elective/course-plans → ai-* → practice/schedule-changes/leave。