@oj-module/cli 0.2.17 → 0.2.18
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/manual.md +1 -1
- package/devkit/menu-rules.md +0 -1
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +2 -2
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/lifecycle/__tests__/manifest-contract.test.ts +15 -56
- package/src/manifest-contract.ts +2 -25
- package/vendor/host-versions.json +3 -3
package/devkit/manual.md
CHANGED
|
@@ -1837,7 +1837,7 @@ export default defineModule({
|
|
|
1837
1837
|
|
|
1838
1838
|
### 13.3 menu.main 统一 + 路径助手
|
|
1839
1839
|
|
|
1840
|
-
- 模块根菜单标题与 /system 下的管理子组标题**共用** `<模块>:menu.main` 一个 i18n key(嵌套在 `menu.main
|
|
1840
|
+
- 模块根菜单标题与 /system 下的管理子组标题**共用** `<模块>:menu.main` 一个 i18n key(嵌套在 `menu.main`,是模块级标题的唯一真源,缺则构建期 fail-fast)。
|
|
1841
1841
|
- 模块内所有前端路径——`navigate` / `Link to` / `useMatch`·`matchPath` / `currentActiveMenu` / SEO 正则——统一从模块基路径助手(如 `paths.ts` 的 `wikiPath(...)` / `wikiBase`)派生,不散落 `/wiki/...` 字面量;`currentActiveMenu` 必须写绝对路径(指向真实菜单 key)。`tree/bus.ts` 的 room 名是后端 WS 契约,非前端路由,不动。
|
|
1842
1842
|
|
|
1843
1843
|
### 13.4 开发者的该做 / 别做
|
package/devkit/menu-rules.md
CHANGED
|
@@ -156,7 +156,6 @@ export default defineModule({
|
|
|
156
156
|
- 必须**嵌套**在 `menu.main`,不能平铺成 `"<模块名>:menu.main": "..."`(i18next 点路径取值)。
|
|
157
157
|
- 缺这条 key → 构建期契约校验直接 fail(构建期 fail-fast)。
|
|
158
158
|
- 普通子项(space / status / collections 等)的 `title` 仍各自匹配 `<模块名>:menu.*`(如 `wiki:menu.space`)。
|
|
159
|
-
- 旧设计的 `menu.group` key 已废弃:`menu.main` 取代它成为根标题与管理子组标题的统一真源。
|
|
160
159
|
|
|
161
160
|
---
|
|
162
161
|
|
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.18",
|
|
5
5
|
"description": "React Antd Admin 模块工程命令行工具(含预构建宿主 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.18",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
55
|
"esbuild": "^0.28.2",
|
|
@@ -303,10 +303,10 @@ function getAppInfo() {
|
|
|
303
303
|
return {
|
|
304
304
|
"pkg": {
|
|
305
305
|
"name": "@oj-module/runtime",
|
|
306
|
-
"version": "0.2.
|
|
306
|
+
"version": "0.2.18",
|
|
307
307
|
"license": "MIT"
|
|
308
308
|
},
|
|
309
|
-
"lastBuildTime": "2026-10-21
|
|
309
|
+
"lastBuildTime": "2026-10-21 22:40:24"
|
|
310
310
|
};
|
|
311
311
|
}
|
|
312
312
|
var init_get_app_info = __esmMin((() => {}));
|
package/shell-dist/index.html
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
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-
|
|
8
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-NpuwH1G8uW9SNQdyFsPFYQ=='; 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
9
|
|
|
10
|
-
<script type="importmap" nonce="
|
|
10
|
+
<script type="importmap" nonce="NpuwH1G8uW9SNQdyFsPFYQ==">{
|
|
11
11
|
"imports": {
|
|
12
12
|
"react": "/assets/react.js",
|
|
13
13
|
"react/jsx-runtime": "/assets/jsx-runtime.js",
|
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.18",
|
|
11
|
+
"@oj-module/runtime/contract": "0.2.18",
|
|
12
|
+
"@oj-module/runtime/contract/errors": "0.2.18",
|
|
13
13
|
"@plane/kit": "0.1.20",
|
|
14
14
|
"@plane/editor": "0.1.20",
|
|
15
15
|
"antd": "6.6.1",
|
|
@@ -14,7 +14,7 @@ import { parse } from "yaml";
|
|
|
14
14
|
// 跨端引用(双端一致钉):被测实现同仓分居两运行时(计划 §1「两运行时取舍」),
|
|
15
15
|
// 测试直接互调,比「两套件各读同一夹具」更强——漂移即本文件红。
|
|
16
16
|
import { preflight } from "../../../templates/api/src/_platform/_lifecycle/steps/preflight";
|
|
17
|
-
import { checkLocalesComplete, checkTablesAgainstSchema,
|
|
17
|
+
import { checkLocalesComplete, checkTablesAgainstSchema, parseManifest, validateContract } from "../../manifest-contract";
|
|
18
18
|
|
|
19
19
|
const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "fixtures");
|
|
20
20
|
const readText = (p: string) => readFileSync(join(FIXTURES, p), "utf8");
|
|
@@ -214,72 +214,31 @@ describe("checkLocalesComplete(C2-25:构建期缺 key fail-fast)", () => {
|
|
|
214
214
|
});
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
-
// ──
|
|
217
|
+
// ── 归组父标题不再进引用面(0.2.18:`menu.main` 是模块级标题的唯一真源)──
|
|
218
218
|
|
|
219
|
-
describe("
|
|
219
|
+
describe("main/admin 路由不再要求归组父标题", () => {
|
|
220
220
|
const baseManifest = (): any => ({
|
|
221
221
|
name: "demo-solution",
|
|
222
222
|
version: "0.1.0",
|
|
223
223
|
routes: [
|
|
224
224
|
{ path: "/demo-solution", category: "main", order: 200, title_key: "demo-solution:menu.main" },
|
|
225
|
+
{ path: "/demo-solution/users", category: "admin", order: 210, title_key: "demo-solution:menu.users" },
|
|
225
226
|
],
|
|
226
227
|
});
|
|
227
|
-
const ZH_FULL = JSON.stringify({ menu: { main: "主", group: "分组" } });
|
|
228
|
-
const EN_FULL = JSON.stringify({ menu: { main: "Main", group: "Group" } });
|
|
229
228
|
|
|
230
|
-
it("
|
|
231
|
-
|
|
229
|
+
it("声明 main/admin 路由、locale 只有 menu.main → 零违规", () => {
|
|
230
|
+
const zh = JSON.stringify({ menu: { main: "主", users: "用户" } });
|
|
231
|
+
const en = JSON.stringify({ menu: { main: "Main", users: "Users" } });
|
|
232
|
+
expect(checkLocalesComplete(baseManifest(), zh, en)).toEqual([]);
|
|
232
233
|
});
|
|
233
234
|
|
|
234
|
-
it("
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
];
|
|
240
|
-
expect(
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("groupI18nKeys:仅 hidden 路由不产 group key(C3)", () => {
|
|
244
|
-
const m = baseManifest();
|
|
245
|
-
m.routes = [{ path: "/x", category: "hidden", order: 210, title_key: "demo-solution:menu.x" }];
|
|
246
|
-
expect(groupI18nKeys(m)).toEqual([]);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("c1 正例:menu.group 双语齐备 → 零违规", () => {
|
|
250
|
-
expect(checkLocalesComplete(baseManifest(), ZH_FULL, EN_FULL)).toEqual([]);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it("c2 反例:zh-CN 缺 menu.group → C2-43(非 C2-25,去重生效)", () => {
|
|
254
|
-
const zh = JSON.stringify({ menu: { main: "主" } });
|
|
255
|
-
const v = checkLocalesComplete(baseManifest(), zh, EN_FULL);
|
|
256
|
-
const hit = v.find(x => x.includes("C2-43"));
|
|
257
|
-
expect(hit, `期望 C2-43,实际:\n${v.join("\n")}`).toBeTruthy();
|
|
258
|
-
expect(hit).not.toContain("C2-25");
|
|
259
|
-
expect(hit).toContain("menu.group");
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it("c4 反例:en-US 缺 menu.group → C2-43 指明 en-US", () => {
|
|
263
|
-
const en = JSON.stringify({ menu: { main: "Main" } });
|
|
264
|
-
const v = checkLocalesComplete(baseManifest(), ZH_FULL, en);
|
|
265
|
-
const hit = v.find(x => x.includes("C2-43"));
|
|
266
|
-
expect(hit).toBeTruthy();
|
|
267
|
-
expect(hit).toContain("en-US");
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it("去重:多条 main 路由只产一条 group key,缺失时按语言各报一次(非按路由重复)", () => {
|
|
271
|
-
const m = baseManifest();
|
|
272
|
-
m.routes = [
|
|
273
|
-
{ path: "/a", category: "main", order: 200, title_key: "demo-solution:menu.a" },
|
|
274
|
-
{ path: "/b", category: "main", order: 210, title_key: "demo-solution:menu.b" },
|
|
275
|
-
];
|
|
276
|
-
const v = checkLocalesComplete(m, JSON.stringify({ menu: { main: "主" } }), JSON.stringify({ menu: { main: "Main" } }));
|
|
277
|
-
const c243 = v.filter(x => x.includes("C2-43"));
|
|
278
|
-
// 关键:2 条路由只产 1 个 group key → 缺失时按语言各报 1 次 = 2 条 C2-43,
|
|
279
|
-
// 而非 2 路由 × 2 语言 = 4 条(去重生效的判据是「< 路由数 × 语言数」)
|
|
280
|
-
expect(c243.length).toBe(2);
|
|
281
|
-
expect(c243.some(x => x.includes("zh-CN 缺少归组父标题 key「demo-solution:menu.group」"))).toBe(true);
|
|
282
|
-
expect(c243.some(x => x.includes("en-US 缺少归组父标题 key「demo-solution:menu.group」"))).toBe(true);
|
|
235
|
+
it("根标题 menu.main 仍受 C2-25 约束:缺则报 C2-25", () => {
|
|
236
|
+
const zh = JSON.stringify({ menu: { users: "用户" } });
|
|
237
|
+
const en = JSON.stringify({ menu: { main: "Main", users: "Users" } });
|
|
238
|
+
const v = checkLocalesComplete(baseManifest(), zh, en);
|
|
239
|
+
expect(v).toHaveLength(1);
|
|
240
|
+
expect(v[0]).toContain("C2-25");
|
|
241
|
+
expect(v[0]).toContain("demo-solution:menu.main");
|
|
283
242
|
});
|
|
284
243
|
});
|
|
285
244
|
|
package/src/manifest-contract.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// bundle 级 manifest.yaml 契约校验(P1-11,`ojm build --bundle` 构建期执行点)。
|
|
2
|
-
// 规则真源:docs/prd/solution/202610171254-lifecycle-manifest-contract.md §2/§4(C2-01~33 构建列;C2-34~42 为归档签名与信任,见 sign.ts
|
|
2
|
+
// 规则真源:docs/prd/solution/202610171254-lifecycle-manifest-contract.md §2/§4(C2-01~33 构建列;C2-34~42 为归档签名与信任,见 sign.ts);
|
|
3
3
|
// 期望真源:src/lifecycle/__tests__/fixtures/(fixture-valid 零违规 / fixture-invalid 七码齐报)。
|
|
4
4
|
// 双端一致:本实现与 isolate 侧 preflight(templates/api/.../steps/preflight.ts)共享同一组夹具,
|
|
5
5
|
// 正则与规则**逐字一致**(§4.1 矩阵构建列 vs 安装列的执行点差异见各规则注释)——
|
|
@@ -581,15 +581,6 @@ export function validateContract(m: unknown): string[] {
|
|
|
581
581
|
return v0;
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
-
/** C2-43 引用面:声明 main/admin 路由的方案须提供归组父标题 `<name>:menu.group`(单 bundle 单 name,去重只产一条) */
|
|
585
|
-
export function groupI18nKeys(m: BundleManifest): string[] {
|
|
586
|
-
if (!isObj(m) || typeof m.name !== "string")
|
|
587
|
-
return [];
|
|
588
|
-
const routes = Array.isArray(m.routes) ? m.routes : [];
|
|
589
|
-
const hasGroupable = routes.some(r => isObj(r) && (r.category === "main" || r.category === "admin"));
|
|
590
|
-
return hasGroupable ? [`${m.name}:menu.group`] : [];
|
|
591
|
-
}
|
|
592
|
-
|
|
593
584
|
/** 收集 manifest 引用的全部 i18n key(C2-25 locales 齐备检查的引用面;bundle.ts 以此判定 locales 文件是否必查) */
|
|
594
585
|
export function referencedI18nKeys(m: BundleManifest): string[] {
|
|
595
586
|
const keys: string[] = [];
|
|
@@ -620,10 +611,6 @@ export function referencedI18nKeys(m: BundleManifest): string[] {
|
|
|
620
611
|
}
|
|
621
612
|
}
|
|
622
613
|
}
|
|
623
|
-
// C2-43:归组父标题纳入引用面,交由 checkLocalesComplete 双语齐备(改报 C2-43)
|
|
624
|
-
for (const k of groupI18nKeys(m)) {
|
|
625
|
-
keys.push(k);
|
|
626
|
-
}
|
|
627
614
|
return keys;
|
|
628
615
|
}
|
|
629
616
|
|
|
@@ -646,12 +633,9 @@ export function checkLocalesComplete(m: unknown, zhText: string, enText: string)
|
|
|
646
633
|
if (!isObj(m))
|
|
647
634
|
return [v("C2-25", "manifest 不是键值对象,无法核对 locales 引用")];
|
|
648
635
|
const locales: [string, Record<string, unknown> | null][] = [["zh-CN", parseJson(zhText)], ["en-US", parseJson(enText)]];
|
|
649
|
-
// group key 识别(C2-43):归组父标题缺失须归因 C2-43 而非笼统 C2-25,避免重复报错
|
|
650
|
-
const groupKeys = new Set(groupI18nKeys(m));
|
|
651
636
|
const out: string[] = [];
|
|
652
637
|
for (const key of referencedI18nKeys(m)) {
|
|
653
638
|
const nsKey = key.includes(":") ? key.slice(key.indexOf(":") + 1) : key;
|
|
654
|
-
const isGroup = groupKeys.has(key);
|
|
655
639
|
for (const [lang, tree] of locales) {
|
|
656
640
|
if (tree === null) {
|
|
657
641
|
out.push(v("C2-25", `${lang} locale 不是合法 JSON 对象,无法核对引用 key`));
|
|
@@ -659,14 +643,7 @@ export function checkLocalesComplete(m: unknown, zhText: string, enText: string)
|
|
|
659
643
|
}
|
|
660
644
|
const val = lookupKey(tree, nsKey);
|
|
661
645
|
if (typeof val !== "string" || val === "") {
|
|
662
|
-
out.push(
|
|
663
|
-
v(
|
|
664
|
-
isGroup ? "C2-43" : "C2-25",
|
|
665
|
-
isGroup
|
|
666
|
-
? `${lang} 缺少归组父标题 key「${key}」(须为嵌套 menu.group,C2-43;声明 main/admin 路由即须提供)`
|
|
667
|
-
: `${lang} 缺少 manifest 引用的 key「${key}」(双语齐备,D4;构建期 fail-fast)`,
|
|
668
|
-
),
|
|
669
|
-
);
|
|
646
|
+
out.push(v("C2-25", `${lang} 缺少 manifest 引用的 key「${key}」(双语齐备,D4;构建期 fail-fast)`));
|
|
670
647
|
}
|
|
671
648
|
}
|
|
672
649
|
}
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"react-router": "7.18.2",
|
|
9
9
|
"react-router/dom": "7.18.2",
|
|
10
10
|
"@tanstack/react-query": "5.102.8",
|
|
11
|
-
"@oj-module/runtime": "0.2.
|
|
12
|
-
"@oj-module/runtime/contract": "0.2.
|
|
13
|
-
"@oj-module/runtime/contract/errors": "0.2.
|
|
11
|
+
"@oj-module/runtime": "0.2.18",
|
|
12
|
+
"@oj-module/runtime/contract": "0.2.18",
|
|
13
|
+
"@oj-module/runtime/contract/errors": "0.2.18",
|
|
14
14
|
"@plane/kit": "0.1.20",
|
|
15
15
|
"@plane/editor": "0.1.20",
|
|
16
16
|
"antd": "6.6.1",
|