@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 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`,缺则构建期 fail-fast)。旧 `menu.group` key 已废弃。
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 开发者的该做 / 别做
@@ -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.17",
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.17",
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.17",
306
+ "version": "0.2.18",
307
307
  "license": "MIT"
308
308
  },
309
- "lastBuildTime": "2026-10-21 21:54:09"
309
+ "lastBuildTime": "2026-10-21 22:40:24"
310
310
  };
311
311
  }
312
312
  var init_get_app_info = __esmMin((() => {}));
@@ -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-NKdPzzWoLWlLgH/F6SaXrg=='; 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" />
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="NKdPzzWoLWlLgH/F6SaXrg==">{
10
+ <script type="importmap" nonce="NpuwH1G8uW9SNQdyFsPFYQ==">{
11
11
  "imports": {
12
12
  "react": "/assets/react.js",
13
13
  "react/jsx-runtime": "/assets/jsx-runtime.js",
@@ -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.17",
11
- "@oj-module/runtime/contract": "0.2.17",
12
- "@oj-module/runtime/contract/errors": "0.2.17",
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, groupI18nKeys, parseManifest, validateContract } from "../../manifest-contract";
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
- // ── C2-43 归组父标题 i18n(solution menu grouping;纯内容入参)──
217
+ // ── 归组父标题不再进引用面(0.2.18:`menu.main` 是模块级标题的唯一真源)──
218
218
 
219
- describe("c2-43 归组父标题 i18n(solution menu grouping)", () => {
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("groupI18nKeys:声明 main/admin 路由才产一条 <name>:menu.group", () => {
231
- expect(groupI18nKeys(baseManifest())).toEqual(["demo-solution:menu.group"]);
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("groupI18nKeys:多条 main 路由去重只产一条(C5)", () => {
235
- const m = baseManifest();
236
- m.routes = [
237
- { path: "/a", category: "main", order: 200, title_key: "demo-solution:menu.a" },
238
- { path: "/b", category: "main", order: 210, title_key: "demo-solution:menu.b" },
239
- ];
240
- expect(groupI18nKeys(m)).toEqual(["demo-solution:menu.group"]);
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
 
@@ -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;本文件新增 C2-43);
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.17",
12
- "@oj-module/runtime/contract": "0.2.17",
13
- "@oj-module/runtime/contract/errors": "0.2.17",
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",