forked from wangziqi/gongxue-base
chore: harden h5 launch evidence gate
This commit is contained in:
@@ -450,7 +450,7 @@ cp docs/refactor/production-launch-evidence.template.json docs/refactor/producti
|
|||||||
npm run launch:gate -- --evidence docs/refactor/production-launch-evidence.json
|
npm run launch:gate -- --evidence docs/refactor/production-launch-evidence.json
|
||||||
```
|
```
|
||||||
|
|
||||||
`production-launch-evidence.json` 不入 Git,里面只记录验收摘要、artifact 路径、审批人和时间,不保存真实 access token、支付密钥、对象存储密钥或用户隐私明细。门禁会要求以下证据全部齐备并通过:`readiness:production`、`readiness:production:db`、真实 `smoke:auth:remote`、`test:rls`、PocketBase production dry-run、`pb:import:validate`、`pb:import:sample`、真实数据 API 读路径压测、API/worker/Taro 构建、`audit:runtime`、`@codex-security` 扫描,以及备份、回滚、真实数据抽样、生产 provider、对象存储控制、支付对账和三套 H5 `runtime-config.json` 人工确认。
|
`production-launch-evidence.json` 不入 Git,里面只记录验收摘要、artifact 路径、审批人和时间,不保存真实 access token、支付密钥、对象存储密钥或用户隐私明细。门禁会要求以下证据全部齐备并通过:`readiness:production`、`readiness:production:db`、真实 `smoke:auth:remote`、`test:rls`、PocketBase production dry-run、`pb:import:validate`、`pb:import:sample`、真实数据 API 读路径压测、API/worker/Taro 构建、`smoke:taro:h5`、严格 `taro-h5-release-guardrails-test --require-runtime-config`、`audit:runtime`、真实 `@codex-security` 扫描,以及备份、回滚、真实数据抽样、生产 provider、对象存储控制、支付对账和三套 H5 `runtime-config.json` 人工确认。
|
||||||
|
|
||||||
补充说明:当前 Codex 环境如果没有暴露 `@codex-security` 可调用工具,不能把插件扫描写成已完成;只能先用 `npm run audit:runtime`、`npm run test:readiness`、`npm run test:rls` 和代码审查作为临时安全证据,并在上线证据里保留插件扫描待补项。
|
补充说明:当前 Codex 环境如果没有暴露 `@codex-security` 可调用工具,不能把插件扫描写成已完成;只能先用 `npm run audit:runtime`、`npm run test:readiness`、`npm run test:rls` 和代码审查作为临时安全证据,并在上线证据里保留插件扫描待补项。
|
||||||
|
|
||||||
@@ -575,6 +575,13 @@ node scripts\taro-h5-release-guardrails-test.js --require-dist
|
|||||||
|
|
||||||
`smoke:taro:h5` 会启动临时静态服务器和 mock API,验证三套 H5 的 `index.html`、静态资源、history fallback、公开 runtime config 和租户解析契约。`taro-h5-release-guardrails-test` 会确认三套 H5 目录存在 `index.html`,并扫描源码/产物是否混入旧 PocketBase、`x-user-id`、平台本地 key、数据库连接串或服务端密钥形态。正式部署时还必须在每个 H5 目录根部放置对应的 `runtime-config.json`。
|
`smoke:taro:h5` 会启动临时静态服务器和 mock API,验证三套 H5 的 `index.html`、静态资源、history fallback、公开 runtime config 和租户解析契约。`taro-h5-release-guardrails-test` 会确认三套 H5 目录存在 `index.html`,并扫描源码/产物是否混入旧 PocketBase、`x-user-id`、平台本地 key、数据库连接串或服务端密钥形态。正式部署时还必须在每个 H5 目录根部放置对应的 `runtime-config.json`。
|
||||||
|
|
||||||
|
写入生产上线证据时使用严格模式,确保三套正式发布目录已经放好真实公开 `runtime-config.json`,且 warning 为 0:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm --silent run smoke:taro:h5 -- --json > docs/refactor/launch-artifacts/taro-h5-static-smoke.json
|
||||||
|
node scripts\taro-h5-release-guardrails-test.js --require-dist --require-runtime-config --json > docs/refactor/launch-artifacts/taro-h5-release-guardrails.json
|
||||||
|
```
|
||||||
|
|
||||||
最近一次本地真实迁移库数据规模约为 74,117 题、1,601 个题目合集、3,106 个练习蓝图、3,505 个单词、2,678 条知识手册、3,690 个用户、113,810 条答题记录、38,207 条错题和 458 条权益。压测 worker 是无停顿请求流,不能直接等同于真实在线学生数;前端完成后需要用真实页面埋点估算单个学生平均 RPS,再折算在线容量。
|
最近一次本地真实迁移库数据规模约为 74,117 题、1,601 个题目合集、3,106 个练习蓝图、3,505 个单词、2,678 条知识手册、3,690 个用户、113,810 条答题记录、38,207 条错题和 458 条权益。压测 worker 是无停顿请求流,不能直接等同于真实在线学生数;前端完成后需要用真实页面埋点估算单个学生平均 RPS,再折算在线容量。
|
||||||
|
|
||||||
| 并发 worker | 时长 | 刷题写入比例 | 请求数 | 错误率 | 吞吐 | P95 | P99 |
|
| 并发 worker | 时长 | 刷题写入比例 | 请求数 | 错误率 | 吞吐 | P95 | P99 |
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
- `apps/taro/src/services/api.ts` 现在默认 Supabase JWT 优先、迁移期 `tk_` 兜底;公共接口必须显式 `authMode='none'`。页面不要手写 `Authorization`、`x-tenant-id` 或 `x-user-id`。
|
- `apps/taro/src/services/api.ts` 现在默认 Supabase JWT 优先、迁移期 `tk_` 兜底;公共接口必须显式 `authMode='none'`。页面不要手写 `Authorization`、`x-tenant-id` 或 `x-user-id`。
|
||||||
- H5 可以优先验证 `@supabase/supabase-js` 管理 Auth session;微信小程序端先验证运行时兼容性,业务数据默认仍走 `apps/api`。
|
- H5 可以优先验证 `@supabase/supabase-js` 管理 Auth session;微信小程序端先验证运行时兼容性,业务数据默认仍走 `apps/api`。
|
||||||
- H5 生产部署优先用每个静态目录自己的 `runtime-config.json` 配置 `apiBaseUrl`、`supabaseUrl`、`supabasePublishableKey`、`tenantCode`;不要为了换域名重打包,也不要把任何 service role、数据库、支付、短信、对象存储密钥放进该文件。
|
- H5 生产部署优先用每个静态目录自己的 `runtime-config.json` 配置 `apiBaseUrl`、`supabaseUrl`、`supabasePublishableKey`、`tenantCode`;不要为了换域名重打包,也不要把任何 service role、数据库、支付、短信、对象存储密钥放进该文件。
|
||||||
- 上线前需要把三套 H5 构建、`runtime-config.json` 人工复核、真实 Auth/RLS、迁移 dry-run、对象存储、支付对账和 `@codex-security` 结果写入 `production-launch-evidence.json`,并通过 `npm run launch:gate`。
|
- 上线前需要把三套 H5 构建、`npm run smoke:taro:h5` 静态启动烟测、严格 `taro-h5-release-guardrails-test --require-runtime-config`、`runtime-config.json` 人工复核、真实 Auth/RLS、迁移 dry-run、对象存储、支付对账和真实 `@codex-security` 结果写入 `production-launch-evidence.json`,并通过 `npm run launch:gate`。当前环境没有暴露安全扫描工具时只能标记待补,不能把模板占位当完成。
|
||||||
- 可以接入租户品牌、已发布主题、公开素材、功能开关和域名/小程序参数解析;学生端只读 `/api/tenant/resolve` 的 `branding.theme/publicAssets`,租户后台草稿走 `/api/tenant-admin/theme`。
|
- 可以接入租户品牌、已发布主题、公开素材、功能开关和域名/小程序参数解析;学生端只读 `/api/tenant/resolve` 的 `branding.theme/publicAssets`,租户后台草稿走 `/api/tenant-admin/theme`。
|
||||||
- 租户后台可以接入角色模板和成员 API:`/api/tenant-admin/role-templates`、`/api/tenant-admin/members`,用于运营、教师、销售、代理等自定义菜单/模块/字段可见性和成员模板绑定。
|
- 租户后台可以接入角色模板和成员 API:`/api/tenant-admin/role-templates`、`/api/tenant-admin/members`,用于运营、教师、销售、代理等自定义菜单/模块/字段可见性和成员模板绑定。
|
||||||
- 租户后台可以接入勋章管理、手动发放、积分任务、每日签到奖励、积分兑换、积分风控只读报表和反馈运营报表:`GET/PUT /api/tenant-admin/badges`、`GET/POST /api/tenant-admin/badge-grants`、`GET/PUT /api/tenant-admin/point-activity-tasks`、`GET /api/tenant-admin/point-activity-claims`、`GET/PUT /api/tenant-admin/point-exchange-items`、`GET /api/tenant-admin/point-exchange-orders`、`GET /api/tenant-admin/points-risk-report`、`GET /api/tenant-admin/feedbacks/report`;学生端用 `GET /api/profile/badges` 展示成就,通过 `POST /api/profile/check-in` 自动领取 `daily_check_in` 任务奖励,并通过 `GET /api/profile/activity-tasks`、`POST /api/profile/activity-tasks/claim`、`GET /api/profile/exchange-items`、`POST /api/profile/exchange-items/redeem` 接其它积分活动和兑换。
|
- 租户后台可以接入勋章管理、手动发放、积分任务、每日签到奖励、积分兑换、积分风控只读报表和反馈运营报表:`GET/PUT /api/tenant-admin/badges`、`GET/POST /api/tenant-admin/badge-grants`、`GET/PUT /api/tenant-admin/point-activity-tasks`、`GET /api/tenant-admin/point-activity-claims`、`GET/PUT /api/tenant-admin/point-exchange-items`、`GET /api/tenant-admin/point-exchange-orders`、`GET /api/tenant-admin/points-risk-report`、`GET /api/tenant-admin/feedbacks/report`;学生端用 `GET /api/profile/badges` 展示成就,通过 `POST /api/profile/check-in` 自动领取 `daily_check_in` 任务奖励,并通过 `GET /api/profile/activity-tasks`、`POST /api/profile/activity-tasks/claim`、`GET /api/profile/exchange-items`、`POST /api/profile/exchange-items/redeem` 接其它积分活动和兑换。
|
||||||
|
|||||||
@@ -265,7 +265,7 @@
|
|||||||
2. 继续补 Taro 学生端旧体验:地区选择、刷题答题卡、后端权威断点续练、本地进度恢复、模拟倒计时、主观题后端自评、阅读理解/案例分析多小题、视频播放、反馈、模考报告、逐题复盘、错题/收藏专题、个人中心学习报告、男女预设头像选择、收银台、订单详情、售后入口、独立消息中心、积分任务/兑换/积分明细、题干/解析/知识手册 RichContent 安全渲染、知识手册章节内搜索/安全摘要高亮/目录定位、H5 KaTeX 公式渲染、私有资源 ID 题图短签名、背单词学习概览/卡片学习/发音/收藏练习、资料短签名水印预览/下载确认已接第一版;继续补小程序公式真机验收、题图资源字段化、小程序支付容器、分享场景和状态管理。前端不得实现头像上传、头像裁剪或第三方头像同步。
|
2. 继续补 Taro 学生端旧体验:地区选择、刷题答题卡、后端权威断点续练、本地进度恢复、模拟倒计时、主观题后端自评、阅读理解/案例分析多小题、视频播放、反馈、模考报告、逐题复盘、错题/收藏专题、个人中心学习报告、男女预设头像选择、收银台、订单详情、售后入口、独立消息中心、积分任务/兑换/积分明细、题干/解析/知识手册 RichContent 安全渲染、知识手册章节内搜索/安全摘要高亮/目录定位、H5 KaTeX 公式渲染、私有资源 ID 题图短签名、背单词学习概览/卡片学习/发音/收藏练习、资料短签名水印预览/下载确认已接第一版;继续补小程序公式真机验收、题图资源字段化、小程序支付容器、分享场景和状态管理。前端不得实现头像上传、头像裁剪或第三方头像同步。
|
||||||
3. 补平台后台增强:租户基础资料编辑增强、平台审计告警升级策略、平台催缴通知配置操作台细节和平台在线收款。
|
3. 补平台后台增强:租户基础资料编辑增强、平台审计告警升级策略、平台催缴通知配置操作台细节和平台在线收款。
|
||||||
4. 云服务器部署 Supabase/PostgreSQL 和 API,配置对象存储生产环境变量,跑 `check:refactor` 的远程等价测试。
|
4. 云服务器部署 Supabase/PostgreSQL 和 API,配置对象存储生产环境变量,跑 `check:refactor` 的远程等价测试。
|
||||||
5. 三套 H5 上云前必须在目标目录补真实公开 `runtime-config.json`,执行 `node scripts/taro-h5-release-guardrails-test.js --require-dist`、人工打开学生端/租户后台/平台后台域名,确认入口、租户解析、登录态和 `Authorization + x-tenant-id` 请求正常。当前构建仍有 webpack 体积 warning,后续做首屏拆包、按入口拆页面和 Supabase client 引入优化。
|
5. 三套 H5 上云前必须在目标目录补真实公开 `runtime-config.json`,先执行 `npm run smoke:taro:h5` 和 `node scripts/taro-h5-release-guardrails-test.js --require-dist` 做本地发布目录烟测;写入生产上线证据时必须执行 `npm --silent run smoke:taro:h5 -- --json` 和 `node scripts/taro-h5-release-guardrails-test.js --require-dist --require-runtime-config --json`,确保 warning 为 0。随后人工打开学生端/租户后台/平台后台域名,确认入口、租户解析、登录态和 `Authorization + x-tenant-id` 请求正常。当前构建仍有 webpack 体积 warning,后续做首屏拆包、按入口拆页面和 Supabase client 引入优化。
|
||||||
6. 使用 `F:\project\参考\旧题库数据库文件` 中的真实 PocketBase 数据,按 `docs/refactor/pocketbase-real-data-migration-runbook.md` 继续做人工复核和抽样验收;当前 dry-run/导入/校验链路已跑通,下一步重点是 7 个已支付缺用户订单、22 个待复核手册章节、2533 道旧分类缺失题目和 5298 条引用已删除题目的答题记录的运营处理结论。导入过程中发现的字段污染、跨集合引用断裂、敏感字段和旧权限问题都要沉淀到 importer mapper 或修复脚本,不手工临时修库。
|
6. 使用 `F:\project\参考\旧题库数据库文件` 中的真实 PocketBase 数据,按 `docs/refactor/pocketbase-real-data-migration-runbook.md` 继续做人工复核和抽样验收;当前 dry-run/导入/校验链路已跑通,下一步重点是 7 个已支付缺用户订单、22 个待复核手册章节、2533 道旧分类缺失题目和 5298 条引用已删除题目的答题记录的运营处理结论。导入过程中发现的字段污染、跨集合引用断裂、敏感字段和旧权限问题都要沉淀到 importer mapper 或修复脚本,不手工临时修库。
|
||||||
7. 并行补真实登录、真实生产账单格式验收、异常订单运营台、对象存储真实 AV/内容安全服务联调、转码/CDN 级水印/生命周期、题库导出模板精排/操作台、公共题库生产定时调度和失败告警。
|
7. 并行补真实登录、真实生产账单格式验收、异常订单运营台、对象存储真实 AV/内容安全服务联调、转码/CDN 级水印/生命周期、题库导出模板精排/操作台、公共题库生产定时调度和失败告警。
|
||||||
8. 前后端联调通过后,再做支付、权限、数据导入、资料下载、视频播放的商用验收。
|
8. 前后端联调通过后,再做支付、权限、数据导入、资料下载、视频播放的商用验收。
|
||||||
|
|||||||
@@ -213,6 +213,29 @@
|
|||||||
"failed": 0
|
"failed": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "taro.h5-static-smoke",
|
||||||
|
"status": "pass",
|
||||||
|
"command": "npm --silent run smoke:taro:h5 -- --json > docs/refactor/launch-artifacts/taro-h5-static-smoke.json",
|
||||||
|
"completedAt": "2026-06-30T12:12:00+08:00",
|
||||||
|
"artifact": "launch-artifacts/taro-h5-static-smoke.json",
|
||||||
|
"summary": {
|
||||||
|
"fail": 0,
|
||||||
|
"portals": 3,
|
||||||
|
"tenantResolveRequests": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "taro.h5-release-guardrails",
|
||||||
|
"status": "pass",
|
||||||
|
"command": "node scripts/taro-h5-release-guardrails-test.js --require-dist --require-runtime-config --json > docs/refactor/launch-artifacts/taro-h5-release-guardrails.json",
|
||||||
|
"completedAt": "2026-06-30T12:15:00+08:00",
|
||||||
|
"artifact": "launch-artifacts/taro-h5-release-guardrails.json",
|
||||||
|
"summary": {
|
||||||
|
"fail": 0,
|
||||||
|
"warn": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "audit.runtime",
|
"id": "audit.runtime",
|
||||||
"status": "pass",
|
"status": "pass",
|
||||||
@@ -226,13 +249,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "security.codex-scan",
|
"id": "security.codex-scan",
|
||||||
"status": "pass",
|
"status": "replace-with-pass-after-real-scan",
|
||||||
"command": "@codex-security scan; save findings to docs/refactor/launch-artifacts/codex-security.md",
|
"command": "Run the real @codex-security scan only when the tool is exposed; save findings to docs/refactor/launch-artifacts/codex-security.md",
|
||||||
"completedAt": "2026-06-30T12:30:00+08:00",
|
"completedAt": "replace-with-real-scan-time",
|
||||||
"artifact": "launch-artifacts/codex-security.md",
|
"artifact": "launch-artifacts/codex-security.md",
|
||||||
"summary": {
|
"summary": {
|
||||||
"critical": 0,
|
"critical": "replace-with-number",
|
||||||
"high": 0
|
"high": "replace-with-number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -197,6 +197,13 @@ H5 正式回归时建议把前端登录态切到 Supabase Auth,并观察业务
|
|||||||
|
|
||||||
`smoke:taro:h5` 会用临时静态服务器检查三套 H5 产物可托管、资源可加载、history fallback 可用,并用 mock API 验证租户解析契约。发布守卫会检查三套 H5 产物是否存在 `index.html`,源码和产物是否混入 `x-user-id`、`x-platform-admin-key`、PocketBase 引用、数据库连接串、服务端密钥形态,并检查运行时配置示例只包含公开字段。若还没有把真实 `runtime-config.json` 放入静态目录,会显示 warning;正式发布前必须在每个 H5 目录根部补齐该文件。
|
`smoke:taro:h5` 会用临时静态服务器检查三套 H5 产物可托管、资源可加载、history fallback 可用,并用 mock API 验证租户解析契约。发布守卫会检查三套 H5 产物是否存在 `index.html`,源码和产物是否混入 `x-user-id`、`x-platform-admin-key`、PocketBase 引用、数据库连接串、服务端密钥形态,并检查运行时配置示例只包含公开字段。若还没有把真实 `runtime-config.json` 放入静态目录,会显示 warning;正式发布前必须在每个 H5 目录根部补齐该文件。
|
||||||
|
|
||||||
|
写入 `production-launch-evidence.json` 的正式证据必须使用严格模式,确保三套发布目录都已放置真实公开 `runtime-config.json` 且没有 warning:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm --silent run smoke:taro:h5 -- --json > docs/refactor/launch-artifacts/taro-h5-static-smoke.json
|
||||||
|
node scripts/taro-h5-release-guardrails-test.js --require-dist --require-runtime-config --json > docs/refactor/launch-artifacts/taro-h5-release-guardrails.json
|
||||||
|
```
|
||||||
|
|
||||||
7. 收集生产上线证据并运行 launch gate:
|
7. 收集生产上线证据并运行 launch gate:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -88,6 +88,13 @@ npm run smoke:launch-persona
|
|||||||
|
|
||||||
`smoke:taro:h5` 会启动临时静态服务器和 mock API,验证三套 H5 的 `index.html`、JS/CSS 资源、history fallback、公开 runtime config 和 `/api/tenant/resolve` 契约。它是发布目录级启动烟测,不替代真实浏览器/真机点击验收。`taro-h5-release-guardrails-test` 会扫描源码、三套 H5 产物和 runtime-config 边界,防止旧 PocketBase、`x-user-id`、`x-platform-admin-key`、数据库连接串和服务端密钥形态进入前端发布目录。若刚构建完但未放入真实 `runtime-config.json`,脚本允许 warning;正式部署目录必须补齐。
|
`smoke:taro:h5` 会启动临时静态服务器和 mock API,验证三套 H5 的 `index.html`、JS/CSS 资源、history fallback、公开 runtime config 和 `/api/tenant/resolve` 契约。它是发布目录级启动烟测,不替代真实浏览器/真机点击验收。`taro-h5-release-guardrails-test` 会扫描源码、三套 H5 产物和 runtime-config 边界,防止旧 PocketBase、`x-user-id`、`x-platform-admin-key`、数据库连接串和服务端密钥形态进入前端发布目录。若刚构建完但未放入真实 `runtime-config.json`,脚本允许 warning;正式部署目录必须补齐。
|
||||||
|
|
||||||
|
写入生产上线证据时,三套正式发布目录必须先放入真实公开 `runtime-config.json`,再运行严格模式:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm --silent run smoke:taro:h5 -- --json > docs/refactor/launch-artifacts/taro-h5-static-smoke.json
|
||||||
|
node scripts/taro-h5-release-guardrails-test.js --require-dist --require-runtime-config --json > docs/refactor/launch-artifacts/taro-h5-release-guardrails.json
|
||||||
|
```
|
||||||
|
|
||||||
如 `@codex-security` 插件在当前 Codex 环境暴露扫描工具,再补插件扫描结果。若工具不可用,不能把该项标记为已完成,只能在上线证据里标记为待补。
|
如 `@codex-security` 插件在当前 Codex 环境暴露扫描工具,再补插件扫描结果。若工具不可用,不能把该项标记为已完成,只能在上线证据里标记为待补。
|
||||||
|
|
||||||
生产 API 推荐:
|
生产 API 推荐:
|
||||||
|
|||||||
@@ -143,6 +143,17 @@ assert.ok(
|
|||||||
'missing import business sampling should be reported as a blocker',
|
'missing import business sampling should be reported as a blocker',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const missingTaroStaticSmoke = runGate(tempDir => {
|
||||||
|
const evidence = createEvidence(tempDir);
|
||||||
|
evidence.checks = evidence.checks.filter(item => item.id !== 'taro.h5-static-smoke');
|
||||||
|
return evidence;
|
||||||
|
});
|
||||||
|
assert.notEqual(missingTaroStaticSmoke.status, 0, 'missing H5 static smoke should fail launch gate');
|
||||||
|
assert.ok(
|
||||||
|
missingTaroStaticSmoke.payload.checks?.some(item => item.id === 'check.taro.h5-static-smoke' && item.status === 'blocker'),
|
||||||
|
'missing H5 static smoke should be reported as a blocker',
|
||||||
|
);
|
||||||
|
|
||||||
const missingAttestation = runGate(tempDir => {
|
const missingAttestation = runGate(tempDir => {
|
||||||
const evidence = createEvidence(tempDir);
|
const evidence = createEvidence(tempDir);
|
||||||
evidence.attestations = evidence.attestations.filter(item => item.id !== 'backup.snapshot');
|
evidence.attestations = evidence.attestations.filter(item => item.id !== 'backup.snapshot');
|
||||||
|
|||||||
@@ -124,6 +124,25 @@ const gateChecks = [
|
|||||||
commandIncludes: 'build:taro:h5:platform',
|
commandIncludes: 'build:taro:h5:platform',
|
||||||
summary: { failed: 0 },
|
summary: { failed: 0 },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'taro.h5-static-smoke',
|
||||||
|
label: 'Taro H5 static startup smoke',
|
||||||
|
commandIncludes: 'smoke:taro:h5',
|
||||||
|
summary: {
|
||||||
|
fail: 0,
|
||||||
|
portals: 3,
|
||||||
|
tenantResolveRequests: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'taro.h5-release-guardrails',
|
||||||
|
label: 'Taro H5 release artifact guardrails',
|
||||||
|
commandIncludes: 'taro-h5-release-guardrails-test.js',
|
||||||
|
summary: {
|
||||||
|
fail: 0,
|
||||||
|
warn: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'audit.runtime',
|
id: 'audit.runtime',
|
||||||
label: 'Runtime dependency audit',
|
label: 'Runtime dependency audit',
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ const portals = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
return {
|
||||||
|
json: argv.includes('--json'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const allowedRuntimeConfigKeys = new Set([
|
const allowedRuntimeConfigKeys = new Set([
|
||||||
'portal',
|
'portal',
|
||||||
'apiBaseUrl',
|
'apiBaseUrl',
|
||||||
@@ -363,6 +369,7 @@ function summarize(results) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
const options = parseArgs(process.argv.slice(2));
|
||||||
const api = await createMockApiServer();
|
const api = await createMockApiServer();
|
||||||
try {
|
try {
|
||||||
const results = [];
|
const results = [];
|
||||||
@@ -370,21 +377,47 @@ async function main() {
|
|||||||
results.push(await smokePortal(portal, api));
|
results.push(await smokePortal(portal, api));
|
||||||
}
|
}
|
||||||
const summary = summarize(results);
|
const summary = summarize(results);
|
||||||
console.log(`Taro H5 static smoke: ${summary.fail} fail(s), ${summary.pass} pass(es)`);
|
|
||||||
for (const result of results) {
|
|
||||||
console.log(`[PORTAL] ${result.portal} ${result.staticBaseUrl} -> ${result.landingPath}`);
|
|
||||||
for (const check of result.checks) {
|
|
||||||
console.log(`[${check.ok ? 'PASS' : 'FAIL'}] ${check.id}: ${check.detail}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const resolveRequests = api.requests.filter(item => item.path === '/api/tenant/resolve');
|
const resolveRequests = api.requests.filter(item => item.path === '/api/tenant/resolve');
|
||||||
console.log(`[MOCK API] tenant resolve requests=${resolveRequests.length}`);
|
const payload = {
|
||||||
|
summary: {
|
||||||
|
fail: summary.fail + (resolveRequests.length === portals.length ? 0 : 1),
|
||||||
|
pass: summary.pass + (resolveRequests.length === portals.length ? 1 : 0),
|
||||||
|
portals: portals.length,
|
||||||
|
tenantResolveRequests: resolveRequests.length,
|
||||||
|
},
|
||||||
|
portals: results,
|
||||||
|
checks: [
|
||||||
|
...summary.checks.map(item => ({
|
||||||
|
status: item.ok ? 'pass' : 'fail',
|
||||||
|
id: item.id,
|
||||||
|
detail: item.detail,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
status: resolveRequests.length === portals.length ? 'pass' : 'fail',
|
||||||
|
id: 'mock_api.tenant_resolve_count',
|
||||||
|
detail: `expected=${portals.length} actual=${resolveRequests.length}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.json) {
|
||||||
|
console.log(JSON.stringify(payload, null, 2));
|
||||||
|
} else {
|
||||||
|
console.log(`Taro H5 static smoke: ${payload.summary.fail} fail(s), ${payload.summary.pass} pass(es)`);
|
||||||
|
for (const result of results) {
|
||||||
|
console.log(`[PORTAL] ${result.portal} ${result.staticBaseUrl} -> ${result.landingPath}`);
|
||||||
|
for (const check of result.checks) {
|
||||||
|
console.log(`[${check.ok ? 'PASS' : 'FAIL'}] ${check.id}: ${check.detail}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`[MOCK API] tenant resolve requests=${resolveRequests.length}`);
|
||||||
|
}
|
||||||
if (resolveRequests.length !== portals.length) {
|
if (resolveRequests.length !== portals.length) {
|
||||||
console.log(`[FAIL] mock_api.tenant_resolve_count: expected ${portals.length}, got ${resolveRequests.length}`);
|
if (!options.json) console.log(`[FAIL] mock_api.tenant_resolve_count: expected ${portals.length}, got ${resolveRequests.length}`);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (summary.fail > 0) process.exitCode = 1;
|
if (payload.summary.fail > 0) process.exitCode = 1;
|
||||||
} finally {
|
} finally {
|
||||||
await api.close();
|
await api.close();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user