@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.
- package/bin/.oj-version +1 -0
- package/bin/oj +4 -0
- package/devkit/README.md +15 -7
- package/devkit/SKILL.md +38 -4
- package/devkit/framework-dev.md +10 -4
- package/devkit/manual.md +146 -24
- package/devkit/menu-rules.md +19 -4
- package/devkit/module-dev-and-cdn-playground.md +4 -0
- package/devkit/publish-walkthrough.md +132 -37
- package/devkit/wecom-internal-app.md +8 -3
- package/devkit/wecom-multi-corp-links.md +163 -0
- package/package.json +2 -2
- package/shell-dist/assets/{index.i0kqbxp050.js → index.6alqyx6ae0.js} +21 -1
- package/shell-dist/assets/{runtime.1xfsxcajj0.js → runtime.1xzahckt10.js} +4863 -4847
- package/shell-dist/index.html +4 -4
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/cli.test.ts +31 -0
- package/src/__tests__/new-scaffold.test.ts +13 -0
- package/src/__tests__/publish-keygen.test.ts +157 -0
- package/src/__tests__/templates-migration-schema-parity.test.ts +540 -0
- package/src/__tests__/templates-schema-columns.test.ts +15 -2
- package/src/__tests__/templates-tenant-scope.test.ts +6 -2
- package/src/build/module-def.ts +1 -0
- package/src/builtin.ts +3 -2
- package/src/bundle.ts +8 -3
- package/src/cli.ts +21 -5
- package/src/dev.ts +15 -10
- package/src/init.ts +47 -41
- package/src/install.ts +11 -4
- package/src/keygen.ts +54 -0
- package/src/lifecycle/__tests__/bundle.test.ts +21 -3
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +1 -1
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +2 -2
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +5 -2
- package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +1 -1
- package/src/lifecycle/__tests__/manifest-contract.test.ts +75 -3
- package/src/lifecycle/__tests__/smoke.test.ts +2 -2
- package/src/manifest-contract.ts +91 -8
- package/src/market/bootstrap.ts +26 -2
- package/src/market/command.ts +1 -0
- package/src/market/publish.ts +8 -1
- package/src/market/walkthrough.ts +24 -8
- package/src/new.ts +2 -0
- package/src/scaffold-guard.ts +227 -0
- package/src/site.ts +2 -0
- package/src/usage.ts +13 -9
- package/src/vendor.ts +5 -1
- package/templates/config.yaml +4 -2
- package/templates/init.include.yaml +10 -3
- package/templates/openapi.json +460 -1
- package/templates/src/README.md +37 -0
- package/templates/src/app/api/_platform/_lifecycle/compensate.ts +136 -0
- package/templates/src/app/api/_platform/_lifecycle/installer.ts +78 -25
- package/templates/src/app/api/_platform/_lifecycle/lock.ts +23 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/deploy-web.ts +28 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +46 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +100 -36
- package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +7 -15
- package/templates/src/app/api/_platform/_lifecycle/text-edit.ts +71 -19
- package/templates/src/app/api/_platform/_lifecycle/types.ts +26 -2
- package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +93 -14
- package/templates/src/app/api/_platform/_shared/service/domain/settings.ts +8 -4
- package/templates/src/app/api/_platform/_shared/service/domain/solution-perms.ts +25 -10
- package/templates/src/app/api/_platform/manifest.yaml +1 -0
- package/templates/src/app/api/_platform/market/api.ts +235 -10
- package/templates/src/app/api/_platform/migrations/0001__baseline.mysql.sql +178 -0
- package/templates/src/app/api/_platform/migrations/0001__baseline.pg.sql +173 -0
- package/templates/src/app/api/_platform/migrations/0001__baseline.sql +188 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.mysql.sql +17 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.pg.sql +16 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.sql +33 -0
- package/templates/src/app/api/_platform/schema.yaml +18 -0
- package/templates/src/app/api/_platform/seed.mysql.sql +10 -0
- package/templates/src/app/api/_platform/seed.pg.sql +10 -0
- package/templates/src/app/api/auth/_shared/wecom/client.ts +24 -4
- package/templates/src/app/api/home/migrations/0001__create_home_tables.mysql.sql +14 -0
- package/templates/src/app/api/home/migrations/0001__create_home_tables.pg.sql +14 -0
- package/templates/src/app/api/home/migrations/0002__tenant.pg.sql +4 -0
- package/templates/src/app/api/home/seed.mysql.sql +71 -0
- package/templates/src/app/api/home/seed.pg.sql +71 -0
- package/templates/src/app/api/notification/_shared/notify.ts +6 -6
- package/templates/src/app/api/notification/migrations/0001__create_notifications.mysql.sql +11 -0
- package/templates/src/app/api/notification/migrations/0001__create_notifications.pg.sql +11 -0
- package/templates/src/app/api/notification/migrations/0002__notifications_user.mysql.sql +8 -0
- package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.mysql.sql +21 -0
- package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.pg.sql +22 -0
- package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.mysql.sql +10 -0
- package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.pg.sql +11 -0
- package/templates/src/app/api/notification/migrations/0005__tenant.pg.sql +6 -0
- package/templates/src/app/api/notification/seed.mysql.sql +8 -0
- package/templates/src/app/api/notification/seed.pg.sql +11 -0
- package/templates/src/app/api/publish/_shared/identity.ts +69 -0
- package/templates/src/app/api/publish/api.ts +349 -0
- package/templates/src/app/api/publish/manifest.yaml +8 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.mysql.sql +23 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.pg.sql +23 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.sql +39 -0
- package/templates/src/app/api/publish/schema.yaml +33 -0
- package/templates/src/app/api/system/login-methods/api.ts +2 -1
- package/templates/src/app/web/_platform/__client/api.schemas.ts +15 -0
- package/templates/src/app/web/_platform/__client/index.ts +25 -1
- package/templates/src/app/web/entry.ts +9 -5
- package/templates/src/app/web/home/routes.ts +5 -0
- package/templates/src/app/web/login/locales/en-US.json +7 -1
- package/templates/src/app/web/login/locales/zh-CN.json +7 -1
- package/templates/src/app/web/login/pages/login.tsx +79 -86
- package/templates/src/app/web/login/routes.ts +54 -1
- package/templates/src/app/web/personal-center/locales/en-US.json +1 -0
- package/templates/src/app/web/personal-center/locales/zh-CN.json +1 -0
- package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +5 -0
- package/templates/src/app/web/system/__client/api.schemas.ts +2 -1
- package/templates/src/app/web/system/locales/en-US.json +1 -8
- package/templates/src/app/web/system/locales/zh-CN.json +1 -8
- package/templates/src/app/web/system/pages/settings/index.tsx +17 -4
- package/templates/src/app/web/system/routes.ts +0 -6
- package/templates/src/oj-market/web/__tests__/CardGrid.test.tsx +33 -0
- package/templates/src/oj-market/web/__tests__/DetailDrawer.test.tsx +69 -0
- package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +7 -1
- package/templates/src/oj-market/web/api/client.ts +3 -0
- package/templates/src/oj-market/web/api/defaultClient.ts +5 -0
- package/templates/src/oj-market/web/components/CardItem.tsx +44 -1
- package/templates/src/oj-market/web/components/DetailDrawer.tsx +17 -1
- package/templates/src/oj-market/web/entry.ts +31 -2
- package/templates/src/oj-market/web/locales/en-US.json +5 -1
- package/templates/src/oj-market/web/locales/zh-CN.json +5 -1
- package/templates/src/oj-market/web/publish/__client/api.schemas.ts +61 -0
- package/templates/src/oj-market/web/publish/__client/index.ts +164 -0
- package/templates/src/oj-market/web/publish/locales/en-US.json +80 -0
- package/templates/src/oj-market/web/publish/locales/zh-CN.json +80 -0
- package/templates/src/oj-market/web/publish/pages/history.tsx +112 -0
- package/templates/src/oj-market/web/publish/pages/index.tsx +196 -0
- package/templates/src/oj-market/web/publish/pages/model.ts +52 -0
- package/templates/src/oj-market/web/publish/pages/token-modal.tsx +82 -0
- package/templates/src/oj-market/web/publish/routes.ts +41 -0
- package/templates/src/oj-market/web/types.ts +30 -0
- package/templates/tests/lifecycle/compensate.spec.ts +207 -0
- package/templates/tests/lifecycle/deploy-smoke.spec.ts +38 -14
- package/templates/tests/lifecycle/installer.spec.ts +28 -9
- package/templates/tests/lifecycle/market-api.spec.ts +164 -12
- package/templates/tests/lifecycle/preflight.spec.ts +68 -0
- package/templates/tests/lifecycle/register.spec.ts +22 -0
- package/templates/tests/lifecycle/role-title-translate.spec.ts +4 -34
- package/templates/tests/lifecycle/text-edit.spec.ts +45 -27
- package/templates/tests/lifecycle/uninstall.spec.ts +36 -0
- package/templates/tests/publish-credential.spec.ts +354 -0
- package/templates/tests/publish-identity.spec.ts +44 -0
- package/templates/tests/rbac-solution-perms.spec.ts +48 -3
- package/templates/tests/wecom-client-timeout.spec.ts +33 -3
- package/templates/tests/wecom-landing.spec.ts +63 -1
- package/templates/tsconfig.paths.json +1 -0
- package/templates/web.config.ts +8 -4
- package/vendor/host-versions.json +3 -3
- package/templates/seed.mysql.sql +0 -6
- package/templates/seed.pg.sql +0 -6
- package/templates/src/app/api/_platform/migrations/0001__create_users.mysql.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0001__create_users.pg.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0001__create_users.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.mysql.sql +0 -5
- package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.sql +0 -5
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.mysql.sql +0 -70
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.pg.sql +0 -67
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.sql +0 -67
- package/templates/src/app/api/_platform/migrations/0005__users_must_change.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0005__users_must_change.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0007__users_email.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.mysql.sql +0 -10
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.pg.sql +0 -10
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.sql +0 -11
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.mysql.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.pg.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.sql +0 -13
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.mysql.sql +0 -17
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.pg.sql +0 -16
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.sql +0 -18
- package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.mysql.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0012__market_history.mysql.sql +0 -13
- package/templates/src/app/api/_platform/migrations/0012__market_history.pg.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0012__market_history.sql +0 -14
- package/templates/src/app/api/_platform/migrations/0013__users_profile.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.mysql.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.pg.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.sql +0 -8
- package/templates/src/app/api/_platform/migrations/0015__tenant.mysql.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0015__tenant.pg.sql +0 -11
- package/templates/src/app/api/_platform/migrations/0015__tenant.sql +0 -44
- package/templates/src/app/api/_platform/migrations/0016__org.mysql.sql +0 -41
- package/templates/src/app/api/_platform/migrations/0016__org.pg.sql +0 -40
- package/templates/src/app/api/_platform/migrations/0016__org.sql +0 -62
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +0 -8
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +0 -8
- package/templates/src/app/web/system/pages/login-methods/index.tsx +0 -30
package/shell-dist/index.html
CHANGED
|
@@ -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-
|
|
9
|
-
<script type="importmap" nonce="
|
|
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.
|
|
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="
|
|
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>
|
package/shell-dist/versions.json
CHANGED
|
@@ -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.
|
|
11
|
-
"@oj-module/runtime/contract": "0.2.
|
|
12
|
-
"@oj-module/runtime/contract/errors": "0.2.
|
|
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
|
+
});
|