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,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { schemaError } = require('./errors');
|
|
4
|
+
|
|
5
|
+
const MANIFEST_INPUT_LIMITS = Object.freeze({
|
|
6
|
+
maxBytes: 1024 * 1024,
|
|
7
|
+
maxDepth: 80,
|
|
8
|
+
maxNodes: 20000,
|
|
9
|
+
maxCollectionItems: 1000,
|
|
10
|
+
maxStringLength: 8192,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const MANIFEST_LIMIT_KEYS = Object.freeze(Object.keys(MANIFEST_INPUT_LIMITS));
|
|
14
|
+
|
|
15
|
+
function createManifestLimitState(options = {}) {
|
|
16
|
+
return {
|
|
17
|
+
limits: resolveManifestInputLimits(options),
|
|
18
|
+
nodes: 0,
|
|
19
|
+
seen: typeof WeakSet === 'function' ? new WeakSet() : null,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveManifestInputLimits(options = {}) {
|
|
24
|
+
const overrides = options.limits;
|
|
25
|
+
if (overrides === undefined) {
|
|
26
|
+
return Object.assign({}, MANIFEST_INPUT_LIMITS);
|
|
27
|
+
}
|
|
28
|
+
if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides)) {
|
|
29
|
+
throwInvalidLimit();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result = {};
|
|
33
|
+
const knownKeys = new Set(MANIFEST_LIMIT_KEYS);
|
|
34
|
+
for (const key of Object.keys(overrides)) {
|
|
35
|
+
if (!knownKeys.has(key)) {
|
|
36
|
+
throwInvalidLimit();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const key of MANIFEST_LIMIT_KEYS) {
|
|
40
|
+
const defaultValue = MANIFEST_INPUT_LIMITS[key];
|
|
41
|
+
if (!Object.prototype.hasOwnProperty.call(overrides, key)) {
|
|
42
|
+
result[key] = defaultValue;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const override = overrides[key];
|
|
46
|
+
if (!Number.isSafeInteger(override) || override <= 0) {
|
|
47
|
+
throwInvalidLimit();
|
|
48
|
+
}
|
|
49
|
+
result[key] = Math.min(defaultValue, override);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function throwInvalidLimit() {
|
|
55
|
+
throw schemaError('SCHEMA_MANIFEST_LIMIT_INVALID', 'Manifest input limit override is invalid.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function assertManifestFileSize(byteLength, options = {}) {
|
|
59
|
+
const limits = resolveManifestInputLimits(options);
|
|
60
|
+
if (!Number.isFinite(byteLength) || byteLength < 0) {
|
|
61
|
+
throw schemaError('SCHEMA_MANIFEST_LOAD_FAILED', 'Unable to read manifest file.');
|
|
62
|
+
}
|
|
63
|
+
if (byteLength > limits.maxBytes) {
|
|
64
|
+
throw schemaError('SCHEMA_MANIFEST_TOO_LARGE', 'Manifest file exceeds the maximum supported size.', {
|
|
65
|
+
path: '/',
|
|
66
|
+
details: {
|
|
67
|
+
limit: limits.maxBytes,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function assertManifestNode(state, pathSegments, depth) {
|
|
74
|
+
if (depth > state.limits.maxDepth) {
|
|
75
|
+
throw schemaError('SCHEMA_MANIFEST_DEPTH_LIMIT_EXCEEDED', 'Manifest JSON exceeds the maximum supported depth.', {
|
|
76
|
+
path: formatJsonPointer(pathSegments),
|
|
77
|
+
details: {
|
|
78
|
+
limit: state.limits.maxDepth,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
state.nodes += 1;
|
|
83
|
+
if (state.nodes > state.limits.maxNodes) {
|
|
84
|
+
throw schemaError('SCHEMA_MANIFEST_NODE_LIMIT_EXCEEDED', 'Manifest JSON exceeds the maximum supported node count.', {
|
|
85
|
+
path: formatJsonPointer(pathSegments),
|
|
86
|
+
details: {
|
|
87
|
+
limit: state.limits.maxNodes,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function assertManifestCollectionSize(state, size, pathSegments) {
|
|
94
|
+
if (size > state.limits.maxCollectionItems) {
|
|
95
|
+
throw schemaError('SCHEMA_MANIFEST_COLLECTION_LIMIT_EXCEEDED', 'Manifest JSON collection exceeds the maximum supported item count.', {
|
|
96
|
+
path: formatJsonPointer(pathSegments),
|
|
97
|
+
details: {
|
|
98
|
+
limit: state.limits.maxCollectionItems,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function assertManifestStringLength(state, value, pathSegments) {
|
|
105
|
+
if (String(value).length > state.limits.maxStringLength) {
|
|
106
|
+
throw schemaError('SCHEMA_MANIFEST_STRING_LIMIT_EXCEEDED', 'Manifest JSON string exceeds the maximum supported length.', {
|
|
107
|
+
path: formatJsonPointer(pathSegments),
|
|
108
|
+
details: {
|
|
109
|
+
limit: state.limits.maxStringLength,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function assertManifestObjectLimits(value, options = {}) {
|
|
116
|
+
const state = createManifestLimitState(options);
|
|
117
|
+
visit(value, state, [], 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function visit(value, state, pathSegments, depth) {
|
|
121
|
+
assertManifestNode(state, pathSegments, depth);
|
|
122
|
+
if (typeof value === 'string') {
|
|
123
|
+
assertManifestStringLength(state, value, pathSegments);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!value || typeof value !== 'object') {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (state.seen) {
|
|
130
|
+
if (state.seen.has(value)) {
|
|
131
|
+
throw schemaError('SCHEMA_MANIFEST_STRUCTURE_UNSUPPORTED', 'Manifest JSON object graph must be acyclic.', {
|
|
132
|
+
path: formatJsonPointer(pathSegments),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
state.seen.add(value);
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(value)) {
|
|
138
|
+
assertManifestCollectionSize(state, value.length, pathSegments);
|
|
139
|
+
value.forEach((item, index) => visit(item, state, pathSegments.concat(String(index)), depth + 1));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!isPlainJsonObject(value)) {
|
|
143
|
+
throw schemaError('SCHEMA_MANIFEST_STRUCTURE_UNSUPPORTED', 'Manifest JSON object graph must contain only plain objects and arrays.', {
|
|
144
|
+
path: formatJsonPointer(pathSegments),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const keys = Object.keys(value);
|
|
148
|
+
assertManifestCollectionSize(state, keys.length, pathSegments);
|
|
149
|
+
keys.forEach((key) => {
|
|
150
|
+
assertManifestStringLength(state, key, pathSegments.concat(key));
|
|
151
|
+
visit(value[key], state, pathSegments.concat(key), depth + 1);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isPlainJsonObject(value) {
|
|
156
|
+
const prototype = Object.getPrototypeOf(value);
|
|
157
|
+
return prototype === Object.prototype || prototype === null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function formatJsonPointer(segments) {
|
|
161
|
+
if (!segments || segments.length === 0) {
|
|
162
|
+
return '/';
|
|
163
|
+
}
|
|
164
|
+
return '/' + segments.map(segment => String(segment).replace(/~/g, '~0').replace(/\//g, '~1')).join('/');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = {
|
|
168
|
+
MANIFEST_INPUT_LIMITS,
|
|
169
|
+
assertManifestCollectionSize,
|
|
170
|
+
assertManifestFileSize,
|
|
171
|
+
assertManifestNode,
|
|
172
|
+
assertManifestObjectLimits,
|
|
173
|
+
assertManifestStringLength,
|
|
174
|
+
createManifestLimitState,
|
|
175
|
+
formatJsonPointer,
|
|
176
|
+
resolveManifestInputLimits,
|
|
177
|
+
};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { schemaError } = require('./errors');
|
|
6
|
+
const {
|
|
7
|
+
assertManifestCollectionSize,
|
|
8
|
+
assertManifestFileSize,
|
|
9
|
+
assertManifestNode,
|
|
10
|
+
assertManifestStringLength,
|
|
11
|
+
createManifestLimitState,
|
|
12
|
+
formatJsonPointer,
|
|
13
|
+
resolveManifestInputLimits,
|
|
14
|
+
} = require('./manifest-limits');
|
|
15
|
+
|
|
16
|
+
function loadManifest(manifestPath, options = {}) {
|
|
17
|
+
if (!manifestPath || typeof manifestPath !== 'string') {
|
|
18
|
+
throw schemaError('SCHEMA_INVALID_ARGUMENTS', 'Manifest path is required.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const resolved = resolveManifestPath(manifestPath, options);
|
|
22
|
+
const raw = readManifestFileBounded(resolved.realPath, options);
|
|
23
|
+
|
|
24
|
+
detectDuplicateJsonKeys(raw, options);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw schemaError('SCHEMA_MANIFEST_PARSE_FAILED', 'Manifest file must be valid JSON.', {
|
|
30
|
+
details: {
|
|
31
|
+
reason: error && error.message ? String(error.message).split('\n')[0] : 'Invalid JSON',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readManifestFileBounded(filePath, options = {}) {
|
|
38
|
+
const fsImpl = options.fsImpl || fs;
|
|
39
|
+
const limits = resolveManifestInputLimits(options);
|
|
40
|
+
const readLimit = limits.maxBytes + 1;
|
|
41
|
+
const flags = resolveOpenFlags(fsImpl);
|
|
42
|
+
let fd;
|
|
43
|
+
try {
|
|
44
|
+
fd = fsImpl.openSync(filePath, flags);
|
|
45
|
+
const stat = fsImpl.fstatSync(fd);
|
|
46
|
+
if (!stat || typeof stat.isFile !== 'function' || !stat.isFile()) {
|
|
47
|
+
throw schemaError('SCHEMA_MANIFEST_LOAD_FAILED', 'Unable to read manifest file.');
|
|
48
|
+
}
|
|
49
|
+
assertManifestFileSize(stat.size, { limits });
|
|
50
|
+
|
|
51
|
+
const buffer = Buffer.alloc(readLimit);
|
|
52
|
+
let totalRead = 0;
|
|
53
|
+
while (totalRead < readLimit) {
|
|
54
|
+
const bytesRead = fsImpl.readSync(fd, buffer, totalRead, readLimit - totalRead, null);
|
|
55
|
+
if (bytesRead === 0) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
totalRead += bytesRead;
|
|
59
|
+
if (totalRead > limits.maxBytes) {
|
|
60
|
+
throw schemaError('SCHEMA_MANIFEST_TOO_LARGE', 'Manifest file exceeds the maximum supported size.', {
|
|
61
|
+
path: '/',
|
|
62
|
+
details: { limit: limits.maxBytes },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return buffer.toString('utf8', 0, totalRead);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error && error.code && String(error.code).startsWith('SCHEMA_')) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
throw schemaError('SCHEMA_MANIFEST_LOAD_FAILED', 'Unable to read manifest file.');
|
|
72
|
+
} finally {
|
|
73
|
+
if (fd !== undefined) {
|
|
74
|
+
try {
|
|
75
|
+
fsImpl.closeSync(fd);
|
|
76
|
+
} catch {
|
|
77
|
+
// Best effort close: keep the original read/validation error stable.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveOpenFlags(fsImpl) {
|
|
84
|
+
const constants = fsImpl.constants || fs.constants || {};
|
|
85
|
+
const readOnly = constants.O_RDONLY !== undefined ? constants.O_RDONLY : 'r';
|
|
86
|
+
if (typeof readOnly === 'number' && typeof constants.O_NOFOLLOW === 'number') {
|
|
87
|
+
return readOnly | constants.O_NOFOLLOW;
|
|
88
|
+
}
|
|
89
|
+
return readOnly;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveManifestPath(manifestPath, options = {}) {
|
|
93
|
+
const workspaceRoot = path.resolve(options.workspaceRoot || options.projectRoot || process.cwd());
|
|
94
|
+
let rootRealPath;
|
|
95
|
+
try {
|
|
96
|
+
rootRealPath = fs.realpathSync(workspaceRoot);
|
|
97
|
+
} catch {
|
|
98
|
+
throw schemaError('SCHEMA_MANIFEST_PATH_UNSAFE', 'Manifest path must stay inside the workspace trust root.', {
|
|
99
|
+
path: '/',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const absolutePath = path.isAbsolute(manifestPath)
|
|
104
|
+
? path.resolve(manifestPath)
|
|
105
|
+
: path.resolve(rootRealPath, manifestPath);
|
|
106
|
+
const lexicallyInside = isPathInside(rootRealPath, absolutePath) ||
|
|
107
|
+
(path.isAbsolute(manifestPath) && isPathInside(workspaceRoot, absolutePath));
|
|
108
|
+
|
|
109
|
+
let realPath;
|
|
110
|
+
try {
|
|
111
|
+
realPath = fs.realpathSync(absolutePath);
|
|
112
|
+
} catch {
|
|
113
|
+
if (!lexicallyInside) {
|
|
114
|
+
throw schemaError('SCHEMA_MANIFEST_PATH_UNSAFE', 'Manifest path must stay inside the workspace trust root.', {
|
|
115
|
+
path: '/',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
throw schemaError('SCHEMA_MANIFEST_LOAD_FAILED', 'Unable to read manifest file.');
|
|
119
|
+
}
|
|
120
|
+
if (!isPathInside(rootRealPath, realPath)) {
|
|
121
|
+
throw schemaError('SCHEMA_MANIFEST_PATH_UNSAFE', 'Manifest path must stay inside the workspace trust root.', {
|
|
122
|
+
path: '/',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
realPath,
|
|
128
|
+
rootRealPath,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isPathInside(rootPath, targetPath) {
|
|
133
|
+
const relative = path.relative(rootPath, targetPath);
|
|
134
|
+
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function detectDuplicateJsonKeys(source, options = {}) {
|
|
138
|
+
let index = 0;
|
|
139
|
+
const limitState = createManifestLimitState(options);
|
|
140
|
+
|
|
141
|
+
function skipWhitespace() {
|
|
142
|
+
while (index < source.length && /\s/.test(source[index])) {
|
|
143
|
+
index += 1;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseString(pathSegments) {
|
|
148
|
+
let result = '';
|
|
149
|
+
index += 1;
|
|
150
|
+
while (index < source.length) {
|
|
151
|
+
const char = source[index];
|
|
152
|
+
if (char === '"') {
|
|
153
|
+
index += 1;
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
if (char === '\\') {
|
|
157
|
+
index += 1;
|
|
158
|
+
if (index >= source.length) {
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
const escaped = source[index];
|
|
162
|
+
if (escaped === 'u') {
|
|
163
|
+
const hex = source.slice(index + 1, index + 5);
|
|
164
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
165
|
+
result += String.fromCharCode(parseInt(hex, 16));
|
|
166
|
+
index += 5;
|
|
167
|
+
} else {
|
|
168
|
+
index += 1;
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const escapeMap = {
|
|
173
|
+
'"': '"',
|
|
174
|
+
'\\': '\\',
|
|
175
|
+
'/': '/',
|
|
176
|
+
b: '\b',
|
|
177
|
+
f: '\f',
|
|
178
|
+
n: '\n',
|
|
179
|
+
r: '\r',
|
|
180
|
+
t: '\t',
|
|
181
|
+
};
|
|
182
|
+
result += Object.prototype.hasOwnProperty.call(escapeMap, escaped) ? escapeMap[escaped] : escaped;
|
|
183
|
+
assertManifestStringLength(limitState, result, pathSegments);
|
|
184
|
+
index += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
result += char;
|
|
188
|
+
assertManifestStringLength(limitState, result, pathSegments);
|
|
189
|
+
index += 1;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function parseNumber() {
|
|
195
|
+
const start = index;
|
|
196
|
+
while (index < source.length && /[-+0-9.eE]/.test(source[index])) {
|
|
197
|
+
index += 1;
|
|
198
|
+
}
|
|
199
|
+
if (index === start) {
|
|
200
|
+
index += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseLiteral(value) {
|
|
205
|
+
if (source.slice(index, index + value.length) === value) {
|
|
206
|
+
index += value.length;
|
|
207
|
+
} else {
|
|
208
|
+
index += 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function parseArray(pathSegments, depth) {
|
|
213
|
+
index += 1;
|
|
214
|
+
let itemIndex = 0;
|
|
215
|
+
skipWhitespace();
|
|
216
|
+
if (source[index] === ']') {
|
|
217
|
+
index += 1;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
while (index < source.length) {
|
|
221
|
+
if (itemIndex + 1 > limitState.limits.maxCollectionItems) {
|
|
222
|
+
assertManifestCollectionSize(limitState, itemIndex + 1, pathSegments);
|
|
223
|
+
}
|
|
224
|
+
parseValue(pathSegments.concat(String(itemIndex)), depth + 1);
|
|
225
|
+
itemIndex += 1;
|
|
226
|
+
skipWhitespace();
|
|
227
|
+
if (source[index] === ',') {
|
|
228
|
+
index += 1;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (source[index] === ']') {
|
|
232
|
+
index += 1;
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function parseObject(pathSegments, depth) {
|
|
239
|
+
index += 1;
|
|
240
|
+
const keys = new Set();
|
|
241
|
+
skipWhitespace();
|
|
242
|
+
if (source[index] === '}') {
|
|
243
|
+
index += 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
while (index < source.length) {
|
|
247
|
+
skipWhitespace();
|
|
248
|
+
if (source[index] !== '"') {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const key = parseString(pathSegments);
|
|
252
|
+
assertManifestStringLength(limitState, key, pathSegments.concat(key));
|
|
253
|
+
if (keys.has(key)) {
|
|
254
|
+
throw schemaError('SCHEMA_DUPLICATE_KEY', 'Manifest contains duplicate JSON object keys.', {
|
|
255
|
+
path: formatJsonPointer(pathSegments.concat(key)),
|
|
256
|
+
details: { key },
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
keys.add(key);
|
|
260
|
+
assertManifestCollectionSize(limitState, keys.size, pathSegments);
|
|
261
|
+
skipWhitespace();
|
|
262
|
+
if (source[index] !== ':') {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
index += 1;
|
|
266
|
+
parseValue(pathSegments.concat(key), depth + 1);
|
|
267
|
+
skipWhitespace();
|
|
268
|
+
if (source[index] === ',') {
|
|
269
|
+
index += 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (source[index] === '}') {
|
|
273
|
+
index += 1;
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function parseValue(pathSegments, depth) {
|
|
280
|
+
skipWhitespace();
|
|
281
|
+
assertManifestNode(limitState, pathSegments, depth);
|
|
282
|
+
const char = source[index];
|
|
283
|
+
if (char === '{') {
|
|
284
|
+
parseObject(pathSegments, depth);
|
|
285
|
+
} else if (char === '[') {
|
|
286
|
+
parseArray(pathSegments, depth);
|
|
287
|
+
} else if (char === '"') {
|
|
288
|
+
parseString(pathSegments);
|
|
289
|
+
} else if (char === 't') {
|
|
290
|
+
parseLiteral('true');
|
|
291
|
+
} else if (char === 'f') {
|
|
292
|
+
parseLiteral('false');
|
|
293
|
+
} else if (char === 'n') {
|
|
294
|
+
parseLiteral('null');
|
|
295
|
+
} else {
|
|
296
|
+
parseNumber();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
parseValue([], 0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
detectDuplicateJsonKeys,
|
|
305
|
+
isPathInside,
|
|
306
|
+
loadManifest,
|
|
307
|
+
readManifestFileBounded,
|
|
308
|
+
resolveManifestPath,
|
|
309
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://openyida.ai/schemas/openyida-app-manifest-v1.json",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["kind", "schemaVersion", "app"],
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"kind": {
|
|
9
|
+
"const": "openyida_app_manifest"
|
|
10
|
+
},
|
|
11
|
+
"schemaVersion": {
|
|
12
|
+
"const": 1
|
|
13
|
+
},
|
|
14
|
+
"app": {
|
|
15
|
+
"$ref": "#/definitions/app"
|
|
16
|
+
},
|
|
17
|
+
"forms": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": {
|
|
20
|
+
"$ref": "#/definitions/form"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"processes": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": {
|
|
26
|
+
"$ref": "#/definitions/process"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"pages": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": {
|
|
32
|
+
"$ref": "#/definitions/page"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"definitions": {
|
|
37
|
+
"app": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["key", "name"],
|
|
40
|
+
"additionalProperties": false,
|
|
41
|
+
"properties": {
|
|
42
|
+
"key": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"minLength": 1
|
|
45
|
+
},
|
|
46
|
+
"name": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"minLength": 1
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"form": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"required": ["title", "fields"],
|
|
55
|
+
"additionalProperties": false,
|
|
56
|
+
"properties": {
|
|
57
|
+
"title": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"minLength": 1
|
|
60
|
+
},
|
|
61
|
+
"mode": {
|
|
62
|
+
"enum": ["receipt", "process"]
|
|
63
|
+
},
|
|
64
|
+
"dependsOn": {
|
|
65
|
+
"$ref": "#/definitions/dependencyList"
|
|
66
|
+
},
|
|
67
|
+
"fields": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"minProperties": 1,
|
|
70
|
+
"additionalProperties": {
|
|
71
|
+
"$ref": "#/definitions/field"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"field": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"required": ["type", "label"],
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"properties": {
|
|
81
|
+
"type": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"minLength": 1
|
|
84
|
+
},
|
|
85
|
+
"label": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"minLength": 1
|
|
88
|
+
},
|
|
89
|
+
"required": {
|
|
90
|
+
"type": "boolean"
|
|
91
|
+
},
|
|
92
|
+
"options": {
|
|
93
|
+
"type": "array",
|
|
94
|
+
"items": {
|
|
95
|
+
"oneOf": [
|
|
96
|
+
{ "type": "string", "minLength": 1 },
|
|
97
|
+
{
|
|
98
|
+
"type": "object",
|
|
99
|
+
"required": ["label", "value"],
|
|
100
|
+
"additionalProperties": false,
|
|
101
|
+
"properties": {
|
|
102
|
+
"label": { "type": "string", "minLength": 1 },
|
|
103
|
+
"value": { "type": "string", "minLength": 1 }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"form": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"minLength": 1
|
|
112
|
+
},
|
|
113
|
+
"dependsOn": {
|
|
114
|
+
"$ref": "#/definitions/dependencyList"
|
|
115
|
+
},
|
|
116
|
+
"children": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"minProperties": 1,
|
|
119
|
+
"additionalProperties": {
|
|
120
|
+
"$ref": "#/definitions/field"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"process": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"required": ["form", "nodes"],
|
|
128
|
+
"additionalProperties": false,
|
|
129
|
+
"properties": {
|
|
130
|
+
"form": {
|
|
131
|
+
"type": "string",
|
|
132
|
+
"minLength": 1
|
|
133
|
+
},
|
|
134
|
+
"nodes": {
|
|
135
|
+
"type": "array",
|
|
136
|
+
"minItems": 1,
|
|
137
|
+
"items": {
|
|
138
|
+
"$ref": "#/definitions/processNode"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"page": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"required": ["title", "source"],
|
|
146
|
+
"additionalProperties": false,
|
|
147
|
+
"properties": {
|
|
148
|
+
"title": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"minLength": 1
|
|
151
|
+
},
|
|
152
|
+
"source": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"minLength": 1
|
|
155
|
+
},
|
|
156
|
+
"dependsOn": {
|
|
157
|
+
"$ref": "#/definitions/dependencyList"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"processNode": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"required": ["key", "type", "name", "approver"],
|
|
164
|
+
"additionalProperties": false,
|
|
165
|
+
"properties": {
|
|
166
|
+
"key": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"minLength": 1
|
|
169
|
+
},
|
|
170
|
+
"type": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"minLength": 1
|
|
173
|
+
},
|
|
174
|
+
"name": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"minLength": 1
|
|
177
|
+
},
|
|
178
|
+
"approver": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"minLength": 1
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"dependencyList": {
|
|
185
|
+
"type": "array",
|
|
186
|
+
"items": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"minLength": 1
|
|
189
|
+
},
|
|
190
|
+
"uniqueItems": true
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|