@oj-module/cli 0.2.18 → 0.2.23

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 (345) 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 +352 -168
  5. package/devkit/menu-rules.md +3 -3
  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 +59 -34
  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__/init-packagejson.test.ts +29 -0
  14. package/src/__tests__/layout.test.ts +176 -0
  15. package/src/__tests__/module-list.test.ts +66 -0
  16. package/src/__tests__/oj-caps.test.ts +114 -0
  17. package/src/alias-paths.ts +102 -0
  18. package/src/args.ts +54 -4
  19. package/src/build.ts +41 -9
  20. package/src/builtin.ts +93 -0
  21. package/src/bundle.ts +254 -38
  22. package/src/config.ts +82 -9
  23. package/src/contract/__tests__/market-client.integration.test.ts +14 -14
  24. package/src/contract/check.ts +56 -28
  25. package/src/contract/emit-stub.ts +21 -9
  26. package/src/contract/handler-scan.ts +19 -10
  27. package/src/contract/run.ts +14 -4
  28. package/src/dev.ts +80 -9
  29. package/src/index.ts +27 -7
  30. package/src/info.ts +69 -10
  31. package/src/init.ts +114 -28
  32. package/src/layout.ts +282 -24
  33. package/src/lifecycle/__tests__/args.test.ts +20 -0
  34. package/src/lifecycle/__tests__/builtin.test.ts +97 -0
  35. package/src/lifecycle/__tests__/bundle.test.ts +184 -6
  36. package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
  37. package/src/lifecycle/__tests__/new.test.ts +129 -0
  38. package/src/lifecycle/__tests__/smoke.test.ts +2 -1
  39. package/src/manifest-contract.ts +16 -3
  40. package/src/market/__tests__/bootstrap.test.ts +17 -9
  41. package/src/market/bootstrap.ts +30 -17
  42. package/src/market/publish.ts +10 -2
  43. package/src/new.ts +208 -0
  44. package/src/oj-caps.ts +133 -0
  45. package/src/oj.ts +27 -4
  46. package/src/preview-config.ts +1 -1
  47. package/src/preview.ts +2 -1
  48. package/src/site.ts +2 -2
  49. package/src/usage.ts +24 -12
  50. package/src/vendor.ts +5 -1
  51. package/templates/{api/config.yaml → config.yaml} +13 -8
  52. package/templates/global.d.ts +7 -6
  53. package/templates/init.include.yaml +41 -36
  54. package/templates/package.json +3 -1
  55. package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/installer.ts +4 -0
  56. package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/smoke.ts +21 -2
  57. package/templates/{api/src/_platform → src/_platform/api}/fixtures/l1-test-user.sql +4 -5
  58. package/templates/{api/src/_platform → src/_platform/api}/market/api.ts +25 -15
  59. package/templates/src/_platform/api/seed.mysql.sql +16 -0
  60. package/templates/src/_platform/api/seed.pg.sql +16 -0
  61. package/templates/{api/src/_platform → src/_platform/api}/seed.sql +16 -5
  62. package/templates/{api/src/_platform → src/_platform/api}/solutions/api.ts +7 -3
  63. package/templates/{api/src/auth → src/auth/api}/_shared/login-throttle.ts +2 -2
  64. package/templates/{api/src/auth → src/auth/api}/_shared/session.ts +2 -2
  65. package/templates/{api/src/auth → src/auth/api}/api.ts +1 -1
  66. package/templates/{api/src/auth → src/auth/api}/ldap-login/api.ts +1 -1
  67. package/templates/src/auth/api/manifest.yaml +6 -0
  68. package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/InstallDialog.test.tsx +53 -1
  69. package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/marketClient.fake.ts +6 -0
  70. package/templates/{web/src/oj-market → src/oj-market/web}/api/client.ts +5 -0
  71. package/templates/{web/src/oj-market → src/oj-market/web}/api/context.ts +2 -2
  72. package/templates/{web/src/oj-market → src/oj-market/web}/api/defaultClient.ts +7 -1
  73. package/templates/src/oj-market/web/components/InstallDialog.tsx +194 -0
  74. package/templates/{web/src/oj-market → src/oj-market/web}/components/RollbackDialog.tsx +7 -3
  75. package/templates/{web/src/oj-market → src/oj-market/web}/components/UninstallConfirm.tsx +18 -7
  76. package/templates/{web/src/oj-market → src/oj-market/web}/hooks/useMarket.ts +29 -12
  77. package/templates/{web/src/oj-market → src/oj-market/web}/locales/en-US.json +10 -1
  78. package/templates/{web/src/oj-market → src/oj-market/web}/locales/zh-CN.json +10 -1
  79. package/templates/{web/src/oj-market → src/oj-market/web}/types.ts +15 -2
  80. package/templates/{api/src/system → src/system/api}/login-methods/api.ts +2 -2
  81. package/templates/src/system/api/manifest.yaml +6 -0
  82. package/templates/{api/src/system → src/system/api}/oplog/api.ts +4 -4
  83. package/templates/{api/src/system → src/system/api}/permissions/api.ts +3 -3
  84. package/templates/{api/src/system → src/system/api}/roles/api.ts +3 -3
  85. package/templates/{api/src/system → src/system/api}/roles/bindings/api.ts +3 -3
  86. package/templates/{api/src/system → src/system/api}/settings/api.ts +3 -3
  87. package/templates/{api/src/system → src/system/api}/users/api.ts +3 -3
  88. package/templates/{api/src/system → src/system/api}/users/forbidden/api.ts +3 -3
  89. package/templates/{api/src/system → src/system/api}/users/password/api.ts +3 -3
  90. package/templates/{api/src/system → src/system/api}/users/roles/api.ts +3 -3
  91. package/templates/{web/src/system → src/system/web}/locales/en-US.json +1 -1
  92. package/templates/{web/src/system → src/system/web}/locales/zh-CN.json +1 -1
  93. package/templates/src/tsconfig.json +16 -0
  94. package/templates/{api/tests → tests}/auth-ldap.spec.ts +2 -2
  95. package/templates/{api/tests → tests}/auth-session.spec.ts +3 -3
  96. package/templates/{api/tests → tests}/auth-throttle.spec.ts +1 -1
  97. package/templates/tests/fixture.ts +434 -0
  98. package/templates/{api/tests → tests}/guard.spec.ts +1 -1
  99. package/templates/{api/tests → tests}/l1-builder.test.ts +2 -2
  100. package/templates/{api/tests → tests}/lifecycle/atomic-state.spec.ts +3 -3
  101. package/templates/{api/tests → tests}/lifecycle/bootstrap.spec.ts +8 -4
  102. package/templates/{api/tests → tests}/lifecycle/deploy-smoke.spec.ts +31 -8
  103. package/templates/{api/tests → tests}/lifecycle/installer.spec.ts +15 -15
  104. package/templates/{api/tests → tests}/lifecycle/market-api.spec.ts +30 -29
  105. package/templates/{api/tests → tests}/lifecycle/migrate.spec.ts +6 -6
  106. package/templates/{api/tests → tests}/lifecycle/preflight.spec.ts +4 -4
  107. package/templates/{api/tests → tests}/lifecycle/register.spec.ts +1 -1
  108. package/templates/{api/tests → tests}/lifecycle/semver.spec.ts +1 -1
  109. package/templates/{api/tests → tests}/lifecycle/sha384.spec.ts +1 -1
  110. package/templates/{api/tests → tests}/lifecycle/sig-fixtures.spec.ts +1 -1
  111. package/templates/{api/tests → tests}/lifecycle/solutions-api.spec.ts +1 -1
  112. package/templates/{api/tests → tests}/lifecycle/steps-fs.spec.ts +12 -12
  113. package/templates/{api/tests → tests}/lifecycle/text-edit.spec.ts +1 -1
  114. package/templates/{api/tests → tests}/lifecycle/tgz-guard.spec.ts +1 -1
  115. package/templates/{api/tests → tests}/lifecycle/trust.spec.ts +5 -5
  116. package/templates/{api/tests → tests}/lifecycle/uninstall.spec.ts +7 -7
  117. package/templates/{api/tests → tests}/lifecycle/verify-checks.spec.ts +9 -6
  118. package/templates/{api/tests → tests}/lifecycle/verify-report.spec.ts +7 -5
  119. package/templates/{api/tests → tests}/query-utils.spec.ts +1 -1
  120. package/templates/{api/tests → tests}/rbac-audit.spec.ts +1 -1
  121. package/templates/{api/tests → tests}/rbac-contacts.spec.ts +2 -2
  122. package/templates/{api/tests → tests}/rbac-guard.spec.ts +1 -1
  123. package/templates/{api/tests → tests}/rbac-roles.spec.ts +2 -2
  124. package/templates/{api/tests → tests}/rbac-seed.spec.ts +13 -11
  125. package/templates/{api/tests → tests}/rbac-settings.spec.ts +2 -2
  126. package/templates/{api/tests → tests}/rbac-solution-perms.spec.ts +1 -1
  127. package/templates/{api/tests → tests}/rbac-users.spec.ts +3 -3
  128. package/templates/tests/seed-role-accounts.spec.ts +87 -0
  129. package/templates/tsconfig.json +5 -1
  130. package/templates/tsconfig.paths.json +19 -0
  131. package/templates/types/oj-modules.d.ts +17 -0
  132. package/templates/vitest.config.ts +22 -0
  133. package/templates/web.config.ts +23 -13
  134. package/vendor/host-versions.json +3 -3
  135. package/templates/api/src/_platform/seed.mysql.sql +0 -8
  136. package/templates/api/src/_platform/seed.pg.sql +0 -8
  137. package/templates/api/src/auth/manifest.yaml +0 -5
  138. package/templates/api/src/system/manifest.yaml +0 -5
  139. package/templates/api/tests/fixture.ts +0 -58
  140. package/templates/api/tsconfig.json +0 -8
  141. package/templates/api/vitest.config.ts +0 -12
  142. package/templates/logs/task-events.jsonl +0 -0
  143. package/templates/web/src/oj-market/components/InstallDialog.tsx +0 -112
  144. /package/templates/{api/.ojm-api-exempt.json → .ojm-api-exempt.json} +0 -0
  145. /package/templates/{api/seed.mysql.sql → seed.mysql.sql} +0 -0
  146. /package/templates/{api/seed.pg.sql → seed.pg.sql} +0 -0
  147. /package/templates/{api/src/_platform → src/_platform/api}/README.md +0 -0
  148. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/atomic.ts +0 -0
  149. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/history.ts +0 -0
  150. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/lock.ts +0 -0
  151. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/semver.ts +0 -0
  152. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/sha384.ts +0 -0
  153. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/state.ts +0 -0
  154. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/deploy-web.ts +0 -0
  155. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/download.ts +0 -0
  156. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/extract.ts +0 -0
  157. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/migrate.ts +0 -0
  158. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/preflight.ts +0 -0
  159. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/register.ts +0 -0
  160. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/reload.ts +0 -0
  161. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/verify.ts +0 -0
  162. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tasks.ts +0 -0
  163. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/text-edit.ts +0 -0
  164. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tgz-guard.ts +0 -0
  165. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/trust.ts +0 -0
  166. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/types.ts +0 -0
  167. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/uninstall.ts +0 -0
  168. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-checks.ts +0 -0
  169. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-report.ts +0 -0
  170. /package/templates/{api/src/_platform → src/_platform/api}/_shared/respond.ts +0 -0
  171. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/contacts.ts +0 -0
  172. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/roles.ts +0 -0
  173. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/settings.ts +0 -0
  174. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/solution-perms.ts +0 -0
  175. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/users.ts +0 -0
  176. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/access.ts +0 -0
  177. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/audit.ts +0 -0
  178. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/guard.ts +0 -0
  179. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/oplog.ts +0 -0
  180. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/query.ts +0 -0
  181. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/rate.ts +0 -0
  182. /package/templates/{api/src/_platform → src/_platform/api}/_shared/site-facade.ts +0 -0
  183. /package/templates/{api/src/_platform → src/_platform/api}/contacts/api.ts +0 -0
  184. /package/templates/{api/src/_platform → src/_platform/api}/contacts/detail/api.ts +0 -0
  185. /package/templates/{api/src/_platform → src/_platform/api}/manifest.yaml +0 -0
  186. /package/templates/{api/src/_platform → src/_platform/api}/market/bootstrap.ts +0 -0
  187. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.mysql.sql +0 -0
  188. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.pg.sql +0 -0
  189. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.sql +0 -0
  190. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
  191. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.sql +0 -0
  192. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.mysql.sql +0 -0
  193. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.sql +0 -0
  194. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.mysql.sql +0 -0
  195. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.pg.sql +0 -0
  196. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.sql +0 -0
  197. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.mysql.sql +0 -0
  198. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.sql +0 -0
  199. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
  200. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.sql +0 -0
  201. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0007__users_email.sql +0 -0
  202. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
  203. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.pg.sql +0 -0
  204. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.sql +0 -0
  205. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.mysql.sql +0 -0
  206. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.pg.sql +0 -0
  207. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.sql +0 -0
  208. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.mysql.sql +0 -0
  209. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.pg.sql +0 -0
  210. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.sql +0 -0
  211. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
  212. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.sql +0 -0
  213. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.mysql.sql +0 -0
  214. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.pg.sql +0 -0
  215. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.sql +0 -0
  216. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0013__users_profile.sql +0 -0
  217. /package/templates/{api/src/_platform → src/_platform/api}/schema.yaml +0 -0
  218. /package/templates/{web/src/_platform → src/_platform/web}/__client/api.schemas.ts +0 -0
  219. /package/templates/{web/src/_platform → src/_platform/web}/__client/api.ts +0 -0
  220. /package/templates/{web/src/about → src/about/web}/entry.ts +0 -0
  221. /package/templates/{web/src/about → src/about/web}/locales/en-US.json +0 -0
  222. /package/templates/{web/src/about → src/about/web}/locales/zh-CN.json +0 -0
  223. /package/templates/{web/src/about → src/about/web}/pages/constants.ts +0 -0
  224. /package/templates/{web/src/about → src/about/web}/pages/index.tsx +0 -0
  225. /package/templates/{web/src/access → src/access/web}/entry.ts +0 -0
  226. /package/templates/{web/src/access → src/access/web}/locales/en-US.json +0 -0
  227. /package/templates/{web/src/access → src/access/web}/locales/zh-CN.json +0 -0
  228. /package/templates/{web/src/access → src/access/web}/pages/access-mode/index.tsx +0 -0
  229. /package/templates/{web/src/access → src/access/web}/pages/admin-visible/index.tsx +0 -0
  230. /package/templates/{web/src/access → src/access/web}/pages/button-control/index.tsx +0 -0
  231. /package/templates/{web/src/access → src/access/web}/pages/common-visible/index.tsx +0 -0
  232. /package/templates/{web/src/access → src/access/web}/pages/page-control/index.tsx +0 -0
  233. /package/templates/{api/src/auth → src/auth/api}/_shared/ldap-login.ts +0 -0
  234. /package/templates/{api/src/auth → src/auth/api}/_shared/ldap.ts +0 -0
  235. /package/templates/{web/src/auth → src/auth/web}/__client/api.schemas.ts +0 -0
  236. /package/templates/{web/src/auth → src/auth/web}/__client/api.ts +0 -0
  237. /package/templates/{api/src/demo → src/demo/api}/manifest.yaml +0 -0
  238. /package/templates/{api/src/demo → src/demo/api}/migrations/0001__create_todos.sql +0 -0
  239. /package/templates/{api/src/demo → src/demo/api}/schema.yaml +0 -0
  240. /package/templates/{api/src/demo → src/demo/api}/seed.sql +0 -0
  241. /package/templates/{api/src/demo → src/demo/api}/todos/api.ts +0 -0
  242. /package/templates/{web/src/demo → src/demo/web}/__client/api.schemas.ts +0 -0
  243. /package/templates/{web/src/demo → src/demo/web}/__client/api.ts +0 -0
  244. /package/templates/{web/src/demo → src/demo/web}/entry.ts +0 -0
  245. /package/templates/{web/src/demo → src/demo/web}/locales/en-US.json +0 -0
  246. /package/templates/{web/src/demo → src/demo/web}/locales/zh-CN.json +0 -0
  247. /package/templates/{web/src/demo → src/demo/web}/pages/index.tsx +0 -0
  248. /package/templates/{web/src/exception → src/exception/web}/entry.ts +0 -0
  249. /package/templates/{web/src/exception → src/exception/web}/locales/en-US.json +0 -0
  250. /package/templates/{web/src/exception → src/exception/web}/locales/zh-CN.json +0 -0
  251. /package/templates/{web/src/exception → src/exception/web}/pages/403/index.tsx +0 -0
  252. /package/templates/{web/src/exception → src/exception/web}/pages/404/index.tsx +0 -0
  253. /package/templates/{web/src/exception → src/exception/web}/pages/500/index.tsx +0 -0
  254. /package/templates/{web/src/exception → src/exception/web}/pages/unknown-component/index.tsx +0 -0
  255. /package/templates/{api/src/home → src/home/api}/line/api.ts +0 -0
  256. /package/templates/{api/src/home → src/home/api}/manifest.yaml +0 -0
  257. /package/templates/{api/src/home → src/home/api}/migrations/0001__create_home_tables.sql +0 -0
  258. /package/templates/{api/src/home → src/home/api}/pie/api.ts +0 -0
  259. /package/templates/{api/src/home → src/home/api}/schema.yaml +0 -0
  260. /package/templates/{api/src/home → src/home/api}/seed.sql +0 -0
  261. /package/templates/{web/src/home → src/home/web}/__client/api.schemas.ts +0 -0
  262. /package/templates/{web/src/home → src/home/web}/__client/api.ts +0 -0
  263. /package/templates/{web/src/home → src/home/web}/entry.ts +0 -0
  264. /package/templates/{web/src/home → src/home/web}/locales/en-US.json +0 -0
  265. /package/templates/{web/src/home → src/home/web}/locales/zh-CN.json +0 -0
  266. /package/templates/{web/src/home → src/home/web}/pages/components/bar-chart.tsx +0 -0
  267. /package/templates/{web/src/home → src/home/web}/pages/components/card-list.tsx +0 -0
  268. /package/templates/{web/src/home → src/home/web}/pages/components/line-chart.tsx +0 -0
  269. /package/templates/{web/src/home → src/home/web}/pages/components/pie-chart.tsx +0 -0
  270. /package/templates/{web/src/home → src/home/web}/pages/index.tsx +0 -0
  271. /package/templates/{web/src/login → src/login/web}/entry.ts +0 -0
  272. /package/templates/{web/src/login → src/login/web}/locales/en-US.json +0 -0
  273. /package/templates/{web/src/login → src/login/web}/locales/zh-CN.json +0 -0
  274. /package/templates/{web/src/login → src/login/web}/pages/change-password.tsx +0 -0
  275. /package/templates/{web/src/login → src/login/web}/pages/login.tsx +0 -0
  276. /package/templates/{api/src/notification → src/notification/api}/_shared/notify.ts +0 -0
  277. /package/templates/{api/src/notification → src/notification/api}/api.ts +0 -0
  278. /package/templates/{api/src/notification → src/notification/api}/del/api.ts +0 -0
  279. /package/templates/{api/src/notification → src/notification/api}/manifest.yaml +0 -0
  280. /package/templates/{api/src/notification → src/notification/api}/migrations/0001__create_notifications.sql +0 -0
  281. /package/templates/{api/src/notification → src/notification/api}/migrations/0002__notifications_user.sql +0 -0
  282. /package/templates/{api/src/notification → src/notification/api}/migrations/0003__notifications_broadcast.sql +0 -0
  283. /package/templates/{api/src/notification → src/notification/api}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
  284. /package/templates/{api/src/notification → src/notification/api}/schema.yaml +0 -0
  285. /package/templates/{api/src/notification → src/notification/api}/seed.sql +0 -0
  286. /package/templates/{web/src/notification → src/notification/web}/__client/api.schemas.ts +0 -0
  287. /package/templates/{web/src/notification → src/notification/web}/__client/api.ts +0 -0
  288. /package/templates/{web/src/notification → src/notification/web}/entry.ts +0 -0
  289. /package/templates/{web/src/notification → src/notification/web}/locales/en-US.json +0 -0
  290. /package/templates/{web/src/notification → src/notification/web}/locales/zh-CN.json +0 -0
  291. /package/templates/{web/src/notification → src/notification/web}/pages/center/index.tsx +0 -0
  292. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/CardGrid.test.tsx +0 -0
  293. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/HistoryDrawer.test.tsx +0 -0
  294. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/RollbackDialog.test.tsx +0 -0
  295. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/UninstallConfirm.test.tsx +0 -0
  296. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/poll.test.ts +0 -0
  297. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/render.tsx +0 -0
  298. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/runtime-stub.ts +0 -0
  299. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/semver.test.ts +0 -0
  300. /package/templates/{web/src/oj-market → src/oj-market/web}/api/MarketClientProvider.tsx +0 -0
  301. /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardGrid.tsx +0 -0
  302. /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardItem.tsx +0 -0
  303. /package/templates/{web/src/oj-market → src/oj-market/web}/components/DetailDrawer.tsx +0 -0
  304. /package/templates/{web/src/oj-market → src/oj-market/web}/components/HistoryDrawer.tsx +0 -0
  305. /package/templates/{web/src/oj-market → src/oj-market/web}/entry.ts +0 -0
  306. /package/templates/{web/src/oj-market → src/oj-market/web}/pages/MarketApp.tsx +0 -0
  307. /package/templates/{web/src/oj-market → src/oj-market/web}/utils/poll.ts +0 -0
  308. /package/templates/{web/src/oj-market → src/oj-market/web}/utils/semver.ts +0 -0
  309. /package/templates/{web/src/outside → src/outside/web}/entry.ts +0 -0
  310. /package/templates/{web/src/outside → src/outside/web}/locales/en-US.json +0 -0
  311. /package/templates/{web/src/outside → src/outside/web}/locales/zh-CN.json +0 -0
  312. /package/templates/{api/src/personal-center → src/personal-center/api}/manifest.yaml +0 -0
  313. /package/templates/{api/src/personal-center → src/personal-center/api}/profile/api.ts +0 -0
  314. /package/templates/{api/src/personal-center → src/personal-center/api}/upload/api.ts +0 -0
  315. /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.schemas.ts +0 -0
  316. /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.ts +0 -0
  317. /package/templates/{web/src/personal-center → src/personal-center/web}/entry.ts +0 -0
  318. /package/templates/{web/src/personal-center → src/personal-center/web}/locales/en-US.json +0 -0
  319. /package/templates/{web/src/personal-center → src/personal-center/web}/locales/zh-CN.json +0 -0
  320. /package/templates/{web/src/personal-center → src/personal-center/web}/pages/my-profile/index.tsx +0 -0
  321. /package/templates/{web/src/route-nest → src/route-nest/web}/entry.ts +0 -0
  322. /package/templates/{web/src/route-nest → src/route-nest/web}/locales/en-US.json +0 -0
  323. /package/templates/{web/src/route-nest → src/route-nest/web}/locales/zh-CN.json +0 -0
  324. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-1/index.tsx +0 -0
  325. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-2/index.tsx +0 -0
  326. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu2/index.tsx +0 -0
  327. /package/templates/{web/src → src}/styles.css +0 -0
  328. /package/templates/{web/src/system → src/system/web}/__client/api.schemas.ts +0 -0
  329. /package/templates/{web/src/system → src/system/web}/__client/api.ts +0 -0
  330. /package/templates/{web/src/system → src/system/web}/entry.ts +0 -0
  331. /package/templates/{web/src/system → src/system/web}/pages/errors.ts +0 -0
  332. /package/templates/{web/src/system → src/system/web}/pages/forbidden.tsx +0 -0
  333. /package/templates/{web/src/system → src/system/web}/pages/login-methods/index.tsx +0 -0
  334. /package/templates/{web/src/system → src/system/web}/pages/oplog/index.tsx +0 -0
  335. /package/templates/{web/src/system → src/system/web}/pages/roles/index.tsx +0 -0
  336. /package/templates/{web/src/system → src/system/web}/pages/settings/index.tsx +0 -0
  337. /package/templates/{web/src/system → src/system/web}/pages/users/index.tsx +0 -0
  338. /package/templates/{web/src/system → src/system/web}/pages/users/roles.tsx +0 -0
  339. /package/templates/{api/src/web → src/web/api}/get-async-routes/api.ts +0 -0
  340. /package/templates/{api/src/web → src/web/api}/hello/api.ts +0 -0
  341. /package/templates/{api/src/web → src/web/api}/manifest.yaml +0 -0
  342. /package/templates/{api/src/web → src/web/api}/user-info/api.ts +0 -0
  343. /package/templates/{web/src → src/web}/web/__client/api.schemas.ts +0 -0
  344. /package/templates/{web/src → src/web}/web/__client/api.ts +0 -0
  345. /package/templates/{api/tests → tests}/rbac-tables.spec.ts +0 -0
@@ -0,0 +1,194 @@
1
+ import type { IndexRow, TaskRecord } from "../types";
2
+ import { Alert, Button, Modal, Progress, Space, Typography } from "antd";
3
+ import React, { useEffect, useRef, useState } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+ import { useMarketClient } from "../api/context";
6
+ import { useInstallTask } from "../hooks/useMarket";
7
+
8
+ export interface InstallDialogProps {
9
+ row: IndexRow | null
10
+ open: boolean
11
+ onClose: () => void
12
+ onDone?: (record: TaskRecord) => void
13
+ /** 显式目标版本(回滚用);缺省取 row.latest(安装/升级) */
14
+ version?: string | null
15
+ /** 动作模式:安装/升级/回滚(仅影响标题文案;回滚 = 对旧版降级安装) */
16
+ mode?: "install" | "upgrade" | "rollback"
17
+ }
18
+
19
+ const isTerminal = (r: TaskRecord | null) => r != null && r.status !== "running";
20
+
21
+ /**
22
+ * 阶段失败指引(契约附录 A 的 `lifecycle.stage.*` key)→ 本模块文案。
23
+ * 附录 A 的这批 key 在模板 locales 里尚未落地,直接 t() 会把裸 key 显示给运维,
24
+ * 故此处显式映射;未收录的 key 保持原样(不吞信息)。
25
+ */
26
+ const HINT_KEYS: Record<string, string> = {
27
+ "lifecycle.stage.smoke.hint": "oj-market:task:hintSmoke",
28
+ "lifecycle.stage.cancelled": "oj-market:task:hintCancelled",
29
+ "lifecycle.stage.reloading.hint": "oj-market:task:hintReloading",
30
+ };
31
+
32
+ /**
33
+ * 安装/升级/回滚对话框:触发 POST install → 800ms 轮询 TaskRecord 终态渲染(reason+retryable+hintKey 双语)。
34
+ * 回滚 = 对已装旧版降级安装(显式 version);降版迁移差异校验(R12)的可读失败由终态 error 渲染。
35
+ */
36
+ export function InstallDialog({ row, open, onClose, onDone, version, mode }: InstallDialogProps) {
37
+ const { t } = useTranslation();
38
+ const client = useMarketClient();
39
+ const [taskId, setTaskId] = useState<string | null>(null);
40
+ const [started, setStarted] = useState(false);
41
+ const [trusting, setTrusting] = useState(false);
42
+ const [trustError, setTrustError] = useState<string | null>(null);
43
+ const { record, done } = useInstallTask(taskId);
44
+ // onDone 对同一任务只通告一次:父级每次渲染都会换 onDone 身份,若只靠 deps 触发,
45
+ // 终态后 onDone → index.retry() → 父级重渲染 → onDone 身份变 → 再触发,会形成
46
+ // 无限请求循环(浏览器侧表现为 ERR_INSUFFICIENT_RESOURCES)。
47
+ const notifiedTaskIdRef = useRef<string | null>(null);
48
+
49
+ const targetVersion = version ?? row?.latest ?? null;
50
+ const title
51
+ = mode === "rollback"
52
+ ? t("oj-market:rollback:running")
53
+ : row?.installed
54
+ ? t("oj-market:card:upgrade")
55
+ : t("oj-market:task:installing");
56
+
57
+ // 打开即触发安装(version 缺省取 latest;已装视为升级;显式 version = 回滚降级安装)。
58
+ // 注:下面的同步 set 均为「弹窗打开/关闭生命周期」的有意重置与触发守卫;
59
+ // 曾尝试渲染期派生重置,但同组件 notifier effect 里的 onDone?.(record) 会触发
60
+ // eslint-react set-state-in-effect 的误判(prop 回调被解析回组件函数),故保留原结构。
61
+ useEffect(() => {
62
+ if (open && row && !started) {
63
+ // eslint-disable-next-line react/set-state-in-effect -- 有意:打开弹窗时置「已触发」守卫,保证安装只发起一次
64
+ setStarted(true);
65
+ client
66
+ .install({ name: row.name, version: targetVersion, source: "cdn" })
67
+ .then(res => setTaskId(res.task_id))
68
+ .catch(e => setTaskId(`__error__${e instanceof Error ? e.message : String(e)}`));
69
+ }
70
+ if (!open) {
71
+ // eslint-disable-next-line react/set-state-in-effect -- 有意:关闭弹窗时清空任务,下次打开是全新一次安装
72
+ setTaskId(null);
73
+ // eslint-disable-next-line react/set-state-in-effect -- 有意:关闭时复位触发守卫,配合上行清场
74
+ setStarted(false);
75
+ // eslint-disable-next-line react/set-state-in-effect -- 有意:关闭时清掉信任错误,避免残留到下次打开
76
+ setTrustError(null);
77
+ notifiedTaskIdRef.current = null;
78
+ }
79
+ }, [open, row, started, client, targetVersion]);
80
+
81
+ useEffect(() => {
82
+ if (!record || !isTerminal(record) || !done)
83
+ return;
84
+ if (notifiedTaskIdRef.current === record.id)
85
+ return;
86
+ notifiedTaskIdRef.current = record.id;
87
+ onDone?.(record);
88
+ }, [record, done, onDone]);
89
+
90
+ // TOFU 确认:确认入库后重试同一安装(服务端 staging 未清场,续跑不重下)
91
+ const handleTrustConfirm = async () => {
92
+ if (taskId === null || taskId.startsWith("__error__"))
93
+ return;
94
+ setTrusting(true);
95
+ setTrustError(null);
96
+ try {
97
+ await client.trust(taskId);
98
+ notifiedTaskIdRef.current = null;
99
+ setTaskId(null);
100
+ setStarted(false);
101
+ }
102
+ catch (e) {
103
+ setTrustError(e instanceof Error ? e.message : String(e));
104
+ }
105
+ finally {
106
+ setTrusting(false);
107
+ }
108
+ };
109
+
110
+ const terminal = isTerminal(record);
111
+ const needsTrust = record?.status === "needs_trust";
112
+ const succeeded = record?.status === "done";
113
+ const failed = record?.status === "failed" || record?.status === "partial";
114
+ const errorTask = typeof taskId === "string" && taskId.startsWith("__error__");
115
+
116
+ return (
117
+ <Modal
118
+ title={title}
119
+ open={open}
120
+ onCancel={onClose}
121
+ footer={terminal ? <Button onClick={onClose}>{t("oj-market:task:close")}</Button> : null}
122
+ data-testid="install-dialog"
123
+ >
124
+ {errorTask && <Alert type="error" message={taskId!.slice("__error__".length)} />}
125
+ {!errorTask && !record && (
126
+ <Typography.Text>
127
+ {t("oj-market:task:installing")}
128
+ …
129
+ </Typography.Text>
130
+ )}
131
+ {record && (
132
+ <>
133
+ <Progress percent={record.progress} status={terminal ? (succeeded ? "success" : "exception") : "active"} />
134
+ <div data-testid="install-stage">{record.stage ? t(`oj-market:task:stage.${record.stage}`, record.stage) : record.stage}</div>
135
+ <div data-testid="install-message">{record.message}</div>
136
+ {needsTrust && record?.trust && (
137
+ <Alert
138
+ style={{ marginTop: 12 }}
139
+ type="warning"
140
+ showIcon
141
+ message={t("oj-market:task:trustTitle")}
142
+ description={(
143
+ <Space direction="vertical" size={4}>
144
+ <span>{t("oj-market:task:trustBody", { keyId: record.trust.key_id })}</span>
145
+ <span data-testid="install-trust-fingerprint">
146
+ {t("oj-market:task:trustFingerprint")}
147
+ <Typography.Text code copyable={{ text: record.trust.fingerprint }}>
148
+ {record.trust.fingerprint}
149
+ </Typography.Text>
150
+ </span>
151
+ <span>{t("oj-market:task:trustHint")}</span>
152
+ </Space>
153
+ )}
154
+ />
155
+ )}
156
+ {needsTrust && trustError && <Alert style={{ marginTop: 8 }} type="error" showIcon message={trustError} />}
157
+ {needsTrust && (
158
+ <Button type="primary" style={{ marginTop: 12 }} loading={trusting} onClick={handleTrustConfirm}>
159
+ {t("oj-market:task:trustConfirm")}
160
+ </Button>
161
+ )}
162
+ {failed && record.error && (
163
+ <Alert
164
+ type="error"
165
+ showIcon
166
+ message={record.error.reason}
167
+ description={(
168
+ <Space direction="vertical">
169
+ <span>{record.error.retryable ? t("oj-market:task:retryable") : t("oj-market:task:notRetryable")}</span>
170
+ {record.error.hintKey && (
171
+ <span data-testid="install-hint">
172
+ {t(HINT_KEYS[record.error.hintKey] ?? record.error.hintKey, record.error.hintKey)}
173
+ </span>
174
+ )}
175
+ </Space>
176
+ )}
177
+ />
178
+ )}
179
+ {failed && (
180
+ <Button
181
+ style={{ marginTop: 12 }}
182
+ onClick={() => {
183
+ setTaskId(null);
184
+ setStarted(false);
185
+ }}
186
+ >
187
+ {t("oj-market:card:retry")}
188
+ </Button>
189
+ )}
190
+ </>
191
+ )}
192
+ </Modal>
193
+ );
194
+ }
@@ -1,6 +1,6 @@
1
1
  import type { IndexRow } from "../types";
2
2
  import { Alert, Modal, Radio, Space, Spin, Typography } from "antd";
3
- import React, { useEffect, useMemo, useState } from "react";
3
+ import React, { useMemo, useState } from "react";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import { useMarketHistory } from "../hooks/useMarket";
6
6
  import { compareVersions } from "../utils/semver";
@@ -34,9 +34,13 @@ export function RollbackDialog({ row, open, onClose, onRollback }: RollbackDialo
34
34
  return [...versions].sort((a, b) => compareVersions(b, a)); // 较新的旧版在前
35
35
  }, [row, status, data]);
36
36
 
37
- useEffect(() => {
37
+ // 候选集变化(历史加载完成/切换方案)时默认选中最新旧版;
38
+ // 渲染期同步派生重置,替代 effect 内同步 set(react/set-state-in-effect)
39
+ const [prevCandidates, setPrevCandidates] = useState(candidates);
40
+ if (prevCandidates !== candidates) {
41
+ setPrevCandidates(candidates);
38
42
  setSelected(candidates.length ? candidates[0] : null);
39
- }, [candidates]);
43
+ }
40
44
 
41
45
  const loading = status === "loading";
42
46
 
@@ -19,22 +19,33 @@ export function UninstallConfirm({ row, open, onClose, onConfirmed }: UninstallC
19
19
  const { t } = useTranslation();
20
20
  const client = useMarketClient();
21
21
  const [impact, setImpact] = useState<UninstallImpact | null>(null);
22
- const [loading, setLoading] = useState(false);
23
22
  const [error, setError] = useState<string | null>(null);
24
23
 
24
+ // 打开(或切换方案)即清场上一轮结果;渲染期同步派生重置,替代 effect 内同步 set
25
+ const rowKey = open && row ? row.name : null;
26
+ const [prevRowKey, setPrevRowKey] = useState(rowKey);
27
+ if (prevRowKey !== rowKey) {
28
+ setPrevRowKey(rowKey);
29
+ setImpact(null);
30
+ setError(null);
31
+ }
32
+
25
33
  useEffect(() => {
26
34
  if (!open || !row)
27
35
  return;
28
- setLoading(true);
29
- setError(null);
30
- setImpact(null);
36
+ let active = true;
31
37
  client
32
38
  .uninstallImpact(row.name)
33
- .then(setImpact)
34
- .catch(e => setError(e instanceof Error ? e.message : String(e)))
35
- .finally(() => setLoading(false));
39
+ .then(i => active && setImpact(i))
40
+ .catch(e => active && setError(e instanceof Error ? e.message : String(e)));
41
+ return () => {
42
+ active = false;
43
+ };
36
44
  }, [open, row, client]);
37
45
 
46
+ // 加载态派生:已打开且尚无结果/错误即视为预检中(替代原 effect 同步 setLoading)
47
+ const loading = rowKey !== null && impact === null && error === null;
48
+
38
49
  return (
39
50
  <Modal
40
51
  title={t("oj-market:confirm:uninstallTitle")}
@@ -12,10 +12,12 @@ export type LoadState<T> = { status: "loading", data?: undefined } | { status: "
12
12
  export function useMarketIndex(): LoadState<IndexRow[]> & { retry: () => void } {
13
13
  const client = useMarketClient();
14
14
  const [state, setState] = useState<LoadState<IndexRow[]>>({ status: "loading" });
15
+ // 重试不复跑「先同步 set loading 再拉取」的加载函数(effect 内同步 set 触发
16
+ // react/set-state-in-effect),改为事件里置 loading + 递增 nonce 驱动 effect 重跑。
17
+ const [nonce, setNonce] = useState(0);
15
18
 
16
- const load = useCallback(() => {
19
+ useEffect(() => {
17
20
  let active = true;
18
- setState({ status: "loading" });
19
21
  client
20
22
  .index()
21
23
  .then(data => active && setState({ status: "ok", data }))
@@ -23,11 +25,14 @@ export function useMarketIndex(): LoadState<IndexRow[]> & { retry: () => void }
23
25
  return () => {
24
26
  active = false;
25
27
  };
26
- }, [client]);
28
+ }, [client, nonce]);
27
29
 
28
- useEffect(() => load(), [load]);
30
+ const retry = useCallback(() => {
31
+ setState({ status: "loading" });
32
+ setNonce(n => n + 1);
33
+ }, []);
29
34
 
30
- return { ...state, retry: load };
35
+ return { ...state, retry };
31
36
  }
32
37
 
33
38
  /** 任务轮询:给定 task_id,800ms 轮询至终态(done/failed/partial);超时不抛,返回当前值 */
@@ -39,12 +44,20 @@ export function useInstallTask(taskId: string | null): {
39
44
  const [record, setRecord] = useState<TaskRecord | null>(null);
40
45
  const [done, setDone] = useState(false);
41
46
 
47
+ // 无任务 id / 切换任务 = 尚无任务:清空上一次任务的记录,避免「重新安装/信任后重试」时
48
+ // 旧终态(可能是 needs_trust)残留在弹窗里被误读成本次结果。
49
+ // (渲染期同步派生重置,替代 effect 内同步 set,规避 react/set-state-in-effect)
50
+ const [prevTaskId, setPrevTaskId] = useState(taskId);
51
+ if (prevTaskId !== taskId) {
52
+ setPrevTaskId(taskId);
53
+ setRecord(null);
54
+ setDone(false);
55
+ }
56
+
42
57
  useEffect(() => {
43
58
  if (!taskId)
44
59
  return;
45
60
  let active = true;
46
- setRecord(null);
47
- setDone(false);
48
61
  poll(
49
62
  () => client.task(taskId),
50
63
  r => r.status !== "running",
@@ -75,10 +88,11 @@ export function useInstallTask(taskId: string | null): {
75
88
  export function useMarketHistory(): LoadState<MarketHistory[]> & { retry: () => void } {
76
89
  const client = useMarketClient();
77
90
  const [state, setState] = useState<LoadState<MarketHistory[]>>({ status: "loading" });
91
+ // 同 useMarketIndex:重试走 nonce 驱动,不在 effect 链路里同步 set
92
+ const [nonce, setNonce] = useState(0);
78
93
 
79
- const load = useCallback(() => {
94
+ useEffect(() => {
80
95
  let active = true;
81
- setState({ status: "loading" });
82
96
  client
83
97
  .history()
84
98
  .then(data => active && setState({ status: "ok", data }))
@@ -86,9 +100,12 @@ export function useMarketHistory(): LoadState<MarketHistory[]> & { retry: () =>
86
100
  return () => {
87
101
  active = false;
88
102
  };
89
- }, [client]);
103
+ }, [client, nonce]);
90
104
 
91
- useEffect(() => load(), [load]);
105
+ const retry = useCallback(() => {
106
+ setState({ status: "loading" });
107
+ setNonce(n => n + 1);
108
+ }, []);
92
109
 
93
- return { ...state, retry: load };
110
+ return { ...state, retry };
94
111
  }
@@ -49,12 +49,21 @@
49
49
  "running": "Running",
50
50
  "done": "Done",
51
51
  "failed": "Failed",
52
- "partial": "Partial"
52
+ "partial": "Partial",
53
+ "needs_trust": "Awaiting trust"
53
54
  },
55
+ "trustTitle": "Trust confirmation required: publisher key not in host trust store",
56
+ "trustBody": "The signing key of publisher \"{{keyId}}\" is not trusted by this host yet (first-use TOFU).",
57
+ "trustFingerprint": "Key fingerprint: ",
58
+ "trustHint": "Compare this fingerprint with the one published by the vendor; confirm only if they match. Confirming retries this install automatically and reuses the staged files.",
59
+ "trustConfirm": "Fingerprint matches — trust and retry",
54
60
  "reason": "Reason",
55
61
  "retryable": "Retryable",
56
62
  "notRetryable": "Not retryable",
57
63
  "hint": "Hint",
64
+ "hintSmoke": "Smoke probe failures usually mean one of two things: (1) the anonymous endpoints added by this install (auth.anonymous_paths) are pending a restart — restart the host and retry; (2) in dev the host already ships a module with the same name, so the route belongs to the source module and the probe is not hitting the freshly installed package.",
65
+ "hintCancelled": "This install was cancelled: lock file, version directory and staging were restored to their pre-install state. You can retry directly.",
66
+ "hintReloading": "Config hot-reload was partial: hot-applicable sections (vars, ...) are applied, while the auth section (including anonymous endpoints) needs a host restart.",
58
67
  "close": "Close",
59
68
  "viewLog": "View log"
60
69
  },
@@ -49,12 +49,21 @@
49
49
  "running": "进行中",
50
50
  "done": "完成",
51
51
  "failed": "失败",
52
- "partial": "部分完成"
52
+ "partial": "部分完成",
53
+ "needs_trust": "待信任确认"
53
54
  },
55
+ "trustTitle": "待信任确认:发布方密钥不在宿主信任库",
56
+ "trustBody": "发布方「{{keyId}}」的签名密钥尚未被本宿主信任(首次信任 TOFU)。",
57
+ "trustFingerprint": "密钥指纹:",
58
+ "trustHint": "请与发布方公布的公钥指纹逐字核对,一致才确认。确认后会自动重试本次安装,已暂存的内容不会重新下载。",
59
+ "trustConfirm": "指纹一致,信任并重试",
54
60
  "reason": "原因",
55
61
  "retryable": "可重试",
56
62
  "notRetryable": "不可重试",
57
63
  "hint": "提示",
64
+ "hintSmoke": "冒烟探活失败常见于两类原因:① 本次安装新增的匿名端点(auth.anonymous_paths)属「待重启生效」,重启宿主后再点重试即可;② dev 形态下宿主已内置同名模块,该路由由源码模块占位,探活打到的不是刚装的包。",
65
+ "hintCancelled": "本次安装已被取消:锁、版本目录与 staging 均已回到发起前状态,可直接重试。",
66
+ "hintReloading": "配置热更未完全生效:可热改段(vars 等)已应用,auth 段(含匿名端点)需重启宿主后生效。",
58
67
  "close": "关闭",
59
68
  "viewLog": "查看日志"
60
69
  },
@@ -5,8 +5,12 @@
5
5
  /** 安装阶段名(契约附录 A 十阶段逐字 + lock_commit/audit/done 终态) */
6
6
  export type InstallStage = "preflight" | "downloading" | "verifying" | "extracting" | "migrating" | "lock_commit" | "registering" | "deploying_web" | "reloading" | "smoke" | "audit" | "done";
7
7
 
8
- /** 任务状态(§10.2 三态 + partial:锁后失败部分生效) */
9
- export type TaskStatus = "running" | "done" | "failed" | "partial";
8
+ /**
9
+ * 任务状态(§10.2 三态 + partial:锁后失败部分生效 + needs_trust:未知发布方 key 待人工
10
+ * TOFU 确认,staging 不清场,确认入库后重试同一安装即可续跑)。
11
+ * needs_trust 是非成功态:消费面不得按绿色成功渲染(真源 api/src/_platform/_lifecycle/types.ts:64)。
12
+ */
13
+ export type TaskStatus = "running" | "done" | "failed" | "partial" | "needs_trust";
10
14
 
11
15
  /** 阶段明细日志(UI 详情日志区数据源) */
12
16
  export interface TaskStageLog {
@@ -22,6 +26,13 @@ export interface TaskError {
22
26
  hintKey?: string
23
27
  }
24
28
 
29
+ /** needs_trust 终态:待确认的发布方 key(指纹 = sha256(public_key) 十六进制,供人核对) */
30
+ export interface TaskTrust {
31
+ key_id: string
32
+ public_key: string
33
+ fingerprint: string
34
+ }
35
+
25
36
  /** 安装任务记录(§10.2 + stages 明细日志;落 .install-tasks/<id>.json,800ms 轮询消费面) */
26
37
  export interface TaskRecord {
27
38
  id: string
@@ -34,6 +45,8 @@ export interface TaskRecord {
34
45
  message: string
35
46
  stages: TaskStageLog[]
36
47
  error?: TaskError
48
+ /** needs_trust 终态才有:待确认的发布方 key(POST /market/trust {task_id} 消费) */
49
+ trust?: TaskTrust
37
50
  startedAt: number
38
51
  finishedAt?: number
39
52
  appliedSeq?: number
@@ -1,7 +1,7 @@
1
- import { respond } from "../../_platform/_shared/respond";
1
+ import { respond } from "#/_platform/_shared/respond";
2
2
  // GET /api/system/login-methods —— 登录方式发现(C5/D2;匿名端点:登录前无 token,
3
3
  // 项目 config.yaml auth.anonymous_paths 必须列 /system/login-methods,漏列则握手 401 登录页必坏)。
4
- import { listLoginMethods } from "../../_platform/_shared/service/domain/settings";
4
+ import { listLoginMethods } from "#/_platform/_shared/service/domain/settings";
5
5
 
6
6
  async function get() {
7
7
  await respond(async () => {
@@ -0,0 +1,6 @@
1
+ name: system
2
+ desc: 框架管理域端点(/api/system/*):角色/权限点管理(assertSystemAdmin 判权,H2)
3
+ version: 0.1.0
4
+ deps: # 两处都用:roles/bindings/permissions 读写 _platform 的 sys_* 表(ownership_guard: deny);
5
+ # 跨模块别名 `#/_platform/…` 的依赖门禁(oj build S008)
6
+ _platform: ^0.1.0
@@ -1,9 +1,9 @@
1
- import { respond } from "../../_platform/_shared/respond";
1
+ import { respond } from "#/_platform/_shared/respond";
2
2
  // GET /api/system/oplog?action=&username=&module=&limit= —— 操作日志查询(AC1-29;D13 admin 可查)。
3
3
  // POST /api/system/oplog —— 操作日志写入(C3;登录态即可,解决方案模块记录自身操作)。
4
- import { assertSystemAdmin, currentAccess } from "../../_platform/_shared/service/infra/access";
5
- import { recordOp } from "../../_platform/_shared/service/infra/audit";
6
- import { searchOplog } from "../../_platform/_shared/service/infra/oplog";
4
+ import { assertSystemAdmin, currentAccess } from "#/_platform/_shared/service/infra/access";
5
+ import { recordOp } from "#/_platform/_shared/service/infra/audit";
6
+ import { searchOplog } from "#/_platform/_shared/service/infra/oplog";
7
7
 
8
8
  async function get() {
9
9
  await respond(async () => {
@@ -1,7 +1,7 @@
1
- import { respond } from "../../_platform/_shared/respond";
2
- import { listPermissions } from "../../_platform/_shared/service/domain/roles";
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { listPermissions } from "#/_platform/_shared/service/domain/roles";
3
3
  // GET /api/system/permissions —— 权限点全量(C2 绑定编辑器数据源,deprecated 标灰)。
4
- import { assertSystemAdmin } from "../../_platform/_shared/service/infra/access";
4
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
5
5
 
6
6
  async function get() {
7
7
  await respond(async () => {
@@ -1,8 +1,8 @@
1
- import { respond } from "../../_platform/_shared/respond";
2
- import { createRole, deleteRole, listRoles } from "../../_platform/_shared/service/domain/roles";
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { createRole, deleteRole, listRoles } from "#/_platform/_shared/service/domain/roles";
3
3
  // 管理域端点 /api/system/roles(设计文档 D207:/api/system/* = 管理域,assertSystemAdmin 判权)。
4
4
  // handler 层只编排:守卫 → service → json 信封;name 走 query(wiki 先例:静态段 + http.query)。
5
- import { assertSystemAdmin } from "../../_platform/_shared/service/infra/access";
5
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
6
6
 
7
7
  async function guard(): Promise<void> {
8
8
  await assertSystemAdmin();
@@ -1,9 +1,9 @@
1
- import { respond } from "../../../_platform/_shared/respond";
2
- import { updateBindings } from "../../../_platform/_shared/service/domain/roles";
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { updateBindings } from "#/_platform/_shared/service/domain/roles";
3
3
  // PUT /api/system/roles/bindings?name=xxx —— 整集替换角色绑定。
4
4
  // AC1-18 声明角色绑定可调整;AC1-19 builtin 硬拒(service 层);AC1-30 摘绑定 = 紧急吊销出口
5
5
  // (service 内写后按持有者失效判权缓存)。
6
- import { assertSystemAdmin } from "../../../_platform/_shared/service/infra/access";
6
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
7
7
 
8
8
  async function put() {
9
9
  await respond(async () => {
@@ -1,7 +1,7 @@
1
- import { respond } from "../../_platform/_shared/respond";
2
- import { listSettings, updateSetting } from "../../_platform/_shared/service/domain/settings";
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { listSettings, updateSetting } from "#/_platform/_shared/service/domain/settings";
3
3
  // /api/system/settings —— 系统设置(PRD §4.3):GET 按组列出(secret 掩码),PUT 写值(写审计)。
4
- import { assertSystemAdmin } from "../../_platform/_shared/service/infra/access";
4
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
5
5
 
6
6
  const settingItemSchema = {
7
7
  type: "object",
@@ -1,9 +1,9 @@
1
- import { respond } from "../../_platform/_shared/respond";
2
- import { createUser, listUsers } from "../../_platform/_shared/service/domain/users";
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { createUser, listUsers } from "#/_platform/_shared/service/domain/users";
3
3
  // 管理域端点组 /api/system/users(AC1-7~10):handler 只编排——assertSystemAdmin 守卫 → service → json。
4
4
  // 保护红线在 service 层硬 enforcement:禁止操作自己、admin 不碰成员资格、最后 superadmin 兜底。
5
5
  // 子端点:roles/password/forbidden 各为独立目录(wiki per-endpoint 先例)。
6
- import { assertSystemAdmin } from "../../_platform/_shared/service/infra/access";
6
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
7
7
 
8
8
  const bcryptDeps = { bcryptHash: (pw: string) => bcrypt.hash(pw) };
9
9
 
@@ -1,7 +1,7 @@
1
- import { respond } from "../../../_platform/_shared/respond";
2
- import { setForbidden } from "../../../_platform/_shared/service/domain/users";
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { setForbidden } from "#/_platform/_shared/service/domain/users";
3
3
  // PUT /api/system/users/forbidden?uid= —— 禁用/解禁(禁用即删 refresh,撤销即强退)。
4
- import { assertSystemAdmin } from "../../../_platform/_shared/service/infra/access";
4
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
5
5
 
6
6
  async function put() {
7
7
  await respond(async () => {
@@ -1,7 +1,7 @@
1
- import { respond } from "../../../_platform/_shared/respond";
2
- import { changePasswordByAdmin } from "../../../_platform/_shared/service/domain/users";
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { changePasswordByAdmin } from "#/_platform/_shared/service/domain/users";
3
3
  // PUT /api/system/users/password?uid= —— 管理员重置口令(must_change=1 + 级联吊销 refresh)。
4
- import { assertSystemAdmin } from "../../../_platform/_shared/service/infra/access";
4
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
5
5
 
6
6
  async function put() {
7
7
  await respond(async () => {
@@ -1,7 +1,7 @@
1
- import { respond } from "../../../_platform/_shared/respond";
2
- import { updateUserRoles } from "../../../_platform/_shared/service/domain/users";
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { updateUserRoles } from "#/_platform/_shared/service/domain/users";
3
3
  // PUT /api/system/users/roles?uid= —— 整集替换用户角色(AC1-7;sys_user_role 权威源 + 写时失效)。
4
- import { assertSystemAdmin } from "../../../_platform/_shared/service/infra/access";
4
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
5
5
 
6
6
  async function put() {
7
7
  await respond(async () => {
@@ -30,7 +30,7 @@
30
30
  "displayName": "Display Name",
31
31
  "email": "Email",
32
32
  "roles": "Roles",
33
- "status": "Status",
33
+ "status": "Account status (forbidden / password change)",
34
34
  "actions": "Actions"
35
35
  }
36
36
  },
@@ -30,7 +30,7 @@
30
30
  "displayName": "显示名",
31
31
  "email": "邮箱",
32
32
  "roles": "角色",
33
- "status": "状态",
33
+ "status": "账号状态(禁用/首登改密)",
34
34
  "actions": "操作"
35
35
  }
36
36
  },
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": [
3
+ "../tsconfig.json",
4
+ "../tsconfig.paths.json"
5
+ ],
6
+ "include": [
7
+ ".",
8
+ "../global.d.ts",
9
+ "../env.d.ts",
10
+ "../types"
11
+ ],
12
+ "exclude": [
13
+ "*/web",
14
+ "*/web/**"
15
+ ]
16
+ }
@@ -4,8 +4,8 @@
4
4
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
5
5
  import { afterEach, beforeAll, describe, expect, it } from "vitest";
6
6
 
7
- import { esc, firstAttr } from "../src/auth/_shared/ldap";
8
- import { createLdapLogin } from "../src/auth/_shared/ldap-login";
7
+ import { esc, firstAttr } from "../src/auth/api/_shared/ldap";
8
+ import { createLdapLogin } from "../src/auth/api/_shared/ldap-login";
9
9
  import { installDb } from "./fixture";
10
10
 
11
11
  let dbo: ReturnType<typeof installDb>;
@@ -1,11 +1,11 @@
1
1
  // BDD:登录链——B1 登录顺序(D14 统一 401)、B2 首登改密(AC1-27)、B3 refresh 轮换/reuse 检测(AC1-13)、B4 JWT 仅 sub。
2
2
  // deps 全注入(Node 侧 crypto 只读不可 stub,设计 §9.1);fake bcrypt 以 `hashed:<pw>` 为口令哈希约定。
3
3
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
4
- import type { SessionDeps } from "../src/auth/_shared/session";
4
+ import type { SessionDeps } from "../src/auth/api/_shared/session";
5
5
 
6
6
  import { afterEach, beforeAll, describe, expect, it } from "vitest";
7
- import { assertPermission } from "../src/_platform/_shared/service/infra/access";
8
- import { createSession } from "../src/auth/_shared/session";
7
+ import { assertPermission } from "../src/_platform/api/_shared/service/infra/access";
8
+ import { createSession } from "../src/auth/api/_shared/session";
9
9
  import { installDb } from "./fixture";
10
10
 
11
11
  let dbo: ReturnType<typeof installDb>;