@oj-module/cli 0.2.53 → 0.2.56

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 (330) hide show
  1. package/devkit/SKILL.md +4 -4
  2. package/devkit/manual.md +79 -65
  3. package/package.json +2 -2
  4. package/shell-dist/assets/{index-e_Mp2mb5.js → index-CImwskAz.js} +24 -11
  5. package/shell-dist/assets/runtime.js +7 -4
  6. package/shell-dist/index.html +3 -3
  7. package/shell-dist/versions.json +3 -3
  8. package/src/__tests__/layout.test.ts +30 -1
  9. package/src/__tests__/templates-schema-columns.test.ts +99 -0
  10. package/src/__tests__/templates-tenant-scope.test.ts +3 -2
  11. package/src/alias-paths.ts +11 -12
  12. package/src/builtin.ts +10 -3
  13. package/src/config.ts +9 -0
  14. package/src/contract/__tests__/client-path.test.ts +30 -0
  15. package/src/contract/__tests__/emit-client.test.ts +20 -2
  16. package/src/contract/__tests__/handler-scan.test.ts +23 -0
  17. package/src/contract/__tests__/market-client.integration.test.ts +2 -2
  18. package/src/contract/__tests__/run-api.test.ts +2 -2
  19. package/src/contract/check.ts +11 -7
  20. package/src/contract/client-path.ts +30 -0
  21. package/src/contract/emit-client.ts +27 -6
  22. package/src/contract/handler-scan.ts +40 -3
  23. package/src/contract/run.ts +15 -5
  24. package/src/init.ts +29 -3
  25. package/src/layout.ts +72 -16
  26. package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
  27. package/src/market/__tests__/bootstrap.test.ts +1 -1
  28. package/src/site.ts +1 -1
  29. package/templates/README.md +49 -43
  30. package/templates/init.include.yaml +31 -19
  31. package/templates/openapi.json +1488 -120
  32. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/users.ts +27 -0
  33. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/oplog.ts +4 -2
  34. package/templates/src/{_platform/api → app/api/_platform}/contacts/api.ts +29 -1
  35. package/templates/src/{_platform/api → app/api/_platform}/contacts/detail/api.ts +25 -1
  36. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +7 -0
  37. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +4 -0
  38. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +8 -0
  39. package/templates/src/{_platform/api → app/api/_platform}/schema.yaml +5 -0
  40. package/templates/src/{_platform/api → app/api/_platform}/solutions/api.ts +60 -1
  41. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/replay.ts +9 -3
  42. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/tenant-admin.ts +18 -27
  43. package/templates/src/{auth/api → app/api/auth}/api.ts +59 -0
  44. package/templates/src/{auth/api → app/api/auth}/ldap-login/api.ts +37 -1
  45. package/templates/src/{auth/api → app/api/auth}/wecom/api.ts +23 -0
  46. package/templates/src/{notification/api → app/api/notification}/api.ts +17 -0
  47. package/templates/src/{notification/api → app/api/notification}/del/api.ts +2 -0
  48. package/templates/src/{system/api → app/api/system}/oplog/api.ts +5 -3
  49. package/templates/src/{system/api → app/api/system}/roles/api.ts +3 -0
  50. package/templates/src/{system/api → app/api/system}/roles/bindings/api.ts +3 -0
  51. package/templates/src/{system/api → app/api/system}/settings/api.ts +3 -0
  52. package/templates/src/{system/api → app/api/system}/users/forbidden/api.ts +3 -0
  53. package/templates/src/{system/api → app/api/system}/users/password/api.ts +3 -0
  54. package/templates/src/{system/api → app/api/system}/users/roles/api.ts +3 -0
  55. package/templates/src/app/api/web/get-async-routes/api.ts +23 -0
  56. package/templates/src/app/api/web/hello/api.ts +22 -0
  57. package/templates/src/{web/api → app/api/web}/user-info/api.ts +21 -1
  58. package/templates/src/{_platform/web → app/web/_platform}/__client/api.schemas.ts +99 -35
  59. package/templates/src/{_platform/web/__client/api.ts → app/web/_platform/__client/index.ts} +108 -61
  60. package/templates/src/{auth/web → app/web/auth}/__client/api.schemas.ts +133 -13
  61. package/templates/src/{auth/web/__client/api.ts → app/web/auth/__client/index.ts} +134 -33
  62. package/templates/src/app/web/entry.ts +77 -0
  63. package/templates/src/{home/web/__client/api.ts → app/web/home/__client/index.ts} +5 -2
  64. package/templates/src/app/web/home/routes.ts +41 -0
  65. package/templates/src/app/web/login/routes.ts +71 -0
  66. package/templates/src/{notification/web → app/web/notification}/__client/api.schemas.ts +14 -0
  67. package/templates/src/{notification/web/__client/api.ts → app/web/notification/__client/index.ts} +67 -20
  68. package/templates/src/{notification/web → app/web/notification}/pages/center/index.tsx +1 -1
  69. package/templates/src/app/web/notification/routes.ts +70 -0
  70. package/templates/src/{personal-center/web/__client/api.ts → app/web/personal-center/__client/index.ts} +5 -2
  71. package/templates/src/{personal-center/web → app/web/personal-center}/pages/my-profile/index.tsx +1 -1
  72. package/templates/src/app/web/personal-center/routes.ts +49 -0
  73. package/templates/src/{system/web → app/web/system}/__client/api.schemas.ts +29 -22
  74. package/templates/src/{system/web/__client/api.ts → app/web/system/__client/index.ts} +103 -58
  75. package/templates/src/{system/web → app/web/system}/locales/en-US.json +2 -3
  76. package/templates/src/{system/web → app/web/system}/locales/zh-CN.json +2 -3
  77. package/templates/src/{system/web → app/web/system}/pages/departments/index.tsx +2 -2
  78. package/templates/src/{system/web → app/web/system}/pages/login-methods/index.tsx +1 -1
  79. package/templates/src/{system/web → app/web/system}/pages/oplog/index.tsx +44 -19
  80. package/templates/src/{system/web → app/web/system}/pages/roles/index.tsx +5 -3
  81. package/templates/src/app/web/system/pages/roles/visible-roles.ts +14 -0
  82. package/templates/src/{system/web → app/web/system}/pages/settings/index.tsx +2 -2
  83. package/templates/src/app/web/system/pages/users/can-manage.ts +21 -0
  84. package/templates/src/{system/web → app/web/system}/pages/users/index.tsx +23 -15
  85. package/templates/src/{system/web → app/web/system}/pages/users/roles.tsx +1 -1
  86. package/templates/src/{system/web → app/web/system}/pages/wecom/index.tsx +12 -3
  87. package/templates/src/{system/web/entry.ts → app/web/system/routes.ts} +25 -36
  88. package/templates/src/app/web/web/__client/api.schemas.ts +36 -0
  89. package/templates/src/{web/web/__client/api.ts → app/web/web/__client/index.ts} +38 -11
  90. package/templates/src/oj-market/web/api/defaultClient.ts +1 -1
  91. package/templates/tests/auth-ldap.spec.ts +2 -2
  92. package/templates/tests/auth-session.spec.ts +3 -3
  93. package/templates/tests/auth-throttle.spec.ts +1 -1
  94. package/templates/tests/fixture.ts +6 -4
  95. package/templates/tests/guard.spec.ts +1 -1
  96. package/templates/tests/lifecycle/atomic-state.spec.ts +3 -3
  97. package/templates/tests/lifecycle/bootstrap.spec.ts +3 -3
  98. package/templates/tests/lifecycle/deploy-smoke.spec.ts +8 -8
  99. package/templates/tests/lifecycle/installer.spec.ts +10 -10
  100. package/templates/tests/lifecycle/market-api.spec.ts +14 -14
  101. package/templates/tests/lifecycle/migrate.spec.ts +6 -6
  102. package/templates/tests/lifecycle/preflight.spec.ts +3 -3
  103. package/templates/tests/lifecycle/register.spec.ts +1 -1
  104. package/templates/tests/lifecycle/semver.spec.ts +1 -1
  105. package/templates/tests/lifecycle/sha384.spec.ts +1 -1
  106. package/templates/tests/lifecycle/solutions-api.spec.ts +1 -1
  107. package/templates/tests/lifecycle/source-seq.spec.ts +1 -1
  108. package/templates/tests/lifecycle/steps-fs.spec.ts +9 -9
  109. package/templates/tests/lifecycle/text-edit.spec.ts +1 -1
  110. package/templates/tests/lifecycle/tgz-guard.spec.ts +2 -2
  111. package/templates/tests/lifecycle/trust.spec.ts +5 -5
  112. package/templates/tests/lifecycle/uninstall.spec.ts +7 -7
  113. package/templates/tests/lifecycle/verify-checks.spec.ts +5 -5
  114. package/templates/tests/lifecycle/verify-report.spec.ts +4 -4
  115. package/templates/tests/org-departments.spec.ts +10 -6
  116. package/templates/tests/org-read.spec.ts +7 -7
  117. package/templates/tests/query-utils.spec.ts +1 -1
  118. package/templates/tests/rbac-audit.spec.ts +1 -1
  119. package/templates/tests/rbac-contacts.spec.ts +2 -2
  120. package/templates/tests/rbac-guard.spec.ts +1 -1
  121. package/templates/tests/rbac-roles.spec.ts +10 -2
  122. package/templates/tests/rbac-seed.spec.ts +1 -1
  123. package/templates/tests/rbac-settings.spec.ts +77 -7
  124. package/templates/tests/rbac-solution-perms.spec.ts +1 -1
  125. package/templates/tests/rbac-tables.spec.ts +29 -0
  126. package/templates/tests/rbac-users.spec.ts +55 -3
  127. package/templates/tests/roles-page.spec.ts +40 -0
  128. package/templates/tests/seed-role-accounts.spec.ts +1 -1
  129. package/templates/tests/tenant-access-cache.spec.ts +1 -1
  130. package/templates/tests/tenant-forgery.spec.ts +5 -5
  131. package/templates/tests/tenant-isolation.spec.ts +5 -4
  132. package/templates/tests/username-namespace.spec.ts +3 -3
  133. package/templates/tests/users-page.spec.ts +39 -0
  134. package/templates/tests/wecom-admin.spec.ts +6 -6
  135. package/templates/tests/wecom-client-timeout.spec.ts +1 -1
  136. package/templates/tests/wecom-connector.spec.ts +9 -9
  137. package/templates/tests/wecom-corp.spec.ts +9 -9
  138. package/templates/tests/wecom-crypt.spec.ts +1 -1
  139. package/templates/tests/wecom-endpoints.spec.ts +2 -2
  140. package/templates/tests/wecom-landing.spec.ts +1 -1
  141. package/templates/tests/wecom-org-sync.spec.ts +7 -7
  142. package/templates/tests/wecom-sync.spec.ts +3 -3
  143. package/templates/tsconfig.paths.json +11 -11
  144. package/templates/vitest.config.ts +2 -2
  145. package/templates/web.config.ts +24 -15
  146. package/vendor/host-versions.json +3 -3
  147. package/templates/src/home/web/entry.ts +0 -49
  148. package/templates/src/login/web/entry.ts +0 -79
  149. package/templates/src/notification/web/entry.ts +0 -84
  150. package/templates/src/personal-center/web/entry.ts +0 -60
  151. package/templates/src/web/api/get-async-routes/api.ts +0 -8
  152. package/templates/src/web/api/hello/api.ts +0 -10
  153. package/templates/src/web/web/__client/api.schemas.ts +0 -8
  154. package/templates/src/{_platform/api → app/api/_platform}/README.md +0 -0
  155. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/atomic.ts +0 -0
  156. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/history.ts +0 -0
  157. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/installer.ts +0 -0
  158. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/limits.ts +0 -0
  159. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/lock.ts +0 -0
  160. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/semver.ts +0 -0
  161. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/sha384.ts +0 -0
  162. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/source-seq.ts +0 -0
  163. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/state.ts +0 -0
  164. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/deploy-web.ts +0 -0
  165. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/download.ts +0 -0
  166. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/extract.ts +0 -0
  167. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/migrate.ts +0 -0
  168. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/preflight.ts +0 -0
  169. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/register.ts +0 -0
  170. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/reload.ts +0 -0
  171. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/smoke.ts +0 -0
  172. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/verify.ts +0 -0
  173. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tasks.ts +0 -0
  174. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/text-edit.ts +0 -0
  175. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tgz-guard.ts +0 -0
  176. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/trust.ts +0 -0
  177. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/types.ts +0 -0
  178. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/uninstall.ts +0 -0
  179. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-checks.ts +0 -0
  180. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-report.ts +0 -0
  181. package/templates/src/{_platform/api → app/api/_platform}/_shared/respond.ts +0 -0
  182. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/contacts.ts +0 -0
  183. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org-read.ts +0 -0
  184. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org.ts +0 -0
  185. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/roles.ts +0 -0
  186. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/settings.ts +0 -0
  187. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/solution-perms.ts +0 -0
  188. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/access.ts +0 -0
  189. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/audit.ts +0 -0
  190. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/guard.ts +0 -0
  191. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/query.ts +0 -0
  192. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/rate.ts +0 -0
  193. package/templates/src/{_platform/api → app/api/_platform}/_shared/site-facade.ts +0 -0
  194. package/templates/src/{_platform/api → app/api/_platform}/fixtures/l1-test-user.sql +0 -0
  195. package/templates/src/{_platform/api → app/api/_platform}/manifest.yaml +0 -0
  196. package/templates/src/{_platform/api → app/api/_platform}/market/api.ts +0 -0
  197. package/templates/src/{_platform/api → app/api/_platform}/market/bootstrap.ts +0 -0
  198. package/templates/src/{_platform/api → app/api/_platform}/market/index-source.ts +0 -0
  199. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.mysql.sql +0 -0
  200. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.pg.sql +0 -0
  201. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.sql +0 -0
  202. package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
  203. package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.sql +0 -0
  204. package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.mysql.sql +0 -0
  205. package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.sql +0 -0
  206. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.mysql.sql +0 -0
  207. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.pg.sql +0 -0
  208. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.sql +0 -0
  209. package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.mysql.sql +0 -0
  210. package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.sql +0 -0
  211. package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
  212. package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.sql +0 -0
  213. package/templates/src/{_platform/api → app/api/_platform}/migrations/0007__users_email.sql +0 -0
  214. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
  215. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.pg.sql +0 -0
  216. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.sql +0 -0
  217. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.mysql.sql +0 -0
  218. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.pg.sql +0 -0
  219. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.sql +0 -0
  220. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.mysql.sql +0 -0
  221. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.pg.sql +0 -0
  222. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.sql +0 -0
  223. package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
  224. package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.sql +0 -0
  225. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.mysql.sql +0 -0
  226. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.pg.sql +0 -0
  227. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.sql +0 -0
  228. package/templates/src/{_platform/api → app/api/_platform}/migrations/0013__users_profile.sql +0 -0
  229. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.mysql.sql +0 -0
  230. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.pg.sql +0 -0
  231. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.sql +0 -0
  232. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.mysql.sql +0 -0
  233. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.pg.sql +0 -0
  234. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.sql +0 -0
  235. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.mysql.sql +0 -0
  236. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.pg.sql +0 -0
  237. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.sql +0 -0
  238. package/templates/src/{_platform/api → app/api/_platform}/org/departments/api.ts +0 -0
  239. package/templates/src/{_platform/api → app/api/_platform}/org/is-member/api.ts +0 -0
  240. package/templates/src/{_platform/api → app/api/_platform}/org/members/api.ts +0 -0
  241. package/templates/src/{_platform/api → app/api/_platform}/org/picker/api.ts +0 -0
  242. package/templates/src/{_platform/api → app/api/_platform}/org/resolve/api.ts +0 -0
  243. package/templates/src/{_platform/api → app/api/_platform}/org/tree/api.ts +0 -0
  244. package/templates/src/{_platform/api → app/api/_platform}/org/user/api.ts +0 -0
  245. package/templates/src/{_platform/api → app/api/_platform}/seed.mysql.sql +0 -0
  246. package/templates/src/{_platform/api → app/api/_platform}/seed.pg.sql +0 -0
  247. package/templates/src/{_platform/api → app/api/_platform}/seed.sql +0 -0
  248. package/templates/src/{auth/api → app/api/auth}/_shared/ldap-login.ts +0 -0
  249. package/templates/src/{auth/api → app/api/auth}/_shared/ldap.ts +0 -0
  250. package/templates/src/{auth/api → app/api/auth}/_shared/login-throttle.ts +0 -0
  251. package/templates/src/{auth/api → app/api/auth}/_shared/session.ts +0 -0
  252. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/admin.ts +1 -1
  253. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/client.ts +0 -0
  254. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/config.ts +0 -0
  255. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/contacts.ts +0 -0
  256. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/crypt.ts +0 -0
  257. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/events.ts +1 -1
  258. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/login.ts +0 -0
  259. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/payload.ts +0 -0
  260. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/store.ts +0 -0
  261. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/sync.ts +0 -0
  262. package/templates/src/{auth/api → app/api/auth}/manifest.yaml +0 -0
  263. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.mysql.sql +0 -0
  264. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.pg.sql +0 -0
  265. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.sql +0 -0
  266. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.mysql.sql +0 -0
  267. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.pg.sql +0 -0
  268. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.sql +0 -0
  269. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.mysql.sql +0 -0
  270. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.pg.sql +0 -0
  271. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.sql +0 -0
  272. package/templates/src/{auth/api → app/api/auth}/schema.yaml +0 -0
  273. package/templates/src/{auth/api → app/api/auth}/wecom/admin/api.ts +0 -0
  274. package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/api.ts +1 -1
  275. package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/password/api.ts +1 -1
  276. /package/templates/src/{auth/api → app/api/auth}/wecom/sync/api.ts +0 -0
  277. /package/templates/src/{home/api → app/api/home}/line/api.ts +0 -0
  278. /package/templates/src/{home/api → app/api/home}/manifest.yaml +0 -0
  279. /package/templates/src/{home/api → app/api/home}/migrations/0001__create_home_tables.sql +0 -0
  280. /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.mysql.sql +0 -0
  281. /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.sql +0 -0
  282. /package/templates/src/{home/api → app/api/home}/pie/api.ts +0 -0
  283. /package/templates/src/{home/api → app/api/home}/schema.yaml +0 -0
  284. /package/templates/src/{home/api → app/api/home}/seed.sql +0 -0
  285. /package/templates/src/{notification/api → app/api/notification}/_shared/notify.ts +0 -0
  286. /package/templates/src/{notification/api → app/api/notification}/manifest.yaml +0 -0
  287. /package/templates/src/{notification/api → app/api/notification}/migrations/0001__create_notifications.sql +0 -0
  288. /package/templates/src/{notification/api → app/api/notification}/migrations/0002__notifications_user.sql +0 -0
  289. /package/templates/src/{notification/api → app/api/notification}/migrations/0003__notifications_broadcast.sql +0 -0
  290. /package/templates/src/{notification/api → app/api/notification}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
  291. /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.mysql.sql +0 -0
  292. /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.sql +0 -0
  293. /package/templates/src/{notification/api → app/api/notification}/schema.yaml +0 -0
  294. /package/templates/src/{notification/api → app/api/notification}/seed.sql +0 -0
  295. /package/templates/src/{personal-center/api → app/api/personal-center}/manifest.yaml +0 -0
  296. /package/templates/src/{personal-center/api → app/api/personal-center}/profile/api.ts +0 -0
  297. /package/templates/src/{personal-center/api → app/api/personal-center}/upload/api.ts +0 -0
  298. /package/templates/src/{system/api → app/api/system}/departments/api.ts +0 -0
  299. /package/templates/src/{system/api → app/api/system}/departments/detach/api.ts +0 -0
  300. /package/templates/src/{system/api → app/api/system}/departments/members/api.ts +0 -0
  301. /package/templates/src/{system/api → app/api/system}/departments/move/api.ts +0 -0
  302. /package/templates/src/{system/api → app/api/system}/login-methods/api.ts +0 -0
  303. /package/templates/src/{system/api → app/api/system}/manifest.yaml +0 -0
  304. /package/templates/src/{system/api → app/api/system}/permissions/api.ts +0 -0
  305. /package/templates/src/{system/api → app/api/system}/users/api.ts +0 -0
  306. /package/templates/src/{system/api → app/api/system}/users/profile/api.ts +0 -0
  307. /package/templates/src/{web/api → app/api/web}/manifest.yaml +0 -0
  308. /package/templates/src/{home/web → app/web/home}/__client/api.schemas.ts +0 -0
  309. /package/templates/src/{home/web → app/web/home}/locales/en-US.json +0 -0
  310. /package/templates/src/{home/web → app/web/home}/locales/zh-CN.json +0 -0
  311. /package/templates/src/{home/web → app/web/home}/pages/components/bar-chart.tsx +0 -0
  312. /package/templates/src/{home/web → app/web/home}/pages/components/card-list.tsx +0 -0
  313. /package/templates/src/{home/web → app/web/home}/pages/components/line-chart.tsx +0 -0
  314. /package/templates/src/{home/web → app/web/home}/pages/components/pie-chart.tsx +0 -0
  315. /package/templates/src/{home/web → app/web/home}/pages/index.tsx +0 -0
  316. /package/templates/src/{login/web → app/web/login}/auth-provider.ts +0 -0
  317. /package/templates/src/{login/web → app/web/login}/locales/en-US.json +0 -0
  318. /package/templates/src/{login/web → app/web/login}/locales/zh-CN.json +0 -0
  319. /package/templates/src/{login/web → app/web/login}/pages/change-password.tsx +0 -0
  320. /package/templates/src/{login/web → app/web/login}/pages/login.tsx +0 -0
  321. /package/templates/src/{login/web → app/web/login}/wecom.ts +0 -0
  322. /package/templates/src/{notification/web → app/web/notification}/locales/en-US.json +0 -0
  323. /package/templates/src/{notification/web → app/web/notification}/locales/zh-CN.json +0 -0
  324. /package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.schemas.ts +0 -0
  325. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/en-US.json +0 -0
  326. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/zh-CN.json +0 -0
  327. /package/templates/src/{system/web → app/web/system}/pages/departments/sync-bar.tsx +0 -0
  328. /package/templates/src/{system/web → app/web/system}/pages/departments/tree.ts +0 -0
  329. /package/templates/src/{system/web → app/web/system}/pages/errors.ts +0 -0
  330. /package/templates/src/{system/web → app/web/system}/pages/forbidden.tsx +0 -0
package/devkit/SKILL.md CHANGED
@@ -54,7 +54,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
54
54
  - **注册时机**:一切 `ctx.register.*` 必须在 `lifecycle.onInit` 内、且在 onInit 返回前完成。
55
55
  拖到 `onActivate` 或某个异步回调里注册不保证生效。`onInit` 签名是 **async**(返回
56
56
  `Promise<void>`)——漏写 `async` 会被 `tsc` 以 TS2322 拦下,范本见模板
57
- `src/notification/web/entry.ts`。
57
+ `src/app/web/notification/entry.ts`。
58
58
  - **一模块一 client**:`create<Module>Client(ctx)` 只在 onInit 里创建一次。
59
59
  页面/组件/事件回调里**不要**再创建——会覆盖 request 单槽。页面里直接 import 裸函数调用。
60
60
  - **只创建自己模块的 client**:A 模块的 onInit 里创建 B 模块的 client 会 DEV 告警。要别家接口,
@@ -103,7 +103,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
103
103
 
104
104
  ## 企业微信(接通真实套件 / 自建应用)
105
105
 
106
- 模板已内置企微登录(`src/auth/api/wecom/` + 登录页「企业微信」页签 + `/system/wecom` 后台)。
106
+ 模板已内置企微登录(`src/app/api/auth/wecom/` + 登录页「企业微信」页签 + `/system/wecom` 后台)。
107
107
  两种来源、两本手册,**按客户形态选**:
108
108
 
109
109
  | 客户形态 | 读 | 凭据 |
@@ -132,7 +132,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
132
132
  - **整页跳转拼 API base**:`window.location.assign(resolveApiBase() + wecomAuthorizePath(corp))`;
133
133
  拼站点 base 会被 SPA 兜底页吞掉(页面原地重载、一个请求都不发,控制台无报错)。
134
134
  - **通讯录同步是「组织架构底座 + 企微适配器」,不是企微专属功能**:模板的
135
- `src/auth/api/wecom/{sync,contacts}.ts` 只负责「取快照 → 喂给领域层」,模型与读面在 `_platform`
135
+ `src/app/api/auth/wecom/{sync,contacts}.ts` 只负责「取快照 → 喂给领域层」,模型与读面在 `_platform`
136
136
  (手册 §14)。要按部门算人、要选人、要给组织架构接 LDAP,一律读 §14,不要在企微代码里加分支。
137
137
  同步/事件增量与回调解密是**两条独立纪律**:回调必须回 `success`,而事件处理失败**也照样回 `success`**
138
138
  (重推补不回已判死的失败,兜底是下一轮全量同步)。
@@ -187,7 +187,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
187
187
  | 登录 401 且 msg 不是 `invalid credentials` | `/auth/*` 不在 `anonymous_paths` | 加进工程根 `config.yaml`,重启 |
188
188
  | WS 连接 401(「文件在却连不上」) | 模块写了 `ws.ts`,但其 `/ws` 路由不在 `anonymous_paths`(oj v0.1.30+ WS 握手也过鉴权) | 匿名 WS 加进 `anonymous_paths`;受保护 WS 带 Bearer/Cookie 是正当用法。`ojm api --check` 会对此 warn |
189
189
  | 登出/回登录页落空,或登录后落错误边界 | 工程缺 `login` / `home` 模块 | 在 `web.config.ts` 保留两者 |
190
- | 通知铃 404 `no route matched`(`/api/notifications`) | 前端 `notification` 模块没加载 / 没注册 `notificationsApi`,回落到内置只读兜底;脚手架不再提供 root 级 `/api/notifications` | 保留 `src/notification/web/entry.ts` 的登记 + onInit 注册四方法;后端由 `src/notification/api` 出 `/api/notification/*` |
190
+ | 通知铃 404 `no route matched`(`/api/notifications`) | 前端 `notification` 模块没加载 / 没注册 `notificationsApi`,回落到内置只读兜底;脚手架不再提供 root 级 `/api/notifications` | 保留 `src/app/web/notification/entry.ts` 的登记 + onInit 注册四方法;后端由 `src/app/api/notification` 出 `/api/notification/*` |
191
191
  | `oj serve` 启动即退,报 `config uses removed server.* site keys (v0.1.58): api_prefix` | 用了 oj v0.1.58 起删除的旧站点键(`api_prefix`/`app_path`/`app_prefix`/`static_sites`/`app_spa_fallback`) | 改 `server.api_prefix: /api` 为顶层 `mounts:`(module-root 写 `{prefix: "/api", api: "api/dist"}`;历史布局写 `api: "src"`、源码交给 `-d`);迁移完整 YAML 由报错给出 |
192
192
  | 改完 `mounts:` 仍全 404 | `api:` 指向目录不对(相对 config 所在目录) | module-root 指向产物目录 `"api/dist"`、历史布局指向源码树 `"src"`;`-c` 指向工程根 `config.yaml`(ojm dev/preview 已内置)。module-root 模板**故意不写 api 挂载**(源码交给 `--module-root src`、产物交给 `--api-path api/dist`,同时出现会报 module declared in multiple places) |
193
193
  | `oj serve` 启动即退,报 `module '_shared' missing manifest.yaml` | 把共享后端代码放到了扫描根顶层(`src/_shared/`)——oj 把扫描根下的顶层目录都当模块;历史布局是 `api/src/_shared/` | 嵌进所属模块:`src/<模块>/api/_shared/`(历史布局 `api/src/<模块>/_shared/`),用 `#<本模块>/...` 别名 |
package/devkit/manual.md CHANGED
@@ -42,16 +42,27 @@
42
42
  │ ├── tsconfig.json # 后端 typecheck 工程(extends 上两者,exclude */web)
43
43
  │ ├── .ojm-builtin.json # 框架自带方案名单(init 派生,可手改/删除;打包排除 + info 折叠,见 §11)
44
44
  │ ├── styles.css
45
- │ ├── _platform/ # 框架级:users 表 / rbac / 市场编排 / 生命周期
46
- │ │ ├── api/ # + _shared/(respond、service/、site-facade)
47
- │ │ └── web/ # 仅 __client 生成物(无页面)
48
- │ ├── auth/api/ # login / refresh / logout / ldap-login
49
- │ ├── system/{api,web}/ # 用户 / 角色 / 权限 / 设置 / oplog / login-methods
50
- │ ├── notification/{api,web}/ # 通知互动(表 + push + del)
51
- │ ├── personal-center/{api,web}/ # 头像上传
52
- │ └── web/{api,web}/ # root 级 /api/web(hello / user-info / get-async-routes 在 api 侧)
53
- ├── api/dist/ # 后端产物(release 根:<name>-<ver>/ + manifests.yaml + versions.json)—构建后才有
54
- ├── web/dist/ # 站点产物(shell + modules/ + modules.json)—构建后才有
45
+ │ ├── app/ # 整个应用(后端模块 + 前端聚合模块)
46
+ │ │ ├── api/ # 后端模块(模块名 = URL 前缀,各自仍是独立 oj 模块)
47
+ │ │ │ ├── _platform/ # users 表 / rbac / 市场编排 / 生命周期 + _shared/
48
+ │ │ │ ├── auth/ # login / refresh / logout / ldap-login
49
+ │ │ │ ├── system/ # 用户 / 角色 / 权限 / 设置 / oplog / login-methods
50
+ │ │ │ ├── notification/ # 通知互动(表 + push + del)
51
+ │ │ │ ├── personal-center/ # 头像上传
52
+ │ │ │ └── web/ # root 级 /api/web(hello / user-info / get-async-routes)
53
+ │ │ └── web/ # 前端**多根聚合模块**(一个 entry 多个根)
54
+ │ │ ├── entry.ts # routes 聚合 + i18nNamespaces + 生命周期
55
+ │ │ ├── home/ # /home(routes.ts + pages + locales + __client)
56
+ │ │ ├── login/ # /login、/change-password
57
+ │ │ ├── personal-center/ # /personal-center/my-profile
58
+ │ │ ├── notification/ # /notification-center
59
+ │ │ ├── system/ # /system/*(根上 handle.roles = 系统管理员)
60
+ │ │ ├── auth/ # 仅 __client(无页面)
61
+ │ │ ├── _platform/ # 仅 __client(oj-market 引用)
62
+ │ │ └── web/ # 仅 __client
63
+ │ ├── oj-market/web/ # 解决方案市场(独立模块,删目录即下线)
64
+ ├── dist/api/ # 后端产物(release 根:<name>-<ver>/ + manifests.yaml + versions.json)—构建后才有
65
+ ├── dist/web/ # 站点产物(shell + modules/ + modules.json)—构建后才有
55
66
  ├── bin/ # oj 二进制 + plugins/ + devkit/(不入库)
56
67
  └── .claude/skills/ # ojm-module-dev(本 devkit)+ oj-api-dev(随 oj 二进制)
57
68
  ```
@@ -62,13 +73,15 @@
62
73
  所在目录**,所有 `fs.*` 相对路径、`db.default`、`platform.root`、`mounts[].web`
63
74
  都相对它解析;证书与密钥落在同级的 `config/`(`ojm` 侧由 `resolveConfigPath()` /
64
75
  `resolveCertDir()` 单点派生,你不需要自己拼路径)。
65
- - **产物有两个根**:后端 `api/dist`(oj 扁平 release,`<name>-<ver>/` 与
66
- `manifests.yaml` 同层),站点 `web/dist`(`modules/` + `modules.json`)。
67
- `config.yaml` 上移到工程根后,两者不再同名 `dist`。
68
- - **`src/` 下每个目录都得是一个模块**:脚本 / 共享库请放在 `src/` 之外,否则
69
- `ojm dev/build` 会以「既无 `api/manifest.yaml` 也无 `web/entry.ts`」直接报错。
70
-
71
- > 模板里还有一批 demo-only 模块(`src/{demo,home}/api`、`src/{about,access,exception,
76
+ - **产物有两个根**:后端 `dist/api`(oj 扁平 release,`<name>-<ver>/` 与
77
+ `manifests.yaml` 同层),站点 `dist/web`(`modules/` + `modules.json`)。
78
+ - **`src/` 下的顶层目录必须是一个模块**(`api/manifest.yaml` 或 `web/entry.ts`):
79
+ 脚本 / 共享库请放在 `src/` 之外。应用容器 `src/app` 是唯一例外——它的 `api/<name>/`
80
+ 子目录各自是独立模块(模块名 = 子目录名 = URL 前缀,oj 的 `--module-root` 递归发现)。
81
+ - **前端 client 入口是 `__client/index.ts`**(0.2.54 起,旧名 `api.ts` 会被 oj dev
82
+ 当路由扫描)。页面导入写 `from "../__client"`。
83
+
84
+ > 模板里还有一批 demo-only 模块(`src/app/api/home`、`src/{about,access,exception,
72
85
  > outside,route-nest}/web`)仅供模板自演示,**不进脚手架**;其 `web.config.ts` 条目带
73
86
  > `// #region demo-only` 标记,init 拷贝时整段剥离。新布局的清单是「扫描 + 覆盖」并集,
74
87
  > 注释掉不会让模块消失——要「不构建」必须写 `enabled: false`。
@@ -93,7 +106,7 @@
93
106
  > **更早建好的 dev 库里仍是旧口令**——拿 `aA123456` 登不上时先怀疑这个,别去查登录链路。
94
107
  > 要统一:删 `db.sqlite` 重跑 `oj migrate`(seed 随服务启动重放),或直接
95
108
  > `UPDATE users SET password_hash = '<aA123456 的哈希>' WHERE username IN ('superadmin','admin','editor','viewer');`。
96
- > 哈希现成值从 `src/_platform/api/seed.sql` 里 grep `$2y$10$` 即可。
109
+ > 哈希现成值从 `src/app/api/_platform/seed.sql` 里 grep `$2y$10$` 即可。
97
110
 
98
111
  端口:前端 devServer **5174**,oj 后端 **9778**
99
112
  (以工程根 `config.yaml` 的 `server.port` 为准)。
@@ -147,9 +160,9 @@
147
160
  src/<模块>/api/<路径...>/api.ts → /api/<模块>/<路径...>
148
161
  ```
149
162
 
150
- `src/web/api/hello/api.ts` → `GET|POST /api/web/hello`。
163
+ `src/app/api/web/hello/api.ts` → `GET|POST /api/web/hello`。
151
164
 
152
- 注意中间的 `api` 是**布局分隔符,不是路由段**(`src/web/api/hello` ↔ `/api/web/hello`,
165
+ 注意中间的 `api` 是**布局分隔符,不是路由段**(`src/app/api/web/hello` ↔ `/api/web/hello`,
153
166
  不是 `/api/web/api/hello`)——`ojm` 的契约扫描按「模块的 api 侧目录」逐模块进行,
154
167
  不会把 `src` 整棵当扫描根(历史布局同理:`api/src/web/hello/api.ts`)。
155
168
 
@@ -210,13 +223,14 @@ src/<模块>/api/<路径...>/api.ts → /api/<模块>/<路径...>
210
223
 
211
224
  | 文件位置 | 到 `_platform/_shared` 的写法 |
212
225
  |---|---|
213
- | `src/system/api/<x>/api.ts` | `../../../_platform/api/_shared/...` |
214
- | `src/system/api/<x>/<y>/api.ts` | `../../../../_platform/api/_shared/...` |
215
- | `src/auth/api/<z>/api.ts` | `../../../_platform/api/_shared/...` |
216
- | `src/_platform/api/<x>/api.ts` | `../_shared/...` |
217
- | `src/_platform/api/<x>/<y>/api.ts` | `../../_shared/...` |
218
-
219
- > 历史布局把 `src/<m>/api` 换成 `api/src/<m>` 后同构,深度不变。
226
+ | `src/app/api/system/<x>/api.ts` | `../../_platform/_shared/...` |
227
+ | `src/app/api/system/<x>/<y>/api.ts` | `../../../_platform/_shared/...` |
228
+ | `src/app/api/auth/<z>/api.ts` | `../../_platform/_shared/...` |
229
+ | `src/app/api/_platform/<x>/api.ts` | `../_shared/...` |
230
+ | `src/app/api/_platform/<x>/<y>/api.ts` | `../../_shared/...` |
231
+
232
+ > 历史布局把 `src/<m>/api` 换成 `api/src/<m>` 后同构,深度不变;内置模块收进
233
+ > `src/app/api/<m>` 后比原 `src/<m>/api` **少一层**(上表已按新布局校准)。
220
234
  3. **模块级可变状态不跨请求存活。** oj **每请求重建 handler 模块实例**(实测:模块级
221
235
  计数器连打三次恒为初值)——`let` / `Map` / `Set` 级状态一律视为**单请求作用域**。
222
236
  需要跨请求的状态(限速、节流、去重窗口、判权缓存)**必须落库**或走 oj 持久面。
@@ -280,7 +294,7 @@ src/<模块名>/web/
280
294
  > 就是忘了登记,结果模块安静地没加载、按钮安静地灰着,**全程没有一个报错**。
281
295
 
282
296
  **建档顺序很重要**(有接口的模块):先写 handler 的 `.schema` → 跑 `oj openapi` + `ojm api` → 再写 `entry.ts`。
283
- 生成前 `import { createXxxClient } from "./__client/api"` 必然 ts 报红,
297
+ 生成前 `import { createXxxClient } from "./__client"` 必然 ts 报红,
284
298
  那不是你写错了,是产物还没生成。完整流水线见 §3.4。
285
299
 
286
300
  ```ts
@@ -289,14 +303,14 @@ export default {
289
303
  baseUrl: "", // 模块产物 URL 前缀(可填 CDN 绝对域);与站点部署位置无关
290
304
  // siteBase: "/oj", // 站点挂在 https://host/oj/ 下时才需要(默认根路径),见 §13 的路径空间一节
291
305
  modules: [
292
- // home 必须保留且尽量靠前:shell 预构建把 "/" → VITE_BASE_HOME_PATH(=/home)
293
- // 重定向,缺 /home 路由会让登录回跳 / 点 logo 落错误边界。
294
- { name: "home", entry: "src/home/web/entry.ts", enabled: true },
295
- // login 必须保留:shell 宿主只消费模块路由、不挂 runtime 内置 baseRoutes,
296
- // 缺它 `/login` 无路由可跳(登出会落空)。
297
- { name: "login", entry: "src/login/web/entry.ts", enabled: true },
306
+ // 框架内置页面聚合为一个**多根模块**(/home /login /personal-center
307
+ // /notification /system):一个 entry 多个根,各根按 handle.order 排菜单。
308
+ // 缺 /home 会让登录回跳 / 点 logo 落错误边界;缺 /login 则登出无路由可跳。
309
+ { name: "app", entry: "src/app/web/entry.ts", enabled: true },
298
310
  { name: "books", entry: "src/books/web/entry.ts", enabled: true },
299
311
  ],
312
+ // 多根聚合模块的 client 归属(生成物落 src/app/web/<api 前缀>/__client):
313
+ // clientOwners: { home: "app", "personal-center": "app", notification: "app", system: "app" },
300
314
  };
301
315
  ```
302
316
 
@@ -309,7 +323,7 @@ import { BookOutlined } from "@ant-design/icons";
309
323
  import { defineModule } from "@oj-module/runtime";
310
324
  import { createElement, lazy } from "react";
311
325
 
312
- import { createBooksClient } from "./__client/api";
326
+ import { createBooksClient } from "./__client";
313
327
 
314
328
  const Books = lazy(() => import("./pages/index"));
315
329
 
@@ -351,7 +365,7 @@ export default defineModule({
351
365
  `defineModule` 字段:`name`(= 目录名)、`description`、`version`、
352
366
  `peerRuntime`、`routes`、`i18n?`、`lifecycle?`。
353
367
 
354
- > 页面组件用**静态 import 或 `lazy()` 都行**:模板自带模块(`src/home/web/entry.ts` 等)
368
+ > 页面组件用**静态 import 或 `lazy()` 都行**:模板自带模块(`src/app/web/home/entry.ts` 等)
355
369
  > 用静态 import,多页面模块可对非首屏页面用 `lazy(() => import("./pages/x"))` 做代码分割。
356
370
  > 单个页面就用静态 import,少一层间接。
357
371
 
@@ -476,7 +490,7 @@ const roles = useUserStore(s => s.roles);
476
490
 
477
491
  **写(改密/改角色/改设置)→ 走契约 API client,不碰框架内部**
478
492
 
479
- - 框架已把 `auth/*`、`system/*`、`web/*` 等端点经 OpenAPI 契约定义,并从 `@oj-module/runtime` 导出类型化 fetch(如 `api/rbac` 的 `fetchChangePassword`、`fetchRbacUpdateUserRoles`,`api/user` 的 `fetchUserInfo`)。要调框架内置写接口,直接复用这些导出。**但更推荐模块 own 自己的契约**:在**自己模块的 handler 动词**上写 `.schema`,跑 `oj openapi` + `ojm api` 生成 `src/<模块>/web/__client/api.ts`,模块经自己的 client 调框架端点——接口由契约(versioned OpenAPI)对齐,而非把框架源文件打进来。
493
+ - 框架已把 `auth/*`、`system/*`、`web/*` 等端点经 OpenAPI 契约定义,并从 `@oj-module/runtime` 导出类型化 fetch(如 `api/rbac` 的 `fetchChangePassword`、`fetchRbacUpdateUserRoles`,`api/user` 的 `fetchUserInfo`)。要调框架内置写接口,直接复用这些导出。**但更推荐模块 own 自己的契约**:在**自己模块的 handler 动词**上写 `.schema`,跑 `oj openapi` + `ojm api` 生成 `src/<模块>/web/__client/index.ts`,模块经自己的 client 调框架端点——接口由契约(versioned OpenAPI)对齐,而非把框架源文件打进来。
480
494
  - 底层都走同一个 `request` 单例 + 契约 URL;模块侧请求被 `defineModule` 的 `ctx` 收敛到登记过的 `apiPrefix`,越界 / `../` 穿越 / 逐请求 prefix 覆盖均被拒(请求边界见 §2.6 + `tests/runtime/scoped-request.test.ts`)。
481
495
 
482
496
  **场景:模块记录自己的操作日志(写端)**
@@ -530,7 +544,7 @@ oj openapi -c config.yaml --module-root src -b /api -o openapi.json → OpenA
530
544
  ojm api → irFromOpenapi → emitZodFromJsonSchema / emitClient
531
545
  │
532
546
  ▼
533
- src/<模块>/web/__client/{api.ts, api.schemas.ts}(+ stub / mock)
547
+ src/<模块>/web/__client/{index.ts, api.schemas.ts}(+ stub / mock)
534
548
  ```
535
549
 
536
550
  `openapi.json` 即权威文档源,`ojm api --docs` 直接聚合它渲染文档站。`.schema` / `.response`
@@ -549,7 +563,7 @@ src/<模块>/web/__client/{api.ts, api.schemas.ts}(+ stub / mock)
549
563
 
550
564
  | 工程形态 | 权威源 | 产物流向 |
551
565
  |---|---|---|
552
- | **module-root**(`ojm init` 产物,推荐) | 后端 `src/<模块>/api/**/api.ts` 上的 `.schema` | `src/<模块>/web/__client/{api.ts, api.schemas.ts}`(由 `ojm api` 消费 `openapi.json` 生成);stub → `src/<模块>/api/<路径>/api.ts`(未实现 handler 时补骨架) |
566
+ | **module-root**(`ojm init` 产物,推荐) | 后端 `src/<模块>/api/**/api.ts` 上的 `.schema` | `src/<模块>/web/__client/{index.ts, api.schemas.ts}`(由 `ojm api` 消费 `openapi.json` 生成);stub → `src/<模块>/api/<路径>/api.ts`(未实现 handler 时补骨架) |
553
567
  | **uni-dev**(历史布局) | 后端 `api/src/<模块>/**/api.ts` 上的 `.schema` | `web/src/<模块>/__client/…`;stub → `api/src/<模块>/<路径>/api.ts` |
554
568
  | 纯前端 | 仅消费他人 `openapi.json` | `__client/` 全部落前端侧,无 stub |
555
569
 
@@ -562,7 +576,7 @@ src/<模块>/web/__client/{api.ts, api.schemas.ts}(+ stub / mock)
562
576
  以目录名作 `MODULE_PREFIX`(跨模块认领护栏的对比基准),只改一边会让 dev 控制台报「正在认领
563
577
  别人的 client」。与旧 `apiPrefix` 字面相等约束同源,只是不再手写字段。
564
578
 
565
- > 工程里有两个 "web":前端侧 `src/web/web/`,与后端 `src/web/api/`(提供 user-info 等内置接口)。
579
+ > 工程里有两个 "web":前端侧 `src/app/web/web/`,与后端 `src/app/api/web/`(提供 user-info 等内置接口)。
566
580
  > 豁免清单里的 `"modules": ["web"]` 指的是**后者**。
567
581
 
568
582
  ### 3.3 handler `.schema`(入参)+ `.response`(出参)写法(JSON Schema 子集)
@@ -629,7 +643,7 @@ minLength / maxLength / pattern / enum / nullable / minItems / maxItems / defaul
629
643
  **上传文件(multipart/form-data)**:当前 `ojm api` 的 openapi 流水线**不生成** form 通道的
630
644
  前端 client(文件部件是二进制,不进 JSON Schema)。惯例走 antd `Upload` **直连**后端
631
645
  (不经 JSON client),handler 用 `http.files` / `http.file(i)` 读字节,仅平级声明
632
- `.response`(见 `src/personal-center/api/upload/api.ts` 范本)。
646
+ `.response`(见 `src/app/api/personal-center/upload/api.ts` 范本)。
633
647
 
634
648
  **不要手写 `content-type`**:运行时(ky)见到 `FormData` 会自己补 `multipart/form-data;
635
649
  boundary=…`,手写反而丢 boundary。
@@ -639,7 +653,7 @@ boundary=…`,手写反而丢 boundary。
639
653
 
640
654
  ### 3.4 生成物使用契约
641
655
 
642
- 生成的 `src/<模块>/web/__client/api.ts` 导出:
656
+ 生成的 `src/<模块>/web/__client/index.ts` 导出:
643
657
 
644
658
  - 裸请求函数(`fetchNotifications`、`markRead`…)——**直接 import 调用**;
645
659
  - `API_PREFIX` 常量——契约前缀的唯一真源;
@@ -665,7 +679,7 @@ const booksClient = createBooksClient(ctx);
665
679
  > 目录却不在」只有一种解释:那个模块的 `__client/` 从没生成过(模板里的
666
680
  > personal-center 上传走 antd Upload 直连,不经过 JSON client,故没人 import 它的 `__client`)。
667
681
  > 你在自己工程跑一次 `ojm api`,它就会出现。
668
- > 判断某模块**要不要用** client 的标准只有一个:代码里有没有人 import `./__client/api`。
682
+ > 判断某模块**要不要用** client 的标准只有一个:代码里有没有人 import `./__client`。
669
683
  > 有就在 onInit 里 `create<Module>Client(ctx)` 一次;没有就什么都不写,
670
684
  > 生成了但没人 import 也不报错。
671
685
 
@@ -676,7 +690,7 @@ const booksClient = createBooksClient(ctx);
676
690
 
677
691
  ### 3.5 生成物勿手改
678
692
 
679
- `__client/api.ts`、`__client/api.schemas.ts` **重跑即覆盖**(eslint 已忽略这些路径)。
693
+ `__client/index.ts`、`__client/api.schemas.ts` **重跑即覆盖**(eslint 已忽略这些路径)。
680
694
  改需求 = 改 handler 的 `.schema`(或路由),重跑 `oj openapi` + `ojm api`。
681
695
 
682
696
  **唯一例外:stub。** `src/<模块>/api/**/api.ts` 的 stub 文件头带指纹
@@ -825,7 +839,7 @@ export default { get };
825
839
  3. **生成规范**:`oj openapi -c config.yaml --module-root src -b /api -o openapi.json`
826
840
  (历史布局用 `-d api/src`;项目级单文件、含全部模块;组级 `.schema`/`.response` 已由
827
841
  oj 内省期合并)。
828
- 4. **生成 client**:`ojm api` → `src/<模块>/web/__client/{api.ts, api.schemas.ts}`
842
+ 4. **生成 client**:`ojm api` → `src/<模块>/web/__client/{index.ts, api.schemas.ts}`
829
843
  (历史布局 `web/src/<模块>/__client/`)。
830
844
  5. **改前端消费点**:函数名从旧人类命名(`fetchNotifications`)改为
831
845
  `camelCase(operationId)`(`notificationNotificationsGet`)——用「逐字节比对」识别,
@@ -897,9 +911,9 @@ Provider 就是:「这块别用框架自带的了,换成我的实现。」
897
911
 
898
912
  ### 4.4 案例:接管通知功能(标准四文件)
899
913
 
900
- **① handler `.schema`**(`src/notification/api/api.ts` 上各动词挂的 `.schema`)——见 §3.3 与 §3.4。
914
+ **① handler `.schema`**(`src/app/api/notification/api.ts` 上各动词挂的 `.schema`)——见 §3.3 与 §3.4。
901
915
 
902
- **② handler**(`src/notification/api/api.ts`,模块根嵌套组——组名 = 路径段,oj v0.1.64):
916
+ **② handler**(`src/app/api/notification/api.ts`,模块根嵌套组——组名 = 路径段,oj v0.1.64):
903
917
 
904
918
  ```ts
905
919
  const notifications = {
@@ -932,7 +946,7 @@ export default { notifications, /* push, "push-broadcast" 等同理 */ };
932
946
  ```ts
933
947
  import type { NotificationsApiProvider } from "@oj-module/runtime";
934
948
  import { defineModule } from "@oj-module/runtime";
935
- import { createNotificationClient } from "./__client/api";
949
+ import { createNotificationClient } from "./__client";
936
950
 
937
951
  export default defineModule({
938
952
  name: "notification",
@@ -957,7 +971,7 @@ export default defineModule({
957
971
  **⑤ 跨模块发射原语(任何后端模块都能推通知)**
958
972
 
959
973
  通知不是「只有通知模块自己用」的功能——下单、审核、@我等其它模块也需要给某个人推一条提醒。
960
- 把发射逻辑收敛成 `src/notification/api/_shared/notify.ts` 里的纯函数原语,其它模块**别名导入
974
+ 把发射逻辑收敛成 `src/app/api/notification/_shared/notify.ts` 里的纯函数原语,其它模块**别名导入
961
975
  直接调用,无需走 HTTP**:
962
976
 
963
977
  ```ts
@@ -990,7 +1004,7 @@ export default {
990
1004
  不带 `node_modules`——dev 能跑、release 或装到站点后必炸。后端共享代码放本模块
991
1005
  `api/_shared/`,跨模块走 `#/<模块名>/_shared/...` 别名;类型与 runtime 的
992
1006
  `@oj-module/runtime/notify` 保持同形即可(共享契约类型单一真源在 runtime)。
993
- - **`_shared` 必须嵌进所属模块的 api 侧**(`src/notification/api/_shared/`):
1007
+ - **`_shared` 必须嵌进所属模块的 api 侧**(`src/app/api/notification/_shared/`):
994
1008
  `src/` 下每个目录都得是模块(`api/manifest.yaml` 或 `web/entry.ts`),`src/_shared` 会被
995
1009
  `ojm dev/build` 判成坏目录;放进模块内则是「无 `api.ts` 的纯工具目录」,不产生路由。
996
1010
  (历史布局同理:`api/src/notification/_shared/`,而 `api/src/_shared` 会被 oj 当成缺
@@ -1349,7 +1363,7 @@ useEffect(() => {
1349
1363
  复用展示不同内容(列表页/`index` 路由),离开时必须 `resetTableTitle(tabKey)`,否则再进来看到
1350
1364
  上一个内容的标题。
1351
1365
 
1352
- 推荐形态(照 `src/home/web/pages/`、`src/personal-center/web/pages/my-profile/` 抄):
1366
+ 推荐形态(照 `src/app/web/home/pages/`、`src/app/web/personal-center/pages/my-profile/` 抄):
1353
1367
  `BasicContent` 包一层 → 搜索区 + `BasicTable` → 新增/编辑用 antd `Modal` + `Form`
1354
1368
  → 提交后重新拉列表。数据来自 §3.4 的生成函数,**页面里直接 import 裸函数调用**。
1355
1369
 
@@ -1402,7 +1416,7 @@ useEffect(() => {
1402
1416
  `useEffect` 重新注册(上面的形态天然满足)。
1403
1417
  - 同一 key 关闭流程进行中再次触发被忽略(per-key in-flight 去重),双击不会弹两个框。
1404
1418
 
1405
- 可运行示例见 playground-oj `web/src/demo/pages/guard.tsx`(本仓 playground 仍是历史布局;
1419
+ 可运行示例见模板工程 `src/app/web/*/pages/` 下的页面(layouts/权限门同源);
1406
1420
  模板侧对应 `src/<模块>/web/pages/`,表单 dirty → 关页签弹确认)。
1407
1421
 
1408
1422
  ### 6.5 两份 tsconfig:`typecheck` 与 `typecheck:api`
@@ -1556,7 +1570,7 @@ export default async (path: string, html: string) => {
1556
1570
  | 登录 401 且 msg 不是 `invalid credentials` | `/auth/*` 不在 `anonymous_paths` | 加进工程根 `config.yaml`,重启 |
1557
1571
  | 登出/回登录页空白或 404 | 工程缺 `login` 模块(shell 无内置登录回落) | 在 `web.config.ts` 保留 `login` |
1558
1572
  | 登录后 / 点 logo 落 React Router 错误边界 | 工程缺 `home` 模块 | 在 `web.config.ts` 保留 `home`(尽量靠前) |
1559
- | 通知铃 404 `no route matched`(`/api/notifications`) | 前端 `notification` 模块没加载 / 没注册 `notificationsApi`,回落到内置只读兜底——而脚手架不再提供 root 级 `/api/notifications` 模块 | 保留 `web.config.ts` 的 `notification` 登记,onInit 里注册四方法(模板 `src/notification/web/entry.ts`);后端由 `src/notification/api` 提供 `/api/notification/*` |
1573
+ | 通知铃 404 `no route matched`(`/api/notifications`) | 前端 `notification` 模块没加载 / 没注册 `notificationsApi`,回落到内置只读兜底——而脚手架不再提供 root 级 `/api/notifications` 模块 | 保留 `web.config.ts` 的 `notification` 登记,onInit 里注册四方法(模板 `src/app/web/notification/entry.ts`);后端由 `src/app/api/notification` 提供 `/api/notification/*` |
1560
1574
  | `@types/react` / `typescript` 装成 `*` | 宿主 versions.json 未列 | 安装后按实际版本钉死(`*` 不可复现) |
1561
1575
  | 迁移账本落后、服务拒绝启动 | release `verify` 闸 | 先 `ojm migrate -d api/dist`(透传自动补 `-c`) |
1562
1576
  | `seed.sql` 没生效 / 语法错 | 按 `;` 朴素切分,注释里有分号 | 去掉注释里的分号字面量 |
@@ -1579,15 +1593,15 @@ export default async (path: string, html: string) => {
1579
1593
 
1580
1594
  | 想抄什么 | 抄这里 |
1581
1595
  |---|---|
1582
- | 带页面的模块(路由 / i18n / order) | `src/home/web/entry.ts`、`src/demo/web/entry.ts` |
1583
- | 接管通知 provider(四方法全量) | `src/notification/web/entry.ts` |
1584
- | 上传 provider(action + headers 方法) | `src/personal-center/web/entry.ts` |
1585
- | 无页面模块(`routes: []`) | 同 `src/notification/web/entry.ts` |
1586
- | handler `.schema` 入参 + 平级 `.response` 出参写法(GET + POST、参数段) | `src/notification/api/api.ts` |
1587
- | handler 写法(嵌套组列表 / 写入 / 403 归属) | `src/notification/api/api.ts`(模块根单文件,`read`/`read-all`/`clear` 子组) |
1588
- | 统一异常出口 `respond()`(service 抛 `Error & {status}` → 信封 `json.fail`) | 模板 `src/_platform/api/_shared/respond.ts` + 各 handler 用法(§1.7-1) |
1589
- | 生成物长什么样(含工厂) | `src/notification/web/__client/api.ts` |
1590
- | 建表三件套 | `src/notification/api/{schema.yaml,manifest.yaml,migrations/}` |
1596
+ | 带页面的模块(路由 / i18n / order) | `src/app/web/home/entry.ts`、`src/app/web/entry.ts` |
1597
+ | 接管通知 provider(四方法全量) | `src/app/web/notification/entry.ts` |
1598
+ | 上传 provider(action + headers 方法) | `src/app/web/personal-center/entry.ts` |
1599
+ | 无页面模块(`routes: []`) | 同 `src/app/web/notification/entry.ts` |
1600
+ | handler `.schema` 入参 + 平级 `.response` 出参写法(GET + POST、参数段) | `src/app/api/notification/api.ts` |
1601
+ | handler 写法(嵌套组列表 / 写入 / 403 归属) | `src/app/api/notification/api.ts`(模块根单文件,`read`/`read-all`/`clear` 子组) |
1602
+ | 统一异常出口 `respond()`(service 抛 `Error & {status}` → 信封 `json.fail`) | 模板 `src/app/api/_platform/_shared/respond.ts` + 各 handler 用法(§1.7-1) |
1603
+ | 生成物长什么样(含工厂) | `src/app/web/notification/__client/index.ts` |
1604
+ | 建表三件套 | `src/app/api/notification/{schema.yaml,manifest.yaml,migrations/}` |
1591
1605
  | 全局类型(`window.$message` 等) | 工程根 `env.d.ts` |
1592
1606
 
1593
1607
  另有:
@@ -2009,7 +2023,7 @@ ojm trust revoke <key_id> # 吊销:阻断新装/升级,不动已装
2009
2023
 
2010
2024
  ### 12.3 第一方 bootstrap key(首装免 TOFU)
2011
2025
 
2012
- 平台模板内置第一方发布方**公钥**(`src/_platform/api/market/bootstrap.ts` 的
2026
+ 平台模板内置第一方发布方**公钥**(`src/app/api/_platform/market/bootstrap.ts` 的
2013
2027
  `BOOTSTRAP_KEY`):信任库文件首次初始化时自动播种一次——**market 方案首装免 TOFU 直接过
2014
2028
  验签**。发布时序(平台发行人执行):`ojm keygen` → 签 oj-market 首发包 → 公钥 + key_id
2015
2029
  回填 `BOOTSTRAP_KEY` 随模板发布。播种纪律:库已初始化(含空库)不播种——运维 `revoke`
@@ -2225,11 +2239,11 @@ async function post() {
2225
2239
  所以前端消费组织架构的唯一正路是「借壳」:
2226
2240
 
2227
2241
  1. 在**自己模块**的 handler 里调 `resolveReadFace` + 需要的那几个读函数(§14.2);
2228
- 2. 跑 `oj openapi` + `ojm api`,生成本模块的 `src/<模块>/web/__client/api.ts`;
2242
+ 2. 跑 `oj openapi` + `ojm api`,生成本模块的 `src/<模块>/web/__client/index.ts`;
2229
2243
  3. 页面用本模块 client 调。
2230
2244
 
2231
2245
  多一层 handler 换来三件事:接口形状由**你自己的契约**决定(只出页面要的字段)、框架内部实现不进你的
2232
- bundle、契约变更走你自己的版本号。仓库里 `src/system/web/pages/departments` 的组织架构页就是这条路的
2246
+ bundle、契约变更走你自己的版本号。仓库里 `src/app/web/system/pages/departments` 的组织架构页就是这条路的
2233
2247
  另一个方向(它消费的是管理端 `/api/system/departments`,见 §14.4)。
2234
2248
 
2235
2249
  ### 14.4 管理端 CRUD:只有「管组织架构」的模块才该碰
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oj-module/cli",
3
3
  "type": "module",
4
- "version": "0.2.53",
4
+ "version": "0.2.56",
5
5
  "description": "React Antd 模块工程命令行工具(含预构建宿主 shell-dist)",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -49,7 +49,7 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@faker-js/faker": "^10.4.0",
52
- "@oj-module/runtime": "0.2.53",
52
+ "@oj-module/runtime": "0.2.56",
53
53
  "@redocly/cli": "^2.51.0",
54
54
  "@tailwindcss/vite": "^4.3.0",
55
55
  "cac": "^6.7.14",
@@ -276,9 +276,30 @@ function buildShellChildren() {
276
276
  * 路由表;随后 SPA 登录(不刷新页面)菜单回来了,但 /system/* 仍不在路由表里,
277
277
  * 直达恒落 catch-all 404「返回首页」,须硬刷新才恢复。
278
278
  */
279
+ /**
280
+ * 宿主路由根(Boot 建表与登录后重登记共用同一形状,children 按当前角色快照生成)。
281
+ *
282
+ * 路由根只渲染容器(Outlet),整站 chrome 由模块自带的 ContainerLayout 提供;
283
+ * 宿主不叠加自己的 Layout chrome,避免「宿主侧栏 + 模块侧栏」双层嵌套。
284
+ * element 里同时挂 LayoutEffects(暗色类/动态标题/NProgress,与 App 链同源,偏差 4)
285
+ * 与页面级 ErrorBoundary(模块路由不经 runtime LayoutRoot,缺它时渲染抛错整页白屏)。
286
+ */
287
+ function buildShellRoot() {
288
+ return {
289
+ path: "/",
290
+ id: SHELL_ROOT_ID,
291
+ element: /* @__PURE__ */ jsxs(ErrorBoundary, {
292
+ FallbackComponent: PageError,
293
+ children: [/* @__PURE__ */ jsx(LayoutEffects, {}), /* @__PURE__ */ jsx(Outlet, {})]
294
+ }),
295
+ children: buildShellChildren()
296
+ };
297
+ }
279
298
  function repatchShellRoutes() {
280
299
  if (!shellRouter) return;
281
- shellRouter.patchRoutes(SHELL_ROOT_ID, buildShellChildren());
300
+ const internal = shellRouter._internalSetRoutes;
301
+ if (internal) internal.call(shellRouter, [buildShellRoot()]);
302
+ else shellRouter.patchRoutes(SHELL_ROOT_ID, buildShellChildren());
282
303
  const { pathname, search } = shellRouter.state.location;
283
304
  shellRouter.navigate(`${pathname}${search}`, { replace: true }).catch(() => {});
284
305
  }
@@ -313,7 +334,7 @@ function Boot() {
313
334
  (async () => {
314
335
  try {
315
336
  if (typeof navigator !== "undefined" && /wxwork/i.test(navigator.userAgent) && location.pathname.endsWith("/login") && !new URLSearchParams(location.search).get("wecom_ticket")) {
316
- window.location.assign(resolveApiBase() + "/auth/wecom/login");
337
+ window.location.assign(`${resolveApiBase()}/auth/wecom/login`);
317
338
  return;
318
339
  }
319
340
  ensureI18n();
@@ -348,15 +369,7 @@ function Boot() {
348
369
  await hydrateUser();
349
370
  await loadAll(manifest);
350
371
  if (cancelled) return;
351
- shellRouter = createBrowserRouter([{
352
- path: "/",
353
- id: SHELL_ROOT_ID,
354
- element: /* @__PURE__ */ jsxs(ErrorBoundary, {
355
- FallbackComponent: PageError,
356
- children: [/* @__PURE__ */ jsx(LayoutEffects, {}), /* @__PURE__ */ jsx(Outlet, {})]
357
- }),
358
- children: buildShellChildren()
359
- }], {
372
+ shellRouter = createBrowserRouter([buildShellRoot()], {
360
373
  /**
361
374
  * 子路径部署(oj `mounts: [{ prefix: /oj, web: dist, spa: true }]`):
362
375
  * 不设 basename 时 react-router 看到的是含前缀的 `/oj/wiki/...`,
@@ -304,10 +304,10 @@ function getAppInfo() {
304
304
  return {
305
305
  "pkg": {
306
306
  "name": "@oj-module/runtime",
307
- "version": "0.2.53",
307
+ "version": "0.2.56",
308
308
  "license": "MIT"
309
309
  },
310
- "lastBuildTime": "2026-11-02 15:46:13"
310
+ "lastBuildTime": "2026-11-02 23:15:22"
311
311
  };
312
312
  }
313
313
  var init_get_app_info = __esmMin((() => {}));
@@ -7500,11 +7500,14 @@ function topologicalSort(entries, definitions) {
7500
7500
  return sorted;
7501
7501
  }
7502
7502
  async function mergeI18nResources(definition) {
7503
- if (!definition.i18n) return;
7504
- for (const [locale, loader] of Object.entries(definition.i18n)) {
7503
+ if (definition.i18n) for (const [locale, loader] of Object.entries(definition.i18n)) {
7505
7504
  const resources = await loader();
7506
7505
  i18next.addResourceBundle(locale, definition.name, resources.default || resources);
7507
7506
  }
7507
+ for (const [namespace, bundle] of Object.entries(definition.i18nNamespaces ?? {})) for (const [locale, loader] of Object.entries(bundle)) {
7508
+ const resources = await loader();
7509
+ i18next.addResourceBundle(locale, namespace, resources.default || resources);
7510
+ }
7508
7511
  }
7509
7512
  async function loadAll(manifest) {
7510
7513
  const enabledEntries = manifest.modules.filter((m) => m.enabled !== false);
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" href="/favicon.ico" />
7
7
  <title>React Antd Admin · Shell</title>
8
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-vlbkVLVjOGWbsP3B+GCfRg=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
9
- <script type="importmap" nonce="vlbkVLVjOGWbsP3B+GCfRg==">{
8
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-oa6e5O+MEhBiegxvbtPppw=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
9
+ <script type="importmap" nonce="oa6e5O+MEhBiegxvbtPppw==">{
10
10
  "imports": {
11
11
  "react": "/assets/react.js",
12
12
  "react/jsx-runtime": "/assets/jsx-runtime.js",
@@ -130,7 +130,7 @@
130
130
  .boot-skeleton:after{content:"";width:18px;height:18px;margin-left:8px;border:2px solid #d9d9d9;border-top-color:#1677ff;border-radius:50%;animation:bs-spin .8s linear infinite}
131
131
  @keyframes bs-spin{to{transform:rotate(360deg)}}
132
132
  </style>
133
- <script nonce="vlbkVLVjOGWbsP3B+GCfRg==" data-ojm-entry="/assets/index-e_Mp2mb5.js">(function(){try{var ua=(navigator.userAgent||"").toLowerCase(),p=location.pathname;while(p.length>1&&p.charAt(p.length-1)==="/")p=p.slice(0,-1);if(ua.indexOf("wxwork")>=0&&p.slice(-6)==="/login"&&!new URLSearchParams(location.search).get("wecom_ticket")){var a=(typeof window.__OJ_API_BASE__==="string"&&window.__OJ_API_BASE__.trim())||"/api";while(a.length>1&&a.charAt(a.length-1)==="/")a=a.slice(0,-1);location.replace(a+"/auth/wecom/login");return}}catch(e){}var s=document.createElement("script");s.type="module";s.src=document.currentScript.getAttribute("data-ojm-entry");document.head.appendChild(s);})();</script>
133
+ <script nonce="oa6e5O+MEhBiegxvbtPppw==" data-ojm-entry="/assets/index-CImwskAz.js">(function(){try{var ua=(navigator.userAgent||"").toLowerCase(),p=location.pathname;while(p.length>1&&p.charAt(p.length-1)==="/")p=p.slice(0,-1);if(ua.indexOf("wxwork")>=0&&p.slice(-6)==="/login"&&!new URLSearchParams(location.search).get("wecom_ticket")){var a=(typeof window.__OJ_API_BASE__==="string"&&window.__OJ_API_BASE__.trim())||"/api";while(a.length>1&&a.charAt(a.length-1)==="/")a=a.slice(0,-1);location.replace(a+"/auth/wecom/login");return}}catch(e){}var s=document.createElement("script");s.type="module";s.src=document.currentScript.getAttribute("data-ojm-entry");document.head.appendChild(s);})();</script>
134
134
  </head>
135
135
  <body>
136
136
  <div id="root"><div class="boot-skeleton">加载中…</div></div>
@@ -7,9 +7,9 @@
7
7
  "react-router": "7.18.2",
8
8
  "react-router/dom": "7.18.2",
9
9
  "@tanstack/react-query": "5.102.8",
10
- "@oj-module/runtime": "0.2.53",
11
- "@oj-module/runtime/contract": "0.2.53",
12
- "@oj-module/runtime/contract/errors": "0.2.53",
10
+ "@oj-module/runtime": "0.2.56",
11
+ "@oj-module/runtime/contract": "0.2.56",
12
+ "@oj-module/runtime/contract/errors": "0.2.56",
13
13
  "@plane/kit": "0.1.20",
14
14
  "@plane/editor": "0.1.20",
15
15
  "antd": "6.6.1",
@@ -111,7 +111,7 @@ describe("resolveLayout / 历史布局", () => {
111
111
  it("web/<name>/entry.ts 识别为 flat,web 产物落 build/(框架根仓约定不变),api 产物归 dist/api", () => {
112
112
  const root = newRoot();
113
113
  writeFile(root, "web/home/entry.ts", "export default {};\n");
114
- writeFile(root, "web/home/__client/api.ts", "export {};\n");
114
+ writeFile(root, "web/home/__client/index.ts", "export {};\n");
115
115
 
116
116
  const layout = resolveLayout(root);
117
117
  expect(layout.kind).toBe("flat");
@@ -177,3 +177,32 @@ describe("路径取值函数", () => {
177
177
  expect(webSrcOf(layout, "wiki")).toBe(join(layout.modulesSrc, "wiki", "web"));
178
178
  });
179
179
  });
180
+
181
+ /**
182
+ * 聚合容器:src/app 同时有 web 侧(多根前端模块)与 api 下多个后端模块。
183
+ * 模块身份取 api 下那层目录名(= URL 前缀),与平铺 src/<name>/api 并存且互不干扰。
184
+ */
185
+ describe("resolveLayout / 聚合容器(src/app/api/<name>)", () => {
186
+ it("api 子目录各自成模块,web 侧仍是 app 模块", () => {
187
+ const root = newRoot();
188
+ writeFile(root, "src/app/api/system/manifest.yaml", "name: system\nversion: 0.1.0\n");
189
+ writeFile(root, "src/app/api/system/users/api.ts", "export default { get() {} };\n");
190
+ writeFile(root, "src/app/api/notification/manifest.yaml", "name: notification\nversion: 0.1.0\n");
191
+ writeFile(root, "src/app/web/entry.ts", "export default {};\n");
192
+ const layout = resolveLayout(root);
193
+ const mods = scanModules(layout);
194
+
195
+ expect(mods.map(m => m.name)).toEqual(["app", "notification", "system"]);
196
+ expect(mods.find(m => m.name === "system")?.apiSrc).toBe(join(root, "src/app/api/system"));
197
+ expect(mods.find(m => m.name === "system")?.webSrc).toBeUndefined();
198
+ expect(apiSrcOf(layout, "system")).toBe(join(root, "src/app/api/system"));
199
+ expect(webSrcOf(layout, "app")).toBe(join(root, "src/app/web"));
200
+ });
201
+
202
+ it("同名后端模块重复(旧 src/system/api + 新 src/app/api/system)→ 明确报错", () => {
203
+ const root = newRoot();
204
+ writeFile(root, "src/system/api/manifest.yaml", "name: system\nversion: 0.1.0\n");
205
+ writeFile(root, "src/app/api/system/manifest.yaml", "name: system\nversion: 0.1.0\n");
206
+ expect(() => resolveLayout(root)).toThrowError(/模块名重复:system/);
207
+ });
208
+ });