openyida 2026.7.29-beta.2 → 2026.7.30
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/README.md +1 -0
- package/lib/app/create-form/args.js +19 -0
- package/lib/app/create-form/commands.js +2 -0
- package/lib/app/create-form.js +145 -166
- package/lib/app/form-field-validator.js +560 -0
- package/lib/core/command-manifest.js +5 -0
- package/lib/core/locales/en.js +1 -0
- package/lib/core/locales/zh.js +1 -0
- package/package.json +1 -1
- package/yida-skills/skills/yida-create-form-page/SKILL.md +3 -3
- package/yida-skills/skills/yida-create-form-page/references/field-definition-guide.md +2 -0
package/README.md
CHANGED
|
@@ -387,6 +387,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
387
387
|
| Command | Description |
|
|
388
388
|
|---------|-------------|
|
|
389
389
|
| `openyida create-form create <appType> ... [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
|
|
390
|
+
| `openyida create-form validate-fields <fieldsJsonOrFile> [--json]` | Validate form field JSON locally |
|
|
390
391
|
| `openyida create-form update <appType> ... [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Update a form page |
|
|
391
392
|
| `openyida create-form patch <appType> <formUuid> <patchJsonOrFile> [--open\|--no-open]` | Update a form page |
|
|
392
393
|
| `openyida create-form rule <appType> <formUuid> <rulesJsonOrFile> [--open\|--no-open]` | Update a form page |
|
|
@@ -115,6 +115,10 @@ function createParseArgs(dependencies) {
|
|
|
115
115
|
options.force = true;
|
|
116
116
|
args.splice(i, 1);
|
|
117
117
|
i--;
|
|
118
|
+
} else if (args[i] === '--json') {
|
|
119
|
+
options.json = true;
|
|
120
|
+
args.splice(i, 1);
|
|
121
|
+
i--;
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -132,6 +136,21 @@ function createParseArgs(dependencies) {
|
|
|
132
136
|
|
|
133
137
|
const mode = args[0];
|
|
134
138
|
|
|
139
|
+
if (mode === 'validate-fields') {
|
|
140
|
+
if (args.length === 2) {
|
|
141
|
+
return {
|
|
142
|
+
mode: 'validate-fields',
|
|
143
|
+
fieldsJsonOrFile: args[1],
|
|
144
|
+
...options
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
usage(
|
|
148
|
+
'openyida create-form validate-fields <fieldsJsonOrFile> --json',
|
|
149
|
+
'openyida create-form validate-fields .cache/openyida/forms/fields.json --json'
|
|
150
|
+
);
|
|
151
|
+
throwCreateFormError('openyida create-form validate-fields <fieldsJsonOrFile> --json', 'CREATE_FORM_INVALID_ARGUMENTS');
|
|
152
|
+
}
|
|
153
|
+
|
|
135
154
|
if (mode === 'create') {
|
|
136
155
|
if (args.length < 4) {
|
|
137
156
|
usage(t('create_form.usage_create'), t('create_form.example_create'));
|
|
@@ -16,6 +16,8 @@ function dispatchCreateFormCommand(parsedArgs, authContext, handlers) {
|
|
|
16
16
|
return handlers.bindDataSource(...args);
|
|
17
17
|
case 'add-option':
|
|
18
18
|
return handlers.addOption(...args);
|
|
19
|
+
case 'validate-fields':
|
|
20
|
+
return handlers.validateFields(...args);
|
|
19
21
|
case 'create':
|
|
20
22
|
return handlers.create(...args);
|
|
21
23
|
default:
|
package/lib/app/create-form.js
CHANGED
|
@@ -81,6 +81,9 @@ const { deepMerge, splitJsonPointer } = require('./create-form/schema-patch');
|
|
|
81
81
|
const { FORM_RULES_BLOCK_END, FORM_RULES_BLOCK_START } = require('./create-form/rule-builder');
|
|
82
82
|
const { SMART_VALIDATION_BLOCK_END, SMART_VALIDATION_BLOCK_START } = require('./create-form/validation-builder');
|
|
83
83
|
const { createFieldNormalizers } = require('./create-form/field-normalizers');
|
|
84
|
+
const {
|
|
85
|
+
validateFormFieldDefinitions: collectFormFieldValidationDiagnostics,
|
|
86
|
+
} = require('./form-field-validator');
|
|
84
87
|
|
|
85
88
|
function parseOpenOption(inputArgs) {
|
|
86
89
|
const openOption = parseBrowserOpenOption(inputArgs);
|
|
@@ -351,182 +354,134 @@ function throwInvalidFieldDefinition(message, code, field, fieldPath, extra) {
|
|
|
351
354
|
);
|
|
352
355
|
}
|
|
353
356
|
|
|
354
|
-
function
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
'CREATE_FORM_INVALID_FIELD_DEFINITION',
|
|
359
|
-
field,
|
|
360
|
-
fieldPath
|
|
361
|
-
);
|
|
357
|
+
function getValueAtDiagnosticPath(root, diagnosticPath, rootPath) {
|
|
358
|
+
const normalizedRootPath = rootPath || 'fields';
|
|
359
|
+
if (!diagnosticPath || diagnosticPath.indexOf(normalizedRootPath) !== 0) {
|
|
360
|
+
return undefined;
|
|
362
361
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
362
|
+
const relativePath = diagnosticPath.slice(normalizedRootPath.length);
|
|
363
|
+
const tokens = [];
|
|
364
|
+
const pattern = /\[(\d+)\]|\.([A-Za-z_$][\w$]*)/g;
|
|
365
|
+
let match;
|
|
366
|
+
while ((match = pattern.exec(relativePath)) !== null) {
|
|
367
|
+
tokens.push(match[1] !== undefined ? Number(match[1]) : match[2]);
|
|
368
368
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
{ componentName }
|
|
376
|
-
);
|
|
369
|
+
let current = root;
|
|
370
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
371
|
+
if (current === undefined || current === null) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
current = current[tokens[index]];
|
|
377
375
|
}
|
|
378
|
-
return
|
|
376
|
+
return current;
|
|
379
377
|
}
|
|
380
378
|
|
|
381
|
-
function
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
379
|
+
function findDiagnosticField(fields, diagnosticPath, rootPath) {
|
|
380
|
+
let currentPath = diagnosticPath || rootPath || 'fields';
|
|
381
|
+
while (currentPath && currentPath !== (rootPath || 'fields')) {
|
|
382
|
+
const value = getValueAtDiagnosticPath(fields, currentPath, rootPath);
|
|
383
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
384
|
+
return value;
|
|
385
|
+
}
|
|
386
|
+
const dotIndex = currentPath.lastIndexOf('.');
|
|
387
|
+
const bracketIndex = currentPath.lastIndexOf('[');
|
|
388
|
+
const cutIndex = Math.max(dotIndex, bracketIndex);
|
|
389
|
+
if (cutIndex <= 0) {
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
currentPath = currentPath.slice(0, cutIndex);
|
|
385
393
|
}
|
|
386
|
-
return
|
|
394
|
+
return undefined;
|
|
387
395
|
}
|
|
388
396
|
|
|
389
|
-
function
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
'
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
397
|
+
function mapFieldValidationCode(code) {
|
|
398
|
+
const cliErrorCodesByValidationCode = {
|
|
399
|
+
FIELD_TYPE_MISSING: 'CREATE_FORM_FIELD_TYPE_MISSING',
|
|
400
|
+
INVALID_FIELD_DEFINITION: 'CREATE_FORM_INVALID_FIELD_DEFINITION',
|
|
401
|
+
INVALID_FIELDS_ROOT: 'CREATE_FORM_INVALID_FIELD_DEFINITION',
|
|
402
|
+
UNSUPPORTED_FIELD_TYPE: 'CREATE_FORM_UNSUPPORTED_FIELD_TYPE',
|
|
403
|
+
COLUMN_OUTSIDE_COLUMN_CONTAINER: 'CREATE_FORM_COLUMN_OUTSIDE_COLUMNS_LAYOUT',
|
|
404
|
+
ASSOCIATION_FORM_MISSING: 'CREATE_FORM_ASSOCIATION_FORM_UUID_MISSING',
|
|
405
|
+
TABLE_CHILD_PRESENTATION_UNSUPPORTED: 'CREATE_FORM_TABLE_CHILD_PRESENTATION_UNSUPPORTED',
|
|
406
|
+
INVALID_TABLE_FIELD_CHILDREN_DEPTH: 'CREATE_FORM_INVALID_TABLE_CHILDREN',
|
|
407
|
+
INVALID_COLUMN_CONTAINER_CHILDREN_DEPTH: 'CREATE_FORM_INVALID_COLUMN_CONTAINER_CHILDREN_DEPTH',
|
|
408
|
+
INVALID_CHILDREN_SHAPE: 'CREATE_FORM_INVALID_CONTAINER_CHILDREN',
|
|
409
|
+
NESTED_TABLE_FIELD_UNSUPPORTED: 'CREATE_FORM_NESTED_TABLE_FIELD_UNSUPPORTED',
|
|
410
|
+
OPTION_FIELD_DATASOURCE_MISSING: 'CREATE_FORM_OPTION_FIELD_DATASOURCE_MISSING',
|
|
411
|
+
BUSINESS_FIELD_LABEL_MISSING: 'CREATE_FORM_BUSINESS_FIELD_LABEL_MISSING',
|
|
412
|
+
INVALID_MULTIPLE_TYPE: 'CREATE_FORM_INVALID_MULTIPLE_TYPE',
|
|
413
|
+
INVALID_DIVIDER_TITLE: 'CREATE_FORM_INVALID_DIVIDER_TITLE',
|
|
414
|
+
INVALID_DIVIDER_TYPE: 'CREATE_FORM_INVALID_DIVIDER_TYPE',
|
|
415
|
+
};
|
|
416
|
+
return cliErrorCodesByValidationCode[code] || 'CREATE_FORM_FIELD_JSON_INVALID';
|
|
400
417
|
}
|
|
401
418
|
|
|
402
|
-
function
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const componentName = normalizeFormDefinitionType(field);
|
|
407
|
-
if (!componentName) {
|
|
408
|
-
throwInvalidFieldDefinition(
|
|
409
|
-
'字段定义缺少 type/componentName/componentType: ' + fieldPath,
|
|
410
|
-
'CREATE_FORM_FIELD_TYPE_MISSING',
|
|
411
|
-
field,
|
|
412
|
-
fieldPath
|
|
413
|
-
);
|
|
419
|
+
function normalizeDiagnosticDetailPath(diagnostic) {
|
|
420
|
+
if (!diagnostic || !diagnostic.path) {
|
|
421
|
+
return 'fields';
|
|
414
422
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (validationOptions.disallowPresentation) {
|
|
418
|
-
throwInvalidFieldDefinition(
|
|
419
|
-
'TableField.children 不支持展示布局组件: ' + componentName,
|
|
420
|
-
'CREATE_FORM_TABLE_CHILD_PRESENTATION_UNSUPPORTED',
|
|
421
|
-
field,
|
|
422
|
-
fieldPath,
|
|
423
|
-
{ componentName }
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
if (componentName === 'Column' && !validationOptions.allowColumn) {
|
|
427
|
-
throwInvalidFieldDefinition(
|
|
428
|
-
'Column 只能作为 ColumnContainer.children 的 Column 对象使用: ' + fieldPath,
|
|
429
|
-
'CREATE_FORM_COLUMN_OUTSIDE_COLUMNS_LAYOUT',
|
|
430
|
-
field,
|
|
431
|
-
fieldPath,
|
|
432
|
-
{ componentName }
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
if (componentName === 'ColumnsLayout') {
|
|
436
|
-
validateColumnsLayoutDefinition(field, fieldPath);
|
|
437
|
-
} else if (componentName === 'PageSection') {
|
|
438
|
-
validateContainerChildrenArray(field, fieldPath, componentName).forEach(function (child, childIndex) {
|
|
439
|
-
validateFieldDefinition(child, fieldPath + '.children[' + childIndex + ']');
|
|
440
|
-
});
|
|
441
|
-
} else if (componentName === 'Column') {
|
|
442
|
-
validateContainerChildrenArray(field, fieldPath, componentName).forEach(function (child, childIndex) {
|
|
443
|
-
validateFieldDefinition(child, fieldPath + '.children[' + childIndex + ']');
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
return componentName;
|
|
423
|
+
if (diagnostic.code === 'FIELD_TYPE_MISSING' && diagnostic.path.endsWith('.type')) {
|
|
424
|
+
return diagnostic.path.slice(0, -'.type'.length);
|
|
447
425
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
throwInvalidFieldDefinition(
|
|
451
|
-
'不支持的字段类型: ' + componentName,
|
|
452
|
-
'CREATE_FORM_UNSUPPORTED_FIELD_TYPE',
|
|
453
|
-
field,
|
|
454
|
-
fieldPath,
|
|
455
|
-
{ componentName }
|
|
456
|
-
);
|
|
426
|
+
if (diagnostic.code === 'ASSOCIATION_FORM_MISSING' && diagnostic.path.endsWith('.associationForm')) {
|
|
427
|
+
return diagnostic.path.slice(0, -'.associationForm'.length);
|
|
457
428
|
}
|
|
429
|
+
return diagnostic.path;
|
|
430
|
+
}
|
|
458
431
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
432
|
+
function validateFormFieldDefinitions(fields, rootPath) {
|
|
433
|
+
const diagnostics = collectFormFieldValidationDiagnostics(fields, {
|
|
434
|
+
rootPath: rootPath || 'fields',
|
|
435
|
+
});
|
|
436
|
+
throwFieldValidationDiagnostics(fields, diagnostics, rootPath || 'fields');
|
|
437
|
+
}
|
|
462
438
|
|
|
463
|
-
|
|
464
|
-
|
|
439
|
+
function throwFieldValidationDiagnostics(fields, diagnostics, rootPath) {
|
|
440
|
+
if (diagnostics.length === 0) {
|
|
441
|
+
return;
|
|
465
442
|
}
|
|
466
443
|
|
|
467
|
-
|
|
444
|
+
const first = diagnostics[0];
|
|
445
|
+
const detailPath = normalizeDiagnosticDetailPath(first);
|
|
446
|
+
const field = findDiagnosticField(fields, detailPath, rootPath || 'fields');
|
|
447
|
+
const details = buildInvalidFieldDefinitionDetails(field, detailPath, {
|
|
448
|
+
diagnostics,
|
|
449
|
+
expected: first.expected,
|
|
450
|
+
actual: first.actual,
|
|
451
|
+
suggestion: first.suggestion,
|
|
452
|
+
});
|
|
453
|
+
throwCreateFormError(
|
|
454
|
+
first.message,
|
|
455
|
+
mapFieldValidationCode(first.code),
|
|
456
|
+
details
|
|
457
|
+
);
|
|
468
458
|
}
|
|
469
459
|
|
|
470
|
-
function
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
if (
|
|
481
|
-
columnDefinition &&
|
|
482
|
-
typeof columnDefinition === 'object' &&
|
|
483
|
-
!Array.isArray(columnDefinition) &&
|
|
484
|
-
normalizeFormDefinitionType(columnDefinition) === 'Column'
|
|
485
|
-
) {
|
|
486
|
-
validateFieldDefinition(columnDefinition, columnPath, { allowColumn: true });
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
throwInvalidFieldDefinition(
|
|
490
|
-
'ColumnContainer.children 只能包含二维字段数组或 Column 对象: ' + columnPath,
|
|
491
|
-
'CREATE_FORM_INVALID_COLUMNS_LAYOUT_CHILDREN',
|
|
492
|
-
columnDefinition,
|
|
493
|
-
columnPath,
|
|
494
|
-
{ componentName: normalizeFormDefinitionType(columnDefinition) || readFormDefinitionType(columnDefinition) }
|
|
495
|
-
);
|
|
460
|
+
function collectSingleFieldValidationDiagnostics(field, rootPath) {
|
|
461
|
+
const internalRootPath = '__field';
|
|
462
|
+
const diagnostics = collectFormFieldValidationDiagnostics([field], {
|
|
463
|
+
rootPath: internalRootPath,
|
|
464
|
+
});
|
|
465
|
+
return diagnostics.map(function (diagnostic) {
|
|
466
|
+
return Object.assign({}, diagnostic, {
|
|
467
|
+
path: diagnostic.path.replace(internalRootPath + '[0]', rootPath),
|
|
468
|
+
});
|
|
496
469
|
});
|
|
497
470
|
}
|
|
498
471
|
|
|
499
|
-
function
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
if (!Array.isArray(field.children)) {
|
|
504
|
-
throwInvalidFieldDefinition(
|
|
505
|
-
'TableField.children 必须是字段数组: ' + fieldPath,
|
|
506
|
-
'CREATE_FORM_INVALID_TABLE_CHILDREN',
|
|
507
|
-
field,
|
|
508
|
-
fieldPath,
|
|
509
|
-
{ componentName: 'TableField' }
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
field.children.forEach(function (child, childIndex) {
|
|
513
|
-
validateFieldDefinition(child, fieldPath + '.children[' + childIndex + ']', Object.assign({}, options, {
|
|
514
|
-
disallowPresentation: true,
|
|
515
|
-
}));
|
|
516
|
-
});
|
|
472
|
+
function validateSingleFieldDefinition(field, rootPath) {
|
|
473
|
+
const diagnostics = collectSingleFieldValidationDiagnostics(field, rootPath);
|
|
474
|
+
throwFieldValidationDiagnostics(field, diagnostics, rootPath);
|
|
517
475
|
}
|
|
518
476
|
|
|
519
|
-
function
|
|
520
|
-
if (!Array.isArray(
|
|
521
|
-
|
|
522
|
-
'字段定义必须是数组',
|
|
523
|
-
'CREATE_FORM_INVALID_FIELD_DEFINITION',
|
|
524
|
-
null,
|
|
525
|
-
rootPath || 'fields'
|
|
526
|
-
);
|
|
477
|
+
function validateChangeFieldDefinitions(changes) {
|
|
478
|
+
if (!Array.isArray(changes)) {
|
|
479
|
+
return;
|
|
527
480
|
}
|
|
528
|
-
|
|
529
|
-
|
|
481
|
+
changes.forEach(function (change, changeIndex) {
|
|
482
|
+
if (change && change.action === 'add') {
|
|
483
|
+
validateSingleFieldDefinition(change.field, 'changes[' + changeIndex + '].field');
|
|
484
|
+
}
|
|
530
485
|
});
|
|
531
486
|
}
|
|
532
487
|
|
|
@@ -5095,6 +5050,24 @@ async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, ver
|
|
|
5095
5050
|
|
|
5096
5051
|
// ── create 模式主流程 ─────────────────────────────────
|
|
5097
5052
|
|
|
5053
|
+
async function mainValidateFields(parsedArgs) {
|
|
5054
|
+
const { fieldsJsonOrFile } = parsedArgs;
|
|
5055
|
+
assertNoEmojiInDefinitionFileName(fieldsJsonOrFile);
|
|
5056
|
+
const { fields } = readFieldsDefinition(fieldsJsonOrFile);
|
|
5057
|
+
const diagnostics = collectFormFieldValidationDiagnostics(fields, { rootPath: 'fields' });
|
|
5058
|
+
const output = {
|
|
5059
|
+
success: diagnostics.length === 0,
|
|
5060
|
+
valid: diagnostics.length === 0,
|
|
5061
|
+
fieldCount: Array.isArray(fields) ? fields.length : 0,
|
|
5062
|
+
diagnostics,
|
|
5063
|
+
};
|
|
5064
|
+
console.log(JSON.stringify(output, null, parsedArgs.json ? 2 : 0));
|
|
5065
|
+
if (diagnostics.length > 0) {
|
|
5066
|
+
process.exitCode = 1;
|
|
5067
|
+
}
|
|
5068
|
+
return output;
|
|
5069
|
+
}
|
|
5070
|
+
|
|
5098
5071
|
async function mainCreate(parsedArgs, authRef) {
|
|
5099
5072
|
const { appType, formTitle, fieldsJsonOrFile, layout, theme, labelAlign } = parsedArgs;
|
|
5100
5073
|
|
|
@@ -6030,7 +6003,21 @@ async function mainUpdate(parsedArgs, authRef) {
|
|
|
6030
6003
|
label('Form UUID:', formUuid);
|
|
6031
6004
|
label('Changes:', changesJsonOrFile);
|
|
6032
6005
|
|
|
6033
|
-
step(2, t('create_form.
|
|
6006
|
+
step(2, t('create_form.step_read_changes', 2));
|
|
6007
|
+
const changes = readChangesDefinition(changesJsonOrFile);
|
|
6008
|
+
validateChangeFieldDefinitions(changes);
|
|
6009
|
+
success(t('create_form.changes_loaded', changes.length));
|
|
6010
|
+
changes.forEach(function (change, changeIndex) {
|
|
6011
|
+
if (change.action === 'add') {
|
|
6012
|
+
listItem((changeIndex + 1) + '. [' + t('create_form.action_add') + '] ' + change.field.type + ': ' + change.field.label);
|
|
6013
|
+
} else if (change.action === 'delete') {
|
|
6014
|
+
listItem((changeIndex + 1) + '. [' + t('create_form.action_delete') + '] ' + change.label);
|
|
6015
|
+
} else if (change.action === 'update') {
|
|
6016
|
+
listItem((changeIndex + 1) + '. [' + t('create_form.action_update') + '] ' + change.label + ' → ' + Object.keys(change.changes || {}).join(', '));
|
|
6017
|
+
}
|
|
6018
|
+
});
|
|
6019
|
+
|
|
6020
|
+
step(3, t('create_form.step_get_schema', 3));
|
|
6034
6021
|
info(t('create_form.sending_get_schema'));
|
|
6035
6022
|
const schemaResult = await requestWithAutoLogin(function (auth) {
|
|
6036
6023
|
return sendGetRequest(
|
|
@@ -6082,7 +6069,7 @@ async function mainUpdate(parsedArgs, authRef) {
|
|
|
6082
6069
|
warn(t('create_form.schema_got_empty'));
|
|
6083
6070
|
}
|
|
6084
6071
|
|
|
6085
|
-
step(
|
|
6072
|
+
step(4, t('create_form.step_check_data', 4));
|
|
6086
6073
|
const dataCheckResult = await requestWithAutoLogin(function (auth) {
|
|
6087
6074
|
return sendGetRequest(
|
|
6088
6075
|
auth.baseUrl,
|
|
@@ -6122,19 +6109,6 @@ async function mainUpdate(parsedArgs, authRef) {
|
|
|
6122
6109
|
success(t('create_form.data_check_empty'));
|
|
6123
6110
|
}
|
|
6124
6111
|
|
|
6125
|
-
step(4, t('create_form.step_read_changes', 4));
|
|
6126
|
-
const changes = readChangesDefinition(changesJsonOrFile);
|
|
6127
|
-
success(t('create_form.changes_loaded', changes.length));
|
|
6128
|
-
changes.forEach(function (change, changeIndex) {
|
|
6129
|
-
if (change.action === 'add') {
|
|
6130
|
-
listItem((changeIndex + 1) + '. [' + t('create_form.action_add') + '] ' + change.field.type + ': ' + change.field.label);
|
|
6131
|
-
} else if (change.action === 'delete') {
|
|
6132
|
-
listItem((changeIndex + 1) + '. [' + t('create_form.action_delete') + '] ' + change.label);
|
|
6133
|
-
} else if (change.action === 'update') {
|
|
6134
|
-
listItem((changeIndex + 1) + '. [' + t('create_form.action_update') + '] ' + change.label + ' → ' + Object.keys(change.changes || {}).join(', '));
|
|
6135
|
-
}
|
|
6136
|
-
});
|
|
6137
|
-
|
|
6138
6112
|
step(5, t('create_form.step_apply_changes', 5));
|
|
6139
6113
|
const appliedChanges = applyChangesToSchema(schema, changes);
|
|
6140
6114
|
const fieldDiagnostics = appliedChanges.diagnostics || [];
|
|
@@ -6215,6 +6189,9 @@ async function run(args) {
|
|
|
6215
6189
|
if (parsedArgs.help) {
|
|
6216
6190
|
return parsedArgs;
|
|
6217
6191
|
}
|
|
6192
|
+
if (parsedArgs.mode === 'validate-fields') {
|
|
6193
|
+
return mainValidateFields(parsedArgs);
|
|
6194
|
+
}
|
|
6218
6195
|
|
|
6219
6196
|
step(1, t('common.step_login', 1));
|
|
6220
6197
|
const authRef = createAuthRef();
|
|
@@ -6232,6 +6209,7 @@ async function run(args) {
|
|
|
6232
6209
|
validation: mainValidation,
|
|
6233
6210
|
bindDataSource: mainBindDataSource,
|
|
6234
6211
|
addOption: mainAddOption,
|
|
6212
|
+
validateFields: mainValidateFields,
|
|
6235
6213
|
}
|
|
6236
6214
|
);
|
|
6237
6215
|
}
|
|
@@ -6254,6 +6232,7 @@ module.exports = {
|
|
|
6254
6232
|
collectComponentNames,
|
|
6255
6233
|
normalizeFormDefinitionType,
|
|
6256
6234
|
validateFormFieldDefinitions,
|
|
6235
|
+
collectFormFieldValidationDiagnostics,
|
|
6257
6236
|
ensureDividerThemeAction,
|
|
6258
6237
|
applyChangesToSchema,
|
|
6259
6238
|
},
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const FIELD_TYPE_ALIAS = Object.freeze({
|
|
4
|
+
TextAreaField: 'TextareaField',
|
|
5
|
+
Textareafield: 'TextareaField',
|
|
6
|
+
textareaField: 'TextareaField',
|
|
7
|
+
textAreaField: 'TextareaField',
|
|
8
|
+
Textfield: 'TextField',
|
|
9
|
+
textfield: 'TextField',
|
|
10
|
+
Numberfield: 'NumberField',
|
|
11
|
+
numberfield: 'NumberField',
|
|
12
|
+
Selectfield: 'SelectField',
|
|
13
|
+
selectfield: 'SelectField',
|
|
14
|
+
Radiofield: 'RadioField',
|
|
15
|
+
radiofield: 'RadioField',
|
|
16
|
+
Checkboxfield: 'CheckboxField',
|
|
17
|
+
checkboxfield: 'CheckboxField',
|
|
18
|
+
Datefield: 'DateField',
|
|
19
|
+
datefield: 'DateField',
|
|
20
|
+
Tablefield: 'TableField',
|
|
21
|
+
tablefield: 'TableField',
|
|
22
|
+
Ratefield: 'RateField',
|
|
23
|
+
ratefield: 'RateField',
|
|
24
|
+
Imagefield: 'ImageField',
|
|
25
|
+
imagefield: 'ImageField',
|
|
26
|
+
Attachmentfield: 'AttachmentField',
|
|
27
|
+
attachmentfield: 'AttachmentField',
|
|
28
|
+
Employeefield: 'EmployeeField',
|
|
29
|
+
employeefield: 'EmployeeField',
|
|
30
|
+
MultiSelectfield: 'MultiSelectField',
|
|
31
|
+
Multiselectfield: 'MultiSelectField',
|
|
32
|
+
multiselectfield: 'MultiSelectField',
|
|
33
|
+
SerialNumberfield: 'SerialNumberField',
|
|
34
|
+
Serialnumberfield: 'SerialNumberField',
|
|
35
|
+
serialnumberfield: 'SerialNumberField',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const PRESENTATION_TYPE_ALIAS = Object.freeze({
|
|
39
|
+
Divider: 'Divider',
|
|
40
|
+
divider: 'Divider',
|
|
41
|
+
ColumnsLayout: 'ColumnContainer',
|
|
42
|
+
columnsLayout: 'ColumnContainer',
|
|
43
|
+
ColumnContainer: 'ColumnContainer',
|
|
44
|
+
columnContainer: 'ColumnContainer',
|
|
45
|
+
Column: 'Column',
|
|
46
|
+
column: 'Column',
|
|
47
|
+
GroupContainer: 'GroupContainer',
|
|
48
|
+
groupContainer: 'GroupContainer',
|
|
49
|
+
PageSection: 'PageSection',
|
|
50
|
+
pageSection: 'PageSection',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const BUSINESS_FIELD_TYPES = Object.freeze([
|
|
54
|
+
'TextField',
|
|
55
|
+
'TextareaField',
|
|
56
|
+
'RadioField',
|
|
57
|
+
'SelectField',
|
|
58
|
+
'CheckboxField',
|
|
59
|
+
'MultiSelectField',
|
|
60
|
+
'NumberField',
|
|
61
|
+
'RateField',
|
|
62
|
+
'DateField',
|
|
63
|
+
'CascadeDateField',
|
|
64
|
+
'EmployeeField',
|
|
65
|
+
'DepartmentSelectField',
|
|
66
|
+
'CountrySelectField',
|
|
67
|
+
'AddressField',
|
|
68
|
+
'AttachmentField',
|
|
69
|
+
'ImageField',
|
|
70
|
+
'TableField',
|
|
71
|
+
'AssociationFormField',
|
|
72
|
+
'SerialNumberField',
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
const PRESENTATION_FIELD_TYPES = Object.freeze([
|
|
76
|
+
'Divider',
|
|
77
|
+
'ColumnContainer',
|
|
78
|
+
'Column',
|
|
79
|
+
'GroupContainer',
|
|
80
|
+
'PageSection',
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const OPTION_FIELD_TYPES = Object.freeze([
|
|
84
|
+
'SelectField',
|
|
85
|
+
'RadioField',
|
|
86
|
+
'CheckboxField',
|
|
87
|
+
'MultiSelectField',
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const READABLE_I18N_LABEL_KEYS = Object.freeze([
|
|
91
|
+
'zh_CN',
|
|
92
|
+
'en_US',
|
|
93
|
+
'ja_JP',
|
|
94
|
+
'zh_TW',
|
|
95
|
+
'zh_HK',
|
|
96
|
+
'pureEn_US',
|
|
97
|
+
'name',
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
function isPlainObject(value) {
|
|
101
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function describeActual(value) {
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
return 'array';
|
|
107
|
+
}
|
|
108
|
+
if (value === null) {
|
|
109
|
+
return 'null';
|
|
110
|
+
}
|
|
111
|
+
return typeof value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readDefinitionType(field) {
|
|
115
|
+
if (!isPlainObject(field)) {
|
|
116
|
+
return '';
|
|
117
|
+
}
|
|
118
|
+
if (field.type !== undefined && field.type !== null && field.type !== '') {
|
|
119
|
+
return String(field.type).trim();
|
|
120
|
+
}
|
|
121
|
+
if (field.componentName !== undefined && field.componentName !== null && field.componentName !== '') {
|
|
122
|
+
return String(field.componentName).trim();
|
|
123
|
+
}
|
|
124
|
+
if (field.componentType !== undefined && field.componentType !== null && field.componentType !== '') {
|
|
125
|
+
return String(field.componentType).trim();
|
|
126
|
+
}
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function normalizeDefinitionType(field) {
|
|
131
|
+
const rawType = readDefinitionType(field);
|
|
132
|
+
if (!rawType) {
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
return PRESENTATION_TYPE_ALIAS[rawType] || FIELD_TYPE_ALIAS[rawType] || rawType;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isBusinessFieldType(type) {
|
|
139
|
+
return BUSINESS_FIELD_TYPES.indexOf(type) !== -1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isPresentationFieldType(type) {
|
|
143
|
+
return PRESENTATION_FIELD_TYPES.indexOf(type) !== -1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function hasNonEmptyText(value) {
|
|
147
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function hasNonEmptyI18nObject(value) {
|
|
151
|
+
if (!isPlainObject(value)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return READABLE_I18N_LABEL_KEYS.some(function (key) {
|
|
155
|
+
return hasNonEmptyText(value[key]);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function hasFieldLabel(value) {
|
|
160
|
+
return hasNonEmptyText(value) || hasNonEmptyI18nObject(value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function hasFixedOptionSource(field) {
|
|
164
|
+
return Array.isArray(field.dataSource) && field.dataSource.length > 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function hasLegacyOptionSource(field) {
|
|
168
|
+
return Array.isArray(field.options) && field.options.length > 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function hasRemoteOptionSource(field) {
|
|
172
|
+
return !!(
|
|
173
|
+
field.remoteDataSource ||
|
|
174
|
+
field.searchDataSource ||
|
|
175
|
+
field.dataSourceConfig ||
|
|
176
|
+
field.dataSourceUrl ||
|
|
177
|
+
field.searchConfig
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function hasAssociationForm(field) {
|
|
182
|
+
return isPlainObject(field.associationForm) && hasNonEmptyText(field.associationForm.formUuid);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function createDiagnostic(code, path, expected, actual, message, suggestion) {
|
|
186
|
+
return {
|
|
187
|
+
code,
|
|
188
|
+
path,
|
|
189
|
+
expected,
|
|
190
|
+
actual,
|
|
191
|
+
message,
|
|
192
|
+
suggestion,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function pushDiagnostic(diagnostics, code, path, expected, actual, message, suggestion) {
|
|
197
|
+
diagnostics.push(createDiagnostic(code, path, expected, actual, message, suggestion));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function validateChildrenPropertyShape(field, path, diagnostics, expectedDescription) {
|
|
201
|
+
if (field.children === undefined || field.children === null) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (!Array.isArray(field.children)) {
|
|
205
|
+
pushDiagnostic(
|
|
206
|
+
diagnostics,
|
|
207
|
+
'INVALID_CHILDREN_SHAPE',
|
|
208
|
+
path + '.children',
|
|
209
|
+
expectedDescription,
|
|
210
|
+
describeActual(field.children),
|
|
211
|
+
'children must be an array with the expected depth for this component.',
|
|
212
|
+
'Rewrite children to match the component-specific shape before creating or updating the form.'
|
|
213
|
+
);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function validateDivider(field, path, diagnostics) {
|
|
220
|
+
if (
|
|
221
|
+
field.title !== undefined &&
|
|
222
|
+
typeof field.title !== 'string' &&
|
|
223
|
+
!isPlainObject(field.title)
|
|
224
|
+
) {
|
|
225
|
+
pushDiagnostic(
|
|
226
|
+
diagnostics,
|
|
227
|
+
'INVALID_DIVIDER_TITLE',
|
|
228
|
+
path + '.title',
|
|
229
|
+
'string or i18n object',
|
|
230
|
+
describeActual(field.title),
|
|
231
|
+
'Divider.title must be a string or an i18n object when provided.',
|
|
232
|
+
'Use a plain string title or omit title.'
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (field.dividerType !== undefined && typeof field.dividerType !== 'string') {
|
|
236
|
+
pushDiagnostic(
|
|
237
|
+
diagnostics,
|
|
238
|
+
'INVALID_DIVIDER_TYPE',
|
|
239
|
+
path + '.dividerType',
|
|
240
|
+
'string',
|
|
241
|
+
describeActual(field.dividerType),
|
|
242
|
+
'Divider.dividerType must be a string when provided.',
|
|
243
|
+
'Use a supported divider style string or omit dividerType.'
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function validateFieldDefinition(field, path, diagnostics, options) {
|
|
249
|
+
const validationOptions = options || {};
|
|
250
|
+
|
|
251
|
+
if (!isPlainObject(field)) {
|
|
252
|
+
pushDiagnostic(
|
|
253
|
+
diagnostics,
|
|
254
|
+
'INVALID_FIELD_DEFINITION',
|
|
255
|
+
path,
|
|
256
|
+
'object',
|
|
257
|
+
describeActual(field),
|
|
258
|
+
'Field definition must be an object.',
|
|
259
|
+
'Replace this item with an object such as {"type":"TextField","label":"Name"}.'
|
|
260
|
+
);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const type = normalizeDefinitionType(field);
|
|
265
|
+
if (!type) {
|
|
266
|
+
pushDiagnostic(
|
|
267
|
+
diagnostics,
|
|
268
|
+
'FIELD_TYPE_MISSING',
|
|
269
|
+
path + '.type',
|
|
270
|
+
'non-empty supported field type',
|
|
271
|
+
'missing',
|
|
272
|
+
'Field definition must include type, componentName, or componentType.',
|
|
273
|
+
'Add a supported type such as TextField, SelectField, TableField, or ColumnContainer.'
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!isBusinessFieldType(type) && !isPresentationFieldType(type)) {
|
|
279
|
+
pushDiagnostic(
|
|
280
|
+
diagnostics,
|
|
281
|
+
'UNSUPPORTED_FIELD_TYPE',
|
|
282
|
+
path + '.type',
|
|
283
|
+
BUSINESS_FIELD_TYPES.concat(PRESENTATION_FIELD_TYPES).join(', '),
|
|
284
|
+
type,
|
|
285
|
+
'Field type is not supported by openyida create-form.',
|
|
286
|
+
'Use one of the supported field types, or add CLI support before using this type.'
|
|
287
|
+
);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (validationOptions.insideTable && type === 'TableField') {
|
|
292
|
+
pushDiagnostic(
|
|
293
|
+
diagnostics,
|
|
294
|
+
'NESTED_TABLE_FIELD_UNSUPPORTED',
|
|
295
|
+
path,
|
|
296
|
+
'non-TableField child',
|
|
297
|
+
'TableField',
|
|
298
|
+
'TableField.children cannot contain another TableField.',
|
|
299
|
+
'Move the nested table to the top level or flatten the subtable fields.'
|
|
300
|
+
);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (validationOptions.insideTable && isPresentationFieldType(type)) {
|
|
305
|
+
pushDiagnostic(
|
|
306
|
+
diagnostics,
|
|
307
|
+
'TABLE_CHILD_PRESENTATION_UNSUPPORTED',
|
|
308
|
+
path,
|
|
309
|
+
'business field object',
|
|
310
|
+
type,
|
|
311
|
+
'TableField.children cannot contain presentation components.',
|
|
312
|
+
'Use business fields only inside TableField.children.'
|
|
313
|
+
);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (type === 'Column') {
|
|
318
|
+
if (!validationOptions.allowColumn) {
|
|
319
|
+
pushDiagnostic(
|
|
320
|
+
diagnostics,
|
|
321
|
+
'COLUMN_OUTSIDE_COLUMN_CONTAINER',
|
|
322
|
+
path,
|
|
323
|
+
'Column object inside ColumnContainer.children',
|
|
324
|
+
'Column',
|
|
325
|
+
'Column can only be used as a ColumnContainer.children Column object.',
|
|
326
|
+
'Move this Column object under ColumnContainer.children, or replace it with a normal field definition.'
|
|
327
|
+
);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
validateColumnObject(field, path, diagnostics);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (isBusinessFieldType(type) && !hasFieldLabel(field.label)) {
|
|
335
|
+
pushDiagnostic(
|
|
336
|
+
diagnostics,
|
|
337
|
+
'BUSINESS_FIELD_LABEL_MISSING',
|
|
338
|
+
path + '.label',
|
|
339
|
+
'non-empty string or i18n object',
|
|
340
|
+
field.label === undefined ? 'missing' : describeActual(field.label),
|
|
341
|
+
'Business field definitions must include a non-empty label.',
|
|
342
|
+
'Add a label that users will see on the form, such as "Name" or {"zh_CN":"姓名","en_US":"Name"}.'
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (type === 'Divider') {
|
|
347
|
+
validateDivider(field, path, diagnostics);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (type === 'ColumnContainer') {
|
|
352
|
+
validateColumnContainer(field, path, diagnostics);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (type === 'GroupContainer' || type === 'PageSection') {
|
|
357
|
+
validateOneDimensionalChildren(field, path, diagnostics, type);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (type === 'TableField') {
|
|
362
|
+
validateTableField(field, path, diagnostics);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (
|
|
366
|
+
OPTION_FIELD_TYPES.indexOf(type) !== -1 &&
|
|
367
|
+
!hasFixedOptionSource(field) &&
|
|
368
|
+
!hasLegacyOptionSource(field) &&
|
|
369
|
+
!hasRemoteOptionSource(field)
|
|
370
|
+
) {
|
|
371
|
+
pushDiagnostic(
|
|
372
|
+
diagnostics,
|
|
373
|
+
'OPTION_FIELD_DATASOURCE_MISSING',
|
|
374
|
+
path + '.dataSource',
|
|
375
|
+
'non-empty dataSource/options array or remote data source config',
|
|
376
|
+
'missing',
|
|
377
|
+
type + ' must define fixed options or a remote data source.',
|
|
378
|
+
'Provide dataSource (preferred), options, remoteDataSource, dataSourceConfig, dataSourceUrl, or searchConfig.'
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (type === 'AssociationFormField' && !hasAssociationForm(field)) {
|
|
383
|
+
pushDiagnostic(
|
|
384
|
+
diagnostics,
|
|
385
|
+
'ASSOCIATION_FORM_MISSING',
|
|
386
|
+
path + '.associationForm',
|
|
387
|
+
'object with non-empty formUuid',
|
|
388
|
+
field.associationForm === undefined ? 'missing' : describeActual(field.associationForm),
|
|
389
|
+
'AssociationFormField must include associationForm.formUuid.',
|
|
390
|
+
'Set associationForm to the target form metadata before creating or updating the form.'
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (
|
|
395
|
+
(type === 'EmployeeField' || type === 'DepartmentSelectField') &&
|
|
396
|
+
field.multiple !== undefined &&
|
|
397
|
+
typeof field.multiple !== 'boolean'
|
|
398
|
+
) {
|
|
399
|
+
pushDiagnostic(
|
|
400
|
+
diagnostics,
|
|
401
|
+
'INVALID_MULTIPLE_TYPE',
|
|
402
|
+
path + '.multiple',
|
|
403
|
+
'boolean',
|
|
404
|
+
describeActual(field.multiple),
|
|
405
|
+
type + '.multiple must be a boolean when provided.',
|
|
406
|
+
'Use true or false instead of strings or numbers.'
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function validateColumnObject(field, path, diagnostics) {
|
|
412
|
+
if (!validateChildrenPropertyShape(field, path, diagnostics, 'FieldDefinition[]')) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (field.children === undefined || field.children === null) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
field.children.forEach(function (child, childIndex) {
|
|
419
|
+
const childPath = path + '.children[' + childIndex + ']';
|
|
420
|
+
if (Array.isArray(child)) {
|
|
421
|
+
pushDiagnostic(
|
|
422
|
+
diagnostics,
|
|
423
|
+
'INVALID_COLUMN_CONTAINER_CHILDREN_DEPTH',
|
|
424
|
+
childPath,
|
|
425
|
+
'FieldDefinition object',
|
|
426
|
+
'array',
|
|
427
|
+
'Column.children must be a one-dimensional field array.',
|
|
428
|
+
'Remove the extra array nesting so Column.children is FieldDefinition[].'
|
|
429
|
+
);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
validateFieldDefinition(child, childPath, diagnostics);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function validateColumnContainer(field, path, diagnostics) {
|
|
437
|
+
if (!validateChildrenPropertyShape(field, path, diagnostics, 'FieldDefinition[][] or Column[]')) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (field.children === undefined || field.children === null) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
field.children.forEach(function (columnChildren, columnIndex) {
|
|
444
|
+
const columnPath = path + '.children[' + columnIndex + ']';
|
|
445
|
+
if (isPlainObject(columnChildren) && normalizeDefinitionType(columnChildren) === 'Column') {
|
|
446
|
+
validateFieldDefinition(columnChildren, columnPath, diagnostics, { allowColumn: true });
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (!Array.isArray(columnChildren)) {
|
|
450
|
+
pushDiagnostic(
|
|
451
|
+
diagnostics,
|
|
452
|
+
'INVALID_COLUMN_CONTAINER_CHILDREN_DEPTH',
|
|
453
|
+
columnPath,
|
|
454
|
+
'array of FieldDefinition objects or Column object',
|
|
455
|
+
describeActual(columnChildren),
|
|
456
|
+
'ColumnContainer.children must contain two-dimensional field arrays or Column objects.',
|
|
457
|
+
'Use children: [[fieldA], [fieldB]] or children: [{ type: "Column", children: [fieldA] }].'
|
|
458
|
+
);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
columnChildren.forEach(function (child, childIndex) {
|
|
462
|
+
const childPath = columnPath + '[' + childIndex + ']';
|
|
463
|
+
if (Array.isArray(child)) {
|
|
464
|
+
pushDiagnostic(
|
|
465
|
+
diagnostics,
|
|
466
|
+
'INVALID_COLUMN_CONTAINER_CHILDREN_DEPTH',
|
|
467
|
+
childPath,
|
|
468
|
+
'FieldDefinition object',
|
|
469
|
+
'array',
|
|
470
|
+
'ColumnContainer.children must be exactly two levels deep.',
|
|
471
|
+
'Remove the extra array nesting so children is FieldDefinition[][].'
|
|
472
|
+
);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
validateFieldDefinition(child, childPath, diagnostics);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function validateOneDimensionalChildren(field, path, diagnostics, type) {
|
|
481
|
+
if (!validateChildrenPropertyShape(field, path, diagnostics, 'FieldDefinition[]')) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (field.children === undefined || field.children === null) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
field.children.forEach(function (child, childIndex) {
|
|
488
|
+
const childPath = path + '.children[' + childIndex + ']';
|
|
489
|
+
if (Array.isArray(child)) {
|
|
490
|
+
pushDiagnostic(
|
|
491
|
+
diagnostics,
|
|
492
|
+
'INVALID_' + type.toUpperCase() + '_CHILDREN_DEPTH',
|
|
493
|
+
childPath,
|
|
494
|
+
'FieldDefinition object',
|
|
495
|
+
'array',
|
|
496
|
+
type + '.children must be a one-dimensional field array.',
|
|
497
|
+
'Remove extra array nesting from ' + type + '.children.'
|
|
498
|
+
);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
validateFieldDefinition(child, childPath, diagnostics);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function validateTableField(field, path, diagnostics) {
|
|
506
|
+
if (!validateChildrenPropertyShape(field, path, diagnostics, 'FieldDefinition[]')) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (field.children === undefined || field.children === null) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
field.children.forEach(function (child, childIndex) {
|
|
513
|
+
const childPath = path + '.children[' + childIndex + ']';
|
|
514
|
+
if (Array.isArray(child)) {
|
|
515
|
+
pushDiagnostic(
|
|
516
|
+
diagnostics,
|
|
517
|
+
'INVALID_TABLE_FIELD_CHILDREN_DEPTH',
|
|
518
|
+
childPath,
|
|
519
|
+
'FieldDefinition object',
|
|
520
|
+
'array',
|
|
521
|
+
'TableField.children must be a one-dimensional field array.',
|
|
522
|
+
'Remove extra array nesting from TableField.children.'
|
|
523
|
+
);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
validateFieldDefinition(child, childPath, diagnostics, { insideTable: true });
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function validateFormFieldDefinitions(fields, options) {
|
|
531
|
+
const validationOptions = options || {};
|
|
532
|
+
const rootPath = validationOptions.rootPath || 'fields';
|
|
533
|
+
const diagnostics = [];
|
|
534
|
+
|
|
535
|
+
if (!Array.isArray(fields)) {
|
|
536
|
+
pushDiagnostic(
|
|
537
|
+
diagnostics,
|
|
538
|
+
'INVALID_FIELDS_ROOT',
|
|
539
|
+
rootPath,
|
|
540
|
+
'FieldDefinition[]',
|
|
541
|
+
describeActual(fields),
|
|
542
|
+
'Field definitions root must be an array.',
|
|
543
|
+
'Use an array of field definitions or an object with a fields array.'
|
|
544
|
+
);
|
|
545
|
+
return diagnostics;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
fields.forEach(function (field, index) {
|
|
549
|
+
validateFieldDefinition(field, rootPath + '[' + index + ']', diagnostics);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
return diagnostics;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
module.exports = {
|
|
556
|
+
validateFormFieldDefinitions,
|
|
557
|
+
normalizeDefinitionType,
|
|
558
|
+
BUSINESS_FIELD_TYPES,
|
|
559
|
+
PRESENTATION_FIELD_TYPES,
|
|
560
|
+
};
|
|
@@ -358,6 +358,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
|
|
|
358
358
|
'agent-capabilities',
|
|
359
359
|
'check-page',
|
|
360
360
|
'commands',
|
|
361
|
+
'create-form.validate-fields',
|
|
361
362
|
'dingtalk-link',
|
|
362
363
|
'formula.evaluate',
|
|
363
364
|
'integration.diagnose',
|
|
@@ -616,6 +617,7 @@ const COMMAND_PERMISSIONS = new Map([
|
|
|
616
617
|
'connector.list',
|
|
617
618
|
'connector.list-actions',
|
|
618
619
|
'connector.list-connections',
|
|
620
|
+
'create-form.validate-fields',
|
|
619
621
|
'dingtalk-link',
|
|
620
622
|
'dws.contact-user-search',
|
|
621
623
|
'formula.evaluate',
|
|
@@ -1025,6 +1027,9 @@ const COMMAND_GROUPS = [
|
|
|
1025
1027
|
titleKey: 'help.group_form',
|
|
1026
1028
|
commands: [
|
|
1027
1029
|
command('create-form.create', ['create-form', 'create'], 'create-form create <appType> ... [--locale zh_CN|en_US|ja_JP] [--open|--no-open]', 'help.cmd_create_form'),
|
|
1030
|
+
command('create-form.validate-fields', ['create-form', 'validate-fields'], 'create-form validate-fields <fieldsJsonOrFile> [--json]', 'help.cmd_validate_form', {
|
|
1031
|
+
requiresLogin: false,
|
|
1032
|
+
}),
|
|
1028
1033
|
command('create-form.update', ['create-form', 'update'], 'create-form update <appType> ... [--locale zh_CN|en_US|ja_JP] [--open|--no-open]', 'help.cmd_update_form'),
|
|
1029
1034
|
command('create-form.patch', ['create-form', 'patch'], 'create-form patch <appType> <formUuid> <patchJsonOrFile> [--open|--no-open]', 'help.cmd_update_form'),
|
|
1030
1035
|
command('create-form.rule', ['create-form', 'rule'], 'create-form rule <appType> <formUuid> <rulesJsonOrFile> [--open|--no-open]', 'help.cmd_update_form'),
|
package/lib/core/locales/en.js
CHANGED
|
@@ -30,6 +30,7 @@ module.exports = {
|
|
|
30
30
|
cmd_import: 'Import migration package, rebuild app',
|
|
31
31
|
group_form: 'Forms & Pages',
|
|
32
32
|
cmd_create_form: 'Create a form page',
|
|
33
|
+
cmd_validate_form: 'Validate form field JSON locally',
|
|
33
34
|
cmd_update_form: 'Update a form page',
|
|
34
35
|
cmd_list_forms: 'List forms/pages in an app',
|
|
35
36
|
cmd_aggregate_table: 'Manage aggregate tables (virtualView)',
|
package/lib/core/locales/zh.js
CHANGED
package/package.json
CHANGED
|
@@ -219,8 +219,8 @@ openyida create-form rule <appType> <formUuid> <rulesJsonOrFile>
|
|
|
219
219
|
| `TextField` / `TextareaField` | 单行 / 多行文本 | 最常用文本字段 |
|
|
220
220
|
| `NumberField` | 数字 | 金额、数量、分值 |
|
|
221
221
|
| `DateField` / `CascadeDateField` | 日期 / 日期区间 | 流程表单常用 |
|
|
222
|
-
| `SelectField` / `RadioField` | 单选 |
|
|
223
|
-
| `CheckboxField` / `MultiSelectField` | 多选 |
|
|
222
|
+
| `SelectField` / `RadioField` | 单选 | 创建字段 JSON 时必须提供 `dataSource`,不要省略或只写旧式 `options` |
|
|
223
|
+
| `CheckboxField` / `MultiSelectField` | 多选 | 创建字段 JSON 时必须提供 `dataSource`,不要省略或只写旧式 `options` |
|
|
224
224
|
| `EmployeeField` | 成员 | 细节见 [employee-field.md](references/employee-field.md) |
|
|
225
225
|
| `DepartmentSelectField` | 部门 | 支持 `multiple` |
|
|
226
226
|
| `AttachmentField` / `ImageField` | 附件 / 图片 | 表单内上传能力 |
|
|
@@ -244,7 +244,7 @@ openyida create-form rule <appType> <formUuid> <rulesJsonOrFile>
|
|
|
244
244
|
|
|
245
245
|
- `appType` 必须来自已创建应用或用户提供
|
|
246
246
|
- 字段类型必须使用标准组件名,如 `TextField`、`SelectField`
|
|
247
|
-
- `SelectField`、`RadioField`、`CheckboxField
|
|
247
|
+
- `SelectField`、`MultiSelectField`、`RadioField`、`CheckboxField` 固定选项必须提供 `dataSource`;远程选项字段必须提供 `remoteDataSource` 或通过 `bind-datasource` 配置,不要生成无选项源的字段 JSON。
|
|
248
248
|
- `TableField` 必须提供 `children`,且子表不能嵌套子表
|
|
249
249
|
- `AssociationFormField` 必须提供 `associationForm`
|
|
250
250
|
- update / add-option / bind-datasource / validation / rule 按字段 `label`、`fieldId` 或 `tableLabel + label` 解析并要求唯一命中;如果有重名字段,先看命令返回的 `diagnostics[].candidates`,可用 `tableLabel` 或 `fieldId` 缩小范围,仍不明确时再用 `get-schema --compact --resolve-fields`。
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
| `children` | Object[] | 条件必填 | `TableField` / 展示布局组件必填 |
|
|
43
43
|
| `associationForm` | Object | 条件必填 | `AssociationFormField` 必填 |
|
|
44
44
|
|
|
45
|
+
选项类字段包括 `SelectField`、`MultiSelectField`、`RadioField`、`CheckboxField`。固定选项必须在字段 JSON 中提供非空 `dataSource`;不要省略选项源,也不要只写旧式 `options`。
|
|
46
|
+
|
|
45
47
|
## 展示/布局组件
|
|
46
48
|
|
|
47
49
|
### Divider
|