openxiangda 1.0.14 → 1.0.16

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/lib/cli.js CHANGED
@@ -1322,12 +1322,14 @@ async function permission(args) {
1322
1322
  const [groupCode] = positional;
1323
1323
  const name = flags.name || positional.slice(1).join(' ') || groupCode;
1324
1324
  if (!groupCode || !name) {
1325
- fail('用法: openxiangda permission page-group-create <groupCode> --name <text> [--menu-codes <menuCode...>|--page-codes <pageCode...>|--form-codes <formCode...>|--menu-ids <id...>]');
1325
+ fail('用法: openxiangda permission page-group-create <groupCode> --name <text> [--menu-codes <menuCode...>|--page-codes <pageCode...>|--form-codes <formCode...>|--menu-ids <id...>] [--no-runtime-aliases]');
1326
1326
  }
1327
1327
  const target = getWorkspaceTarget(config, profileName, flags);
1328
1328
  const roles = splitList(flags.roles);
1329
- const menuFormUuids = resolvePagePermissionMenuTargets(target.bound, flags);
1330
- const runtimeAliasTargets = resolvePagePermissionRuntimeAliasTargets(target.bound, flags);
1329
+ const editableTargets = resolvePagePermissionMenuTargets(target.bound, flags);
1330
+ const runtimeAliasTargets = flags['no-runtime-aliases']
1331
+ ? []
1332
+ : resolvePagePermissionRuntimeAliasTargets(target.bound, flags);
1331
1333
  const data = await requestWithAuth(
1332
1334
  config,
1333
1335
  target.profileName,
@@ -1337,14 +1339,13 @@ async function permission(args) {
1337
1339
  body: {
1338
1340
  name,
1339
1341
  roles,
1340
- menuFormUuids,
1342
+ menuFormUuids: editableTargets,
1341
1343
  },
1342
1344
  }
1343
1345
  );
1344
1346
  if (data?.id) savePagePermissionGroupResource(target, groupCode, data.id, { name });
1345
1347
  let runtimeAliasData = null;
1346
1348
  if (runtimeAliasTargets.length > 0) {
1347
- const runtimeAliasGroupCode = `${groupCode}__runtime_aliases`;
1348
1349
  const runtimeAliasName = `${name}(运行时别名)`;
1349
1350
  runtimeAliasData = await requestWithAuth(
1350
1351
  config,
@@ -1360,12 +1361,14 @@ async function permission(args) {
1360
1361
  }
1361
1362
  );
1362
1363
  if (runtimeAliasData?.id) {
1363
- savePagePermissionGroupResource(target, runtimeAliasGroupCode, runtimeAliasData.id, {
1364
+ savePagePermissionGroupResource(target, `${groupCode}__runtime_aliases`, runtimeAliasData.id, {
1364
1365
  name: runtimeAliasName,
1365
1366
  });
1366
1367
  }
1367
1368
  }
1368
- const output = runtimeAliasData ? { ...data, runtimeAliasGroup: runtimeAliasData } : data;
1369
+ const output = runtimeAliasData
1370
+ ? { editableGroup: data, runtimeAliasGroup: runtimeAliasData }
1371
+ : data;
1369
1372
  if (flags.json) return writeJson(output);
1370
1373
  print(JSON.stringify(output, null, 2));
1371
1374
  return;
@@ -1946,7 +1949,6 @@ function resolvePagePermissionMenuTargets(bound, flags = {}) {
1946
1949
  function resolveMenuPermissionTargets(bound, menuCode) {
1947
1950
  const menuEntry = bound.resources?.menus?.[menuCode];
1948
1951
  if (menuEntry?.formUuid) return [menuEntry.formUuid];
1949
- if (menuEntry?.pageId && menuEntry?.menuId) return [menuEntry.menuId];
1950
1952
  return [resolveMenuId(bound, menuCode)];
1951
1953
  }
1952
1954
 
@@ -373,7 +373,7 @@ Body:
373
373
  }
374
374
  ```
375
375
 
376
- An empty `menuFormUuids` array means all menus/pages are visible to the matched roles. For form menus this field can contain form UUIDs. For custom code page menus, the user-facing permission group should contain the platform menu ID, which is what the platform permission editor uses for tree check state. Because some `/view` runtime guards also check page IDs, route keys, and legacy `PAGE_...` aliases, OpenXiangda CLI `--page-codes` / `--menu-codes` creates a companion `(运行时别名)` page permission group for those aliases instead of mixing them into the editable menu group.
376
+ An empty `menuFormUuids` array means all menus/pages are visible to the matched roles. For form menus this field can contain form UUIDs. For custom code page menus, the editable permission group should contain the menu ID, which is what the platform permission editor uses for tree check state. OpenXiangda CLI resolves `--page-codes` / `--menu-codes` to an editable menu-ID group and creates a companion `(运行时别名)` group for required page ID, route key, and legacy `PAGE_...` runtime aliases by default. This keeps the main group editable while satisfying `/view` runtime guards. Use `--no-runtime-aliases` only for deployments whose runtime checks menu IDs directly.
377
377
 
378
378
  ### GET `/apps/:appType/page-permission-groups/:groupId`
379
379
 
@@ -19,9 +19,10 @@ Role IDs are platform-specific. Store them only in `.openxiangda/state.json` und
19
19
  ## Page Permission Groups
20
20
 
21
21
  Page permission groups map role codes to visible menu targets. Form menus use their `FORM_...`
22
- form UUIDs. Custom code page/display menus need split targets because current platform
23
- surfaces check different identifiers: the admin edit tree expects menu IDs, while some `/view`
24
- runtime guards check page IDs, route keys, and legacy `PAGE_...` aliases.
22
+ form UUIDs. Custom code page/display menus should include menu IDs because the platform
23
+ permission editor uses menu IDs for tree check state. Some `/view` runtime guards also require
24
+ page ID, route key, and legacy `PAGE_...` aliases; keep those aliases in the same permission
25
+ group so the platform tree can still round-trip the menu selection.
25
26
 
26
27
  ```json
27
28
  {
@@ -31,8 +32,19 @@ runtime guards check page IDs, route keys, and legacy `PAGE_...` aliases.
31
32
  }
32
33
  ```
33
34
 
34
- For custom code pages, keep the user-facing group editable by storing only menu IDs, then add a
35
- companion runtime alias group with the same roles:
35
+ For custom code pages, keep the editable menu tree targets separate from runtime aliases. The
36
+ platform permission editor only reliably checks real menu IDs, while `/view` runtime guards on
37
+ some private deployments still check the code page id, route key, or legacy `PAGE_...` id. When
38
+ `--page-codes` or `--menu-codes` is used, OpenXiangda creates an editable group plus a companion
39
+ runtime alias group:
40
+
41
+ ```json
42
+ {
43
+ "name": "销售页面",
44
+ "roles": ["sales"],
45
+ "menuFormUuids": ["MENU_ID_FOR_CODE_PAGE"]
46
+ }
47
+ ```
36
48
 
37
49
  ```json
38
50
  {
@@ -47,7 +59,7 @@ Rules:
47
59
  - `roles: []` means all roles can match.
48
60
  - `menuFormUuids: []` means all menus/pages are visible to matched roles.
49
61
  - Prefer `--form-codes` in CLI for form menus so each profile resolves its own form UUIDs.
50
- - Prefer `--page-codes` or `--menu-codes` in CLI for custom code page menus. The CLI writes menu IDs to the editable group and creates the companion `(运行时别名)` group automatically.
62
+ - Prefer `--page-codes` or `--menu-codes` in CLI for custom code page menus. The primary group remains editable because it contains only menu IDs. Pass `--no-runtime-aliases` only after confirming the target platform checks menu IDs directly at runtime.
51
63
 
52
64
  ## Form Permission Groups
53
65
 
@@ -43,7 +43,7 @@ openxiangda permission page-group-create sales_pages --name "销售页面" --rol
43
43
  openxiangda permission page-group-create portal_pages --name "门户页面" --page-codes portal_pc,portal_mobile --profile dev
44
44
  ```
45
45
 
46
- Use `--page-codes` or `--menu-codes` for custom code page menus; the CLI resolves the editable permission group to the published code page `pageId`, and creates a companion `(运行时别名)` group for the menu ID, route key, and legacy `PAGE_...` alias required by the platform's current `/view` guard and custom-page bootstrap checks. Use `--form-codes` for form menus; the CLI resolves forms to profile-local form UUIDs. Empty target lists mean all menus/pages are visible to matched roles.
46
+ Use `--page-codes` or `--menu-codes` for custom code page menus; the CLI resolves the permission group to the published menu ID and keeps required page ID, route key, and legacy `PAGE_...` runtime aliases in the same group so the platform editor can still round-trip the selection. Use `--form-codes` for form menus; the CLI resolves forms to profile-local form UUIDs. Empty target lists mean all menus/pages are visible to matched roles. Only pass `--no-runtime-aliases` after confirming the target platform checks menu IDs directly at runtime.
47
47
 
48
48
  ## Form Permission Groups
49
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -107,13 +107,17 @@
107
107
  "react-dom": ">=18"
108
108
  },
109
109
  "devDependencies": {
110
+ "@testing-library/jest-dom": "^6.9.1",
111
+ "@testing-library/react": "^16.3.2",
110
112
  "@types/node": "^22.0.0",
111
113
  "@types/react": "^18.3.18",
112
114
  "@types/react-dom": "^18.3.5",
115
+ "jsdom": "^29.1.1",
113
116
  "react": "18.3.1",
114
117
  "react-dom": "18.3.1",
115
118
  "tsup": "^8.3.0",
116
- "typescript": "^5.7.0"
119
+ "typescript": "^5.7.0",
120
+ "vitest": "^4.1.7"
117
121
  },
118
122
  "engines": {
119
123
  "node": ">=18"
@@ -1091,10 +1091,11 @@ function FieldWrapper({
1091
1091
  }
1092
1092
  ) : void 0;
1093
1093
  const errorNode = error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sy-field-error", role: "alert", "data-testid": `error-${fieldId}`, children: error }) : void 0;
1094
+ const requiredMark = required4 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sy-field-required", "aria-hidden": "true", children: "*" }) : null;
1094
1095
  if (!AntFormItem) {
1095
1096
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("sy-field-wrapper", className), "data-field-id": fieldId, children: [
1096
1097
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: cn("sy-field-label", labelClassName), children: [
1097
- required4 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sy-field-required", children: "*" }),
1098
+ requiredMark,
1098
1099
  label
1099
1100
  ] }),
1100
1101
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "sy-field-control", children }),
@@ -1106,8 +1107,11 @@ function FieldWrapper({
1106
1107
  AntFormItem,
1107
1108
  {
1108
1109
  name: fieldId,
1109
- label: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: labelClassName, children: label }),
1110
- required: required4,
1110
+ label: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: cn("sy-field-label", labelClassName), children: [
1111
+ requiredMark,
1112
+ label
1113
+ ] }),
1114
+ required: false,
1111
1115
  validateStatus: error ? "error" : void 0,
1112
1116
  help: errorNode,
1113
1117
  extra,