openyida 2026.7.18 → 2026.7.21
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 +37 -2
- package/bin/yida.js +113 -18
- package/docs/capabilities.md +202 -0
- package/lib/agent-center/agent-center.js +2 -3
- package/lib/app/app-list.js +3 -4
- package/lib/app/build-page.js +13 -6
- package/lib/app/check-page.js +16 -6
- package/lib/app/compile.js +17 -7
- package/lib/app/create-app.js +14 -2
- package/lib/app/create-form/api-path.js +25 -0
- package/lib/app/create-form/args.js +13 -0
- package/lib/app/create-form/commands.js +4 -0
- package/lib/app/create-form/definition-reader.js +23 -23
- package/lib/app/create-form/field-normalizers.js +26 -7
- package/lib/app/create-form/schema-patch.js +4 -1
- package/lib/app/create-form.js +155 -68
- package/lib/app/create-page.js +47 -10
- package/lib/app/generate-page.js +20 -1
- package/lib/app/get-form-config.js +2 -1
- package/lib/app/get-schema.js +67 -15
- package/lib/app/import-app.js +94 -42
- package/lib/app/list-forms.js +4 -8
- package/lib/app/page-ir.js +140 -0
- package/lib/app/publish.js +289 -556
- package/lib/app/schema-field-resolution.js +370 -0
- package/lib/app/services/app-reader.js +85 -0
- package/lib/app/services/app-service.js +235 -0
- package/lib/app/services/canvas-page-compiler.js +91 -0
- package/lib/app/services/canvas-page-schema-builder.js +149 -0
- package/lib/app/services/field-bindings.js +68 -0
- package/lib/app/services/form-compiler.js +1922 -0
- package/lib/app/services/form-mode-service.js +182 -0
- package/lib/app/services/form-schema-patcher.js +333 -0
- package/lib/app/services/form-schema-reader.js +59 -0
- package/lib/app/services/form-service.js +379 -0
- package/lib/app/services/form-validation.js +562 -0
- package/lib/app/services/native-page-compiler.js +92 -0
- package/lib/app/services/native-page-schema-builder.js +428 -0
- package/lib/app/services/page-resource-service.js +185 -0
- package/lib/app/update-app.js +22 -9
- package/lib/app/update-form-config.js +4 -2
- package/lib/asset/asset-cmd.js +19 -6
- package/lib/auth/oauth-loopback.js +376 -15
- package/lib/auth/token-auth.js +34 -3
- package/lib/auth/token-store.js +22 -4
- package/lib/basic-info/basic-info.js +2 -1
- package/lib/bridge/bridge.js +43 -18
- package/lib/core/agent-capabilities.js +5 -3
- package/lib/core/command-errors.js +38 -0
- package/lib/core/command-manifest.js +59 -1
- package/lib/core/copy.js +12 -0
- package/lib/core/doctor.js +15 -4
- package/lib/core/env-cmd.js +1 -1
- package/lib/core/env.js +7 -6
- package/lib/core/i18n.js +66 -8
- package/lib/core/legacy-schema-guard.js +408 -0
- package/lib/core/locales/en.js +38 -46
- package/lib/core/locales/zh.js +30 -46
- package/lib/core/sample.js +12 -2
- package/lib/core/utils.js +334 -66
- package/lib/core/yida-client.js +108 -5
- package/lib/corp-efficiency/corp-efficiency.js +2 -2
- package/lib/corp-manager/corp-manager.js +3 -3
- package/lib/formula/evaluate.js +11 -68
- package/lib/formula/field-refs.js +83 -0
- package/lib/i18n-management/i18n-management.js +2 -3
- package/lib/process/configure-process.js +44 -1992
- package/lib/process/create-process.js +54 -87
- package/lib/process/preview-process.js +20 -8
- package/lib/process/services/process-compiler.js +2056 -0
- package/lib/process/services/process-reader.js +232 -0
- package/lib/process/services/process-resource-service.js +571 -0
- package/lib/process/services/process-service.js +122 -0
- package/lib/process/services/process-stage-checkpoint.js +246 -0
- package/lib/report/append.js +7 -26
- package/lib/report/http.js +31 -5
- package/lib/report/index.js +10 -2
- package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
- package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
- package/lib/samples/yida-canvas-custom-page/dashboard-starter.canvas.jsx +5 -2
- package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
- package/lib/samples/yida-canvas-custom-page/portal-shell-home.canvas.jsx +2 -2
- package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +206 -10
- package/lib/samples/yida-custom-page/data-management.oyd.jsx +14 -3
- package/lib/samples/yida-custom-page/design-tokens.js +2 -2
- package/lib/samples/yida-custom-page/detail-profile.oyd.jsx +1 -1
- package/lib/samples/yida-custom-page/portal-shell-home.oyd.jsx +7 -2
- package/lib/samples/yida-custom-page/split-pane-detail.oyd.jsx +2 -1
- package/lib/schema/adapters/app-adapter.js +210 -0
- package/lib/schema/adapters/form-adapter.js +1362 -0
- package/lib/schema/adapters/keys.js +19 -0
- package/lib/schema/adapters/page-adapter.js +566 -0
- package/lib/schema/adapters/process-adapter.js +519 -0
- package/lib/schema/applier.js +1878 -0
- package/lib/schema/apply-store.js +1083 -0
- package/lib/schema/command.js +645 -0
- package/lib/schema/dependency-graph.js +83 -0
- package/lib/schema/errors.js +481 -0
- package/lib/schema/hash.js +9 -0
- package/lib/schema/manifest-limits.js +177 -0
- package/lib/schema/manifest-loader.js +309 -0
- package/lib/schema/manifest-schema-v1.json +193 -0
- package/lib/schema/normalize-manifest.js +281 -0
- package/lib/schema/page-canvas-foundation.js +447 -0
- package/lib/schema/page-data-source-builder.js +561 -0
- package/lib/schema/page-foundation.js +556 -0
- package/lib/schema/page-source-loader.js +314 -0
- package/lib/schema/planner.js +691 -0
- package/lib/schema/remote-dispatch-boundary.js +39 -0
- package/lib/schema/remote-missing.js +21 -0
- package/lib/schema/remote-reader.js +189 -0
- package/lib/schema/resource-registry.js +157 -0
- package/lib/schema/server-revision.js +71 -0
- package/lib/schema/sort.js +28 -0
- package/lib/schema/state-store.js +589 -0
- package/package.json +10 -8
- package/scripts/check-release-risks.js +254 -0
- package/scripts/generate-command-docs.js +6 -1
- package/scripts/i18n-baseline.json +11 -11
- package/scripts/postinstall.js +8 -12
- package/scripts/validate-ci.sh +15 -11
- package/scripts/validate-i18n.js +30 -9
- package/scripts/validate-package-size.js +5 -2
- package/scripts/validate-skills.js +4 -0
- package/scripts/validate-structure.js +15 -0
- package/yida-skills/SKILL.md +117 -30
- package/yida-skills/references/schema-as-code-phase1.md +99 -0
- package/yida-skills/references/setup-and-env.md +59 -59
- package/yida-skills/references/task-retrospective.md +6 -6
- package/yida-skills/skills/yida-app/SKILL.md +78 -17
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +15 -0
- package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
- package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
- package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
- package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
- package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
- package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
- package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
- package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
- package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
- package/yida-skills/skills/yida-integration/SKILL.md +7 -1
- package/yida-skills/skills/yida-login/SKILL.md +50 -99
- package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
- package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
- package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
- package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
- package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
- package/yida-skills/skills/yida-report/SKILL.md +9 -235
- package/yida-skills/skills/yida-report/references/schema-builder-details.md +239 -0
- package/yida-skills/skills/yida-skill-evaluator/SKILL.md +170 -0
- package/yida-skills/skills-index.json +238 -54
- package/lib/core/locales/ar.js +0 -834
- package/lib/core/locales/de.js +0 -834
- package/lib/core/locales/es.js +0 -834
- package/lib/core/locales/fr.js +0 -834
- package/lib/core/locales/hi.js +0 -834
- package/lib/core/locales/ja.js +0 -1227
- package/lib/core/locales/ko.js +0 -836
- package/lib/core/locales/pt.js +0 -834
- package/lib/core/locales/vi.js +0 -834
- package/lib/core/locales/zh-HK.js +0 -1263
- package/project/pages/src/demo-birthday-game.oyd.jsx +0 -832
- package/project/pages/src/demo-chip-insight.oyd.jsx +0 -983
- package/project/pages/src/demo-compat-smoke.oyd.jsx +0 -58
- package/project/pages/src/demo-crm-batch-entry.oyd.jsx +0 -805
- package/project/pages/src/demo-crm-dashboard.oyd.jsx +0 -677
- package/project/pages/src/demo-future-vision-2026.oyd.jsx +0 -1102
- package/project/pages/src/demo-ppt.oyd.jsx +0 -1192
- package/project/pages/src/demo-salary-calculator.oyd.jsx +0 -904
- package/project/pages/src/openyida-knowledge-doc.oyd.jsx +0 -1714
- package/yida-skills/skills/large-file-write/SKILL.md +0 -93
- package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
- package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
- package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
- package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
- package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { isDeepStrictEqual } = require('util');
|
|
5
|
+
const { schemaError } = require('./errors');
|
|
6
|
+
const { hashStable } = require('./state-store');
|
|
7
|
+
const {
|
|
8
|
+
NATIVE_PAGE_PROFILE,
|
|
9
|
+
isCompiledNativePage,
|
|
10
|
+
} = require('../app/services/native-page-compiler');
|
|
11
|
+
|
|
12
|
+
const DISPLAY_FORM_TYPE = 'display';
|
|
13
|
+
const SHELL_PROFILE_CONTRACT_VERSION = 1;
|
|
14
|
+
const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
15
|
+
const SHELL_PROFILE_ID_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/;
|
|
16
|
+
const SHELL_PROFILES = new WeakSet();
|
|
17
|
+
const COMPONENT_ORDER_KEYS = new Set(['children', 'componentsMap', 'componentsTree']);
|
|
18
|
+
const SHELL_NEUTRAL_TIME_KEYS = new Set([
|
|
19
|
+
'createtime',
|
|
20
|
+
'createdat',
|
|
21
|
+
'gmtcreate',
|
|
22
|
+
'gmtmodified',
|
|
23
|
+
'modifiedtime',
|
|
24
|
+
'timestamp',
|
|
25
|
+
'updatedat',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function createNativePageShellProfile(input = {}) {
|
|
29
|
+
const profileId = typeof input.profileId === 'string' ? input.profileId.trim() : '';
|
|
30
|
+
const fingerprint = typeof input.fingerprint === 'string' ? input.fingerprint.trim() : '';
|
|
31
|
+
if (
|
|
32
|
+
!SHELL_PROFILE_ID_PATTERN.test(profileId) ||
|
|
33
|
+
!HASH_PATTERN.test(fingerprint)
|
|
34
|
+
) {
|
|
35
|
+
foundationError(
|
|
36
|
+
'SCHEMA_PAGE_FOUNDATION_SHELL_PROFILE_INVALID',
|
|
37
|
+
'Native page shell profile is invalid.'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const profile = Object.freeze({
|
|
41
|
+
contractVersion: SHELL_PROFILE_CONTRACT_VERSION,
|
|
42
|
+
fingerprint,
|
|
43
|
+
profileId,
|
|
44
|
+
});
|
|
45
|
+
SHELL_PROFILES.add(profile);
|
|
46
|
+
return profile;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function computeNativePageShellFingerprint(schemaInput) {
|
|
50
|
+
const schema = normalizeSchema(schemaInput);
|
|
51
|
+
inspectNativeStructure(schema);
|
|
52
|
+
inspectActions(schema.actions);
|
|
53
|
+
return computeShellFingerprint(schema);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function projectNativePageSchema(input = {}) {
|
|
57
|
+
return projectNativePageSchemaInternal(input, true);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function projectNativePageSchemaInternal(input, enforceShellProfile) {
|
|
61
|
+
const observedIdentity = normalizeObservedIdentity(input);
|
|
62
|
+
if (observedIdentity.formType !== DISPLAY_FORM_TYPE) {
|
|
63
|
+
foundationError(
|
|
64
|
+
'SCHEMA_PAGE_FOUNDATION_PROFILE_UNSUPPORTED',
|
|
65
|
+
'Observed page is not a display resource.'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const shellProfile = requireShellProfile(input.shellProfile);
|
|
69
|
+
const schema = normalizeSchema(input.schema);
|
|
70
|
+
const structure = inspectNativeStructure(schema);
|
|
71
|
+
const actions = inspectActions(schema.actions);
|
|
72
|
+
const shellProfileMatches = computeShellFingerprint(schema) === shellProfile.fingerprint;
|
|
73
|
+
if (enforceShellProfile && !shellProfileMatches) {
|
|
74
|
+
foundationError(
|
|
75
|
+
'SCHEMA_PAGE_FOUNDATION_CONFLICT',
|
|
76
|
+
'Remote page Schema does not match the approved initial shell profile.'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const managed = {
|
|
80
|
+
formType: observedIdentity.formType,
|
|
81
|
+
profile: NATIVE_PAGE_PROFILE,
|
|
82
|
+
title: observedIdentity.title,
|
|
83
|
+
};
|
|
84
|
+
let classification = 'PAGE_CREATED';
|
|
85
|
+
if (actions.saved) {
|
|
86
|
+
classification = 'SCHEMA_SAVED';
|
|
87
|
+
managed.sourceHash = hashStableText(actions.source);
|
|
88
|
+
managed.compiledHash = hashStableText(actions.compiled);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
classification,
|
|
92
|
+
managed,
|
|
93
|
+
managedHash: hashStable(managed),
|
|
94
|
+
remoteSchemaHash: hashRemoteSchema(schema),
|
|
95
|
+
shellProfileMatches,
|
|
96
|
+
structure: {
|
|
97
|
+
jsxComponentCount: structure.jsxComponents.length,
|
|
98
|
+
pageComponentCount: structure.pageComponents.length,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildNativePageSchemaPatch(input = {}) {
|
|
104
|
+
const observedIdentity = normalizeObservedIdentity(input);
|
|
105
|
+
const desiredIdentity = normalizeDesiredIdentity(input);
|
|
106
|
+
assertIdentityMatches(observedIdentity, desiredIdentity);
|
|
107
|
+
const compiledPage = requireCompiledPage(input.compiledPage);
|
|
108
|
+
const schema = normalizeSchema(input.remoteSchema);
|
|
109
|
+
const beforeProjection = projectNativePageSchema({
|
|
110
|
+
observedFormType: observedIdentity.formType,
|
|
111
|
+
observedTitle: observedIdentity.title,
|
|
112
|
+
schema,
|
|
113
|
+
shellProfile: input.shellProfile,
|
|
114
|
+
});
|
|
115
|
+
if (
|
|
116
|
+
input.expectedRemoteSchemaHash !== undefined &&
|
|
117
|
+
input.expectedRemoteSchemaHash !== beforeProjection.remoteSchemaHash
|
|
118
|
+
) {
|
|
119
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_CONFLICT', 'Remote page Schema changed before patching.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const desiredManaged = buildDesiredManaged(desiredIdentity.title, compiledPage);
|
|
123
|
+
if (beforeProjection.classification === 'SCHEMA_SAVED') {
|
|
124
|
+
if (isDeepStrictEqual(beforeProjection.managed, desiredManaged)) {
|
|
125
|
+
return {
|
|
126
|
+
baselineRemoteSchemaHash: beforeProjection.remoteSchemaHash,
|
|
127
|
+
managed: desiredManaged,
|
|
128
|
+
managedHash: hashStable(desiredManaged),
|
|
129
|
+
preparedRemoteSchemaHash: beforeProjection.remoteSchemaHash,
|
|
130
|
+
schema,
|
|
131
|
+
unmanagedHash: hashUnmanagedSchema(schema),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const beforeUnmanagedHash = hashUnmanagedSchema(schema);
|
|
137
|
+
const patched = clonePlain(schema);
|
|
138
|
+
if (patched.actions === undefined) {
|
|
139
|
+
patched.actions = {};
|
|
140
|
+
}
|
|
141
|
+
assertPlainObject(patched.actions);
|
|
142
|
+
if (patched.actions.module === undefined) {
|
|
143
|
+
patched.actions.module = {};
|
|
144
|
+
}
|
|
145
|
+
assertPlainObject(patched.actions.module);
|
|
146
|
+
patched.actions.module.source = compiledPage.source;
|
|
147
|
+
patched.actions.module.compiled = compiledPage.compiled;
|
|
148
|
+
|
|
149
|
+
const afterUnmanagedHash = hashUnmanagedSchema(patched);
|
|
150
|
+
if (afterUnmanagedHash !== beforeUnmanagedHash) {
|
|
151
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Page patch changed unmanaged Schema content.');
|
|
152
|
+
}
|
|
153
|
+
const afterProjection = projectNativePageSchema({
|
|
154
|
+
observedFormType: observedIdentity.formType,
|
|
155
|
+
observedTitle: observedIdentity.title,
|
|
156
|
+
schema: patched,
|
|
157
|
+
shellProfile: input.shellProfile,
|
|
158
|
+
});
|
|
159
|
+
if (!isDeepStrictEqual(afterProjection.managed, desiredManaged)) {
|
|
160
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Page patch did not produce the desired managed content.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
baselineRemoteSchemaHash: beforeProjection.remoteSchemaHash,
|
|
165
|
+
managed: desiredManaged,
|
|
166
|
+
managedHash: afterProjection.managedHash,
|
|
167
|
+
preparedRemoteSchemaHash: afterProjection.remoteSchemaHash,
|
|
168
|
+
schema: patched,
|
|
169
|
+
unmanagedHash: beforeUnmanagedHash,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function compareNativePageWrite(input = {}) {
|
|
174
|
+
const observedIdentity = normalizeObservedIdentity(input);
|
|
175
|
+
const desiredIdentity = normalizeDesiredIdentity(input);
|
|
176
|
+
assertIdentityMatches(observedIdentity, desiredIdentity);
|
|
177
|
+
const compiledPage = requireCompiledPage(input.compiledPage);
|
|
178
|
+
const beforeSchema = normalizeSchema(input.beforeSchema);
|
|
179
|
+
const afterSchema = normalizeSchema(input.afterSchema);
|
|
180
|
+
projectNativePageSchema({
|
|
181
|
+
observedFormType: observedIdentity.formType,
|
|
182
|
+
observedTitle: observedIdentity.title,
|
|
183
|
+
schema: beforeSchema,
|
|
184
|
+
shellProfile: input.shellProfile,
|
|
185
|
+
});
|
|
186
|
+
const afterProjection = projectNativePageSchemaInternal({
|
|
187
|
+
observedFormType: observedIdentity.formType,
|
|
188
|
+
observedTitle: observedIdentity.title,
|
|
189
|
+
schema: afterSchema,
|
|
190
|
+
shellProfile: input.shellProfile,
|
|
191
|
+
}, false);
|
|
192
|
+
const desiredManaged = buildDesiredManaged(desiredIdentity.title, compiledPage);
|
|
193
|
+
const managedMatches = isDeepStrictEqual(afterProjection.managed, desiredManaged);
|
|
194
|
+
const beforeUnmanagedHash = hashUnmanagedSchema(beforeSchema);
|
|
195
|
+
const afterUnmanagedHash = hashUnmanagedSchema(afterSchema);
|
|
196
|
+
const unmanagedPreserved = beforeUnmanagedHash === afterUnmanagedHash;
|
|
197
|
+
return {
|
|
198
|
+
managedMatches,
|
|
199
|
+
projection: afterProjection,
|
|
200
|
+
shellProfileMatches: afterProjection.shellProfileMatches,
|
|
201
|
+
unexpectedDelta: !unmanagedPreserved,
|
|
202
|
+
unmanagedPreserved,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildDesiredManaged(title, compiledPage) {
|
|
207
|
+
return {
|
|
208
|
+
compiledHash: compiledPage.compiledHash,
|
|
209
|
+
formType: DISPLAY_FORM_TYPE,
|
|
210
|
+
profile: NATIVE_PAGE_PROFILE,
|
|
211
|
+
sourceHash: compiledPage.sourceHash,
|
|
212
|
+
title,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normalizeObservedIdentity(input) {
|
|
217
|
+
const title = normalizeTitle(
|
|
218
|
+
input.observedTitle,
|
|
219
|
+
'SCHEMA_PAGE_FOUNDATION_OBSERVED_IDENTITY_INVALID',
|
|
220
|
+
'Observed page title is invalid.'
|
|
221
|
+
);
|
|
222
|
+
const formType = normalizeFormType(
|
|
223
|
+
input.observedFormType,
|
|
224
|
+
'SCHEMA_PAGE_FOUNDATION_OBSERVED_IDENTITY_INVALID',
|
|
225
|
+
'Observed page form type is invalid.'
|
|
226
|
+
);
|
|
227
|
+
return { formType, title };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function normalizeDesiredIdentity(input) {
|
|
231
|
+
const title = normalizeTitle(
|
|
232
|
+
input.desiredTitle,
|
|
233
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
234
|
+
'Desired page title is invalid.'
|
|
235
|
+
);
|
|
236
|
+
const formType = input.desiredFormType === undefined
|
|
237
|
+
? DISPLAY_FORM_TYPE
|
|
238
|
+
: normalizeFormType(
|
|
239
|
+
input.desiredFormType,
|
|
240
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
241
|
+
'Desired page form type is invalid.'
|
|
242
|
+
);
|
|
243
|
+
if (formType !== DISPLAY_FORM_TYPE) {
|
|
244
|
+
foundationError(
|
|
245
|
+
'SCHEMA_PAGE_FOUNDATION_PROFILE_UNSUPPORTED',
|
|
246
|
+
'Desired page must use the display resource type.'
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return { formType, title };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function normalizeTitle(value, code, message) {
|
|
253
|
+
const title = typeof value === 'string' ? value.trim() : '';
|
|
254
|
+
if (!title || title.length > 256 || containsUnsafeText(title)) {
|
|
255
|
+
foundationError(code, message);
|
|
256
|
+
}
|
|
257
|
+
return title;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function normalizeFormType(value, code, message) {
|
|
261
|
+
const formType = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
262
|
+
if (!formType || formType.length > 64 || containsUnsafeText(formType)) {
|
|
263
|
+
foundationError(code, message);
|
|
264
|
+
}
|
|
265
|
+
return formType;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function assertIdentityMatches(observedIdentity, desiredIdentity) {
|
|
269
|
+
if (
|
|
270
|
+
observedIdentity.title !== desiredIdentity.title ||
|
|
271
|
+
observedIdentity.formType !== desiredIdentity.formType
|
|
272
|
+
) {
|
|
273
|
+
foundationError(
|
|
274
|
+
'SCHEMA_PAGE_FOUNDATION_CONFLICT',
|
|
275
|
+
'Observed page identity does not match desired identity.'
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function normalizeSchema(value) {
|
|
281
|
+
let candidate = value;
|
|
282
|
+
if (
|
|
283
|
+
candidate &&
|
|
284
|
+
typeof candidate === 'object' &&
|
|
285
|
+
!Array.isArray(candidate) &&
|
|
286
|
+
candidate.success === true &&
|
|
287
|
+
Object.prototype.hasOwnProperty.call(candidate, 'content')
|
|
288
|
+
) {
|
|
289
|
+
candidate = candidate.content;
|
|
290
|
+
}
|
|
291
|
+
if (typeof candidate === 'string') {
|
|
292
|
+
if (!candidate.trim()) {
|
|
293
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Page Schema content is empty.');
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
candidate = JSON.parse(candidate);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Page Schema content is invalid.');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
assertPlainObject(candidate);
|
|
302
|
+
return clonePlain(candidate);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function inspectNativeStructure(schema) {
|
|
306
|
+
if (
|
|
307
|
+
schema.schemaType !== 'superform' ||
|
|
308
|
+
typeof schema.schemaVersion !== 'string' ||
|
|
309
|
+
!schema.schemaVersion.startsWith('5') ||
|
|
310
|
+
!Array.isArray(schema.pages) ||
|
|
311
|
+
schema.pages.length !== 1
|
|
312
|
+
) {
|
|
313
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Page Schema root is invalid.');
|
|
314
|
+
}
|
|
315
|
+
const page = schema.pages[0];
|
|
316
|
+
assertPlainObject(page);
|
|
317
|
+
if (!Array.isArray(page.componentsTree)) {
|
|
318
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Page component tree is invalid.');
|
|
319
|
+
}
|
|
320
|
+
const components = collectComponents(page.componentsTree);
|
|
321
|
+
if (components.some(component => component.componentName === 'YidaCodeCanvas')) {
|
|
322
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_PROFILE_UNSUPPORTED', 'Canvas page Schema is unsupported.');
|
|
323
|
+
}
|
|
324
|
+
const pageComponents = components.filter(component => component.componentName === 'Page');
|
|
325
|
+
const jsxComponents = components.filter(component => component.componentName === 'Jsx');
|
|
326
|
+
if (pageComponents.length !== 1 || jsxComponents.length !== 1) {
|
|
327
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Native page component structure is ambiguous.');
|
|
328
|
+
}
|
|
329
|
+
return { jsxComponents, pageComponents };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function inspectActions(actions) {
|
|
333
|
+
if (actions === undefined) {
|
|
334
|
+
return { saved: false };
|
|
335
|
+
}
|
|
336
|
+
assertPlainObject(actions);
|
|
337
|
+
if (actions.module === undefined) {
|
|
338
|
+
return { saved: false };
|
|
339
|
+
}
|
|
340
|
+
assertPlainObject(actions.module);
|
|
341
|
+
const source = actions.module.source;
|
|
342
|
+
const compiled = actions.module.compiled;
|
|
343
|
+
const hasSource = typeof source === 'string' && source.length > 0;
|
|
344
|
+
const hasCompiled = typeof compiled === 'string' && compiled.length > 0;
|
|
345
|
+
if (!hasSource && !hasCompiled) {
|
|
346
|
+
return { saved: false };
|
|
347
|
+
}
|
|
348
|
+
if (!hasSource || !hasCompiled) {
|
|
349
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Native page actions are incomplete.');
|
|
350
|
+
}
|
|
351
|
+
return { compiled, saved: true, source };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function collectComponents(value, result = []) {
|
|
355
|
+
if (!value || typeof value !== 'object') {
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
if (Array.isArray(value)) {
|
|
359
|
+
for (const item of value) {
|
|
360
|
+
collectComponents(item, result);
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
if (typeof value.componentName === 'string') {
|
|
365
|
+
result.push(value);
|
|
366
|
+
}
|
|
367
|
+
for (const key of Object.keys(value).sort()) {
|
|
368
|
+
collectComponents(value[key], result);
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function requireShellProfile(value) {
|
|
374
|
+
if (
|
|
375
|
+
!value ||
|
|
376
|
+
typeof value !== 'object' ||
|
|
377
|
+
!SHELL_PROFILES.has(value) ||
|
|
378
|
+
value.contractVersion !== SHELL_PROFILE_CONTRACT_VERSION
|
|
379
|
+
) {
|
|
380
|
+
foundationError(
|
|
381
|
+
'SCHEMA_PAGE_FOUNDATION_SHELL_PROFILE_REQUIRED',
|
|
382
|
+
'An approved native page shell profile is required.'
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return value;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function computeShellFingerprint(schema) {
|
|
389
|
+
const candidate = clonePlain(schema);
|
|
390
|
+
stripManagedActionText(candidate);
|
|
391
|
+
return hashStable(normalizeShellFingerprintValue(candidate));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function normalizeShellFingerprintValue(value, pathSegments = [], inheritedComponentName = '') {
|
|
395
|
+
if (Array.isArray(value)) {
|
|
396
|
+
const normalized = value.map((item, index) => normalizeShellFingerprintValue(
|
|
397
|
+
item,
|
|
398
|
+
pathSegments.concat(String(index)),
|
|
399
|
+
inheritedComponentName
|
|
400
|
+
));
|
|
401
|
+
const collectionName = pathSegments[pathSegments.length - 1];
|
|
402
|
+
if (
|
|
403
|
+
COMPONENT_ORDER_KEYS.has(collectionName) &&
|
|
404
|
+
normalized.every(item => (
|
|
405
|
+
item &&
|
|
406
|
+
typeof item === 'object' &&
|
|
407
|
+
!Array.isArray(item) &&
|
|
408
|
+
typeof item.componentName === 'string'
|
|
409
|
+
))
|
|
410
|
+
) {
|
|
411
|
+
normalized.sort(compareCanonicalValues);
|
|
412
|
+
}
|
|
413
|
+
return normalized;
|
|
414
|
+
}
|
|
415
|
+
if (!value || typeof value !== 'object') {
|
|
416
|
+
return value;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const ownComponentName = typeof value.componentName === 'string'
|
|
420
|
+
? value.componentName
|
|
421
|
+
: '';
|
|
422
|
+
const componentName = ownComponentName || inheritedComponentName;
|
|
423
|
+
const result = {};
|
|
424
|
+
for (const key of Object.keys(value).sort()) {
|
|
425
|
+
if (isShellNeutralKey(value, key, pathSegments, ownComponentName, componentName)) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
result[key] = normalizeShellFingerprintValue(
|
|
429
|
+
value[key],
|
|
430
|
+
pathSegments.concat(key),
|
|
431
|
+
componentName
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function isShellNeutralKey(value, key, pathSegments, ownComponentName, componentName) {
|
|
438
|
+
if (SHELL_NEUTRAL_TIME_KEYS.has(key.toLowerCase())) {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
if (key === 'id' && ownComponentName) {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
if (
|
|
445
|
+
key === 'id' &&
|
|
446
|
+
pathSegments.length === 2 &&
|
|
447
|
+
pathSegments[0] === 'pages' &&
|
|
448
|
+
/^\d+$/.test(pathSegments[1])
|
|
449
|
+
) {
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
if (key === 'fieldId' && componentName === 'Jsx') {
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
return (
|
|
456
|
+
key === 'className' &&
|
|
457
|
+
componentName === 'Page' &&
|
|
458
|
+
typeof value[key] === 'string' &&
|
|
459
|
+
/^page_[a-z0-9]+$/i.test(value[key])
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function compareCanonicalValues(left, right) {
|
|
464
|
+
const leftValue = JSON.stringify(left);
|
|
465
|
+
const rightValue = JSON.stringify(right);
|
|
466
|
+
if (leftValue < rightValue) {
|
|
467
|
+
return -1;
|
|
468
|
+
}
|
|
469
|
+
if (leftValue > rightValue) {
|
|
470
|
+
return 1;
|
|
471
|
+
}
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function stripManagedActionText(schema) {
|
|
476
|
+
if (
|
|
477
|
+
!schema.actions ||
|
|
478
|
+
typeof schema.actions !== 'object' ||
|
|
479
|
+
Array.isArray(schema.actions) ||
|
|
480
|
+
!schema.actions.module ||
|
|
481
|
+
typeof schema.actions.module !== 'object' ||
|
|
482
|
+
Array.isArray(schema.actions.module)
|
|
483
|
+
) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
delete schema.actions.module.source;
|
|
487
|
+
delete schema.actions.module.compiled;
|
|
488
|
+
if (Object.keys(schema.actions.module).length === 0) {
|
|
489
|
+
delete schema.actions.module;
|
|
490
|
+
}
|
|
491
|
+
if (Object.keys(schema.actions).length === 0) {
|
|
492
|
+
delete schema.actions;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function hashUnmanagedSchema(schema) {
|
|
497
|
+
const unmanaged = clonePlain(schema);
|
|
498
|
+
stripManagedActionText(unmanaged);
|
|
499
|
+
return hashStable(unmanaged);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function hashRemoteSchema(schema) {
|
|
503
|
+
const normalized = clonePlain(schema);
|
|
504
|
+
delete normalized.gmtModified;
|
|
505
|
+
return hashStable(normalized);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function requireCompiledPage(value) {
|
|
509
|
+
if (!isCompiledNativePage(value)) {
|
|
510
|
+
foundationError('SCHEMA_PAGE_SOURCE_INVALID', 'Page patch requires trusted compiled native source.');
|
|
511
|
+
}
|
|
512
|
+
return value;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function hashStableText(value) {
|
|
516
|
+
return 'sha256:' + crypto.createHash('sha256').update(value).digest('hex');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function assertPlainObject(value) {
|
|
520
|
+
const prototype = value && typeof value === 'object' ? Object.getPrototypeOf(value) : undefined;
|
|
521
|
+
if (
|
|
522
|
+
!value ||
|
|
523
|
+
typeof value !== 'object' ||
|
|
524
|
+
Array.isArray(value) ||
|
|
525
|
+
(prototype !== Object.prototype && prototype !== null)
|
|
526
|
+
) {
|
|
527
|
+
foundationError('SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID', 'Page Schema value must be a plain object.');
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function containsUnsafeText(value) {
|
|
532
|
+
for (let index = 0; index < value.length; index++) {
|
|
533
|
+
const code = value.charCodeAt(index);
|
|
534
|
+
if ((code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127) {
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function clonePlain(value) {
|
|
542
|
+
return JSON.parse(JSON.stringify(value));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function foundationError(code, message) {
|
|
546
|
+
throw schemaError(code, message);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
module.exports = {
|
|
550
|
+
DISPLAY_FORM_TYPE,
|
|
551
|
+
buildNativePageSchemaPatch,
|
|
552
|
+
compareNativePageWrite,
|
|
553
|
+
computeNativePageShellFingerprint,
|
|
554
|
+
createNativePageShellProfile,
|
|
555
|
+
projectNativePageSchema,
|
|
556
|
+
};
|