openyida 2026.10.12-beta.2 → 2026.10.13
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/LICENSE +20 -20
- package/README.md +16 -27
- package/bin/yida.js +7 -32
- package/lib/app/app-theme.js +289 -0
- package/lib/app/check-prd-completeness.js +4 -15
- package/lib/app/create-form/batch.js +9 -30
- package/lib/app/create-form.js +50 -3
- package/lib/app/custom-theme.js +3 -13
- package/lib/app/get-schema.js +1 -6
- package/lib/app/nav-group-create.js +69 -0
- package/lib/app/nav-group.js +8 -28
- package/lib/app/theme-from-design.js +28 -16
- package/lib/app/theme-source.js +54 -0
- package/lib/app/update-app.js +1 -13
- package/lib/app/update-form-config.js +80 -71
- package/lib/auth/token-auth.js +6 -57
- package/lib/auth/token-store.js +2 -26
- package/lib/core/command-manifest.js +168 -175
- package/lib/core/locales/en.js +23 -47
- package/lib/core/locales/zh.js +23 -47
- package/lib/core/sample.js +21 -2
- package/lib/core/theme-widget.js +115 -0
- package/lib/core/update.js +1 -3
- package/lib/core/utils.js +19 -29
- package/lib/design/document.js +8 -3
- package/lib/design/palette-contrast.js +16 -3
- package/lib/design-plan/confirmation.js +1 -1
- package/lib/design-plan/init.js +21 -6
- package/lib/design-plan/materialize.js +30 -9
- package/lib/design-plan/patch.js +4 -2
- package/lib/design-plan/themes.js +6 -1
- package/lib/design-plan/visual-policy.js +70 -8
- package/lib/page-config/save-share-config.js +5 -16
- package/package.json +1 -10
- package/scripts/postinstall.js +3 -1
- package/yida-skills/SKILL.md +3 -27
- package/yida-skills/skills/yida-app/workflow/parallel-work.md +1 -1
- package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +3 -3
- package/yida-skills/skills/yida-app/workflow/plan/workflow.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +3 -1
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +6 -0
- package/yida-skills/skills/yida-create-form-page/SKILL.md +5 -1
- package/yida-skills/skills/yida-custom-page/SKILL.md +6 -0
- package/yida-skills/skills/yida-dashboard/SKILL.md +2 -2
- package/yida-skills/skills/yida-dashboard/references/theme-presets.md +20 -245
- package/yida-skills/skills/yida-design/SKILL.md +22 -10
- package/yida-skills/skills/yida-design/references/application-style-library.md +19 -6
- package/yida-skills/skills/yida-design/references/application-theme-consistency.md +13 -7
- package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +19 -9
- package/yida-skills/skills/yida-design/references/navigation-decision.md +14 -1
- package/yida-skills/skills/yida-design/references/page-quality-gates.md +16 -0
- package/yida-skills/skills/yida-design/references/responsive-layout.md +54 -0
- package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +5 -24
- package/yida-skills/skills/yida-design/references/theme-preview-images.json +19 -0
- package/yida-skills/skills/yida-design/references/theme-selection.md +80 -9
- package/yida-skills/skills/yida-design/scripts/validate_design_themes.py +324 -303
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +2 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/SKILL.md +86 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/application-coverage.md +44 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/backup-and-restore.md +65 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/component-replacement.md +18 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/confirmation.md +26 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +5 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +1186 -1186
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-normal.svg +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-process.svg +6 -6
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-fit.svg +3 -3
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-relations.svg +7 -7
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-in.svg +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-out.svg +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +29 -22
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +1166 -1166
- package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-executive/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-executive/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-finance/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-finance/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-glass/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-glass/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-line/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-line/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-neon/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-neon/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-paper/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-paper/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/app_theme.css +71 -67
- package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/design.md +108 -69
- package/yida-skills/skills/yida-design/templates/design-themes/app-plum/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-plum/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-pop/app_theme.css +229 -40
- package/yida-skills/skills/yida-design/templates/design-themes/app-pop/design.md +54 -44
- package/yida-skills/skills/yida-design/templates/design-themes/app-sage/app_theme.css +107 -0
- package/yida-skills/skills/yida-design/templates/design-themes/app-sage/design.md +43 -1
- package/yida-skills/skills/yida-design/templates/design-themes/app-teal/app_theme.css +16 -16
- package/yida-skills/skills/yida-design/templates/design-themes/app-teal/design.md +98 -17
- package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/app_theme.css +23 -23
- package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/design.md +98 -25
- package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-wire/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-wire/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/app_theme.css +15 -4
- package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/design.md +22 -10
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +21 -10
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md +24 -20
- package/yida-skills/skills/yida-design/templates/design-themes/free-creative/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/free-creative/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/app_theme.css +160 -18
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/design.md +57 -36
- package/yida-skills/skills/yida-design/templates/design-themes/index.json +73 -16
- package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/app_theme.css +163 -29
- package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/design.md +85 -74
- package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/app_theme.css +170 -27
- package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/design.md +50 -29
- package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/app_theme.css +155 -36
- package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/design.md +46 -26
- package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/app_theme.css +161 -29
- package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/design.md +70 -36
- package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/app_theme.css +119 -8
- package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/design.md +31 -23
- package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/app_theme.css +166 -27
- package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/design.md +50 -30
- package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/app_theme.css +152 -15
- package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/design.md +38 -20
- package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/app_theme.css +1 -1
- package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/design.md +3 -1
- package/yida-skills/skills/yida-design/templates/theme-selector.html +83 -0
- package/yida-skills/skills/yida-design/workflow/output-design.md +7 -4
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +2 -0
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +2 -0
- package/yida-skills/skills/yida-document-markdown/SKILL.md +0 -6
- package/yida-skills/skills/yida-nav-group/SKILL.md +6 -2
- package/yida-skills/skills/yida-nav-shell/SKILL.md +2 -0
- package/yida-skills/skills/yida-prd/SKILL.md +2 -0
- package/yida-skills/skills/yida-publish-page/SKILL.md +4 -0
- package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +4 -2
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +2 -0
- package/yida-skills/skills/yida-tingji/SKILL.md +0 -4
- package/yida-skills/skills-index.json +11 -3
- package/lib/agent/PROTOCOL.md +0 -182
- package/lib/agent/ask-human.js +0 -151
- package/lib/agent/cmd.js +0 -405
- package/lib/agent/command-receipt.js +0 -84
- package/lib/agent/connection-store.js +0 -148
- package/lib/agent/diagnostic-log.js +0 -133
- package/lib/agent/frozen-bundle.js +0 -286
- package/lib/agent/managed-command-map.js +0 -36
- package/lib/agent/managed-context.js +0 -40
- package/lib/agent/managed-run.js +0 -85
- package/lib/agent/mutation-receipt.js +0 -82
- package/lib/agent/private-directory.js +0 -63
- package/lib/agent/provider-preflight.js +0 -257
- package/lib/agent/providers.js +0 -4
- package/lib/agent/runtime-catalog.json +0 -45
- package/lib/agent/runtime-manifest.js +0 -96
- package/lib/agent/runtime-package.js +0 -155
- package/lib/agent/stdio.js +0 -321
- package/lib/auth/profile-lock.js +0 -89
- package/lib/core/managed-policy.js +0 -81
- package/yida-skills/references/local-agent-diagnostics.md +0 -154
- package/yida-skills/references/local-agent-preflight.md +0 -56
package/lib/app/create-form.js
CHANGED
|
@@ -862,6 +862,16 @@ function buildFieldComponent(field) {
|
|
|
862
862
|
props.mode = 'multiple';
|
|
863
863
|
}
|
|
864
864
|
|
|
865
|
+
const explicitDefault = field.value ?? field.defaultValue ?? field.props?.value
|
|
866
|
+
?? field.props?.complexValue?.value;
|
|
867
|
+
const checkedValues = dataSource.filter(item => item.defaultChecked).map(item => item.value);
|
|
868
|
+
const selected = explicitDefault !== undefined
|
|
869
|
+
? explicitDefault
|
|
870
|
+
: ((componentName === 'CheckboxField' || componentName === 'MultiSelectField')
|
|
871
|
+
? (checkedValues.length ? checkedValues : '') : (checkedValues[0] ?? ''));
|
|
872
|
+
props.value = selected;
|
|
873
|
+
props.complexValue.value = selected;
|
|
874
|
+
|
|
865
875
|
if (field.remoteDataSource || field.searchDataSource || field.dataSourceConfig || field.dataSourceUrl || field.searchConfig) {
|
|
866
876
|
applySelectDataSourceConfig(props, field.remoteDataSource || field.searchDataSource || field.dataSourceConfig || {
|
|
867
877
|
url: field.dataSourceUrl,
|
|
@@ -4809,6 +4819,9 @@ function buildCreateFormPostCreateFailurePayload(context) {
|
|
|
4809
4819
|
errorCode: errorObject && errorObject.code ? errorObject.code : (context.errorCode || 'CREATE_FORM_POST_CREATE_FAILED'),
|
|
4810
4820
|
retryAdvice: t('create_form.create_post_failure_retry_advice', context.appType, context.formTitle),
|
|
4811
4821
|
};
|
|
4822
|
+
for (const key of ['remoteSaveState', 'missingFieldCount', 'diagnostics']) {
|
|
4823
|
+
if (errorObject?.details?.[key] !== undefined) { payload[key] = errorObject.details[key]; }
|
|
4824
|
+
}
|
|
4812
4825
|
if (context.fieldCount !== undefined) {
|
|
4813
4826
|
payload.fieldCount = context.fieldCount;
|
|
4814
4827
|
}
|
|
@@ -5057,8 +5070,12 @@ async function mainCreate(parsedArgs, authRef) {
|
|
|
5057
5070
|
formUuid,
|
|
5058
5071
|
fieldCount,
|
|
5059
5072
|
});
|
|
5073
|
+
postCreateStage = 'verifySavedSchema';
|
|
5074
|
+
await verifyCreatedFormSchema(authRef, appType, formUuid, fields);
|
|
5060
5075
|
postCreateStage = 'updateFormNavigationIcon';
|
|
5061
5076
|
await updateCreatedFormNavigationIcon(authRef, appType, formUuid, formIcon);
|
|
5077
|
+
postCreateStage = 'verifyFinalSchema';
|
|
5078
|
+
await verifyCreatedFormSchema(authRef, appType, formUuid, fields);
|
|
5062
5079
|
} catch (err) {
|
|
5063
5080
|
emitCreateFormPostCreateFailure({
|
|
5064
5081
|
appType,
|
|
@@ -5164,7 +5181,9 @@ async function createFormForLegacyProcessQuiet(context, input) {
|
|
|
5164
5181
|
applySmartValidations(schema, createValidationRules);
|
|
5165
5182
|
}
|
|
5166
5183
|
const saveResult = await saveFormSchema(authRef, appType, formUuid, schema, serverRevision, 4);
|
|
5184
|
+
await verifyCreatedFormSchema(authRef, appType, formUuid, fields);
|
|
5167
5185
|
const navIconResult = await updateCreatedFormNavigationIcon(authRef, appType, formUuid, formIcon);
|
|
5186
|
+
await verifyCreatedFormSchema(authRef, appType, formUuid, fields);
|
|
5168
5187
|
const { appUrl, formUrl } = buildFormDeliveryUrls(authRef.baseUrl, appType, formUuid);
|
|
5169
5188
|
return {
|
|
5170
5189
|
success: true,
|
|
@@ -6154,13 +6173,34 @@ async function readResumeSchema(authRef, appType, formUuid) {
|
|
|
6154
6173
|
return { schemaResult, schema, version: extractSchemaServerRevision(schemaResult) };
|
|
6155
6174
|
}
|
|
6156
6175
|
|
|
6176
|
+
async function verifyCreatedFormSchema(authRef, appType, formUuid, fields) {
|
|
6177
|
+
const readback = await readResumeSchema(authRef, appType, formUuid);
|
|
6178
|
+
const root = readback.schema.pages[0].componentsTree?.[0];
|
|
6179
|
+
const container = root ? findFormContainer(root) : null;
|
|
6180
|
+
const verified = buildResumeChanges(
|
|
6181
|
+
collectResumeFieldEvidence(container?.children),
|
|
6182
|
+
collectResumeDesiredFields(fields, { verifyNestedFields: true })
|
|
6183
|
+
);
|
|
6184
|
+
if (verified.missing.length || verified.conflicts.length) {
|
|
6185
|
+
throwCreateFormError(t('create_form.create_readback_mismatch'),
|
|
6186
|
+
'CREATE_FORM_CREATE_READBACK_MISMATCH', {
|
|
6187
|
+
appType, formUuid, retryable: false,
|
|
6188
|
+
remoteSaveState: verified.existing.length ? 'partial' : 'not_saved',
|
|
6189
|
+
missingFieldCount: verified.missing.length, diagnostics: verified.conflicts,
|
|
6190
|
+
});
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
|
|
6157
6194
|
function isRetryableResumeSaveServerFailure(errorObject) {
|
|
6158
6195
|
const status = Number(errorObject?.details?.result?.__httpStatus);
|
|
6196
|
+
const businessStatus = Number(errorObject?.details?.result?.errorCode
|
|
6197
|
+
?? errorObject?.details?.result?.code);
|
|
6159
6198
|
return errorObject?.code === 'CREATE_FORM_SAVE_SCHEMA_FAILED' &&
|
|
6160
|
-
Number.isInteger(status) && status >= 500 && status <= 599
|
|
6199
|
+
((Number.isInteger(status) && status >= 500 && status <= 599)
|
|
6200
|
+
|| (Number.isInteger(businessStatus) && businessStatus >= 500 && businessStatus <= 599));
|
|
6161
6201
|
}
|
|
6162
6202
|
|
|
6163
|
-
async function saveResumeSchemaWithSafeRetry(authRef, appType, formUuid, schema, version, fields) {
|
|
6203
|
+
async function saveResumeSchemaWithSafeRetry(authRef, appType, formUuid, schema, version, fields, attemptedFields = fields) {
|
|
6164
6204
|
const save = (value, revision) => saveFormSchema(
|
|
6165
6205
|
authRef, appType, formUuid, value, revision, 4, { deferFailureOutput: true }
|
|
6166
6206
|
);
|
|
@@ -6170,6 +6210,7 @@ async function saveResumeSchemaWithSafeRetry(authRef, appType, formUuid, schema,
|
|
|
6170
6210
|
} catch (originalError) {
|
|
6171
6211
|
if (!isRetryableResumeSaveServerFailure(originalError)) { throw originalError; }
|
|
6172
6212
|
|
|
6213
|
+
originalError.details = { ...originalError.details, remoteSaveState: 'unknown' };
|
|
6173
6214
|
let readback;
|
|
6174
6215
|
try { readback = await readResumeSchema(authRef, appType, formUuid); } catch (_) { throw originalError; }
|
|
6175
6216
|
const root = readback.schema.pages[0].componentsTree && readback.schema.pages[0].componentsTree[0];
|
|
@@ -6182,6 +6223,12 @@ async function saveResumeSchemaWithSafeRetry(authRef, appType, formUuid, schema,
|
|
|
6182
6223
|
);
|
|
6183
6224
|
if (verified.conflicts.length > 0) { throw originalError; }
|
|
6184
6225
|
if (verified.missing.length === 0) { return 'save_schema_recovered_by_readback'; }
|
|
6226
|
+
const attempted = buildResumeChanges(evidence, collectResumeDesiredFields(attemptedFields, { verifyNestedFields: true }));
|
|
6227
|
+
originalError.details.remoteSaveState = attempted.existing.length ? 'partial' : 'not_saved';
|
|
6228
|
+
// A business 500 can be a duplicate-key INSERT. Readback provides facts,
|
|
6229
|
+
// but never licenses another write on that ambiguous platform path.
|
|
6230
|
+
const businessStatus = Number(originalError.details.result?.errorCode ?? originalError.details.result?.code);
|
|
6231
|
+
if (businessStatus >= 500 && businessStatus <= 599) { throw originalError; }
|
|
6185
6232
|
const retryPlan = buildResumeChanges(evidence, collectResumeDesiredFields(fields));
|
|
6186
6233
|
if (retryPlan.conflicts.length > 0 || retryPlan.missing.length === 0) { throw originalError; }
|
|
6187
6234
|
const applied = applyChangesToSchema(
|
|
@@ -6291,7 +6338,7 @@ async function mainResume(parsedArgs, authRef) {
|
|
|
6291
6338
|
const updatedContainer = findFormContainer(firstRead.schema.pages[0].componentsTree[0]);
|
|
6292
6339
|
fillSerialNumberFormulas(updatedContainer.children, corpId, appType, formUuid);
|
|
6293
6340
|
const saveRecoveryStage = await saveResumeSchemaWithSafeRetry(
|
|
6294
|
-
authRef, appType, formUuid, firstRead.schema, firstRead.version, fields
|
|
6341
|
+
authRef, appType, formUuid, firstRead.schema, firstRead.version, fields, plan.missing
|
|
6295
6342
|
);
|
|
6296
6343
|
completedStages.push('add_missing_fields', 'save_schema');
|
|
6297
6344
|
if (saveRecoveryStage) { completedStages.push(saveRecoveryStage); }
|
package/lib/app/custom-theme.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const {
|
|
6
|
-
const { readManagedContext, managedError } = require('../agent/managed-context');
|
|
5
|
+
const { getAccessToken } = require('../auth/token-auth');
|
|
7
6
|
const { CliError } = require('../core/cli-error');
|
|
8
7
|
const { t } = require('../core/i18n');
|
|
9
8
|
const { findCssStructureIssue } = require('../core/css-structure');
|
|
@@ -261,13 +260,6 @@ function buildCustomThemeStyle(uploadResult) {
|
|
|
261
260
|
}
|
|
262
261
|
|
|
263
262
|
async function uploadCustomThemeFile(appType, filePath, authRef, options = {}) {
|
|
264
|
-
const managed = readManagedContext(process.env);
|
|
265
|
-
if (managed && appType !== managed.appType) {
|
|
266
|
-
throw managedError('MANAGED_APP_MISMATCH', 'managed_app_mismatch');
|
|
267
|
-
}
|
|
268
|
-
if (managed && new URL(authRef.baseUrl).origin !== new URL(managed.baseUrl).origin) {
|
|
269
|
-
throw managedError('MANAGED_OVERRIDE_FORBIDDEN', 'managed_override_forbidden');
|
|
270
|
-
}
|
|
271
263
|
const file = readThemeCssFile(filePath);
|
|
272
264
|
const fetchImpl = options.fetchImpl || globalThis.fetch;
|
|
273
265
|
const FormDataImpl = options.FormDataImpl || globalThis.FormData;
|
|
@@ -276,7 +268,7 @@ async function uploadCustomThemeFile(appType, filePath, authRef, options = {}) {
|
|
|
276
268
|
throw new Error('当前 Node.js 环境缺少 fetch/FormData/Blob,请使用 Node.js 18+');
|
|
277
269
|
}
|
|
278
270
|
|
|
279
|
-
const
|
|
271
|
+
const accessToken = await getAccessToken({ projectRoot: authRef && authRef.projectRoot });
|
|
280
272
|
const endpoint = new URL('/query/app/customTheme/upload.json', authRef.baseUrl);
|
|
281
273
|
if (authRef.csrfToken) {
|
|
282
274
|
endpoint.searchParams.set('_csrf_token', authRef.csrfToken);
|
|
@@ -287,11 +279,9 @@ async function uploadCustomThemeFile(appType, filePath, authRef, options = {}) {
|
|
|
287
279
|
|
|
288
280
|
const response = await fetchImpl(endpoint, {
|
|
289
281
|
method: 'POST',
|
|
290
|
-
// Never forward a multipart request carrying credentials to another URL.
|
|
291
|
-
redirect: 'error',
|
|
292
282
|
headers: {
|
|
293
283
|
Accept: 'application/json, text/plain, */*',
|
|
294
|
-
|
|
284
|
+
Authorization: `Bearer ${accessToken}`,
|
|
295
285
|
Origin: authRef.baseUrl,
|
|
296
286
|
Referer: `${authRef.baseUrl}/`,
|
|
297
287
|
'x-requested-with': 'XMLHttpRequest',
|
package/lib/app/get-schema.js
CHANGED
|
@@ -78,12 +78,7 @@ function extractFieldSummary(schemaResult) {
|
|
|
78
78
|
const complexDefault = props.complexValue && typeof props.complexValue === 'object'
|
|
79
79
|
? props.complexValue.value
|
|
80
80
|
: undefined;
|
|
81
|
-
|
|
82
|
-
// retain an obsolete empty value after a targeted field update.
|
|
83
|
-
const customText = ['TextField', 'TextareaField'].includes(node.componentName) && props.valueType === 'custom';
|
|
84
|
-
const defaultValue = firstPresent(customText
|
|
85
|
-
? [props.value, complexDefault, props.defaultValue, props.default]
|
|
86
|
-
: [complexDefault, props.defaultValue, props.value, props.default]);
|
|
81
|
+
const defaultValue = firstPresent([complexDefault, props.defaultValue, props.value, props.default]);
|
|
87
82
|
const validationRules = Array.isArray(props.validation) ? props.validation : [];
|
|
88
83
|
const hasRequiredSignal = Object.prototype.hasOwnProperty.call(props, 'required')
|
|
89
84
|
|| validationRules.some(rule => rule && (rule.type === 'required' || rule.required === true));
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CliError } = require('../core/cli-error');
|
|
4
|
+
const { resolveFormNavIcon } = require('./create-form/nav-icon');
|
|
5
|
+
const { buildFormNavigationIconUpdatePayload } = require('./create-form/nav-icon-service');
|
|
6
|
+
|
|
7
|
+
/** Create a group, then persist a semantic icon using the platform navigation protocol. */
|
|
8
|
+
async function createGroupWithIcon(appType, name, flags, authRef, dependencies) {
|
|
9
|
+
const { fetchNavigationList, postNavAction, resolveNode, buildI18nTitle,
|
|
10
|
+
getNodeName, normalizeNavNode, rootNavUuid } = dependencies;
|
|
11
|
+
if (!name) {throw new Error('Group name is required');}
|
|
12
|
+
const selection = resolveFormNavIcon(flags.icon, name, []);
|
|
13
|
+
if (!selection.icon) {
|
|
14
|
+
throw new CliError('Invalid navigation icon. Use auto or a supported page navigation icon name.', {
|
|
15
|
+
code: 'NAV_GROUP_ICON_INVALID', details: { icon: flags.icon, retrySafe: true },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const list = await fetchNavigationList(appType, authRef);
|
|
19
|
+
const parent = resolveNode(list, flags.parent || rootNavUuid, { groupOnly: true });
|
|
20
|
+
if (parent.navUuid !== rootNavUuid && parent.parentNavUuid !== rootNavUuid) {
|
|
21
|
+
throw new Error('New groups can only be created at root or under a top-level group');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const beforeIds = new Set(list.map((node) => node.navUuid));
|
|
25
|
+
let created;
|
|
26
|
+
let stage = 'create';
|
|
27
|
+
try {
|
|
28
|
+
// Do not replay a creation on an uncertain response: it can duplicate the group.
|
|
29
|
+
await postNavAction(appType, 'save', {
|
|
30
|
+
parentNavUuid: parent.navUuid,
|
|
31
|
+
title: JSON.stringify(buildI18nTitle(name, flags.en)),
|
|
32
|
+
}, authRef, { oneShot: true });
|
|
33
|
+
stage = 'resolve';
|
|
34
|
+
const updatedList = await fetchNavigationList(appType, authRef);
|
|
35
|
+
const matches = updatedList.filter((node) => node.navType === 'NAV' &&
|
|
36
|
+
!beforeIds.has(node.navUuid) && getNodeName(node) === name &&
|
|
37
|
+
(node.parentNavUuid || rootNavUuid) === parent.navUuid);
|
|
38
|
+
if (matches.length !== 1) {
|
|
39
|
+
throw new Error('Created group could not be resolved uniquely.');
|
|
40
|
+
}
|
|
41
|
+
created = matches[0];
|
|
42
|
+
stage = 'icon';
|
|
43
|
+
await postNavAction(appType, 'update',
|
|
44
|
+
buildFormNavigationIconUpdatePayload(created, selection.icon), authRef, { oneShot: true });
|
|
45
|
+
stage = 'readback';
|
|
46
|
+
const observed = await fetchNavigationList(appType, authRef);
|
|
47
|
+
const verified = resolveNode(observed, created.navUuid, { groupOnly: true });
|
|
48
|
+
if (verified.icon !== selection.icon) {
|
|
49
|
+
throw new Error('Group navigation icon readback did not match the requested icon.');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
success: true, appType, action: 'create', group: normalizeNavNode(verified),
|
|
53
|
+
iconSource: selection.source, readbackVerified: true,
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new CliError(error.message, {
|
|
57
|
+
code: 'NAV_GROUP_CREATE_INCOMPLETE',
|
|
58
|
+
details: {
|
|
59
|
+
appType, stage, name, parentNavUuid: parent.navUuid,
|
|
60
|
+
navUuid: created ? created.navUuid : null, expectedIcon: selection.icon,
|
|
61
|
+
partial: stage !== 'create', sideEffectState: created ? 'committed' : 'unknown',
|
|
62
|
+
retrySafe: false, readbackVerified: false, readbackAllowed: true,
|
|
63
|
+
nextStep: `openyida nav-group list ${appType} --flat`,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { createGroupWithIcon };
|
package/lib/app/nav-group.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const querystring = require('querystring');
|
|
4
|
+
const { createGroupWithIcon } = require('./nav-group-create');
|
|
4
5
|
const {
|
|
5
6
|
httpGet,
|
|
6
7
|
httpPost,
|
|
@@ -44,6 +45,7 @@ function normalizeNavNode(node) {
|
|
|
44
45
|
formUuid: node.formUuid || null,
|
|
45
46
|
relateFormUuid: node.relateFormUuid || null,
|
|
46
47
|
formType: node.formType || '',
|
|
48
|
+
icon: node.icon || null,
|
|
47
49
|
hidden: node.hidden === 'y',
|
|
48
50
|
mobileHidden: node.mobileHidden === 'y',
|
|
49
51
|
listOrder: node.listOrder,
|
|
@@ -734,34 +736,12 @@ async function listGroups(appType, flags, authRef) {
|
|
|
734
736
|
};
|
|
735
737
|
}
|
|
736
738
|
|
|
739
|
+
/** Reuse the page icon catalog and preserve the created navigation node when setting its icon. */
|
|
737
740
|
async function createGroup(appType, name, flags, authRef) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
const list = await fetchNavigationList(appType, authRef);
|
|
743
|
-
const parent = resolveNode(list, flags.parent || ROOT_NAV_UUID, { groupOnly: true });
|
|
744
|
-
if (parent.navUuid !== ROOT_NAV_UUID && parent.parentNavUuid !== ROOT_NAV_UUID) {
|
|
745
|
-
throw new Error('New groups can only be created at root or under a top-level group');
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
const title = buildI18nTitle(name, flags.en);
|
|
749
|
-
await postNavAction(appType, 'save', {
|
|
750
|
-
parentNavUuid: parent.navUuid,
|
|
751
|
-
title: JSON.stringify(title),
|
|
752
|
-
}, authRef);
|
|
753
|
-
|
|
754
|
-
const updatedList = await fetchNavigationList(appType, authRef);
|
|
755
|
-
const created = updatedList
|
|
756
|
-
.filter((node) => node.navType === 'NAV' && getNodeName(node) === name)
|
|
757
|
-
.sort((a, b) => (b.gmtCreate || 0) - (a.gmtCreate || 0))[0];
|
|
758
|
-
|
|
759
|
-
return {
|
|
760
|
-
success: true,
|
|
761
|
-
appType,
|
|
762
|
-
action: 'create',
|
|
763
|
-
group: created ? normalizeNavNode(created) : { name, parentNavUuid: parent.navUuid },
|
|
764
|
-
};
|
|
741
|
+
return createGroupWithIcon(appType, name, flags, authRef, {
|
|
742
|
+
fetchNavigationList, postNavAction, resolveNode, buildI18nTitle,
|
|
743
|
+
getNodeName, normalizeNavNode, rootNavUuid: ROOT_NAV_UUID,
|
|
744
|
+
});
|
|
765
745
|
}
|
|
766
746
|
|
|
767
747
|
async function renameGroup(appType, value, name, flags, authRef) {
|
|
@@ -899,7 +879,7 @@ function printUsage() {
|
|
|
899
879
|
console.error(`
|
|
900
880
|
Usage:
|
|
901
881
|
openyida nav-group list <appType> [--flat] [--include-system] [--raw]
|
|
902
|
-
openyida nav-group create <appType> "<groupName>" [--parent <groupNavUuid|groupName>] [--en "<EnglishName>"]
|
|
882
|
+
openyida nav-group create <appType> "<groupName>" [--parent <groupNavUuid|groupName>] [--en "<EnglishName>"] [--icon auto|<iconName>]
|
|
903
883
|
openyida nav-group rename <appType> <groupNavUuid|groupName> "<newName>" [--en "<EnglishName>"]
|
|
904
884
|
openyida nav-group delete <appType> <groupNavUuid|groupName> [--force]
|
|
905
885
|
openyida nav-group move <appType> <navUuid|formUuid|name> --to <groupNavUuid|groupName|root> [--before <node>] [--after <node>]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { CliError } = require('../core/cli-error');
|
|
6
|
-
const {
|
|
6
|
+
const { validateThemeCssContent, extractThemeColor } = require('./custom-theme');
|
|
7
7
|
const { parseDesignDocument, extractDesignTokens } = require('../design/document');
|
|
8
8
|
const { normalizeThemeScopes } = require('./theme-scope');
|
|
9
9
|
const DECLARATIONS = /^[ \t]*(--[\w-]+)\s*:\s*([^;]+);/gm;
|
|
@@ -25,23 +25,22 @@ function readDesignTokens(markdown) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function tokensFromMetadata(metadata) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
throw new CliError(`design.md 缺少品牌 token:${missing.join(', ')}`, { code: 'DESIGN_THEME_TOKENS_REQUIRED' });
|
|
32
|
-
}
|
|
33
|
-
return tokens;
|
|
28
|
+
// A design may override only part of the paired CSS. Validate the completed
|
|
29
|
+
// stylesheet after merging instead of requiring duplicate declarations here.
|
|
30
|
+
return extractDesignTokens(metadata, { strict: false });
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
function resolveDesignTokens(metadata) {
|
|
33
|
+
function resolveDesignTokens(metadata, options = {}) {
|
|
37
34
|
const tokens = tokensFromMetadata(metadata);
|
|
35
|
+
if (options.paired) {return tokens;}
|
|
38
36
|
for (const [alias, source] of Object.entries({
|
|
39
37
|
'--color-brand-1': '--color-brand1-10', '--color-brand-2': '--color-brand1-1',
|
|
40
38
|
'--color-brand-3': '--color-brand1-6', '--color-brand-4': '--color-brand1-9',
|
|
41
39
|
})) {
|
|
42
|
-
tokens[alias] = tokens[alias] || tokens[source];
|
|
40
|
+
if (tokens[source]) {tokens[alias] = tokens[alias] || tokens[source];}
|
|
43
41
|
}
|
|
44
|
-
|
|
42
|
+
const group = [6, 1, 5, 2, 9, 3].map(n => tokens[`--color-brand1-${n}`]);
|
|
43
|
+
if (group.every(Boolean)) {tokens['--color-group'] = tokens['--color-group'] || group.join(', ');}
|
|
45
44
|
return tokens;
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -108,10 +107,20 @@ function applyNavigationShape(css, tokens) {
|
|
|
108
107
|
return block ? `${css.trimEnd()}\n\n${block}\n` : css;
|
|
109
108
|
}
|
|
110
109
|
|
|
111
|
-
|
|
110
|
+
// Template baselines can contain brand expressions; they are comparison data,
|
|
111
|
+
// never CSS output. Completed project tokens still use the normal validator.
|
|
112
|
+
function authoredTokens(node, result = {}) {
|
|
113
|
+
for (const [name, value] of Object.entries(node || {})) {
|
|
114
|
+
if (name.startsWith('--')) {result[name] = String(value);}
|
|
115
|
+
else if (value && typeof value === 'object') {authoredTokens(value, result);}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function applyDesignTokens(template, markdown, previousMarkdown, options = {}) {
|
|
112
121
|
const metadata = designMetadata(markdown);
|
|
113
122
|
const previousMetadata = previousMarkdown === markdown ? metadata : (previousMarkdown ? designMetadata(previousMarkdown) : null);
|
|
114
|
-
const tokens = resolveDesignTokens(metadata);
|
|
123
|
+
const tokens = resolveDesignTokens(metadata, options);
|
|
115
124
|
// Bundled CSS is an authoring template. Fill only unresolved declarations from
|
|
116
125
|
// the completed design before validating and rewriting CSS scopes.
|
|
117
126
|
template = template.replace(/^([ \t]*)(--[\w-]+)(\s*:\s*)([^;\n]+);/gm,
|
|
@@ -124,11 +133,14 @@ function applyDesignTokens(template, markdown, previousMarkdown) {
|
|
|
124
133
|
});
|
|
125
134
|
// Reject damaged templates or existing stylesheets before the scope rewrite.
|
|
126
135
|
validateThemeCssContent(template);
|
|
127
|
-
const previous = previousMetadata === metadata ? tokens : (previousMetadata
|
|
136
|
+
const previous = previousMetadata === metadata ? tokens : (previousMetadata
|
|
137
|
+
? (options.paired ? authoredTokens(previousMetadata.tokens) : resolveDesignTokens(previousMetadata)) : null);
|
|
128
138
|
navigationTone(metadata); // Validate platform metadata; it does not select a second palette.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
if (!options.paired) {
|
|
140
|
+
template = normalizeThemeScopes(template, navigationTone(previousMetadata || metadata));
|
|
141
|
+
template = applyNavigationShape(template, tokens);
|
|
142
|
+
if (previous) {template = restoreRemovedNavigation(template, tokens, previous);}
|
|
143
|
+
}
|
|
132
144
|
const changed = Object.fromEntries(Object.entries(tokens).filter(([name, value]) => !previous || previous[name] !== value));
|
|
133
145
|
if (!Object.keys(changed).length) {
|
|
134
146
|
validateThemeCssContent(template);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { CliError } = require('../core/cli-error');
|
|
6
|
+
const { t } = require('../core/i18n');
|
|
7
|
+
const { parseDesignDocument } = require('../design/document');
|
|
8
|
+
const { DESIGN_SKILL_ROOT, loadThemeIndex } = require('../design-plan/themes');
|
|
9
|
+
const { applyDesignTokens } = require('./theme-from-design');
|
|
10
|
+
const SOURCE = /^\/\* openyida-theme-source: ([a-z0-9-]+) \*\/\r?\n/m;
|
|
11
|
+
|
|
12
|
+
function sourceId(markdown) {
|
|
13
|
+
const metadata = parseDesignDocument(markdown, { legacyTokenValues: true }).metadata;
|
|
14
|
+
return metadata.themeProfile?.sourceThemeId ?? metadata.themeId;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Resolve only catalog paths, never a project-supplied CSS path or theme directory.
|
|
18
|
+
function themeSource(markdown) {
|
|
19
|
+
const id = sourceId(markdown);
|
|
20
|
+
const theme = id && loadThemeIndex().themes.find(item => item.themeId === id);
|
|
21
|
+
if (id !== undefined && !theme) {
|
|
22
|
+
throw new CliError(t('design_document.invalid', 'themeProfile.sourceThemeId', 'APPLICATION_STYLE_UNKNOWN'), {
|
|
23
|
+
code: 'APPLICATION_STYLE_UNKNOWN', details: { themeId: id },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const relative = theme && theme.mode !== 'creative' ? theme.cssTemplatePath : 'references/theme/app-custom-theme-template.css';
|
|
27
|
+
const file = path.resolve(DESIGN_SKILL_ROOT, relative);
|
|
28
|
+
if (!file.startsWith(DESIGN_SKILL_ROOT + path.sep)) {throw new Error('Invalid theme CSS path');}
|
|
29
|
+
return { id: id || 'legacy', css: fs.readFileSync(file, 'utf8'), paired: theme?.mode === 'template',
|
|
30
|
+
design: theme?.mode === 'template' ? fs.readFileSync(path.resolve(DESIGN_SKILL_ROOT, theme.templatePath), 'utf8') : undefined };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function generateThemeCss(markdown, existingCss, previousMarkdown) {
|
|
34
|
+
const source = themeSource(markdown);
|
|
35
|
+
const marker = existingCss?.match(SOURCE)?.[1];
|
|
36
|
+
// An exported paired stylesheet is also a valid first-generation input.
|
|
37
|
+
// Unknown historical CSS cannot safely be relabeled as a different template.
|
|
38
|
+
const previousId = marker || (previousMarkdown && sourceId(previousMarkdown))
|
|
39
|
+
|| (existingCss?.startsWith(source.css.trimEnd()) ? source.id : 'legacy');
|
|
40
|
+
if (existingCss !== undefined && previousId !== source.id) {
|
|
41
|
+
throw new CliError(t('theme_source.conflict'), {
|
|
42
|
+
code: 'THEME_SOURCE_CONFLICT', details: { previousThemeId: previousId, themeId: source.id },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const input = existingCss ?? source.css;
|
|
46
|
+
// The paired CSS wins when its design has a different authored default.
|
|
47
|
+
// Only project deltas and unresolved brand expressions are instantiated.
|
|
48
|
+
const css = applyDesignTokens(input, markdown, previousMarkdown ?? source.design, { paired: source.paired });
|
|
49
|
+
// Keep historical token-only files byte-compatible. Named and creative outputs
|
|
50
|
+
// retain identity even when the file is renamed or its token snapshot is lost.
|
|
51
|
+
return source.id === 'legacy' ? css : `/* openyida-theme-source: ${source.id} */\n${css.replace(SOURCE, '')}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { generateThemeCss, sourceId, themeSource };
|
package/lib/app/update-app.js
CHANGED
|
@@ -763,22 +763,11 @@ async function run(args) {
|
|
|
763
763
|
const postData = querystring.stringify(postDataObj);
|
|
764
764
|
return httpPost(
|
|
765
765
|
auth.baseUrl,
|
|
766
|
-
|
|
766
|
+
`/query/app/updateAppName.json?_api=Form.updateAppName&_mock=false&_stamp=${Date.now()}`,
|
|
767
767
|
postData
|
|
768
768
|
);
|
|
769
769
|
}, authRef);
|
|
770
770
|
|
|
771
|
-
// Verify the persisted resource before exposing success or a mutation receipt.
|
|
772
|
-
let nameVerification;
|
|
773
|
-
if (response && response.success && params.name) {
|
|
774
|
-
const savedApp = await fetchCurrentApp(params.appType, authRef);
|
|
775
|
-
const savedName = pickText(savedApp.appName, '');
|
|
776
|
-
if (savedName !== params.name) {
|
|
777
|
-
throwCommandError(t('update_app.name_not_persisted'), { code: 'APP_NAME_NOT_PERSISTED' });
|
|
778
|
-
}
|
|
779
|
-
nameVerification = { verified: true, name: savedName };
|
|
780
|
-
}
|
|
781
|
-
|
|
782
771
|
// 输出结果
|
|
783
772
|
if (response && response.success) {
|
|
784
773
|
const details = [['appType', params.appType]];
|
|
@@ -789,7 +778,6 @@ async function run(args) {
|
|
|
789
778
|
success: true,
|
|
790
779
|
appType: params.appType,
|
|
791
780
|
themeVerification: response.themeVerification,
|
|
792
|
-
nameVerification,
|
|
793
781
|
navigationVerification: response.navigationVerification,
|
|
794
782
|
...(params.disableCustomTheme ? { runtimeVerification: { verified: false } } : {}),
|
|
795
783
|
updatedFields: {
|