@oj-module/cli 0.2.64 → 0.2.67

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 (369) hide show
  1. package/bin/.oj-version +1 -0
  2. package/bin/oj +4 -0
  3. package/devkit/README.md +18 -4
  4. package/devkit/SKILL.md +35 -3
  5. package/devkit/framework-dev.md +22 -11
  6. package/devkit/manual.md +161 -68
  7. package/devkit/menu-rules.md +4 -1
  8. package/devkit/publish-walkthrough.md +110 -37
  9. package/devkit/wecom-internal-app.md +7 -2
  10. package/devkit/wecom-multi-corp-links.md +163 -0
  11. package/package.json +3 -2
  12. package/shell-dist/assets/ahooks.2l850ymmh0.js +18 -0
  13. package/shell-dist/assets/antd-es-locale-zh_CN.1gjjsvxbf0.js +1 -0
  14. package/shell-dist/assets/antd-img-crop.196rkty480.js +99 -0
  15. package/shell-dist/assets/antd-locale-en_US.561tozx860.js +1 -0
  16. package/shell-dist/assets/antd-locale-zh_CN.1cmpmgzh70.js +1 -0
  17. package/shell-dist/assets/antd.uw1coq15l0.js +348 -0
  18. package/shell-dist/assets/clsx.jotilrt2e0.js +1 -0
  19. package/shell-dist/assets/contract-errors.2o17vz9u50.js +1 -0
  20. package/shell-dist/assets/contract.3h327o3mj0.js +57 -0
  21. package/shell-dist/assets/cssinjs.atgmhan7m0.js +3 -0
  22. package/shell-dist/assets/dayjs-plugin-advancedFormat.3vsyd2eis0.js +1 -0
  23. package/shell-dist/assets/dayjs-plugin-customParseFormat.13xhghv1d0.js +1 -0
  24. package/shell-dist/assets/dayjs-plugin-isoWeek.1fn1tw82p0.js +1 -0
  25. package/shell-dist/assets/dayjs-plugin-localeData.2rwbjp9nk0.js +1 -0
  26. package/shell-dist/assets/dayjs-plugin-quarterOfYear.2oqo1lcx80.js +1 -0
  27. package/shell-dist/assets/dayjs-plugin-relativeTime.2pqs3et4y0.js +1 -0
  28. package/shell-dist/assets/dayjs-plugin-weekOfYear.gvn9iph080.js +1 -0
  29. package/shell-dist/assets/dayjs-plugin-weekYear.2u78l3ex10.js +1 -0
  30. package/shell-dist/assets/dayjs-plugin-weekday.2d56a4tml0.js +1 -0
  31. package/shell-dist/assets/dayjs.pa60l6wjo0.js +1 -0
  32. package/shell-dist/assets/dnd-kit-core.1xyreig6r0.js +5 -0
  33. package/shell-dist/assets/dnd-kit-sortable.s94qciuft0.js +1 -0
  34. package/shell-dist/assets/dnd-kit-utilities.1umw4xphb0.js +1 -0
  35. package/shell-dist/assets/i18next.33zqofapj0.js +1 -0
  36. package/shell-dist/assets/icons-es-components-Context.365qmflwg0.js +1 -0
  37. package/shell-dist/assets/icons.159wjwals0.js +57 -0
  38. package/shell-dist/assets/{index-xZhmujjM.js → index.6alqyx6ae0.js} +21 -1
  39. package/shell-dist/assets/jsx-dev-runtime.3uibuc82f0.js +14 -0
  40. package/shell-dist/assets/jsx-runtime.h40xu4o5b0.js +14 -0
  41. package/shell-dist/assets/keepalive-for-react.xenu5vam70.js +1 -0
  42. package/shell-dist/assets/kit.37kr8ui390.js +1 -0
  43. package/shell-dist/assets/ky.1cgch7gu60.js +6 -0
  44. package/shell-dist/assets/motion-react.pjsxytvjs0.js +12 -0
  45. package/shell-dist/assets/motion.21tciqutg0.js +4 -0
  46. package/shell-dist/assets/nprogress.2nz76n9ny0.js +7 -0
  47. package/shell-dist/assets/ojm-dayjs-locale-zh-cn.3vr3beiyp0.js +9 -0
  48. package/shell-dist/assets/pinyin-pro.3v0mnxczn0.js +1 -0
  49. package/shell-dist/assets/pro-components.2kyot1vcl0.js +83 -0
  50. package/shell-dist/assets/react-countup.2x8idnr8y0.js +10 -0
  51. package/shell-dist/assets/react-dom-client.ick0v05ob0.js +43 -0
  52. package/shell-dist/assets/react-dom.2so9uxg3f0.js +22 -0
  53. package/shell-dist/assets/react-error-boundary.2dncdfj3b0.js +1 -0
  54. package/shell-dist/assets/react-i18next.auhvjhqv00.js +1 -0
  55. package/shell-dist/assets/react-jss.1ei6dr9e00.js +29 -0
  56. package/shell-dist/assets/react-query.3rln45ow00.js +1 -0
  57. package/shell-dist/assets/react-router-dom.3c6zld8h40.js +44 -0
  58. package/shell-dist/assets/react-router.2h9rebcv50.js +56 -0
  59. package/shell-dist/assets/react.1ew63mwts0.js +14 -0
  60. package/shell-dist/assets/runtime.2dgstpjsu0.js +21702 -0
  61. package/shell-dist/assets/simplebar-react.2g739uvai0.js +1 -0
  62. package/shell-dist/assets/spin-delay.yjeyj6iwp0.js +9 -0
  63. package/shell-dist/assets/tailwind-merge.125ehiqjw0.js +1 -0
  64. package/shell-dist/assets/use-sync-external-store-shim.1nz1pi9bb0.js +22 -0
  65. package/shell-dist/assets/zustand-middleware.1xaxjueme0.js +6 -0
  66. package/shell-dist/assets/zustand-react-shallow.pybayv1hx0.js +1 -0
  67. package/shell-dist/assets/zustand-react.9fsl9brxy0.js +1 -0
  68. package/shell-dist/assets/zustand-shallow.6cz0yyv9x0.js +1 -0
  69. package/shell-dist/assets/zustand-vanilla-shallow.3424monv50.js +1 -0
  70. package/shell-dist/assets/zustand-vanilla.1mo3ka3g30.js +1 -0
  71. package/shell-dist/assets/zustand.3gjgzlnqo0.js +1 -0
  72. package/shell-dist/index.html +67 -118
  73. package/shell-dist/versions.json +3 -3
  74. package/src/__tests__/publish-keygen.test.ts +157 -0
  75. package/src/__tests__/templates-migration-schema-parity.test.ts +540 -0
  76. package/src/__tests__/templates-schema-columns.test.ts +15 -2
  77. package/src/__tests__/templates-tenant-scope.test.ts +6 -2
  78. package/src/bundle.ts +8 -3
  79. package/src/cli.ts +17 -5
  80. package/src/dev.ts +15 -10
  81. package/src/esm-exports.ts +152 -70
  82. package/src/install.ts +11 -4
  83. package/src/keygen.ts +54 -0
  84. package/src/lifecycle/__tests__/bundle.test.ts +21 -3
  85. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +1 -1
  86. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +2 -2
  87. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +5 -2
  88. package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +1 -1
  89. package/src/lifecycle/__tests__/manifest-contract.test.ts +75 -3
  90. package/src/lifecycle/__tests__/smoke.test.ts +2 -2
  91. package/src/manifest-contract.ts +91 -8
  92. package/src/market/bootstrap.ts +26 -2
  93. package/src/market/command.ts +1 -0
  94. package/src/market/publish.ts +5 -1
  95. package/src/market/walkthrough.ts +24 -8
  96. package/src/shared-deps.ts +69 -19
  97. package/src/site-base.ts +4 -0
  98. package/src/site.ts +2 -0
  99. package/src/usage.ts +8 -4
  100. package/src/vendor.ts +5 -1
  101. package/templates/config.yaml +4 -2
  102. package/templates/init.include.yaml +8 -3
  103. package/templates/openapi.json +460 -1
  104. package/templates/src/app/api/_platform/_lifecycle/compensate.ts +136 -0
  105. package/templates/src/app/api/_platform/_lifecycle/installer.ts +71 -22
  106. package/templates/src/app/api/_platform/_lifecycle/lock.ts +23 -0
  107. package/templates/src/app/api/_platform/_lifecycle/steps/deploy-web.ts +28 -0
  108. package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +46 -0
  109. package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +100 -36
  110. package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +7 -15
  111. package/templates/src/app/api/_platform/_lifecycle/text-edit.ts +71 -19
  112. package/templates/src/app/api/_platform/_lifecycle/types.ts +26 -2
  113. package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +93 -14
  114. package/templates/src/app/api/_platform/_shared/service/domain/settings.ts +8 -4
  115. package/templates/src/app/api/_platform/manifest.yaml +1 -0
  116. package/templates/src/app/api/_platform/market/api.ts +235 -10
  117. package/templates/src/app/api/_platform/migrations/0001__baseline.mysql.sql +178 -0
  118. package/templates/src/app/api/_platform/migrations/0001__baseline.pg.sql +173 -0
  119. package/templates/src/app/api/_platform/migrations/0001__baseline.sql +188 -0
  120. package/templates/src/app/api/_platform/migrations/0002__market_release.mysql.sql +17 -0
  121. package/templates/src/app/api/_platform/migrations/0002__market_release.pg.sql +16 -0
  122. package/templates/src/app/api/_platform/migrations/0002__market_release.sql +33 -0
  123. package/templates/src/app/api/_platform/schema.yaml +18 -0
  124. package/templates/src/app/api/_platform/seed.mysql.sql +10 -0
  125. package/templates/src/app/api/_platform/seed.pg.sql +10 -0
  126. package/templates/src/app/api/auth/_shared/wecom/client.ts +24 -4
  127. package/templates/src/app/api/home/migrations/0001__create_home_tables.mysql.sql +14 -0
  128. package/templates/src/app/api/home/migrations/0001__create_home_tables.pg.sql +14 -0
  129. package/templates/src/app/api/home/migrations/0002__tenant.pg.sql +4 -0
  130. package/templates/src/app/api/home/seed.mysql.sql +71 -0
  131. package/templates/src/app/api/home/seed.pg.sql +71 -0
  132. package/templates/src/app/api/notification/_shared/notify.ts +6 -6
  133. package/templates/src/app/api/notification/migrations/0001__create_notifications.mysql.sql +11 -0
  134. package/templates/src/app/api/notification/migrations/0001__create_notifications.pg.sql +11 -0
  135. package/templates/src/app/api/notification/migrations/0002__notifications_user.mysql.sql +8 -0
  136. package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.mysql.sql +21 -0
  137. package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.pg.sql +22 -0
  138. package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.mysql.sql +10 -0
  139. package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.pg.sql +11 -0
  140. package/templates/src/app/api/notification/migrations/0005__tenant.pg.sql +6 -0
  141. package/templates/src/app/api/notification/seed.mysql.sql +8 -0
  142. package/templates/src/app/api/notification/seed.pg.sql +11 -0
  143. package/templates/src/app/api/publish/_shared/identity.ts +69 -0
  144. package/templates/src/app/api/publish/api.ts +349 -0
  145. package/templates/src/app/api/publish/manifest.yaml +8 -0
  146. package/templates/src/app/api/publish/migrations/0001__publish_credential.mysql.sql +23 -0
  147. package/templates/src/app/api/publish/migrations/0001__publish_credential.pg.sql +23 -0
  148. package/templates/src/app/api/publish/migrations/0001__publish_credential.sql +39 -0
  149. package/templates/src/app/api/publish/schema.yaml +33 -0
  150. package/templates/src/app/api/system/login-methods/api.ts +2 -1
  151. package/templates/src/app/web/_platform/__client/api.schemas.ts +15 -0
  152. package/templates/src/app/web/_platform/__client/index.ts +25 -1
  153. package/templates/src/app/web/entry.ts +9 -5
  154. package/templates/src/app/web/home/pages/components/bar-chart.tsx +5 -3
  155. package/templates/src/app/web/home/pages/components/echarts-setup.ts +30 -0
  156. package/templates/src/app/web/home/pages/components/line-chart.tsx +5 -3
  157. package/templates/src/app/web/home/pages/components/pie-chart.tsx +6 -3
  158. package/templates/src/app/web/home/pages/index.tsx +3 -2
  159. package/templates/src/app/web/home/routes.ts +8 -2
  160. package/templates/src/app/web/login/locales/en-US.json +7 -1
  161. package/templates/src/app/web/login/locales/zh-CN.json +7 -1
  162. package/templates/src/app/web/login/pages/login.tsx +79 -86
  163. package/templates/src/app/web/login/routes.ts +54 -1
  164. package/templates/src/app/web/personal-center/locales/en-US.json +1 -0
  165. package/templates/src/app/web/personal-center/locales/zh-CN.json +1 -0
  166. package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +5 -0
  167. package/templates/src/app/web/system/__client/api.schemas.ts +2 -1
  168. package/templates/src/app/web/system/locales/en-US.json +1 -8
  169. package/templates/src/app/web/system/locales/zh-CN.json +1 -8
  170. package/templates/src/app/web/system/pages/settings/index.tsx +17 -4
  171. package/templates/src/app/web/system/routes.ts +0 -6
  172. package/templates/src/oj-market/web/__tests__/CardGrid.test.tsx +33 -0
  173. package/templates/src/oj-market/web/__tests__/DetailDrawer.test.tsx +69 -0
  174. package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +7 -1
  175. package/templates/src/oj-market/web/api/client.ts +3 -0
  176. package/templates/src/oj-market/web/api/defaultClient.ts +5 -0
  177. package/templates/src/oj-market/web/components/CardItem.tsx +44 -1
  178. package/templates/src/oj-market/web/components/DetailDrawer.tsx +17 -1
  179. package/templates/src/oj-market/web/entry.ts +31 -2
  180. package/templates/src/oj-market/web/locales/en-US.json +5 -1
  181. package/templates/src/oj-market/web/locales/zh-CN.json +5 -1
  182. package/templates/src/oj-market/web/publish/__client/api.schemas.ts +61 -0
  183. package/templates/src/oj-market/web/publish/__client/index.ts +164 -0
  184. package/templates/src/oj-market/web/publish/locales/en-US.json +80 -0
  185. package/templates/src/oj-market/web/publish/locales/zh-CN.json +80 -0
  186. package/templates/src/oj-market/web/publish/pages/history.tsx +112 -0
  187. package/templates/src/oj-market/web/publish/pages/index.tsx +196 -0
  188. package/templates/src/oj-market/web/publish/pages/model.ts +52 -0
  189. package/templates/src/oj-market/web/publish/pages/token-modal.tsx +82 -0
  190. package/templates/src/oj-market/web/publish/routes.ts +41 -0
  191. package/templates/src/oj-market/web/types.ts +30 -0
  192. package/templates/tests/lifecycle/compensate.spec.ts +207 -0
  193. package/templates/tests/lifecycle/deploy-smoke.spec.ts +38 -14
  194. package/templates/tests/lifecycle/installer.spec.ts +28 -9
  195. package/templates/tests/lifecycle/market-api.spec.ts +164 -12
  196. package/templates/tests/lifecycle/preflight.spec.ts +68 -0
  197. package/templates/tests/lifecycle/role-title-translate.spec.ts +2 -32
  198. package/templates/tests/lifecycle/text-edit.spec.ts +45 -27
  199. package/templates/tests/lifecycle/uninstall.spec.ts +36 -0
  200. package/templates/tests/publish-credential.spec.ts +354 -0
  201. package/templates/tests/publish-identity.spec.ts +44 -0
  202. package/templates/tests/wecom-client-timeout.spec.ts +33 -3
  203. package/templates/tests/wecom-landing.spec.ts +63 -1
  204. package/templates/tsconfig.paths.json +1 -0
  205. package/templates/web.config.ts +6 -4
  206. package/vendor/host-versions.json +3 -3
  207. package/shell-dist/assets/ahooks.js +0 -6909
  208. package/shell-dist/assets/antd-es-locale-zh_CN.js +0 -257
  209. package/shell-dist/assets/antd-img-crop.js +0 -1495
  210. package/shell-dist/assets/antd-locale-en_US.js +0 -374
  211. package/shell-dist/assets/antd-locale-zh_CN.js +0 -379
  212. package/shell-dist/assets/antd.js +0 -106874
  213. package/shell-dist/assets/clsx.js +0 -33
  214. package/shell-dist/assets/contract-errors.js +0 -30
  215. package/shell-dist/assets/contract.js +0 -18900
  216. package/shell-dist/assets/cssinjs.js +0 -2595
  217. package/shell-dist/assets/dayjs-plugin-advancedFormat.js +0 -98
  218. package/shell-dist/assets/dayjs-plugin-customParseFormat.js +0 -173
  219. package/shell-dist/assets/dayjs-plugin-isoWeek.js +0 -77
  220. package/shell-dist/assets/dayjs-plugin-localeData.js +0 -126
  221. package/shell-dist/assets/dayjs-plugin-quarterOfYear.js +0 -77
  222. package/shell-dist/assets/dayjs-plugin-relativeTime.js +0 -92
  223. package/shell-dist/assets/dayjs-plugin-weekOfYear.js +0 -73
  224. package/shell-dist/assets/dayjs-plugin-weekYear.js +0 -63
  225. package/shell-dist/assets/dayjs-plugin-weekday.js +0 -63
  226. package/shell-dist/assets/dayjs.js +0 -326
  227. package/shell-dist/assets/dnd-kit-core.js +0 -3601
  228. package/shell-dist/assets/dnd-kit-sortable.js +0 -717
  229. package/shell-dist/assets/dnd-kit-utilities.js +0 -340
  230. package/shell-dist/assets/i18next.js +0 -2321
  231. package/shell-dist/assets/icons-es-components-Context.js +0 -20
  232. package/shell-dist/assets/icons.js +0 -34961
  233. package/shell-dist/assets/jsx-dev-runtime.js +0 -75
  234. package/shell-dist/assets/jsx-runtime.js +0 -97
  235. package/shell-dist/assets/keepalive-for-react.js +0 -283
  236. package/shell-dist/assets/kit.js +0 -151
  237. package/shell-dist/assets/ky.js +0 -1427
  238. package/shell-dist/assets/motion-react.js +0 -13589
  239. package/shell-dist/assets/motion.js +0 -9959
  240. package/shell-dist/assets/nprogress.js +0 -314
  241. package/shell-dist/assets/ojm--ant-design-icons-es-icons-BarsOutlined.js +0 -7
  242. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CalendarOutlined.js +0 -7
  243. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretDownFilled.js +0 -7
  244. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretDownOutlined.js +0 -7
  245. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretUpOutlined.js +0 -7
  246. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CheckCircleFilled.js +0 -7
  247. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CheckOutlined.js +0 -7
  248. package/shell-dist/assets/ojm--ant-design-icons-es-icons-ClockCircleOutlined.js +0 -7
  249. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CloseCircleFilled.js +0 -7
  250. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CloseOutlined.js +0 -7
  251. package/shell-dist/assets/ojm--ant-design-icons-es-icons-CopyOutlined.js +0 -7
  252. package/shell-dist/assets/ojm--ant-design-icons-es-icons-DeleteOutlined.js +0 -7
  253. package/shell-dist/assets/ojm--ant-design-icons-es-icons-DoubleLeftOutlined.js +0 -7
  254. package/shell-dist/assets/ojm--ant-design-icons-es-icons-DoubleRightOutlined.js +0 -7
  255. package/shell-dist/assets/ojm--ant-design-icons-es-icons-DownOutlined.js +0 -7
  256. package/shell-dist/assets/ojm--ant-design-icons-es-icons-DownloadOutlined.js +0 -7
  257. package/shell-dist/assets/ojm--ant-design-icons-es-icons-EditOutlined.js +0 -7
  258. package/shell-dist/assets/ojm--ant-design-icons-es-icons-EllipsisOutlined.js +0 -7
  259. package/shell-dist/assets/ojm--ant-design-icons-es-icons-EnterOutlined.js +0 -7
  260. package/shell-dist/assets/ojm--ant-design-icons-es-icons-ExclamationCircleFilled.js +0 -7
  261. package/shell-dist/assets/ojm--ant-design-icons-es-icons-EyeInvisibleOutlined.js +0 -7
  262. package/shell-dist/assets/ojm--ant-design-icons-es-icons-EyeOutlined.js +0 -7
  263. package/shell-dist/assets/ojm--ant-design-icons-es-icons-FileOutlined.js +0 -7
  264. package/shell-dist/assets/ojm--ant-design-icons-es-icons-FileTextOutlined.js +0 -7
  265. package/shell-dist/assets/ojm--ant-design-icons-es-icons-FilterFilled.js +0 -7
  266. package/shell-dist/assets/ojm--ant-design-icons-es-icons-FolderOpenOutlined.js +0 -7
  267. package/shell-dist/assets/ojm--ant-design-icons-es-icons-FolderOutlined.js +0 -7
  268. package/shell-dist/assets/ojm--ant-design-icons-es-icons-HolderOutlined.js +0 -7
  269. package/shell-dist/assets/ojm--ant-design-icons-es-icons-InfoCircleFilled.js +0 -7
  270. package/shell-dist/assets/ojm--ant-design-icons-es-icons-LeftOutlined.js +0 -7
  271. package/shell-dist/assets/ojm--ant-design-icons-es-icons-LoadingOutlined.js +0 -7
  272. package/shell-dist/assets/ojm--ant-design-icons-es-icons-MinusOutlined.js +0 -7
  273. package/shell-dist/assets/ojm--ant-design-icons-es-icons-MinusSquareOutlined.js +0 -7
  274. package/shell-dist/assets/ojm--ant-design-icons-es-icons-PaperClipOutlined.js +0 -7
  275. package/shell-dist/assets/ojm--ant-design-icons-es-icons-PictureOutlined.js +0 -7
  276. package/shell-dist/assets/ojm--ant-design-icons-es-icons-PlusOutlined.js +0 -7
  277. package/shell-dist/assets/ojm--ant-design-icons-es-icons-PlusSquareOutlined.js +0 -7
  278. package/shell-dist/assets/ojm--ant-design-icons-es-icons-QuestionCircleOutlined.js +0 -7
  279. package/shell-dist/assets/ojm--ant-design-icons-es-icons-ReloadOutlined.js +0 -7
  280. package/shell-dist/assets/ojm--ant-design-icons-es-icons-RightOutlined.js +0 -7
  281. package/shell-dist/assets/ojm--ant-design-icons-es-icons-RotateLeftOutlined.js +0 -7
  282. package/shell-dist/assets/ojm--ant-design-icons-es-icons-RotateRightOutlined.js +0 -7
  283. package/shell-dist/assets/ojm--ant-design-icons-es-icons-SearchOutlined.js +0 -7
  284. package/shell-dist/assets/ojm--ant-design-icons-es-icons-StarFilled.js +0 -7
  285. package/shell-dist/assets/ojm--ant-design-icons-es-icons-SwapOutlined.js +0 -7
  286. package/shell-dist/assets/ojm--ant-design-icons-es-icons-SwapRightOutlined.js +0 -7
  287. package/shell-dist/assets/ojm--ant-design-icons-es-icons-UpOutlined.js +0 -7
  288. package/shell-dist/assets/ojm--ant-design-icons-es-icons-VerticalAlignTopOutlined.js +0 -7
  289. package/shell-dist/assets/ojm--ant-design-icons-es-icons-WarningFilled.js +0 -7
  290. package/shell-dist/assets/ojm--ant-design-icons-es-icons-ZoomInOutlined.js +0 -7
  291. package/shell-dist/assets/ojm--ant-design-icons-es-icons-ZoomOutOutlined.js +0 -7
  292. package/shell-dist/assets/ojm-dayjs-locale-zh-cn.js +0 -81
  293. package/shell-dist/assets/pinyin-pro.js +0 -25175
  294. package/shell-dist/assets/pro-components.js +0 -44627
  295. package/shell-dist/assets/react-countup.js +0 -525
  296. package/shell-dist/assets/react-dom-client.js +0 -12134
  297. package/shell-dist/assets/react-dom.js +0 -266
  298. package/shell-dist/assets/react-error-boundary.js +0 -141
  299. package/shell-dist/assets/react-i18next.js +0 -1773
  300. package/shell-dist/assets/react-jss.js +0 -4089
  301. package/shell-dist/assets/react-query.js +0 -3072
  302. package/shell-dist/assets/react-router-dom.js +0 -9927
  303. package/shell-dist/assets/react-router.js +0 -14584
  304. package/shell-dist/assets/react.js +0 -590
  305. package/shell-dist/assets/runtime.js +0 -28311
  306. package/shell-dist/assets/simplebar-react.js +0 -1119
  307. package/shell-dist/assets/spin-delay.js +0 -111
  308. package/shell-dist/assets/tailwind-merge.js +0 -3364
  309. package/shell-dist/assets/use-sync-external-store-shim.js +0 -130
  310. package/shell-dist/assets/zustand-middleware.js +0 -513
  311. package/shell-dist/assets/zustand-react-shallow.js +0 -27
  312. package/shell-dist/assets/zustand-react.js +0 -39
  313. package/shell-dist/assets/zustand-shallow.js +0 -22
  314. package/shell-dist/assets/zustand-vanilla-shallow.js +0 -71
  315. package/shell-dist/assets/zustand-vanilla.js +0 -40
  316. package/shell-dist/assets/zustand.js +0 -32
  317. package/templates/seed.mysql.sql +0 -6
  318. package/templates/seed.pg.sql +0 -6
  319. package/templates/src/app/api/_platform/migrations/0001__create_users.mysql.sql +0 -9
  320. package/templates/src/app/api/_platform/migrations/0001__create_users.pg.sql +0 -9
  321. package/templates/src/app/api/_platform/migrations/0001__create_users.sql +0 -7
  322. package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.mysql.sql +0 -3
  323. package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.sql +0 -2
  324. package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.mysql.sql +0 -5
  325. package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.sql +0 -5
  326. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.mysql.sql +0 -70
  327. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.pg.sql +0 -67
  328. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.sql +0 -67
  329. package/templates/src/app/api/_platform/migrations/0005__users_must_change.mysql.sql +0 -3
  330. package/templates/src/app/api/_platform/migrations/0005__users_must_change.sql +0 -2
  331. package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.mysql.sql +0 -3
  332. package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.sql +0 -3
  333. package/templates/src/app/api/_platform/migrations/0007__users_email.sql +0 -3
  334. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.mysql.sql +0 -10
  335. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.pg.sql +0 -10
  336. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.sql +0 -11
  337. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.mysql.sql +0 -12
  338. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.pg.sql +0 -12
  339. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.sql +0 -13
  340. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.mysql.sql +0 -17
  341. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.pg.sql +0 -16
  342. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.sql +0 -18
  343. package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.mysql.sql +0 -2
  344. package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.sql +0 -4
  345. package/templates/src/app/api/_platform/migrations/0012__market_history.mysql.sql +0 -13
  346. package/templates/src/app/api/_platform/migrations/0012__market_history.pg.sql +0 -12
  347. package/templates/src/app/api/_platform/migrations/0012__market_history.sql +0 -14
  348. package/templates/src/app/api/_platform/migrations/0013__users_profile.sql +0 -4
  349. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.mysql.sql +0 -9
  350. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.pg.sql +0 -7
  351. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.sql +0 -8
  352. package/templates/src/app/api/_platform/migrations/0015__tenant.mysql.sql +0 -12
  353. package/templates/src/app/api/_platform/migrations/0015__tenant.pg.sql +0 -11
  354. package/templates/src/app/api/_platform/migrations/0015__tenant.sql +0 -44
  355. package/templates/src/app/api/_platform/migrations/0016__org.mysql.sql +0 -41
  356. package/templates/src/app/api/_platform/migrations/0016__org.pg.sql +0 -40
  357. package/templates/src/app/api/_platform/migrations/0016__org.sql +0 -62
  358. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +0 -7
  359. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +0 -4
  360. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +0 -8
  361. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +0 -3
  362. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +0 -3
  363. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +0 -8
  364. package/templates/src/app/web/system/pages/login-methods/index.tsx +0 -30
  365. /package/shell-dist/assets/{nprogress-css.js → nprogress-css.1cq2pzkre0.js} +0 -0
  366. /package/shell-dist/assets/{ojm-antd-es-button.js → ojm-antd-es-button.2uriphio50.js} +0 -0
  367. /package/shell-dist/assets/{ojm-antd-es-modal.js → ojm-antd-es-modal.bf3tgdh8v0.js} +0 -0
  368. /package/shell-dist/assets/{ojm-antd-es-slider.js → ojm-antd-es-slider.xyvptuhid0.js} +0 -0
  369. /package/shell-dist/assets/{ojm-antd-es-upload.js → ojm-antd-es-upload.13gx45e0g0.js} +0 -0
@@ -88,12 +88,25 @@ describe("templates 后端 schema.yaml 列白名单", () => {
88
88
  expect(violations).toEqual([]);
89
89
  });
90
90
 
91
- it("s-2 回归锚点:0017 三方言迁移齐备,users.employee_no 已声明为 text", () => {
91
+ it("s-2 回归锚点:_platform 基线为 0001、增量 0002(发布历史)三方言齐备,且基线里确有 users.employee_no", () => {
92
92
  const migDir = join(srcRoot, "app", "api", "_platform", "migrations");
93
- for (const file of ["0017__users_employee_no.sql", "0017__users_employee_no.mysql.sql", "0017__users_employee_no.pg.sql"])
93
+ for (const file of [
94
+ "0001__baseline.sql",
95
+ "0001__baseline.mysql.sql",
96
+ "0001__baseline.pg.sql",
97
+ "0002__market_release.sql",
98
+ "0002__market_release.mysql.sql",
99
+ "0002__market_release.pg.sql",
100
+ ])
94
101
  expect(existsSync(join(migDir, file)), `缺迁移 ${file}`).toBe(true);
95
102
 
103
+ // 0001 是压平后的结构基线,已应用过的库不会重跑——新增结构只能追加新序号,
104
+ // 不得回改基线(否则老库拿不到)。序号必须从 0001 起连续、无空洞。
105
+ const seqs = [...new Set(readdirSync(migDir).map(f => f.slice(0, 4)))].sort();
106
+ expect(seqs).toEqual(["0001", "0002"]);
107
+
96
108
  const schema = parse(readFileSync(join(srcRoot, "app", "api", "_platform", "schema.yaml"), "utf-8")) as SchemaDoc;
97
109
  expect(schema.tables?.users?.columns?.employee_no).toMatchObject({ type: "text" });
110
+ expect(readFileSync(join(migDir, "0001__baseline.sql"), "utf-8")).toContain("employee_no");
98
111
  });
99
112
  });
@@ -115,7 +115,7 @@ describe("templates 租户定性(A 租户表 / B 共享表)", () => {
115
115
  expect(unknown).toEqual([]);
116
116
  });
117
117
 
118
- it("t-4 27 张表的 A/B 分组与方案 v3 §5.2 一致(新增表须先定性)", () => {
118
+ it("t-4 29 张表的 A/B 分组与方案 v3 §5.2 一致(新增表须先定性)", () => {
119
119
  const { tables } = loadSchemaTables();
120
120
  const shared = [...tables].filter(([, d]) => d.tenant === false).map(([n]) => n).sort();
121
121
  // Phase 0 落地 21 张(11 A + 10 B);Phase 1 加企微三表,全部 B 组(D9:查它们时必然还没有租户上下文)
@@ -125,10 +125,14 @@ describe("templates 租户定性(A 租户表 / B 共享表)", () => {
125
125
  // (D7 不接受 corpId 参数),故不进 shared_allow——跨租户读元数据那条另走显式 tenant_id 的裸 SQL。
126
126
  // 改这张表清单 = 改租户隔离边界,必须同步 config.yaml 的 tenant.shared_allow(t-2/t-3 会把关)。
127
127
  // 2026-11-02 demo 模块下线(ecc7b35e)删掉 demo/api/schema.yaml 的 todos 表,28 → 27。
128
- expect(tables.size).toBe(27);
128
+ // 2026-11 发布凭证加 publish_credential:共享表(CLI 兑换无租户语义,归属走 owner_tenant),27 → 28。
129
+ // 2026-11 发布历史加 market_release:共享表(发布物落在站点级 CDN,历史按站点归口),28 → 29。
130
+ expect(tables.size).toBe(29);
129
131
  expect(shared).toEqual([
130
132
  "_oj_migrations",
131
133
  "market_history",
134
+ "market_release",
135
+ "publish_credential",
132
136
  "reg_source_seq",
133
137
  "sys_login_throttle",
134
138
  "sys_permission",
package/src/bundle.ts CHANGED
@@ -456,14 +456,19 @@ function checkModuleManifest(built: string, name: string, version: string, _summ
456
456
  return out;
457
457
  }
458
458
 
459
- /** api.tables ↔ schema.yaml 表集对照(C2-12a 构建列;schema 缺失/声明不一致均 fail-fast) */
459
+ /**
460
+ * api.tables ↔ schema.yaml 表集对照(C2-12a)+ 表前缀(C2-12c)。
461
+ * 引用式方案(tables 缺省)只要 schema.yaml 存在也做前缀检查——表名冲突与打包形态无关。
462
+ */
460
463
  function checkTables(m: Record<string, unknown>, layout: ProjectLayout, name: string): string[] {
461
464
  const api = m.api as Record<string, unknown> | undefined;
462
- if (api === undefined || api.tables === undefined)
465
+ if (api === undefined)
463
466
  return [];
464
467
  const schemaPath = path.join(apiSrcOf(layout, name), "schema.yaml");
465
468
  if (!fs.existsSync(schemaPath)) {
466
- return [`【C2-12a】api.tables 已声明但缺少 ${path.relative(layout.projectRoot, schemaPath)},无法对照表集(oj 冗余校验同源)`];
469
+ return api.tables === undefined
470
+ ? []
471
+ : [`【C2-12a】api.tables 已声明但缺少 ${path.relative(layout.projectRoot, schemaPath)},无法对照表集(oj 冗余校验同源)`];
467
472
  }
468
473
  return checkTablesAgainstSchema(m, fs.readFileSync(schemaPath, "utf8"));
469
474
  }
package/src/cli.ts CHANGED
@@ -223,9 +223,21 @@ function makeAction(def: HelpEntry, projectRoot: string, lang: Lang, exit: (code
223
223
  await mergeManifests(out ?? "", inputs);
224
224
  };
225
225
  case "keygen":
226
- return async (opts: { id?: string, out?: string, yes?: boolean } = {}) => {
227
- const { keygenCommand } = await import("./keygen");
228
- keygenCommand({ id: opts.id, out: opts.out, yes: opts.yes });
226
+ return async (opts: { id?: string, out?: string, yes?: boolean, credential?: string, siteUrl?: string } = {}) => {
227
+ const { exchangePublishCredential, keygenCommand } = await import("./keygen");
228
+ // 令牌解析:--credential > OJM_PUBLISH_CREDENTIAL;都没有则走原离线路径(逐字节不变)
229
+ const credential = (opts.credential ?? process.env.OJM_PUBLISH_CREDENTIAL ?? "").trim();
230
+ if (credential === "") {
231
+ keygenCommand({ id: opts.id, out: opts.out, yes: opts.yes });
232
+ return;
233
+ }
234
+ // 身份只来自凭证:--id 是另一条(离线)路径的入口,同时给必然有一方被静默忽略
235
+ if (opts.id !== undefined)
236
+ throw new UsageError("[ojm] --credential 与 --id 不能同时使用:凭证已绑定发布身份(要自定义身份请走离线 ojm keygen --id)");
237
+ const identity = await exchangePublishCredential(credential, { siteUrl: opts.siteUrl });
238
+ const who = identity.display_name === null ? identity.username : `${identity.display_name}(${identity.username})`;
239
+ console.log(`[ojm] 凭证已兑换:产品 ${identity.product} / 申请人 ${who}`);
240
+ keygenCommand({ id: identity.key_id, out: opts.out, yes: opts.yes });
229
241
  };
230
242
  case "sign":
231
243
  return async (file: string | undefined, opts: { id?: string, key?: string, check?: boolean, registryId?: string } = {}) => {
@@ -283,9 +295,9 @@ function makeAction(def: HelpEntry, projectRoot: string, lang: Lang, exit: (code
283
295
  exit(EXIT_FAILURE);
284
296
  };
285
297
  case "install":
286
- return async (target: string | undefined, opts: SiteOpts & { locale?: string, name?: string, version?: string, registry?: string } = {}) => {
298
+ return async (target: string | undefined, opts: SiteOpts & { locale?: string, name?: string, version?: string, registry?: string, force?: boolean } = {}) => {
287
299
  const { installCommand } = await import("./install");
288
- await installCommand(target ?? "", { siteUrl: opts.siteUrl, user: opts.user, pass: opts.pass, locale: opts.locale, name: opts.name, version: opts.version, registry: opts.registry });
300
+ await installCommand(target ?? "", { siteUrl: opts.siteUrl, user: opts.user, pass: opts.pass, locale: opts.locale, name: opts.name, version: opts.version, registry: opts.registry, force: opts.force });
289
301
  };
290
302
  case "list":
291
303
  return async (opts: SiteOpts & { all?: boolean, json?: boolean } = {}) => {
package/src/dev.ts CHANGED
@@ -230,12 +230,6 @@ export async function devServer(projectRoot: string, opts: DevOptions = {}): Pro
230
230
  proxyWsUpgrade(ojTarget)(req, socket, head);
231
231
  });
232
232
 
233
- // 退出回收:server 关闭 → SSE 通道 + oj 子进程一并回收
234
- server.on("close", () => {
235
- hub.close();
236
- void oj?.stop();
237
- });
238
-
239
233
  // 终端 Ctrl+C / `kill`(SIGINT / SIGTERM)终止 dev 进程时,后端 oj 必须随之消亡:
240
234
  // 起一个统一优雅停机,先停 oj 再退出;并打印后端终止信息。
241
235
  // 终端 Ctrl-C 是进程组信号:外层 bin 转发一份 + 组送达一份,内层会收到
@@ -249,13 +243,24 @@ export async function devServer(projectRoot: string, opts: DevOptions = {}): Pro
249
243
  shuttingDown = true;
250
244
  void shutdownDev(server, oj, hub);
251
245
  };
252
- process.on("SIGINT", shutdown);
253
- process.on("SIGTERM", shutdown);
254
-
255
246
  // 兜底:dev 进程以任何非优雅路径退出(未走 SIGINT/SIGTERM,如未捕获异常)时,
256
247
  // 同步强杀 oj,确保它不孤儿化继续占端口(shutdownDev 已停则 kill 为 no-op)。
257
- process.once("exit", () => {
248
+ const killOjOnExit = () => {
258
249
  oj?.kill?.();
250
+ };
251
+ process.on("SIGINT", shutdown);
252
+ process.on("SIGTERM", shutdown);
253
+ process.once("exit", killOjOnExit);
254
+
255
+ // 退出回收:server 关闭 → SSE 通道 + oj 子进程一并回收,并摘除本实例注册的
256
+ // 进程级信号/exit handler。devServer 可在同一进程被多次调用(编程式 / 测试),
257
+ // 不摘除会累积闭包、触发 MaxListenersExceededWarning(与 preview 的 stop 同款)。
258
+ server.on("close", () => {
259
+ hub.close();
260
+ void oj?.stop();
261
+ process.removeListener("SIGINT", shutdown);
262
+ process.removeListener("SIGTERM", shutdown);
263
+ process.removeListener("exit", killOjOnExit);
259
264
  });
260
265
 
261
266
  // 端口被占用时自动顺延,避免 `EADDRINUSE` 直接让 `pnpm dev` 以非零码退出
@@ -14,14 +14,19 @@
14
14
  * 本模块只做「扫描产物文本、回答谁导出/导入了什么」,不含修复逻辑,
15
15
  * 供 shell 构建(门禁,失败即中断)与 tests/ 共用,避免两套正则漂移。
16
16
  *
17
- * 解析口径:只认**行首**的 `import` / `export`。esbuild 与 vite 在
18
- * `minify: false` 下顶层语句一律顶格输出,依赖内部的代码有缩进,
19
- * 借此把「字符串字面量里恰好写了 import」这类误报压到最低。
17
+ * 解析口径:`es-module-lexer` 真词法扫描,而非行首正则。
18
+ * 早先的实现假设「esbuild/vite 在 `minify: false` 下顶层语句一律顶格」,一旦
19
+ * 共享资产开启压缩(`minifyWhitespace` 会把 `export{a as b}` / `}from"react"`
20
+ * 挤成一行且行内任意位置),行首锚定的正则全部失配 → 门禁集体误报「零具名导出」
21
+ * 和「裸说明符未登记」。词法扫描只依赖语法、与压缩/换行无关,故换掉。
22
+ * 顺带消灭了「字符串字面量里恰好写了 import/from」的误报(原先靠行首锚定压制)。
20
23
  */
21
24
 
22
25
  import { existsSync, readdirSync, readFileSync } from "node:fs";
23
26
  import { resolve } from "node:path";
24
27
 
28
+ import { ImportType, initSync, parse as lexModule } from "es-module-lexer";
29
+
25
30
  /** 一个 ESM 文件的导出面 */
26
31
  export interface EsmExports {
27
32
  /** 静态具名导出(不含 `default`、不含 `export *` 透传的部分) */
@@ -56,22 +61,6 @@ export interface EsmImport {
56
61
  needsDefault: boolean
57
62
  }
58
63
 
59
- /**
60
- * `export { a, b as c }`(本地导出块)与 `export { a, b as c } from "x"`(再导出)
61
- * 的公共部分:取出**对外暴露**的名字(`b as c` 取 `c`)。
62
- */
63
- function exportedNamesFromClause(clause: string): string[] {
64
- return clause
65
- .split(",")
66
- .map(part => part.trim())
67
- .filter(Boolean)
68
- .map((part) => {
69
- const as = part.split(/\s+as\s+/);
70
- return (as[as.length - 1] ?? part).trim();
71
- })
72
- .filter(name => name !== "default");
73
- }
74
-
75
64
  /**
76
65
  * `import { a, b as c }` 中被**请求**的名字。
77
66
  *
@@ -88,51 +77,75 @@ function importedNamesFromClause(clause: string): string[] {
88
77
  .filter(name => name !== "default");
89
78
  }
90
79
 
80
+ /** wasm 实例化只需一次;按需初始化,避免「只是 import 一下」的消费方付启动开销 */
81
+ let lexReady = false;
82
+ function lex(source: string): ReturnType<typeof lexModule> {
83
+ if (!lexReady) {
84
+ initSync();
85
+ lexReady = true;
86
+ }
87
+ return lexModule(source);
88
+ }
89
+
90
+ /** 语句文本;`ss`/`se` 是 lexer 给出的整条语句区间(含分号) */
91
+ function statementAt(source: string, ss: number, se: number): string {
92
+ return source.slice(ss, se);
93
+ }
94
+
95
+ /**
96
+ * 从一条 `import … from "x"` / `export … from "x"` 语句里分出关键字与子句。
97
+ *
98
+ * 语句边界已由 lexer 确定,这里只处理语句内部的切片,因此不再需要行首锚定,
99
+ * 压缩后(`import{a as b}from"react"`)同样成立。
100
+ */
101
+ function clauseOf(statement: string): { keyword: "import" | "export", clause: string } | undefined {
102
+ const head = /^\s*(import|export)\b/.exec(statement);
103
+ if (!head)
104
+ return undefined;
105
+ const rest = statement.slice(head[0].length);
106
+ // `from` 后必须紧跟说明符字符串,才不会误取子句里的同名标识符
107
+ const fromIdx = rest.search(/\bfrom\s*["']/);
108
+ if (fromIdx < 0)
109
+ return undefined;
110
+ return { keyword: head[1] as "import" | "export", clause: rest.slice(0, fromIdx) };
111
+ }
112
+
91
113
  /** 解析产物的静态导出面 */
92
114
  export function parseEsmExports(source: string): EsmExports {
115
+ const [imports, exports] = lex(source);
93
116
  const names = new Set<string>();
117
+ let hasDefault = false;
94
118
 
95
- // 收集全部 export 子句(本地导出块 + 再导出),具名与 default 两种判定共用
96
- const clauses: string[] = [];
97
- for (const m of source.matchAll(/^export\s*\{([^}]*)\}(?:\s*from\s*["'][^"']+["'])?\s*(?:;\s*)?$/gm)) {
98
- clauses.push(m[1] ?? "");
99
- }
100
-
101
- for (const clause of clauses) {
102
- for (const name of exportedNamesFromClause(clause))
103
- names.add(name);
119
+ for (const exp of exports) {
120
+ if (exp.n === "default")
121
+ hasDefault = true;
122
+ else
123
+ names.add(exp.n);
104
124
  }
105
125
 
106
- // 声明式导出:export const/let/var/function/class/async function
107
- for (const m of source.matchAll(/^export\s+(?:declare\s+)?(?:async\s+)?(?:const|let|var|function\*?|class)\s+([A-Za-z_$][\w$]*)/gm)) {
108
- names.add(m[1]!);
109
- }
126
+ // `export * from "x"` 不产生具名导出(静态不可展开),lexer 也不把它列进 exports,
127
+ // 只能从「语句以 export 开头、星号之后不带 as」反推。
128
+ // `export * as ns from "x"` 是有名字的导出(ns),导出面仍可静态确定,不算在内。
129
+ const hasStarReexport = imports.some((imp) => {
130
+ if (imp.t !== ImportType.Static || imp.ss < 0)
131
+ return false;
132
+ return /^export\s*\*(?!\s*as\b)/.test(statementAt(source, imp.ss, imp.se));
133
+ });
110
134
 
111
- // default 有两副面孔:声明式 `export default …`,或子句里的 `x as default`
112
- // (esbuild 产出的正是后者,形如 `rad_shim_xxx_default as default`)
113
- const hasDefaultClause = clauses.some(clause => clause
114
- .split(",")
115
- .map(part => part.trim())
116
- .some(part => /\bas\s+default$/.test(part)));
117
-
118
- return {
119
- names,
120
- hasDefault: /^export\s+default\b/m.test(source) || hasDefaultClause,
121
- hasStarReexport: /^export\s*\*/m.test(source),
122
- };
135
+ return { names, hasDefault, hasStarReexport };
123
136
  }
124
137
 
125
138
  /**
126
139
  * 解析产物消费了哪些说明符的哪些具名导出。
127
140
  *
128
- * 覆盖三种形态(均为 esbuild / vite 非压缩产物的常见写法):
129
- * `import { a, b as c } from "x"`、`import { a } from "x"`(多行)、
130
- * `export { a } from "x"`(再导出同样是消费)。
141
+ * 覆盖:`import { a, b as c } from "x"`、`import X, { a } from "x"`、
142
+ * `export { a } from "x"`(再导出同样是消费)。
131
143
  *
132
144
  * 不覆盖:`import * as ns from "x"`、动态 `import("x")` 后取属性——
133
145
  * 这些都拿不到具名绑定,无从校验。
134
146
  */
135
147
  export function parseEsmImports(source: string): EsmImport[] {
148
+ const [imports] = lex(source);
136
149
  const found = new Map<string, { names: Set<string>, needsDefault: boolean }>();
137
150
 
138
151
  const slot = (specifier: string) => {
@@ -144,25 +157,30 @@ export function parseEsmImports(source: string): EsmImport[] {
144
157
  return entry;
145
158
  };
146
159
 
147
- // 具名导入(允许跨行);用 [^;]*? 兜住语句边界,避免跨越到下一条语句
148
- for (const m of source.matchAll(/^import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/gm)) {
149
- const entry = slot(m[2]!);
150
- for (const name of importedNamesFromClause(m[1] ?? ""))
151
- entry.names.add(name);
152
- }
160
+ for (const imp of imports) {
161
+ // 动态 import / import.meta 不带静态具名绑定
162
+ if (imp.t !== ImportType.Static || imp.ss < 0 || !imp.n)
163
+ continue;
164
+ const parsed = clauseOf(statementAt(source, imp.ss, imp.se));
165
+ if (!parsed)
166
+ continue;
153
167
 
154
- // 具名再导出
155
- for (const m of source.matchAll(/^export\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/gm)) {
156
- const entry = slot(m[2]!);
157
- for (const name of importedNamesFromClause(m[1] ?? ""))
158
- entry.names.add(name);
159
- }
168
+ const entry = slot(imp.n);
169
+ const brace = /\{([^}]*)\}/.exec(parsed.clause);
170
+ if (brace) {
171
+ for (const name of importedNamesFromClause(brace[1] ?? ""))
172
+ entry.names.add(name);
173
+ }
160
174
 
161
- // default 导入:`import X from "x"`(X 非 { 、非 *)
162
- // 不能假定它一定可用——只有 default 的包若 shim 没兜住,浏览器同样是
163
- // "does not provide an export named 'default'"
164
- for (const m of source.matchAll(/^import\s+([A-Za-z_$][\w$]*)\s+from\s*["']([^"']+)["']/gm)) {
165
- slot(m[2]!).needsDefault = true;
175
+ // default 导入:子句去掉 `{…}` 后仍剩标识符(`X` / `X,`)。
176
+ // `* as ns` 是命名空间导入,不消费 default。
177
+ // 不能假定它一定可用——只有 default 的包若 shim 没兜住,浏览器同样是
178
+ // "does not provide an export named 'default'"。
179
+ if (parsed.keyword === "import") {
180
+ const head = parsed.clause.replace(/\{[^}]*\}/g, "").trim();
181
+ if (head && !head.startsWith("*"))
182
+ entry.needsDefault = true;
183
+ }
166
184
  }
167
185
 
168
186
  return [...found].map(([specifier, entry]) => ({
@@ -233,24 +251,88 @@ export function collectDynamicRequires(distDir: string): DynamicRequireHit[] {
233
251
  }
234
252
 
235
253
  /**
236
- * 产物引用的全部裸说明符(三种形态:具名/副作用 import、`import("x")`、再导出)。
254
+ * 产物引用的全部裸说明符(静态/副作用 import、`import("x")`、再导出)。
237
255
  *
238
256
  * 浏览器只能解析 importmap 里登记过的裸说明符,漏一个就是
239
257
  * `Failed to resolve module specifier` 白屏。
240
258
  */
241
259
  export function parseBareSpecifiers(source: string): string[] {
260
+ const [imports] = lex(source);
242
261
  const found = new Set<string>();
243
- // 行首锚定,规避字符串字面量里的假阳性(P3.5 教训)
244
- const pattern = /^import\s*\(\s*["']([^"']+)["']\s*\)|^(?:import|export)\s[^;]*?from\s*["']([^"']+)["']|^import\s+["']([^"']+)["']/gm;
245
- for (const m of source.matchAll(pattern)) {
246
- const spec = m[1] ?? m[2] ?? m[3];
247
- if (!spec || spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("data:") || spec.startsWith("http"))
262
+ // lexer 已给出每条语句的说明符原文;非字符串字面量(`import(x)`)为 undefined,跳过
263
+ for (const imp of imports) {
264
+ const spec = imp.n;
265
+ if (!spec)
266
+ continue;
267
+ if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("data:") || spec.startsWith("http"))
248
268
  continue;
249
269
  found.add(spec);
250
270
  }
251
271
  return [...found];
252
272
  }
253
273
 
274
+ /**
275
+ * 产物的**静态**依赖说明符:`import … from "x"`、副作用 `import "x"`、再导出。
276
+ *
277
+ * 与 {@link parseBareSpecifiers} 的差别有两点,都是为了算「首屏静态闭包」:
278
+ * - **排除动态 `import("x")`**:那是懒加载,不在首屏关键路径上,预取它会白拉;
279
+ * - **保留相对/绝对路径**:闭包要顺着 `/assets/x.hash.js` 继续走,不能只看裸名。
280
+ *
281
+ * 静态图的切分口径与浏览器一致(缓存模块记录前先取完整静态图),所以这里能算出
282
+ * 「浏览器一定会请求的最小集合」。
283
+ */
284
+ export function parseStaticSpecifiers(source: string): string[] {
285
+ const [imports] = lex(source);
286
+ const found = new Set<string>();
287
+ for (const imp of imports) {
288
+ // d === -1 才是静态导入;`import("x")` 的 d 是 `(` 的偏移
289
+ if (imp.d !== -1 || !imp.n)
290
+ continue;
291
+ found.add(imp.n);
292
+ }
293
+ return [...found];
294
+ }
295
+
296
+ /**
297
+ * 首屏静态依赖闭包:从入口出发,顺着**静态** import 一直走到没得走。
298
+ *
299
+ * 这就是浏览器一定会请求的集合(ESM 语义:先取完整静态图再执行),所以可以放心
300
+ * 全量预取;动态 `import()` 不在其中。裸说明符经 importmap 解析,相对路径按当前
301
+ * 文件所在目录拼。
302
+ *
303
+ * `readAsset` 返回 undefined 表示读不到(跨源 CDN 资产、或缺失文件):该 URL 仍算
304
+ * 闭包成员(预取它对浏览器有效),但不再往下展开。
305
+ *
306
+ * 与构建脚本共用同一份实现,是为了让测试能重算一遍对撞产物——两套实现必然漂移。
307
+ */
308
+ export function collectStaticClosure(
309
+ entryUrl: string,
310
+ importmap: Record<string, string>,
311
+ readAsset: (url: string) => string | undefined,
312
+ ): string[] {
313
+ const seen = new Set<string>();
314
+ const queue = [entryUrl];
315
+ while (queue.length) {
316
+ const url = queue.shift()!;
317
+ if (seen.has(url))
318
+ continue;
319
+ seen.add(url);
320
+ const source = readAsset(url);
321
+ if (source === undefined)
322
+ continue;
323
+ for (const spec of parseStaticSpecifiers(source)) {
324
+ const target = spec.startsWith("/")
325
+ ? spec
326
+ : spec.startsWith(".")
327
+ ? resolve("/", url.slice(0, url.lastIndexOf("/") + 1), spec)
328
+ : importmap[spec];
329
+ if (target)
330
+ queue.push(target);
331
+ }
332
+ }
333
+ return [...seen];
334
+ }
335
+
254
336
  /** 一处「资产引用了 importmap 未登记的裸说明符」 */
255
337
  export interface UnresolvedSpecifier {
256
338
  file: string
package/src/install.ts CHANGED
@@ -254,6 +254,11 @@ export interface InstallOptions {
254
254
  locale?: string
255
255
  /** REG-R9②:从指定 registry 源安装(白名单在服务端;旧站点 API < 0.2.0 不支持 → exit 1) */
256
256
  registry?: string
257
+ /**
258
+ * 强制重装配(oj 0.1.123+ reloadConfig(force)):旁路「unchanged=已是最新装配」幂等短路,
259
+ * 强制宿主重做一次装配。运维显式重装配用;缺省 false。
260
+ */
261
+ force?: boolean
257
262
  /** 测试注入;缺省 globalThis.fetch */
258
263
  fetchFn?: typeof fetch
259
264
  /** 测试注入;缺省真睡 */
@@ -269,6 +274,8 @@ export async function installCommand(target: string, opts: InstallOptions = {}):
269
274
  const fetchFn = opts.fetchFn ?? globalThis.fetch;
270
275
  const { base, user, pass } = resolveSite(opts);
271
276
  const token = await login(fetchFn, base, user, pass);
277
+ // force 透传(oj 0.1.123+ reloadConfig(force) 逃逸舱):仅运维显式重装配时用;常规安装走锁 diff
278
+ const forceQ = opts.force === true ? "?force=1" : "";
272
279
 
273
280
  let response: { task_id: string };
274
281
  if (opts.registry !== undefined) {
@@ -282,13 +289,13 @@ export async function installCommand(target: string, opts: InstallOptions = {}):
282
289
  if (!isSiteApiAtLeast(meta.apiVersion, "0.2.0"))
283
290
  throw new Error(`[ojm] 目标站点平台 API 版本过低(需 ≥ 0.2.0 才支持 --registry 源安装,实为 ${meta.apiVersion ?? "未知"})——请升级目标站点的 oj-module,或改用 CDN/file 源`);
284
291
  const registryOrigin = originOf(src.url) ?? src.url;
285
- response = await callApi<{ task_id: string }>(fetchFn, base, token, "POST", "/api/_platform/market/install", {
292
+ response = await callApi<{ task_id: string }>(fetchFn, base, token, "POST", `/api/_platform/market/install${forceQ}`, {
286
293
  name: slug,
287
294
  version: opts.version ?? version,
288
295
  source: "registry",
289
296
  registry: registryOrigin,
290
297
  });
291
- console.log(`[ojm] 已从 registry 源提交安装任务(源 ${registryOrigin},物理名 ${slug})`);
298
+ console.log(`[ojm] 已从 registry 源提交安装任务(源 ${registryOrigin},物理名 ${slug}${opts.force === true ? ",强制重装配" : ""})`);
292
299
  }
293
300
  else {
294
301
  const parsed = parseTarget(target);
@@ -314,7 +321,7 @@ export async function installCommand(target: string, opts: InstallOptions = {}):
314
321
  else {
315
322
  console.log(`[ojm] 未发现签名侧车(${path.basename(sigPath)})——宿主验签硬 fail-closed,未签名包将被拒(sig_missing);签名方法见 devkit「签名与信任」`);
316
323
  }
317
- const res = await fetchFn(`${base}/api/_platform/market/install`, {
324
+ const res = await fetchFn(`${base}/api/_platform/market/install${forceQ}`, {
318
325
  method: "POST",
319
326
  headers: { authorization: `Bearer ${token}`, ...tenantHeaders() },
320
327
  body: form,
@@ -324,7 +331,7 @@ export async function installCommand(target: string, opts: InstallOptions = {}):
324
331
  else {
325
332
  if (opts.name !== undefined && opts.name !== parsed.name)
326
333
  throw new Error(`[ojm] --name「${opts.name}」与目标「${parsed.name}」不一致——请二选一`);
327
- response = await callApi<{ task_id: string }>(fetchFn, base, token, "POST", "/api/_platform/market/install", {
334
+ response = await callApi<{ task_id: string }>(fetchFn, base, token, "POST", `/api/_platform/market/install${forceQ}`, {
328
335
  name: parsed.name,
329
336
  version: opts.version ?? parsed.version,
330
337
  source: "cdn",
package/src/keygen.ts CHANGED
@@ -10,6 +10,7 @@ import path from "node:path";
10
10
  import process from "node:process";
11
11
  import { generateKeyPair } from "./lifecycle/keygen";
12
12
  import { fingerprintOfPublicKey } from "./lifecycle/sign";
13
+ import { DEFAULT_SITE_URL, tenantHeaders, unwrapEnvelope } from "./site";
13
14
 
14
15
  export interface KeygenOptions {
15
16
  /** 发布方 key_id(C2-37:<publisher>.<id> 全小写,禁裸通用名) */
@@ -67,3 +68,56 @@ export function keygenCommand(opts: KeygenOptions = {}): KeygenResult {
67
68
  console.log(`[ojm] 下一步:ojm sign <pkg>.tgz --id ${keyId}(或 --key ${privateKeyPath} --id ${keyId})`);
68
69
  return { keyId, publicKeyHex: kp.publicKeyHex, fingerprint, dir, privateKeyPath, publicKeyPath };
69
70
  }
71
+
72
+ /** 兑换结果:服务端下发身份(CLI 零推导——编码规则只在 templates 侧一份) */
73
+ export interface ExchangedIdentity {
74
+ key_id: string
75
+ product: string
76
+ username: string
77
+ display_name: string | null
78
+ }
79
+
80
+ export interface ExchangeOptions {
81
+ /** 站点地址;flag > OJM_SITE_URL > DEFAULT_SITE_URL */
82
+ siteUrl?: string
83
+ /** 测试注入 */
84
+ fetchFn?: typeof fetch
85
+ }
86
+
87
+ /**
88
+ * 用一次性发布凭证换身份(匿名端点,不需要账号密码)。
89
+ *
90
+ * 只做「取回服务端算好的 key_id」——不做任何编码推导(规则漂移比多一次网络请求贵得多),
91
+ * 但**必须**校验拿回的 key_id 形状:它马上会被拼进 `~/.ojm/keys/<key_id>`,
92
+ * 一个被中间人改成 `../../x` 的响应就是任意路径写入。失败一律不落盘(调用方先兑换后生成)。
93
+ */
94
+ export async function exchangePublishCredential(token: string, opts: ExchangeOptions = {}): Promise<ExchangedIdentity> {
95
+ const t = token.trim();
96
+ // 形状先挡:不是 64 位 hex 的输入连请求都不必发(与页面生成规则一致)
97
+ if (!/^[0-9a-f]{64}$/i.test(t))
98
+ throw new Error("[ojm] 发布凭证格式不对(应为 64 位十六进制):请在站点「发布管理」页复制完整凭证");
99
+
100
+ const base = (opts.siteUrl ?? process.env.OJM_SITE_URL ?? DEFAULT_SITE_URL).replace(/\/+$/, "");
101
+ const fetchFn = opts.fetchFn ?? fetch;
102
+ let res: Response;
103
+ try {
104
+ res = await fetchFn(`${base}/api/publish/credential/exchange`, {
105
+ method: "POST",
106
+ headers: { "content-type": "application/json", ...tenantHeaders() },
107
+ body: JSON.stringify({ token: t }),
108
+ });
109
+ }
110
+ catch (e) {
111
+ throw new Error(`[ojm] 无法连接站点 ${base}:${e instanceof Error ? e.message : String(e)}——请确认站点地址(--site-url / OJM_SITE_URL)与网络`);
112
+ }
113
+ const data = await unwrapEnvelope(res) as Partial<ExchangedIdentity> | null;
114
+ const keyId = String(data?.key_id ?? "");
115
+ if (!isValidKeyId(keyId))
116
+ throw new Error(`[ojm] 站点返回的 key_id 非法(C2-37):「${keyId}」——已中止,未写入任何密钥(请核对站点版本)`);
117
+ return {
118
+ key_id: keyId,
119
+ product: String(data?.product ?? ""),
120
+ username: String(data?.username ?? ""),
121
+ display_name: data?.display_name == null ? null : String(data.display_name),
122
+ };
123
+ }
@@ -53,8 +53,8 @@ function makePrebuiltArtifacts(): void {
53
53
  const apiDist = join(project, "dist/api/demo-solution-0.1.0");
54
54
  mkdirSync(join(apiDist, "ping"), { recursive: true });
55
55
  mkdirSync(join(apiDist, "migrations"), { recursive: true });
56
- writeFileSync(join(apiDist, "manifest.yaml"), "name: demo-solution\ndesc: manifest.yaml 契约全字段合法夹具\nversion: 0.1.0\ntables:\n - note\n");
57
- writeFileSync(join(apiDist, "schema.yaml"), "tables:\n note: {}\n");
56
+ writeFileSync(join(apiDist, "manifest.yaml"), "name: demo-solution\ndesc: manifest.yaml 契约全字段合法夹具\nversion: 0.1.0\ntables:\n - demo_solution_note\n");
57
+ writeFileSync(join(apiDist, "schema.yaml"), "prefix: demo_solution_\ntables:\n demo_solution_note: {}\n");
58
58
  writeFileSync(join(apiDist, "routes.js"), "export {};\n");
59
59
  writeFileSync(join(apiDist, "ping/api.js"), "export async function ping() {}\n");
60
60
  // 站点构建产物(dist/web/modules/<name>/<version>/entry.js + css,对应 web.entry/css 声明)
@@ -317,11 +317,29 @@ describe("buildBundle · 校验先于打包(fail-fast)", () => {
317
317
 
318
318
  it("c2-12a:api.tables 与 schema.yaml 表集不一致 → 构建期拒绝", async () => {
319
319
  const m = readManifest();
320
- m.api.tables = ["note", "ghost"];
320
+ m.api.tables = ["demo_solution_note", "ghost"];
321
321
  writeManifest(m);
322
322
  await expect(pack()).rejects.toThrow(/C2-12a/);
323
323
  });
324
324
 
325
+ it("c2-12c:schema.yaml 缺 prefix / 表名未带方案前缀 → 构建期拒绝", async () => {
326
+ const schemaPath = join(project, "api/src/demo-solution/schema.yaml");
327
+ writeFileSync(schemaPath, "tables:\n demo_solution_note: {}\n");
328
+ await expect(pack()).rejects.toThrow(/C2-12c.*prefix/);
329
+
330
+ writeFileSync(schemaPath, "prefix: demo_solution_\ntables:\n note: {}\n");
331
+ await expect(pack()).rejects.toThrow(/C2-12c.*demo_solution_note/);
332
+ });
333
+
334
+ it("c2-12c:prefix 落在第一方保留段 → 构建期拒绝", async () => {
335
+ const m = readManifest();
336
+ m.api.tables = ["oj_note"];
337
+ writeManifest(m);
338
+ const schemaPath = join(project, "api/src/demo-solution/schema.yaml");
339
+ writeFileSync(schemaPath, "prefix: oj_\ntables:\n oj_note: {}\n");
340
+ await expect(pack()).rejects.toThrow(/C2-12c.*保留段/);
341
+ });
342
+
325
343
  it("c2-25:locales 缺引用 key → 构建期 fail-fast", async () => {
326
344
  const m = readManifest();
327
345
  m.roles[0].title_key = "demo-solution:role.absent";
@@ -6,4 +6,4 @@ name: demo-solution
6
6
  desc: manifest.yaml 契约全字段合法夹具
7
7
  version: 0.1.0
8
8
  tables:
9
- - note
9
+ - demo_solution_note
@@ -1,5 +1,5 @@
1
- -- 0001__init:建 note 表(与 schema.yaml 列集一致;oj migrate 按 seq 升序执行)
2
- CREATE TABLE IF NOT EXISTS note (
1
+ -- 0001__init:建 demo_solution_note 表(与 schema.yaml 列集一致;oj migrate 按 seq 升序执行)
2
+ CREATE TABLE IF NOT EXISTS demo_solution_note (
3
3
  id INTEGER PRIMARY KEY AUTOINCREMENT,
4
4
  title TEXT NOT NULL,
5
5
  create_time INTEGER NOT NULL
@@ -1,7 +1,10 @@
1
1
  # 声明式表结构(形态对齐 apps/playground-oj/api/src/demo/schema.yaml)。
2
- # 契约 C2-12:bundle manifest 的 api.tables 必须与本表 keys 一致(oj 冗余校验 fail-fast)。
2
+ # C2-12c:prefix 必须 = 模块名 kebab→snake 再补尾部 `_`(demo-solution → demo_solution_),
3
+ # 且每张表名以它开头——表名全库共享,前缀把归属编码进名字,避免方案之间撞表。
4
+ prefix: demo_solution_
5
+ # 契约 C2-12a:bundle manifest 的 api.tables 必须与本表 keys 一致(oj 冗余校验 fail-fast)。
3
6
  tables:
4
- note:
7
+ demo_solution_note:
5
8
  pk: id
6
9
  columns:
7
10
  id: {type: integer, autoincrement: true}