lan-low-code-cli 2.0.0 → 2.2.0

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.
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseRelationSpecs = parseRelationSpecs;
4
+ exports.appIdOfForm = appIdOfForm;
5
+ exports.resolveRelations = resolveRelations;
6
+ const formDefine_1 = require("./api/formDefine");
7
+ /** 解析 `--relation`:`字段名=目标表单[:显示值=字段1,字段2][:显示项=字段1,字段2]` */
8
+ function parseRelationSpecs(raw) {
9
+ return raw.map((item) => {
10
+ const text = item.trim();
11
+ const eq = text.indexOf('=');
12
+ if (eq <= 0) {
13
+ throw new Error(`--relation 格式错误:${item},应为 "字段名=目标表单[:显示值=字段][:显示项=字段1,字段2]"`);
14
+ }
15
+ const fieldName = text.slice(0, eq).trim();
16
+ const rest = text.slice(eq + 1);
17
+ const [targetRaw, ...optionParts] = rest.split(':');
18
+ const target = targetRaw.trim();
19
+ if (!fieldName || !target) {
20
+ throw new Error(`--relation 格式错误:${item},字段名与目标表单都不能为空`);
21
+ }
22
+ const parsed = { fieldName, target };
23
+ for (const part of optionParts) {
24
+ const trimmed = part.trim();
25
+ if (!trimmed)
26
+ continue;
27
+ const sep = trimmed.indexOf('=');
28
+ if (sep <= 0) {
29
+ throw new Error(`--relation 选项格式错误:${part}(应为 显示值=字段 或 显示项=字段1,字段2)`);
30
+ }
31
+ const name = trimmed.slice(0, sep).trim();
32
+ const values = trimmed
33
+ .slice(sep + 1)
34
+ .split(',')
35
+ .map((v) => v.trim())
36
+ .filter(Boolean);
37
+ if (!values.length)
38
+ throw new Error(`--relation 选项 ${name} 不能为空(${part})`);
39
+ if (name === '显示值' || name === 'refField' || name === 'ref') {
40
+ parsed.refFields = values;
41
+ }
42
+ else if (name === '显示项' || name === 'columns') {
43
+ parsed.columns = values;
44
+ }
45
+ else {
46
+ throw new Error(`--relation 不支持的选项:${name}(支持 显示值 / 显示项)`);
47
+ }
48
+ }
49
+ return parsed;
50
+ });
51
+ }
52
+ /** 查找业务对象表单:按 fdId 或表单名(同名多份时优先与当前表单同应用) */
53
+ async function findTargetForm(client, key, currentAppId) {
54
+ const byId = /^[0-9a-z]{20,}$/i.test(key);
55
+ const page = await formDefine_1.modelingXFormApi.query(client, {
56
+ conditions: byId ? { fdId: key } : { fdName: key },
57
+ });
58
+ const list = (page.content ?? []);
59
+ if (!list.length)
60
+ throw new Error(`未找到业务对象(目标表单):${key}`);
61
+ if (list.length > 1) {
62
+ const same = currentAppId ? list.filter((f) => f.fdApp?.fdId === currentAppId) : [];
63
+ if (same.length === 1)
64
+ return same[0];
65
+ throw new Error(`业务对象「${key}」匹配到 ${list.length} 个表单,请改用 fdId 指定:` +
66
+ list.map((f) => `${f.fdName}(${f.fdId}/应用 ${f.fdApp?.fdName ?? '-'})`).join(';'));
67
+ }
68
+ return list[0];
69
+ }
70
+ /** 查询表单所属应用 id(业务对象同名消歧用) */
71
+ async function appIdOfForm(client, formId) {
72
+ const page = await formDefine_1.modelingXFormApi.query(client, { conditions: { fdId: formId } });
73
+ const first = (page.content ?? [])[0];
74
+ return first?.fdApp?.fdId;
75
+ }
76
+ /** 目标表单主表的业务字段(非系统、显示) */
77
+ function businessFields(main) {
78
+ return (main?.fdFields ?? []).filter((f) => !f.fdIsSystem && f.fdDisplay !== false);
79
+ }
80
+ /**
81
+ * 查询环境解析业务关联:定位目标表单 → 取主表字段 → 构造 `IRelationSpec`。
82
+ *
83
+ * @param currentAppId 当前表单所属应用(同名表单消歧用)
84
+ */
85
+ async function resolveRelations(client, parsed, currentAppId) {
86
+ const specs = [];
87
+ for (const item of parsed) {
88
+ const form = await findTargetForm(client, item.target, currentAppId);
89
+ const design = await formDefine_1.modelingXFormApi.loadDesign(client, form.fdId);
90
+ const cfg = typeof design.fdConfig === 'string' ? JSON.parse(design.fdConfig) : design.fdConfig;
91
+ const main = (cfg?.dataModel ?? []).find((m) => m.fdType === 'main');
92
+ const fields = businessFields(main);
93
+ if (!fields.length)
94
+ throw new Error(`业务对象「${form.fdName}」主表没有可用业务字段`);
95
+ const labelOf = new Map(fields.map((f) => [f.fdName, f.fdLabel || f.fdName]));
96
+ // 显示项:缺省为该表全部业务字段
97
+ const columnNames = item.columns?.length ? item.columns : fields.map((f) => f.fdName);
98
+ const unknownColumns = columnNames.filter((n) => !labelOf.has(n));
99
+ if (unknownColumns.length) {
100
+ throw new Error(`业务对象「${form.fdName}」中不存在显示项字段:${unknownColumns.join('、')};` +
101
+ `可用字段:${fields.map((f) => f.fdName).join('、')}`);
102
+ }
103
+ // 显示值:缺省取首个文本类业务字段(无则取第一个业务字段)
104
+ const refNames = item.refFields ??
105
+ [
106
+ fields.find((f) => ['text', 'textarea'].includes(String(f.fdType)))?.fdName ??
107
+ fields[0].fdName,
108
+ ];
109
+ const unknownRefs = refNames.filter((n) => !labelOf.has(n));
110
+ if (unknownRefs.length) {
111
+ throw new Error(`业务对象「${form.fdName}」中不存在显示值字段:${unknownRefs.join('、')};` +
112
+ `可用字段:${fields.map((f) => f.fdName).join('、')}`);
113
+ }
114
+ specs.push({
115
+ fieldName: item.fieldName,
116
+ target: {
117
+ // appCode 存的是应用 fdId(与真实表单一致)
118
+ appCode: form.fdApp?.fdId ?? '',
119
+ xformName: form.fdName,
120
+ modelId: form.fdId,
121
+ tableName: main?.fdTableName ?? form.fdTableName ?? '',
122
+ fdFormType: form.fdType ?? 0,
123
+ },
124
+ refFieldName: refNames.map((n) => `$${n}$`).join(''),
125
+ showFields: refNames.map((n) => `$${labelOf.get(n)}$`).join(''),
126
+ columns: columnNames.map((n) => ({ name: n, label: labelOf.get(n) })),
127
+ });
128
+ }
129
+ return specs;
130
+ }
package/dist/widgets.js CHANGED
@@ -4,6 +4,7 @@ exports.WidgetRegistry = void 0;
4
4
  exports.originOf = originOf;
5
5
  exports.collectWidgetDefaults = collectWidgetDefaults;
6
6
  exports.hasConfigItem = hasConfigItem;
7
+ exports.collectFrontExtendKeys = collectFrontExtendKeys;
7
8
  exports.validateRequiredProps = validateRequiredProps;
8
9
  exports.elementLangKey = elementLangKey;
9
10
  const fs_1 = require("fs");
@@ -60,6 +61,40 @@ function hasConfigItem(meta, key) {
60
61
  });
61
62
  return found;
62
63
  }
64
+ /**
65
+ * 收集控件 `config.json` 中声明为「前端扩展数据」(`frontExtendData`)的属性项。
66
+ *
67
+ * 平台把这类属性**同时**存在两处:控件属性 `controlProps` 与数据项
68
+ * `fdFontExtendData`(前端 `syncApi.frontExtendData2Config` 用它反向同步,
69
+ * 公式/接口 Schema 的 `$ref` 也依赖它,如 address 的 `orgTypeArr`)。
70
+ *
71
+ * 声明形式:
72
+ * - `frontExtendData: true` → 键自身(如 address 的 `range` / `multi`);
73
+ * - `frontExtendData: { children: ['orgTypeArr', …] }` → 取 `controlProps[键]` 的子键
74
+ * (如 address 的 `org.orgTypeArr`)。
75
+ *
76
+ * @returns `path` 为在 controlProps 中的取值路径,`key` 为写入 fdFontExtendData 的键
77
+ */
78
+ function collectFrontExtendKeys(meta) {
79
+ const out = [];
80
+ walkConfig(meta?.config, (item) => {
81
+ const decl = item.frontExtendData;
82
+ if (!decl || typeof item.key !== 'string')
83
+ return;
84
+ if (decl === true) {
85
+ out.push({ path: item.key, key: item.key });
86
+ return;
87
+ }
88
+ const children = decl.children;
89
+ if (Array.isArray(children)) {
90
+ for (const child of children) {
91
+ if (typeof child === 'string')
92
+ out.push({ path: `${item.key}.${child}`, key: child });
93
+ }
94
+ }
95
+ });
96
+ return out;
97
+ }
63
98
  /**
64
99
  * 校验控件必填属性值(对齐设计器发布前校验)。
65
100
  *
@@ -120,10 +155,10 @@ function validateRequiredProps(meta, config) {
120
155
  if ((value === undefined || value === null || value === '') && item.type === 'inputLocale') {
121
156
  value = labelProps.title ?? controlProps.title;
122
157
  }
123
- const empty = value === undefined ||
124
- value === null ||
125
- value === '' ||
126
- (Array.isArray(value) && value.length === 0);
158
+ // 空数组**不算空**:设计器规则是 `{ required: true, message }`(不带 type),
159
+ // async-validator 的 isEmptyValue 仅在 `type === 'array'` 时把 `[]` 判为空,
160
+ // 因此设计器允许保存 `preSelectValue: []`(address/地址本自定义范围的真实结构)。
161
+ const empty = value === undefined || value === null || value === '';
127
162
  if (empty) {
128
163
  errors.push({ key: item.key, label: String(item.label ?? item.key) });
129
164
  }
package/package.json CHANGED
@@ -1,43 +1,45 @@
1
- {
2
- "name": "lan-low-code-cli",
3
- "version": "2.0.0",
4
- "description": "lan-low-code cli:低代码表单引擎命令行工具(创建表单、获取可用字段、自动完善表单字段)",
5
- "keywords": [
6
- "low-code",
7
- "lan-low-code",
8
- "llc",
9
- "form",
10
- "cli"
11
- ],
12
- "license": "MIT",
13
- "bin": {
14
- "llc": "bin/llc.js"
15
- },
16
- "main": "dist/index.js",
17
- "dependencies": {
18
- "commander": "^11.1.0"
19
- },
20
- "devDependencies": {
21
- "@types/node": "^20.11.30",
22
- "rimraf": "^5.0.5",
23
- "typescript": "^5.4.5"
24
- },
25
- "engines": {
26
- "node": ">=18",
27
- "pnpm": ">=8"
28
- },
29
- "files": [
30
- "bin",
31
- "dist",
32
- "README.md"
33
- ],
34
- "scripts": {
35
- "build": "tsc -p tsconfig.json",
36
- "clean": "rimraf dist",
37
- "rebuild": "pnpm run clean && pnpm run build",
38
- "start": "pnpm run build && node bin/llc.js",
39
- "typecheck": "tsc -p tsconfig.json --noEmit",
40
- "bump": "node scripts/bump-version.js",
41
- "bump:check": "node scripts/bump-version.js --check"
42
- }
43
- }
1
+ {
2
+ "name": "lan-low-code-cli",
3
+ "version": "2.2.0",
4
+ "description": "lan-low-code cli:低代码表单引擎命令行工具(创建表单、获取可用字段、自动完善表单字段)",
5
+ "keywords": [
6
+ "low-code",
7
+ "lan-low-code",
8
+ "llc",
9
+ "form",
10
+ "cli"
11
+ ],
12
+ "license": "MIT",
13
+ "packageManager": "pnpm@10.26.2",
14
+ "bin": {
15
+ "llc": "bin/llc.js"
16
+ },
17
+ "main": "dist/index.js",
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "clean": "rimraf dist",
21
+ "rebuild": "pnpm run clean && pnpm run build",
22
+ "start": "pnpm run build && node bin/llc.js",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "prepare": "pnpm run build",
25
+ "bump": "node scripts/bump-version.js",
26
+ "bump:check": "node scripts/bump-version.js --check"
27
+ },
28
+ "dependencies": {
29
+ "commander": "^11.1.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.11.30",
33
+ "rimraf": "^5.0.5",
34
+ "typescript": "^5.4.5"
35
+ },
36
+ "engines": {
37
+ "node": ">=18",
38
+ "pnpm": ">=8"
39
+ },
40
+ "files": [
41
+ "bin",
42
+ "dist",
43
+ "README.md"
44
+ ]
45
+ }