@oj-module/cli 0.2.1 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/devkit/README.md +14 -3
  2. package/devkit/SKILL.md +16 -4
  3. package/devkit/framework-dev.md +8 -6
  4. package/devkit/manual.md +243 -23
  5. package/package.json +2 -2
  6. package/shell-dist/assets/runtime.js +143 -8
  7. package/shell-dist/index.html +2 -2
  8. package/shell-dist/versions.json +3 -3
  9. package/src/args.ts +67 -0
  10. package/src/build.ts +11 -3
  11. package/src/bundle.ts +197 -0
  12. package/src/contract/__tests__/handler-scan.test.ts +82 -0
  13. package/src/contract/check.ts +2 -1
  14. package/src/contract/handler-scan.ts +32 -0
  15. package/src/deterministic-tar.ts +120 -0
  16. package/src/dev.ts +4 -2
  17. package/src/enable.ts +28 -0
  18. package/src/index.ts +63 -2
  19. package/src/info.ts +3 -2
  20. package/src/init.ts +147 -14
  21. package/src/install.ts +268 -0
  22. package/src/keygen.ts +88 -0
  23. package/src/layout.ts +31 -0
  24. package/src/lifecycle/__tests__/authoring.test.ts +109 -0
  25. package/src/lifecycle/__tests__/bundle.test.ts +150 -0
  26. package/src/lifecycle/__tests__/deterministic-tar.test.ts +145 -0
  27. package/src/lifecycle/__tests__/fixtures/README.md +59 -0
  28. package/src/lifecycle/__tests__/fixtures/fixture-invalid/EXPECTED-VIOLATIONS.md +27 -0
  29. package/src/lifecycle/__tests__/fixtures/fixture-invalid/api/src/Demo/manifest.yaml +6 -0
  30. package/src/lifecycle/__tests__/fixtures/fixture-invalid/manifest.yaml +40 -0
  31. package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/entry.ts +14 -0
  32. package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/locales/en-US.json +12 -0
  33. package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/locales/zh-CN.json +12 -0
  34. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +9 -0
  35. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +6 -0
  36. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/ping/api.ts +9 -0
  37. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +9 -0
  38. package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +96 -0
  39. package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/entry.ts +23 -0
  40. package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/en-US.json +29 -0
  41. package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/zh-CN.json +29 -0
  42. package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz +0 -0
  43. package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.bad.sig +7 -0
  44. package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.sig +7 -0
  45. package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.unknown.sig +7 -0
  46. package/src/lifecycle/__tests__/fixtures/sig/gen-signed-fixtures.d.mts +21 -0
  47. package/src/lifecycle/__tests__/fixtures/sig/gen-signed-fixtures.mjs +196 -0
  48. package/src/lifecycle/__tests__/helpers.ts +78 -0
  49. package/src/lifecycle/__tests__/install.test.ts +514 -0
  50. package/src/lifecycle/__tests__/keygen.test.ts +61 -0
  51. package/src/lifecycle/__tests__/manifest-contract.test.ts +228 -0
  52. package/src/lifecycle/__tests__/publish.test.ts +80 -0
  53. package/src/lifecycle/__tests__/sig-fixtures.test.ts +100 -0
  54. package/src/lifecycle/__tests__/sign.test.ts +131 -0
  55. package/src/lifecycle/__tests__/smoke.test.ts +61 -0
  56. package/src/lifecycle/__tests__/trust.test.ts +177 -0
  57. package/src/lifecycle/__tests__/uninstall.test.ts +87 -0
  58. package/src/lifecycle/keygen.ts +40 -0
  59. package/src/lifecycle/sign.ts +74 -0
  60. package/src/manifest-contract.ts +696 -0
  61. package/src/oj-cert.ts +19 -0
  62. package/src/oj-cmd.ts +3 -3
  63. package/src/preview.ts +2 -2
  64. package/src/publish.ts +150 -0
  65. package/src/sign.ts +128 -0
  66. package/src/site.ts +146 -0
  67. package/src/trust.ts +116 -0
  68. package/src/uninstall.ts +29 -0
  69. package/src/usage.ts +35 -1
  70. package/templates/README.md +46 -7
  71. package/templates/api/config.yaml +47 -0
  72. package/templates/api/seed.mysql.sql +1 -1
  73. package/templates/api/seed.pg.sql +1 -1
  74. package/templates/api/src/_platform/_lifecycle/atomic.ts +98 -0
  75. package/templates/api/src/_platform/_lifecycle/history.ts +31 -0
  76. package/templates/api/src/_platform/_lifecycle/installer.ts +1080 -0
  77. package/templates/api/src/_platform/_lifecycle/lock.ts +148 -0
  78. package/templates/api/src/_platform/_lifecycle/semver.ts +61 -0
  79. package/templates/api/src/_platform/_lifecycle/sha384.ts +187 -0
  80. package/templates/api/src/_platform/_lifecycle/state.ts +256 -0
  81. package/templates/api/src/_platform/_lifecycle/steps/deploy-web.ts +182 -0
  82. package/templates/api/src/_platform/_lifecycle/steps/download.ts +90 -0
  83. package/templates/api/src/_platform/_lifecycle/steps/extract.ts +239 -0
  84. package/templates/api/src/_platform/_lifecycle/steps/migrate.ts +363 -0
  85. package/templates/api/src/_platform/_lifecycle/steps/preflight.ts +590 -0
  86. package/templates/api/src/_platform/_lifecycle/steps/register.ts +211 -0
  87. package/templates/api/src/_platform/_lifecycle/steps/reload.ts +81 -0
  88. package/templates/api/src/_platform/_lifecycle/steps/smoke.ts +216 -0
  89. package/templates/api/src/_platform/_lifecycle/steps/verify.ts +207 -0
  90. package/templates/api/src/_platform/_lifecycle/tasks.ts +119 -0
  91. package/templates/api/src/_platform/_lifecycle/text-edit.ts +110 -0
  92. package/templates/api/src/_platform/_lifecycle/tgz-guard.ts +41 -0
  93. package/templates/api/src/_platform/_lifecycle/trust.ts +229 -0
  94. package/templates/api/src/_platform/_lifecycle/types.ts +94 -0
  95. package/templates/api/src/_platform/_lifecycle/uninstall.ts +283 -0
  96. package/templates/api/src/_platform/_lifecycle/verify-checks.ts +228 -0
  97. package/templates/api/src/_platform/_lifecycle/verify-report.ts +92 -0
  98. package/templates/api/src/_platform/_shared/service/access.ts +2 -2
  99. package/templates/api/src/_platform/_shared/service/settings.ts +10 -1
  100. package/templates/api/src/_platform/_shared/service/solution-perms.ts +14 -7
  101. package/templates/api/src/_platform/_shared/site-facade.ts +44 -0
  102. package/templates/api/src/_platform/manifest.yaml +1 -0
  103. package/templates/api/src/_platform/market/api.ts +883 -0
  104. package/templates/api/src/_platform/market/bootstrap.ts +39 -0
  105. package/templates/api/src/_platform/migrations/0010__sys_solution_route.mysql.sql +17 -0
  106. package/templates/api/src/_platform/migrations/0010__sys_solution_route.pg.sql +16 -0
  107. package/templates/api/src/_platform/migrations/0010__sys_solution_route.sql +18 -0
  108. package/templates/api/src/_platform/migrations/0011__sys_setting_group_label.mysql.sql +2 -0
  109. package/templates/api/src/_platform/migrations/0011__sys_setting_group_label.sql +4 -0
  110. package/templates/api/src/_platform/migrations/0012__market_history.mysql.sql +13 -0
  111. package/templates/api/src/_platform/migrations/0012__market_history.pg.sql +12 -0
  112. package/templates/api/src/_platform/migrations/0012__market_history.sql +14 -0
  113. package/templates/api/src/_platform/schema.yaml +21 -4
  114. package/templates/api/src/_platform/seed.sql +2 -2
  115. package/templates/api/src/_platform/solutions/api.ts +212 -0
  116. package/templates/api/src/demo/manifest.yaml +5 -0
  117. package/templates/api/src/demo/migrations/0001__create_todos.sql +9 -0
  118. package/templates/api/src/demo/schema.yaml +11 -0
  119. package/templates/api/src/demo/seed.sql +5 -0
  120. package/templates/api/src/demo/todos/api.ts +59 -0
  121. package/templates/api/src/home/line/api.ts +57 -0
  122. package/templates/api/src/home/manifest.yaml +6 -0
  123. package/templates/api/src/home/migrations/0001__create_home_tables.sql +13 -0
  124. package/templates/api/src/home/pie/api.ts +40 -0
  125. package/templates/api/src/home/schema.yaml +14 -0
  126. package/templates/api/src/home/seed.sql +70 -0
  127. package/templates/api/src/system/oplog/api.ts +44 -2
  128. package/templates/api/tests/fixture.ts +16 -0
  129. package/templates/api/tests/lifecycle/atomic-state.spec.ts +183 -0
  130. package/templates/api/tests/lifecycle/bootstrap.spec.ts +116 -0
  131. package/templates/api/tests/lifecycle/deploy-smoke.spec.ts +492 -0
  132. package/templates/api/tests/lifecycle/installer.spec.ts +1218 -0
  133. package/templates/api/tests/lifecycle/market-api.spec.ts +971 -0
  134. package/templates/api/tests/lifecycle/migrate.spec.ts +575 -0
  135. package/templates/api/tests/lifecycle/preflight.spec.ts +255 -0
  136. package/templates/api/tests/lifecycle/register.spec.ts +194 -0
  137. package/templates/api/tests/lifecycle/semver.spec.ts +43 -0
  138. package/templates/api/tests/lifecycle/sha384.spec.ts +48 -0
  139. package/templates/api/tests/lifecycle/sig-fixtures.spec.ts +65 -0
  140. package/templates/api/tests/lifecycle/solutions-api.spec.ts +98 -0
  141. package/templates/api/tests/lifecycle/steps-fs.spec.ts +623 -0
  142. package/templates/api/tests/lifecycle/text-edit.spec.ts +177 -0
  143. package/templates/api/tests/lifecycle/tgz-guard.spec.ts +103 -0
  144. package/templates/api/tests/lifecycle/trust.spec.ts +310 -0
  145. package/templates/api/tests/lifecycle/uninstall.spec.ts +386 -0
  146. package/templates/api/tests/lifecycle/verify-checks.spec.ts +385 -0
  147. package/templates/api/tests/lifecycle/verify-report.spec.ts +290 -0
  148. package/templates/api/tests/rbac-settings.spec.ts +8 -0
  149. package/templates/init.include.yaml +56 -0
  150. package/templates/openapi.json +527 -0
  151. package/templates/package.json +52 -0
  152. package/templates/web/src/_platform/__client/api.schemas.ts +8 -0
  153. package/templates/web/src/_platform/__client/api.ts +335 -0
  154. package/templates/web/src/about/entry.ts +42 -0
  155. package/templates/web/src/about/locales/en-US.json +16 -0
  156. package/templates/web/src/about/locales/zh-CN.json +16 -0
  157. package/templates/web/src/about/pages/constants.ts +20 -0
  158. package/templates/web/src/about/pages/index.tsx +135 -0
  159. package/templates/web/src/access/entry.ts +74 -0
  160. package/templates/web/src/access/locales/en-US.json +46 -0
  161. package/templates/web/src/access/locales/zh-CN.json +46 -0
  162. package/templates/web/src/access/pages/access-mode/index.tsx +16 -0
  163. package/templates/web/src/access/pages/admin-visible/index.tsx +16 -0
  164. package/templates/web/src/access/pages/button-control/index.tsx +181 -0
  165. package/templates/web/src/access/pages/common-visible/index.tsx +16 -0
  166. package/templates/web/src/access/pages/page-control/index.tsx +142 -0
  167. package/templates/web/src/demo/__client/api.schemas.ts +20 -0
  168. package/templates/web/src/demo/__client/api.ts +89 -0
  169. package/templates/web/src/exception/entry.ts +80 -0
  170. package/templates/web/src/exception/locales/en-US.json +13 -0
  171. package/templates/web/src/exception/locales/zh-CN.json +13 -0
  172. package/templates/web/src/exception/pages/403/index.tsx +30 -0
  173. package/templates/web/src/exception/pages/404/index.tsx +30 -0
  174. package/templates/web/src/exception/pages/500/index.tsx +30 -0
  175. package/templates/web/src/exception/pages/unknown-component/index.tsx +38 -0
  176. package/templates/web/src/home/__client/api.schemas.ts +22 -0
  177. package/templates/web/src/home/__client/api.ts +113 -0
  178. package/templates/web/src/login/locales/en-US.json +6 -0
  179. package/templates/web/src/login/locales/zh-CN.json +6 -0
  180. package/templates/web/src/login/pages/login.tsx +67 -18
  181. package/templates/web/src/oj-market/__tests__/CardGrid.test.tsx +93 -0
  182. package/templates/web/src/oj-market/__tests__/HistoryDrawer.test.tsx +28 -0
  183. package/templates/web/src/oj-market/__tests__/InstallDialog.test.tsx +69 -0
  184. package/templates/web/src/oj-market/__tests__/RollbackDialog.test.tsx +43 -0
  185. package/templates/web/src/oj-market/__tests__/UninstallConfirm.test.tsx +30 -0
  186. package/templates/web/src/oj-market/__tests__/marketClient.fake.ts +70 -0
  187. package/templates/web/src/oj-market/__tests__/poll.test.ts +24 -0
  188. package/templates/web/src/oj-market/__tests__/render.tsx +14 -0
  189. package/templates/web/src/oj-market/__tests__/runtime-stub.ts +14 -0
  190. package/templates/web/src/oj-market/__tests__/semver.test.ts +26 -0
  191. package/templates/web/src/oj-market/api/MarketClientProvider.tsx +12 -0
  192. package/templates/web/src/oj-market/api/client.ts +135 -0
  193. package/templates/web/src/oj-market/api/context.ts +11 -0
  194. package/templates/web/src/oj-market/api/defaultClient.ts +7 -0
  195. package/templates/web/src/oj-market/components/CardGrid.tsx +76 -0
  196. package/templates/web/src/oj-market/components/CardItem.tsx +102 -0
  197. package/templates/web/src/oj-market/components/DetailDrawer.tsx +53 -0
  198. package/templates/web/src/oj-market/components/HistoryDrawer.tsx +75 -0
  199. package/templates/web/src/oj-market/components/InstallDialog.tsx +112 -0
  200. package/templates/web/src/oj-market/components/RollbackDialog.tsx +79 -0
  201. package/templates/web/src/oj-market/components/UninstallConfirm.tsx +74 -0
  202. package/templates/web/src/oj-market/entry.ts +27 -0
  203. package/templates/web/src/oj-market/hooks/useMarket.ts +91 -0
  204. package/templates/web/src/oj-market/locales/en-US.json +100 -0
  205. package/templates/web/src/oj-market/locales/zh-CN.json +100 -0
  206. package/templates/web/src/oj-market/pages/MarketApp.tsx +136 -0
  207. package/templates/web/src/oj-market/types.ts +84 -0
  208. package/templates/web/src/oj-market/utils/poll.ts +36 -0
  209. package/templates/web/src/oj-market/utils/semver.ts +14 -0
  210. package/templates/web/src/outside/entry.ts +80 -0
  211. package/templates/web/src/outside/locales/en-US.json +10 -0
  212. package/templates/web/src/outside/locales/zh-CN.json +10 -0
  213. package/templates/web/src/personal-center/entry.ts +1 -10
  214. package/templates/web/src/personal-center/locales/en-US.json +1 -5
  215. package/templates/web/src/personal-center/locales/zh-CN.json +1 -5
  216. package/templates/web/src/route-nest/entry.ts +73 -0
  217. package/templates/web/src/route-nest/locales/en-US.json +9 -0
  218. package/templates/web/src/route-nest/locales/zh-CN.json +9 -0
  219. package/templates/web/src/route-nest/pages/menu1/menu1-1/index.tsx +5 -0
  220. package/templates/web/src/route-nest/pages/menu1/menu1-2/index.tsx +5 -0
  221. package/templates/web/src/route-nest/pages/menu2/index.tsx +5 -0
  222. package/templates/web/src/system/__client/api.schemas.ts +162 -0
  223. package/templates/web/src/system/__client/api.ts +382 -0
  224. package/templates/web/src/system/pages/oplog/index.tsx +1 -1
  225. package/templates/web/src/system/pages/roles/index.tsx +1 -0
  226. package/templates/web/src/system/pages/settings/index.tsx +1 -1
  227. package/templates/web/src/system/pages/users/index.tsx +1 -1
  228. package/templates/web.config.ts +32 -40
  229. package/vendor/host-versions.json +3 -3
  230. package/templates/pnpm-workspace.yaml +0 -14
  231. package/templates/web/src/personal-center/pages/settings/index.tsx +0 -5
package/devkit/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # oj-module DevKit——模块开发手册 + agent skill
2
2
 
3
3
  面向用 `ojm` 开发前后端一体工程的开发者与 AI agent。本目录是**源文件**,
4
- 发布时整目录归置进 `bin/devkit/`,再由 `ojm init` 分发到工程内。
4
+ 发布时由 cli 的 `prepack`(`packages/cli/scripts/sync-devkit.mjs`)整目录同步进
5
+ `packages/cli/devkit/`,随 `@oj-module/cli` 发布,再由 `ojm init` 分发到工程内。
6
+
7
+ > 两个 `devkit` 别混:**本目录**(ojm 模块开发)进 cli 包;`bin/devkit/` 是 **oj 自带**
8
+ > devkit(`oj-api-dev` skill)在工程里的落点,由 `ojm vendor` / `ojm init` 装 oj 时一并落盘。
5
9
 
6
10
  它补的是 `oj-api-dev`(oj 后端 API 开发)之外的另一半:**前端模块、契约、
7
11
  前后端对接**。
@@ -15,7 +19,7 @@
15
19
  | 文件 | 用途 |
16
20
  |---|---|
17
21
  | `SKILL.md` | Claude Code 等 agent 的 skill 入口:工作流、红线、新模块 checklist、陷阱速查,按章节号引用手册 |
18
- | `manual.md` | 模块开发手册(10 章):工程结构(含 §1.7 handler 四红线——oj 运行时实测钉出,后端必读)、写模块、契约与代码生成(含 §3.10 旧 contract.ts 工程迁移指南)、provider 接管(覆盖框架内置功能)、布局、i18n、构建发布、排查、CLI 子命令参考(ojm 原生命令 vs oj 透传命令) |
22
+ | `manual.md` | 模块开发手册(12 章):工程结构(含 §1.7 handler 四红线——oj 运行时实测钉出,后端必读)、写模块、契约与代码生成(含 §3.10 旧 contract.ts 工程迁移指南)、provider 接管(覆盖框架内置功能)、布局、i18n、构建发布、排查、CLI 子命令参考(ojm 原生命令 vs oj 透传命令)、方案包打包与分发、签名与信任 |
19
23
  | `framework-dev.md` | 框架贡献者路线(**仅框架仓库**):改 `packages/runtime`、`packages/cli` 时的入口与红线 |
20
24
 
21
25
  ## 安装(业务工程)
@@ -29,7 +33,7 @@
29
33
  ```sh
30
34
  # 本 devkit(ojm 模块开发):拷入工程的 Claude Code skill 目录
31
35
  mkdir -p .claude/skills/ojm-module-dev
32
- cp SKILL.md manual.md .claude/skills/ojm-module-dev/
36
+ cp SKILL.md manual.md README.md .claude/skills/ojm-module-dev/
33
37
 
34
38
  # 后端 API 开发另装 oj 自带的 devkit(独立 skill,不在此目录内):
35
39
  # 权威源随 oj 二进制分发——本机安装在 ~/.local/share/oj/devkit/,
@@ -46,6 +50,13 @@ cp ~/.local/share/oj/devkit/SKILL.md ~/.local/share/oj/devkit/api-manual.md .cla
46
50
  `devkit/`(prepack 由 `docs/devkit/` 同步而来)拷 `SKILL.md` / `manual.md` / `README.md`
47
51
  到 `.claude/skills/ojm-module-dev/`。发布链路细节见 `framework-dev.md` §7。
48
52
 
53
+ ```mermaid
54
+ flowchart LR
55
+ SRC["docs/devkit/(源文件,只在此处维护)"] -->|"cli prepack:sync-devkit.mjs"| PKG["packages/cli/devkit/(发布副本,随 @oj-module/cli)"]
56
+ PKG -->|"ojm init 第 4b 步(幂等跳过已存在)"| PROJ[".claude/skills/ojm-module-dev/"]
57
+ OJBIN["bin/devkit/(oj 自带)"] -->|"ojm init 第 4 步"| API[".claude/skills/oj-api-dev/"]
58
+ ```
59
+
49
60
  ## 更新
50
61
 
51
62
  手册与 skill 随 `@oj-module/cli` 版本一起发布;升级 cli 后重跑 `ojm init` 即可刷新
package/devkit/SKILL.md CHANGED
@@ -24,20 +24,32 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
24
24
  ```
25
25
  写 handler .schema → 写 handler 业务逻辑(api.ts) → oj openapi + ojm api 生成 client → entry.ts 的 onInit 里 create<Module>Client(ctx) + 注册
26
26
  ```
27
+
28
+ ```mermaid
29
+ flowchart LR
30
+ A["handler 动词写 .schema(入参)+ 平级 .response(出参)"] --> B["写业务逻辑 api.ts"]
31
+ B --> C["oj openapi 生成 openapi.json(唯一事实源)"]
32
+ C --> D["ojm api 生成 web/src/模块/__client"]
33
+ D --> E["entry.ts onInit:createModuleClient(ctx) 一次"]
34
+ E --> F["web.config.ts 登记模块(漏了静默不加载)"]
35
+ ```
27
36
  4. **登记**:往工程根 `web.config.ts` 的 `modules` 数组加一项。**漏了这步模块静默不加载、全程不报错。**
28
37
  5. **验证**:`pnpm dev`(前端 devServer **5174** + oj 后端 **9778**,端口以 `api/config.yaml` 为准)
29
38
  → 登录 `admin / 123456` → 按 §8.1 五步清单核对。
30
39
 
31
40
  ## 红线(不可违反)
32
41
 
33
- - **登记**:模块必须在 `web.config.ts` 里 `enabled: true`。文件写完 ≠ 模块会跑。
42
+ - **登记**:模块必须写进 `web.config.ts` 的 `modules`(`enabled` 缺省即启用,显式
43
+ `enabled: false` 才禁用)。文件写完 ≠ 模块会跑。
34
44
  清单**先后顺序就是接管优先级**(同一入口先注册者生效,无优先级数字可填)。
35
45
  - **`home` / `login` 模块必须保留**:shell 预构建把 `/` 重定向到 `/home`,且宿主只消费
36
46
  模块路由、不挂内置登录页。删掉它们 → 登录回跳落错误边界、`/login` 无路由可跳。
37
47
  - **布局级路由 handle 必写 `layout: "container"`**:不写页面会裸渲染(无侧边栏/页签),
38
48
  且 `keepAlive` 失效。`"container" | "parent" | "none" | 自定义名`。
39
49
  - **注册时机**:一切 `ctx.register.*` 必须在 `lifecycle.onInit` 内、且在 onInit 返回前完成。
40
- 拖到 `onActivate` 或某个异步回调里注册不保证生效。
50
+ 拖到 `onActivate` 或某个异步回调里注册不保证生效。`onInit` 签名是 **async**(返回
51
+ `Promise<void>`)——漏写 `async` 会被 `tsc` 以 TS2322 拦下,范本见模板
52
+ `web/src/notification/entry.ts`。
41
53
  - **一模块一 client**:`create<Module>Client(ctx)` 只在 onInit 里创建一次。
42
54
  页面/组件/事件回调里**不要**再创建——会覆盖 request 单槽。页面里直接 import 裸函数调用。
43
55
  - **只创建自己模块的 client**:A 模块的 onInit 里创建 B 模块的 client 会 DEV 告警。要别家接口,
@@ -143,7 +155,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
143
155
 
144
156
  ## 手册
145
157
 
146
- `manual.md`(同目录)共 10 章:1 工程结构(含 **§1.7 handler 四红线**——oj 运行时实测钉出,
158
+ `manual.md`(同目录)共 12 章:1 工程结构(含 **§1.7 handler 四红线**——oj 运行时实测钉出,
147
159
  后端 handler 必读)/ 2 写前端模块 / 3 契约与代码生成 / 4 接管框架内置功能(provider)/
148
160
  5 布局与插槽 / 6 i18n 与样式 / 7 构建与发布 / 8 排查 / 9 参考 / 10 CLI 子命令参考
149
- (ojm 原生命令 vs oj 透传命令的差异区分)。
161
+ (ojm 原生命令 vs oj 透传命令的差异区分)/ 11 方案包打包与分发 / 12 签名与信任。
@@ -18,8 +18,9 @@
18
18
  | 后端 handler 写法 | `bin/devkit/api-manual.md`(`oj-api-dev` skill) |
19
19
 
20
20
  > ⚠️ 那两份 guide 有**已知陈旧处**,以源码为准:
21
- > dev 端口实际是 **3333**(guide 写的 5173 已过时);包版本以
22
- > `packages/*/package.json` 为准(guide 内部 0.1.6 / 0.1.8 不一致,实际 **0.1.16**)。
21
+ > dev 端口实际是 **3333**(guide 写的 5173 已过时;注意与 `ojm dev` 的前端 devServer
22
+ > **5174** 不是一回事——前者是本仓 vite,后者是业务工程 devServer);包版本以
23
+ > `packages/*/package.json` 为准(guide 内部版本号不一致,实际 **0.2.5**)。
23
24
 
24
25
  ## §2 两包结构与依赖方向
25
26
 
@@ -29,7 +30,7 @@ pnpm 改写成**精确版本**,不是 `^` 范围)。
29
30
  | 包 | 面 | 内容 |
30
31
  |---|---|---|
31
32
  | `packages/runtime` | 浏览器 | 路由 / 布局 / store / 请求 / 组件 / module-loader,+ 契约 DSL 子路径(`./contract`、`./contract/errors`) |
32
- | `packages/cli` | Node 工具链 | `ojm dev/build/info/merge/api/vendor`,+ 预构建宿主(源码 `shell/`、产物 `shell-dist/`、模板 `templates/`) |
33
+ | `packages/cli` | Node 工具链 | `ojm init/dev/build/preview/api/info/vendor/merge/install/enable/disable/uninstall/trust/keygen/sign/publish`,+ 预构建宿主(源码 `shell/`、产物 `shell-dist/`、模板 `templates/`、分发 devkit `devkit/`) |
33
34
 
34
35
  - 宿主产物随 cli 发布;`ojm dev/build` 直接从 cli 包内 `shell-dist` 取宿主,
35
36
  不再依赖独立 shell 包或 monorepo 路径回退。
@@ -125,14 +126,15 @@ pnpm test tests/shell # 宿主全量
125
126
  | `tests/shell/shell-importmap.test.ts` | 门禁一致性 + runtime.js 与包 dist 一致 + 深路径回归 |
126
127
  | `tests/shell/host-version-drift.test.ts`、`version-gate.test.ts` | 共享依赖三处同步 |
127
128
  | `tests/cli/release-manifest.test.ts` | 真跑 `pnpm pack`,守护 `workspace:*` → 精确版本改写 |
128
- | `tests/cli/__snapshots__` | 契约代码生成快照(改 `defineApi` 校验规则需同步更新) |
129
+ | `tests/cli/__snapshots__` | 契约代码生成快照(改 `.schema` 发射规则需同步更新) |
129
130
 
130
131
  ## §5 发布(lockstep,别用 npm publish)
131
132
 
132
- **一键发布**:`node scripts/publish-packages.mjs`(lockstep 四包校验 → 构建 → 守卫测试 →
133
+ **一键发布**:`node scripts/publish-packages.mjs`(lockstep 门禁 → 构建 → 守卫测试 →
133
134
  runtime → cli → registry 复核;`--dry-run` 只演练)。下面的手动步骤是它的展开参考。
134
135
 
135
- - 两包**同版本号**(当前 `0.1.16`),后续发版保持 lockstep。
136
+ - **两包**同版本号(`@oj-module/runtime` + `@oj-module/cli`,当前 `0.2.5`),后续发版保持
137
+ lockstep;`@plane/kit` / `@plane/editor` 不在发布集内(脚本里已注释)。
136
138
  - **必须 `pnpm publish`**——用 `npm publish` 会漏出 `workspace:*` / `catalog:` 字面量。
137
139
  - **顺序:先 `runtime` 后 `cli`**。`runtime` 未公开时,新 `cli` 的精确依赖会悬空,
138
140
  消费者直接装不上。
package/devkit/manual.md CHANGED
@@ -12,30 +12,50 @@
12
12
 
13
13
  ### 1.1 脚手架产物
14
14
 
15
- `ojm init <目录>` 生成(幂等补缺,重跑只补缺失文件,**永不覆盖已有文件**):
15
+ `ojm init <目录>` 生成(幂等补缺,重跑只补缺失文件,**永不覆盖已有文件**)。
16
+ 模板拷贝按 `packages/cli/templates/init.include.yaml` **白名单**执行——未收录的
17
+ 路径一律不拷(demo-only 演示模块、模板自运行产物都在清单外):
16
18
 
17
19
  ```
18
20
  <工程根>/
19
- ├── package.json # scripts: dev / build / preview / info / typecheck,全部转发 ojm
20
- ├── web.config.ts # 前端模块清单(name + entry + enabled)
21
+ ├── README.md
22
+ ├── package.json # scripts: dev / build / preview / info / typecheck;devDeps 钉死真实版本
23
+ ├── pnpm-workspace.yaml # 构建审批(esbuild)+ redocly 钉版(init 生成,非模板拷贝)
24
+ ├── web.config.ts # 前端模块清单(name + entry)
25
+ ├── openapi.json # 契约唯一事实源(oj openapi 产物,模板随附,ojm api --check 开箱即过)
21
26
  ├── tsconfig.json
22
27
  ├── global.d.ts # 后端 oj 全局对象(db/json/http…)类型声明
23
28
  ├── env.d.ts # ImportMeta.env 类型补丁
29
+ ├── .gitignore
24
30
  ├── api/
25
- │ ├── config.yaml # oj 服务配置(端口、db、auth、secrets);站点挂载见顶层 mounts:(v0.1.58 起替代 server.api_prefix)
26
- │ ├── config/ # dev 证书(public.pem / cert.jws)
31
+ │ ├── config.yaml # oj 服务配置(mounts / db / auth / secrets)
32
+ │ ├── config/ # 现场签发:证书三件套(private/public.pem + cert.jws)+ secrets 密钥对
27
33
  │ ├── .ojm-api-exempt.json
34
+ │ ├── seed.mysql.sql / seed.pg.sql
35
+ │ ├── vitest.config.ts
36
+ │ ├── tests/ # 后端 BDD 测试(node:sqlite 内存库 + 真实迁移建表)
28
37
  │ └── src/
29
- │ ├── _platform/ # 框架级共有表(users)
30
- │ ├── auth/ # login / refresh / logout
31
- │ ├── web/ # hello / user-info / get-async-routes
32
- │ └── notifications/ # root 级 /api/notifications(通知铃的后备实现)
38
+ │ ├── _platform/ # 框架级:users 表 / rbac / 市场编排 / 生命周期
39
+ │ ├── auth/ # login / refresh / logout / ldap-login
40
+ │ ├── notification/ # 通知互动(表 + push + del)
41
+ │ ├── notifications/ # root 级 /api/notifications(通知铃的后备实现)
42
+ │ ├── personal-center/ # 头像上传
43
+ │ ├── system/ # 用户 / 角色 / 权限 / 设置 / oplog / login-methods
44
+ │ └── web/ # hello / user-info / get-async-routes
33
45
  ├── web/
34
- │ ├── src/<模块>/ # 前端模块源码
35
- │ └── dist/ # 构建产物(完整站点)
36
- └── bin/ # oj 二进制 + plugins/ + devkit/(不入库)
46
+ │ └── src/
47
+ │ ├── home/ demo/ login/ # 带页面模块(home/login 不可少,见 SKILL 红线)
48
+ │ ├── personal-center/ notification/ system/ # 其余默认前端模块
49
+ │ ├── _platform/ auth/ notifications/ web/ # 仅 __client 生成物(无页面)
50
+ │ └── styles.css
51
+ ├── bin/ # oj 二进制 + plugins/ + devkit/(不入库)
52
+ └── .claude/skills/ # ojm-module-dev(本 devkit)+ oj-api-dev(随 oj 二进制)
37
53
  ```
38
54
 
55
+ > 模板里还有一批 demo-only 模块(`api/src/{demo,home}`、`web/src/{about,access,
56
+ > exception,outside,route-nest,oj-market}`)仅供模板自演示,**不进脚手架**;
57
+ > 其 `web.config.ts` 条目带 `// #region demo-only` 标记,init 拷贝时整段剥离。
58
+
39
59
  登录账号 `admin / 123456`。端口:前端 devServer **5174**,oj 后端 **9778**
40
60
  (以 `api/config.yaml` 的 `server.port` 为准)。
41
61
 
@@ -96,7 +116,7 @@ api/src/<模块>/<路径...>/api.ts → /api/<模块>/<路径...>
96
116
 
97
117
  以下四条都是真机冒烟(`ojm init` + `ojm dev`/`build`)实测钉出的 oj 运行时行为,
98
118
  **dev 模式体感正常、一到构建/并发就炸**——vitest 直引 service 的测试结构性覆盖不到,
99
- 评审 checklist 必查。逐条展开:
119
+ 提交前必查。逐条展开:
100
120
 
101
121
  1. **未捕获异常恒 500,不读 `err.status`。** handler 抛出任何异常,oj 运行时一律回
102
122
  `{"code":500,"msg":"Error: ..."}`——你在 service 层精心设计的 401/403/409 全被抹平。
@@ -277,7 +297,7 @@ export default defineModule({
277
297
  可服务的同源路径)。直接 `import "vditor/dist/..."` 其它深路径会命中前缀 external 但无
278
298
  importmap 键,构建期报错——需要新深路径请联系框架方在共享表登记。
279
299
 
280
- ### 2.7 本版本共享依赖钉版(0.1.20 快照)
300
+ ### 2.7 本版本共享依赖钉版(0.2.5 快照)
281
301
 
282
302
  宿主经 importmap 提供以下单例(模块 `devDependencies` 须对齐,供 `tsc` 用);完整 68 项矩阵以
283
303
  `pnpm exec ojm info` 或 `packages/cli/shell-dist/versions.json`(构建期生成)为准。
@@ -306,10 +326,12 @@ importmap 键,构建期报错——需要新深路径请联系框架方在共
306
326
  | `pinyin-pro` | 3.29.3 |
307
327
  | `vditor` | 4.0.0 |
308
328
 
309
- 框架包版本:`@oj-module/runtime` / `@oj-module/cli` / `@plane/kit` / `@plane/editor` 均为 `0.1.20`
310
- (lockstep);`oj` 二进制最低版本门禁 `MIN_OJ_VERSION = 0.1.73`——契约流水线要求 oj ≥ 0.1.73:
311
- 平级 `.response` 出参声明位自 v0.1.72 起、`oj openapi` 的 `del`→`delete` 操作键归一自 v0.1.73 起
312
- (低版本 oj 生成 `openapi.json` 会丢出参类型或产出非法 `"del"` 键,`ojm api --check` 必漂移)。
329
+ 框架包版本:`@oj-module/runtime` / `@oj-module/cli` 为 `0.2.5`(**双包 lockstep**);
330
+ `@plane/kit` / `@plane/editor` 是独立版本线(当前 `0.1.20`,随宿主 importmap 提供,
331
+ 不参与 lockstep)。`oj` 二进制最低版本门禁 `MIN_OJ_VERSION = 0.1.73`——契约流水线要求
332
+ oj ≥ 0.1.73:平级 `.response` 出参声明位自 v0.1.72 起、`oj openapi` 的 `del`→`delete`
333
+ 操作键归一自 v0.1.73 起(低版本 oj 生成 `openapi.json` 会丢出参类型或产出非法
334
+ `"del"` 键,`ojm api --check` 必漂移)。
313
335
 
314
336
  > **`@oj-module/runtime` 到底导出哪些东西?** 权威清单随包发布在
315
337
  > `node_modules/@oj-module/runtime/dist/index.d.ts`——直接 Read 它,或在编辑器里
@@ -342,9 +364,30 @@ const roles = useUserStore(s => s.roles);
342
364
 
343
365
  **写(改密/改角色/改设置)→ 走契约 API client,不碰框架内部**
344
366
 
345
- - 框架已把 `auth/*`、`system/*`、`web/*` 等端点经 OpenAPI 契约定义,并从 `@oj-module/runtime` 导出类型化 fetch(如 `api/rbac` 的 `fetchChangePassword`、`fetchRbacUpdateUserRoles`,`api/user` 的 `fetchUserInfo`)。要调框架内置写接口,直接复用这些导出。**但更推荐模块 own 自己的契约**:在 `api/src/<模块>/contract.ts` 声明,跑 `ojm api` 生成 `web/src/<模块>/__client/api.ts`,模块经自己的 client 调框架端点——接口由契约(versioned OpenAPI)对齐,而非把框架源文件打进来。
367
+ - 框架已把 `auth/*`、`system/*`、`web/*` 等端点经 OpenAPI 契约定义,并从 `@oj-module/runtime` 导出类型化 fetch(如 `api/rbac` 的 `fetchChangePassword`、`fetchRbacUpdateUserRoles`,`api/user` 的 `fetchUserInfo`)。要调框架内置写接口,直接复用这些导出。**但更推荐模块 own 自己的契约**:在**自己模块的 handler 动词**上写 `.schema`,跑 `oj openapi` + `ojm api` 生成 `web/src/<模块>/__client/api.ts`,模块经自己的 client 调框架端点——接口由契约(versioned OpenAPI)对齐,而非把框架源文件打进来。
346
368
  - 底层都走同一个 `request` 单例 + 契约 URL;模块侧请求被 `defineModule` 的 `ctx` 收敛到登记过的 `apiPrefix`,越界 / `../` 穿越 / 逐请求 prefix 覆盖均被拒(请求边界见 §2.6 + `tests/runtime/scoped-request.test.ts`)。
347
369
 
370
+ **场景:模块记录自己的操作日志(写端)**
371
+
372
+ 解决方案模块经常要审计自身的关键写操作(建单、改配置、删资源…)。**正确姿势:调 runtime 导出的 `fetchRbacOplogPost`,绝不在模块里 import 框架 `#src/audit` 的 `recordOp`**(那是框架内部,违反 §2.6 红线,且模块 bundle 会被夹带框架实现)。
373
+
374
+ ```ts
375
+ import { fetchRbacOplogPost } from "@oj-module/runtime";
376
+
377
+ // 在模块自己的写操作成功后调用;module 取本模块名(defineModule 的 name),
378
+ // 用于后端按模块收敛查询与隔离;detail 脱敏红线:不得含口令 / 密钥 / token。
379
+ await fetchRbacOplogPost({
380
+ action: "order.create",
381
+ module: "order", // 本模块名,非 "framework"
382
+ target: String(orderId),
383
+ detail: { sku, qty },
384
+ });
385
+ ```
386
+
387
+ - 后端 `POST /api/system/oplog` 由框架提供,登录态即可写入(审计面向当前用户,非仅管理员);`user_id` / `username` 由请求上下文自动落库,调用方无需传。
388
+ - 读取走 `fetchRbacOplog`(管理面 `system/oplog` 页,admin 可见);模块自身只写不读自己的审计流。
389
+ - `module` 是隔离锚点:跨模块写别家审计流必须由后端按 `module` 收敛,前端传错模块名不会污染他模块数据。
390
+
348
391
  **为什么不会「版本不一致」**
349
392
 
350
393
  宿主(cli 内置 `shell-dist`)把 `react` / `antd` / `runtime` 等共享依赖预打包、经 importmap 提供**单例**——宿主与所有模块命中同一份实例。所以模块的 `useUserStore` 和框架的是**同一个 store**,antd `ConfigProvider` 也只有一个:不会出现两份 React / 两份 store 状态不同步。共享依赖版本以 `catalog:` 单一真相钉死(见 §2.7),模块只在 `devDependencies` 对齐版本供 `tsc` 用。
@@ -549,6 +592,14 @@ pnpm exec ojm api --docs
549
592
 
550
593
  `--check` 对四件事(只读,绝不写盘、绝不修文件):
551
594
 
595
+ ```mermaid
596
+ flowchart TD
597
+ C["ojm api --check(只读)"] --> L1["① 生成物同步:内存重跑 ojm api vs 磁盘 __client 逐字节 diff"]
598
+ C --> L2["② route 双向对账:生成物 ⊄ handler 已实现 → route-not-implemented;handler ⊄ 生成物 → route-unregistered"]
599
+ C --> L3["③ routes.js diff:oj build 产物路由表 vs 生成物(无 dist 只提示,不判违规)"]
600
+ C --> L4["④ WS 路由鉴权:ws.ts 产生的 GET ws 路由不在 anonymous_paths → 只 warn"]
601
+ ```
602
+
552
603
  1. **生成物同步(artifact-stale)**:内存重跑 `ojm api` vs 磁盘逐字节 diff(`__client/{api.ts,api.schemas.ts}`);
553
604
  2. **route 双向对账**:生成物端点 ⊄ handler 已实现表 → `route-not-implemented`;handler 表 ⊄ 生成物 → `route-unregistered`(参数段不一致 error);
554
605
  3. **`routes.js` diff**:`oj build` 产物路由表 vs 生成物;无 dist 给提示不判违规;
@@ -633,7 +684,7 @@ export default { get };
633
684
 
634
685
  | 旧 `defineApi` 字段 | 新声明位 | 说明 |
635
686
  |---|---|---|
636
- | `route` / `method` | handler 所在目录/文件名 + 动词函数名 | 路由由目录镜像推导,不再手写(AC-D9) |
687
+ | `route` / `method` | handler 所在目录/文件名 + 动词函数名 | 路由由目录镜像推导,不再手写(前缀 = 后端模块目录名,见 §3.2 硬约束) |
637
688
  | `params` | `.schema.params` | 路径参数,与 route 的 `{id}` 段对应 |
638
689
  | `query` | `.schema.query` | URL query |
639
690
  | `body` | `.schema.body` | JSON 请求体 |
@@ -797,7 +848,7 @@ export default {
797
848
  };
798
849
  ```
799
850
 
800
- 原语清单(`_shared/notify.ts`,设计文档 §4.3 / §6):
851
+ 原语清单(`_shared/notify.ts`):
801
852
 
802
853
  | 原语 | 作用 | 返回 |
803
854
  |---|---|---|
@@ -1249,6 +1300,24 @@ pnpm exec ojm info # 版本矩阵 + 模块清单(报障用)
1249
1300
  pnpm exec ojm vendor # 下载/重装 oj 二进制
1250
1301
  ```
1251
1302
 
1303
+ `ojm build` 的两条产物线与全站合并:
1304
+
1305
+ ```mermaid
1306
+ flowchart LR
1307
+ subgraph backend["后端线"]
1308
+ direction TB
1309
+ OJB["oj build -d api/src"] --> RD["api/dist/routes.js(release 下唯一路由来源,目录镜像不生效)"]
1310
+ end
1311
+ subgraph frontend["前端线"]
1312
+ direction TB
1313
+ MB["逐模块构建(esbuild bundle)"] --> EN["web/dist/modules/模块名/版本/entry.js(带 integrity)"]
1314
+ MB --> MJ["web/dist/modules.json + versions.json"]
1315
+ end
1316
+ HOST["cli 内置 shell-dist 宿主(importmap 单例)"] --> MERGE["全站合并 mergeSite"]
1317
+ MJ --> MERGE
1318
+ MERGE --> SITE["web/dist/ = index.html + assets/ + modules/ + modules.json + versions.json"]
1319
+ ```
1320
+
1252
1321
  - `web/dist/` = **完整站点**(`index.html`、`assets/`、`modules/`、`modules.json`、`versions.json`)。
1253
1322
  - `api/dist/` = oj build 产物。**release 下只有 `routes.js` 里的路由存在,
1254
1323
  目录镜像路由不生效**——所以参数路由要先 `oj build` 再测。
@@ -1394,7 +1463,7 @@ export default async (path: string, html: string) => {
1394
1463
 
1395
1464
  | 类别 | 命令 | 谁实现 | 一句话 |
1396
1465
  |---|---|---|---|
1397
- | **ojm 原生** | `init` / `dev` / `build` / `preview` / `api` / `info` / `vendor` / `merge` | `ojm` 自己 | 前后端一体工程的脚手架、开发、构建、契约生成、版本/清单、oj 分发、清单合并 |
1466
+ | **ojm 原生** | `init` / `dev` / `build` / `preview` / `api` / `info` / `vendor` / `merge` / `install` / `enable` / `disable` / `uninstall` / `trust` / `keygen` / `sign` / `publish` | `ojm` 自己 | 前后端一体工程的脚手架、开发、构建、契约生成、版本/清单、oj 分发、清单合并;方案包安装面与签名信任(install/enable/disable/uninstall 见 §11,trust/keygen/sign 见 §12,publish 见 §11.2) |
1398
1467
  | **oj 透传** | `test` / `exec` / `openapi` / `migrate` / `schema` | `oj`(ojm 仅转发) | 这 5 个**就是 oj 的同名子命令**,ojm 只负责定位 `bin/oj` 并补上 `-c api/config.yaml` |
1399
1468
 
1400
1469
  > **为什么只有这 5 个走透传?** oj 还有 `serve` / `build` / `secret`(`keygen`/`seal` 等)
@@ -1414,7 +1483,11 @@ export default async (path: string, html: string) => {
1414
1483
  - `--yes`:跳过交互确认,直接补缺。
1415
1484
 
1416
1485
  产物见 §1.1(`api/` 后端骨架 + `web/` 前端骨架 + `bin/`、`web.config.ts`、`tsconfig.json` 等)。
1417
- `oj` 二进制缺失时,会提示重跑 `ojm init` 或 `ojm vendor`。
1486
+ 模板拷贝按 `packages/cli/templates/init.include.yaml` 白名单——demo-only 演示模块
1487
+ 与模板自运行产物不在清单内,不会进工程(§1.1)。
1488
+ `bin/oj` 缺失时自动经 npm 包 `@oj-bin/oj` 下载(与 `ojm vendor` 同款,受
1489
+ `MIN_OJ_VERSION` 门禁约束);下载失败即报错——可先 `ojm vendor`,或把本机 oj
1490
+ 预置进 `bin/` 后重跑(`bin/oj` 已在则跳过下载,init 幂等)。
1418
1491
 
1419
1492
  #### `ojm dev [port]`
1420
1493
 
@@ -1519,6 +1592,151 @@ export default async (path: string, html: string) => {
1519
1592
 
1520
1593
  ---
1521
1594
 
1595
+ ## §11 方案包:打包与分发
1596
+
1597
+ 「站点级构建」见 §7;本节讲**方案包**(solution bundle)——单方案的可分发产物与到达站点的方式。
1598
+
1599
+ ### 11.1 打包:`ojm build --bundle`
1600
+
1601
+ ```bash
1602
+ ojm build --bundle # --out <dir> 缺省 dist/
1603
+ # [ojm] 方案包已生成 → dist/<name>-<version>.tgz
1604
+ # [ojm] sha256:…(确定性打包:同源同字节)
1605
+ ```
1606
+
1607
+ - 打包即校验(fail-fast,坏清单出不了包):方案 manifest 契约校验(字段齐全 / 类型白名单 /
1608
+ 保留名 / 键空间 → 可读违规清单)→ 文件面校验(`api.tables` ↔ `schema.yaml`、locales 双语齐备)。
1609
+ - **只出包不签名**——签名见 §12(关注点分离:作者签名是显式动作,不是构建副产品)。
1610
+ - 产物内 `manifest.yaml` 的 `archive` 段是 **Form A**(仅 `file`);`sha256`/`size` 由
1611
+ file 源上传时点补齐(宿主安装器)或发布通道 Form B 自带。
1612
+ - **入包红线**:`*.pem` / `*.key` / `.env` / `id_ed25519` / `id_rsa` 等私钥形态文件禁入包
1613
+ (与 `modules.json` 禁入同款自检)——私钥入包即信任根基烧毁。
1614
+
1615
+ ### 11.2 两种分发源
1616
+
1617
+ | 源 | 形态 | 说明 |
1618
+ |---|---|---|
1619
+ | **file** | `ojm install dist/<name>-<version>.tgz` | multipart 直传(同目录 `<tgz>.sig` 自动探测随包上传,§12) |
1620
+ | **cdn** | `ojm install <name>[@version]` | 站点从 CDN 拉 Form B 索引:`bundles.yaml`(版本清单)+ `manifests.yaml`(聚合清单)+ 分发包 |
1621
+
1622
+ Form B 索引规范(宿主侧强制,防索引投毒):`versions[].file` 必须逐字
1623
+ `<name>-<version>.tgz`(禁 `../`、绝对路径、异名、异版本);`archive.size` 在下载段立即
1624
+ 比对;索引拉取恒 `Cache-Control: no-cache`。索引本身未签名(签名只保护分发包本身,
1625
+ `.sig` 侧车随包上传)。
1626
+
1627
+ **作者侧**:索引的产出/重签用 `ojm publish <发布目录>`——对 `bundles.yaml` +
1628
+ `manifests.yaml` 重算(确定性 stringify)→ detached 签名(`.sig` 侧车)→ 写回
1629
+ (本地文件目标;CDN 推送走同一 resolveCdn 面)。发布方 key 与 `ojm sign` 同身份体系
1630
+ (`~/.ojm/keys`)。
1631
+
1632
+ ### 11.3 安装面速览
1633
+
1634
+ `ojm install`(file/cdn 双源)→ 站点编排端点 `POST /api/_platform/market/install`
1635
+ (需 superadmin)→ 十阶段状态机(§7 verify 闸语义的方案版,失败语义见下图)→
1636
+ 任务记录 `.install-tasks/<id>.json`(保留 10 条,800ms 轮询消费面)。市场页族端点
1637
+ (install/task/index/meta/trust/uninstall…)已全量登记 openapi.json,
1638
+ 前端经 `web/src/_platform/__client` 消费。
1639
+
1640
+ 十阶段(阶段名如下)+ 失败语义:
1641
+
1642
+ ```mermaid
1643
+ flowchart TD
1644
+ A(["POST /api/_platform/market/install"]) --> P["1 preflight 预检"]
1645
+ P --> D["2 downloading 下载"]
1646
+ D --> V["3 verifying 验签"]
1647
+ V --> X["4 extracting 解包"]
1648
+ X --> M["5 migrating 迁移"]
1649
+ M --> L["6 lock_commit 落锁生效"]
1650
+ L --> R["7 registering 登记"]
1651
+ R --> W["8 deploying_web 部署前端"]
1652
+ W --> RL["9 reloading 热更"]
1653
+ RL --> SM["10 smoke 冒烟"]
1654
+ SM --> AU["audit 终态审计(恒末位,失败也审)"]
1655
+ AU --> OK(["done"])
1656
+ V -. "unknown_key 或吊销:预锁段即停,staging 不清场,公钥指纹交人核对" .-> NT(["needs_trust 第五终态"])
1657
+ P -. "① 锁前段(preflight–lock_commit)失败:清场重跑,什么都没发生" .-> F(["failed"])
1658
+ R -. "② 锁后段(registering–smoke)失败:partial 部分生效" .-> PA(["partial"])
1659
+ ```
1660
+
1661
+ - **needs_trust 确认后重试同一安装**即可续跑;一切消费面须把它当**非成功态**渲染。
1662
+
1663
+ ---
1664
+
1665
+ ## §12 签名与信任
1666
+
1667
+ 方案的发布与安装建立在 **Ed25519 detached 签名 + 站点信任库** 之上。
1668
+ **硬 fail-closed:未签名包拒装(sig_missing),无任何逃生开关。**
1669
+ (fail-closed = 校验不过时**拒绝安装**,而不是放行、也不是告警后继续——
1670
+ 信任链上一律不做「先装了再说」的妥协。)
1671
+
1672
+ ```mermaid
1673
+ flowchart LR
1674
+ subgraph author["作者侧"]
1675
+ direction TB
1676
+ K["ojm keygen --id 发布方.密钥名"] --> S["ojm sign dist/name-version.tgz --id … --check"]
1677
+ S --> PUB["ojm publish 发布目录(索引重算 + .sig 侧车)"]
1678
+ end
1679
+ subgraph ops["运维侧(站点)"]
1680
+ direction TB
1681
+ INST["ojm install(file 直传 / cdn 拉取)"] --> VER["verifying 段验签"]
1682
+ VER -->|"签名缺失"| E1["sig_missing 拒装"]
1683
+ VER -->|"算法 / key_id 命名 / .sig 结构违约"| E2["sig_alg / sig_key / sig_format 拒装"]
1684
+ VER -->|"验签不过"| E3["tampered 拒装"]
1685
+ VER -->|"信任库无此 key"| TOFU["needs_trust:展示公钥指纹,与发布方逐字核对"]
1686
+ TOFU -->|"ojm trust confirm --task 安装任务id"| LIB[".solutions/trusted-keys.yaml 入库"]
1687
+ LIB --> INST
1688
+ VER -->|"验签通过"| NEXT["继续 installing 后续阶段(§11.3)"]
1689
+ end
1690
+ BOOT["模板内置 BOOTSTRAP_KEY(第一方公钥)"] -. "market 方案首装免 TOFU" .-> VER
1691
+ ```
1692
+
1693
+ ### 12.1 发布流程(作者侧,三步)
1694
+
1695
+ ```bash
1696
+ # ① 一次性:生成 Ed25519 密钥对(私钥 ~/.ojm/keys/<id>/ 永不出目录——--out 指进工程树须 --yes)
1697
+ ojm keygen --id <publisher>.<key-name>
1698
+
1699
+ # ② 每次发布:签名(产物 = 同目录 <tgz>.sig;--check 离线自检,不必靠装失败发现签错)
1700
+ ojm sign dist/<name>-<version>.tgz --id <publisher>.<key-name> --check
1701
+
1702
+ # ③ 安装/分发:.sig 自动探测随包上传
1703
+ ojm install dist/<name>-<version>.tgz --url <站点> --user admin --pass …
1704
+ ```
1705
+
1706
+ 宿主验签六码(失败文案双语,`--locale` 切换):`sig_missing`(缺签)/ `sig_alg`(算法
1707
+ 不受接受)/ `sig_key`(key_id 命名空间违约)/ `tampered`(验签不过)/ `unknown_key`
1708
+ (信任库无此 key → **needs_trust 第五终态**)/ `sig_format`(.sig 结构违约)。
1709
+
1710
+ ### 12.2 信任库与 TOFU(运维侧)
1711
+
1712
+ **TOFU = Trust On First Use(首次使用即信任)**:陌生发布方的第一个包,安装器无法判断
1713
+ 该不该信,于是把公钥指纹摆出来让人核对——核对通过才入库,之后同一 key_id 直接放行。
1714
+ 信任的建立因此有两条路:**第一方**走模板内置公钥免核对(§12.3),**第三方**走本节的
1715
+ 人工核对。
1716
+
1717
+ - 信任库 = `.solutions/trusted-keys.yaml`(key_id → 公钥 + added_at;首次绑定不可改,
1718
+ 轮换 = 新增 key_id)。
1719
+ - **TOFU 双轨**:未知发布方首装 → 任务终态 `needs_trust`(非成功非崩溃,staging 不清场),
1720
+ 展示公钥指纹;**与发布方逐字核对指纹后**确认入库并重试:
1721
+
1722
+ ```bash
1723
+ ojm trust ls # 列已信任 key(含 sha256 指纹分组)
1724
+ ojm trust confirm --task <id> # 把 needs_trust 待确认 key 入库,然后重跑 install
1725
+ ojm trust add <key_id> <pubhex> # 直接登记
1726
+ ojm trust revoke <key_id> # 吊销:阻断新装/升级,不动已装方案
1727
+ ```
1728
+
1729
+ ### 12.3 第一方 bootstrap key(首装免 TOFU)
1730
+
1731
+ 平台模板内置第一方发布方**公钥**(`api/src/_platform/market/bootstrap.ts` 的
1732
+ `BOOTSTRAP_KEY`):信任库文件首次初始化时自动播种一次——**market 方案首装免 TOFU 直接过
1733
+ 验签**。发布时序(平台发行人执行):`ojm keygen` → 签 oj-market 首发包 → 公钥 + key_id
1734
+ 回填 `BOOTSTRAP_KEY` 随模板发布。播种纪律:库已初始化(含空库)不播种——运维 `revoke`
1735
+ 后不复活。**模板内只有公钥**(packages/cli 全树私钥零命中钉成测试;私钥只活在发行人
1736
+ `~/.ojm/keys/`)。
1737
+
1738
+ ---
1739
+
1522
1740
  ## 附录:章节目录
1523
1741
 
1524
1742
  | 章 | 内容 | 什么时候读 |
@@ -1533,3 +1751,5 @@ export default async (path: string, html: string) => {
1533
1751
  | §8 排查 | 五步排查法、症状速查表 | 任何「没生效」 |
1534
1752
  | §9 参考 | 可抄的工程内文件、oj-api-dev 手册指引 | 找范本 |
1535
1753
  | §10 CLI 子命令参考 | ojm 原生命令 vs oj 透传命令 | 查命令用法 |
1754
+ | §11 方案包:打包与分发 | build --bundle、Form A/B、分发源、索引规范 | 发布方案包 |
1755
+ | §12 签名与信任 | keygen/sign/install 发布流程、信任库、TOFU、bootstrap key | 首次签名发布;管理信任 |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oj-module/cli",
3
3
  "type": "module",
4
- "version": "0.2.1",
4
+ "version": "0.2.5",
5
5
  "description": "React Antd Admin 模块工程命令行工具(含预构建宿主 shell-dist)",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -49,7 +49,7 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@faker-js/faker": "^10.4.0",
52
- "@oj-module/runtime": "0.2.1",
52
+ "@oj-module/runtime": "0.2.5",
53
53
  "@redocly/cli": "^2.51.0",
54
54
  "@tailwindcss/vite": "^4.3.0",
55
55
  "esbuild": "^0.28.2",