openyida 2026.8.31 → 2026.9.2-beta.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.
- package/README.md +2 -2
- package/lib/app/create-app.js +1 -0
- package/lib/app/create-form.js +334 -66
- package/lib/app/display-page-readback.js +63 -5
- package/lib/app/form-action-binding.js +185 -0
- package/lib/app/get-schema.js +168 -14
- package/lib/app/publish.js +138 -6
- package/lib/app/schema-semantic-analysis.js +56 -14
- package/lib/auth/token-store.js +13 -1
- package/lib/core/command-manifest.js +3 -3
- package/lib/core/locales/en.js +20 -0
- package/lib/core/locales/zh.js +20 -0
- package/lib/core/query-data.js +215 -23
- package/lib/core/utils.js +409 -0
- package/lib/report/append.js +3 -1
- package/lib/report/contract.js +7 -0
- package/lib/report/index.js +94 -5
- package/lib/report/inspect.js +32 -3
- package/lib/report/runtime-probe.js +248 -0
- package/lib/report/url.js +13 -0
- package/package.json +1 -1
- package/yida-skills/references/report-field-config-guide.md +10 -28
- package/yida-skills/references/yida-api.md +10 -0
- package/yida-skills/skills/yida-app/SKILL.md +5 -3
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-5-seed-records.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +9 -7
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +5 -2
- package/yida-skills/skills/yida-chart/references/echarts-bindding-guide.md +1 -1
- package/yida-skills/skills/yida-create-form-page/SKILL.md +2 -2
- package/yida-skills/skills/yida-create-form-page/references/advanced-form-modes.md +35 -2
- package/yida-skills/skills/yida-data-management/SKILL.md +17 -16
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +1 -1
- package/yida-skills/skills/yida-design/SKILL.md +1 -1
- package/yida-skills/skills/yida-design/workflow/output-prd.md +6 -4
- package/yida-skills/skills/yida-design/workflow/step-3-information-architecture.md +5 -2
- package/yida-skills/skills/yida-report/SKILL.md +6 -11
- package/yida-skills/skills/yida-report/references/examples.md +11 -18
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ openyida configure-process APP_XXX FORM_XXX .cache/openyida/process/process.json
|
|
|
200
200
|
openyida process preview APP_XXX PROC_INST_XXX --output .cache/openyida/process/process.html
|
|
201
201
|
openyida data query form APP_XXX FORM_XXX --page 1 --size 20
|
|
202
202
|
openyida data query form APP_XXX FORM_XXX --dynamic-order '{"dateField_xxx":"-"}'
|
|
203
|
-
openyida data create form APP_XXX FORM_XXX --data-file .cache/openyida/data-import/record.json
|
|
203
|
+
openyida data create form APP_XXX FORM_XXX --expect-form-name 客户 --expect-form-type receipt --data-file .cache/openyida/data-import/record.json
|
|
204
204
|
openyida get-permission APP_XXX FORM_XXX
|
|
205
205
|
```
|
|
206
206
|
|
|
@@ -430,7 +430,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
430
430
|
|
|
431
431
|
| Command | Description |
|
|
432
432
|
|---------|-------------|
|
|
433
|
-
| `openyida data <query\|get\|create\|update> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --confirm [--json]` | Unified data management (form/process/task/subform) |
|
|
433
|
+
| `openyida data <query\|get\|create\|update> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --expect-form-name <name> --expect-form-type receipt --confirm [--json]` | Unified data management (form/process/task/subform) |
|
|
434
434
|
| `openyida task-center <type> [options]` | Global task center (todo/processed/cc etc.) |
|
|
435
435
|
| `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
|
|
436
436
|
| `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
|
package/lib/app/create-app.js
CHANGED
|
@@ -269,6 +269,7 @@ function buildCreateAppPayload(params, auth, contentLocale, openExclusive, openP
|
|
|
269
269
|
openPhysicColumn,
|
|
270
270
|
openIsolationDatabase: 'n',
|
|
271
271
|
openExclusiveUnit: 'n',
|
|
272
|
+
createWithModernTheme: 'y',
|
|
272
273
|
group: 'ALL',
|
|
273
274
|
fromBuilderAi: 'y',
|
|
274
275
|
builderAiSource: resolveBuilderAiSource(),
|
package/lib/app/create-form.js
CHANGED
|
@@ -84,6 +84,13 @@ const { dispatchCreateFormCommand } = require('./create-form/commands');
|
|
|
84
84
|
const { buildApiPath } = require('./create-form/api-path');
|
|
85
85
|
const { deepMerge, splitJsonPointer } = require('./create-form/schema-patch');
|
|
86
86
|
const { ensureYidaGlobalThemeAction } = require('./form-theme-action');
|
|
87
|
+
const {
|
|
88
|
+
actionRefName,
|
|
89
|
+
buildDesignerEventBinding,
|
|
90
|
+
extractExportedActionFunctionNames,
|
|
91
|
+
inspectActionBindings,
|
|
92
|
+
syncDesignerActionCatalog,
|
|
93
|
+
} = require('./form-action-binding');
|
|
87
94
|
const { FORM_RULES_BLOCK_END, FORM_RULES_BLOCK_START } = require('./create-form/rule-builder');
|
|
88
95
|
const { SMART_VALIDATION_BLOCK_END, SMART_VALIDATION_BLOCK_START } = require('./create-form/validation-builder');
|
|
89
96
|
const { createFieldNormalizers } = require('./create-form/field-normalizers');
|
|
@@ -2678,6 +2685,116 @@ function compileActionSource(source) {
|
|
|
2678
2685
|
return compiledResult.compiled;
|
|
2679
2686
|
}
|
|
2680
2687
|
|
|
2688
|
+
function bindFieldAction(schema, formContainer, operation) {
|
|
2689
|
+
if (!formContainer || !formContainer.children) {
|
|
2690
|
+
throw new Error('未找到 FormContainer');
|
|
2691
|
+
}
|
|
2692
|
+
const fieldKey = operation.fieldId || operation.field || operation.label;
|
|
2693
|
+
const eventName = operation.event || 'onChange';
|
|
2694
|
+
const actionName = operation.name || operation.actionName;
|
|
2695
|
+
if (!actionName) {
|
|
2696
|
+
throw new Error('bind-field-action 必须提供 name/actionName');
|
|
2697
|
+
}
|
|
2698
|
+
if (!isValidActionIdentifier(actionName)) {
|
|
2699
|
+
throw new Error('动作名称不是合法的 JavaScript 标识符: ' + actionName);
|
|
2700
|
+
}
|
|
2701
|
+
const found = findFieldByIdOrLabelDeep(formContainer.children, fieldKey);
|
|
2702
|
+
if (!found) {
|
|
2703
|
+
throw new Error('未找到字段: ' + fieldKey);
|
|
2704
|
+
}
|
|
2705
|
+
found.field.props = found.field.props || {};
|
|
2706
|
+
const existingActionName = actionRefName(found.field.props[eventName]);
|
|
2707
|
+
if (existingActionName && existingActionName !== actionName && operation.replaceExisting !== true) {
|
|
2708
|
+
throw createCreateFormError(
|
|
2709
|
+
t('create_form.action_event_conflict', fieldKey, eventName, existingActionName),
|
|
2710
|
+
'FORM_ACTION_EVENT_CONFLICT',
|
|
2711
|
+
{
|
|
2712
|
+
status: 'SEMANTIC_FAILURE',
|
|
2713
|
+
retryable: false,
|
|
2714
|
+
sideEffectState: 'none',
|
|
2715
|
+
nextStep: 'review_existing_field_action',
|
|
2716
|
+
field: fieldKey,
|
|
2717
|
+
event: eventName,
|
|
2718
|
+
existingActionName,
|
|
2719
|
+
requestedActionName: actionName,
|
|
2720
|
+
}
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
found.field.props[eventName] = buildDesignerEventBinding(
|
|
2725
|
+
actionName,
|
|
2726
|
+
operation.params || {},
|
|
2727
|
+
found.field.props[eventName]
|
|
2728
|
+
);
|
|
2729
|
+
|
|
2730
|
+
const actions = ensureActionModule(schema);
|
|
2731
|
+
const fieldId = found.field.props.fieldId || found.field.id || String(fieldKey || '');
|
|
2732
|
+
const relatedEventId = operation.relatedEventId || ((found.field.id || fieldId) + ':' + eventName);
|
|
2733
|
+
syncDesignerActionCatalog(actions);
|
|
2734
|
+
|
|
2735
|
+
return {
|
|
2736
|
+
action: operation.action === 'field-action' ? 'field-action' : 'bind-field-action',
|
|
2737
|
+
field: fieldKey,
|
|
2738
|
+
fieldId,
|
|
2739
|
+
event: eventName,
|
|
2740
|
+
actionName,
|
|
2741
|
+
relatedEventId,
|
|
2742
|
+
replacedActionName: existingActionName && existingActionName !== actionName ? existingActionName : '',
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
function applyAtomicFieldAction(schema, formContainer, operation) {
|
|
2747
|
+
const actionName = operation.name || operation.actionName;
|
|
2748
|
+
if (!actionName) {
|
|
2749
|
+
throw new Error('field-action 必须提供 name/actionName');
|
|
2750
|
+
}
|
|
2751
|
+
if (!isValidActionIdentifier(actionName)) {
|
|
2752
|
+
throw new Error('动作名称不是合法的 JavaScript 标识符: ' + actionName);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
const actions = ensureActionModule(schema);
|
|
2756
|
+
const source = readOptionalSource(operation, 'source', 'sourceFile');
|
|
2757
|
+
if (source !== undefined) {
|
|
2758
|
+
if (!extractExportedActionFunctionNames(source).includes(actionName)) {
|
|
2759
|
+
throw createCreateFormError(
|
|
2760
|
+
t('create_form.action_source_export_missing', actionName),
|
|
2761
|
+
'FORM_ACTION_FUNCTION_MISSING',
|
|
2762
|
+
{
|
|
2763
|
+
status: 'SEMANTIC_FAILURE',
|
|
2764
|
+
retryable: false,
|
|
2765
|
+
sideEffectState: 'none',
|
|
2766
|
+
nextStep: 'export_named_action_function',
|
|
2767
|
+
actionName,
|
|
2768
|
+
}
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
const blockStart = '/* openyida:field-action:start:' + actionName + ' */';
|
|
2772
|
+
const blockEnd = '/* openyida:field-action:end:' + actionName + ' */';
|
|
2773
|
+
actions.module.source = upsertGeneratedSourceBlockWithBounds(
|
|
2774
|
+
actions.module.source || '',
|
|
2775
|
+
source,
|
|
2776
|
+
blockStart,
|
|
2777
|
+
blockEnd
|
|
2778
|
+
);
|
|
2779
|
+
actions.module.compiled = compileActionSource(actions.module.source);
|
|
2780
|
+
actions.type = actions.type || 'FUNCTION';
|
|
2781
|
+
} else if (!extractExportedActionFunctionNames(actions.module.source || '').includes(actionName)) {
|
|
2782
|
+
throw createCreateFormError(
|
|
2783
|
+
t('create_form.action_source_missing', actionName),
|
|
2784
|
+
'FORM_ACTION_FUNCTION_MISSING',
|
|
2785
|
+
{
|
|
2786
|
+
status: 'SEMANTIC_FAILURE',
|
|
2787
|
+
retryable: false,
|
|
2788
|
+
sideEffectState: 'none',
|
|
2789
|
+
nextStep: 'provide_action_source',
|
|
2790
|
+
actionName,
|
|
2791
|
+
}
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
return bindFieldAction(schema, formContainer, Object.assign({}, operation, { action: 'field-action' }));
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2681
2798
|
function readOptionalSource(operation, key, fileKey) {
|
|
2682
2799
|
if (operation[fileKey]) {
|
|
2683
2800
|
const sourcePath = path.resolve(operation[fileKey]);
|
|
@@ -2773,6 +2890,11 @@ function applySchemaPatchOperations(schema, operations) {
|
|
|
2773
2890
|
return;
|
|
2774
2891
|
}
|
|
2775
2892
|
|
|
2893
|
+
if (action === 'field-action') {
|
|
2894
|
+
applied.push(applyAtomicFieldAction(schema, formContainer, operation));
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2776
2898
|
if (['bind-datasource', 'data-source', 'datasource', 'select-datasource'].includes(String(action).toLowerCase())) {
|
|
2777
2899
|
if (!formContainer || !formContainer.children) {
|
|
2778
2900
|
throw new Error('未找到 FormContainer');
|
|
@@ -2802,42 +2924,7 @@ function applySchemaPatchOperations(schema, operations) {
|
|
|
2802
2924
|
}
|
|
2803
2925
|
|
|
2804
2926
|
if (action === 'bind-field-action') {
|
|
2805
|
-
|
|
2806
|
-
throw new Error('未找到 FormContainer');
|
|
2807
|
-
}
|
|
2808
|
-
const fieldKey = operation.fieldId || operation.field || operation.label;
|
|
2809
|
-
const eventName = operation.event || 'onChange';
|
|
2810
|
-
const actionName = operation.name || operation.actionName;
|
|
2811
|
-
if (!actionName) {
|
|
2812
|
-
throw new Error('bind-field-action 必须提供 name/actionName');
|
|
2813
|
-
}
|
|
2814
|
-
const found = findFieldByIdOrLabelDeep(formContainer.children, fieldKey);
|
|
2815
|
-
if (!found) {
|
|
2816
|
-
throw new Error('未找到字段: ' + fieldKey);
|
|
2817
|
-
}
|
|
2818
|
-
found.field.props = found.field.props || {};
|
|
2819
|
-
found.field.props[eventName] = {
|
|
2820
|
-
name: actionName,
|
|
2821
|
-
id: actionName,
|
|
2822
|
-
params: operation.params || {},
|
|
2823
|
-
type: 'actionRef',
|
|
2824
|
-
};
|
|
2825
|
-
|
|
2826
|
-
const actions = ensureActionModule(schema);
|
|
2827
|
-
const relatedEventId = operation.relatedEventId || (found.field.id + ':' + eventName);
|
|
2828
|
-
const existing = actions.list.find(function (item) {
|
|
2829
|
-
return item.relatedEventId === relatedEventId && item.id === actionName;
|
|
2830
|
-
});
|
|
2831
|
-
if (!existing) {
|
|
2832
|
-
actions.list.push({
|
|
2833
|
-
relatedEventId,
|
|
2834
|
-
name: actionName,
|
|
2835
|
-
id: actionName,
|
|
2836
|
-
type: 'componentEvent',
|
|
2837
|
-
params: operation.params || {},
|
|
2838
|
-
});
|
|
2839
|
-
}
|
|
2840
|
-
applied.push({ action: 'bind-field-action', field: fieldKey, event: eventName, actionName });
|
|
2927
|
+
applied.push(bindFieldAction(schema, formContainer, operation));
|
|
2841
2928
|
return;
|
|
2842
2929
|
}
|
|
2843
2930
|
|
|
@@ -3246,18 +3333,71 @@ function upsertActionListEntry(actions, entry) {
|
|
|
3246
3333
|
actions.list.push(entry);
|
|
3247
3334
|
}
|
|
3248
3335
|
|
|
3336
|
+
function removeObsoleteGeneratedRuleCatalogEntries(actions, activeActionNames) {
|
|
3337
|
+
// Only prune OpenYida-owned rule wrappers; unknown user/platform entries must survive catalog sync.
|
|
3338
|
+
const activeNames = new Set(activeActionNames || []);
|
|
3339
|
+
actions.list = (actions.list || []).filter(function (item) {
|
|
3340
|
+
const names = [item && item.id, item && item.name].filter(Boolean).map(String);
|
|
3341
|
+
const generatedNames = names.filter(function (name) {
|
|
3342
|
+
return name.startsWith('openyidaRuleChange_');
|
|
3343
|
+
});
|
|
3344
|
+
return generatedNames.length === 0 || generatedNames.some(function (name) {
|
|
3345
|
+
return activeNames.has(name);
|
|
3346
|
+
});
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
function previousRuleActionName(source, wrapperName) {
|
|
3351
|
+
const start = String(source || '').indexOf('export function ' + wrapperName + '(');
|
|
3352
|
+
if (start === -1) {return '';}
|
|
3353
|
+
const end = String(source || '').indexOf('return this.openyidaApplyRules', start);
|
|
3354
|
+
const wrapperSource = String(source || '').slice(start, end === -1 ? undefined : end);
|
|
3355
|
+
const match = wrapperSource.match(/typeof this\.([A-Za-z_$][A-Za-z0-9_$]*) === ["']function["']/);
|
|
3356
|
+
return match ? match[1] : '';
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
function reconcileGeneratedRuleBindings(value, nextSourceFieldIds, actionsSource) {
|
|
3360
|
+
if (!value) {return;}
|
|
3361
|
+
if (Array.isArray(value)) {
|
|
3362
|
+
value.forEach(function (item) {
|
|
3363
|
+
reconcileGeneratedRuleBindings(item, nextSourceFieldIds, actionsSource);
|
|
3364
|
+
});
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3367
|
+
if (typeof value !== 'object') {return;}
|
|
3368
|
+
|
|
3369
|
+
const props = value.props || {};
|
|
3370
|
+
const fieldId = String(props.fieldId || '');
|
|
3371
|
+
const existingActionName = actionRefName(props.onChange);
|
|
3372
|
+
if (fieldId && existingActionName.startsWith('openyidaRuleChange_') && !nextSourceFieldIds.has(fieldId)) {
|
|
3373
|
+
const previousActionName = previousRuleActionName(actionsSource, existingActionName);
|
|
3374
|
+
if (previousActionName) {
|
|
3375
|
+
props.onChange = buildDesignerEventBinding(previousActionName, {}, props.onChange);
|
|
3376
|
+
} else {
|
|
3377
|
+
delete props.onChange;
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
Object.values(value).forEach(function (child) {
|
|
3382
|
+
if (child !== props) {
|
|
3383
|
+
reconcileGeneratedRuleBindings(child, nextSourceFieldIds, actionsSource);
|
|
3384
|
+
}
|
|
3385
|
+
});
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3249
3388
|
function buildFormRulesActionSource(rules, fieldMap, bindings) {
|
|
3250
3389
|
const rulesJson = JSON.stringify(rules, null, 2);
|
|
3251
3390
|
const fieldMapJson = JSON.stringify(fieldMap, null, 2);
|
|
3252
3391
|
const wrapperSource = bindings.map(function (binding) {
|
|
3253
3392
|
const previousCall = binding.previousActionName && isValidActionIdentifier(binding.previousActionName)
|
|
3254
|
-
? ' if (typeof ' + binding.previousActionName + ' === "function") {\n ' + binding.previousActionName + '
|
|
3393
|
+
? ' if (typeof this.' + binding.previousActionName + ' === "function") {\n this.' + binding.previousActionName + '(event);\n }\n'
|
|
3255
3394
|
: '';
|
|
3256
|
-
|
|
3395
|
+
const title = binding.label ? '/** @title ' + binding.label + '值发生变化 */\n' : '';
|
|
3396
|
+
return title + 'export function ' + binding.wrapperName + '(event) {\n' +
|
|
3257
3397
|
' event = event || {};\n' +
|
|
3258
3398
|
' event.__openyidaSourceFieldId = "' + binding.fieldId + '";\n' +
|
|
3259
3399
|
previousCall +
|
|
3260
|
-
' return openyidaApplyRules
|
|
3400
|
+
' return this.openyidaApplyRules(event);\n' +
|
|
3261
3401
|
'}';
|
|
3262
3402
|
}).join('\n\n');
|
|
3263
3403
|
|
|
@@ -3405,10 +3545,10 @@ export function openyidaApplyRules(event) {
|
|
|
3405
3545
|
}
|
|
3406
3546
|
|
|
3407
3547
|
export function openyidaRulesDidMount(event) {
|
|
3408
|
-
if (typeof didMount === 'function') {
|
|
3409
|
-
didMount
|
|
3548
|
+
if (typeof this.didMount === 'function') {
|
|
3549
|
+
this.didMount(event);
|
|
3410
3550
|
}
|
|
3411
|
-
return openyidaApplyRules
|
|
3551
|
+
return this.openyidaApplyRules(event || {});
|
|
3412
3552
|
}
|
|
3413
3553
|
|
|
3414
3554
|
${wrapperSource}
|
|
@@ -3434,6 +3574,8 @@ function applyFormRules(schema, rawRules) {
|
|
|
3434
3574
|
}
|
|
3435
3575
|
});
|
|
3436
3576
|
|
|
3577
|
+
reconcileGeneratedRuleBindings(formContainer.children, sourceFieldIds, actions.module.source || '');
|
|
3578
|
+
|
|
3437
3579
|
const bindings = [];
|
|
3438
3580
|
sourceFieldIds.forEach(function (fieldId) {
|
|
3439
3581
|
const found = findFieldByIdOrLabelDeep(formContainer.children, fieldId);
|
|
@@ -3443,32 +3585,18 @@ function applyFormRules(schema, rawRules) {
|
|
|
3443
3585
|
found.field.props = found.field.props || {};
|
|
3444
3586
|
const eventName = 'onChange';
|
|
3445
3587
|
const existingAction = found.field.props[eventName];
|
|
3446
|
-
const
|
|
3447
|
-
existingAction.type === 'actionRef' &&
|
|
3448
|
-
existingAction.name &&
|
|
3449
|
-
!String(existingAction.name).startsWith('openyidaRuleChange_')
|
|
3450
|
-
? String(existingAction.name)
|
|
3451
|
-
: '';
|
|
3588
|
+
const existingActionName = actionRefName(existingAction);
|
|
3452
3589
|
const wrapperName = 'openyidaRuleChange_' + sanitizeActionName(fieldId);
|
|
3590
|
+
const previousActionName = existingActionName.startsWith('openyidaRuleChange_')
|
|
3591
|
+
? previousRuleActionName(actions.module.source || '', existingActionName)
|
|
3592
|
+
: existingActionName;
|
|
3453
3593
|
const relatedEventId = (found.field.id || fieldId) + ':' + eventName;
|
|
3454
3594
|
|
|
3455
|
-
found.field.props[eventName] =
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
};
|
|
3461
|
-
|
|
3462
|
-
actions.list = actions.list.filter(function (item) {
|
|
3463
|
-
return !(item.relatedEventId === relatedEventId && String(item.id || '').startsWith('openyidaRuleChange_'));
|
|
3464
|
-
});
|
|
3465
|
-
upsertActionListEntry(actions, {
|
|
3466
|
-
relatedEventId,
|
|
3467
|
-
name: wrapperName,
|
|
3468
|
-
id: wrapperName,
|
|
3469
|
-
type: 'componentEvent',
|
|
3470
|
-
params: {},
|
|
3471
|
-
});
|
|
3595
|
+
found.field.props[eventName] = buildDesignerEventBinding(
|
|
3596
|
+
wrapperName,
|
|
3597
|
+
{},
|
|
3598
|
+
found.field.props[eventName]
|
|
3599
|
+
);
|
|
3472
3600
|
|
|
3473
3601
|
bindings.push({
|
|
3474
3602
|
fieldId,
|
|
@@ -3516,6 +3644,11 @@ function applyFormRules(schema, rawRules) {
|
|
|
3516
3644
|
actions.module.source = upsertGeneratedSourceBlock(actions.module.source || '', generatedSource);
|
|
3517
3645
|
actions.module.compiled = compileActionSource(actions.module.source);
|
|
3518
3646
|
actions.type = actions.type || 'FUNCTION';
|
|
3647
|
+
removeObsoleteGeneratedRuleCatalogEntries(actions, bindings.map(function (binding) {
|
|
3648
|
+
return binding.wrapperName;
|
|
3649
|
+
}));
|
|
3650
|
+
// Merge source exports after the rich lifecycle/component entries above so their metadata is preserved.
|
|
3651
|
+
syncDesignerActionCatalog(actions);
|
|
3519
3652
|
|
|
3520
3653
|
return {
|
|
3521
3654
|
rules: normalized.rules,
|
|
@@ -5332,7 +5465,22 @@ async function mainRule(parsedArgs, authRef) {
|
|
|
5332
5465
|
fillSerialNumberFormulas(formContainer.children, corpId, appType, formUuid);
|
|
5333
5466
|
}
|
|
5334
5467
|
|
|
5468
|
+
const actionBindingExpectations = applied.bindings.map(function (binding) {
|
|
5469
|
+
return {
|
|
5470
|
+
fieldId: binding.fieldId,
|
|
5471
|
+
event: binding.eventName,
|
|
5472
|
+
actionName: binding.wrapperName,
|
|
5473
|
+
relatedEventId: binding.relatedEventId,
|
|
5474
|
+
};
|
|
5475
|
+
});
|
|
5476
|
+
assertFormActionBindings(schema, actionBindingExpectations);
|
|
5335
5477
|
await saveFormSchema(authRef, appType, formUuid, schema, version, 4);
|
|
5478
|
+
const actionReadback = await readBackFormActionBindings(
|
|
5479
|
+
authRef,
|
|
5480
|
+
appType,
|
|
5481
|
+
formUuid,
|
|
5482
|
+
actionBindingExpectations
|
|
5483
|
+
);
|
|
5336
5484
|
|
|
5337
5485
|
const formUrl = authRef.baseUrl + '/' + appType + '/workbench/' + formUuid;
|
|
5338
5486
|
result(true, '表单联动规则保存成功', [
|
|
@@ -5347,6 +5495,7 @@ async function mainRule(parsedArgs, authRef) {
|
|
|
5347
5495
|
formUuid,
|
|
5348
5496
|
appType,
|
|
5349
5497
|
rulesApplied: applied.rules.length,
|
|
5498
|
+
readbackVerified: actionReadback.verified,
|
|
5350
5499
|
rules: applied.rules.map(function (rule) {
|
|
5351
5500
|
return {
|
|
5352
5501
|
type: rule.type,
|
|
@@ -5358,11 +5507,16 @@ async function mainRule(parsedArgs, authRef) {
|
|
|
5358
5507
|
};
|
|
5359
5508
|
}),
|
|
5360
5509
|
eventBindings: applied.bindings.map(function (binding) {
|
|
5510
|
+
const readback = actionReadback.bindings.find(function (item) {
|
|
5511
|
+
return item.fieldId === binding.fieldId && item.event === binding.eventName;
|
|
5512
|
+
});
|
|
5361
5513
|
return {
|
|
5362
5514
|
fieldId: binding.fieldId,
|
|
5363
5515
|
label: binding.label,
|
|
5364
5516
|
event: binding.eventName,
|
|
5365
5517
|
actionName: binding.wrapperName,
|
|
5518
|
+
designerBindingFound: readback && readback.designerBindingFound,
|
|
5519
|
+
verified: readback && readback.verified,
|
|
5366
5520
|
resolved: getFieldEvidenceFromLookup(applied.fieldLookup, binding.fieldId),
|
|
5367
5521
|
};
|
|
5368
5522
|
}),
|
|
@@ -5376,6 +5530,97 @@ async function mainRule(parsedArgs, authRef) {
|
|
|
5376
5530
|
|
|
5377
5531
|
// ── patch 模式主流程 ──────────────────────────────────
|
|
5378
5532
|
|
|
5533
|
+
function actionBindingExpectationsFromOperations(appliedOperations) {
|
|
5534
|
+
return (appliedOperations || []).filter(function (operation) {
|
|
5535
|
+
return operation.action === 'field-action' || operation.action === 'bind-field-action';
|
|
5536
|
+
}).map(function (operation) {
|
|
5537
|
+
return {
|
|
5538
|
+
fieldId: operation.fieldId,
|
|
5539
|
+
event: operation.event,
|
|
5540
|
+
actionName: operation.actionName,
|
|
5541
|
+
relatedEventId: operation.relatedEventId,
|
|
5542
|
+
};
|
|
5543
|
+
});
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5546
|
+
function assertFormActionBindings(schema, expectations, options) {
|
|
5547
|
+
const inspection = inspectActionBindings(schema, expectations);
|
|
5548
|
+
if (inspection.verified) {
|
|
5549
|
+
return inspection;
|
|
5550
|
+
}
|
|
5551
|
+
const readback = options && options.readback === true;
|
|
5552
|
+
throw createCreateFormError(
|
|
5553
|
+
t(readback ? 'create_form.action_readback_mismatch' : 'create_form.action_binding_incomplete'),
|
|
5554
|
+
readback ? 'FORM_ACTION_BINDING_READBACK_MISMATCH' : 'FORM_ACTION_BINDING_INCOMPLETE',
|
|
5555
|
+
{
|
|
5556
|
+
status: 'SEMANTIC_FAILURE',
|
|
5557
|
+
retryable: false,
|
|
5558
|
+
retrySafe: false,
|
|
5559
|
+
sideEffectState: readback ? 'committed' : 'none',
|
|
5560
|
+
mutationAccepted: readback,
|
|
5561
|
+
readbackVerified: false,
|
|
5562
|
+
readbackAllowed: true,
|
|
5563
|
+
nextStep: readback ? 'inspect_existing_form_action_binding' : 'fix_action_binding_patch',
|
|
5564
|
+
bindings: inspection.bindings,
|
|
5565
|
+
}
|
|
5566
|
+
);
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5569
|
+
async function readBackFormActionBindings(authRef, appType, formUuid, expectations) {
|
|
5570
|
+
const schemaResult = await requestWithAutoLogin(function (auth) {
|
|
5571
|
+
return sendGetRequest(
|
|
5572
|
+
auth.baseUrl,
|
|
5573
|
+
buildApiPath(appType, 'getFormSchema', { prefix: '_view', namespace: 'alibaba' }),
|
|
5574
|
+
{ formUuid: formUuid, schemaVersion: 'V5' }
|
|
5575
|
+
);
|
|
5576
|
+
}, authRef);
|
|
5577
|
+
|
|
5578
|
+
if (!schemaResult || schemaResult.success === false || schemaResult.__needLogin) {
|
|
5579
|
+
const errorMsg = schemaResult ? schemaResult.errorMsg || t('common.unknown_error') : t('common.request_failed');
|
|
5580
|
+
throw createCreateFormError(
|
|
5581
|
+
t('create_form.action_readback_failed', errorMsg),
|
|
5582
|
+
'FORM_ACTION_BINDING_READBACK_FAILED',
|
|
5583
|
+
{
|
|
5584
|
+
status: 'READBACK_UNVERIFIED',
|
|
5585
|
+
retryable: true,
|
|
5586
|
+
retrySafe: false,
|
|
5587
|
+
sideEffectState: 'committed',
|
|
5588
|
+
mutationAccepted: true,
|
|
5589
|
+
readbackVerified: false,
|
|
5590
|
+
readbackAllowed: true,
|
|
5591
|
+
nextStep: 'retry_readback_only',
|
|
5592
|
+
target: { appType, formUuid },
|
|
5593
|
+
result: sanitizeFailureResult(schemaResult),
|
|
5594
|
+
}
|
|
5595
|
+
);
|
|
5596
|
+
}
|
|
5597
|
+
|
|
5598
|
+
let schema;
|
|
5599
|
+
try {
|
|
5600
|
+
schema = schemaResult.content
|
|
5601
|
+
? (typeof schemaResult.content === 'string' ? JSON.parse(schemaResult.content) : schemaResult.content)
|
|
5602
|
+
: schemaResult;
|
|
5603
|
+
} catch (parseError) {
|
|
5604
|
+
throw createCreateFormError(
|
|
5605
|
+
t('create_form.action_readback_failed', parseError.message),
|
|
5606
|
+
'FORM_ACTION_BINDING_READBACK_FAILED',
|
|
5607
|
+
{
|
|
5608
|
+
status: 'READBACK_UNVERIFIED',
|
|
5609
|
+
retryable: true,
|
|
5610
|
+
retrySafe: false,
|
|
5611
|
+
sideEffectState: 'committed',
|
|
5612
|
+
mutationAccepted: true,
|
|
5613
|
+
readbackVerified: false,
|
|
5614
|
+
readbackAllowed: true,
|
|
5615
|
+
nextStep: 'retry_readback_only',
|
|
5616
|
+
target: { appType, formUuid },
|
|
5617
|
+
}
|
|
5618
|
+
);
|
|
5619
|
+
}
|
|
5620
|
+
|
|
5621
|
+
return assertFormActionBindings(schema, expectations, { readback: true });
|
|
5622
|
+
}
|
|
5623
|
+
|
|
5379
5624
|
async function mainPatch(parsedArgs, authRef) {
|
|
5380
5625
|
const { appType, formUuid, patchJsonOrFile } = parsedArgs;
|
|
5381
5626
|
|
|
@@ -5429,6 +5674,9 @@ async function mainPatch(parsedArgs, authRef) {
|
|
|
5429
5674
|
try {
|
|
5430
5675
|
appliedOperations = applySchemaPatchOperations(schema, operations);
|
|
5431
5676
|
} catch (patchError) {
|
|
5677
|
+
if (patchError && patchError.isCliError) {
|
|
5678
|
+
throw patchError;
|
|
5679
|
+
}
|
|
5432
5680
|
fail('Schema 补丁应用失败: ' + patchError.message);
|
|
5433
5681
|
console.log(JSON.stringify({
|
|
5434
5682
|
success: false,
|
|
@@ -5441,6 +5689,10 @@ async function mainPatch(parsedArgs, authRef) {
|
|
|
5441
5689
|
formUuid,
|
|
5442
5690
|
});
|
|
5443
5691
|
}
|
|
5692
|
+
const actionBindingExpectations = actionBindingExpectationsFromOperations(appliedOperations);
|
|
5693
|
+
const localActionInspection = actionBindingExpectations.length > 0
|
|
5694
|
+
? assertFormActionBindings(schema, actionBindingExpectations)
|
|
5695
|
+
: null;
|
|
5444
5696
|
success('已应用 ' + appliedOperations.length + ' 个补丁操作');
|
|
5445
5697
|
appliedOperations.forEach(function (operation, index) {
|
|
5446
5698
|
listItem((index + 1) + '. ' + operation.action + (operation.field ? ' ' + operation.field : '') + (operation.path ? ' ' + operation.path : ''));
|
|
@@ -5455,6 +5707,9 @@ async function mainPatch(parsedArgs, authRef) {
|
|
|
5455
5707
|
}
|
|
5456
5708
|
|
|
5457
5709
|
await saveFormSchema(authRef, appType, formUuid, schema, version, 4);
|
|
5710
|
+
const actionReadback = actionBindingExpectations.length > 0
|
|
5711
|
+
? await readBackFormActionBindings(authRef, appType, formUuid, actionBindingExpectations)
|
|
5712
|
+
: null;
|
|
5458
5713
|
|
|
5459
5714
|
const formUrl = authRef.baseUrl + '/' + appType + '/workbench/' + formUuid;
|
|
5460
5715
|
result(true, 'Schema 补丁保存成功', [
|
|
@@ -5464,7 +5719,16 @@ async function mainPatch(parsedArgs, authRef) {
|
|
|
5464
5719
|
]);
|
|
5465
5720
|
|
|
5466
5721
|
console.log(JSON.stringify(withBrowserHandoff(
|
|
5467
|
-
{
|
|
5722
|
+
{
|
|
5723
|
+
success: true,
|
|
5724
|
+
formUuid,
|
|
5725
|
+
appType,
|
|
5726
|
+
operationsApplied: appliedOperations.length,
|
|
5727
|
+
operations: appliedOperations,
|
|
5728
|
+
readbackVerified: actionReadback ? actionReadback.verified : undefined,
|
|
5729
|
+
eventBindings: actionReadback ? actionReadback.bindings : (localActionInspection ? localActionInspection.bindings : undefined),
|
|
5730
|
+
url: formUrl,
|
|
5731
|
+
},
|
|
5468
5732
|
formUrl,
|
|
5469
5733
|
{ stage: 'patch_form_success', title: formUuid },
|
|
5470
5734
|
parsedArgs.browserOpenMode
|
|
@@ -5811,5 +6075,9 @@ module.exports = {
|
|
|
5811
6075
|
ensureYidaGlobalThemeAction,
|
|
5812
6076
|
applyDefaultFormDetailStyle,
|
|
5813
6077
|
applyChangesToSchema,
|
|
6078
|
+
applyFormRules,
|
|
6079
|
+
applySchemaPatchOperations,
|
|
6080
|
+
actionBindingExpectationsFromOperations,
|
|
6081
|
+
assertFormActionBindings,
|
|
5814
6082
|
},
|
|
5815
6083
|
};
|
|
@@ -23,10 +23,30 @@ function fingerprint(value) {
|
|
|
23
23
|
return crypto.createHash('sha256').update(normalized).digest('hex');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function rawFingerprint(value) {
|
|
27
|
+
if (typeof value !== 'string' || !value) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
return crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function positiveSize(value) {
|
|
34
|
+
const size = Number(value);
|
|
35
|
+
return Number.isFinite(size) && size > 0 ? size : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function sha256(value) {
|
|
39
|
+
const digest = String(value || '').toLowerCase();
|
|
40
|
+
return /^[a-f0-9]{64}$/.test(digest) ? digest : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
function createEmptyDisplayInfo() {
|
|
27
44
|
return {
|
|
28
45
|
hasYidaCodeCanvas: false,
|
|
46
|
+
hasCodeBundle: false,
|
|
29
47
|
hasNativeJsx: false,
|
|
48
|
+
codeBundleCount: 0,
|
|
49
|
+
bundleIds: [],
|
|
30
50
|
runtimeCodeBytes: 0,
|
|
31
51
|
sourceCodeBytes: 0,
|
|
32
52
|
compiledCodeBytes: 0,
|
|
@@ -34,6 +54,8 @@ function createEmptyDisplayInfo() {
|
|
|
34
54
|
componentCount: 0,
|
|
35
55
|
canvasRuntimeCode: '',
|
|
36
56
|
canvasSourceCode: '',
|
|
57
|
+
canvasRuntimeSha256: '',
|
|
58
|
+
canvasSourceSha256: '',
|
|
37
59
|
nativeCompiledCode: '',
|
|
38
60
|
nativeSourceCode: '',
|
|
39
61
|
};
|
|
@@ -79,12 +101,37 @@ function traverseDisplayNodes(node, info) {
|
|
|
79
101
|
|
|
80
102
|
if (node.componentName === 'YidaCodeCanvas') {
|
|
81
103
|
const props = node.props || {};
|
|
104
|
+
const codeBundle = props.codeBundle && typeof props.codeBundle === 'object'
|
|
105
|
+
? props.codeBundle
|
|
106
|
+
: null;
|
|
82
107
|
info.hasYidaCodeCanvas = true;
|
|
83
108
|
info.componentCount++;
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
if (typeof props.runtimeCode === 'string' && props.runtimeCode) {
|
|
110
|
+
info.canvasRuntimeCode = props.runtimeCode;
|
|
111
|
+
}
|
|
112
|
+
if (typeof props.code === 'string' && props.code) {
|
|
113
|
+
info.canvasSourceCode = props.code;
|
|
114
|
+
}
|
|
86
115
|
info.runtimeCodeBytes += codeBytes(props.runtimeCode);
|
|
87
116
|
info.sourceCodeBytes += codeBytes(props.code);
|
|
117
|
+
if (codeBundle) {
|
|
118
|
+
const bundleId = String(codeBundle.bundleId || '');
|
|
119
|
+
const runtime = codeBundle.runtime && typeof codeBundle.runtime === 'object'
|
|
120
|
+
? codeBundle.runtime
|
|
121
|
+
: {};
|
|
122
|
+
const source = codeBundle.source && typeof codeBundle.source === 'object'
|
|
123
|
+
? codeBundle.source
|
|
124
|
+
: {};
|
|
125
|
+
info.hasCodeBundle = true;
|
|
126
|
+
info.codeBundleCount++;
|
|
127
|
+
if (bundleId && !info.bundleIds.includes(bundleId)) {
|
|
128
|
+
info.bundleIds.push(bundleId);
|
|
129
|
+
}
|
|
130
|
+
info.runtimeCodeBytes += positiveSize(runtime.size);
|
|
131
|
+
info.sourceCodeBytes += positiveSize(source.size);
|
|
132
|
+
info.canvasRuntimeSha256 = sha256(runtime.sha256) || info.canvasRuntimeSha256;
|
|
133
|
+
info.canvasSourceSha256 = sha256(source.sha256) || info.canvasSourceSha256;
|
|
134
|
+
}
|
|
88
135
|
addImportedModules(info.importedModules, props.importedModules);
|
|
89
136
|
} else if (node.componentName === 'Jsx') {
|
|
90
137
|
info.hasNativeJsx = true;
|
|
@@ -133,7 +180,9 @@ function hasExpectedDisplayComponent(info, publishMode) {
|
|
|
133
180
|
return false;
|
|
134
181
|
}
|
|
135
182
|
if (publishMode === 'canvas') {
|
|
136
|
-
return info.hasYidaCodeCanvas &&
|
|
183
|
+
return info.hasYidaCodeCanvas && (
|
|
184
|
+
info.runtimeCodeBytes > 0 || !!info.canvasRuntimeSha256
|
|
185
|
+
);
|
|
137
186
|
}
|
|
138
187
|
return info.hasNativeJsx && info.compiledCodeBytes > 0;
|
|
139
188
|
}
|
|
@@ -144,7 +193,10 @@ function summarizeDisplayPublishInfo(info) {
|
|
|
144
193
|
}
|
|
145
194
|
return {
|
|
146
195
|
hasYidaCodeCanvas: info.hasYidaCodeCanvas,
|
|
196
|
+
hasCodeBundle: info.hasCodeBundle,
|
|
147
197
|
hasNativeJsx: info.hasNativeJsx,
|
|
198
|
+
codeBundleCount: info.codeBundleCount,
|
|
199
|
+
bundleIds: info.bundleIds.slice(),
|
|
148
200
|
runtimeCodeBytes: info.runtimeCodeBytes,
|
|
149
201
|
sourceCodeBytes: info.sourceCodeBytes,
|
|
150
202
|
compiledCodeBytes: info.compiledCodeBytes,
|
|
@@ -159,8 +211,13 @@ function verifyPublishedContentMatch(readbackSchema, expectedSchemaContent, publ
|
|
|
159
211
|
const displayComponentPresent = hasExpectedDisplayComponent(readbackInfo, publishMode);
|
|
160
212
|
const readbackArtifact = getPublishArtifact(readbackInfo, publishMode);
|
|
161
213
|
const expectedArtifact = getPublishArtifact(expectedInfo, publishMode);
|
|
162
|
-
const
|
|
163
|
-
const
|
|
214
|
+
const codeBundleReadback = publishMode === 'canvas' && readbackInfo && readbackInfo.hasCodeBundle;
|
|
215
|
+
const readbackFingerprint = codeBundleReadback
|
|
216
|
+
? readbackInfo.canvasRuntimeSha256
|
|
217
|
+
: fingerprint(readbackArtifact);
|
|
218
|
+
const expectedFingerprint = codeBundleReadback
|
|
219
|
+
? rawFingerprint(expectedArtifact)
|
|
220
|
+
: fingerprint(expectedArtifact);
|
|
164
221
|
|
|
165
222
|
return {
|
|
166
223
|
readbackInfo,
|
|
@@ -180,6 +237,7 @@ function verifyPublishedContentMatch(readbackSchema, expectedSchemaContent, publ
|
|
|
180
237
|
module.exports = {
|
|
181
238
|
extractDisplayPublishInfo,
|
|
182
239
|
fingerprint,
|
|
240
|
+
rawFingerprint,
|
|
183
241
|
hasExpectedDisplayComponent,
|
|
184
242
|
parseSchemaContent,
|
|
185
243
|
summarizeDisplayPublishInfo,
|