openyida 2026.9.8-1 → 2026.9.8
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 -4
- package/lib/app/create-form/args.js +2 -36
- package/lib/app/create-form/batch.js +7 -24
- package/lib/app/create-form/commands.js +0 -2
- package/lib/app/create-form.js +0 -292
- package/lib/core/command-manifest.js +1 -4
- package/lib/core/locales/en.js +1 -9
- package/lib/core/locales/zh.js +1 -9
- package/lib/process/configure-process.js +1 -18
- package/package.json +1 -1
- package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +3 -3
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +2 -2
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +0 -2
- package/yida-skills/skills/yida-create-form-page/SKILL.md +3 -17
- package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +4 -18
package/README.md
CHANGED
|
@@ -174,8 +174,6 @@ openyida update-app APP_XXX --theme-file .cache/openyida/crm/app-theme.css --nav
|
|
|
174
174
|
openyida corp-efficiency
|
|
175
175
|
openyida create-form create APP_XXX "Customer" .cache/openyida/forms/customer-fields.json
|
|
176
176
|
openyida create-form update APP_XXX FORM_XXX .cache/openyida/forms/customer-changes.json
|
|
177
|
-
openyida create-form update APP_XXX FORM_XXX --data-file .cache/openyida/forms/customer-changes.json
|
|
178
|
-
openyida create-form resume APP_XXX FORM_XXX .cache/openyida/forms/customer-fields.json --json
|
|
179
177
|
openyida sample openyida-page-template form-fields --output .cache/openyida/forms/customer-fields.json
|
|
180
178
|
openyida sample openyida-page-template canvas-form-drawer --output project/pages/src/customer-entry.canvas.jsx --var APP_TYPE=APP_XXX --var FORM_UUID=FORM_XXX
|
|
181
179
|
openyida get-schema APP_XXX FORM_XXX
|
|
@@ -415,8 +413,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
415
413
|
| `openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--icon auto\|<iconName>] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
|
|
416
414
|
| `openyida create-form icons [--json]` | List available form navigation icons |
|
|
417
415
|
| `openyida create-form validate-fields <fieldsJsonOrFile> [--json]` | Validate form field JSON locally |
|
|
418
|
-
| `openyida create-form update <appType>
|
|
419
|
-
| `openyida create-form resume <appType> <formUuid> <fieldsJsonOrFile> [--json]` | Update a form page |
|
|
416
|
+
| `openyida create-form update <appType> ... [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Update a form page |
|
|
420
417
|
| `openyida create-form patch <appType> <formUuid> <patchJsonOrFile> [--open\|--no-open]` | Update a form page |
|
|
421
418
|
| `openyida create-form rule <appType> <formUuid> <rulesJsonOrFile> [--open\|--no-open]` | Update a form page |
|
|
422
419
|
| `openyida create-form validation <appType> <formUuid> <validationsJsonOrFile> [--open\|--no-open]` | Update a form page |
|
|
@@ -89,7 +89,6 @@ function createParseArgs(dependencies) {
|
|
|
89
89
|
contentLocale: null,
|
|
90
90
|
browserOpenMode: openOption.mode,
|
|
91
91
|
};
|
|
92
|
-
let dataFileInput;
|
|
93
92
|
|
|
94
93
|
const args = [...rawArgs];
|
|
95
94
|
|
|
@@ -112,14 +111,6 @@ function createParseArgs(dependencies) {
|
|
|
112
111
|
i--;
|
|
113
112
|
} else if (args[i] === '--icon') {
|
|
114
113
|
throwCreateFormError('--icon requires a value', 'CREATE_FORM_INVALID_ARGUMENTS');
|
|
115
|
-
} else if (args[i] === '--data-file') {
|
|
116
|
-
const next = args[i + 1];
|
|
117
|
-
if (dataFileInput !== undefined || !next || next.startsWith('--')) {
|
|
118
|
-
throwCreateFormError('--data-file requires exactly one value', 'CREATE_FORM_INVALID_ARGUMENTS');
|
|
119
|
-
}
|
|
120
|
-
dataFileInput = next;
|
|
121
|
-
args.splice(i, 2);
|
|
122
|
-
i--;
|
|
123
114
|
} else if ((args[i] === '--locale' || args[i] === '--content-locale' || args[i] === '--lang') && i + 1 < args.length) {
|
|
124
115
|
options.contentLocale = args[i + 1];
|
|
125
116
|
if (!normalizeYidaLocale(options.contentLocale)) {
|
|
@@ -154,10 +145,6 @@ function createParseArgs(dependencies) {
|
|
|
154
145
|
|
|
155
146
|
const mode = args[0];
|
|
156
147
|
|
|
157
|
-
if (dataFileInput !== undefined && mode !== 'update') {
|
|
158
|
-
throwCreateFormError('--data-file is only supported by create-form update', 'CREATE_FORM_INVALID_ARGUMENTS');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
148
|
if (mode === 'icons' || mode === 'list-icons') {
|
|
162
149
|
return {
|
|
163
150
|
mode: 'icons',
|
|
@@ -195,12 +182,7 @@ function createParseArgs(dependencies) {
|
|
|
195
182
|
}
|
|
196
183
|
|
|
197
184
|
if (mode === 'update') {
|
|
198
|
-
|
|
199
|
-
if (args.length > 4 || (positionalInput && dataFileInput !== undefined)) {
|
|
200
|
-
throwCreateFormError('Use either <changesJsonOrFile> or --data-file, not both', 'CREATE_FORM_INVALID_ARGUMENTS');
|
|
201
|
-
}
|
|
202
|
-
const changesJsonOrFile = dataFileInput !== undefined ? dataFileInput : positionalInput;
|
|
203
|
-
if (args.length < 3 || !changesJsonOrFile) {
|
|
185
|
+
if (args.length < 4) {
|
|
204
186
|
usage(t('create_form.usage_update'), t('create_form.example_update'));
|
|
205
187
|
throwCreateFormError(t('create_form.usage_update'), 'CREATE_FORM_INVALID_ARGUMENTS');
|
|
206
188
|
}
|
|
@@ -208,23 +190,7 @@ function createParseArgs(dependencies) {
|
|
|
208
190
|
mode: 'update',
|
|
209
191
|
appType: args[1],
|
|
210
192
|
formUuid: args[2],
|
|
211
|
-
changesJsonOrFile,
|
|
212
|
-
...options
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (mode === 'resume') {
|
|
217
|
-
if (args.length !== 4) {
|
|
218
|
-
throwCreateFormError(
|
|
219
|
-
'openyida create-form resume <appType> <formUuid> <fieldsJsonOrFile> --json',
|
|
220
|
-
'CREATE_FORM_INVALID_ARGUMENTS'
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
mode: 'resume',
|
|
225
|
-
appType: args[1],
|
|
226
|
-
formUuid: args[2],
|
|
227
|
-
fieldsJsonOrFile: args[3],
|
|
193
|
+
changesJsonOrFile: args[3],
|
|
228
194
|
...options
|
|
229
195
|
};
|
|
230
196
|
}
|
|
@@ -101,10 +101,7 @@ function execute(args) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
async function schedule(forms, concurrency, results, worker, save) {
|
|
104
|
-
const pending = new Set(forms.filter(form =>
|
|
105
|
-
const result = results[form.key];
|
|
106
|
-
return !result || result.status === 'blocked' || (result.status === 'failed' && result.formUuid);
|
|
107
|
-
}).map(form => form.key));
|
|
104
|
+
const pending = new Set(forms.filter(form => !results[form.key] || results[form.key].status === 'blocked').map(form => form.key));
|
|
108
105
|
const active = new Map();
|
|
109
106
|
try {
|
|
110
107
|
while (pending.size || active.size) {
|
|
@@ -117,7 +114,7 @@ async function schedule(forms, concurrency, results, worker, save) {
|
|
|
117
114
|
}
|
|
118
115
|
if (active.size >= concurrency || deps.some(status => status !== 'success')) { continue; }
|
|
119
116
|
pending.delete(form.key);
|
|
120
|
-
results[form.key] = {
|
|
117
|
+
results[form.key] = { status: 'running' };
|
|
121
118
|
save(); // Record intent before the request; an interrupted create is never retried automatically.
|
|
122
119
|
const task = Promise.resolve().then(() => worker(form)).then(output => {
|
|
123
120
|
results[form.key] = { ...output, status: 'success' };
|
|
@@ -181,30 +178,16 @@ async function run(args, dependencies = {}) {
|
|
|
181
178
|
return matches[0].fieldId;
|
|
182
179
|
};
|
|
183
180
|
await schedule(forms, options.concurrency, state.results, async form => {
|
|
184
|
-
let formUuid = form.formUuid
|
|
185
|
-
let shouldResume = Boolean(!form.formUuid && formUuid);
|
|
186
|
-
const resolvedFields = mapReferences(form.fields, resolve);
|
|
181
|
+
let formUuid = form.formUuid;
|
|
187
182
|
if (!formUuid) {
|
|
188
|
-
const argv = ['create-form', 'create', options.appType, form.title, JSON.stringify(
|
|
183
|
+
const argv = ['create-form', 'create', options.appType, form.title, JSON.stringify(mapReferences(form.fields, resolve)), '--no-open'];
|
|
189
184
|
for (const key of ['icon', 'locale']) { if (form[key]) { argv.push(`--${key}`, form[key]); } }
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
formUuid = created.formUuid;
|
|
194
|
-
} catch (error) {
|
|
195
|
-
const createdFormUuid = error.output?.formUuid || error.output?.details?.formUuid;
|
|
196
|
-
if (typeof createdFormUuid !== 'string' || !createdFormUuid.startsWith('FORM')) {
|
|
197
|
-
throw error;
|
|
198
|
-
}
|
|
199
|
-
formUuid = createdFormUuid;
|
|
200
|
-
shouldResume = true;
|
|
201
|
-
}
|
|
185
|
+
const created = await call(argv);
|
|
186
|
+
if (typeof created.formUuid !== 'string' || !created.formUuid.startsWith('FORM')) { invalid(`create result: ${form.key}`); }
|
|
187
|
+
formUuid = created.formUuid;
|
|
202
188
|
}
|
|
203
189
|
state.results[form.key].formUuid = formUuid;
|
|
204
190
|
save(state);
|
|
205
|
-
if (shouldResume) {
|
|
206
|
-
await call(['create-form', 'resume', options.appType, formUuid, JSON.stringify(resolvedFields), '--json']);
|
|
207
|
-
}
|
|
208
191
|
const schema = await call(['get-schema', options.appType, formUuid, '--field-map-json']);
|
|
209
192
|
if (schema.formUuid !== formUuid || !Array.isArray(schema.fields)) { invalid(`schema: ${form.key}`); }
|
|
210
193
|
return { formUuid, fields: schema.fields };
|
|
@@ -6,8 +6,6 @@ function dispatchCreateFormCommand(parsedArgs, authContext, handlers) {
|
|
|
6
6
|
switch (parsedArgs.mode) {
|
|
7
7
|
case 'update':
|
|
8
8
|
return handlers.update(...args);
|
|
9
|
-
case 'resume':
|
|
10
|
-
return handlers.resume(...args);
|
|
11
9
|
case 'patch':
|
|
12
10
|
return handlers.patch(...args);
|
|
13
11
|
case 'rule':
|
package/lib/app/create-form.js
CHANGED
|
@@ -5911,297 +5911,6 @@ async function mainValidation(parsedArgs, authRef) {
|
|
|
5911
5911
|
|
|
5912
5912
|
// ── update 模式主流程 ─────────────────────────────────
|
|
5913
5913
|
|
|
5914
|
-
function collectResumeFieldEvidence(fields, output) {
|
|
5915
|
-
const evidence = output || [];
|
|
5916
|
-
(fields || []).forEach(function (field) {
|
|
5917
|
-
if (!field || typeof field !== 'object') {
|
|
5918
|
-
return;
|
|
5919
|
-
}
|
|
5920
|
-
const labelText = extractLabelText(field);
|
|
5921
|
-
if (labelText && !isFormPresentationComponent(field.componentName)) {
|
|
5922
|
-
evidence.push({
|
|
5923
|
-
label: labelText,
|
|
5924
|
-
componentName: field.componentName,
|
|
5925
|
-
fieldId: field.props && field.props.fieldId ? field.props.fieldId : null,
|
|
5926
|
-
});
|
|
5927
|
-
}
|
|
5928
|
-
if (Array.isArray(field.children)) {
|
|
5929
|
-
collectResumeFieldEvidence(field.children.flat(), evidence);
|
|
5930
|
-
}
|
|
5931
|
-
});
|
|
5932
|
-
return evidence;
|
|
5933
|
-
}
|
|
5934
|
-
|
|
5935
|
-
function desiredResumeFieldIdentity(field) {
|
|
5936
|
-
const labelValue = field && field.label;
|
|
5937
|
-
const label = typeof labelValue === 'string'
|
|
5938
|
-
? labelValue
|
|
5939
|
-
: labelValue && (labelValue.zh_CN || labelValue.ja_JP || labelValue.en_US || labelValue.pureEn_US);
|
|
5940
|
-
return {
|
|
5941
|
-
label: String(label || '').trim(),
|
|
5942
|
-
componentName: normalizeFormDefinitionType(field),
|
|
5943
|
-
};
|
|
5944
|
-
}
|
|
5945
|
-
|
|
5946
|
-
function collectResumeDesiredFields(fields, options) {
|
|
5947
|
-
const output = [];
|
|
5948
|
-
const verifyNestedFields = Boolean(options && options.verifyNestedFields);
|
|
5949
|
-
|
|
5950
|
-
function visit(value) {
|
|
5951
|
-
if (Array.isArray(value)) {
|
|
5952
|
-
value.forEach(visit);
|
|
5953
|
-
return;
|
|
5954
|
-
}
|
|
5955
|
-
if (!value || typeof value !== 'object') {
|
|
5956
|
-
return;
|
|
5957
|
-
}
|
|
5958
|
-
const componentName = normalizeFormDefinitionType(value);
|
|
5959
|
-
if (isSupportedBusinessFieldType(componentName)) {
|
|
5960
|
-
output.push(value);
|
|
5961
|
-
if (!verifyNestedFields) {
|
|
5962
|
-
return;
|
|
5963
|
-
}
|
|
5964
|
-
}
|
|
5965
|
-
if (Array.isArray(value.children)) {
|
|
5966
|
-
value.children.forEach(visit);
|
|
5967
|
-
}
|
|
5968
|
-
}
|
|
5969
|
-
|
|
5970
|
-
visit(fields);
|
|
5971
|
-
return output;
|
|
5972
|
-
}
|
|
5973
|
-
|
|
5974
|
-
function isRecoverableBlankResumeSchema(schema) {
|
|
5975
|
-
const allowedShellComponents = new Set(['Page', 'RootHeader', 'RootContent', 'RootFooter', 'FooterYida']);
|
|
5976
|
-
const componentsTree = schema && schema.pages && schema.pages[0] && schema.pages[0].componentsTree;
|
|
5977
|
-
if (!Array.isArray(componentsTree) || componentsTree.length === 0) {
|
|
5978
|
-
return true;
|
|
5979
|
-
}
|
|
5980
|
-
let safe = true;
|
|
5981
|
-
function visit(node) {
|
|
5982
|
-
if (!node || typeof node !== 'object' || !safe) {
|
|
5983
|
-
return;
|
|
5984
|
-
}
|
|
5985
|
-
if (node.componentName && !allowedShellComponents.has(node.componentName)) {
|
|
5986
|
-
safe = false;
|
|
5987
|
-
return;
|
|
5988
|
-
}
|
|
5989
|
-
if (Array.isArray(node.children)) {
|
|
5990
|
-
node.children.forEach(visit);
|
|
5991
|
-
}
|
|
5992
|
-
}
|
|
5993
|
-
componentsTree.forEach(visit);
|
|
5994
|
-
return safe;
|
|
5995
|
-
}
|
|
5996
|
-
|
|
5997
|
-
function buildResumeChanges(existingFields, desiredFields) {
|
|
5998
|
-
const existingByLabel = new Map();
|
|
5999
|
-
existingFields.forEach(function (field) {
|
|
6000
|
-
const bucket = existingByLabel.get(field.label) || [];
|
|
6001
|
-
bucket.push(field);
|
|
6002
|
-
existingByLabel.set(field.label, bucket);
|
|
6003
|
-
});
|
|
6004
|
-
|
|
6005
|
-
const desiredLabels = new Set();
|
|
6006
|
-
const missing = [];
|
|
6007
|
-
const existing = [];
|
|
6008
|
-
const conflicts = [];
|
|
6009
|
-
desiredFields.forEach(function (field) {
|
|
6010
|
-
const identity = desiredResumeFieldIdentity(field);
|
|
6011
|
-
if (!identity.label || desiredLabels.has(identity.label)) {
|
|
6012
|
-
conflicts.push({
|
|
6013
|
-
code: identity.label ? 'CREATE_FORM_RESUME_DUPLICATE_DESIRED_FIELD' : 'CREATE_FORM_RESUME_FIELD_LABEL_REQUIRED',
|
|
6014
|
-
label: identity.label || null,
|
|
6015
|
-
});
|
|
6016
|
-
return;
|
|
6017
|
-
}
|
|
6018
|
-
desiredLabels.add(identity.label);
|
|
6019
|
-
const matches = existingByLabel.get(identity.label) || [];
|
|
6020
|
-
if (matches.length === 0) {
|
|
6021
|
-
missing.push(field);
|
|
6022
|
-
return;
|
|
6023
|
-
}
|
|
6024
|
-
if (matches.length !== 1 || matches[0].componentName !== identity.componentName) {
|
|
6025
|
-
conflicts.push({
|
|
6026
|
-
code: 'CREATE_FORM_RESUME_FIELD_CONFLICT',
|
|
6027
|
-
label: identity.label,
|
|
6028
|
-
requestedType: identity.componentName,
|
|
6029
|
-
observedTypes: matches.map(function (item) { return item.componentName; }),
|
|
6030
|
-
});
|
|
6031
|
-
return;
|
|
6032
|
-
}
|
|
6033
|
-
existing.push({
|
|
6034
|
-
label: identity.label,
|
|
6035
|
-
componentName: identity.componentName,
|
|
6036
|
-
fieldId: matches[0].fieldId,
|
|
6037
|
-
});
|
|
6038
|
-
});
|
|
6039
|
-
return { missing, existing, conflicts };
|
|
6040
|
-
}
|
|
6041
|
-
|
|
6042
|
-
async function readResumeSchema(authRef, appType, formUuid) {
|
|
6043
|
-
const schemaResult = await requestWithAutoLogin(function (auth) {
|
|
6044
|
-
return sendGetRequest(
|
|
6045
|
-
auth.baseUrl,
|
|
6046
|
-
buildApiPath(appType, 'getFormSchema', { prefix: '_view', namespace: 'alibaba' }),
|
|
6047
|
-
{ formUuid: formUuid, schemaVersion: 'V5' }
|
|
6048
|
-
);
|
|
6049
|
-
}, authRef);
|
|
6050
|
-
if (!schemaResult || schemaResult.success === false || schemaResult.__needLogin || schemaResult.__csrfExpired) {
|
|
6051
|
-
throwCreateFormError(t('create_form.resume_readback_failed'), 'CREATE_FORM_RESUME_READBACK_FAILED', {
|
|
6052
|
-
appType,
|
|
6053
|
-
formUuid,
|
|
6054
|
-
result: sanitizeFailureResult(schemaResult),
|
|
6055
|
-
});
|
|
6056
|
-
}
|
|
6057
|
-
if (schemaResult.appType && schemaResult.appType !== appType) {
|
|
6058
|
-
throwCreateFormError(t('create_form.resume_ownership_unverified'), 'CREATE_FORM_RESUME_OWNERSHIP_UNVERIFIED', {
|
|
6059
|
-
appType,
|
|
6060
|
-
formUuid,
|
|
6061
|
-
});
|
|
6062
|
-
}
|
|
6063
|
-
if (schemaResult.formUuid && schemaResult.formUuid !== formUuid) {
|
|
6064
|
-
throwCreateFormError(t('create_form.resume_identity_mismatch'), 'CREATE_FORM_RESUME_OWNERSHIP_UNVERIFIED', {
|
|
6065
|
-
appType,
|
|
6066
|
-
formUuid,
|
|
6067
|
-
});
|
|
6068
|
-
}
|
|
6069
|
-
const schema = schemaResult.content
|
|
6070
|
-
? (typeof schemaResult.content === 'string' ? JSON.parse(schemaResult.content) : schemaResult.content)
|
|
6071
|
-
: schemaResult.pages ? schemaResult : null;
|
|
6072
|
-
if (!schema || !Array.isArray(schema.pages) || schema.pages.length === 0) {
|
|
6073
|
-
throwCreateFormError(t('create_form.resume_schema_invalid'), 'CREATE_FORM_RESUME_SCHEMA_INVALID', {
|
|
6074
|
-
appType,
|
|
6075
|
-
formUuid,
|
|
6076
|
-
});
|
|
6077
|
-
}
|
|
6078
|
-
return { schemaResult, schema, version: extractSchemaServerRevision(schemaResult) };
|
|
6079
|
-
}
|
|
6080
|
-
|
|
6081
|
-
async function mainResume(parsedArgs, authRef) {
|
|
6082
|
-
const { appType, formUuid, fieldsJsonOrFile } = parsedArgs;
|
|
6083
|
-
assertNoEmojiInDefinitionFileName(fieldsJsonOrFile);
|
|
6084
|
-
const { fields, validations } = readFieldsDefinition(fieldsJsonOrFile);
|
|
6085
|
-
assertNoEmojiInFormDefinition(formUuid, fields, [], 'create-form resume input');
|
|
6086
|
-
validateFormFieldDefinitions(fields);
|
|
6087
|
-
|
|
6088
|
-
const firstRead = await readResumeSchema(authRef, appType, formUuid);
|
|
6089
|
-
const root = firstRead.schema.pages[0].componentsTree && firstRead.schema.pages[0].componentsTree[0];
|
|
6090
|
-
const formContainer = root ? findFormContainer(root) : null;
|
|
6091
|
-
const completedStages = ['read_target', 'verify_ownership'];
|
|
6092
|
-
let recoveredBlankShell = false;
|
|
6093
|
-
let plan;
|
|
6094
|
-
|
|
6095
|
-
if (!formContainer) {
|
|
6096
|
-
if (!isRecoverableBlankResumeSchema(firstRead.schema)) {
|
|
6097
|
-
throwCreateFormError(t('create_form.resume_container_invalid'), 'CREATE_FORM_RESUME_SCHEMA_INVALID', {
|
|
6098
|
-
appType,
|
|
6099
|
-
formUuid,
|
|
6100
|
-
retryable: false,
|
|
6101
|
-
remoteWrites: 0,
|
|
6102
|
-
});
|
|
6103
|
-
}
|
|
6104
|
-
const corpId = resolveCorpId(authRef.authData);
|
|
6105
|
-
const formTitle = firstRead.schemaResult.formTitle || firstRead.schemaResult.title || formUuid;
|
|
6106
|
-
firstRead.schema = buildFormSchema(formTitle, fields, formUuid, corpId, appType, 'single', 'default', 'top');
|
|
6107
|
-
const resumeValidationRules = collectSmartValidationRulesFromFields(fields).concat(validations || []);
|
|
6108
|
-
if (resumeValidationRules.length > 0) {
|
|
6109
|
-
applySmartValidations(firstRead.schema, resumeValidationRules);
|
|
6110
|
-
}
|
|
6111
|
-
await saveFormSchema(authRef, appType, formUuid, firstRead.schema, firstRead.version, 4);
|
|
6112
|
-
completedStages.push('rebuild_blank_schema', 'save_schema');
|
|
6113
|
-
recoveredBlankShell = true;
|
|
6114
|
-
plan = {
|
|
6115
|
-
existing: [],
|
|
6116
|
-
missing: collectResumeDesiredFields(fields, { verifyNestedFields: true }),
|
|
6117
|
-
conflicts: [],
|
|
6118
|
-
};
|
|
6119
|
-
} else if (!Array.isArray(formContainer.children)) {
|
|
6120
|
-
throwCreateFormError(t('create_form.resume_container_invalid'), 'CREATE_FORM_RESUME_SCHEMA_INVALID', {
|
|
6121
|
-
appType,
|
|
6122
|
-
formUuid,
|
|
6123
|
-
retryable: false,
|
|
6124
|
-
remoteWrites: 0,
|
|
6125
|
-
});
|
|
6126
|
-
} else {
|
|
6127
|
-
plan = buildResumeChanges(
|
|
6128
|
-
collectResumeFieldEvidence(formContainer.children),
|
|
6129
|
-
collectResumeDesiredFields(fields)
|
|
6130
|
-
);
|
|
6131
|
-
if (plan.conflicts.length > 0) {
|
|
6132
|
-
throwCreateFormError(t('create_form.resume_field_conflict'), 'CREATE_FORM_RESUME_CONFLICT', {
|
|
6133
|
-
appType,
|
|
6134
|
-
formUuid,
|
|
6135
|
-
retryable: false,
|
|
6136
|
-
remoteWrites: 0,
|
|
6137
|
-
diagnostics: plan.conflicts,
|
|
6138
|
-
});
|
|
6139
|
-
}
|
|
6140
|
-
completedStages.push('compare_fields');
|
|
6141
|
-
}
|
|
6142
|
-
|
|
6143
|
-
if (!recoveredBlankShell && plan.missing.length > 0) {
|
|
6144
|
-
const applied = applyChangesToSchema(
|
|
6145
|
-
firstRead.schema,
|
|
6146
|
-
plan.missing.map(function (field) { return { action: 'add', field }; }),
|
|
6147
|
-
{ verbose: true }
|
|
6148
|
-
);
|
|
6149
|
-
if ((applied.diagnostics || []).length > 0) {
|
|
6150
|
-
throwCreateFormError(t('create_form.resume_field_resolution_failed'), 'CREATE_FORM_RESUME_CONFLICT', {
|
|
6151
|
-
appType,
|
|
6152
|
-
formUuid,
|
|
6153
|
-
retryable: false,
|
|
6154
|
-
remoteWrites: 0,
|
|
6155
|
-
diagnostics: applied.diagnostics,
|
|
6156
|
-
});
|
|
6157
|
-
}
|
|
6158
|
-
const corpId = resolveCorpId(authRef.authData);
|
|
6159
|
-
const updatedContainer = findFormContainer(firstRead.schema.pages[0].componentsTree[0]);
|
|
6160
|
-
fillSerialNumberFormulas(updatedContainer.children, corpId, appType, formUuid);
|
|
6161
|
-
await saveFormSchema(authRef, appType, formUuid, firstRead.schema, firstRead.version, 4);
|
|
6162
|
-
completedStages.push('add_missing_fields', 'save_schema');
|
|
6163
|
-
}
|
|
6164
|
-
|
|
6165
|
-
const finalRead = await readResumeSchema(authRef, appType, formUuid);
|
|
6166
|
-
const finalContainer = findFormContainer(finalRead.schema.pages[0].componentsTree[0]);
|
|
6167
|
-
const finalPlan = buildResumeChanges(
|
|
6168
|
-
collectResumeFieldEvidence(finalContainer && finalContainer.children),
|
|
6169
|
-
collectResumeDesiredFields(fields, { verifyNestedFields: true })
|
|
6170
|
-
);
|
|
6171
|
-
if (finalPlan.missing.length > 0 || finalPlan.conflicts.length > 0) {
|
|
6172
|
-
throwCreateFormError(t('create_form.resume_readback_mismatch'), 'CREATE_FORM_RESUME_READBACK_MISMATCH', {
|
|
6173
|
-
appType,
|
|
6174
|
-
formUuid,
|
|
6175
|
-
retryable: false,
|
|
6176
|
-
resultUnknown: true,
|
|
6177
|
-
completedStages,
|
|
6178
|
-
missingFieldCount: finalPlan.missing.length,
|
|
6179
|
-
diagnostics: finalPlan.conflicts,
|
|
6180
|
-
});
|
|
6181
|
-
}
|
|
6182
|
-
completedStages.push('verify_final_schema');
|
|
6183
|
-
const formUrl = authRef.baseUrl + '/' + appType + '/workbench/' + formUuid;
|
|
6184
|
-
const output = {
|
|
6185
|
-
success: true,
|
|
6186
|
-
appType,
|
|
6187
|
-
formUuid,
|
|
6188
|
-
completedStages,
|
|
6189
|
-
requestedFieldCount: fields.length,
|
|
6190
|
-
existingFieldCount: plan.existing.length,
|
|
6191
|
-
addedFieldCount: plan.missing.length,
|
|
6192
|
-
finalFieldCount: collectResumeFieldEvidence(finalContainer.children).length,
|
|
6193
|
-
recoveredBlankShell,
|
|
6194
|
-
url: formUrl,
|
|
6195
|
-
};
|
|
6196
|
-
console.log(JSON.stringify(withBrowserHandoff(
|
|
6197
|
-
output,
|
|
6198
|
-
formUrl,
|
|
6199
|
-
{ stage: 'resume_form_success', title: formUuid },
|
|
6200
|
-
parsedArgs.browserOpenMode
|
|
6201
|
-
)));
|
|
6202
|
-
return output;
|
|
6203
|
-
}
|
|
6204
|
-
|
|
6205
5914
|
async function mainUpdate(parsedArgs, authRef) {
|
|
6206
5915
|
const { appType, formUuid, changesJsonOrFile } = parsedArgs;
|
|
6207
5916
|
|
|
@@ -6393,7 +6102,6 @@ async function run(args) {
|
|
|
6393
6102
|
{
|
|
6394
6103
|
create: mainCreate,
|
|
6395
6104
|
update: mainUpdate,
|
|
6396
|
-
resume: mainResume,
|
|
6397
6105
|
patch: mainPatch,
|
|
6398
6106
|
rule: mainRule,
|
|
6399
6107
|
validation: mainValidation,
|
|
@@ -449,7 +449,6 @@ const COMMAND_SIDE_EFFECTS = new Map([
|
|
|
449
449
|
'create-form.bind-datasource',
|
|
450
450
|
'create-form.create',
|
|
451
451
|
'create-form.patch',
|
|
452
|
-
'create-form.resume',
|
|
453
452
|
'create-form.rule',
|
|
454
453
|
'create-form.update',
|
|
455
454
|
'create-form.validation',
|
|
@@ -690,7 +689,6 @@ const COMMAND_PERMISSIONS = new Map([
|
|
|
690
689
|
'create-form.create',
|
|
691
690
|
'create-form.batch',
|
|
692
691
|
'create-form.patch',
|
|
693
|
-
'create-form.resume',
|
|
694
692
|
'create-form.rule',
|
|
695
693
|
'create-form.update',
|
|
696
694
|
'create-form.validation',
|
|
@@ -1225,8 +1223,7 @@ const COMMAND_GROUPS = [
|
|
|
1225
1223
|
command('create-form.validate-fields', ['create-form', 'validate-fields'], 'create-form validate-fields <fieldsJsonOrFile> [--json]', 'help.cmd_validate_form', {
|
|
1226
1224
|
requiresLogin: false,
|
|
1227
1225
|
}),
|
|
1228
|
-
command('create-form.update', ['create-form', 'update'], 'create-form update <appType>
|
|
1229
|
-
command('create-form.resume', ['create-form', 'resume'], 'create-form resume <appType> <formUuid> <fieldsJsonOrFile> [--json]', 'help.cmd_update_form'),
|
|
1226
|
+
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'),
|
|
1230
1227
|
command('create-form.patch', ['create-form', 'patch'], 'create-form patch <appType> <formUuid> <patchJsonOrFile> [--open|--no-open]', 'help.cmd_update_form'),
|
|
1231
1228
|
command('create-form.rule', ['create-form', 'rule'], 'create-form rule <appType> <formUuid> <rulesJsonOrFile> [--open|--no-open]', 'help.cmd_update_form'),
|
|
1232
1229
|
command('create-form.validation', ['create-form', 'validation'], 'create-form validation <appType> <formUuid> <validationsJsonOrFile> [--open|--no-open]', 'help.cmd_update_form'),
|
package/lib/core/locales/en.js
CHANGED
|
@@ -827,15 +827,7 @@ Examples:
|
|
|
827
827
|
example_update: 'Example: openyida create-form update "APP_XXX" "FORM-YYY" \'[{"action":"add","field":{"type":"TextField","label":"Note"}}]\'',
|
|
828
828
|
usage_label: 'Usage:',
|
|
829
829
|
usage_create_short: ' create: openyida create-form create <appType> <formTitle> <fieldsJsonFile>',
|
|
830
|
-
usage_update_short: ' update: openyida create-form update <appType> <formUuid> <changesJsonOrFile>
|
|
831
|
-
resume_readback_failed: 'Unable to read the target form before resume.',
|
|
832
|
-
resume_ownership_unverified: 'The target form does not belong to the requested app; resume stopped.',
|
|
833
|
-
resume_identity_mismatch: 'The target form identity does not match the readback; resume stopped.',
|
|
834
|
-
resume_schema_invalid: 'The target form schema is unavailable.',
|
|
835
|
-
resume_container_invalid: 'The target form container is unavailable.',
|
|
836
|
-
resume_field_conflict: 'Existing fields conflict with the requested definition; resume stopped.',
|
|
837
|
-
resume_field_resolution_failed: 'Resume field resolution failed.',
|
|
838
|
-
resume_readback_mismatch: 'Resume readback did not confirm all requested fields.',
|
|
830
|
+
usage_update_short: ' update: openyida create-form update <appType> <formUuid> <changesJsonOrFile>',
|
|
839
831
|
example_label: '\nExamples:',
|
|
840
832
|
fields_file_not_found: ' ❌ Fields definition file not found: ',
|
|
841
833
|
fields_format_invalid: 'Invalid fields definition format',
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -798,15 +798,7 @@ openyida - 宜搭命令行工具
|
|
|
798
798
|
example_update: '示例:openyida create-form update "APP_XXX" "FORM-YYY" \'[{"action":"add","field":{"type":"TextField","label":"备注"}}]\'',
|
|
799
799
|
usage_label: '用法:',
|
|
800
800
|
usage_create_short: ' 创建: openyida create-form create <appType> <formTitle> <fieldsJsonFile>',
|
|
801
|
-
usage_update_short: ' 更新: openyida create-form update <appType> <formUuid> <changesJsonOrFile>
|
|
802
|
-
resume_readback_failed: '恢复前无法回读目标表单。',
|
|
803
|
-
resume_ownership_unverified: '目标表单不属于指定应用,已停止恢复。',
|
|
804
|
-
resume_identity_mismatch: '目标表单身份与回读结果不一致,已停止恢复。',
|
|
805
|
-
resume_schema_invalid: '目标表单 Schema 不可用。',
|
|
806
|
-
resume_container_invalid: '目标表单容器不可用。',
|
|
807
|
-
resume_field_conflict: '已有字段与请求定义冲突,恢复已停止。',
|
|
808
|
-
resume_field_resolution_failed: '恢复字段解析失败。',
|
|
809
|
-
resume_readback_mismatch: '恢复后的回读未确认全部请求字段。',
|
|
801
|
+
usage_update_short: ' 更新: openyida create-form update <appType> <formUuid> <changesJsonOrFile>',
|
|
810
802
|
example_label: '\n示例:',
|
|
811
803
|
fields_file_not_found: ' ❌ 字段定义文件不存在: ',
|
|
812
804
|
fields_format_invalid: '字段定义格式不正确',
|
|
@@ -361,12 +361,7 @@ async function run(args, options) {
|
|
|
361
361
|
error.code || 'CONFIGURE_PROCESS_BUILD_FAILED',
|
|
362
362
|
t('configure_process.building_json') + ': ' + error.message,
|
|
363
363
|
'build_definition',
|
|
364
|
-
error
|
|
365
|
-
{
|
|
366
|
-
retryable: false,
|
|
367
|
-
nextAction: 'fix_definition_file',
|
|
368
|
-
diagnostics: [summarizeRemoteResult(error)],
|
|
369
|
-
}
|
|
364
|
+
error
|
|
370
365
|
);
|
|
371
366
|
}
|
|
372
367
|
stageTracker.complete('build_definition');
|
|
@@ -429,18 +424,6 @@ async function run(args, options) {
|
|
|
429
424
|
remoteWrites: 0,
|
|
430
425
|
published: !!versionState.published,
|
|
431
426
|
saved: !!versionState.saved,
|
|
432
|
-
retryable: false,
|
|
433
|
-
requiresExplicitReplace: true,
|
|
434
|
-
nextAction: 'request_confirmation_then_replace',
|
|
435
|
-
suggestedCommand: [
|
|
436
|
-
'openyida configure-process',
|
|
437
|
-
appType,
|
|
438
|
-
formUuid,
|
|
439
|
-
processDefinitionFile,
|
|
440
|
-
processCode,
|
|
441
|
-
'--replace',
|
|
442
|
-
'--json',
|
|
443
|
-
].filter(Boolean).join(' '),
|
|
444
427
|
}
|
|
445
428
|
);
|
|
446
429
|
}
|
package/package.json
CHANGED
|
@@ -34,9 +34,9 @@ HTML 保留“需求总览、数据模型、业务流程、页面规划”四章
|
|
|
34
34
|
|
|
35
35
|
按 [用户交互契约](../../../yida-design/references/ask-human-interaction-contract.md) 执行:
|
|
36
36
|
|
|
37
|
-
1.
|
|
38
|
-
2.
|
|
39
|
-
3.
|
|
37
|
+
1. 在会话中展示“当前这版方案”、3–7 条业务摘要和可打开的 `build-plan.html`。
|
|
38
|
+
2. 展示成功后内部记录 `presentedRevision=meta.revision`,记录后直接提问;收到确认或修改业务事实后再重新生成。用户可见版本称为“第 N 版方案”,展示序号与内部 revision 绑定。
|
|
39
|
+
3. 询问“确认并开始搭建”或“继续调整”,将确认结果绑定到本次展示版本。
|
|
40
40
|
|
|
41
41
|
只有以下条件同时成立才交接:
|
|
42
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Step 4:创建或更新表单/流程
|
|
2
2
|
|
|
3
|
-
按 PRD
|
|
3
|
+
按 PRD 的依赖创建或复用表单和流程。独立普通表单通过 [批量命令](../../yida-create-form-page/references/batch-forms.md) 同时创建;关联表单等待前置表单完成。某页所需表单、流程就绪后即可接入该页;不要因其他页面的资源未完成而阻塞无依赖页面开发。
|
|
4
4
|
|
|
5
5
|
拿到真实 `appType` 和已确认的业务契约即可开始本步骤,不等待主题 CSS 上传或应用主题设置回读。主题分支与本步骤并行,按 [主题与业务资源的依赖](parallel-work.md#主题与业务资源的依赖) 汇合。
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
1. 执行 `use_skill("yida-create-form-page", "创建或更新核心表单字段结构")`,创建或更新普通表单字段结构。
|
|
17
17
|
2. 已有目标表单时,使用 update/patch/rule/bind-datasource。
|
|
18
|
-
3. 缺少支撑 MVP
|
|
18
|
+
3. 缺少支撑 MVP 的核心普通表单且允许创建时,创建普通表单。
|
|
19
19
|
4. 字段配置文件写入 `.cache/openyida/<项目名>/`。
|
|
20
20
|
5. 拿到真实 `formUuid` 后写入资源上下文。
|
|
21
21
|
6. 批量创建返回的字段映射直接复用。其他页面、数据、流程或公式确需多字段映射时,对每个目标表单最多一次性执行 `openyida get-schema <appType> <formUuid> --field-map-json`,合并写回 `.cache/<项目名>-schema.json`。
|
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
- 页面/资源数量完整性风险检查结果。
|
|
15
15
|
- `requirement-brief.json.constraints.prohibitedActions` 与对应跳过证据。
|
|
16
16
|
|
|
17
|
-
主入口与页面入口必须取自本轮 CLI 成功结果返回的 `appUrl`、`workbenchUrl` 或 `url`,并在后续只读回查中保持一致。不得由模型根据 `appType` 自行拼接或猜测链接;下文 URL 规则仅用于校验服务端/CLI 返回值,不是缺失链接时的生成规则。若成功结果没有权威 URL,必须明确交付失败并执行只读回查,不能交付空卡片或用模板补齐。
|
|
18
|
-
|
|
19
17
|
## 完成条件核对
|
|
20
18
|
|
|
21
19
|
完整应用默认完成需要同时满足:
|
|
@@ -58,7 +58,7 @@ description: 表单页面创建与更新;支持 19 种业务字段和 Divider
|
|
|
58
58
|
|
|
59
59
|
## 多表单创建
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
多个普通表单按 [并行创建表单](references/batch-forms.md) 准备任务,执行 `openyida create-form batch <appType> <任务文件> --json`。CLI 同时创建独立表单,等待关联表单与真实字段就绪后创建依赖表单。
|
|
62
62
|
|
|
63
63
|
## 官方表单示例范式
|
|
64
64
|
|
|
@@ -118,7 +118,7 @@ openyida create-form create <appType> <formTitle> <fieldsJsonOrFile> [--layout d
|
|
|
118
118
|
# 文件路径示例:.cache/openyida/<项目名或任务名>/<表单名>-fields.json
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
创建成功后 CLI 会根据表单标题和字段语义选择导航图标,再更新导航节点并回读校验。需要指定时传 `--icon <iconName>`;用 `openyida create-form icons --json` 查看与 yida-next 页面导航选择器一致的 86 个可用值。表单导航图标是纯图标名(如 `name-card`、`Project`、`Todo`、`clock`),不是应用图标的 `xian-*%%color` 协议。
|
|
122
122
|
|
|
123
123
|
导航图标更新必须先读取 `getFormNavigationListByOrder.json` 的当前节点,像 yida-next 的 `DB.Nav.update({ ...node, title: JSON.stringify(node.title), formUuid: node.formUuid || 'NAV-SYSTEM-FROM-ME-UUID', icon })` 一样保留 `gmtModified`、`formType`、`isNewForm`、`listOrder` 等原值,再请求带 `_api=Nav.update&_mock=false&_stamp=...` 的 `updateFormNavigation.json`,最后重新读取导航列表校验图标。禁止仅凭 formUuid 拼一个精简更新 payload。
|
|
124
124
|
|
|
@@ -142,7 +142,7 @@ openyida create-form create <appType> <formTitle> <fieldsJsonOrFile> [--layout d
|
|
|
142
142
|
create 命令失败后,不要立刻重复同一条 create:
|
|
143
143
|
|
|
144
144
|
1. 先确认字段 JSON 文件存在,且内容是结构化写入后的最终字段数组/对象,不是半截 JSON、update changes 或 shell 拼接残留。
|
|
145
|
-
2. 运行 `openyida list-forms <appType> --keyword "<表单名>"`
|
|
145
|
+
2. 运行 `openyida list-forms <appType> --keyword "<表单名>"` 查同名表单;若本轮刚创建过空白表单或已有同名目标表单,优先走 `create-form update` / `patch` / 后续显式 resume 能力复用,不再 create。
|
|
146
146
|
3. 只有确认远端没有同名目标表单,并且已经修改输入文件、参数、登录态或组织后,才重试 create。
|
|
147
147
|
4. 同一 create 命令最多重试 2 次;仍失败时停止并带上完整 stdout/stderr、字段文件路径、appType、表单名和已发现的 formUuid 给用户。
|
|
148
148
|
|
|
@@ -152,23 +152,9 @@ create 命令失败后,不要立刻重复同一条 create:
|
|
|
152
152
|
|
|
153
153
|
```bash
|
|
154
154
|
openyida create-form update <appType> <formUuid> <changesJsonOrFile>
|
|
155
|
-
openyida create-form update <appType> <formUuid> --data-file <changesJsonOrFile>
|
|
156
155
|
# 文件路径示例:.cache/openyida/<项目名或任务名>/<表单名>-changes.json
|
|
157
156
|
```
|
|
158
157
|
|
|
159
|
-
位置参数和 `--data-file` 是同一输入的两种写法,不能同时使用。
|
|
160
|
-
|
|
161
|
-
## 半成功 create 恢复
|
|
162
|
-
|
|
163
|
-
create 已返回真实 `formUuid`、但后续 schema 保存或回读失败时,使用保守恢复命令:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
openyida create-form resume <appType> <formUuid> <fieldsJsonOrFile> --json
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
该命令先回读目标表单并核对字段,只添加可唯一判定的缺失字段,保存后再次回读;同名异类型、重复
|
|
170
|
-
目标字段、归属不匹配或回读不确定时均停止且不写入。它不会新建替代表单,也不会覆盖已有字段。
|
|
171
|
-
|
|
172
158
|
输出:
|
|
173
159
|
|
|
174
160
|
```json
|
|
@@ -115,13 +115,6 @@ Fast / Plan 是面向用户的模式名称,可以展示。已有详细计划
|
|
|
115
115
|
- `allowCustom`:是否允许用户补充自定义答案。
|
|
116
116
|
- `writeBackPath`:答案写回位置。
|
|
117
117
|
|
|
118
|
-
需要让用户查看 workspace 产物后再回答时,同一次 `ask_human` 还必须携带:
|
|
119
|
-
|
|
120
|
-
- `attachments`:待查看文件列表;workspace 文件使用 `{ "name": "搭建方案", "path": "prd/<项目名>/build-plan.html" }`。路径必须位于当前项目工作区,不能传运行时内部路径或外部绝对路径。
|
|
121
|
-
- `revision`:本次问题绑定的事实版本。宿主提交回答时必须原样回传;版本已变化时拒绝旧回答并重新展示最新版本。
|
|
122
|
-
|
|
123
|
-
附件、问题与 `revision` 是一个原子交互。不得先发普通文本附件、再单独调用 `ask_human`,也不得只在问题文案中写文件路径;否则实时界面和历史回放无法可靠证明用户确认的是哪一版方案。
|
|
124
|
-
|
|
125
118
|
## 首次搭建确认
|
|
126
119
|
|
|
127
120
|
先完成需求分析,再按 [首次搭建确认表](../../yida-requirement-analysis/workflow/prepare-brief.md#2-确认首次搭建的未决事项) 询问尚未明确的事项。该表统一维护详细计划复用、搭建方式、业务模块、导航归属与布局、风格和页面范围;已有业务应用的局部增改按本次疑问澄清。
|
|
@@ -135,9 +128,9 @@ Fast / Plan 是面向用户的模式名称,可以展示。已有详细计划
|
|
|
135
128
|
Plan Design 完成当前版本后,按以下顺序与用户交互:
|
|
136
129
|
|
|
137
130
|
1. 在会话中使用“当前这版方案”或“第 N 版方案”,给出 3-7 条业务摘要;原始 `meta.revision` 仅用于内部状态绑定。
|
|
138
|
-
2.
|
|
139
|
-
3.
|
|
140
|
-
4.
|
|
131
|
+
2. 以宿主支持的文件链接、附件或可打开产物形式展示 `prd/<项目名>/build-plan.html`。
|
|
132
|
+
3. 展示成功后令 `meta.planState.presentedRevision` 等于 `meta.revision`;awaiting_confirmation 在生成前写入,展示记账完成后继续询问。
|
|
133
|
+
4. 执行“最新搭建计划确认”类型的 `ask_human`,提供“确认并开始搭建”和“继续调整”两个选择。
|
|
141
134
|
|
|
142
135
|
`build-plan.html` 不承载对话控件或确认按钮;用户在会话中完成确认。
|
|
143
136
|
|
|
@@ -162,14 +155,7 @@ Plan Design 完成当前版本后,按以下顺序与用户交互:
|
|
|
162
155
|
}
|
|
163
156
|
],
|
|
164
157
|
"allowCustom": false,
|
|
165
|
-
"writeBackPath": "meta.planState"
|
|
166
|
-
"attachments": [
|
|
167
|
-
{
|
|
168
|
-
"name": "当前搭建方案",
|
|
169
|
-
"path": "prd/<项目名>/build-plan.html"
|
|
170
|
-
}
|
|
171
|
-
],
|
|
172
|
-
"revision": "{revision}"
|
|
158
|
+
"writeBackPath": "meta.planState"
|
|
173
159
|
}
|
|
174
160
|
```
|
|
175
161
|
|