@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/src/init.ts CHANGED
@@ -240,6 +240,24 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
240
240
  // 与 pnpm-workspace.yaml 同型,改由 init 现场生成
241
241
  ensureGitignore(destDir, report);
242
242
 
243
+ // 5d) 旧布局冲突提示:0.2.54 起因/app 同时承载前端聚合模块与后端模块。
244
+ // init 只补缺不删除——老工程重跑 init 会同时留下旧目录:
245
+ // · src/<name>/web 与 src/app/web 同名路由注册两遍(菜单重复);
246
+ // · src/<name>/api 与 src/app/api/<name> 模块名重复(ojm 扫描直接报错)。
247
+ const mergedWeb = path.join(destDir, "src", "app", "web", "entry.ts");
248
+ const mergedApi = path.join(destDir, "src", "app", "api");
249
+ if (fs.existsSync(mergedWeb) || fs.existsSync(mergedApi)) {
250
+ const appModules = ["home", "login", "personal-center", "notification", "system", "_platform", "auth", "web"];
251
+ const oldWeb = appModules.filter(name => fs.existsSync(path.join(destDir, "src", name, "web", "entry.ts")));
252
+ if (oldWeb.length > 0)
253
+ report.warnings.push(`检测到旧布局的前端模块目录(${oldWeb.map(n => `src/${n}/web`).join("、")})与新聚合模块 src/app/web 并存——路由会重复注册。请手工删除这些旧 web 目录。`);
254
+ const oldApi = appModules.filter(name =>
255
+ fs.existsSync(path.join(destDir, "src", name, "api", "manifest.yaml"))
256
+ && fs.existsSync(path.join(mergedApi, name, "manifest.yaml")));
257
+ if (oldApi.length > 0)
258
+ report.warnings.push(`检测到旧布局的后端模块目录(${oldApi.map(n => `src/${n}/api`).join("、")})与新位置 src/app/api/<name> 同名——模块名重复会让 ojm 扫描报错。请手工删除这些旧 api 目录。`);
259
+ }
260
+
243
261
  console.log(`[ojm] init 完成:${destDir}`);
244
262
  console.log(`[ojm] 新增 ${report.created.length} 项,跳过(已存在)${report.skipped.length} 项`);
245
263
  for (const item of report.skipped)
@@ -400,14 +418,18 @@ function pruneUnincludedApiPaths(destDir: string, include: Set<string>, report:
400
418
  // 只裁本次新建的 openapi.json:init 是幂等补缺,不改动既有工程文件
401
419
  if (!report.created.includes("openapi.json"))
402
420
  return;
403
- // 白名单里 `src/<name>/api` 这条(整模块收录)决定「脚手架有哪些后端模块」
421
+ // 白名单里的 api 侧条目决定「脚手架有哪些后端模块」:
422
+ // 平铺 `src/<name>/api` 与聚合容器 `src/app/api/<name>` 都要认。
404
423
  const mods = new Set([...include]
405
- .map(p => /^src\/([^/]+)\/api$/.exec(p)?.[1])
424
+ .map(p => /^src\/([^/]+)\/api$/.exec(p)?.[1] ?? /^src\/[^/]+\/api\/([^/]+)$/.exec(p)?.[1])
406
425
  .filter((m): m is string => !!m));
407
426
  if (mods.size === 0)
408
427
  return;
409
428
  const openapiPath = path.join(destDir, "openapi.json");
410
- const doc = JSON.parse(fs.readFileSync(openapiPath, "utf-8")) as { paths?: Record<string, unknown> };
429
+ const doc = JSON.parse(fs.readFileSync(openapiPath, "utf-8")) as {
430
+ paths?: Record<string, unknown>
431
+ tags?: { name?: string }[]
432
+ };
411
433
  const paths = doc.paths ?? {};
412
434
  const dropped = Object.keys(paths).filter((p) => {
413
435
  const mod = /^\/api\/([^/]+)\//.exec(p)?.[1];
@@ -417,6 +439,10 @@ function pruneUnincludedApiPaths(destDir: string, include: Set<string>, report:
417
439
  return;
418
440
  for (const p of dropped)
419
441
  delete paths[p];
442
+ // 顶层 tags 也要同步:只裁路径会留下指向不存在模块的 tag(契约自相矛盾,
443
+ // oj openapi 重产时又会消失,属 drift 噪声)。
444
+ if (Array.isArray(doc.tags))
445
+ doc.tags = doc.tags.filter(t => typeof t.name !== "string" || mods.has(t.name));
420
446
  fs.writeFileSync(openapiPath, `${JSON.stringify(doc, null, "\t")}\n`);
421
447
  report.created.push(`openapi.json 裁掉未收录 api 模块端点 ${dropped.length} 条`);
422
448
  }
package/src/layout.ts CHANGED
@@ -6,13 +6,16 @@
6
6
  *
7
7
  * 三种布局(互斥):
8
8
  * module-root <project>/src/<name>/{api,web} 新布局:一目录一模块(设计 v2)
9
+ * 也支持聚合容器:src/app/{api/<模块>,web}——app 的 web 侧是多根前端模块,
10
+ * api 侧每个子目录各自是独立后端模块(模块名 = 子目录名 = URL 前缀)
9
11
  * new <project>/web/src/<name> 历史 uni-dev 布局
10
12
  * flat <project>/web/<name> 框架根仓 dogfooding(产物落 build/)
11
13
  *
12
14
  * module-root 的判定只认「模块根标记文件」而不是单纯的 src/ 存在性:工程里
13
15
  * 常有不属于模块的 src/(脚本、共享库),凭目录名猜会误判。
14
16
  * `src/<x>/api/manifest.yaml` 与 `src/<x>/web/entry.ts` 是唯一真源(设计 C-6:
15
- * 一侧可缺省,两侧皆无则不是模块)。
17
+ * 一侧可缺省,两侧皆无则不是模块)。聚合容器下后端模块根是 `src/app/api/<x>/manifest.yaml`
18
+ * (见 collectApiRoots)。
16
19
  *
17
20
  * 产物根(2026-10-22 起收进工程根单一 dist/,原 {web,api}/dist 与源码混树):
18
21
  * dist/api oj 扁平 release 根(<name>-<version>/ 同层兄弟 + manifests.yaml),三种布局一致
@@ -31,7 +34,7 @@ export type LayoutKind = "module-root" | "new" | "flat";
31
34
  /** 模块一侧的可缺省组合:两侧皆无不是模块(设计 C-6) */
32
35
  export interface ModuleSides {
33
36
  name: string
34
- /** 后端源码目录 `<project>/src/<name>/api`(无此侧为 undefined) */
37
+ /** 后端源码目录(`src/<name>/api` 或聚合容器 `src/app/api/<name>`;无此侧为 undefined) */
35
38
  apiSrc?: string
36
39
  /** 前端源码目录 `<project>/src/<name>/web`(无此侧为 undefined) */
37
40
  webSrc?: string
@@ -82,13 +85,44 @@ function isFile(p: string): boolean {
82
85
  * 坏目录只在 module-root 被判定为当前布局后才报错(见 resolveLayout),
83
86
  * 否则一个恰好带 `src/utils/api/` 的历史工程会被误伤。
84
87
  */
88
+ /** 递归扫描时跳过的目录名(产物 / 依赖 / 生成物,均不可能承载模块根) */
89
+ const SCAN_SKIP_DIRS = new Set(["node_modules", "dist", "build", "__client", ".cache", ".git"]);
90
+
91
+ /**
92
+ * 收集一个容器目录下的后端模块根。
93
+ *
94
+ * 两种形态并存(oj 的 `--module-root` 支持任意深度):
95
+ * · `<container>/api/manifest.yaml` —— 模块根在 api 自身(src/<name>/api)
96
+ * · `<container>/api/<name>/manifest.yaml` —— 聚合容器下的子模块(src/app/api/system)
97
+ *
98
+ * 后者用于「一个应用目录承载多个业务域」:src/app 的 web 侧是多根聚合模块,
99
+ * api 侧按业务域分成 system / notification / … 各自仍是独立模块(模块名 = 目录名
100
+ * = URL 前缀,故不能并成一个 app 模块)。命中模块根即停,不再往 api 内部递归。
101
+ */
102
+ function collectApiRoots(container: string, out: string[]): void {
103
+ const apiDir = path.join(container, "api");
104
+ if (isFile(path.join(apiDir, "manifest.yaml"))) {
105
+ out.push(apiDir);
106
+ return;
107
+ }
108
+ if (!isDir(apiDir))
109
+ return;
110
+ for (const ent of fs.readdirSync(apiDir, { withFileTypes: true })
111
+ .filter(e => e.isDirectory() && !e.name.startsWith(".") && !SCAN_SKIP_DIRS.has(e.name))
112
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
113
+ if (isFile(path.join(apiDir, ent.name, "manifest.yaml")))
114
+ out.push(path.join(apiDir, ent.name));
115
+ }
116
+ }
117
+
85
118
  function scanModuleRoot(projectRoot: string): { modules: ModuleSides[], broken: string[] } | null {
86
119
  const srcDir = path.join(projectRoot, "src");
87
120
  if (!isDir(srcDir))
88
121
  return null;
89
122
 
90
- const modules: ModuleSides[] = [];
123
+ const byName = new Map<string, ModuleSides>();
91
124
  const broken: string[] = [];
125
+ const apiOwners = new Map<string, string>();
92
126
  // 按名字排序:readdir 的顺序取决于文件系统与创建历史(同一份源码在不同机器上可能不同),
93
127
  // 而枚举顺序会进入构建顺序与产物内容——不排序则「同布局两次构建 sha256 相等」无法保证。
94
128
  const entries = fs.readdirSync(srcDir, { withFileTypes: true })
@@ -96,22 +130,41 @@ function scanModuleRoot(projectRoot: string): { modules: ModuleSides[], broken:
96
130
  .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
97
131
  for (const ent of entries) {
98
132
  const dir = path.join(srcDir, ent.name);
99
- const apiDir = path.join(dir, "api");
100
133
  const webDir = path.join(dir, "web");
101
- const hasApi = isFile(path.join(apiDir, "manifest.yaml"));
102
134
  const hasWeb = isFile(path.join(webDir, "entry.ts"));
103
- if (!hasApi && !hasWeb) {
135
+ const apiRoots: string[] = [];
136
+ collectApiRoots(dir, apiRoots);
137
+ if (!hasWeb && apiRoots.length === 0) {
104
138
  // 只把「有 api/ 或 web/ 子目录但缺标记」算坏目录;纯共享库目录(无两者)静默跳过
105
- if (isDir(apiDir) || isDir(webDir))
139
+ if (isDir(path.join(dir, "api")) || isDir(webDir))
106
140
  broken.push(ent.name);
107
141
  continue;
108
142
  }
109
- modules.push({
110
- name: ent.name,
111
- apiSrc: hasApi ? apiDir : undefined,
112
- webSrc: hasWeb ? webDir : undefined,
113
- });
143
+ if (hasWeb)
144
+ byName.set(ent.name, { name: ent.name, webSrc: webDir });
145
+ for (const apiDir of apiRoots) {
146
+ // 模块名:平铺 src/system/api → system(api 的父目录);
147
+ // 聚合 src/app/api/system → system(api 下方那层目录本身)。
148
+ const name = path.basename(apiDir) === "api"
149
+ ? path.basename(path.dirname(apiDir))
150
+ : path.basename(apiDir);
151
+ const prev = apiOwners.get(name);
152
+ if (prev) {
153
+ throw new Error(
154
+ `[ojm] 后端模块名重复:${name} 同时出现在 ${path.relative(projectRoot, prev)} 与 `
155
+ + `${path.relative(projectRoot, apiDir)}。模块名即 URL 前缀,必须唯一——`
156
+ + "删掉旧布局目录(或改名)后重试。",
157
+ );
158
+ }
159
+ apiOwners.set(name, apiDir);
160
+ const hit = byName.get(name);
161
+ if (hit)
162
+ hit.apiSrc = apiDir;
163
+ else
164
+ byName.set(name, { name, apiSrc: apiDir });
165
+ }
114
166
  }
167
+ const modules = [...byName.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
115
168
  return { modules, broken };
116
169
  }
117
170
 
@@ -244,7 +297,7 @@ export function legacyApiModuleRoots(projectRoot: string): ApiModuleRoot[] {
244
297
  * 为什么不直接 walk `apiModulesSrc(layout)`:module-root 布局的树根是 `src`,模块的
245
298
  * api 侧在 `<src>/<name>/api`,中间那层 `api` 是**布局分隔符、不是路由段**。拿 `src`
246
299
  * 当扫描根会双重出错——路由镜像多出 `api` 段(`system/api/users` ≠ 真实 `system/users`),
247
- * 且 `src/<name>/web/__client/api.ts` 这种前端生成物也会被当 handler 扫进来。
300
+ * 且 `src/<name>/web/__client/index.ts` 这种前端生成物也会被当 handler 扫进来。
248
301
  *
249
302
  * module-root → 按 `<name>/api/manifest.yaml` 判模块(与 build 同源,name 取 manifest);
250
303
  * 历史布局 → `<project>/api/src` 的一级目录即模块。
@@ -267,9 +320,12 @@ export function moduleDir(layout: ProjectLayout, name: string): string {
267
320
  * 历史布局为 `<project>/api/src/<name>`——不在 modulesSrc 之下,必须走 projectRoot。
268
321
  */
269
322
  export function apiSrcOf(layout: ProjectLayout, name: string): string {
270
- return layout.kind === "module-root"
271
- ? path.join(layout.modulesSrc, name, "api")
272
- : path.join(layout.projectRoot, "api", "src", name);
323
+ if (layout.kind !== "module-root")
324
+ return path.join(layout.projectRoot, "api", "src", name);
325
+ // 聚合容器(src/app/api/<name>)与平铺(src/<name>/api)并存,以扫描结果为准;
326
+ // 找不到时回落旧口径,保证报错信息里的路径仍指向「本该在的位置」。
327
+ const hit = scanModules(layout).find(m => m.name === name)?.apiSrc;
328
+ return hit ?? path.join(layout.modulesSrc, name, "api");
273
329
  }
274
330
 
275
331
  /**
@@ -1,4 +1,4 @@
1
- import type { InstallContext } from "../../../templates/src/_platform/api/_lifecycle/steps/preflight";
1
+ import type { InstallContext } from "../../../templates/src/app/api/_platform/_lifecycle/steps/preflight";
2
2
 
3
3
  // BDD:bundle 级 manifest.yaml 契约校验(P1-11,C2 规则构建列)。
4
4
  // 规则真源:docs/prd/solution/202610171254-lifecycle-manifest-contract.md(C2-01~33 构建列,§4.1 矩阵);
@@ -13,7 +13,7 @@ import { describe, expect, it } from "vitest";
13
13
  import { parse } from "yaml";
14
14
  // 跨端引用(双端一致钉):被测实现同仓分居两运行时(计划 §1「两运行时取舍」),
15
15
  // 测试直接互调,比「两套件各读同一夹具」更强——漂移即本文件红。
16
- import { preflight } from "../../../templates/src/_platform/api/_lifecycle/steps/preflight";
16
+ import { preflight } from "../../../templates/src/app/api/_platform/_lifecycle/steps/preflight";
17
17
  import { checkLocalesComplete, checkTablesAgainstSchema, parseManifest, RESERVED_NAMES, reservedNameSuggestions, validateContract } from "../../manifest-contract";
18
18
 
19
19
  const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "fixtures");
@@ -68,7 +68,7 @@ describe("given 一个装了 oj 二进制的源工程 When 自举隔离站点",
68
68
 
69
69
  const site = await bootstrapIsolatedSite({ projectRoot: src, starter });
70
70
 
71
- expect(existsSync(join(site.root, "src", "_platform", "api", "seed.sql"))).toBe(true);
71
+ expect(existsSync(join(site.root, "src", "app", "api", "_platform", "seed.sql"))).toBe(true);
72
72
  expect(existsSync(join(site.root, "bin", "oj"))).toBe(true);
73
73
  expect(existsSync(join(site.root, "bin", "plugins", "auth.wasm"))).toBe(true);
74
74
  expect(site.root).not.toBe(src);
package/src/site.ts CHANGED
@@ -21,7 +21,7 @@ export function tenantHeaders(): Record<string, string> {
21
21
  return { [TENANT_HEADER]: DEFAULT_TENANT };
22
22
  }
23
23
 
24
- /** 任务记录(线形状镜像;真源 templates/src/_platform/api/_lifecycle/types.ts:65,客户端不 import 模板内部) */
24
+ /** 任务记录(线形状镜像;真源 templates/src/app/api/_platform/_lifecycle/types.ts:65,客户端不 import 模板内部) */
25
25
  export interface TaskRecord {
26
26
  /**
27
27
  * 记录 schema 版本(P2-10 线形状镜像):缺省(undefined)=旧记录=v1 语义照常渲染;
@@ -6,82 +6,88 @@
6
6
 
7
7
  ```bash
8
8
  pnpm install
9
- pnpm dev # 开发:前端 devServer + oj 后端(api/src 保存即热更)
9
+ pnpm dev # 开发:前端 devServer + oj 后端(src 下 api.ts 保存即热更)
10
10
  pnpm build # 构建:oj build + 前端全站合并到 dist/web
11
11
  pnpm preview # 预览:oj migrate + server(release 产物)
12
12
  ```
13
13
 
14
- 登录账号:`admin` / `aA123456`(见 `api/src/_platform/seed.sql`)。
14
+ 登录账号:`superadmin` / `aA123456`(四个内置角色账号见 `src/app/api/_platform/seed.sql`)。
15
15
 
16
- ## 系统管理模块
16
+ ## 目录(一个 `src/app` 装下整个应用)
17
17
 
18
- `web/src/system/`(用户管理 / 成员资格 / 角色权限 / 操作日志 / 站点设置 / 登录方式)
19
- 的前端 API 客户端 `__client/` **已随模板分发**(与 auth/notification 等一致),`pnpm dev`
20
- 开箱即用、「系统管理」菜单直接可见。
18
+ ```
19
+ config.yaml 站点/数据库/租户配置(init 现场签发证书到 config/)
20
+ src/app/api/<name>/ 后端模块(模块名 = URL 前缀;目录镜像:<路径>/api.ts)
21
+ src/app/web/ 前端聚合模块(多根,一个 entry.ts 挂 /home /login /…)
22
+ <root>/ 每个根:routes.ts + pages/ + locales/ + __client/
23
+ src/oj-market/web/ 解决方案市场(第一方能力,独立模块,删目录即下线)
24
+ dist/api/ oj build 后端产物
25
+ dist/web/ 站点产物(shell + modules + modules.json)
26
+ bin/ oj 二进制(init 解压,不入库)
27
+ ```
28
+
29
+ - **后端**按业务域分模块:`_platform` / `auth` / `notification` / `personal-center` /
30
+ `system` / `web`,各自是独立 oj 模块(模块名即 `/api/<name>` 前缀),只是统一收在
31
+ `src/app/api` 下——顶层不再散落同名目录。
32
+ - **前端** `src/app/web` 是一个**多根模块**:一个 `entry.ts` 里挂六个顶层路由,各根按
33
+ `handle.order` 排菜单,页面按根分目录。新增业务页面时建议单独建模块
34
+ (`pnpm exec ojm new <name>`),不要往聚合模块里塞。
21
35
 
22
- `__client` 是从 `openapi.json`(模板自带,由 `api/src/system` 契约产出)生成的产物;
23
- 契约变更后刷新一次即可:
36
+ ## 契约与 `__client`
37
+
38
+ 前端调后端一律走 `ojm api` 从 `openapi.json` 生成的 `__client/`(随模板分发,开箱即用)。
39
+ 改了 handler 的 `.schema`(入参)/ `.response`(出参)后刷新:
24
40
 
25
41
  ```bash
26
- bin/oj openapi -c config.yaml -d api/src -b /api -o openapi.json # 重产契约
27
- pnpm exec ojm api # 重新生成 web/src/*/__client(dev 检测到 openapi.json 变化也会自动重生成)
42
+ bin/oj openapi -c config.yaml --module-root src -b /api -o openapi.json # 重产契约
43
+ pnpm exec ojm api # 重生成 __client
28
44
  ```
29
45
 
30
- 成员资格页仅 superadmin 可见。改了 `api/src` 的 handler `.schema`/`.response` 后重跑这两条保持同步
31
- (`ojm api --check` 可对账,零漂移为发布门禁)。
46
+ 生成物入口是 `__client/index.ts`(**不是 `api.ts`**:oj dev 会把模块目录下的
47
+ `api.ts` 当路由扫描)。页面里用 `from "../__client"` 导入。
32
48
 
33
- ## 目录
34
-
35
- ```
36
- bin/ oj 二进制(init 解压,不入库)
37
- api/src/ 后端模块源码(目录镜像路由:api/src/<模块>/<路径>/api.ts)
38
- dist/api/ oj build 产物
39
- web/src/ 前端模块源码
40
- dist/web/ 构建产物(完整站点)
41
- ```
49
+ 多根聚合模块的 client 归属由 `web.config.ts` 的 `clientOwners` 决定(`system → app`),
50
+ 生成物落 `src/app/web/system/__client`,与页面同级——页面相对导入不用改。
51
+ `ojm api --check` 可对账漂移(零漂移为发布门禁)。
42
52
 
43
53
  ## 注意
44
54
 
45
- - 改 `api/src` 下的 api.ts 保存即生效;**新增/删除后端模块目录、改
55
+ - 改 `src/app/api/**/api.ts` 保存即生效;**新增/删除模块目录、改
46
56
  schema/migrations/config.yaml 需重启 `ojm dev`**(oj 热更边界)。
47
- - 后端开发请用 `/oj-api-dev` skill(`.claude/skills/oj-api-dev/`),
48
- 手册按章节按需读。
49
- - `api/config.yaml` 的证书为 init 现场签发的本地 dev 证书,不构成信任边界;
57
+ - 后端开发请用 `/oj-api-dev` skill(`.claude/skills/oj-api-dev/`),手册按章节按需读。
58
+ - `config/` 下的证书为 init 现场签发的本地 dev 证书,不构成信任边界;
50
59
  生产环境用 oj 仓库 `oj-cert gen` 全新签发并替换公钥。
51
60
 
52
61
  ## 排障 / 常见问题
53
62
 
54
- - **`pnpm dev` / `pnpm build` 报 `Could not resolve "./__client/api"`**
55
- 某前端模块的 `entry.ts` 引用了 `web/src/<模块>/__client/api`,但该目录不存在——它是
56
- `ojm api` 从 `openapi.json` 生成的产物。在工程根跑一次即可补齐:
63
+ - **`pnpm dev` / `pnpm build` 报 `Could not resolve "../../__client"`**
64
+ 对应根的 `__client` 缺失——它是 `ojm api` 的生成物。在工程根跑一次即可补齐:
57
65
 
58
66
  ```bash
59
- pnpm exec ojm api # 生成 web/src/*/__client(dev 检测到 openapi.json 变化也会自动重生成)
67
+ pnpm exec ojm api # dev 检测到 openapi.json 变化也会自动重生成
60
68
  ```
61
69
 
62
- 改完后端 `api/src` 的契约后,重产契约再刷新:
70
+ 改完后端契约后先重产契约再刷新:
63
71
 
64
72
  ```bash
65
- bin/oj openapi -c config.yaml -d api/src -b /api -o openapi.json
73
+ bin/oj openapi -c config.yaml --module-root src -b /api -o openapi.json
66
74
  pnpm exec ojm api
67
75
  ```
68
76
 
69
77
  - **`__client/` 下的文件能直接改吗?**
70
- 不能手改、也不入库——它是生成物,下次 `ojm api` 会覆盖你的手改。需要改接口就改
71
- `api/src` 的 handler `.schema`/`.response`,再跑上面的命令刷新。
72
- `ojm api --check` 可对账契约与产物的漂移(零漂移为发布门禁)。
78
+ 不能手改、也不入库——它是生成物,下次 `ojm api` 会覆盖。改接口就改 handler 的
79
+ `.schema`/`.response`,再跑上面的命令刷新。
80
+
81
+ - **从旧版本升级(`__client/api.ts`)**
82
+ 0.2.54 起入口改名 `index.ts`(`api.ts` 会被 oj dev 当路由扫描)。重跑一次
83
+ `pnpm exec ojm api` 即可:新文件写入、旧 `api.ts` 自动删除;页面导入改为
84
+ `from "../__client"`。
73
85
 
74
86
  - **`pnpm dlx @oj-module/cli@latest init` 生成的脚手架缺模块 / 看起来很旧**
75
- pnpm dlx 会按首次解析到的版本**缓存** CLI 可执行文件。若某次 `@latest` 指向的是旧版
76
- (例如早于系统管理模块的 `0.1.x`),之后重跑仍可能命中旧缓存,生成的脚手架就会缺新模块、
77
- 模板偏旧(典型表现:`web/src` 下没有 `system` 目录)。
78
- 排查:`ls web/src` 看是否缺预期模块。
79
- 解决:清掉 dlx 缓存后重跑——
87
+ pnpm dlx 会按首次解析到的版本**缓存** CLI 可执行文件。排查:`ls src/app/api` 看是否缺预期模块。
88
+ 解决:清掉 dlx 缓存后显式指定版本重跑——
80
89
 
81
90
  ```bash
82
91
  rm -rf ~/Library/Caches/pnpm/dlx # macOS;其它平台见 pnpm 文档的 dlx-store 路径
83
- # 或:pnpm dlx clear @oj-module/cli
84
- pnpm dlx @oj-module/cli@<明确版本> init <工程名> --yes # 显式指定版本最稳
92
+ pnpm dlx @oj-module/cli@<明确版本> init <工程名> --yes
85
93
  ```
86
-
87
- 养成显式带版本号(如 `@oj-module/cli@0.2.4`)的习惯,可避免 `@latest` 缓存歧义。
@@ -40,7 +40,7 @@ include:
40
40
  - src/styles.css
41
41
  # 后端 typecheck 工程(package.json 的 typecheck:api 指向它);只收后端、排除 web
42
42
  - src/tsconfig.json
43
- # —— 后端:配置 / 种子 / 测试 / 默认模块(模块源码在 src/<name>/api)——
43
+ # —— 后端:配置 / 种子 / 测试 / 默认模块(模块源码在 src/app/api/<模块>)——
44
44
  - config.yaml
45
45
  - .ojm-api-exempt.json
46
46
  - seed.mysql.sql
@@ -71,8 +71,12 @@ include:
71
71
  - tests/rbac-solution-perms.spec.ts
72
72
  - tests/rbac-tables.spec.ts
73
73
  - tests/rbac-users.spec.ts
74
+ # 角色页展示层过滤 superadmin(后端 listRoles 仍返回;页面源码随 src/app/web/system 下发)
75
+ - tests/roles-page.spec.ts
74
76
  - tests/seed-role-accounts.spec.ts
75
- # 企微连接器与组织同步:源码本就分发(src/auth/api),对应回归也必须跟着走,
77
+ # 用户页行级门禁:只有 superadmin 能改 superadmin 账号(后端 assertCanManageTarget 的另一面)
78
+ - tests/users-page.spec.ts
79
+ # 企微连接器与组织同步:源码本就分发(src/app/api/auth),对应回归也必须跟着走,
76
80
  # 否则用户工程升级后改配置/换套件时没有任何本地自检手段。
77
81
  - tests/wecom-admin.spec.ts
78
82
  - tests/wecom-client-timeout.spec.ts
@@ -95,20 +99,30 @@ include:
95
99
  - tests/lifecycle/uninstall.spec.ts
96
100
  - tests/lifecycle/verify-checks.spec.ts
97
101
  - tests/lifecycle/verify-report.spec.ts
98
- - src/_platform/api
99
- - src/auth/api
100
- - src/notification/api
101
- - src/personal-center/api
102
- - src/system/api
103
- - src/web/api
104
- # —— web 前端模块(home 只列页面源码,__client 见顶部说明)——
105
- - src/_platform/web
106
- - src/auth/web
107
- - src/home/web/entry.ts
108
- - src/home/web/locales
109
- - src/home/web/pages
110
- - src/login/web
111
- - src/notification/web
102
+ # 框架内置后端全部收在 src/app/api/<模块>(模块名 = URL 前缀,故仍是独立模块)。
103
+ # home 的 api 是 demo-only 不进脚手架(见下),其余全部下发。
104
+ - src/app/api/_platform
105
+ - src/app/api/auth
106
+ - src/app/api/notification
107
+ - src/app/api/personal-center
108
+ - src/app/api/system
109
+ - src/app/api/web
110
+ # —— web 前端模块 ——
111
+ # 纯 client 归属目录(无页面模块):_platform 的生成物被 oj-market 引用,
112
+ # auth / web 的是各自后端契约的配套产物,一并下发保证 ojm api --check 开箱即过。
113
+ - src/app/web/_platform
114
+ - src/app/web/auth
115
+ # 框架内置页面聚合为一个多根模块(首页/登录/个人中心/通知/系统管理)。
116
+ # home 只列 routes/locales/pages:其 api 模块是 demo-only 不进脚手架,__client
117
+ # 留着会指向不存在的端点(见顶部说明),故逐子路径收录而不是整棵 src/app/web。
118
+ - src/app/web/entry.ts
119
+ - src/app/web/home/routes.ts
120
+ - src/app/web/home/locales
121
+ - src/app/web/home/pages
122
+ - src/app/web/login
123
+ - src/app/web/notification
124
+ - src/app/web/personal-center
125
+ - src/app/web/system
112
126
  # 解决方案市场:默认第一方能力。逐子路径收录——__tests__ 不进脚手架
113
127
  # (用户工程无 web 侧 vitest 基建,拷了是死文件)
114
128
  - src/oj-market/web/entry.ts
@@ -119,6 +133,4 @@ include:
119
133
  - src/oj-market/web/locales
120
134
  - src/oj-market/web/pages
121
135
  - src/oj-market/web/utils
122
- - src/personal-center/web
123
- - src/system/web
124
- - src/web/web
136
+ - src/app/web/web