@oj-module/cli 0.2.66 → 0.2.68

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 (200) hide show
  1. package/bin/.oj-version +1 -0
  2. package/bin/oj +4 -0
  3. package/devkit/README.md +15 -7
  4. package/devkit/SKILL.md +38 -4
  5. package/devkit/framework-dev.md +10 -4
  6. package/devkit/manual.md +146 -24
  7. package/devkit/menu-rules.md +19 -4
  8. package/devkit/module-dev-and-cdn-playground.md +4 -0
  9. package/devkit/publish-walkthrough.md +132 -37
  10. package/devkit/wecom-internal-app.md +8 -3
  11. package/devkit/wecom-multi-corp-links.md +163 -0
  12. package/package.json +2 -2
  13. package/shell-dist/assets/{index.i0kqbxp050.js → index.6alqyx6ae0.js} +21 -1
  14. package/shell-dist/assets/{runtime.1xfsxcajj0.js → runtime.1xzahckt10.js} +4863 -4847
  15. package/shell-dist/index.html +4 -4
  16. package/shell-dist/versions.json +3 -3
  17. package/src/__tests__/cli.test.ts +31 -0
  18. package/src/__tests__/new-scaffold.test.ts +13 -0
  19. package/src/__tests__/publish-keygen.test.ts +157 -0
  20. package/src/__tests__/templates-migration-schema-parity.test.ts +540 -0
  21. package/src/__tests__/templates-schema-columns.test.ts +15 -2
  22. package/src/__tests__/templates-tenant-scope.test.ts +6 -2
  23. package/src/build/module-def.ts +1 -0
  24. package/src/builtin.ts +3 -2
  25. package/src/bundle.ts +8 -3
  26. package/src/cli.ts +21 -5
  27. package/src/dev.ts +15 -10
  28. package/src/init.ts +47 -41
  29. package/src/install.ts +11 -4
  30. package/src/keygen.ts +54 -0
  31. package/src/lifecycle/__tests__/bundle.test.ts +21 -3
  32. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +1 -1
  33. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +2 -2
  34. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +5 -2
  35. package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +1 -1
  36. package/src/lifecycle/__tests__/manifest-contract.test.ts +75 -3
  37. package/src/lifecycle/__tests__/smoke.test.ts +2 -2
  38. package/src/manifest-contract.ts +91 -8
  39. package/src/market/bootstrap.ts +26 -2
  40. package/src/market/command.ts +1 -0
  41. package/src/market/publish.ts +8 -1
  42. package/src/market/walkthrough.ts +24 -8
  43. package/src/new.ts +2 -0
  44. package/src/scaffold-guard.ts +227 -0
  45. package/src/site.ts +2 -0
  46. package/src/usage.ts +13 -9
  47. package/src/vendor.ts +5 -1
  48. package/templates/config.yaml +4 -2
  49. package/templates/init.include.yaml +10 -3
  50. package/templates/openapi.json +460 -1
  51. package/templates/src/README.md +37 -0
  52. package/templates/src/app/api/_platform/_lifecycle/compensate.ts +136 -0
  53. package/templates/src/app/api/_platform/_lifecycle/installer.ts +78 -25
  54. package/templates/src/app/api/_platform/_lifecycle/lock.ts +23 -0
  55. package/templates/src/app/api/_platform/_lifecycle/steps/deploy-web.ts +28 -0
  56. package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +46 -0
  57. package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +100 -36
  58. package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +7 -15
  59. package/templates/src/app/api/_platform/_lifecycle/text-edit.ts +71 -19
  60. package/templates/src/app/api/_platform/_lifecycle/types.ts +26 -2
  61. package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +93 -14
  62. package/templates/src/app/api/_platform/_shared/service/domain/settings.ts +8 -4
  63. package/templates/src/app/api/_platform/_shared/service/domain/solution-perms.ts +25 -10
  64. package/templates/src/app/api/_platform/manifest.yaml +1 -0
  65. package/templates/src/app/api/_platform/market/api.ts +235 -10
  66. package/templates/src/app/api/_platform/migrations/0001__baseline.mysql.sql +178 -0
  67. package/templates/src/app/api/_platform/migrations/0001__baseline.pg.sql +173 -0
  68. package/templates/src/app/api/_platform/migrations/0001__baseline.sql +188 -0
  69. package/templates/src/app/api/_platform/migrations/0002__market_release.mysql.sql +17 -0
  70. package/templates/src/app/api/_platform/migrations/0002__market_release.pg.sql +16 -0
  71. package/templates/src/app/api/_platform/migrations/0002__market_release.sql +33 -0
  72. package/templates/src/app/api/_platform/schema.yaml +18 -0
  73. package/templates/src/app/api/_platform/seed.mysql.sql +10 -0
  74. package/templates/src/app/api/_platform/seed.pg.sql +10 -0
  75. package/templates/src/app/api/auth/_shared/wecom/client.ts +24 -4
  76. package/templates/src/app/api/home/migrations/0001__create_home_tables.mysql.sql +14 -0
  77. package/templates/src/app/api/home/migrations/0001__create_home_tables.pg.sql +14 -0
  78. package/templates/src/app/api/home/migrations/0002__tenant.pg.sql +4 -0
  79. package/templates/src/app/api/home/seed.mysql.sql +71 -0
  80. package/templates/src/app/api/home/seed.pg.sql +71 -0
  81. package/templates/src/app/api/notification/_shared/notify.ts +6 -6
  82. package/templates/src/app/api/notification/migrations/0001__create_notifications.mysql.sql +11 -0
  83. package/templates/src/app/api/notification/migrations/0001__create_notifications.pg.sql +11 -0
  84. package/templates/src/app/api/notification/migrations/0002__notifications_user.mysql.sql +8 -0
  85. package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.mysql.sql +21 -0
  86. package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.pg.sql +22 -0
  87. package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.mysql.sql +10 -0
  88. package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.pg.sql +11 -0
  89. package/templates/src/app/api/notification/migrations/0005__tenant.pg.sql +6 -0
  90. package/templates/src/app/api/notification/seed.mysql.sql +8 -0
  91. package/templates/src/app/api/notification/seed.pg.sql +11 -0
  92. package/templates/src/app/api/publish/_shared/identity.ts +69 -0
  93. package/templates/src/app/api/publish/api.ts +349 -0
  94. package/templates/src/app/api/publish/manifest.yaml +8 -0
  95. package/templates/src/app/api/publish/migrations/0001__publish_credential.mysql.sql +23 -0
  96. package/templates/src/app/api/publish/migrations/0001__publish_credential.pg.sql +23 -0
  97. package/templates/src/app/api/publish/migrations/0001__publish_credential.sql +39 -0
  98. package/templates/src/app/api/publish/schema.yaml +33 -0
  99. package/templates/src/app/api/system/login-methods/api.ts +2 -1
  100. package/templates/src/app/web/_platform/__client/api.schemas.ts +15 -0
  101. package/templates/src/app/web/_platform/__client/index.ts +25 -1
  102. package/templates/src/app/web/entry.ts +9 -5
  103. package/templates/src/app/web/home/routes.ts +5 -0
  104. package/templates/src/app/web/login/locales/en-US.json +7 -1
  105. package/templates/src/app/web/login/locales/zh-CN.json +7 -1
  106. package/templates/src/app/web/login/pages/login.tsx +79 -86
  107. package/templates/src/app/web/login/routes.ts +54 -1
  108. package/templates/src/app/web/personal-center/locales/en-US.json +1 -0
  109. package/templates/src/app/web/personal-center/locales/zh-CN.json +1 -0
  110. package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +5 -0
  111. package/templates/src/app/web/system/__client/api.schemas.ts +2 -1
  112. package/templates/src/app/web/system/locales/en-US.json +1 -8
  113. package/templates/src/app/web/system/locales/zh-CN.json +1 -8
  114. package/templates/src/app/web/system/pages/settings/index.tsx +17 -4
  115. package/templates/src/app/web/system/routes.ts +0 -6
  116. package/templates/src/oj-market/web/__tests__/CardGrid.test.tsx +33 -0
  117. package/templates/src/oj-market/web/__tests__/DetailDrawer.test.tsx +69 -0
  118. package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +7 -1
  119. package/templates/src/oj-market/web/api/client.ts +3 -0
  120. package/templates/src/oj-market/web/api/defaultClient.ts +5 -0
  121. package/templates/src/oj-market/web/components/CardItem.tsx +44 -1
  122. package/templates/src/oj-market/web/components/DetailDrawer.tsx +17 -1
  123. package/templates/src/oj-market/web/entry.ts +31 -2
  124. package/templates/src/oj-market/web/locales/en-US.json +5 -1
  125. package/templates/src/oj-market/web/locales/zh-CN.json +5 -1
  126. package/templates/src/oj-market/web/publish/__client/api.schemas.ts +61 -0
  127. package/templates/src/oj-market/web/publish/__client/index.ts +164 -0
  128. package/templates/src/oj-market/web/publish/locales/en-US.json +80 -0
  129. package/templates/src/oj-market/web/publish/locales/zh-CN.json +80 -0
  130. package/templates/src/oj-market/web/publish/pages/history.tsx +112 -0
  131. package/templates/src/oj-market/web/publish/pages/index.tsx +196 -0
  132. package/templates/src/oj-market/web/publish/pages/model.ts +52 -0
  133. package/templates/src/oj-market/web/publish/pages/token-modal.tsx +82 -0
  134. package/templates/src/oj-market/web/publish/routes.ts +41 -0
  135. package/templates/src/oj-market/web/types.ts +30 -0
  136. package/templates/tests/lifecycle/compensate.spec.ts +207 -0
  137. package/templates/tests/lifecycle/deploy-smoke.spec.ts +38 -14
  138. package/templates/tests/lifecycle/installer.spec.ts +28 -9
  139. package/templates/tests/lifecycle/market-api.spec.ts +164 -12
  140. package/templates/tests/lifecycle/preflight.spec.ts +68 -0
  141. package/templates/tests/lifecycle/register.spec.ts +22 -0
  142. package/templates/tests/lifecycle/role-title-translate.spec.ts +4 -34
  143. package/templates/tests/lifecycle/text-edit.spec.ts +45 -27
  144. package/templates/tests/lifecycle/uninstall.spec.ts +36 -0
  145. package/templates/tests/publish-credential.spec.ts +354 -0
  146. package/templates/tests/publish-identity.spec.ts +44 -0
  147. package/templates/tests/rbac-solution-perms.spec.ts +48 -3
  148. package/templates/tests/wecom-client-timeout.spec.ts +33 -3
  149. package/templates/tests/wecom-landing.spec.ts +63 -1
  150. package/templates/tsconfig.paths.json +1 -0
  151. package/templates/web.config.ts +8 -4
  152. package/vendor/host-versions.json +3 -3
  153. package/templates/seed.mysql.sql +0 -6
  154. package/templates/seed.pg.sql +0 -6
  155. package/templates/src/app/api/_platform/migrations/0001__create_users.mysql.sql +0 -9
  156. package/templates/src/app/api/_platform/migrations/0001__create_users.pg.sql +0 -9
  157. package/templates/src/app/api/_platform/migrations/0001__create_users.sql +0 -7
  158. package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.mysql.sql +0 -3
  159. package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.sql +0 -2
  160. package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.mysql.sql +0 -5
  161. package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.sql +0 -5
  162. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.mysql.sql +0 -70
  163. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.pg.sql +0 -67
  164. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.sql +0 -67
  165. package/templates/src/app/api/_platform/migrations/0005__users_must_change.mysql.sql +0 -3
  166. package/templates/src/app/api/_platform/migrations/0005__users_must_change.sql +0 -2
  167. package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.mysql.sql +0 -3
  168. package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.sql +0 -3
  169. package/templates/src/app/api/_platform/migrations/0007__users_email.sql +0 -3
  170. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.mysql.sql +0 -10
  171. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.pg.sql +0 -10
  172. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.sql +0 -11
  173. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.mysql.sql +0 -12
  174. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.pg.sql +0 -12
  175. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.sql +0 -13
  176. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.mysql.sql +0 -17
  177. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.pg.sql +0 -16
  178. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.sql +0 -18
  179. package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.mysql.sql +0 -2
  180. package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.sql +0 -4
  181. package/templates/src/app/api/_platform/migrations/0012__market_history.mysql.sql +0 -13
  182. package/templates/src/app/api/_platform/migrations/0012__market_history.pg.sql +0 -12
  183. package/templates/src/app/api/_platform/migrations/0012__market_history.sql +0 -14
  184. package/templates/src/app/api/_platform/migrations/0013__users_profile.sql +0 -4
  185. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.mysql.sql +0 -9
  186. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.pg.sql +0 -7
  187. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.sql +0 -8
  188. package/templates/src/app/api/_platform/migrations/0015__tenant.mysql.sql +0 -12
  189. package/templates/src/app/api/_platform/migrations/0015__tenant.pg.sql +0 -11
  190. package/templates/src/app/api/_platform/migrations/0015__tenant.sql +0 -44
  191. package/templates/src/app/api/_platform/migrations/0016__org.mysql.sql +0 -41
  192. package/templates/src/app/api/_platform/migrations/0016__org.pg.sql +0 -40
  193. package/templates/src/app/api/_platform/migrations/0016__org.sql +0 -62
  194. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +0 -7
  195. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +0 -4
  196. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +0 -8
  197. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +0 -3
  198. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +0 -3
  199. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +0 -8
  200. package/templates/src/app/web/system/pages/login-methods/index.tsx +0 -30
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" href="/favicon.ico" />
7
7
  <title>React Antd Admin · Shell</title>
8
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-cmxX2YjsUdU97XzeijFvTw=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
9
- <script type="importmap" nonce="cmxX2YjsUdU97XzeijFvTw==">{
8
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-36CAd5UpXCtQqxASbhUFfw=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
9
+ <script type="importmap" nonce="36CAd5UpXCtQqxASbhUFfw==">{
10
10
  "imports": {
11
11
  "react": "/assets/react.1ew63mwts0.js",
12
12
  "react/jsx-runtime": "/assets/jsx-runtime.h40xu4o5b0.js",
@@ -16,7 +16,7 @@
16
16
  "react-router": "/assets/react-router.2h9rebcv50.js",
17
17
  "react-router/dom": "/assets/react-router-dom.3c6zld8h40.js",
18
18
  "@tanstack/react-query": "/assets/react-query.3rln45ow00.js",
19
- "@oj-module/runtime": "/assets/runtime.1xfsxcajj0.js",
19
+ "@oj-module/runtime": "/assets/runtime.1xzahckt10.js",
20
20
  "@oj-module/runtime/contract": "/assets/contract.3h327o3mj0.js",
21
21
  "@oj-module/runtime/contract/errors": "/assets/contract-errors.2o17vz9u50.js",
22
22
  "@plane/kit": "/assets/kit.37kr8ui390.js",
@@ -79,7 +79,7 @@
79
79
  .boot-skeleton:after{content:"";width:18px;height:18px;margin-left:8px;border:2px solid #d9d9d9;border-top-color:#1677ff;border-radius:50%;animation:bs-spin .8s linear infinite}
80
80
  @keyframes bs-spin{to{transform:rotate(360deg)}}
81
81
  </style>
82
- <script nonce="cmxX2YjsUdU97XzeijFvTw==" data-ojm-preload="/assets/cssinjs.atgmhan7m0.js,/assets/pro-components.2kyot1vcl0.js,/assets/runtime.1xfsxcajj0.js,/assets/react-query.3rln45ow00.js,/assets/antd.uw1coq15l0.js,/assets/react.1ew63mwts0.js,/assets/react-dom-client.ick0v05ob0.js,/assets/react-i18next.auhvjhqv00.js,/assets/react-router.2h9rebcv50.js,/assets/jsx-runtime.h40xu4o5b0.js,/assets/react-dom.2so9uxg3f0.js,/assets/clsx.jotilrt2e0.js,/assets/antd-es-locale-zh_CN.1gjjsvxbf0.js,/assets/dayjs.pa60l6wjo0.js,/assets/ojm-dayjs-locale-zh-cn.3vr3beiyp0.js,/assets/use-sync-external-store-shim.1nz1pi9bb0.js,/assets/icons.159wjwals0.js,/assets/dayjs-plugin-quarterOfYear.2oqo1lcx80.js,/assets/dayjs-plugin-customParseFormat.13xhghv1d0.js,/assets/dayjs-plugin-advancedFormat.3vsyd2eis0.js,/assets/dayjs-plugin-isoWeek.1fn1tw82p0.js,/assets/dayjs-plugin-localeData.2rwbjp9nk0.js,/assets/dayjs-plugin-weekday.2d56a4tml0.js,/assets/dayjs-plugin-weekOfYear.gvn9iph080.js,/assets/dayjs-plugin-relativeTime.2pqs3et4y0.js,/assets/dnd-kit-core.1xyreig6r0.js,/assets/dnd-kit-utilities.1umw4xphb0.js,/assets/dnd-kit-sortable.s94qciuft0.js,/assets/ky.1cgch7gu60.js,/assets/zustand.3gjgzlnqo0.js,/assets/zustand-middleware.1xaxjueme0.js,/assets/react-error-boundary.2dncdfj3b0.js,/assets/antd-locale-en_US.561tozx860.js,/assets/antd-locale-zh_CN.1cmpmgzh70.js,/assets/i18next.33zqofapj0.js,/assets/tailwind-merge.125ehiqjw0.js,/assets/nprogress.2nz76n9ny0.js,/assets/nprogress-css.1cq2pzkre0.js,/assets/react-jss.1ei6dr9e00.js,/assets/ahooks.2l850ymmh0.js,/assets/spin-delay.yjeyj6iwp0.js,/assets/simplebar-react.2g739uvai0.js,/assets/keepalive-for-react.xenu5vam70.js,/assets/zustand-shallow.6cz0yyv9x0.js,/assets/contract-errors.2o17vz9u50.js,/assets/antd-img-crop.196rkty480.js,/assets/icons-es-components-Context.365qmflwg0.js,/assets/dayjs-plugin-weekYear.2u78l3ex10.js,/assets/zustand-vanilla.1mo3ka3g30.js,/assets/zustand-react.9fsl9brxy0.js,/assets/zustand-vanilla-shallow.3424monv50.js,/assets/zustand-react-shallow.pybayv1hx0.js,/assets/ojm-antd-es-modal.bf3tgdh8v0.js,/assets/ojm-antd-es-upload.13gx45e0g0.js,/assets/ojm-antd-es-button.2uriphio50.js,/assets/ojm-antd-es-slider.xyvptuhid0.js" data-ojm-entry="/assets/index.i0kqbxp050.js">(function(){var cs=document.currentScript;try{var ua=(navigator.userAgent||"").toLowerCase(),p=location.pathname;while(p.length>1&&p.charAt(p.length-1)==="/")p=p.slice(0,-1);if(ua.indexOf("wxwork")>=0&&p.slice(-6)==="/login"&&!new URLSearchParams(location.search).get("wecom_ticket")){var a=(typeof window.__OJ_API_BASE__==="string"&&window.__OJ_API_BASE__.trim())||"/api";while(a.length>1&&a.charAt(a.length-1)==="/")a=a.slice(0,-1);location.replace(a+"/auth/wecom/login");return}}catch(e){}var pl=cs.getAttribute("data-ojm-preload");if(pl){var ps=pl.split(",");for(var i=0;i<ps.length;i++){var l=document.createElement("link");l.rel="modulepreload";l.href=ps[i];document.head.appendChild(l);}}var s=document.createElement("script");s.type="module";s.src=cs.getAttribute("data-ojm-entry");document.head.appendChild(s);})();</script>
82
+ <script nonce="36CAd5UpXCtQqxASbhUFfw==" data-ojm-preload="/assets/cssinjs.atgmhan7m0.js,/assets/pro-components.2kyot1vcl0.js,/assets/runtime.1xzahckt10.js,/assets/react-query.3rln45ow00.js,/assets/antd.uw1coq15l0.js,/assets/react.1ew63mwts0.js,/assets/react-dom-client.ick0v05ob0.js,/assets/react-i18next.auhvjhqv00.js,/assets/react-router.2h9rebcv50.js,/assets/jsx-runtime.h40xu4o5b0.js,/assets/react-dom.2so9uxg3f0.js,/assets/clsx.jotilrt2e0.js,/assets/antd-es-locale-zh_CN.1gjjsvxbf0.js,/assets/dayjs.pa60l6wjo0.js,/assets/ojm-dayjs-locale-zh-cn.3vr3beiyp0.js,/assets/use-sync-external-store-shim.1nz1pi9bb0.js,/assets/icons.159wjwals0.js,/assets/dayjs-plugin-quarterOfYear.2oqo1lcx80.js,/assets/dayjs-plugin-customParseFormat.13xhghv1d0.js,/assets/dayjs-plugin-advancedFormat.3vsyd2eis0.js,/assets/dayjs-plugin-isoWeek.1fn1tw82p0.js,/assets/dayjs-plugin-localeData.2rwbjp9nk0.js,/assets/dayjs-plugin-weekday.2d56a4tml0.js,/assets/dayjs-plugin-weekOfYear.gvn9iph080.js,/assets/dayjs-plugin-relativeTime.2pqs3et4y0.js,/assets/dnd-kit-core.1xyreig6r0.js,/assets/dnd-kit-utilities.1umw4xphb0.js,/assets/dnd-kit-sortable.s94qciuft0.js,/assets/ky.1cgch7gu60.js,/assets/zustand.3gjgzlnqo0.js,/assets/zustand-middleware.1xaxjueme0.js,/assets/react-error-boundary.2dncdfj3b0.js,/assets/antd-locale-en_US.561tozx860.js,/assets/antd-locale-zh_CN.1cmpmgzh70.js,/assets/i18next.33zqofapj0.js,/assets/tailwind-merge.125ehiqjw0.js,/assets/nprogress.2nz76n9ny0.js,/assets/nprogress-css.1cq2pzkre0.js,/assets/react-jss.1ei6dr9e00.js,/assets/ahooks.2l850ymmh0.js,/assets/spin-delay.yjeyj6iwp0.js,/assets/simplebar-react.2g739uvai0.js,/assets/keepalive-for-react.xenu5vam70.js,/assets/zustand-shallow.6cz0yyv9x0.js,/assets/contract-errors.2o17vz9u50.js,/assets/antd-img-crop.196rkty480.js,/assets/icons-es-components-Context.365qmflwg0.js,/assets/dayjs-plugin-weekYear.2u78l3ex10.js,/assets/zustand-vanilla.1mo3ka3g30.js,/assets/zustand-react.9fsl9brxy0.js,/assets/zustand-vanilla-shallow.3424monv50.js,/assets/zustand-react-shallow.pybayv1hx0.js,/assets/ojm-antd-es-modal.bf3tgdh8v0.js,/assets/ojm-antd-es-upload.13gx45e0g0.js,/assets/ojm-antd-es-button.2uriphio50.js,/assets/ojm-antd-es-slider.xyvptuhid0.js" data-ojm-entry="/assets/index.6alqyx6ae0.js">(function(){var cs=document.currentScript;try{var ua=(navigator.userAgent||"").toLowerCase(),p=location.pathname;while(p.length>1&&p.charAt(p.length-1)==="/")p=p.slice(0,-1);if(ua.indexOf("wxwork")>=0&&p.slice(-6)==="/login"&&!new URLSearchParams(location.search).get("wecom_ticket")){var a=(typeof window.__OJ_API_BASE__==="string"&&window.__OJ_API_BASE__.trim())||"/api";while(a.length>1&&a.charAt(a.length-1)==="/")a=a.slice(0,-1);var q=new URLSearchParams(location.search),w=new URLSearchParams(),k=["corp","agent"];for(var i=0;i<k.length;i++){var v=(q.get(k[i])||"").trim();if(v)w.set(k[i],v)}var qs=w.toString();location.replace(a+"/auth/wecom/login"+(qs?"?"+qs:""));return}}catch(e){}var pl=cs.getAttribute("data-ojm-preload");if(pl){var ps=pl.split(",");for(var i=0;i<ps.length;i++){var l=document.createElement("link");l.rel="modulepreload";l.href=ps[i];document.head.appendChild(l);}}var s=document.createElement("script");s.type="module";s.src=cs.getAttribute("data-ojm-entry");document.head.appendChild(s);})();</script>
83
83
  </head>
84
84
  <body>
85
85
  <div id="root"><div class="boot-skeleton">加载中…</div></div>
@@ -7,9 +7,9 @@
7
7
  "react-router": "7.18.2",
8
8
  "react-router/dom": "7.18.2",
9
9
  "@tanstack/react-query": "5.102.8",
10
- "@oj-module/runtime": "0.2.66",
11
- "@oj-module/runtime/contract": "0.2.66",
12
- "@oj-module/runtime/contract/errors": "0.2.66",
10
+ "@oj-module/runtime": "0.2.68",
11
+ "@oj-module/runtime/contract": "0.2.68",
12
+ "@oj-module/runtime/contract/errors": "0.2.68",
13
13
  "@plane/kit": "0.1.20",
14
14
  "@plane/editor": "0.1.20",
15
15
  "antd": "6.6.1",
@@ -21,6 +21,12 @@ let exitSpy: ReturnType<typeof vi.fn>;
21
21
  // 提升式 mock:vitest 测试体内注册的 vi.mock 拦不到 cli.ts 的 dynamic import,须 hoisted 顶层注册
22
22
  const buildBundleMock = vi.hoisted(() => vi.fn(async () => [{ file: "x.tgz" }]));
23
23
  vi.mock("../bundle", () => ({ buildBundle: buildBundleMock }));
24
+ // 脚手架完整性门禁(scaffold-guard):门禁失败必须先于一切产物写入
25
+ const guardMock = vi.hoisted(() => vi.fn());
26
+ vi.mock("../scaffold-guard", () => ({ assertDefaultModulesUntouched: guardMock }));
27
+ const buildBackendMock = vi.hoisted(() => vi.fn(async () => {}));
28
+ const buildModulesMock = vi.hoisted(() => vi.fn(async () => []));
29
+ vi.mock("../build", () => ({ buildBackend: buildBackendMock, buildModules: buildModulesMock }));
24
30
  const vendorCommandMock = vi.hoisted(() => vi.fn(async () => {}));
25
31
  vi.mock("../vendor", () => ({ vendorCommand: vendorCommandMock }));
26
32
  // REG-CS3 v2:sign 的 --registry-id 曾只被 signCommand 要求、却没在 CLI 上接线,命令成死路
@@ -39,6 +45,9 @@ afterEach(() => {
39
45
  buildBundleMock.mockClear();
40
46
  vendorCommandMock.mockClear();
41
47
  signCommandMock.mockClear();
48
+ guardMock.mockClear();
49
+ buildBackendMock.mockClear();
50
+ buildModulesMock.mockClear();
42
51
  });
43
52
 
44
53
  interface RunResult { out: string, unknown: string[], exitCode: number | undefined }
@@ -215,6 +224,28 @@ describe("参数映射", () => {
215
224
  });
216
225
  });
217
226
 
227
+ describe("脚手架完整性门禁", () => {
228
+ it("门禁失败 → 普通 build 不执行(零产物)、exit 1", async () => {
229
+ guardMock.mockImplementation(() => {
230
+ throw new Error("[ojm] 框架默认模块与上游模板不一致,已拒绝构建");
231
+ });
232
+ const r = await run(["build"], "zh-CN", tmp);
233
+ expect(guardMock).toHaveBeenCalledWith(tmp);
234
+ expect(buildBackendMock).not.toHaveBeenCalled();
235
+ expect(buildModulesMock).not.toHaveBeenCalled();
236
+ expect(r.exitCode).toBe(1);
237
+ });
238
+
239
+ it("门禁失败 → --bundle 同样不进 buildBundle", async () => {
240
+ guardMock.mockImplementation(() => {
241
+ throw new Error("[ojm] 框架默认模块与上游模板不一致,已拒绝构建");
242
+ });
243
+ const r = await run(["build", "--bundle", "demo"], "zh-CN", tmp);
244
+ expect(buildBundleMock).not.toHaveBeenCalled();
245
+ expect(r.exitCode).toBe(1);
246
+ });
247
+ });
248
+
218
249
  describe("help 词形", () => {
219
250
  it("ojm help build → build --help", () => {
220
251
  expect(rewriteHelpArgv(["help", "build"])).toEqual(["build", "--help"]);
@@ -56,4 +56,17 @@ describe("ojm new --api 骨架可路由(BDD)", () => {
56
56
  expect(doc[key], `solution.yaml 缺 ${key}(market verify 必填)`).toBeDefined();
57
57
  expect((doc.archive as Record<string, unknown>).file).toBe("crm-0.1.0.tgz");
58
58
  });
59
+
60
+ it("给定 createSolution(api: true),api 段声明 ping 匿名(安装预检 anonymous_residual 防线,20261106 市场线实测)", async () => {
61
+ const root = projectRoot();
62
+ const result = createSolution(root, "crm", { api: true, layout: "none" });
63
+ const { parse: yamlParse } = await import("yaml");
64
+ const doc = yamlParse(fs.readFileSync(path.join(result.dir, "solution.yaml"), "utf8")) as Record<string, unknown>;
65
+
66
+ // Then:ping 匿名可达必须在契约里声明(fixture.ts 同形状);漏声明 = 真包装不进(preflight 拒)
67
+ const api = doc.api as Record<string, unknown>;
68
+ expect(api.module).toBe("crm");
69
+ expect(api.prefix).toBe("/api/crm");
70
+ expect(api.anonymous).toEqual(["crm/ping"]);
71
+ });
59
72
  });
@@ -0,0 +1,157 @@
1
+ // BDD:`ojm keygen --credential`(凭证通道,P5)。
2
+ //
3
+ // 三条不可退让的行为:
4
+ // · 身份只来自凭证——服务端下发什么 key_id 就落什么目录,CLI 不推导;
5
+ // · 兑换失败不落任何密钥文件(先兑换、后生成,顺序即保证);
6
+ // · 不给凭证时逐字节走原离线路径(环境变量残留不得把离线用例带进网络分支)。
7
+ import fs from "node:fs";
8
+ import os from "node:os";
9
+ import path from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
+ import { buildCli } from "../cli";
12
+ import { exchangePublishCredential } from "../keygen";
13
+
14
+ const TOKEN = "a".repeat(64);
15
+ const KEY_ID = "demo-solution.wecom-everpan";
16
+
17
+ let tmp: string;
18
+ let logs: string[];
19
+ let exitSpy: ReturnType<typeof vi.fn>;
20
+ let savedCredential: string | undefined;
21
+ let savedSite: string | undefined;
22
+
23
+ beforeEach(() => {
24
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), "ojm-publish-keygen-"));
25
+ logs = [];
26
+ exitSpy = vi.fn();
27
+ vi.spyOn(console, "log").mockImplementation((...args: unknown[]) => logs.push(args.join(" ")));
28
+ vi.spyOn(console, "error").mockImplementation((...args: unknown[]) => logs.push(args.join(" ")));
29
+ // E-4:环境变量残留会把离线用例带进网络分支(先例 registry-cli.test.ts)
30
+ savedCredential = process.env.OJM_PUBLISH_CREDENTIAL;
31
+ savedSite = process.env.OJM_SITE_URL;
32
+ delete process.env.OJM_PUBLISH_CREDENTIAL;
33
+ delete process.env.OJM_SITE_URL;
34
+ });
35
+
36
+ afterEach(() => {
37
+ vi.restoreAllMocks();
38
+ vi.unstubAllGlobals();
39
+ if (savedCredential === undefined)
40
+ delete process.env.OJM_PUBLISH_CREDENTIAL;
41
+ else
42
+ process.env.OJM_PUBLISH_CREDENTIAL = savedCredential;
43
+ if (savedSite === undefined)
44
+ delete process.env.OJM_SITE_URL;
45
+ else
46
+ process.env.OJM_SITE_URL = savedSite;
47
+ fs.rmSync(tmp, { recursive: true, force: true });
48
+ });
49
+
50
+ /** 站点信封应答(oj 统一 {code,msg,data}) */
51
+ function stubExchange(env: { code: number, msg?: string, data?: unknown }): ReturnType<typeof vi.fn> {
52
+ const fn = vi.fn(async () => new Response(JSON.stringify(env), { status: 200, headers: { "content-type": "application/json" } }));
53
+ vi.stubGlobal("fetch", fn);
54
+ return fn;
55
+ }
56
+
57
+ /** 跑一次 CLI 并等动作链结束(动态 import 需数百毫秒,同 cli.test.ts 的轮询口径) */
58
+ async function run(argv: string[]): Promise<{ out: string, exitCode: number | undefined }> {
59
+ const cli = buildCli(tmp, "zh-CN", { exit: exitSpy as unknown as (code: number) => never });
60
+ cli.parse(["node", "ojm", ...argv], { run: true });
61
+ try {
62
+ await vi.waitUntil(() => logs.length > 0 || exitSpy.mock.calls.length > 0, { timeout: 2500, interval: 10 });
63
+ }
64
+ catch {
65
+ // 无输出型动作:兜底继续
66
+ }
67
+ await new Promise(r => setTimeout(r, 50));
68
+ return { out: logs.join("\n"), exitCode: exitSpy.mock.calls[0]?.[0] as number | undefined };
69
+ }
70
+
71
+ describe("ojm keygen --credential", () => {
72
+ it("given 有效凭证 When keygen --credential Then 落盘目录名等于服务端下发 key_id", async () => {
73
+ const fetchFn = stubExchange({ code: 0, data: { key_id: KEY_ID, product: "demo-solution", username: "wecom:everpan", display_name: "张三" } });
74
+
75
+ const r = await run(["keygen", "--credential", TOKEN, "--out", path.join(tmp, "keys"), "--yes"]);
76
+
77
+ expect(r.exitCode).toBeUndefined();
78
+ expect(fetchFn).toHaveBeenCalledTimes(1);
79
+ const [, init] = fetchFn.mock.calls[0] as unknown as [string, RequestInit];
80
+ expect(JSON.parse(String(init.body))).toEqual({ token: TOKEN });
81
+ expect(fs.existsSync(path.join(tmp, "keys", "private.pem"))).toBe(true);
82
+ expect(r.out).toContain(KEY_ID);
83
+ // 申请人展示带回人话(产品 + 人名),而不是只有一串 key_id
84
+ expect(r.out).toContain("demo-solution");
85
+ expect(r.out).toContain("张三");
86
+ });
87
+
88
+ it("given 已用/过期凭证 When keygen --credential Then 非零退出且不落任何密钥", async () => {
89
+ stubExchange({ code: 400, msg: "发布凭证无效或已失效(可能已兑换、已过期或已撤销)。请到「发布管理」页重新申请" });
90
+
91
+ const r = await run(["keygen", "--credential", TOKEN, "--out", path.join(tmp, "keys"), "--yes"]);
92
+
93
+ expect(r.exitCode).toBe(1);
94
+ expect(r.out).toContain("发布管理");
95
+ expect(fs.existsSync(path.join(tmp, "keys"))).toBe(false);
96
+ });
97
+
98
+ it("given 站点连不上 When keygen --credential Then 报站点地址且不落盘", async () => {
99
+ vi.stubGlobal("fetch", vi.fn(async () => {
100
+ throw new Error("ECONNREFUSED");
101
+ }));
102
+
103
+ const r = await run(["keygen", "--credential", TOKEN, "--site-url", "http://10.0.0.1:9778", "--out", path.join(tmp, "keys"), "--yes"]);
104
+
105
+ expect(r.exitCode).toBe(1);
106
+ expect(r.out).toContain("http://10.0.0.1:9778");
107
+ expect(fs.existsSync(path.join(tmp, "keys"))).toBe(false);
108
+ });
109
+
110
+ it("given 同时给 --credential 与 --id Then exit 2 且文案说明二选一", async () => {
111
+ const fetchFn = stubExchange({ code: 0, data: { key_id: KEY_ID } });
112
+
113
+ const r = await run(["keygen", "--credential", TOKEN, "--id", "acme.market"]);
114
+
115
+ expect(r.exitCode).toBe(2);
116
+ expect(r.out).toContain("不能同时使用");
117
+ expect(fetchFn).not.toHaveBeenCalled();
118
+ });
119
+
120
+ it("given 未设任何凭证 When keygen Then 走离线路径(既有行为零回归)", async () => {
121
+ const fetchFn = stubExchange({ code: 0, data: { key_id: KEY_ID } });
122
+
123
+ const r = await run(["keygen", "--out", path.join(tmp, "keys"), "--yes"]);
124
+
125
+ expect(r.exitCode).toBeUndefined();
126
+ expect(fetchFn).not.toHaveBeenCalled();
127
+ expect(r.out).toContain("local.dev");
128
+ expect(fs.existsSync(path.join(tmp, "keys", "private.pem"))).toBe(true);
129
+ });
130
+
131
+ it("given 环境变量 OJM_PUBLISH_CREDENTIAL When keygen 不带 --credential Then 仍走兑换通道", async () => {
132
+ process.env.OJM_PUBLISH_CREDENTIAL = TOKEN;
133
+ const fetchFn = stubExchange({ code: 0, data: { key_id: KEY_ID, product: "demo-solution", username: "u1" } });
134
+
135
+ const r = await run(["keygen", "--out", path.join(tmp, "keys"), "--yes"]);
136
+
137
+ expect(r.exitCode).toBeUndefined();
138
+ expect(fetchFn).toHaveBeenCalledTimes(1);
139
+ expect(r.out).toContain(KEY_ID);
140
+ });
141
+
142
+ it("given 服务端返回越界 key_id When 兑换 Then 拒绝且不落盘(key_id 会拼进密钥目录)", async () => {
143
+ stubExchange({ code: 0, data: { key_id: "../../etc", product: "x", username: "u" } });
144
+
145
+ const r = await run(["keygen", "--credential", TOKEN, "--out", path.join(tmp, "keys"), "--yes"]);
146
+
147
+ expect(r.exitCode).toBe(1);
148
+ expect(r.out).toContain("key_id 非法");
149
+ expect(fs.existsSync(path.join(tmp, "keys"))).toBe(false);
150
+ });
151
+
152
+ it("given 凭证不是 64 位 hex When 兑换 Then 本地即拒,不发请求", () => {
153
+ const fetchFn = stubExchange({ code: 0, data: { key_id: KEY_ID } });
154
+
155
+ return expect(exchangePublishCredential("short", { fetchFn: fetchFn as unknown as typeof fetch })).rejects.toThrow(/64 位十六进制/).then(() => expect(fetchFn).not.toHaveBeenCalled());
156
+ });
157
+ });