@oj-module/cli 0.2.69 → 0.2.71
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/devkit/README.md +10 -8
- package/devkit/framework-dev.md +1 -1
- package/devkit/manual.md +45 -19
- package/devkit/module-dev-and-cdn-playground.md +20 -14
- package/devkit/publish-walkthrough.md +132 -43
- package/devkit/wecom-customer-reauth-guide.md +110 -0
- package/package.json +2 -2
- package/shell-dist/assets/{runtime.zrv2zhj9j0.js → runtime.7x99hmm020.js} +2 -2
- package/shell-dist/bundles/oj-market-0.2.0.tgz +0 -0
- package/shell-dist/bundles/oj-market-0.2.0.tgz.sig +8 -0
- package/shell-dist/index.html +4 -4
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/list.test.ts +7 -7
- package/src/__tests__/oj-health-host.test.ts +19 -0
- package/src/__tests__/site-tenant-header.test.ts +2 -2
- package/src/__tests__/templates-builtin-list.test.ts +47 -0
- package/src/__tests__/templates-schema-columns.test.ts +11 -2
- package/src/__tests__/templates-tenant-scope.test.ts +2 -3
- package/src/__tests__/users-roles-display.test.ts +84 -0
- package/src/bundle.ts +1 -1
- package/src/contract/__tests__/market-client.integration.test.ts +23 -22
- package/src/enable.ts +2 -2
- package/src/init.ts +45 -0
- package/src/install.ts +5 -5
- package/src/lifecycle/__tests__/install.test.ts +29 -29
- package/src/lifecycle/__tests__/market-bundle.test.ts +87 -0
- package/src/lifecycle/__tests__/publish.test.ts +8 -10
- package/src/lifecycle/__tests__/trust.test.ts +14 -14
- package/src/lifecycle/__tests__/uninstall.test.ts +4 -4
- package/src/list.ts +3 -3
- package/src/market/__tests__/bootstrap.test.ts +24 -7
- package/src/market/__tests__/failure-playbook.test.ts +0 -1
- package/src/market/__tests__/remote-push.test.ts +228 -0
- package/src/market/__tests__/verify.test.ts +45 -207
- package/src/market/bootstrap.ts +12 -7
- package/src/market/command.ts +33 -35
- package/src/market/db-seed.ts +129 -0
- package/src/market/failure-playbook.ts +3 -9
- package/src/market/fixture.ts +2 -6
- package/src/market/publish.ts +145 -89
- package/src/market/remote-push.ts +236 -0
- package/src/market/trial.ts +2 -2
- package/src/market/verify.ts +17 -270
- package/src/market/walkthrough.ts +38 -63
- package/src/oj.ts +13 -2
- package/src/preview.ts +5 -3
- package/src/publish.ts +3 -2
- package/src/scaffold-guard.ts +4 -0
- package/src/site.ts +1 -1
- package/src/trust.ts +8 -8
- package/src/uninstall.ts +2 -2
- package/src/usage.ts +23 -21
- package/templates/config.yaml +22 -3
- package/templates/init.include.yaml +16 -11
- package/templates/openapi.json +3948 -3655
- package/templates/src/.ojm-builtin.json +13 -0
- package/templates/src/README.md +5 -2
- package/templates/src/app/api/_platform/README.md +3 -2
- package/templates/src/app/api/_platform/_lifecycle/atomic.ts +7 -2
- package/templates/src/app/api/_platform/_lifecycle/bundle-converge.ts +158 -0
- package/templates/src/app/api/_platform/_lifecycle/history.ts +3 -2
- package/templates/src/app/api/_platform/_lifecycle/installer.ts +25 -5
- package/templates/src/app/api/_platform/_lifecycle/local-archive.ts +61 -0
- package/templates/src/app/api/_platform/_lifecycle/manifest-of-upload.ts +62 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +12 -2
- package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +4 -1
- package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +8 -2
- package/templates/src/app/api/_platform/_lifecycle/types.ts +1 -1
- package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +14 -5
- package/templates/src/app/api/_platform/_shared/site-facade.ts +31 -0
- package/templates/src/app/api/_platform/manifest.yaml +1 -2
- package/templates/src/app/api/_platform/migrations/0003__market_catalog.mysql.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0003__market_catalog.pg.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0003__market_catalog.sql +18 -0
- package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.mysql.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.pg.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.sql +6 -0
- package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.mysql.sql +8 -0
- package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.pg.sql +8 -0
- package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.sql +8 -0
- package/templates/src/app/api/_platform/schema.yaml +8 -11
- package/templates/src/app/api/_platform/seed.mysql.sql +1 -1
- package/templates/src/app/api/_platform/seed.pg.sql +1 -1
- package/templates/src/app/api/_platform/seed.sql +1 -1
- package/templates/src/app/api/auth/_shared/wecom/sync.ts +6 -0
- package/templates/src/app/web/_platform/__client/api.schemas.ts +0 -114
- package/templates/src/app/web/_platform/__client/index.ts +1 -333
- package/templates/src/app/web/system/locales/en-US.json +15 -1
- package/templates/src/app/web/system/locales/zh-CN.json +15 -1
- package/templates/src/app/web/system/pages/departments/sync-bar.tsx +9 -3
- package/templates/src/app/web/system/pages/roles/index.tsx +4 -8
- package/templates/src/app/web/system/pages/roles/role-label.ts +26 -0
- package/templates/src/app/web/system/pages/solutions/index.tsx +110 -0
- package/templates/src/app/web/system/pages/solutions/market-entry.ts +46 -0
- package/templates/src/app/web/system/pages/users/index.tsx +12 -2
- package/templates/src/app/web/system/pages/users/match-keyword.ts +19 -0
- package/templates/src/app/web/system/pages/users/roles.tsx +43 -14
- package/templates/src/app/web/system/routes.ts +18 -1
- package/templates/src/{app/api/_platform/market → oj-market/api}/api.ts +475 -592
- package/templates/src/oj-market/api/backfill.ts +221 -0
- package/templates/src/{app/api/_platform/market → oj-market/api}/bootstrap.ts +2 -2
- package/templates/src/oj-market/api/cdn/api.ts +312 -0
- package/templates/src/oj-market/api/cdn/shared.ts +104 -0
- package/templates/src/oj-market/api/cdn/signed-auth.ts +75 -0
- package/templates/src/oj-market/api/manifest.yaml +8 -0
- package/templates/src/oj-market/solution.yaml +21 -0
- package/templates/src/oj-market/web/__client/api.schemas.ts +167 -0
- package/templates/src/oj-market/web/__client/index.ts +509 -0
- package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +6 -6
- package/templates/src/oj-market/web/api/client.ts +6 -6
- package/templates/src/oj-market/web/api/defaultClient.ts +31 -25
- package/templates/src/oj-market/web/entry.ts +5 -3
- package/templates/src/oj-market/web/hooks/useMarket.ts +6 -6
- package/templates/src/oj-market/web/pages/MarketApp.tsx +20 -3
- package/templates/src/oj-market/web/publish/locales/en-US.json +1 -1
- package/templates/src/oj-market/web/publish/locales/zh-CN.json +1 -1
- package/templates/src/oj-market/web/publish/pages/history.tsx +2 -11
- package/templates/src/oj-market/web/routes.ts +12 -0
- package/templates/src/oj-market/web/types.ts +14 -8
- package/templates/tests/lifecycle/anonymous-wiring.spec.ts +65 -0
- package/templates/tests/lifecycle/backfill.spec.ts +392 -0
- package/templates/tests/lifecycle/bootstrap.spec.ts +1 -1
- package/templates/tests/lifecycle/bundle-converge.spec.ts +264 -0
- package/templates/tests/lifecycle/cdn-upload.spec.ts +661 -0
- package/templates/tests/lifecycle/deploy-smoke.spec.ts +25 -0
- package/templates/tests/lifecycle/exec-grant.spec.ts +38 -0
- package/templates/tests/lifecycle/installer.spec.ts +36 -0
- package/templates/tests/lifecycle/market-api.spec.ts +448 -803
- package/templates/tests/lifecycle/preflight.spec.ts +18 -0
- package/templates/tests/lifecycle/trust.spec.ts +2 -2
- package/templates/tests/lifecycle/uninstall.spec.ts +36 -2
- package/templates/tests/lifecycle/verify-checks.spec.ts +1 -1
- package/templates/tests/solutions-entry.spec.ts +55 -0
- package/templates/tsconfig.paths.json +1 -0
- package/templates/web.config.ts +6 -6
- package/vendor/host-versions.json +3 -3
- package/bin/oj +0 -4
- package/src/market/__tests__/build-index.test.ts +0 -641
- package/src/market/__tests__/sharding.test.ts +0 -90
- package/src/market/__tests__/yank-ledger.test.ts +0 -60
- package/src/market/build-index.ts +0 -526
- package/src/market/index-builder.ts +0 -149
- package/src/market/sharding.ts +0 -94
- package/src/market/yank-ledger.ts +0 -91
- package/templates/src/app/api/_platform/_lifecycle/source-seq.ts +0 -88
- package/templates/src/app/api/_platform/market/index-source.ts +0 -112
- package/templates/tests/lifecycle/source-seq.spec.ts +0 -55
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 企业微信重新授权与数据恢复操作单
|
|
3
|
+
audience: 客户企业微信管理员(可直接转发)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 企业微信重新授权与数据恢复操作单
|
|
7
|
+
|
|
8
|
+
> 致:________________ 企业微信管理员
|
|
9
|
+
>
|
|
10
|
+
> **本页由服务商填写下方「本次信息」后转发给客户管理员。** 带 ☐ 的项请逐项完成并打勾。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 本次信息(服务商填写后再发送)
|
|
15
|
+
|
|
16
|
+
| 项目 | 值 |
|
|
17
|
+
|---|---|
|
|
18
|
+
| 站点地址 | `https://exp.wiz.top` |
|
|
19
|
+
| 贵司企业代码 | `wpnOCvCwAABvYHmfoHSOjY2OAZC-lTug` |
|
|
20
|
+
| 员工登录链接 | `https://exp.wiz.top/login?corp=<企业代码>&agent=<应用ID>` |
|
|
21
|
+
| 应用开通链接 / 二维码 | (服务商生成,有时效;粘贴链接或附二维码) |
|
|
22
|
+
| 管理后台账号 | `admin` |
|
|
23
|
+
| 管理后台初始口令 | (服务商**单独**发送,请勿写在本页、勿转发) |
|
|
24
|
+
| 服务联系人 / 电话 | ________________ |
|
|
25
|
+
|
|
26
|
+
> 「员工登录链接」中的 `<应用ID>` 由服务商在重新授权后确认填入;请勿自行修改或删减链接参数。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 为什么要做这一次
|
|
31
|
+
|
|
32
|
+
站点近期重新部署,本地数据需要重建。**贵司在企业微信里的部门与成员数据是完整的**,重新完成一次应用授权并同步即可恢复。
|
|
33
|
+
|
|
34
|
+
- 预计用时:约 10–20 分钟(企业规模越大,第 3 步越久)
|
|
35
|
+
- 影响范围:操作期间员工暂时无法登录,完成后恢复
|
|
36
|
+
- 需要重新设置的:站内的**角色权限**、个性化设置、操作记录(这些属于本站管理数据,企业微信不保存,无法自动恢复,见第 5 步)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 操作步骤
|
|
41
|
+
|
|
42
|
+
### 第 1 步 重新授权应用(约 2 分钟)
|
|
43
|
+
|
|
44
|
+
- ☐ 使用**企业微信管理员**账号,扫描服务商提供的开通二维码(或打开开通链接)
|
|
45
|
+
- ☐ 授权模式选择「**管理员授权**」
|
|
46
|
+
> **请务必不要选「成员授权」**。选错后应用读不到部门与成员,第 3 步的同步会直接失败。
|
|
47
|
+
- ☐ 应用可见范围设置为「**全公司**」
|
|
48
|
+
- ☐ 确认页面提示「授权成功」
|
|
49
|
+
- ☐ 若提示「应用安装失败,联系服务商」:稍等 1 分钟后重试一次;仍失败请联系服务商
|
|
50
|
+
|
|
51
|
+
### 第 2 步 首次登录管理后台(约 2 分钟)
|
|
52
|
+
|
|
53
|
+
- ☐ 打开站点登录页,切到「**账号密码**」页签
|
|
54
|
+
- ☐ 账号填 `admin`,口令用服务商单独发送的初始口令
|
|
55
|
+
- ☐ 首次登录会要求**修改密码**,请设置新密码并妥善保存
|
|
56
|
+
- ☐ 登录后左侧能看到「**系统管理**」菜单,即表示管理员权限正常
|
|
57
|
+
|
|
58
|
+
### 第 3 步 同步组织架构(约 5–15 分钟,视企业规模)
|
|
59
|
+
|
|
60
|
+
- ☐ 进入 **系统管理 → 组织架构**
|
|
61
|
+
- ☐ 点击页面顶部的「**立即同步**」
|
|
62
|
+
- ☐ 等待状态变成「**同步完成:X 个部门 / Y 名成员**」,期间**不要关闭页面**
|
|
63
|
+
- 过程中显示「正在同步:已处理 X/Y 个部门」属正常
|
|
64
|
+
- 若中途暂停,点「**继续同步**」接着跑,已完成的进度不会丢
|
|
65
|
+
- ☐ 核对部门数、成员数与贵司企业微信通讯录一致
|
|
66
|
+
- ☐ 若页面提示「该企业为「成员授权」模式」或同步报错:回到第 1 步,在企业微信后台把授权模式改为「管理员授权」后重新授权
|
|
67
|
+
|
|
68
|
+
> 同步只拉取贵司自己的组织架构,不会影响其他企业的数据。
|
|
69
|
+
|
|
70
|
+
### 第 4 步 通知员工重新登录
|
|
71
|
+
|
|
72
|
+
- ☐ 将「员工登录链接」发给全员
|
|
73
|
+
- ☐ 员工二选一进入:
|
|
74
|
+
- 在**企业微信工作台**点击本应用 → 直接免登进入;
|
|
75
|
+
- 或在浏览器打开员工登录链接 → 用企业微信「**扫一扫**」扫码进入
|
|
76
|
+
- ☐ 首次登录自动建立账号,**不需要注册、不需要设置密码**
|
|
77
|
+
|
|
78
|
+
### 第 5 步 重新配置角色权限(重要,无法自动恢复)
|
|
79
|
+
|
|
80
|
+
> 角色权限属于本站的管理设置,企业微信不保存。原来给成员分配过的 `admin` / `editor` 等角色需要重新分配。
|
|
81
|
+
|
|
82
|
+
- ☐ 进入 **系统管理 → 用户管理**,找到需要管理权限的成员
|
|
83
|
+
- ☐ 通过「**成员资格**」重新分配角色
|
|
84
|
+
- ☐ 在 **系统管理 → 角色权限** 核对自定义角色配置
|
|
85
|
+
|
|
86
|
+
### 第 6 步 验收
|
|
87
|
+
|
|
88
|
+
- ☐ 管理员能登录管理后台
|
|
89
|
+
- ☐ 组织架构的部门数 / 成员数与企业微信通讯录一致
|
|
90
|
+
- ☐ 员工能正常扫码进入
|
|
91
|
+
- ☐ 需要管理权限的成员已重新赋权
|
|
92
|
+
- ☐ 首页图表显示正常
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 遇到问题怎么办
|
|
97
|
+
|
|
98
|
+
| 现象 | 处理方式 |
|
|
99
|
+
|---|---|
|
|
100
|
+
| 授权页提示「应用安装失败,联系服务商」 | 等待 1 分钟后重试一次;仍失败联系服务商 |
|
|
101
|
+
| 同步页提示「该企业为「成员授权」模式」 | 在企业微信后台改为「管理员授权」后,重做第 1 步 |
|
|
102
|
+
| 同步后部门 / 成员数明显偏少 | 检查应用可见范围是否为「全公司」,然后重新同步 |
|
|
103
|
+
| 员工扫码提示「登录凭据无效或不在应用可见范围内」 | 将该成员加入应用可见范围 |
|
|
104
|
+
| 登录页没有「企业微信」页签 | 联系服务商检查登录方式开关 |
|
|
105
|
+
| 管理员忘记口令 | 联系服务商重设 |
|
|
106
|
+
| 其他情况 | 联系服务联系人,并附上**页面截图**与**完整错误提示文字** |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
> 本页仅涉及贵司在企业微信侧与本站管理后台的操作。服务端配置、开通链接生成、初始口令下发由服务商完成,无需客户管理员处理。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oj-module/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.71",
|
|
5
5
|
"description": "React Antd 模块工程命令行工具(含预构建宿主 shell-dist)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@faker-js/faker": "^10.4.0",
|
|
52
|
-
"@oj-module/runtime": "0.2.
|
|
52
|
+
"@oj-module/runtime": "0.2.71",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
55
|
"cac": "^6.7.14",
|
|
@@ -200,10 +200,10 @@ function hr() {
|
|
|
200
200
|
return {
|
|
201
201
|
pkg: {
|
|
202
202
|
name: "@oj-module/runtime",
|
|
203
|
-
version: "0.2.
|
|
203
|
+
version: "0.2.71",
|
|
204
204
|
license: "MIT"
|
|
205
205
|
},
|
|
206
|
-
lastBuildTime: "2026-11-
|
|
206
|
+
lastBuildTime: "2026-11-07 11:30:02"
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
209
|
var gr = w((() => {}));
|
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"alg": "Ed25519",
|
|
3
|
+
"key_id": "platform.release",
|
|
4
|
+
"public_key": "af47abfea1c735e29ed1a00e367fbfbfdfc2264262202dc1185f9af79ede358a",
|
|
5
|
+
"value": "/DLWQjlkaCGmSzRsQtj+UaN+5J1W6OSC5nbPlFiRMua2FQEOmMfjNZsBL6/3wTjkhs4O6wk3QavVhg8IJDxyBA==",
|
|
6
|
+
"canon": "v2",
|
|
7
|
+
"registry_id": "oj-registry"
|
|
8
|
+
}
|
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-vSU6jmm6pmNOfU+t4LVx6A=='; 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="vSU6jmm6pmNOfU+t4LVx6A==">{
|
|
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.7x99hmm020.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="vSU6jmm6pmNOfU+t4LVx6A==" data-ojm-preload="/assets/cssinjs.atgmhan7m0.js,/assets/pro-components.2kyot1vcl0.js,/assets/runtime.7x99hmm020.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.71",
|
|
11
|
+
"@oj-module/runtime/contract": "0.2.71",
|
|
12
|
+
"@oj-module/runtime/contract/errors": "0.2.71",
|
|
13
13
|
"@plane/kit": "0.1.20",
|
|
14
14
|
"@plane/editor": "0.1.20",
|
|
15
15
|
"antd": "6.6.1",
|
|
@@ -53,7 +53,7 @@ describe("listCommand:已装清单", () => {
|
|
|
53
53
|
it("scenario: 缺省只列已装,按名字排序,upgradable / trust_state 原样透出", async () => {
|
|
54
54
|
const { fetchFn, calls } = fetchStub({
|
|
55
55
|
"/api/auth/login": LOGIN,
|
|
56
|
-
"/api/
|
|
56
|
+
"/api/oj-market/index": () => ok(ROWS),
|
|
57
57
|
});
|
|
58
58
|
const rows = await listCommand({ siteUrl: "http://x", user: "u", pass: "p", fetchFn });
|
|
59
59
|
|
|
@@ -67,7 +67,7 @@ describe("listCommand:已装清单", () => {
|
|
|
67
67
|
trustState: "trusted",
|
|
68
68
|
});
|
|
69
69
|
// 登录用的是站点寻址(Bearer 由 callApi 注入,这里只断言两次调用与顺序)
|
|
70
|
-
expect(calls).toEqual(["http://x/api/auth/login", "http://x/api/
|
|
70
|
+
expect(calls).toEqual(["http://x/api/auth/login", "http://x/api/oj-market/index"]);
|
|
71
71
|
|
|
72
72
|
const text = logs.join("\n");
|
|
73
73
|
expect(text).toContain("已装 2 个方案");
|
|
@@ -80,7 +80,7 @@ describe("listCommand:已装清单", () => {
|
|
|
80
80
|
it("scenario: --all 连未装行一起列(installed 显示 -),人话里给两个计数", async () => {
|
|
81
81
|
const { fetchFn } = fetchStub({
|
|
82
82
|
"/api/auth/login": LOGIN,
|
|
83
|
-
"/api/
|
|
83
|
+
"/api/oj-market/index": () => ok(ROWS),
|
|
84
84
|
});
|
|
85
85
|
const rows = await listCommand({ siteUrl: "http://x", user: "u", pass: "p", fetchFn, all: true });
|
|
86
86
|
|
|
@@ -94,7 +94,7 @@ describe("listCommand:已装清单", () => {
|
|
|
94
94
|
it("scenario: 站点上一个方案都没装 → 成功(exit 0 语义),人话指路 --all", async () => {
|
|
95
95
|
const { fetchFn } = fetchStub({
|
|
96
96
|
"/api/auth/login": LOGIN,
|
|
97
|
-
"/api/
|
|
97
|
+
"/api/oj-market/index": () => ok([ROWS[2]]),
|
|
98
98
|
});
|
|
99
99
|
const rows = await listCommand({ siteUrl: "http://x", user: "u", pass: "p", fetchFn });
|
|
100
100
|
expect(rows).toEqual([]);
|
|
@@ -140,7 +140,7 @@ describe("list:CLI 接线与信封", () => {
|
|
|
140
140
|
it("scenario: --json → stdout 只有一行信封,data.solutions 是过滤后的已装行", async () => {
|
|
141
141
|
const { fetchFn } = fetchStub({
|
|
142
142
|
"/api/auth/login": LOGIN,
|
|
143
|
-
"/api/
|
|
143
|
+
"/api/oj-market/index": () => ok(ROWS),
|
|
144
144
|
});
|
|
145
145
|
const r = await run(["list", "--site-url", "http://x", "--user", "u", "--pass", "p", "--json"], fetchFn);
|
|
146
146
|
|
|
@@ -156,7 +156,7 @@ describe("list:CLI 接线与信封", () => {
|
|
|
156
156
|
it("scenario: --all 也进信封(all = true 且包含未装行)", async () => {
|
|
157
157
|
const { fetchFn } = fetchStub({
|
|
158
158
|
"/api/auth/login": LOGIN,
|
|
159
|
-
"/api/
|
|
159
|
+
"/api/oj-market/index": () => ok(ROWS),
|
|
160
160
|
});
|
|
161
161
|
const r = await run(["list", "--site-url", "http://x", "--user", "u", "--pass", "p", "--all", "--json"], fetchFn);
|
|
162
162
|
const envelope = JSON.parse(r.out[0]!) as { data: { all: boolean, solutions: SolutionRow[] } };
|
|
@@ -167,7 +167,7 @@ describe("list:CLI 接线与信封", () => {
|
|
|
167
167
|
it("scenario: 站点报错(索引 503)→ 业务失败(1),错误话说清楚", async () => {
|
|
168
168
|
const { fetchFn } = fetchStub({
|
|
169
169
|
"/api/auth/login": LOGIN,
|
|
170
|
-
"/api/
|
|
170
|
+
"/api/oj-market/index": () => ({ ok: false, status: 503, text: async () => JSON.stringify({ code: 503, msg: "未配置市场地址" }) }),
|
|
171
171
|
});
|
|
172
172
|
const r = await run(["list", "--site-url", "http://x", "--user", "u", "--pass", "p"], fetchFn);
|
|
173
173
|
expect(r.exitCode).toBe(1);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oj.ts 的 healthHostOf:健康轮询跟随 server.host,通配/缺省回退回环。
|
|
3
|
+
* (preview 改绑局域网 IP 时,回环硬编码会让就绪轮询永不成功。)
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { healthHostOf } from "../oj";
|
|
7
|
+
|
|
8
|
+
describe("healthHostOf", () => {
|
|
9
|
+
it.each([
|
|
10
|
+
[undefined, "127.0.0.1"],
|
|
11
|
+
["", "127.0.0.1"],
|
|
12
|
+
["0.0.0.0", "127.0.0.1"],
|
|
13
|
+
["::", "127.0.0.1"],
|
|
14
|
+
["127.0.0.1", "127.0.0.1"],
|
|
15
|
+
["192.168.1.10", "192.168.1.10"],
|
|
16
|
+
])("host=%j → 轮询 %j", (host, expected) => {
|
|
17
|
+
expect(healthHostOf(host)).toBe(expected);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -44,8 +44,8 @@ describe("站点客户端租户头(v3 Phase 0)", () => {
|
|
|
44
44
|
it("scenario: 带 Bearer 的 GET / POST 都带租户头,且不干扰原有头", async () => {
|
|
45
45
|
const { fetchFn, requests } = recordingFetch({ ok: true });
|
|
46
46
|
|
|
47
|
-
await callApi<{ ok: boolean }>(fetchFn, "http://x", "tok", "GET", "/api/
|
|
48
|
-
await callApi<{ ok: boolean }>(fetchFn, "http://x", "tok", "POST", "/api/
|
|
47
|
+
await callApi<{ ok: boolean }>(fetchFn, "http://x", "tok", "GET", "/api/oj-market/trust");
|
|
48
|
+
await callApi<{ ok: boolean }>(fetchFn, "http://x", "tok", "POST", "/api/oj-market/install", { name: "demo" });
|
|
49
49
|
|
|
50
50
|
expect(headers(requests[0]!)).toEqual({ authorization: "Bearer tok", [TENANT_HEADER]: DEFAULT_TENANT });
|
|
51
51
|
expect(headers(requests[1]!)[TENANT_HEADER]).toBe(DEFAULT_TENANT);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 漂移守卫:templates 自带模块名单(src/.ojm-builtin.json)必须与
|
|
3
|
+
* init.include.yaml 白名单的派生结果一致(单一真源是白名单,builtin.ts)。
|
|
4
|
+
*
|
|
5
|
+
* templates 是脚手架源工程自身:ojm init 只给目标工程生成名单文件,
|
|
6
|
+
* 源工程这份是手工补录的(2026-11-06),缺守卫就会与白名单漂移——
|
|
7
|
+
* 白名单新增模块后构建会再次冒出「缺方案契约」误报。
|
|
8
|
+
*
|
|
9
|
+
* Phase B 例外(2026-11-07 裁决):oj-market 在**模板工程**名单里被刻意移除——
|
|
10
|
+
* 市场 UI 模块化后它是第一方方案包(`ojm build --bundle oj-market`,契约
|
|
11
|
+
* src/oj-market/solution.yaml),名单在场会被内置排除拒打包。用户工程名单由
|
|
12
|
+
* init 按白名单派生仍含 oj-market(api 编排面随脚手架内置)——两侧分歧即本测试
|
|
13
|
+
* 钉住的语义,勿「顺手对齐」。
|
|
14
|
+
*/
|
|
15
|
+
import { dirname, resolve } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { describe, expect, it } from "vitest";
|
|
18
|
+
import { BUILTIN_LIST_FILE, deriveBuiltinModules, readBuiltinModules } from "../builtin";
|
|
19
|
+
import { collectDefaultModuleDrift, readInitInclude } from "../scaffold-guard";
|
|
20
|
+
|
|
21
|
+
const templatesRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../templates");
|
|
22
|
+
/** Phase B:第一方打包放行的刻意分歧(见文件头) */
|
|
23
|
+
const FIRST_PARTY_PACKAGE_EXCEPTIONS = ["oj-market"];
|
|
24
|
+
|
|
25
|
+
describe("templates 自带模块名单", () => {
|
|
26
|
+
it("src/.ojm-builtin.json 存在且 = 白名单派生 − 第一方打包例外", () => {
|
|
27
|
+
const derived = [...deriveBuiltinModules(readInitInclude(templatesRoot))]
|
|
28
|
+
.filter(m => !FIRST_PARTY_PACKAGE_EXCEPTIONS.includes(m));
|
|
29
|
+
const actual = readBuiltinModules(templatesRoot);
|
|
30
|
+
expect([...actual].sort()).toEqual([...derived].sort());
|
|
31
|
+
expect(actual.size).toBeGreaterThan(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("覆盖误报当事模块 app;oj-market 被第一方打包例外移除(用户工程名单仍含)", () => {
|
|
35
|
+
const actual = readBuiltinModules(templatesRoot);
|
|
36
|
+
expect(actual.has("app")).toBe(true);
|
|
37
|
+
expect(actual.has("oj-market")).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("名单文件名与 builtin.ts 约定一致", () => {
|
|
41
|
+
expect(BUILTIN_LIST_FILE).toBe(".ojm-builtin.json");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("工程即 templates 自身时脚手架门禁放行(基线 == 自身,demo-only 不算漂移)", () => {
|
|
45
|
+
expect(collectDefaultModuleDrift(templatesRoot)).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -88,7 +88,7 @@ describe("templates 后端 schema.yaml 列白名单", () => {
|
|
|
88
88
|
expect(violations).toEqual([]);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
it("s-2 回归锚点:_platform 基线为 0001、增量 0002
|
|
91
|
+
it("s-2 回归锚点:_platform 基线为 0001、增量 0002(发布历史)/ 0003(市场目录扩列)/ 0004(退役 reg_source_seq)/ 0005(sig_registry_id)三方言齐备,且基线里确有 users.employee_no", () => {
|
|
92
92
|
const migDir = join(srcRoot, "app", "api", "_platform", "migrations");
|
|
93
93
|
for (const file of [
|
|
94
94
|
"0001__baseline.sql",
|
|
@@ -97,13 +97,22 @@ describe("templates 后端 schema.yaml 列白名单", () => {
|
|
|
97
97
|
"0002__market_release.sql",
|
|
98
98
|
"0002__market_release.mysql.sql",
|
|
99
99
|
"0002__market_release.pg.sql",
|
|
100
|
+
"0003__market_catalog.sql",
|
|
101
|
+
"0003__market_catalog.mysql.sql",
|
|
102
|
+
"0003__market_catalog.pg.sql",
|
|
103
|
+
"0004__market_drop_source_seq.sql",
|
|
104
|
+
"0004__market_drop_source_seq.mysql.sql",
|
|
105
|
+
"0004__market_drop_source_seq.pg.sql",
|
|
106
|
+
"0005__market_sig_registry.sql",
|
|
107
|
+
"0005__market_sig_registry.mysql.sql",
|
|
108
|
+
"0005__market_sig_registry.pg.sql",
|
|
100
109
|
])
|
|
101
110
|
expect(existsSync(join(migDir, file)), `缺迁移 ${file}`).toBe(true);
|
|
102
111
|
|
|
103
112
|
// 0001 是压平后的结构基线,已应用过的库不会重跑——新增结构只能追加新序号,
|
|
104
113
|
// 不得回改基线(否则老库拿不到)。序号必须从 0001 起连续、无空洞。
|
|
105
114
|
const seqs = [...new Set(readdirSync(migDir).map(f => f.slice(0, 4)))].sort();
|
|
106
|
-
expect(seqs).toEqual(["0001", "0002"]);
|
|
115
|
+
expect(seqs).toEqual(["0001", "0002", "0003", "0004", "0005"]);
|
|
107
116
|
|
|
108
117
|
const schema = parse(readFileSync(join(srcRoot, "app", "api", "_platform", "schema.yaml"), "utf-8")) as SchemaDoc;
|
|
109
118
|
expect(schema.tables?.users?.columns?.employee_no).toMatchObject({ type: "text" });
|
|
@@ -115,7 +115,7 @@ describe("templates 租户定性(A 租户表 / B 共享表)", () => {
|
|
|
115
115
|
expect(unknown).toEqual([]);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
it("t-4
|
|
118
|
+
it("t-4 28 张表的 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:查它们时必然还没有租户上下文)
|
|
@@ -127,13 +127,12 @@ describe("templates 租户定性(A 租户表 / B 共享表)", () => {
|
|
|
127
127
|
// 2026-11-02 demo 模块下线(ecc7b35e)删掉 demo/api/schema.yaml 的 todos 表,28 → 27。
|
|
128
128
|
// 2026-11 发布凭证加 publish_credential:共享表(CLI 兑换无租户语义,归属走 owner_tenant),27 → 28。
|
|
129
129
|
// 2026-11 发布历史加 market_release:共享表(发布物落在站点级 CDN,历史按站点归口),28 → 29。
|
|
130
|
-
expect(tables.size).toBe(
|
|
130
|
+
expect(tables.size).toBe(28);
|
|
131
131
|
expect(shared).toEqual([
|
|
132
132
|
"_oj_migrations",
|
|
133
133
|
"market_history",
|
|
134
134
|
"market_release",
|
|
135
135
|
"publish_credential",
|
|
136
|
-
"reg_source_seq",
|
|
137
136
|
"sys_login_throttle",
|
|
138
137
|
"sys_permission",
|
|
139
138
|
"sys_rate_window",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用户管理两项特性的纯函数用例(BDD):
|
|
3
|
+
* - matchUserKeyword:/system/users 关键词搜索(用户名 + 显示名)
|
|
4
|
+
* - roleLabel:成员资格/角色页共用的角色语言名解析(title_key → 内置 locale → title → name)
|
|
5
|
+
*
|
|
6
|
+
* 被测代码在 templates 脚手架源里(pages/users、pages/roles),此处经相对路径
|
|
7
|
+
* 直引纯函数文件(无 react/antd 依赖,cli vitest node 环境可直接跑)。
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { roleLabel } from "../../templates/src/app/web/system/pages/roles/role-label";
|
|
11
|
+
import { matchUserKeyword } from "../../templates/src/app/web/system/pages/users/match-keyword";
|
|
12
|
+
|
|
13
|
+
// 测试桩:模拟 i18next 的 t(key 命中表 → 译文;否则 defaultValue ?? key)
|
|
14
|
+
function makeT(dict: Record<string, string>) {
|
|
15
|
+
return (key: string, opts?: { defaultValue?: string }): string => dict[key] ?? opts?.defaultValue ?? key;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("matchUserKeyword:用户关键词搜索", () => {
|
|
19
|
+
const rows = [
|
|
20
|
+
{ username: "zhangsan", display_name: "张三" },
|
|
21
|
+
{ username: "lisi", display_name: null },
|
|
22
|
+
{ username: "admin", display_name: "Admin 张" },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
it.each([
|
|
26
|
+
["zhang", 0], // 命中 username
|
|
27
|
+
["张", 0], // 命中 display_name(中文)
|
|
28
|
+
["张", 2], // display_name 含中文,username 不含
|
|
29
|
+
["ADMIN", 2], // 大小写不敏感
|
|
30
|
+
])("given 关键字 %j when 过滤 then 命中第 %i 行", (kw, idx) => {
|
|
31
|
+
expect(matchUserKeyword(rows[idx]!, kw)).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it.each([
|
|
35
|
+
["zhang", 1], // lisi 行不命中
|
|
36
|
+
["wang", 0],
|
|
37
|
+
])("given 关键字 %j when 过滤 then 第 %i 行不命中", (kw, idx) => {
|
|
38
|
+
expect(matchUserKeyword(rows[idx]!, kw)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("given display_name 为 null when 过滤 then 不抛错且不匹配", () => {
|
|
42
|
+
expect(matchUserKeyword(rows[1]!, "张")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it.each([
|
|
46
|
+
["", true], // 空串 → 全可见
|
|
47
|
+
[" ", true], // 纯空白 trim 后为空 → 全可见
|
|
48
|
+
])("given 空白关键字 %j when 过滤 then %j", (kw, expected) => {
|
|
49
|
+
expect(matchUserKeyword(rows[0]!, kw)).toBe(expected);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("roleLabel:角色语言名解析", () => {
|
|
54
|
+
it("given title_key 且 t 命中 when roleLabel then 返回译文", () => {
|
|
55
|
+
const t = makeT({ "wiki:role.editor": "编辑者" });
|
|
56
|
+
expect(roleLabel({ name: "editor", title: "Editor", title_key: "wiki:role.editor", builtin: false }, t)).toBe("编辑者");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("given title_key 但 t 未命中 when roleLabel then 回退 title", () => {
|
|
60
|
+
const t = makeT({});
|
|
61
|
+
expect(roleLabel({ name: "editor", title: "Editor", title_key: "wiki:role.editor", builtin: false }, t)).toBe("Editor");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("given 内置角色无 title_key when roleLabel then 查 rolesPage.roleTitle locale 键", () => {
|
|
65
|
+
const t = makeT({ "system:rolesPage.roleTitle.admin": "系统管理员" });
|
|
66
|
+
expect(roleLabel({ name: "admin", title: "Admin", title_key: null, builtin: true }, t)).toBe("系统管理员");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("given 内置角色 locale 键未命中 when roleLabel then 回退 title", () => {
|
|
70
|
+
const t = makeT({});
|
|
71
|
+
expect(roleLabel({ name: "boss", title: "Boss", title_key: null, builtin: true }, t)).toBe("Boss");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("given 内置角色 locale 键未命中且 title 为空 when roleLabel then 回退 name", () => {
|
|
75
|
+
const t = makeT({});
|
|
76
|
+
expect(roleLabel({ name: "boss", title: null, title_key: null, builtin: true }, t)).toBe("boss");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("given custom 角色(非内置、无 title_key)when roleLabel then 原样 title,title 空回退 name", () => {
|
|
80
|
+
const t = makeT({});
|
|
81
|
+
expect(roleLabel({ name: "custom.crm", title: "CRM 专员", title_key: null, builtin: false }, t)).toBe("CRM 专员");
|
|
82
|
+
expect(roleLabel({ name: "custom.crm", title: null, title_key: null, builtin: false }, t)).toBe("custom.crm");
|
|
83
|
+
});
|
|
84
|
+
});
|
package/src/bundle.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// 零份用户契约 → 友好报错指向 `ojm new`
|
|
18
18
|
// 模块级契约强校验 name === 目录名(B8;跨契约查重因 B8 不可达已不实现,见计划文档 B7 决策)。
|
|
19
19
|
// 两段式:全部校验(纯读零写盘)先于全部组装——校验失败 dist/ 零增量,
|
|
20
|
-
//
|
|
20
|
+
// 发布者不会把半截批次直接推上市场(remote-push 整包推送,服务端 pair 校验落库)。
|
|
21
21
|
//
|
|
22
22
|
// 组装映射(对齐 P1-9 staging copy 源一层定版,unpack 布局即安装器 extract 段消费面):
|
|
23
23
|
// <project>/solution.yaml | manifest.yaml → 工程根契约(旧单方案写法)
|
|
@@ -9,7 +9,7 @@ import type { ScopedRequestLike } from "@oj-module/runtime/contract/errors";
|
|
|
9
9
|
// 不加载 @oj-module/runtime 重依赖(antd / pro-components 在 node 环境无法 import),仅桩 request、
|
|
10
10
|
// 透传真实 zod(供生成 client 的 DEV 响应校验走真实路径)。ContractApiError 由轻量子路径提供。
|
|
11
11
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
12
|
-
import { bindRequest } from "../../../templates/src/
|
|
12
|
+
import { bindRequest } from "../../../templates/src/oj-market/web/__client";
|
|
13
13
|
import { httpMarketClient } from "../../../templates/src/oj-market/web/api/defaultClient";
|
|
14
14
|
|
|
15
15
|
// index() 直连 request(不走生成 client),故把桩 request 做成可变实现,测试内统一指向同一 mock。
|
|
@@ -78,39 +78,40 @@ describe("httpMarketClient · 统一委托生成 client 集成回归", () => {
|
|
|
78
78
|
bindMock(mock);
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
it("meta() → GET
|
|
81
|
+
it("meta() → GET oj-market/meta,返回 { apiVersion }", async () => {
|
|
82
82
|
mock = mockRequest(() => ({ data: { apiVersion: "1.2.3" } }));
|
|
83
83
|
bindMock(mock);
|
|
84
84
|
const r = await httpMarketClient.meta();
|
|
85
|
-
expect(mock.calls).toEqual([{ method: "get", url: "
|
|
85
|
+
expect(mock.calls).toEqual([{ method: "get", url: "oj-market/meta", opts: undefined }]);
|
|
86
86
|
expect(r).toEqual({ apiVersion: "1.2.3" });
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
it("index() → GET
|
|
89
|
+
it("index() → GET oj-market/index,返回分页 IndexPage", async () => {
|
|
90
90
|
const rows = [{ name: "a", latest: "1.0", installed: "1.0", enabled: true, upgradable: false }];
|
|
91
|
-
|
|
91
|
+
const page = { list: rows, total: 1, page: 1, size: 20 };
|
|
92
|
+
mock = mockRequest(() => ({ data: page }));
|
|
92
93
|
bindMock(mock);
|
|
93
94
|
const r = await httpMarketClient.index();
|
|
94
|
-
expect(mock.calls[0]).toMatchObject({ method: "get", url: "
|
|
95
|
-
expect(r).toEqual(
|
|
95
|
+
expect(mock.calls[0]).toMatchObject({ method: "get", url: "oj-market/index" });
|
|
96
|
+
expect(r).toEqual(page);
|
|
96
97
|
});
|
|
97
98
|
|
|
98
|
-
it("index() 遇 503(未配置市场地址)→
|
|
99
|
-
mock = mockRequest(c => (c.url === "
|
|
99
|
+
it("index() 遇 503(未配置市场地址)→ 空分页空态,不抛(PRD §5.3)", async () => {
|
|
100
|
+
mock = mockRequest(c => (c.url === "oj-market/index" ? { status: 503 } : { data: {} }));
|
|
100
101
|
bindMock(mock);
|
|
101
102
|
const r = await httpMarketClient.index();
|
|
102
|
-
expect(r).toEqual([]);
|
|
103
|
+
expect(r).toEqual({ list: [], total: 0, page: 1, size: 20 });
|
|
103
104
|
// 503 是配置态而非故障:silentError 抑制全局 toast + retry:0 不重试(原
|
|
104
105
|
// market-client-unconfigured 用例的断言并入此处——工厂形态退役后唯一活口)
|
|
105
106
|
expect(mock.calls).toHaveLength(1);
|
|
106
107
|
expect(mock.calls[0].opts).toMatchObject({ silentError: true, retry: 0 });
|
|
107
108
|
});
|
|
108
109
|
|
|
109
|
-
it("install(input) → POST
|
|
110
|
+
it("install(input) → POST oj-market/install,body 含 name/version/source", async () => {
|
|
110
111
|
mock = mockRequest(() => ({ data: { task_id: "t-1" } }));
|
|
111
112
|
bindMock(mock);
|
|
112
113
|
const r = await httpMarketClient.install({ name: "alpha", version: "2.0", source: "file" });
|
|
113
|
-
expect(mock.calls[0]).toMatchObject({ method: "post", url: "
|
|
114
|
+
expect(mock.calls[0]).toMatchObject({ method: "post", url: "oj-market/install" });
|
|
114
115
|
expect(mock.calls[0].opts?.json).toEqual({ name: "alpha", version: "2.0", source: "file" });
|
|
115
116
|
expect(r).toEqual({ task_id: "t-1" });
|
|
116
117
|
});
|
|
@@ -122,48 +123,48 @@ describe("httpMarketClient · 统一委托生成 client 集成回归", () => {
|
|
|
122
123
|
expect(mock.calls[0].opts?.json).toEqual({ name: "beta", version: null, source: "cdn" });
|
|
123
124
|
});
|
|
124
125
|
|
|
125
|
-
it("task(id) → GET
|
|
126
|
+
it("task(id) → GET oj-market/task? 带 id", async () => {
|
|
126
127
|
const rec = { id: "t-1", name: "a", version: "1.0", status: "done", stage: "done", progress: 100, message: "ok", stages: [], startedAt: 1 };
|
|
127
128
|
mock = mockRequest(() => ({ data: rec }));
|
|
128
129
|
bindMock(mock);
|
|
129
130
|
const r = await httpMarketClient.task("t-1");
|
|
130
|
-
expect(mock.calls[0]).toMatchObject({ method: "get", url: "
|
|
131
|
+
expect(mock.calls[0]).toMatchObject({ method: "get", url: "oj-market/task" });
|
|
131
132
|
expect(mock.calls[0].opts?.searchParams).toEqual({ id: "t-1" });
|
|
132
133
|
expect(r).toEqual(rec);
|
|
133
134
|
});
|
|
134
135
|
|
|
135
|
-
it("enable(input) → POST
|
|
136
|
+
it("enable(input) → POST oj-market/enable,body {name,enabled}", async () => {
|
|
136
137
|
mock = mockRequest(() => ({ data: { ok: true, message: "done" } }));
|
|
137
138
|
bindMock(mock);
|
|
138
139
|
const r = await httpMarketClient.enable({ name: "a", enabled: false });
|
|
139
|
-
expect(mock.calls[0]).toMatchObject({ method: "post", url: "
|
|
140
|
+
expect(mock.calls[0]).toMatchObject({ method: "post", url: "oj-market/enable" });
|
|
140
141
|
expect(mock.calls[0].opts?.json).toEqual({ name: "a", enabled: false });
|
|
141
142
|
expect(r).toEqual({ ok: true, message: "done" });
|
|
142
143
|
});
|
|
143
144
|
|
|
144
|
-
it("history() → GET
|
|
145
|
+
it("history() → GET oj-market/history,返回 MarketHistory[]", async () => {
|
|
145
146
|
const hist = [{ id: 1, solution: "a", version: "1.0", action: "install", status: "ok", operator: null, message: null, created_at: 1 }];
|
|
146
147
|
mock = mockRequest(() => ({ data: hist }));
|
|
147
148
|
bindMock(mock);
|
|
148
149
|
const r = await httpMarketClient.history();
|
|
149
|
-
expect(mock.calls[0]).toMatchObject({ method: "get", url: "
|
|
150
|
+
expect(mock.calls[0]).toMatchObject({ method: "get", url: "oj-market/history" });
|
|
150
151
|
expect(r).toEqual(hist);
|
|
151
152
|
});
|
|
152
153
|
|
|
153
|
-
it("uninstallImpact(name) → GET
|
|
154
|
+
it("uninstallImpact(name) → GET oj-market/uninstall-impact? 带 name", async () => {
|
|
154
155
|
mock = mockRequest(() => ({ data: { name: "a", affectedRoles: 2, affectedUsers: 5 } }));
|
|
155
156
|
bindMock(mock);
|
|
156
157
|
const r = await httpMarketClient.uninstallImpact("a");
|
|
157
|
-
expect(mock.calls[0]).toMatchObject({ method: "get", url: "
|
|
158
|
+
expect(mock.calls[0]).toMatchObject({ method: "get", url: "oj-market/uninstall-impact" });
|
|
158
159
|
expect(mock.calls[0].opts?.searchParams).toEqual({ name: "a" });
|
|
159
160
|
expect(r).toEqual({ name: "a", affectedRoles: 2, affectedUsers: 5 });
|
|
160
161
|
});
|
|
161
162
|
|
|
162
|
-
it("uninstall(name) → POST
|
|
163
|
+
it("uninstall(name) → POST oj-market/uninstall,body {name}", async () => {
|
|
163
164
|
mock = mockRequest(() => ({ data: { ok: true, message: "removed" } }));
|
|
164
165
|
bindMock(mock);
|
|
165
166
|
const r = await httpMarketClient.uninstall("a");
|
|
166
|
-
expect(mock.calls[0]).toMatchObject({ method: "post", url: "
|
|
167
|
+
expect(mock.calls[0]).toMatchObject({ method: "post", url: "oj-market/uninstall" });
|
|
167
168
|
expect(mock.calls[0].opts?.json).toEqual({ name: "a" });
|
|
168
169
|
expect(r).toEqual({ ok: true, message: "removed" });
|
|
169
170
|
});
|