@oj-module/cli 0.2.18 → 0.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/devkit/README.md +4 -4
  2. package/devkit/SKILL.md +41 -37
  3. package/devkit/framework-dev.md +6 -5
  4. package/devkit/manual.md +352 -168
  5. package/devkit/menu-rules.md +3 -3
  6. package/devkit/module-dev-and-cdn-playground.md +31 -22
  7. package/devkit/sql-builder-missing-capabilities-pr.md +11 -6
  8. package/package.json +2 -2
  9. package/shell-dist/assets/runtime.js +59 -34
  10. package/shell-dist/index.html +2 -2
  11. package/shell-dist/versions.json +3 -3
  12. package/src/__tests__/build-backend.test.ts +54 -0
  13. package/src/__tests__/init-packagejson.test.ts +29 -0
  14. package/src/__tests__/layout.test.ts +176 -0
  15. package/src/__tests__/module-list.test.ts +66 -0
  16. package/src/__tests__/oj-caps.test.ts +114 -0
  17. package/src/alias-paths.ts +102 -0
  18. package/src/args.ts +54 -4
  19. package/src/build.ts +41 -9
  20. package/src/builtin.ts +93 -0
  21. package/src/bundle.ts +254 -38
  22. package/src/config.ts +82 -9
  23. package/src/contract/__tests__/market-client.integration.test.ts +14 -14
  24. package/src/contract/check.ts +56 -28
  25. package/src/contract/emit-stub.ts +21 -9
  26. package/src/contract/handler-scan.ts +19 -10
  27. package/src/contract/run.ts +14 -4
  28. package/src/dev.ts +80 -9
  29. package/src/index.ts +27 -7
  30. package/src/info.ts +69 -10
  31. package/src/init.ts +114 -28
  32. package/src/layout.ts +282 -24
  33. package/src/lifecycle/__tests__/args.test.ts +20 -0
  34. package/src/lifecycle/__tests__/builtin.test.ts +97 -0
  35. package/src/lifecycle/__tests__/bundle.test.ts +184 -6
  36. package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
  37. package/src/lifecycle/__tests__/new.test.ts +129 -0
  38. package/src/lifecycle/__tests__/smoke.test.ts +2 -1
  39. package/src/manifest-contract.ts +16 -3
  40. package/src/market/__tests__/bootstrap.test.ts +17 -9
  41. package/src/market/bootstrap.ts +30 -17
  42. package/src/market/publish.ts +10 -2
  43. package/src/new.ts +208 -0
  44. package/src/oj-caps.ts +133 -0
  45. package/src/oj.ts +27 -4
  46. package/src/preview-config.ts +1 -1
  47. package/src/preview.ts +2 -1
  48. package/src/site.ts +2 -2
  49. package/src/usage.ts +24 -12
  50. package/src/vendor.ts +5 -1
  51. package/templates/{api/config.yaml → config.yaml} +13 -8
  52. package/templates/global.d.ts +7 -6
  53. package/templates/init.include.yaml +41 -36
  54. package/templates/package.json +3 -1
  55. package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/installer.ts +4 -0
  56. package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/smoke.ts +21 -2
  57. package/templates/{api/src/_platform → src/_platform/api}/fixtures/l1-test-user.sql +4 -5
  58. package/templates/{api/src/_platform → src/_platform/api}/market/api.ts +25 -15
  59. package/templates/src/_platform/api/seed.mysql.sql +16 -0
  60. package/templates/src/_platform/api/seed.pg.sql +16 -0
  61. package/templates/{api/src/_platform → src/_platform/api}/seed.sql +16 -5
  62. package/templates/{api/src/_platform → src/_platform/api}/solutions/api.ts +7 -3
  63. package/templates/{api/src/auth → src/auth/api}/_shared/login-throttle.ts +2 -2
  64. package/templates/{api/src/auth → src/auth/api}/_shared/session.ts +2 -2
  65. package/templates/{api/src/auth → src/auth/api}/api.ts +1 -1
  66. package/templates/{api/src/auth → src/auth/api}/ldap-login/api.ts +1 -1
  67. package/templates/src/auth/api/manifest.yaml +6 -0
  68. package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/InstallDialog.test.tsx +53 -1
  69. package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/marketClient.fake.ts +6 -0
  70. package/templates/{web/src/oj-market → src/oj-market/web}/api/client.ts +5 -0
  71. package/templates/{web/src/oj-market → src/oj-market/web}/api/context.ts +2 -2
  72. package/templates/{web/src/oj-market → src/oj-market/web}/api/defaultClient.ts +7 -1
  73. package/templates/src/oj-market/web/components/InstallDialog.tsx +194 -0
  74. package/templates/{web/src/oj-market → src/oj-market/web}/components/RollbackDialog.tsx +7 -3
  75. package/templates/{web/src/oj-market → src/oj-market/web}/components/UninstallConfirm.tsx +18 -7
  76. package/templates/{web/src/oj-market → src/oj-market/web}/hooks/useMarket.ts +29 -12
  77. package/templates/{web/src/oj-market → src/oj-market/web}/locales/en-US.json +10 -1
  78. package/templates/{web/src/oj-market → src/oj-market/web}/locales/zh-CN.json +10 -1
  79. package/templates/{web/src/oj-market → src/oj-market/web}/types.ts +15 -2
  80. package/templates/{api/src/system → src/system/api}/login-methods/api.ts +2 -2
  81. package/templates/src/system/api/manifest.yaml +6 -0
  82. package/templates/{api/src/system → src/system/api}/oplog/api.ts +4 -4
  83. package/templates/{api/src/system → src/system/api}/permissions/api.ts +3 -3
  84. package/templates/{api/src/system → src/system/api}/roles/api.ts +3 -3
  85. package/templates/{api/src/system → src/system/api}/roles/bindings/api.ts +3 -3
  86. package/templates/{api/src/system → src/system/api}/settings/api.ts +3 -3
  87. package/templates/{api/src/system → src/system/api}/users/api.ts +3 -3
  88. package/templates/{api/src/system → src/system/api}/users/forbidden/api.ts +3 -3
  89. package/templates/{api/src/system → src/system/api}/users/password/api.ts +3 -3
  90. package/templates/{api/src/system → src/system/api}/users/roles/api.ts +3 -3
  91. package/templates/{web/src/system → src/system/web}/locales/en-US.json +1 -1
  92. package/templates/{web/src/system → src/system/web}/locales/zh-CN.json +1 -1
  93. package/templates/src/tsconfig.json +16 -0
  94. package/templates/{api/tests → tests}/auth-ldap.spec.ts +2 -2
  95. package/templates/{api/tests → tests}/auth-session.spec.ts +3 -3
  96. package/templates/{api/tests → tests}/auth-throttle.spec.ts +1 -1
  97. package/templates/tests/fixture.ts +434 -0
  98. package/templates/{api/tests → tests}/guard.spec.ts +1 -1
  99. package/templates/{api/tests → tests}/l1-builder.test.ts +2 -2
  100. package/templates/{api/tests → tests}/lifecycle/atomic-state.spec.ts +3 -3
  101. package/templates/{api/tests → tests}/lifecycle/bootstrap.spec.ts +8 -4
  102. package/templates/{api/tests → tests}/lifecycle/deploy-smoke.spec.ts +31 -8
  103. package/templates/{api/tests → tests}/lifecycle/installer.spec.ts +15 -15
  104. package/templates/{api/tests → tests}/lifecycle/market-api.spec.ts +30 -29
  105. package/templates/{api/tests → tests}/lifecycle/migrate.spec.ts +6 -6
  106. package/templates/{api/tests → tests}/lifecycle/preflight.spec.ts +4 -4
  107. package/templates/{api/tests → tests}/lifecycle/register.spec.ts +1 -1
  108. package/templates/{api/tests → tests}/lifecycle/semver.spec.ts +1 -1
  109. package/templates/{api/tests → tests}/lifecycle/sha384.spec.ts +1 -1
  110. package/templates/{api/tests → tests}/lifecycle/sig-fixtures.spec.ts +1 -1
  111. package/templates/{api/tests → tests}/lifecycle/solutions-api.spec.ts +1 -1
  112. package/templates/{api/tests → tests}/lifecycle/steps-fs.spec.ts +12 -12
  113. package/templates/{api/tests → tests}/lifecycle/text-edit.spec.ts +1 -1
  114. package/templates/{api/tests → tests}/lifecycle/tgz-guard.spec.ts +1 -1
  115. package/templates/{api/tests → tests}/lifecycle/trust.spec.ts +5 -5
  116. package/templates/{api/tests → tests}/lifecycle/uninstall.spec.ts +7 -7
  117. package/templates/{api/tests → tests}/lifecycle/verify-checks.spec.ts +9 -6
  118. package/templates/{api/tests → tests}/lifecycle/verify-report.spec.ts +7 -5
  119. package/templates/{api/tests → tests}/query-utils.spec.ts +1 -1
  120. package/templates/{api/tests → tests}/rbac-audit.spec.ts +1 -1
  121. package/templates/{api/tests → tests}/rbac-contacts.spec.ts +2 -2
  122. package/templates/{api/tests → tests}/rbac-guard.spec.ts +1 -1
  123. package/templates/{api/tests → tests}/rbac-roles.spec.ts +2 -2
  124. package/templates/{api/tests → tests}/rbac-seed.spec.ts +13 -11
  125. package/templates/{api/tests → tests}/rbac-settings.spec.ts +2 -2
  126. package/templates/{api/tests → tests}/rbac-solution-perms.spec.ts +1 -1
  127. package/templates/{api/tests → tests}/rbac-users.spec.ts +3 -3
  128. package/templates/tests/seed-role-accounts.spec.ts +87 -0
  129. package/templates/tsconfig.json +5 -1
  130. package/templates/tsconfig.paths.json +19 -0
  131. package/templates/types/oj-modules.d.ts +17 -0
  132. package/templates/vitest.config.ts +22 -0
  133. package/templates/web.config.ts +23 -13
  134. package/vendor/host-versions.json +3 -3
  135. package/templates/api/src/_platform/seed.mysql.sql +0 -8
  136. package/templates/api/src/_platform/seed.pg.sql +0 -8
  137. package/templates/api/src/auth/manifest.yaml +0 -5
  138. package/templates/api/src/system/manifest.yaml +0 -5
  139. package/templates/api/tests/fixture.ts +0 -58
  140. package/templates/api/tsconfig.json +0 -8
  141. package/templates/api/vitest.config.ts +0 -12
  142. package/templates/logs/task-events.jsonl +0 -0
  143. package/templates/web/src/oj-market/components/InstallDialog.tsx +0 -112
  144. /package/templates/{api/.ojm-api-exempt.json → .ojm-api-exempt.json} +0 -0
  145. /package/templates/{api/seed.mysql.sql → seed.mysql.sql} +0 -0
  146. /package/templates/{api/seed.pg.sql → seed.pg.sql} +0 -0
  147. /package/templates/{api/src/_platform → src/_platform/api}/README.md +0 -0
  148. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/atomic.ts +0 -0
  149. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/history.ts +0 -0
  150. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/lock.ts +0 -0
  151. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/semver.ts +0 -0
  152. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/sha384.ts +0 -0
  153. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/state.ts +0 -0
  154. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/deploy-web.ts +0 -0
  155. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/download.ts +0 -0
  156. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/extract.ts +0 -0
  157. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/migrate.ts +0 -0
  158. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/preflight.ts +0 -0
  159. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/register.ts +0 -0
  160. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/reload.ts +0 -0
  161. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/steps/verify.ts +0 -0
  162. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tasks.ts +0 -0
  163. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/text-edit.ts +0 -0
  164. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/tgz-guard.ts +0 -0
  165. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/trust.ts +0 -0
  166. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/types.ts +0 -0
  167. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/uninstall.ts +0 -0
  168. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-checks.ts +0 -0
  169. /package/templates/{api/src/_platform → src/_platform/api}/_lifecycle/verify-report.ts +0 -0
  170. /package/templates/{api/src/_platform → src/_platform/api}/_shared/respond.ts +0 -0
  171. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/contacts.ts +0 -0
  172. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/roles.ts +0 -0
  173. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/settings.ts +0 -0
  174. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/solution-perms.ts +0 -0
  175. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/domain/users.ts +0 -0
  176. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/access.ts +0 -0
  177. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/audit.ts +0 -0
  178. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/guard.ts +0 -0
  179. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/oplog.ts +0 -0
  180. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/query.ts +0 -0
  181. /package/templates/{api/src/_platform → src/_platform/api}/_shared/service/infra/rate.ts +0 -0
  182. /package/templates/{api/src/_platform → src/_platform/api}/_shared/site-facade.ts +0 -0
  183. /package/templates/{api/src/_platform → src/_platform/api}/contacts/api.ts +0 -0
  184. /package/templates/{api/src/_platform → src/_platform/api}/contacts/detail/api.ts +0 -0
  185. /package/templates/{api/src/_platform → src/_platform/api}/manifest.yaml +0 -0
  186. /package/templates/{api/src/_platform → src/_platform/api}/market/bootstrap.ts +0 -0
  187. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.mysql.sql +0 -0
  188. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.pg.sql +0 -0
  189. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0001__create_users.sql +0 -0
  190. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
  191. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0002__add_users_avatar_base64.sql +0 -0
  192. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.mysql.sql +0 -0
  193. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0003__users_source_display_name.sql +0 -0
  194. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.mysql.sql +0 -0
  195. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.pg.sql +0 -0
  196. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0004__sys_rbac.sql +0 -0
  197. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.mysql.sql +0 -0
  198. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0005__users_must_change.sql +0 -0
  199. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
  200. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0006__users_is_forbidden.sql +0 -0
  201. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0007__users_email.sql +0 -0
  202. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
  203. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.pg.sql +0 -0
  204. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0008__sys_refresh_token.sql +0 -0
  205. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.mysql.sql +0 -0
  206. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.pg.sql +0 -0
  207. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0009__sys_throttle.sql +0 -0
  208. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.mysql.sql +0 -0
  209. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.pg.sql +0 -0
  210. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0010__sys_solution_route.sql +0 -0
  211. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
  212. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0011__sys_setting_group_label.sql +0 -0
  213. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.mysql.sql +0 -0
  214. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.pg.sql +0 -0
  215. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0012__market_history.sql +0 -0
  216. /package/templates/{api/src/_platform → src/_platform/api}/migrations/0013__users_profile.sql +0 -0
  217. /package/templates/{api/src/_platform → src/_platform/api}/schema.yaml +0 -0
  218. /package/templates/{web/src/_platform → src/_platform/web}/__client/api.schemas.ts +0 -0
  219. /package/templates/{web/src/_platform → src/_platform/web}/__client/api.ts +0 -0
  220. /package/templates/{web/src/about → src/about/web}/entry.ts +0 -0
  221. /package/templates/{web/src/about → src/about/web}/locales/en-US.json +0 -0
  222. /package/templates/{web/src/about → src/about/web}/locales/zh-CN.json +0 -0
  223. /package/templates/{web/src/about → src/about/web}/pages/constants.ts +0 -0
  224. /package/templates/{web/src/about → src/about/web}/pages/index.tsx +0 -0
  225. /package/templates/{web/src/access → src/access/web}/entry.ts +0 -0
  226. /package/templates/{web/src/access → src/access/web}/locales/en-US.json +0 -0
  227. /package/templates/{web/src/access → src/access/web}/locales/zh-CN.json +0 -0
  228. /package/templates/{web/src/access → src/access/web}/pages/access-mode/index.tsx +0 -0
  229. /package/templates/{web/src/access → src/access/web}/pages/admin-visible/index.tsx +0 -0
  230. /package/templates/{web/src/access → src/access/web}/pages/button-control/index.tsx +0 -0
  231. /package/templates/{web/src/access → src/access/web}/pages/common-visible/index.tsx +0 -0
  232. /package/templates/{web/src/access → src/access/web}/pages/page-control/index.tsx +0 -0
  233. /package/templates/{api/src/auth → src/auth/api}/_shared/ldap-login.ts +0 -0
  234. /package/templates/{api/src/auth → src/auth/api}/_shared/ldap.ts +0 -0
  235. /package/templates/{web/src/auth → src/auth/web}/__client/api.schemas.ts +0 -0
  236. /package/templates/{web/src/auth → src/auth/web}/__client/api.ts +0 -0
  237. /package/templates/{api/src/demo → src/demo/api}/manifest.yaml +0 -0
  238. /package/templates/{api/src/demo → src/demo/api}/migrations/0001__create_todos.sql +0 -0
  239. /package/templates/{api/src/demo → src/demo/api}/schema.yaml +0 -0
  240. /package/templates/{api/src/demo → src/demo/api}/seed.sql +0 -0
  241. /package/templates/{api/src/demo → src/demo/api}/todos/api.ts +0 -0
  242. /package/templates/{web/src/demo → src/demo/web}/__client/api.schemas.ts +0 -0
  243. /package/templates/{web/src/demo → src/demo/web}/__client/api.ts +0 -0
  244. /package/templates/{web/src/demo → src/demo/web}/entry.ts +0 -0
  245. /package/templates/{web/src/demo → src/demo/web}/locales/en-US.json +0 -0
  246. /package/templates/{web/src/demo → src/demo/web}/locales/zh-CN.json +0 -0
  247. /package/templates/{web/src/demo → src/demo/web}/pages/index.tsx +0 -0
  248. /package/templates/{web/src/exception → src/exception/web}/entry.ts +0 -0
  249. /package/templates/{web/src/exception → src/exception/web}/locales/en-US.json +0 -0
  250. /package/templates/{web/src/exception → src/exception/web}/locales/zh-CN.json +0 -0
  251. /package/templates/{web/src/exception → src/exception/web}/pages/403/index.tsx +0 -0
  252. /package/templates/{web/src/exception → src/exception/web}/pages/404/index.tsx +0 -0
  253. /package/templates/{web/src/exception → src/exception/web}/pages/500/index.tsx +0 -0
  254. /package/templates/{web/src/exception → src/exception/web}/pages/unknown-component/index.tsx +0 -0
  255. /package/templates/{api/src/home → src/home/api}/line/api.ts +0 -0
  256. /package/templates/{api/src/home → src/home/api}/manifest.yaml +0 -0
  257. /package/templates/{api/src/home → src/home/api}/migrations/0001__create_home_tables.sql +0 -0
  258. /package/templates/{api/src/home → src/home/api}/pie/api.ts +0 -0
  259. /package/templates/{api/src/home → src/home/api}/schema.yaml +0 -0
  260. /package/templates/{api/src/home → src/home/api}/seed.sql +0 -0
  261. /package/templates/{web/src/home → src/home/web}/__client/api.schemas.ts +0 -0
  262. /package/templates/{web/src/home → src/home/web}/__client/api.ts +0 -0
  263. /package/templates/{web/src/home → src/home/web}/entry.ts +0 -0
  264. /package/templates/{web/src/home → src/home/web}/locales/en-US.json +0 -0
  265. /package/templates/{web/src/home → src/home/web}/locales/zh-CN.json +0 -0
  266. /package/templates/{web/src/home → src/home/web}/pages/components/bar-chart.tsx +0 -0
  267. /package/templates/{web/src/home → src/home/web}/pages/components/card-list.tsx +0 -0
  268. /package/templates/{web/src/home → src/home/web}/pages/components/line-chart.tsx +0 -0
  269. /package/templates/{web/src/home → src/home/web}/pages/components/pie-chart.tsx +0 -0
  270. /package/templates/{web/src/home → src/home/web}/pages/index.tsx +0 -0
  271. /package/templates/{web/src/login → src/login/web}/entry.ts +0 -0
  272. /package/templates/{web/src/login → src/login/web}/locales/en-US.json +0 -0
  273. /package/templates/{web/src/login → src/login/web}/locales/zh-CN.json +0 -0
  274. /package/templates/{web/src/login → src/login/web}/pages/change-password.tsx +0 -0
  275. /package/templates/{web/src/login → src/login/web}/pages/login.tsx +0 -0
  276. /package/templates/{api/src/notification → src/notification/api}/_shared/notify.ts +0 -0
  277. /package/templates/{api/src/notification → src/notification/api}/api.ts +0 -0
  278. /package/templates/{api/src/notification → src/notification/api}/del/api.ts +0 -0
  279. /package/templates/{api/src/notification → src/notification/api}/manifest.yaml +0 -0
  280. /package/templates/{api/src/notification → src/notification/api}/migrations/0001__create_notifications.sql +0 -0
  281. /package/templates/{api/src/notification → src/notification/api}/migrations/0002__notifications_user.sql +0 -0
  282. /package/templates/{api/src/notification → src/notification/api}/migrations/0003__notifications_broadcast.sql +0 -0
  283. /package/templates/{api/src/notification → src/notification/api}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
  284. /package/templates/{api/src/notification → src/notification/api}/schema.yaml +0 -0
  285. /package/templates/{api/src/notification → src/notification/api}/seed.sql +0 -0
  286. /package/templates/{web/src/notification → src/notification/web}/__client/api.schemas.ts +0 -0
  287. /package/templates/{web/src/notification → src/notification/web}/__client/api.ts +0 -0
  288. /package/templates/{web/src/notification → src/notification/web}/entry.ts +0 -0
  289. /package/templates/{web/src/notification → src/notification/web}/locales/en-US.json +0 -0
  290. /package/templates/{web/src/notification → src/notification/web}/locales/zh-CN.json +0 -0
  291. /package/templates/{web/src/notification → src/notification/web}/pages/center/index.tsx +0 -0
  292. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/CardGrid.test.tsx +0 -0
  293. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/HistoryDrawer.test.tsx +0 -0
  294. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/RollbackDialog.test.tsx +0 -0
  295. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/UninstallConfirm.test.tsx +0 -0
  296. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/poll.test.ts +0 -0
  297. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/render.tsx +0 -0
  298. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/runtime-stub.ts +0 -0
  299. /package/templates/{web/src/oj-market → src/oj-market/web}/__tests__/semver.test.ts +0 -0
  300. /package/templates/{web/src/oj-market → src/oj-market/web}/api/MarketClientProvider.tsx +0 -0
  301. /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardGrid.tsx +0 -0
  302. /package/templates/{web/src/oj-market → src/oj-market/web}/components/CardItem.tsx +0 -0
  303. /package/templates/{web/src/oj-market → src/oj-market/web}/components/DetailDrawer.tsx +0 -0
  304. /package/templates/{web/src/oj-market → src/oj-market/web}/components/HistoryDrawer.tsx +0 -0
  305. /package/templates/{web/src/oj-market → src/oj-market/web}/entry.ts +0 -0
  306. /package/templates/{web/src/oj-market → src/oj-market/web}/pages/MarketApp.tsx +0 -0
  307. /package/templates/{web/src/oj-market → src/oj-market/web}/utils/poll.ts +0 -0
  308. /package/templates/{web/src/oj-market → src/oj-market/web}/utils/semver.ts +0 -0
  309. /package/templates/{web/src/outside → src/outside/web}/entry.ts +0 -0
  310. /package/templates/{web/src/outside → src/outside/web}/locales/en-US.json +0 -0
  311. /package/templates/{web/src/outside → src/outside/web}/locales/zh-CN.json +0 -0
  312. /package/templates/{api/src/personal-center → src/personal-center/api}/manifest.yaml +0 -0
  313. /package/templates/{api/src/personal-center → src/personal-center/api}/profile/api.ts +0 -0
  314. /package/templates/{api/src/personal-center → src/personal-center/api}/upload/api.ts +0 -0
  315. /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.schemas.ts +0 -0
  316. /package/templates/{web/src/personal-center → src/personal-center/web}/__client/api.ts +0 -0
  317. /package/templates/{web/src/personal-center → src/personal-center/web}/entry.ts +0 -0
  318. /package/templates/{web/src/personal-center → src/personal-center/web}/locales/en-US.json +0 -0
  319. /package/templates/{web/src/personal-center → src/personal-center/web}/locales/zh-CN.json +0 -0
  320. /package/templates/{web/src/personal-center → src/personal-center/web}/pages/my-profile/index.tsx +0 -0
  321. /package/templates/{web/src/route-nest → src/route-nest/web}/entry.ts +0 -0
  322. /package/templates/{web/src/route-nest → src/route-nest/web}/locales/en-US.json +0 -0
  323. /package/templates/{web/src/route-nest → src/route-nest/web}/locales/zh-CN.json +0 -0
  324. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-1/index.tsx +0 -0
  325. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu1/menu1-2/index.tsx +0 -0
  326. /package/templates/{web/src/route-nest → src/route-nest/web}/pages/menu2/index.tsx +0 -0
  327. /package/templates/{web/src → src}/styles.css +0 -0
  328. /package/templates/{web/src/system → src/system/web}/__client/api.schemas.ts +0 -0
  329. /package/templates/{web/src/system → src/system/web}/__client/api.ts +0 -0
  330. /package/templates/{web/src/system → src/system/web}/entry.ts +0 -0
  331. /package/templates/{web/src/system → src/system/web}/pages/errors.ts +0 -0
  332. /package/templates/{web/src/system → src/system/web}/pages/forbidden.tsx +0 -0
  333. /package/templates/{web/src/system → src/system/web}/pages/login-methods/index.tsx +0 -0
  334. /package/templates/{web/src/system → src/system/web}/pages/oplog/index.tsx +0 -0
  335. /package/templates/{web/src/system → src/system/web}/pages/roles/index.tsx +0 -0
  336. /package/templates/{web/src/system → src/system/web}/pages/settings/index.tsx +0 -0
  337. /package/templates/{web/src/system → src/system/web}/pages/users/index.tsx +0 -0
  338. /package/templates/{web/src/system → src/system/web}/pages/users/roles.tsx +0 -0
  339. /package/templates/{api/src/web → src/web/api}/get-async-routes/api.ts +0 -0
  340. /package/templates/{api/src/web → src/web/api}/hello/api.ts +0 -0
  341. /package/templates/{api/src/web → src/web/api}/manifest.yaml +0 -0
  342. /package/templates/{api/src/web → src/web/api}/user-info/api.ts +0 -0
  343. /package/templates/{web/src → src/web}/web/__client/api.schemas.ts +0 -0
  344. /package/templates/{web/src → src/web}/web/__client/api.ts +0 -0
  345. /package/templates/{api/tests → tests}/rbac-tables.spec.ts +0 -0
package/src/index.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
- import { parseApiArgs, parseBuildArgs, parseInitArgs, parseLifecycleArgs, parseVendorArgs } from "./args";
4
+ import { parseApiArgs, parseBuildArgs, parseInitArgs, parseLifecycleArgs, parseNewArgs, parseVendorArgs } from "./args";
5
5
  import { buildBackend, buildModules } from "./build";
6
6
  import { buildBundle } from "./bundle";
7
7
  import { devServer } from "./dev";
8
8
  import { mergeManifests, printInfo } from "./info";
9
9
  import { initProject } from "./init";
10
+ import { createSolution } from "./new";
10
11
  import { runPreview } from "./preview";
11
12
  import { getCommandHelp, resolveHelpRequest, usageText } from "./usage";
12
13
  import { vendorCommand } from "./vendor";
@@ -50,10 +51,13 @@ async function main() {
50
51
 
51
52
  switch (command) {
52
53
  case "build": {
53
- const { bundle, out } = parseBuildArgs(process.argv.slice(3));
54
+ const { bundle, bundleName, out } = parseBuildArgs(process.argv.slice(3));
54
55
  if (bundle) {
55
56
  // 方案包构建(P1-11):契约校验 fail-fast → 确定性 tgz + sha256(消费方:市场安装器)
56
- await buildBundle(projectRoot, { out });
57
+ // 多方案:无 bundleName 打全部用户方案(自带排除),有则只打 name 那一份
58
+ const results = await buildBundle(projectRoot, { out, name: bundleName });
59
+ if (results.length > 1)
60
+ console.log(`[ojm] 共打包 ${results.length} 份方案包(逐包签名:ojm sign dist/<name>-<version>.tgz --id <publisher>.<key> --check)`);
57
61
  break;
58
62
  }
59
63
  // 设计 §5:后端 oj build(零 DB 副作用)→ 前端全站合并(仅 build 清场合并)
@@ -61,6 +65,19 @@ async function main() {
61
65
  await buildModules(projectRoot, { mergeSite: true });
62
66
  break;
63
67
  }
68
+ case "new": {
69
+ const { name, layout, api } = parseNewArgs(process.argv.slice(3));
70
+ if (!name) {
71
+ console.error("[ojm] 用法:ojm new <name> [--layout container|none] [--api](非交互环境必须显式给 name)");
72
+ process.exit(1);
73
+ }
74
+ const created = createSolution(projectRoot, name, { layout, api });
75
+ console.log(`[ojm] 已生成方案骨架:${path.relative(process.cwd(), created.dir)}`);
76
+ for (const f of created.files)
77
+ console.log(`[ojm] + ${f}`);
78
+ console.log(`[ojm] 下一步:pnpm dev 看效果 → ojm build && ojm build --bundle ${name} 打包`);
79
+ break;
80
+ }
64
81
  case "init": {
65
82
  const { dest, yes } = parseInitArgs(process.argv.slice(3));
66
83
  await initProject(dest ? path.resolve(dest) : projectRoot, { yes });
@@ -79,7 +96,7 @@ async function main() {
79
96
  break;
80
97
  }
81
98
  case "info":
82
- await printInfo(projectRoot);
99
+ await printInfo(projectRoot, undefined, { all: process.argv.slice(3).includes("--all") });
83
100
  break;
84
101
  case "vendor": {
85
102
  const { force, tag } = parseVendorArgs(process.argv.slice(3));
@@ -100,7 +117,7 @@ async function main() {
100
117
  }
101
118
  if (check) {
102
119
  const { checkApi } = await import("./contract/check");
103
- // --exempt <path>:相对路径按项目根解析;缺省回退 api/.ojm-api-exempt.json
120
+ // --exempt <path>:相对路径按项目根解析;缺省按布局找 .ojm-api-exempt.json(工程根 → api/)
104
121
  // (旧名 api/.ram-api-exempt.json 仍被读取,R4)
105
122
  const { violations, hints } = await checkApi({ cwd: apiRoot, exempt: exempt ? path.resolve(projectRoot, exempt) : undefined });
106
123
  for (const v of violations)
@@ -197,7 +214,10 @@ async function main() {
197
214
 
198
215
  main().catch((error: unknown) => {
199
216
  // Error 的 message 已是人话(含修复指引);堆栈默认是噪音,OJM_DEBUG=1 才输出
200
- // (旧名 RAM_DEBUG 一并读,兼容既有排障习惯)
201
- console.error(`[ojm] ${error instanceof Error ? ((process.env.OJM_DEBUG ?? process.env.RAM_DEBUG) ? error.stack : error.message) : String(error)}`);
217
+ // (旧名 RAM_DEBUG 一并读,兼容既有排障习惯)。
218
+ // 文案常自带 [ojm] 前缀(bundle/publish/sign 等抛出的错误),顶层不再叠一个,否则变 [ojm] [ojm]。
219
+ const isDebug = !!(process.env.OJM_DEBUG ?? process.env.RAM_DEBUG);
220
+ const body = error instanceof Error ? (isDebug ? error.stack ?? error.message : error.message) : String(error);
221
+ console.error(/^\[ojm/.test(body) ? body : `[ojm] ${body}`);
202
222
  process.exit(1);
203
223
  });
package/src/info.ts CHANGED
@@ -12,9 +12,11 @@ import fs from "node:fs";
12
12
 
13
13
  import http from "node:http";
14
14
  import path from "node:path";
15
- import { loadModulesConfig } from "./config";
16
- import { resolveConfigPath } from "./layout";
15
+ import { readBuiltinModules } from "./builtin";
16
+ import { loadModulesConfig, loadSiteConfig, mergeModuleList } from "./config";
17
+ import { resolveConfigPath, scanModules, tryResolveLayout } from "./layout";
17
18
  import { mergeModuleManifests } from "./manifest";
19
+ import { probeOjCaps } from "./oj-caps";
18
20
  import { readOjApiPrefix, readOjServerField } from "./oj-config";
19
21
  import { isSupportedOjVersion, MIN_OJ_VERSION, readLocalVersion, resolveOjBin } from "./vendor";
20
22
  import { readHostVersions, resolveShellDist } from "./versions";
@@ -90,7 +92,50 @@ export function realOjObservability(projectRoot: string): OjObservability {
90
92
  };
91
93
  }
92
94
 
93
- export async function printInfo(projectRoot: string, oj: OjObservability = realOjObservability(projectRoot)): Promise<void> {
95
+ export interface PrintInfoOptions {
96
+ /** 展开框架自带模块(默认折叠为一行计数) */
97
+ all?: boolean
98
+ }
99
+
100
+ /**
101
+ * 模块清单条目解析:module-root 用「扫描 + 登记覆盖」并集(与 build 同源,
102
+ * 未登记的用户模块也可见——旧实现只读 web.config.ts 会漏掉它们);历史布局
103
+ * 维持登记清单(loadModulesConfig)。
104
+ */
105
+ async function resolveModuleEntries(projectRoot: string): Promise<{ name: string, entry: string, enabled: boolean }[]> {
106
+ const layout = tryResolveLayout(projectRoot);
107
+ if (layout?.kind === "module-root") {
108
+ const config = await loadSiteConfig(projectRoot);
109
+ return mergeModuleList(scanModules(layout), config.modules, projectRoot);
110
+ }
111
+ const config = await loadModulesConfig(projectRoot);
112
+ return config.modules.map(m => ({ name: m.name, entry: m.entry, enabled: m.enabled !== false }));
113
+ }
114
+
115
+ /** 清单渲染:自带模块默认折叠(--all 展开);坏名单文件告警降级为平铺,info 不失败 */
116
+ function renderModuleLines(entries: { name: string, entry: string, enabled: boolean }[], projectRoot: string, all: boolean): string {
117
+ let builtin = new Set<string>();
118
+ try {
119
+ builtin = readBuiltinModules(projectRoot);
120
+ }
121
+ catch (error) {
122
+ console.warn(`[ojm] ⚠️ ${error instanceof Error ? error.message : String(error)}——本次按无内置名单渲染。`);
123
+ }
124
+ const fmt = (m: { name: string, entry: string, enabled: boolean }) => ` · ${m.name}(${m.entry})${m.enabled ? "" : " [disabled]"}`;
125
+ const own = entries.filter(m => builtin.has(m.name));
126
+ const user = entries.filter(m => !builtin.has(m.name));
127
+ const lines = user.map(fmt);
128
+ if (own.length > 0) {
129
+ if (all)
130
+ lines.push(...own.map(fmt));
131
+ else
132
+ lines.push(` 框架自带:${own.length} 个(ojm info --all 展开)`);
133
+ }
134
+ // 零模块时留占位行,避免清单段整段空白像渲染故障
135
+ return lines.length > 0 ? lines.join("\n") : "(无 web 模块)";
136
+ }
137
+
138
+ export async function printInfo(projectRoot: string, oj: OjObservability = realOjObservability(projectRoot), opts: PrintInfoOptions = {}): Promise<void> {
94
139
  const cliVersion = readPkgVersion(path.join(projectRoot, "node_modules/@oj-module/cli/package.json"));
95
140
  const runtimeVersion = readPkgVersion(path.join(projectRoot, "node_modules/@oj-module/runtime/package.json"));
96
141
 
@@ -106,10 +151,8 @@ export async function printInfo(projectRoot: string, oj: OjObservability = realO
106
151
 
107
152
  let moduleLines = "(web.config.ts 加载失败或无模块)";
108
153
  try {
109
- const config = await loadModulesConfig(projectRoot);
110
- moduleLines = config.modules
111
- .map(m => ` · ${m.name}(${m.entry})${m.enabled === false ? " [disabled]" : ""}`)
112
- .join("\n");
154
+ const entries = await resolveModuleEntries(projectRoot);
155
+ moduleLines = renderModuleLines(entries, projectRoot, opts.all ?? false);
113
156
  }
114
157
  catch (error) {
115
158
  moduleLines = `(加载失败:${(error as Error).message})`;
@@ -150,21 +193,37 @@ export async function printInfo(projectRoot: string, oj: OjObservability = realO
150
193
  : "证书状态: 无法探测(服务未运行时属正常;ojm dev/preview 起服后可测)";
151
194
 
152
195
  const markerShort = markerVersion?.replace(/^v/, "");
153
- const drift = markerShort && siteVersion.includes(markerShort)
196
+ // 版本门禁以**现场版本**为准,标记只用来报漂移:`bin/oj` 允许手替(vendor.ts 逃生门),
197
+ // 只看标记会把「标记新、二进制旧」判成健康(设计 §7「双探」第 1 条)。
198
+ const liveShort = /(\d+\.\d+\.\d+)/.exec(siteVersion)?.[1];
199
+ const drift = markerShort && (liveShort ?? siteVersion).includes(markerShort)
154
200
  ? ""
155
201
  : markerShort
156
202
  ? `\n [!] 现场版本与安装标记(${markerVersion})不一致,请核对(ojm vendor --force 重装)`
157
203
  : "";
204
+ const gateShort = liveShort ?? markerShort;
158
205
  // 最低版本门禁:ojm 只与 >= MIN_OJ_VERSION 的 oj 组合可用(子命令 serve 等契约)
159
- const outdated = markerShort && !isSupportedOjVersion(markerShort)
206
+ const outdated = gateShort && !isSupportedOjVersion(gateShort)
160
207
  ? `\n [!] 低于 ojm 要求的最低版本 v${MIN_OJ_VERSION}(oj v0.1.32 起启动子命令 server → serve)——请升级:ojm vendor v${MIN_OJ_VERSION}`
161
208
  : "";
209
+ // 能力探针(§7 第 2 条):版本号是声明,能跑通才是事实
210
+ const capsLine = fs.existsSync(ojBin)
211
+ ? (() => {
212
+ try {
213
+ return `--module-root ${probeOjCaps(ojBin).moduleRoot ? "可用" : `不可用(src/<name>/{api,web} 布局需 oj >= v0.1.85;ojm vendor v${MIN_OJ_VERSION})`}`;
214
+ }
215
+ catch (error) {
216
+ return `探测失败(${(error as Error).message})`;
217
+ }
218
+ })()
219
+ : "未知(bin/oj 不存在,ojm vendor 安装)";
162
220
  backendBlock = `
163
221
  后端(oj):
164
222
  安装标记: ${markerVersion ?? "未知(缺 bin/.oj-version,ojm vendor 重装)"}
165
223
  现场版本: ${siteVersion}${drift}${outdated}
166
224
  版本要求: >= v${MIN_OJ_VERSION}(ojm 以最新 oj 为准,不做兼容分支)
167
- 端口/base: ${port} / ${base}(api/config.yaml)
225
+ 能力探针: ${capsLine}
226
+ 端口/base: ${port} / ${base}(config.yaml)
168
227
  ${certLine}
169
228
  `;
170
229
  }
package/src/init.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * `ojm init` —— 前后端一体化工程脚手架(设计 §3,D1/D3/D6/D10)。
3
3
  *
4
- * 产物:api/(oj 后端 + seed)、config/(config.yaml + 现场签发证书,与 api/、web/ 同级)、
5
- * web/src/(前端模块)、bin/(从 everpan/only-js 最新 release 联网下载:oj + plugins + devkit)、
6
- * 工程配置文件。
4
+ * 产物(布局 `src/<name>/{api,web}`):src/(模块源码单树)、config.yaml + config/(现场签发证书)、
5
+ * seed.*.sql / tests / vitest.config.ts(后端工程面,与 config.yaml 同级)、
6
+ * bin/(从 everpan/only-js 最新 release 联网下载:oj + plugins + devkit)、工程配置文件。
7
7
  *
8
- * 幂等补缺:对既有工程重跑只补缺失文件(bin/oj 已在则跳过下载),config.yaml 与
9
- * 用户文件永不覆盖——「重新 init 覆盖脚手架」是审阅记录二明令禁止的行为。
8
+ * 幂等补缺:对既有工程重跑只补缺失文件(bin/oj 已在则跳过下载,但先过 §7 能力闸门),
9
+ * config.yaml 与用户文件永不覆盖——「重新 init 覆盖脚手架」是审阅记录二明令禁止的行为。
10
10
  */
11
11
 
12
12
  import { execFileSync } from "node:child_process";
@@ -16,9 +16,12 @@ import path from "node:path";
16
16
  import process from "node:process";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import yaml from "yaml";
19
+ import { ALIAS_PATHS_FILE, syncAliasPaths } from "./alias-paths";
20
+ import { BUILTIN_LIST_FILE, writeBuiltinList } from "./builtin";
19
21
  import { resolveCertDir, resolveConfigPath } from "./layout";
22
+ import { probeOjCaps } from "./oj-caps";
20
23
  import { mintDevCert } from "./oj-cert";
21
- import { resolveOjBin, vendorCommand } from "./vendor";
24
+ import { isSupportedOjVersion, MIN_OJ_VERSION, resolveOjBin, vendorCommand } from "./vendor";
22
25
  import { readHostVersions } from "./versions";
23
26
 
24
27
  export interface InitOptions {
@@ -33,6 +36,7 @@ export interface InitOptions {
33
36
  interface InitReport {
34
37
  created: string[]
35
38
  skipped: string[]
39
+ warnings: string[]
36
40
  }
37
41
 
38
42
  /**
@@ -41,6 +45,59 @@ interface InitReport {
41
45
  */
42
46
  export type Sealer = (destDir: string, plainSecret: string) => Promise<string | null>;
43
47
 
48
+ /**
49
+ * 既有 `bin/oj` 的能力预检(设计 §7「双探」;评审 BLOCKING #8)。
50
+ *
51
+ * 只查**已存在**的二进制:刚由 `vendorCommand` 装下的那份已被 `MIN_OJ_VERSION` 挡过一遍,
52
+ * 而「从别处拷来的旧二进制」正是这条闸门要拦的形态。
53
+ *
54
+ * 三档处置:
55
+ * - `-V` 读不出版本(桩/损坏二进制)→ 告警放行,不阻断幂等补缺;
56
+ * - 版本 < MIN_OJ_VERSION → 抛错(`ojm vendor --force` 可覆盖标记);
57
+ * - 版本够但 `--module-root` 缺失 → 抛错,这是「声明新、能力旧」的真实形态。
58
+ */
59
+ function assertExistingOjUsable(destDir: string, report: InitReport): void {
60
+ const ojBin = resolveOjBin(destDir);
61
+ if (!fs.existsSync(ojBin))
62
+ return;
63
+
64
+ let printed: string;
65
+ try {
66
+ printed = execFileSync(ojBin, ["-V"], { encoding: "utf-8", timeout: 10_000 }).trim();
67
+ }
68
+ catch (error) {
69
+ report.warnings.push(`无法执行 ${ojBin} -V(${(error as Error).message}),跳过版本校验`);
70
+ return;
71
+ }
72
+
73
+ const version = /(\d+\.\d+\.\d+)/.exec(printed)?.[1];
74
+ if (!version) {
75
+ report.warnings.push(`无法解析 ${ojBin} -V 的输出(${printed || "空"}),跳过版本校验`);
76
+ return;
77
+ }
78
+ if (!isSupportedOjVersion(version)) {
79
+ throw new Error(
80
+ `[ojm] ${ojBin} 是 v${version},低于 ojm 要求的最低版本 v${MIN_OJ_VERSION}。\n`
81
+ + "原因:init 不再对已存在的 bin/oj 静默跳过——旧二进制生成的新布局工程会在 `ojm build` 时才失败。\n"
82
+ + `处置:\`ojm vendor --force v${MIN_OJ_VERSION}\` 重装,或删除 ${ojBin} 后重跑 init。`,
83
+ );
84
+ }
85
+
86
+ try {
87
+ if (probeOjCaps(ojBin).moduleRoot)
88
+ return;
89
+ }
90
+ catch (error) {
91
+ report.warnings.push(`能力探针失败(${(error as Error).message}),跳过 --module-root 校验`);
92
+ return;
93
+ }
94
+ throw new Error(
95
+ `[ojm] ${ojBin}(v${version})不接受 --module-root,无法支撑 src/<name>/{api,web} 布局。\n`
96
+ + "原因:版本号是声明,能跑通才是事实——`oj build --help` 里没有该旗标。\n"
97
+ + `处置:\`ojm vendor --force v${MIN_OJ_VERSION}\` 重装。`,
98
+ );
99
+ }
100
+
44
101
  export async function initProject(destDir: string, opts: InitOptions = {}): Promise<void> {
45
102
  fs.mkdirSync(destDir, { recursive: true });
46
103
  const existing = fs.readdirSync(destDir);
@@ -51,9 +108,13 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
51
108
  );
52
109
  }
53
110
 
54
- const report: InitReport = { created: [], skipped: [] };
111
+ const report: InitReport = { created: [], skipped: [], warnings: [] };
55
112
  const cliRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
56
113
 
114
+ // 0) 已有 bin/oj 先过能力闸门(评审 BLOCKING #8):早前「bin/oj 存在即跳过」
115
+ // 会让从旧工程带来的 0.1.84 静默过关,等 `ojm build` 才死在 --module-root 不认。
116
+ assertExistingOjUsable(destDir, report);
117
+
57
118
  // 1) 模板(占位符替换 + 幂等跳过)
58
119
  const templatesDir = path.join(cliRoot, "templates");
59
120
  const jwtSecret = crypto.randomBytes(32).toString("hex");
@@ -65,14 +126,29 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
65
126
  }, report, include, templatesDir);
66
127
  pruneUnincludedApiPaths(destDir, include, report);
67
128
 
68
- // 2) 证书三件套(现场签发,D6)——落点即 resolveCertDir()(config.yaml 同级的 config/)
129
+ // 1a) src/.ojm-builtin.json:脚手架自带模块身份(名单派生自上面的白名单,单一真源)。
130
+ // copyIfAbsent:旧工程重跑 init 时缺失才补写,已存在不覆盖用户手改。
131
+ const builtinStatus = writeBuiltinList(destDir, include);
132
+ if (builtinStatus === "created")
133
+ report.created.push(`src/${BUILTIN_LIST_FILE}`);
134
+ else if (builtinStatus === "skipped")
135
+ report.skipped.push(`${BUILTIN_LIST_FILE}(已存在,不覆盖用户手改)`);
136
+
137
+ // 1b) tsconfig.paths.json:把 oj 别名 `#/<模块名>/…` 投影成 TS 的 paths。
138
+ // 必须**现场生成**而不是随模板拷贝——模板兼作 demo 工程(多出 demo/home 两个
139
+ // 模块),而用户工程只有白名单里的那些;照拷会让 paths 指向不存在的目录。
140
+ if (syncAliasPaths(destDir) === "created")
141
+ report.created.push(ALIAS_PATHS_FILE);
142
+
143
+ // 2) 证书三件套(现场签发,D6)——落点即 resolveCertDir()(config.yaml 同级的 config/;
144
+ // 新布局 config.yaml 在工程根,故为 <工程根>/config/)
69
145
  const certDir = resolveCertDir(destDir);
70
146
  if (fs.existsSync(path.join(certDir, "cert.jws"))) {
71
- report.skipped.push("api/config/cert.jws");
147
+ report.skipped.push("config/cert.jws");
72
148
  }
73
149
  else {
74
150
  await mintDevCert(certDir);
75
- report.created.push("api/config/{private.pem,public.pem,cert.jws}");
151
+ report.created.push("config/{private.pem,public.pem,cert.jws}");
76
152
  }
77
153
 
78
154
  // 3) oj 二进制:从最新 release 联网下载(sha256 校验 → tar 解包 → chmod),
@@ -164,7 +240,9 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
164
240
  console.log(`[ojm] 新增 ${report.created.length} 项,跳过(已存在)${report.skipped.length} 项`);
165
241
  for (const item of report.skipped)
166
242
  console.log(`[ojm] · 跳过 ${item}`);
167
- console.log("[ojm] 下一步:pnpm install && pnpm dev(登录 admin / 123456)");
243
+ for (const item of report.warnings)
244
+ console.warn(`[ojm] ! ${item}`);
245
+ console.log("[ojm] 下一步:pnpm install && pnpm dev(登录 superadmin / 123456,角色账号见 devkit 手册 §1.1)");
168
246
  }
169
247
 
170
248
  /** 默认密封器:bin/oj secret keygen + seal(失败返回 null,不抛) */
@@ -213,7 +291,7 @@ async function sealJwtSecret(destDir: string, report: InitReport, seal: Sealer =
213
291
  // secrets 段:私钥只放部署机,路径相对 config 目录(config.yaml 同级)
214
292
  const withSecrets = `${next.trimEnd()}\nsecrets:\n private_key_path: ./config/secrets-private.pem\n public_key_path: ./config/secrets-public.pem\n`;
215
293
  fs.writeFileSync(configPath, withSecrets);
216
- report.created.push("api/config/{secrets-private.pem,secrets-public.pem} + auth.jwt_secret 已密封(ENC[...])");
294
+ report.created.push("config/{secrets-private.pem,secrets-public.pem} + auth.jwt_secret 已密封(ENC[...])");
217
295
  }
218
296
 
219
297
  /**
@@ -318,9 +396,10 @@ function pruneUnincludedApiPaths(destDir: string, include: Set<string>, report:
318
396
  // 只裁本次新建的 openapi.json:init 是幂等补缺,不改动既有工程文件
319
397
  if (!report.created.includes("openapi.json"))
320
398
  return;
399
+ // 白名单里 `src/<name>/api` 这条(整模块收录)决定「脚手架有哪些后端模块」
321
400
  const mods = new Set([...include]
322
- .map(p => (p.startsWith("api/src/") ? p.slice("api/src/".length) : null))
323
- .filter((m): m is string => !!m && !m.includes("/")));
401
+ .map(p => /^src\/([^/]+)\/api$/.exec(p)?.[1])
402
+ .filter((m): m is string => !!m));
324
403
  if (mods.size === 0)
325
404
  return;
326
405
  const openapiPath = path.join(destDir, "openapi.json");
@@ -401,15 +480,15 @@ node_modules/
401
480
  bin/
402
481
  .ojm-tmp-*
403
482
  # ojm preview 的派生 config(临时产物,进程退出即删;见 preview-config.ts)
404
- api/.ojm-preview.yaml
405
- # 本地 sqlite 与运行日志
406
- api/db.sqlite
407
- api/db.sqlite-shm
408
- api/db.sqlite-wal
409
- api/logs/
483
+ .ojm-preview.yaml
484
+ # 本地 sqlite 与运行日志(config.yaml 在工程根,路径都相对工程根)
485
+ db.sqlite
486
+ db.sqlite-shm
487
+ db.sqlite-wal
488
+ logs/
410
489
  # 现场签发的开发证书与凭据私钥(ojm dev 缺证时自动签发,见 oj-cert.ts);私钥绝不入仓
411
- api/config/*.pem
412
- api/config/cert.jws
490
+ config/*.pem
491
+ config/cert.jws
413
492
  # 构建产物
414
493
  api/dist/
415
494
  web/dist/
@@ -506,7 +585,8 @@ function mergePackageJson(existing: Record<string, any>, generated: Record<strin
506
585
  return merged;
507
586
  }
508
587
 
509
- function generatePackageJson(cliRoot: string, projectName: string) {
588
+ /** 生成脚手架 package.json(导出供守卫测试:scripts ⊆ 模板 scripts,防双写漂移) */
589
+ export function generatePackageJson(cliRoot: string, projectName: string) {
510
590
  const { matrix: hostVersions } = resolveVersionMatrix(cliRoot);
511
591
  const cliPkg = JSON.parse(fs.readFileSync(path.join(cliRoot, "package.json"), "utf-8"));
512
592
 
@@ -556,11 +636,17 @@ function generatePackageJson(cliRoot: string, projectName: string) {
556
636
  type: "module",
557
637
  private: true,
558
638
  scripts: {
559
- dev: "ojm dev",
560
- build: "ojm build",
561
- preview: "ojm preview",
562
- info: "ojm info",
563
- typecheck: "tsc --noEmit -p tsconfig.json",
639
+ "dev": "ojm dev",
640
+ "build": "ojm build",
641
+ "preview": "ojm preview",
642
+ "info": "ojm info",
643
+ "typecheck": "tsc --noEmit -p tsconfig.json",
644
+ // typecheck:api 指向 src/tsconfig.json(后端工程面,白名单收录)——
645
+ // 早期生成器漏发这条(与模板双写漂移),补上并钉守卫测试
646
+ "typecheck:api": "tsc --noEmit -p src/tsconfig.json",
647
+ // 多方案(phase 2/3):打包全部用户方案(自带自动排除)/ 生成新方案骨架
648
+ "bundle": "ojm build --bundle",
649
+ "new": "ojm new",
564
650
  },
565
651
  devDependencies: devDeps,
566
652
  };