@oj-module/cli 0.2.17 → 0.2.20

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 (332) hide show
  1. package/devkit/README.md +4 -4
  2. package/devkit/SKILL.md +41 -37
  3. package/devkit/framework-dev.md +6 -5
  4. package/devkit/manual.md +260 -162
  5. package/devkit/menu-rules.md +3 -4
  6. package/devkit/module-dev-and-cdn-playground.md +31 -22
  7. package/devkit/sql-builder-missing-capabilities-pr.md +11 -6
  8. package/package.json +2 -2
  9. package/shell-dist/assets/runtime.js +2 -2
  10. package/shell-dist/index.html +2 -2
  11. package/shell-dist/versions.json +3 -3
  12. package/src/__tests__/build-backend.test.ts +54 -0
  13. package/src/__tests__/layout.test.ts +176 -0
  14. package/src/__tests__/module-list.test.ts +66 -0
  15. package/src/__tests__/oj-caps.test.ts +114 -0
  16. package/src/alias-paths.ts +102 -0
  17. package/src/build.ts +41 -9
  18. package/src/bundle.ts +25 -13
  19. package/src/config.ts +82 -9
  20. package/src/contract/__tests__/market-client.integration.test.ts +2 -2
  21. package/src/contract/check.ts +56 -28
  22. package/src/contract/emit-stub.ts +21 -9
  23. package/src/contract/handler-scan.ts +19 -10
  24. package/src/contract/run.ts +14 -4
  25. package/src/dev.ts +22 -7
  26. package/src/index.ts +1 -1
  27. package/src/info.ts +20 -3
  28. package/src/init.ts +91 -21
  29. package/src/layout.ts +282 -24
  30. package/src/lifecycle/__tests__/bundle.test.ts +16 -1
  31. package/src/lifecycle/__tests__/manifest-contract.test.ts +17 -58
  32. package/src/manifest-contract.ts +2 -25
  33. package/src/market/__tests__/bootstrap.test.ts +17 -9
  34. package/src/market/bootstrap.ts +30 -17
  35. package/src/oj-caps.ts +133 -0
  36. package/src/oj.ts +27 -4
  37. package/src/preview-config.ts +1 -1
  38. package/src/preview.ts +2 -1
  39. package/src/site.ts +2 -2
  40. package/src/usage.ts +6 -6
  41. package/src/vendor.ts +5 -1
  42. package/templates/{api/config.yaml → config.yaml} +13 -8
  43. package/templates/global.d.ts +7 -6
  44. package/templates/init.include.yaml +41 -36
  45. package/templates/package.json +1 -1
  46. package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/installer.ts +4 -0
  47. package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/smoke.ts +21 -2
  48. package/templates/{api/src/_platform → src/_platform/api}/market/api.ts +25 -15
  49. package/templates/{api/src/_platform → src/_platform/api}/solutions/api.ts +7 -3
  50. package/templates/{api/src/auth → src/auth/api}/_shared/login-throttle.ts +2 -2
  51. package/templates/{api/src/auth → src/auth/api}/_shared/session.ts +2 -2
  52. package/templates/{api/src/auth → src/auth/api}/api.ts +1 -1
  53. package/templates/{api/src/auth → src/auth/api}/ldap-login/api.ts +1 -1
  54. package/templates/src/auth/api/manifest.yaml +6 -0
  55. package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/InstallDialog.test.tsx +53 -1
  56. package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/marketClient.fake.ts +6 -0
  57. package/templates/{web/src/oj-market → src/oj-market/web}/api/client.ts +5 -0
  58. package/templates/{web/src/oj-market → src/oj-market/web}/api/defaultClient.ts +7 -1
  59. package/templates/{web/src/oj-market → src/oj-market/web}/components/InstallDialog.tsx +80 -5
  60. package/templates/{web/src/oj-market → src/oj-market/web}/hooks/useMarket.ts +6 -1
  61. package/templates/{web/src/oj-market → src/oj-market/web}/locales/en-US.json +10 -1
  62. package/templates/{web/src/oj-market → src/oj-market/web}/locales/zh-CN.json +10 -1
  63. package/templates/{web/src/oj-market → src/oj-market/web}/types.ts +15 -2
  64. package/templates/{api/src/system → src/system/api}/login-methods/api.ts +2 -2
  65. package/templates/src/system/api/manifest.yaml +6 -0
  66. package/templates/{api/src/system → src/system/api}/oplog/api.ts +4 -4
  67. package/templates/{api/src/system → src/system/api}/permissions/api.ts +3 -3
  68. package/templates/{api/src/system → src/system/api}/roles/api.ts +3 -3
  69. package/templates/{api/src/system → src/system/api}/roles/bindings/api.ts +3 -3
  70. package/templates/{api/src/system → src/system/api}/settings/api.ts +3 -3
  71. package/templates/{api/src/system → src/system/api}/users/api.ts +3 -3
  72. package/templates/{api/src/system → src/system/api}/users/forbidden/api.ts +3 -3
  73. package/templates/{api/src/system → src/system/api}/users/password/api.ts +3 -3
  74. package/templates/{api/src/system → src/system/api}/users/roles/api.ts +3 -3
  75. package/templates/src/tsconfig.json +16 -0
  76. package/templates/{api/tests → tests}/auth-ldap.spec.ts +2 -2
  77. package/templates/{api/tests → tests}/auth-session.spec.ts +3 -3
  78. package/templates/{api/tests → tests}/auth-throttle.spec.ts +1 -1
  79. package/templates/tests/fixture.ts +434 -0
  80. package/templates/{api/tests → tests}/guard.spec.ts +1 -1
  81. package/templates/{api/tests → tests}/lifecycle/atomic-state.spec.ts +3 -3
  82. package/templates/{api/tests → tests}/lifecycle/bootstrap.spec.ts +8 -4
  83. package/templates/{api/tests → tests}/lifecycle/deploy-smoke.spec.ts +31 -8
  84. package/templates/{api/tests → tests}/lifecycle/installer.spec.ts +15 -15
  85. package/templates/{api/tests → tests}/lifecycle/market-api.spec.ts +30 -29
  86. package/templates/{api/tests → tests}/lifecycle/migrate.spec.ts +6 -6
  87. package/templates/{api/tests → tests}/lifecycle/preflight.spec.ts +4 -4
  88. package/templates/{api/tests → tests}/lifecycle/register.spec.ts +1 -1
  89. package/templates/{api/tests → tests}/lifecycle/semver.spec.ts +1 -1
  90. package/templates/{api/tests → tests}/lifecycle/sha384.spec.ts +1 -1
  91. package/templates/{api/tests → tests}/lifecycle/sig-fixtures.spec.ts +1 -1
  92. package/templates/{api/tests → tests}/lifecycle/solutions-api.spec.ts +1 -1
  93. package/templates/{api/tests → tests}/lifecycle/steps-fs.spec.ts +12 -12
  94. package/templates/{api/tests → tests}/lifecycle/text-edit.spec.ts +1 -1
  95. package/templates/{api/tests → tests}/lifecycle/tgz-guard.spec.ts +1 -1
  96. package/templates/{api/tests → tests}/lifecycle/trust.spec.ts +5 -5
  97. package/templates/{api/tests → tests}/lifecycle/uninstall.spec.ts +7 -7
  98. package/templates/{api/tests → tests}/lifecycle/verify-checks.spec.ts +9 -6
  99. package/templates/{api/tests → tests}/lifecycle/verify-report.spec.ts +7 -5
  100. package/templates/{api/tests → tests}/query-utils.spec.ts +1 -1
  101. package/templates/{api/tests → tests}/rbac-audit.spec.ts +1 -1
  102. package/templates/{api/tests → tests}/rbac-contacts.spec.ts +2 -2
  103. package/templates/{api/tests → tests}/rbac-guard.spec.ts +1 -1
  104. package/templates/{api/tests → tests}/rbac-roles.spec.ts +2 -2
  105. package/templates/{api/tests → tests}/rbac-seed.spec.ts +1 -1
  106. package/templates/{api/tests → tests}/rbac-settings.spec.ts +2 -2
  107. package/templates/{api/tests → tests}/rbac-solution-perms.spec.ts +1 -1
  108. package/templates/{api/tests → tests}/rbac-users.spec.ts +3 -3
  109. package/templates/tsconfig.json +5 -1
  110. package/templates/tsconfig.paths.json +19 -0
  111. package/templates/types/oj-modules.d.ts +17 -0
  112. package/templates/vitest.config.ts +22 -0
  113. package/templates/web.config.ts +23 -13
  114. package/vendor/host-versions.json +3 -3
  115. package/templates/api/src/auth/manifest.yaml +0 -5
  116. package/templates/api/src/system/manifest.yaml +0 -5
  117. package/templates/api/tests/fixture.ts +0 -58
  118. package/templates/api/tsconfig.json +0 -8
  119. package/templates/api/vitest.config.ts +0 -12
  120. package/templates/logs/task-events.jsonl +0 -0
  121. /package/templates/{api/.ojm-api-exempt.json → .ojm-api-exempt.json} +0 -0
  122. /package/templates/{api/seed.mysql.sql → seed.mysql.sql} +0 -0
  123. /package/templates/{api/seed.pg.sql → seed.pg.sql} +0 -0
  124. /package/templates/{api/src/_platform → src/_platform/api}/README.md +0 -0
  125. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/atomic.ts +0 -0
  126. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/history.ts +0 -0
  127. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/lock.ts +0 -0
  128. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/semver.ts +0 -0
  129. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/sha384.ts +0 -0
  130. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/state.ts +0 -0
  131. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/deploy-web.ts +0 -0
  132. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/download.ts +0 -0
  133. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/extract.ts +0 -0
  134. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/migrate.ts +0 -0
  135. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/preflight.ts +0 -0
  136. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/register.ts +0 -0
  137. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/reload.ts +0 -0
  138. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/verify.ts +0 -0
  139. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tasks.ts +0 -0
  140. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/text-edit.ts +0 -0
  141. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tgz-guard.ts +0 -0
  142. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/trust.ts +0 -0
  143. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/types.ts +0 -0
  144. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/uninstall.ts +0 -0
  145. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-checks.ts +0 -0
  146. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-report.ts +0 -0
  147. /package/templates/{api/src/_platform → src/_platform/api}/_shared/respond.ts +0 -0
  148. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/contacts.ts +0 -0
  149. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/roles.ts +0 -0
  150. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/settings.ts +0 -0
  151. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/solution-perms.ts +0 -0
  152. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/users.ts +0 -0
  153. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/access.ts +0 -0
  154. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/audit.ts +0 -0
  155. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/guard.ts +0 -0
  156. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/oplog.ts +0 -0
  157. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/query.ts +0 -0
  158. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/rate.ts +0 -0
  159. /package/templates/{api/src/_platform → src/_platform/api}/_shared/site-facade.ts +0 -0
  160. /package/templates/{api/src/_platform → src/_platform/api}/contacts/api.ts +0 -0
  161. /package/templates/{api/src/_platform → src/_platform/api}/contacts/detail/api.ts +0 -0
  162. /package/templates/{api/src/_platform → src/_platform/api}/fixtures/l1-test-user.sql +0 -0
  163. /package/templates/{api/src/_platform → src/_platform/api}/manifest.yaml +0 -0
  164. /package/templates/{api/src/_platform → src/_platform/api}/market/bootstrap.ts +0 -0
  165. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.mysql.sql +0 -0
  166. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.pg.sql +0 -0
  167. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.sql +0 -0
  168. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
  169. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.sql +0 -0
  170. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.mysql.sql +0 -0
  171. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.sql +0 -0
  172. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.mysql.sql +0 -0
  173. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.pg.sql +0 -0
  174. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.sql +0 -0
  175. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.mysql.sql +0 -0
  176. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.sql +0 -0
  177. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
  178. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.sql +0 -0
  179. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0007__users_email.sql +0 -0
  180. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
  181. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.pg.sql +0 -0
  182. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.sql +0 -0
  183. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.mysql.sql +0 -0
  184. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.pg.sql +0 -0
  185. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.sql +0 -0
  186. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.mysql.sql +0 -0
  187. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.pg.sql +0 -0
  188. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.sql +0 -0
  189. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
  190. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.sql +0 -0
  191. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.mysql.sql +0 -0
  192. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.pg.sql +0 -0
  193. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.sql +0 -0
  194. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0013__users_profile.sql +0 -0
  195. /package/templates/{api/src/_platform → src/_platform/api}/schema.yaml +0 -0
  196. /package/templates/{api/src/_platform → src/_platform/api}/seed.mysql.sql +0 -0
  197. /package/templates/{api/src/_platform → src/_platform/api}/seed.pg.sql +0 -0
  198. /package/templates/{api/src/_platform → src/_platform/api}/seed.sql +0 -0
  199. /package/templates/{web/src/_platform → src/_platform/web}/__client/api.schemas.ts +0 -0
  200. /package/templates/{web/src/_platform → src/_platform/web}/__client/api.ts +0 -0
  201. /package/templates/{web/src/about → src/about/web}/entry.ts +0 -0
  202. /package/templates/{web/src/about → src/about/web}/locales/en-US.json +0 -0
  203. /package/templates/{web/src/about → src/about/web}/locales/zh-CN.json +0 -0
  204. /package/templates/{web/src/about → src/about/web}/pages/constants.ts +0 -0
  205. /package/templates/{web/src/about → src/about/web}/pages/index.tsx +0 -0
  206. /package/templates/{web/src/access → src/access/web}/entry.ts +0 -0
  207. /package/templates/{web/src/access → src/access/web}/locales/en-US.json +0 -0
  208. /package/templates/{web/src/access → src/access/web}/locales/zh-CN.json +0 -0
  209. /package/templates/{web/src/access → src/access/web}/pages/access-mode/index.tsx +0 -0
  210. /package/templates/{web/src/access → src/access/web}/pages/admin-visible/index.tsx +0 -0
  211. /package/templates/{web/src/access → src/access/web}/pages/button-control/index.tsx +0 -0
  212. /package/templates/{web/src/access → src/access/web}/pages/common-visible/index.tsx +0 -0
  213. /package/templates/{web/src/access → src/access/web}/pages/page-control/index.tsx +0 -0
  214. /package/templates/{api/src/auth → src/auth/api}/_shared/ldap-login.ts +0 -0
  215. /package/templates/{api/src/auth → src/auth/api}/_shared/ldap.ts +0 -0
  216. /package/templates/{web/src/auth → src/auth/web}/__client/api.schemas.ts +0 -0
  217. /package/templates/{web/src/auth → src/auth/web}/__client/api.ts +0 -0
  218. /package/templates/{api/src/demo → src/demo/api}/manifest.yaml +0 -0
  219. /package/templates/{api/src/demo → src/demo/api}/migrations/0001__create_todos.sql +0 -0
  220. /package/templates/{api/src/demo → src/demo/api}/schema.yaml +0 -0
  221. /package/templates/{api/src/demo → src/demo/api}/seed.sql +0 -0
  222. /package/templates/{api/src/demo → src/demo/api}/todos/api.ts +0 -0
  223. /package/templates/{web/src/demo → src/demo/web}/__client/api.schemas.ts +0 -0
  224. /package/templates/{web/src/demo → src/demo/web}/__client/api.ts +0 -0
  225. /package/templates/{web/src/demo → src/demo/web}/entry.ts +0 -0
  226. /package/templates/{web/src/demo → src/demo/web}/locales/en-US.json +0 -0
  227. /package/templates/{web/src/demo → src/demo/web}/locales/zh-CN.json +0 -0
  228. /package/templates/{web/src/demo → src/demo/web}/pages/index.tsx +0 -0
  229. /package/templates/{web/src/exception → src/exception/web}/entry.ts +0 -0
  230. /package/templates/{web/src/exception → src/exception/web}/locales/en-US.json +0 -0
  231. /package/templates/{web/src/exception → src/exception/web}/locales/zh-CN.json +0 -0
  232. /package/templates/{web/src/exception → src/exception/web}/pages/403/index.tsx +0 -0
  233. /package/templates/{web/src/exception → src/exception/web}/pages/404/index.tsx +0 -0
  234. /package/templates/{web/src/exception → src/exception/web}/pages/500/index.tsx +0 -0
  235. /package/templates/{web/src/exception → src/exception/web}/pages/unknown-component/index.tsx +0 -0
  236. /package/templates/{api/src/home → src/home/api}/line/api.ts +0 -0
  237. /package/templates/{api/src/home → src/home/api}/manifest.yaml +0 -0
  238. /package/templates/{api/src/home → src/home/api}/migrations/0001__create_home_tables.sql +0 -0
  239. /package/templates/{api/src/home → src/home/api}/pie/api.ts +0 -0
  240. /package/templates/{api/src/home → src/home/api}/schema.yaml +0 -0
  241. /package/templates/{api/src/home → src/home/api}/seed.sql +0 -0
  242. /package/templates/{web/src/home → src/home/web}/__client/api.schemas.ts +0 -0
  243. /package/templates/{web/src/home → src/home/web}/__client/api.ts +0 -0
  244. /package/templates/{web/src/home → src/home/web}/entry.ts +0 -0
  245. /package/templates/{web/src/home → src/home/web}/locales/en-US.json +0 -0
  246. /package/templates/{web/src/home → src/home/web}/locales/zh-CN.json +0 -0
  247. /package/templates/{web/src/home → src/home/web}/pages/components/bar-chart.tsx +0 -0
  248. /package/templates/{web/src/home → src/home/web}/pages/components/card-list.tsx +0 -0
  249. /package/templates/{web/src/home → src/home/web}/pages/components/line-chart.tsx +0 -0
  250. /package/templates/{web/src/home → src/home/web}/pages/components/pie-chart.tsx +0 -0
  251. /package/templates/{web/src/home → src/home/web}/pages/index.tsx +0 -0
  252. /package/templates/{web/src/login → src/login/web}/entry.ts +0 -0
  253. /package/templates/{web/src/login → src/login/web}/locales/en-US.json +0 -0
  254. /package/templates/{web/src/login → src/login/web}/locales/zh-CN.json +0 -0
  255. /package/templates/{web/src/login → src/login/web}/pages/change-password.tsx +0 -0
  256. /package/templates/{web/src/login → src/login/web}/pages/login.tsx +0 -0
  257. /package/templates/{api/src/notification → src/notification/api}/_shared/notify.ts +0 -0
  258. /package/templates/{api/src/notification → src/notification/api}/api.ts +0 -0
  259. /package/templates/{api/src/notification → src/notification/api}/del/api.ts +0 -0
  260. /package/templates/{api/src/notification → src/notification/api}/manifest.yaml +0 -0
  261. /package/templates/{api/src/notification → src/notification/api}/migrations/0001__create_notifications.sql +0 -0
  262. /package/templates/{api/src/notification → src/notification/api}/migrations/0002__notifications_user.sql +0 -0
  263. /package/templates/{api/src/notification → src/notification/api}/migrations/0003__notifications_broadcast.sql +0 -0
  264. /package/templates/{api/src/notification → src/notification/api}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
  265. /package/templates/{api/src/notification → src/notification/api}/schema.yaml +0 -0
  266. /package/templates/{api/src/notification → src/notification/api}/seed.sql +0 -0
  267. /package/templates/{web/src/notification → src/notification/web}/__client/api.schemas.ts +0 -0
  268. /package/templates/{web/src/notification → src/notification/web}/__client/api.ts +0 -0
  269. /package/templates/{web/src/notification → src/notification/web}/entry.ts +0 -0
  270. /package/templates/{web/src/notification → src/notification/web}/locales/en-US.json +0 -0
  271. /package/templates/{web/src/notification → src/notification/web}/locales/zh-CN.json +0 -0
  272. /package/templates/{web/src/notification → src/notification/web}/pages/center/index.tsx +0 -0
  273. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/CardGrid.test.tsx +0 -0
  274. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/HistoryDrawer.test.tsx +0 -0
  275. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/RollbackDialog.test.tsx +0 -0
  276. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/UninstallConfirm.test.tsx +0 -0
  277. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/poll.test.ts +0 -0
  278. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/render.tsx +0 -0
  279. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/runtime-stub.ts +0 -0
  280. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/semver.test.ts +0 -0
  281. /package/templates/{web/src/oj-market → src/oj-market/web}/api/MarketClientProvider.tsx +0 -0
  282. /package/templates/{web/src/oj-market → src/oj-market/web}/api/context.ts +0 -0
  283. /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardGrid.tsx +0 -0
  284. /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardItem.tsx +0 -0
  285. /package/templates/{web/src/oj-market → src/oj-market/web}/components/DetailDrawer.tsx +0 -0
  286. /package/templates/{web/src/oj-market → src/oj-market/web}/components/HistoryDrawer.tsx +0 -0
  287. /package/templates/{web/src/oj-market → src/oj-market/web}/components/RollbackDialog.tsx +0 -0
  288. /package/templates/{web/src/oj-market → src/oj-market/web}/components/UninstallConfirm.tsx +0 -0
  289. /package/templates/{web/src/oj-market → src/oj-market/web}/entry.ts +0 -0
  290. /package/templates/{web/src/oj-market → src/oj-market/web}/pages/MarketApp.tsx +0 -0
  291. /package/templates/{web/src/oj-market → src/oj-market/web}/utils/poll.ts +0 -0
  292. /package/templates/{web/src/oj-market → src/oj-market/web}/utils/semver.ts +0 -0
  293. /package/templates/{web/src/outside → src/outside/web}/entry.ts +0 -0
  294. /package/templates/{web/src/outside → src/outside/web}/locales/en-US.json +0 -0
  295. /package/templates/{web/src/outside → src/outside/web}/locales/zh-CN.json +0 -0
  296. /package/templates/{api/src/personal-center → src/personal-center/api}/manifest.yaml +0 -0
  297. /package/templates/{api/src/personal-center → src/personal-center/api}/profile/api.ts +0 -0
  298. /package/templates/{api/src/personal-center → src/personal-center/api}/upload/api.ts +0 -0
  299. /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.schemas.ts +0 -0
  300. /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.ts +0 -0
  301. /package/templates/{web/src/personal-center → src/personal-center/web}/entry.ts +0 -0
  302. /package/templates/{web/src/personal-center → src/personal-center/web}/locales/en-US.json +0 -0
  303. /package/templates/{web/src/personal-center → src/personal-center/web}/locales/zh-CN.json +0 -0
  304. /package/templates/{web/src/personal-center → src/personal-center/web}/pages/my-profile/index.tsx +0 -0
  305. /package/templates/{web/src/route-nest → src/route-nest/web}/entry.ts +0 -0
  306. /package/templates/{web/src/route-nest → src/route-nest/web}/locales/en-US.json +0 -0
  307. /package/templates/{web/src/route-nest → src/route-nest/web}/locales/zh-CN.json +0 -0
  308. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-1/index.tsx +0 -0
  309. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-2/index.tsx +0 -0
  310. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu2/index.tsx +0 -0
  311. /package/templates/{web/src → src}/styles.css +0 -0
  312. /package/templates/{web/src/system → src/system/web}/__client/api.schemas.ts +0 -0
  313. /package/templates/{web/src/system → src/system/web}/__client/api.ts +0 -0
  314. /package/templates/{web/src/system → src/system/web}/entry.ts +0 -0
  315. /package/templates/{web/src/system → src/system/web}/locales/en-US.json +0 -0
  316. /package/templates/{web/src/system → src/system/web}/locales/zh-CN.json +0 -0
  317. /package/templates/{web/src/system → src/system/web}/pages/errors.ts +0 -0
  318. /package/templates/{web/src/system → src/system/web}/pages/forbidden.tsx +0 -0
  319. /package/templates/{web/src/system → src/system/web}/pages/login-methods/index.tsx +0 -0
  320. /package/templates/{web/src/system → src/system/web}/pages/oplog/index.tsx +0 -0
  321. /package/templates/{web/src/system → src/system/web}/pages/roles/index.tsx +0 -0
  322. /package/templates/{web/src/system → src/system/web}/pages/settings/index.tsx +0 -0
  323. /package/templates/{web/src/system → src/system/web}/pages/users/index.tsx +0 -0
  324. /package/templates/{web/src/system → src/system/web}/pages/users/roles.tsx +0 -0
  325. /package/templates/{api/src/web → src/web/api}/get-async-routes/api.ts +0 -0
  326. /package/templates/{api/src/web → src/web/api}/hello/api.ts +0 -0
  327. /package/templates/{api/src/web → src/web/api}/manifest.yaml +0 -0
  328. /package/templates/{api/src/web → src/web/api}/user-info/api.ts +0 -0
  329. /package/templates/{web/src → src/web}/web/__client/api.schemas.ts +0 -0
  330. /package/templates/{web/src → src/web}/web/__client/api.ts +0 -0
  331. /package/templates/{api/tests → tests}/l1-builder.test.ts +0 -0
  332. /package/templates/{api/tests → tests}/rbac-tables.spec.ts +0 -0
@@ -2,7 +2,7 @@
2
2
  # 本文件由 ojm init 生成;config 内相对路径相对本文件所在目录。
3
3
  # 更多能力见手册 §10:vars / broker / tasks / oidc / ws / html_meta / secrets。
4
4
  #
5
- # ---- 证书与密钥的唯一落点:`./config/`(= api/config/,本文件同级的子目录)----
5
+ # ---- 证书与密钥的唯一落点:`./config/`(= 工程根/config/,本文件同级的子目录)----
6
6
  # 三件套由 `ojm init` 现场签发(dev 缺证时 `ojm dev` 也会补签),逐把的作用与后果:
7
7
  #
8
8
  # ① private.pem —— 证书签发私钥(RSA 2048)。只用来给 ③ 签名,**oj 不读它**,
@@ -25,19 +25,24 @@
25
25
  # 站点挂载(oj v0.1.58 起顶层扁平表:一行=一个 URL 前缀+一个目录,api/web 恰好其一)。
26
26
  # 被删除的 server.api_prefix / app_spa_fallback 已由 mounts 取代;
27
27
  # ojm dev 的 SPA 深链回落由 ojm 自建静态层负责;ojm preview 为单服务形态,
28
- # 现场生成派生 config(api/.ojm-preview.yaml,注入下面的 web 挂载 + spa: true),无需手改。
28
+ # 现场生成派生 config(.ojm-preview.yaml,注入下面的 web 挂载 + spa: true),无需手改。
29
+ #
30
+ # **这里没有 api 挂载是有意的**:本工程是 `src/<name>/{api,web}` 布局,模块分散在
31
+ # `src/<name>/api` 下,而 mounts 的 api 项按 `<目录>/<模块>/manifest.yaml` 单层解析、
32
+ # 不会递归(实测报 `module 'x' missing manifest.yaml`)。源码形态由 `ojm dev` 用
33
+ # `--module-root src` 供给,产物形态由 `ojm preview` 用 `--api-path api/dist` 供给;
34
+ # 两者与 mounts 的 api 项同时出现还会报「module declared in multiple places」。
35
+ # 手写部署(不经 ojm)时才需要自己加回一条 `- prefix: /api` + `api: dist`。
29
36
  mounts:
30
- - prefix: /api # api 挂载:/api/<module>/...(目录相对 config 所在目录 → api/src)
31
- api: src
32
37
  # 本地模拟 CDN:直出工程根 cdn/(内容见 cdn/README.md)。
33
38
  # 红线:生产请删掉这一条挂载,并把 system 设置 framework.market_cdn_url 指向真 CDN。
34
39
  # 不得给此挂载设 spa——缺文件必须返回 404,回落 index.html 会让市场解析崩。
35
40
  - prefix: /cdn
36
- web: ../cdn
41
+ web: ./cdn
37
42
  # 生产若由 oj 直出前端(前后端同进程,即 ojm preview 的生产形态),
38
43
  # 取消下一行注释并先 build 出 web/dist(preview 会自动注入等价挂载,无需手改):
39
44
  # - prefix: "/"
40
- # web: "../web/dist" # 相对 api/config.yaml → 项目根/web/dist
45
+ # web: "./web/dist" # 相对 config.yaml → 工程根/web/dist
41
46
  # spa: true
42
47
  # 嵌套 handler 组(oj v0.1.64)无需配置开关,组织方式见 api.ts 顶部注释。
43
48
 
@@ -84,8 +89,8 @@ platform:
84
89
  # 平台特权模块名单:方案安装器的 reloading 段调 server.reloadConfig 触发热更。
85
90
  # 缺这条 → 装在最后一步报 NotCapable: module '_platform' is not platform-privileged。
86
91
  modules: [_platform]
87
- # 受控挂载根 = api 挂载目录(dev 形态 src)。ojm preview 派生 config 时会改写为 dist,
88
- # 保持与 `--api-path` 同源;手写部署请按实际模块根填写。
92
+ # 受控根 = 后端模块源码根(`src/<name>/api` 的父目录,即 src)。ojm preview 派生 config
93
+ # 时会改写为 dist,保持与 `--api-path` 同源;手写部署请按实际模块根填写。
89
94
  root: src
90
95
  # ---- U6 信任库加固(P3-1;P2-2 红线 spike 结论)----
91
96
  # 威胁:devkit 0.1.75 语义下 fs: 段对全部模块生效(jail = fs.root),且 trusted_paths 只增不减——
@@ -840,11 +840,12 @@ interface OjRabbitClient {
840
840
  metadata: () => Promise<any>
841
841
  }
842
842
 
843
- // oj 的导入别名 `#x`(本模块根)/ `#/m/x`(src 根)是「引用方所在模块」相对的,
844
- // tsconfig 的 `paths` 无法表达模块相对解析,只能枚举模块目录(见 tsconfig.json),
845
- // 因此仅能命中**已存在**的文件。无法静态定位的别名(如尚未创建的 `#_shared/view`)
846
- // 由 `types/oj-modules.d.ts`(非模块,真 ambient `declare module "#*"`)兜底为 `any`
847
- // 而非报 TS2307;已存在、能被 `paths` 命中的别名仍走真实文件类型。运行时由 oj 的
848
- // loader 解析,本文件与该兜底只影响编辑器 / tsc。
843
+ // oj 的导入别名 `#x`(本模块根)/ `#/<模块名>/x`(目标模块根)是「引用方所在模块」
844
+ // 相对的,tsconfig 的 `paths` 表达不了模块相对解析,只能按模块枚举
845
+ // (`tsconfig.paths.json`,由 `ojm init` / `ojm build` / `ojm dev` 按 `src/*/api/manifest.yaml`
846
+ // 生成,被 `src/tsconfig.json` extends),因此仅能命中**已存在**的文件。无法静态定位
847
+ // 的别名(如尚未创建的 `#_shared/view`)由 `types/oj-modules.d.ts`(非模块,真 ambient
848
+ // `declare module "#*"`)兜底为 `any` 而非报 TS2307;已存在、能被 `paths` 命中的别名
849
+ // 仍走真实文件类型。运行时由 oj 的 loader 解析,本文件与该兜底只影响编辑器 / tsc。
849
850
 
850
851
  export {};
@@ -8,7 +8,7 @@
8
8
  # 2. 模板自运行专属文件——templates 本身也是一个可 `pnpm dev` 的工程,
9
9
  # 它的 package.json / node_modules / bin / lock 绝不能泄漏进用户工程;
10
10
  # package.json 由 init 现场生成(钉死真实版本,不带 workspace:/catalog:)。
11
- # 3. 构建与运行期产物——api/dist、web/dist、api/logs、签发的私钥与 sqlite;
11
+ # 3. 构建与运行期产物——api/dist、web/dist、logs、签发的私钥与 sqlite;
12
12
  # 模板跑过 build/dev 后会留在本地,既不进用户工程,更不该让所有工程
13
13
  # 共享同一把私钥。
14
14
  # 4. 模板自用元数据——本清单自身。
@@ -32,43 +32,48 @@ include:
32
32
  - tsconfig.json
33
33
  - global.d.ts
34
34
  - env.d.ts
35
+ # 别名类型兜底(`declare module "#*"`,非模块文件)。`tsconfig.paths.json` 不在此列:
36
+ # 它由 init 按目标工程的模块清单**现场生成**(模板兼作 demo 工程,多出的模块不该带过去)
37
+ - types
35
38
  - web.config.ts
36
39
  - openapi.json
37
- - web/src/styles.css
38
- # —— api 后端:配置 / 种子 / 测试 / 默认模块 ——
39
- - api/config.yaml
40
- - api/.ojm-api-exempt.json
41
- - api/seed.mysql.sql
42
- - api/seed.pg.sql
43
- - api/vitest.config.ts
44
- - api/tests
45
- - api/src/_platform
46
- - api/src/auth
47
- - api/src/notification
48
- - api/src/personal-center
49
- - api/src/system
50
- - api/src/web
40
+ - src/styles.css
41
+ # 后端 typecheck 工程(package.json 的 typecheck:api 指向它);只收后端、排除 web
42
+ - src/tsconfig.json
43
+ # —— 后端:配置 / 种子 / 测试 / 默认模块(模块源码在 src/<name>/api)——
44
+ - config.yaml
45
+ - .ojm-api-exempt.json
46
+ - seed.mysql.sql
47
+ - seed.pg.sql
48
+ - vitest.config.ts
49
+ - tests
50
+ - src/_platform/api
51
+ - src/auth/api
52
+ - src/notification/api
53
+ - src/personal-center/api
54
+ - src/system/api
55
+ - src/web/api
51
56
  # —— web 前端模块(demo/home 只列页面源码,__client 见顶部说明)——
52
- - web/src/_platform
53
- - web/src/auth
54
- - web/src/demo/entry.ts
55
- - web/src/demo/locales
56
- - web/src/demo/pages
57
- - web/src/home/entry.ts
58
- - web/src/home/locales
59
- - web/src/home/pages
60
- - web/src/login
61
- - web/src/notification
57
+ - src/_platform/web
58
+ - src/auth/web
59
+ - src/demo/web/entry.ts
60
+ - src/demo/web/locales
61
+ - src/demo/web/pages
62
+ - src/home/web/entry.ts
63
+ - src/home/web/locales
64
+ - src/home/web/pages
65
+ - src/login/web
66
+ - src/notification/web
62
67
  # 解决方案市场:默认第一方能力。逐子路径收录——__tests__ 不进脚手架
63
68
  # (用户工程无 web 侧 vitest 基建,拷了是死文件)
64
- - web/src/oj-market/entry.ts
65
- - web/src/oj-market/types.ts
66
- - web/src/oj-market/api
67
- - web/src/oj-market/components
68
- - web/src/oj-market/hooks
69
- - web/src/oj-market/locales
70
- - web/src/oj-market/pages
71
- - web/src/oj-market/utils
72
- - web/src/personal-center
73
- - web/src/system
74
- - web/src/web
69
+ - src/oj-market/web/entry.ts
70
+ - src/oj-market/web/types.ts
71
+ - src/oj-market/web/api
72
+ - src/oj-market/web/components
73
+ - src/oj-market/web/hooks
74
+ - src/oj-market/web/locales
75
+ - src/oj-market/web/pages
76
+ - src/oj-market/web/utils
77
+ - src/personal-center/web
78
+ - src/system/web
79
+ - src/web/web
@@ -9,7 +9,7 @@
9
9
  "preview": "ojm preview",
10
10
  "info": "ojm info",
11
11
  "typecheck": "tsc --noEmit -p tsconfig.json",
12
- "typecheck:api": "tsc --noEmit -p api/tsconfig.json"
12
+ "typecheck:api": "tsc --noEmit -p src/tsconfig.json"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@ant-design/cssinjs": "catalog:",
@@ -89,6 +89,8 @@ export interface InstallRequest extends InstallContext {
89
89
  expected?: string[]
90
90
  /** 安装器内部通道(外部调用方忽略):终态审计读 status/failedStage/taskId + migrate 捕获 appliedSeq */
91
91
  outcome?: { taskId?: string, status?: TaskStatus, failedStage?: InstallStage, appliedSeq?: number | null, cancelled?: boolean }
92
+ /** 安装器内部通道(reloading → smoke):本次新追加进 auth.anonymous_paths 的路径(restart_pending,探活对其 401 判待重启) */
93
+ deferredAnonymous?: string[]
92
94
  /**
93
95
  * 取消安装令牌(U5,P3-8):运行期外部置位(端点 `?cancel=<taskId>` 或任务状态 `cancelling` 驱动的闭包),
94
96
  * 返回 true 即中止——PIPELINE 循环每段前探测 + migrating 段 spawn 流逐块探测;幂等、可重入
@@ -347,6 +349,8 @@ async function reloadStage(req: InstallRequest): Promise<StageResult> {
347
349
  const anonymous = Array.isArray(api.anonymous) ? api.anonymous.filter((x): x is string => typeof x === "string") : [];
348
350
  const patched = anonymous.length > 0 ? appendAnonymousPaths(bumped.content, anonymous) : { content: bumped.content, added: [] };
349
351
  await atomicWriteText(path, patched.content);
352
+ // 交接给 smoke:本次真正新增的匿名路径在重启前恒 401(restart_pending)
353
+ req.deferredAnonymous = patched.added;
350
354
  }
351
355
  catch (e) {
352
356
  return { stage: "reloading", ok: false, retryable: true, message: `热更失败:config.yaml 安装序号自增/匿名端点接线失败(${String(e)})——reload unchanged 判定基于 config 文档 sha,无变更则热更不可能生效(B-11,PRD E5 口径)` };
@@ -30,6 +30,12 @@ export interface SmokeContext extends InstallContext {
30
30
  modulesJsonPath: string
31
31
  /** 注册快照查询;缺省回落 queryRegistered(isolate db 全局;测试注入 stub) */
32
32
  query?: (name: string) => Promise<SolutionSnapshot | null>
33
+ /**
34
+ * 本次安装**新追加**进 config.yaml `auth.anonymous_paths` 的路径(reloading 段注入,带前导 /)。
35
+ * 匿名接线属 restart_pending(reload 段已明示),本进程内尚未生效——探活对这些路径的
36
+ * 401 判「待重启」而非失败;已在配置里却仍 401 才是真失败(说明接线或守卫有问题)。
37
+ */
38
+ deferredAnonymous?: string[]
33
39
  }
34
40
 
35
41
  export interface StageResult {
@@ -116,6 +122,7 @@ export async function smoke(ctx: SmokeContext): Promise<StageResult> {
116
122
  const web = isObj(m.web) ? m.web : undefined;
117
123
 
118
124
  // ① 探活:health 优先(2xx),缺省首个非匿名路由(连通即可)
125
+ const notes: string[] = [];
119
126
  try {
120
127
  const health = typeof m.health === "string" && m.health !== "" ? m.health : undefined;
121
128
  const target = health ?? firstNonAnonymousRoute(m);
@@ -123,7 +130,18 @@ export async function smoke(ctx: SmokeContext): Promise<StageResult> {
123
130
  const url = probeUrlOf(ctx.apiPrefix, target);
124
131
  const res = await ctx.fetch(url);
125
132
  if (health !== undefined && !res.ok) {
126
- failures.push({ item: "探活", retryable: true, reason: `health 端点 ${url} 返回 HTTP ${res.status}(C2-33 冒烟探针要求 2xx)` });
133
+ // 本次安装新增的匿名接线尚未生效(restart_pending):此处的 401 反映的是
134
+ // 「宿主还没重启」,不是包或站点的问题——判待重启并落人话提示,不判失败。
135
+ const rel = (s: string) => s.replace(/^\/+/, "");
136
+ const healthRel = rel(health);
137
+ const pending = (ctx.deferredAnonymous ?? []).some(a => healthRel === rel(a) || healthRel.startsWith(`${rel(a)}/`));
138
+ const deferred = res.status === 401 && pending;
139
+ if (deferred) {
140
+ notes.push(`探活 ${url} 返回 401:该端点是本次安装新增的匿名路径(auth.anonymous_paths),匿名接线属 restart_pending——重启宿主后生效,不计入失败`);
141
+ }
142
+ else {
143
+ failures.push({ item: "探活", retryable: true, reason: `health 端点 ${url} 返回 HTTP ${res.status}(C2-33 冒烟探针要求 2xx)` });
144
+ }
127
145
  }
128
146
  }
129
147
  }
@@ -205,7 +223,8 @@ export async function smoke(ctx: SmokeContext): Promise<StageResult> {
205
223
  // ④ entry 浏览器加载不在安装器验(设计 §4.1)——无判定,口径钉于成功 message。
206
224
 
207
225
  if (failures.length === 0) {
208
- return { stage: "smoke", ok: true, message: "冒烟通过:探活 / integrity / 注册一致性 三项全过(entry 浏览器加载不在安装器验——设计 §4.1,宿主 error boundary + P2 深检兜底)" };
226
+ const passed = "冒烟通过:探活 / integrity / 注册一致性 三项全过(entry 浏览器加载不在安装器验——设计 §4.1,宿主 error boundary + P2 深检兜底)";
227
+ return { stage: "smoke", ok: true, message: notes.length === 0 ? passed : `${passed}\n${notes.map(n => `- ${n}`).join("\n")}` };
209
228
  }
210
229
  return {
211
230
  stage: "smoke",
@@ -52,9 +52,9 @@ export interface MarketEnv {
52
52
  trustPath: string
53
53
  /** sqlite 库文件路径(R14 备份源;模板 config.yaml `sqlite://db.sqlite` → 站点根 db.sqlite) */
54
54
  dbPath: string
55
- /** 站点 web 模块目录(<dist>/modules) */
55
+ /** 站点 web 模块目录(站点产物根下的 modules/) */
56
56
  siteModulesDir: string
57
- /** 站点 modules.json 路径(与 siteModulesDir 同属 <dist>) */
57
+ /** 站点 modules.json 路径(与 siteModulesDir 同属站点产物根) */
58
58
  modulesJsonPath: string
59
59
  /** API 前缀(smoke 探针 URL 前缀) */
60
60
  apiPrefix: string
@@ -78,17 +78,20 @@ export interface MarketEnv {
78
78
  /** 迁移账本 head 真源(P1-16 接线①:缺省读 oj journal 表 `_oj_migrations` max(version),查询失败 → null 不猜) */
79
79
  ledgerHeadOf?: (name: string) => Promise<number | bigint | null>
80
80
  /**
81
- * 宿主 versions.json(C2-09 peerRuntime 比对真源,键 = 包名)。缺省读 <dist>/versions.json
82
- * (ojm 宿主构建产物,与 modules.json 同目录,B-11 接线);不可得 → undefined,manifest 声明
83
- * peerRuntime 即预检 fail-closed 拒装(C2-09 语义保持)。
81
+ * 宿主 versions.json(C2-09 peerRuntime 比对真源,键 = 包名)。缺省读 `<sharedRoot>/versions.json`
82
+ * (ojm 宿主构建产物:与 manifests.yaml 同层,即 release 根,B-11 接线);不可得 → undefined,
83
+ * manifest 声明 peerRuntime 即预检 fail-closed 拒装(C2-09 语义保持)。
84
84
  */
85
85
  hostVersions?: Record<string, string>
86
86
  }
87
87
 
88
- /** 宿主 versions.json 布局推导(B-11):与 modules.json 同目录(<dist>/versions.json) */
89
- export function versionsJsonPathOf(modulesJsonPath: string): string {
90
- const slash = modulesJsonPath.lastIndexOf("/");
91
- return slash === -1 ? "versions.json" : `${modulesJsonPath.slice(0, slash)}/versions.json`;
88
+ /**
89
+ * 宿主 versions.json 布局推导(B-11):与 manifests.yaml 同层(release 根 = `sharedRoot`),
90
+ * 不是站点产物根——两处根在 `src/<name>/{api,web}` 布局下已分流(`api/dist` vs `web/dist`)。
91
+ */
92
+ export function versionsJsonPathOf(releaseRoot: string): string {
93
+ const trimmed = releaseRoot.replace(/\/+$/, "");
94
+ return trimmed === "" ? "versions.json" : `${trimmed}/versions.json`;
92
95
  }
93
96
 
94
97
  /** 宿主 versions.json 解析(纯函数):{packageName: version} 全字符串表;任何违约 → undefined(fail-closed 不猜) */
@@ -113,30 +116,37 @@ export function parseVersionsJson(text: string): Record<string, string> | undefi
113
116
  return out;
114
117
  }
115
118
 
116
- /** hostVersions 注入缺省:显式注入优先,否则读 <dist>/versions.json;不可得 → undefined(C2-09 fail-closed) */
119
+ /** hostVersions 注入缺省:显式注入优先,否则读 `<sharedRoot>/versions.json`;不可得 → undefined(C2-09 fail-closed) */
117
120
  async function hostVersionsOf(env: MarketEnv): Promise<Record<string, string> | undefined> {
118
121
  if (env.hostVersions !== undefined) {
119
122
  return env.hostVersions;
120
123
  }
121
124
  try {
122
- return parseVersionsJson(new TextDecoder().decode(await fsFace().readFile(versionsJsonPathOf(env.modulesJsonPath))));
125
+ return parseVersionsJson(new TextDecoder().decode(await fsFace().readFile(versionsJsonPathOf(env.sharedRoot))));
123
126
  }
124
127
  catch {
125
128
  return undefined;
126
129
  }
127
130
  }
128
131
 
129
- /** oj serve release 布局生产缺省(P1-16 B3/B4;全字段可覆盖) */
132
+ /**
133
+ * oj serve release 布局生产缺省(P1-16 B3/B4;全字段可覆盖)。
134
+ *
135
+ * 两处根在 `src/<name>/{api,web}` 布局下分流,且都相对 fs jail 根(= config.yaml 所在目录):
136
+ * release 根 `api/dist` —— 版本目录 / manifests.yaml / versions.json / migrations(后端产物)
137
+ * 站点根 `web/dist` —— modules/ + modules.json(前端产物,安装器 upsert 的落点)
138
+ * 暂存 / 任务 / 状态 / 信任库 / db 仍在 jail 根下:它们是安装器私有面,不属任何产物根。
139
+ */
130
140
  export function marketEnv(): MarketEnv {
131
141
  return {
132
- sharedRoot: "dist",
142
+ sharedRoot: "api/dist",
133
143
  staging: "modules/.staging",
134
144
  taskDir: "modules/.install-tasks",
135
145
  statePath: "modules/.solutions/state.yaml",
136
146
  trustPath: ".solutions/trusted-keys.yaml",
137
147
  dbPath: "db.sqlite",
138
- siteModulesDir: "dist/modules",
139
- modulesJsonPath: "dist/modules.json",
148
+ siteModulesDir: "web/dist/modules",
149
+ modulesJsonPath: "web/dist/modules.json",
140
150
  apiPrefix: "/api",
141
151
  ledgerHeadOf: journalHeadOf,
142
152
  };
@@ -30,12 +30,16 @@ import { journalHeadOf, siteOrigin } from "../_shared/site-facade";
30
30
  /** 线格式行(与 packages/runtime/src/api/rbac/types.ts 的 SolutionRouteRow 对齐) */
31
31
  export type { SolutionRouteRow } from "../_lifecycle/verify-checks";
32
32
 
33
- /** oj serve release 布局生产缺省 */
33
+ /**
34
+ * oj serve release 布局生产缺省(与 marketEnv 同口径,见其注释):
35
+ * sharedRoot = 后端 release 根(版本目录 / migrations)
36
+ * modulesJsonPath = 站点产物根下的 modules.json(web 入口 integrity 真源)
37
+ */
34
38
  export function solutionsEnv(): SolutionsEnv {
35
39
  return {
36
- sharedRoot: "dist",
40
+ sharedRoot: "api/dist",
37
41
  statePath: "modules/.solutions/state.yaml",
38
- modulesJsonPath: "dist/modules.json",
42
+ modulesJsonPath: "web/dist/modules.json",
39
43
  apiPrefix: "/api",
40
44
  };
41
45
  }
@@ -1,7 +1,7 @@
1
1
  // 登录限速编排(B5/AC1-26):auth/login 与 auth/ldap-login 共用的 (IP, username) 双维度守卫 + 失败记账。
2
2
  // 状态全在 DB(sys_login_throttle/sys_rate_window,迁移 0009)——oj 每请求重建模块,进程内方案恒失效(§9.3-3)。
3
- import { auditOp } from "../../_platform/_shared/service/infra/audit";
4
- import { loginBlocked, loginFailed, loginSucceeded, rateLimit } from "../../_platform/_shared/service/infra/rate";
3
+ import { auditOp } from "#/_platform/_shared/service/infra/audit";
4
+ import { loginBlocked, loginFailed, loginSucceeded, rateLimit } from "#/_platform/_shared/service/infra/rate";
5
5
 
6
6
  /** 客户端 IP:优先 X-Forwarded-For(反代后),回落 X-Real-IP。 */
7
7
  export async function clientIp(): Promise<string> {
@@ -4,8 +4,8 @@
4
4
  // B4:access JWT 仅载 sub(roles 走登录响应/资料接口,不进 claims)。
5
5
  // B3:refresh = 不透明随机串 + DB 存 SHA-256 哈希(sys_refresh_token,迁移 0008);
6
6
  // 轮换作旧 + 滑动窗口(绝对 TTL 封顶 min(旧 exp, now+30d));reuse 检测全族吊销 + 审计。
7
- import { invalidateUser } from "../../_platform/_shared/service/infra/access";
8
- import { recordOp } from "../../_platform/_shared/service/infra/audit";
7
+ import { invalidateUser } from "#/_platform/_shared/service/infra/access";
8
+ import { recordOp } from "#/_platform/_shared/service/infra/audit";
9
9
 
10
10
  export interface SessionDeps {
11
11
  randomHex: (bytes: number) => string
@@ -1,4 +1,4 @@
1
- import { respond } from "../_platform/_shared/respond";
1
+ import { respond } from "#/_platform/_shared/respond";
2
2
  // oj 嵌套 handler 组:组名 = 路径段(login → POST /api/auth/login,路由不变)。
3
3
  // 阶段 B 登录链(D14/AC1-12/13/27):领域逻辑全在 _shared/session.ts(createSession 注入 deps),
4
4
  // handler 只编排:取参 → 调 service → json 信封;B5 限速收敛在 _shared/login-throttle.ts(与 ldap-login 共用)。
@@ -1,6 +1,6 @@
1
1
  // POST /api/auth/ldap-login —— LDAP 目录登录(D1/AC1-12;匿名端点,E6:anonymous_paths 写 /auth/ldap-login)。
2
2
  // 编排:B5 限速守卫(与 auth/login 同维度)→ ldapLogin 九步 → respond 统一异常出口(§9.3-1)。
3
- import { respond } from "../../_platform/_shared/respond";
3
+ import { respond } from "#/_platform/_shared/respond";
4
4
  import { createLdapLogin, runtimeLdapGateway } from "../_shared/ldap-login";
5
5
  import { clientIp, guardLoginThrottle, noteLoginFailed, noteLoginSucceeded } from "../_shared/login-throttle";
6
6
  import { createSession } from "../_shared/session";
@@ -0,0 +1,6 @@
1
+ name: auth
2
+ desc: JWT 鉴权端点(业务路由):login/refresh/logout;Bearer 守卫由 oj-auth 提供
3
+ version: 0.1.0
4
+ deps: # 两处都用:login/refresh 读 _platform.users(ownership_guard: deny,§3/S003);
5
+ # 跨模块别名 `#/_platform/…` 的依赖门禁(oj build S008)
6
+ _platform: ^0.1.0
@@ -1,5 +1,6 @@
1
1
  import type { IndexRow, TaskRecord } from "../types";
2
- import { screen, waitFor } from "@testing-library/react";
2
+ import { fireEvent, screen, waitFor } from "@testing-library/react";
3
+ import { useState } from "react";
3
4
  import { describe, expect, it } from "vitest";
4
5
  import { InstallDialog } from "../components/InstallDialog";
5
6
  import { InMemoryMarketClient } from "./marketClient.fake";
@@ -69,4 +70,55 @@ describe("installDialog 安装进度(BDD)", () => {
69
70
  expect(client.calls).toContain("install:alpha@1.1.0");
70
71
  expect(screen.getByText("回滚中")).toBeTruthy();
71
72
  });
73
+
74
+ it("given needs_trust 终态 When 打开 Then 渲染指纹与确认按钮,点击后入库并重试安装", async () => {
75
+ const pending: TaskRecord = {
76
+ id: "t1",
77
+ name: "alpha",
78
+ version: "1.2.0",
79
+ status: "needs_trust",
80
+ stage: "verifying",
81
+ progress: 18,
82
+ message: "待信任确认",
83
+ stages: [],
84
+ trust: { key_id: "alpha.author", public_key: "ab".repeat(32), fingerprint: "6afc".repeat(16) },
85
+ startedAt: 0,
86
+ };
87
+ const client = new InMemoryMarketClient({ taskRecord: pending, installResult: { task_id: "t1" } });
88
+ renderWithProviders(<InstallDialog row={row} open onClose={() => {}} />, client);
89
+ const button = await screen.findByText("指纹一致,信任并重试");
90
+ expect(screen.getByTestId("install-trust-fingerprint").textContent).toContain("6afc".repeat(16));
91
+ fireEvent.click(button);
92
+ await waitFor(() => expect(client.calls).toContain("trust:t1"));
93
+ // 确认入库后自动重试同一安装(staging 未清场,服务端续跑)
94
+ await waitFor(() => expect(client.calls.filter(c => c.startsWith("install:")).length).toBe(2));
95
+ });
96
+
97
+ it("given 终态 When 父级每次渲染都换 onDone 身份 Then 只通告一次(防 onDone→重渲染→onDone 死循环)", async () => {
98
+ const done: TaskRecord = {
99
+ id: "t1",
100
+ name: "alpha",
101
+ version: "1.2.0",
102
+ status: "done",
103
+ stage: "done",
104
+ progress: 100,
105
+ message: "ok",
106
+ stages: [],
107
+ startedAt: 0,
108
+ };
109
+ const client = new InMemoryMarketClient({ taskRecord: done, installResult: { task_id: "t1" } });
110
+ function Harness() {
111
+ const [n, setN] = useState(0);
112
+ return (
113
+ <>
114
+ <span data-testid="done-count">{n}</span>
115
+ <InstallDialog row={row} open onClose={() => {}} onDone={() => setN(v => v + 1)} />
116
+ </>
117
+ );
118
+ }
119
+ renderWithProviders(<Harness />, client);
120
+ await waitFor(() => expect(screen.getByTestId("install-message").textContent).toBe("ok"), { timeout: 5000 });
121
+ await new Promise(resolve => setTimeout(resolve, 50));
122
+ expect(screen.getByTestId("done-count").textContent).toBe("1");
123
+ });
72
124
  });
@@ -11,6 +11,7 @@ export interface FakeState {
11
11
  enableResult?: { ok: boolean, message: string }
12
12
  uninstallResult?: { ok: boolean, message: string }
13
13
  installResult?: { task_id: string }
14
+ trustResult?: { result: string, key_id: string, fingerprint: string }
14
15
  }
15
16
 
16
17
  export class InMemoryMarketClient implements MarketClient {
@@ -49,6 +50,11 @@ export class InMemoryMarketClient implements MarketClient {
49
50
  } as TaskRecord);
50
51
  }
51
52
 
53
+ async trust(taskId: string) {
54
+ this.calls.push(`trust:${taskId}`);
55
+ return this.state.trustResult ?? { result: "added", key_id: "k1", fingerprint: "f".repeat(64) };
56
+ }
57
+
52
58
  async enable(input: EnableInput) {
53
59
  this.calls.push(`enable:${input.name}:${input.enabled}`);
54
60
  return this.state.enableResult ?? { ok: true, message: "ok" };
@@ -24,6 +24,11 @@ export interface MarketClient {
24
24
  install: (input: InstallInput) => Promise<{ task_id: string }>
25
25
  /** 任务轮询消费面(query 形态 ?id=) */
26
26
  task: (id: string) => Promise<TaskRecord>
27
+ /**
28
+ * TOFU 确认:把 needs_trust 任务记录里的发布方 key 写入宿主信任库(服务端自任务记录重取
29
+ * 公钥复算指纹比对,防混淆代理)。确认成功后重试同一安装即可续跑(staging 未清场)。
30
+ */
31
+ trust: (taskId: string) => Promise<{ result: string, key_id: string, fingerprint: string }>
27
32
  /** 生命周期开关(同步 setEnabled,无任务模型;{ok,message} 200 渲染 ok 位) */
28
33
  enable: (input: EnableInput) => Promise<{ ok: boolean, message: string }>
29
34
  /** 市场操作历史(_platform 只读端点 /_platform/market/history) */
@@ -25,9 +25,10 @@ import {
25
25
  platformMarketInstallPost,
26
26
  platformMarketMetaGet,
27
27
  platformMarketTaskGet,
28
+ platformMarketTrustPost,
28
29
  platformMarketUninstallImpactGet,
29
30
  platformMarketUninstallPost,
30
- } from "../../_platform/__client/api";
31
+ } from "../../../_platform/web/__client/api";
31
32
 
32
33
  // 绑定运行时 request 到生成 client 的模块级单槽(生成 client 是静态单例,必须由某处
33
34
  // bindRequest;此处兜底确保市场子应用无论挂载顺序都能拿到请求实例)。与 _platform 模块
@@ -96,6 +97,11 @@ export const httpMarketClient: MarketClient = {
96
97
  // 运行时形状由服务端真源 + DEV zod 校验保证,TS 侧以领域类型为准。
97
98
  return (await platformMarketTaskGet({ id })) as unknown as TaskRecord;
98
99
  },
100
+ async trust(taskId: string) {
101
+ // 服务端自任务记录重取公钥复算指纹比对(防混淆代理),前端只传 task_id,不传公钥
102
+ const data = await platformMarketTrustPost({ task_id: taskId });
103
+ return data as { result: string, key_id: string, fingerprint: string };
104
+ },
99
105
  async enable(input: EnableInput) {
100
106
  const data = await platformMarketEnablePost({ name: input.name, enabled: input.enabled });
101
107
  return data as { ok: boolean, message: string };