openyida 2026.7.19 → 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 +14 -0
- package/bin/yida.js +107 -18
- package/lib/app/create-app.js +12 -1
- package/lib/app/create-form/args.js +13 -0
- package/lib/app/create-form.js +154 -67
- package/lib/app/create-page.js +45 -9
- package/lib/app/get-schema.js +65 -14
- package/lib/app/import-app.js +92 -41
- 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 +18 -6
- package/lib/auth/token-auth.js +34 -3
- package/lib/auth/token-store.js +22 -4
- package/lib/bridge/bridge.js +37 -5
- package/lib/core/agent-capabilities.js +5 -3
- package/lib/core/command-manifest.js +40 -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/legacy-schema-guard.js +408 -0
- package/lib/core/locales/en.js +8 -46
- package/lib/core/locales/zh.js +8 -46
- package/lib/core/sample.js +1 -1
- package/lib/core/utils.js +334 -66
- package/lib/core/yida-client.js +104 -3
- package/lib/formula/evaluate.js +8 -66
- package/lib/formula/field-refs.js +83 -0
- package/lib/process/configure-process.js +44 -1992
- package/lib/process/create-process.js +54 -87
- 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/data-management.canvas.jsx +228 -14
- package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
- 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 +2 -2
- package/scripts/postinstall.js +8 -12
- package/scripts/validate-package-size.js +1 -1
- package/yida-skills/SKILL.md +114 -29
- 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 +60 -14
- 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 +7 -1
- 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 +7 -1
- package/yida-skills/skills-index.json +14 -46
- 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,589 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const { schemaError } = require('./errors');
|
|
7
|
+
const { createDefaultRegistry } = require('./resource-registry');
|
|
8
|
+
const { compareCodePoints } = require('./sort');
|
|
9
|
+
const { canonicalizeKey, stableStringify } = require('./normalize-manifest');
|
|
10
|
+
|
|
11
|
+
const STATE_KIND = 'openyida_resource_state';
|
|
12
|
+
const STATE_CONTRACT_VERSION = 1;
|
|
13
|
+
const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
14
|
+
const ATOMIC_NONCE_PATTERN = /^[a-f0-9]{24}$/;
|
|
15
|
+
const ENVIRONMENT_SCOPE_TOKEN = 'openyida-schema-unscoped-internal';
|
|
16
|
+
|
|
17
|
+
const SENSITIVE_STATE_KEY_CANONICALS = new Set([
|
|
18
|
+
'apikey',
|
|
19
|
+
'authorization',
|
|
20
|
+
'clientsecret',
|
|
21
|
+
'cookie',
|
|
22
|
+
'cookies',
|
|
23
|
+
'credential',
|
|
24
|
+
'credentials',
|
|
25
|
+
'csrf',
|
|
26
|
+
'csrftoken',
|
|
27
|
+
'header',
|
|
28
|
+
'headers',
|
|
29
|
+
'internalpath',
|
|
30
|
+
'internalurl',
|
|
31
|
+
'password',
|
|
32
|
+
'refreshtoken',
|
|
33
|
+
'secret',
|
|
34
|
+
'token',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const REAL_ID_KEY_CANONICALS = new Set([
|
|
38
|
+
'apptype',
|
|
39
|
+
'fieldid',
|
|
40
|
+
'formuuid',
|
|
41
|
+
'nodeid',
|
|
42
|
+
'processcode',
|
|
43
|
+
'processid',
|
|
44
|
+
'processversion',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
function resolveDefaultStatePath(cwd) {
|
|
48
|
+
return path.join(cwd || process.cwd(), '.cache', 'openyida', 'state.v1.json');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createSha256(value) {
|
|
52
|
+
return 'sha256:' + crypto
|
|
53
|
+
.createHash('sha256')
|
|
54
|
+
.update(String(value === undefined || value === null ? '' : value))
|
|
55
|
+
.digest('hex');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hashStable(value) {
|
|
59
|
+
return createSha256(stableStringify(value));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createEnvironmentIdentity(input = {}, options = {}) {
|
|
63
|
+
if (input && input.environmentKey && input.corpIdHash) {
|
|
64
|
+
assertHash(input.environmentKey, 'environmentKey', '/environment/environmentKey');
|
|
65
|
+
assertHash(input.corpIdHash, 'corpIdHash', '/environment/corpIdHash');
|
|
66
|
+
return {
|
|
67
|
+
environmentKey: input.environmentKey,
|
|
68
|
+
corpIdHash: input.corpIdHash,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (options.allowUnscopedEnvironment === true) {
|
|
73
|
+
return {
|
|
74
|
+
environmentKey: createSha256(`${ENVIRONMENT_SCOPE_TOKEN}:environment`),
|
|
75
|
+
corpIdHash: createSha256(`${ENVIRONMENT_SCOPE_TOKEN}:corp`),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const endpoint = input && (input.endpoint !== undefined ? input.endpoint : input.baseUrl);
|
|
80
|
+
const corpId = input && input.corpId;
|
|
81
|
+
if (!hasNonEmptyString(endpoint) || !hasNonEmptyString(corpId)) {
|
|
82
|
+
throw schemaError('SCHEMA_STATE_ENVIRONMENT_REQUIRED', 'State operations require endpoint/environmentKey and corpId/corpIdHash.', {
|
|
83
|
+
path: '/environment',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
environmentKey: createSha256(normalizeEndpoint(endpoint)),
|
|
89
|
+
corpIdHash: createSha256(corpId),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createEmptyState(environmentInput, options = {}) {
|
|
94
|
+
const environment = createEnvironmentIdentity(environmentInput, options);
|
|
95
|
+
const state = {
|
|
96
|
+
kind: STATE_KIND,
|
|
97
|
+
contractVersion: STATE_CONTRACT_VERSION,
|
|
98
|
+
revision: 0,
|
|
99
|
+
environment,
|
|
100
|
+
resources: {},
|
|
101
|
+
};
|
|
102
|
+
if (options.manifestHash !== undefined) {
|
|
103
|
+
assertHash(options.manifestHash, 'manifestHash', '/manifestHash');
|
|
104
|
+
state.manifestHash = options.manifestHash;
|
|
105
|
+
}
|
|
106
|
+
validateStateShape(state, {
|
|
107
|
+
registry: options.registry,
|
|
108
|
+
});
|
|
109
|
+
return state;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readState(statePath, options = {}) {
|
|
113
|
+
const fsImpl = options.fsImpl || fs;
|
|
114
|
+
const environment = createEnvironmentIdentity(options.environment, options);
|
|
115
|
+
|
|
116
|
+
let exists;
|
|
117
|
+
try {
|
|
118
|
+
exists = fsImpl.existsSync(statePath);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw schemaError('SCHEMA_STATE_READ_FAILED', 'State file could not be read.', {
|
|
121
|
+
details: { operation: 'exists' },
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!exists) {
|
|
126
|
+
return createEmptyState(environment, {
|
|
127
|
+
manifestHash: options.manifestHash,
|
|
128
|
+
registry: options.registry,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let raw;
|
|
133
|
+
try {
|
|
134
|
+
raw = fsImpl.readFileSync(statePath, 'utf8');
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw schemaError('SCHEMA_STATE_READ_FAILED', 'State file could not be read.', {
|
|
137
|
+
details: { operation: 'read' },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let parsed;
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(raw);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State file is not valid JSON.', {
|
|
146
|
+
details: { reason: 'invalid_json' },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
validateState(parsed, {
|
|
151
|
+
environment,
|
|
152
|
+
registry: options.registry,
|
|
153
|
+
});
|
|
154
|
+
return parsed;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function writeStateAtomic(statePath, state, options = {}) {
|
|
158
|
+
const fsImpl = options.fsImpl || fs;
|
|
159
|
+
const environment = createEnvironmentIdentity(options.environment, options);
|
|
160
|
+
const nextState = prepareStateForWrite(state, {
|
|
161
|
+
environment,
|
|
162
|
+
incrementRevision: options.incrementRevision,
|
|
163
|
+
registry: options.registry,
|
|
164
|
+
});
|
|
165
|
+
const dir = path.dirname(statePath);
|
|
166
|
+
const data = stableStringify(nextState) + '\n';
|
|
167
|
+
let fd;
|
|
168
|
+
let tmpPath;
|
|
169
|
+
let tempCreated = false;
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
tmpPath = createAtomicTempPath(statePath, options);
|
|
173
|
+
fsImpl.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
174
|
+
fd = fsImpl.openSync(tmpPath, 'wx', 0o600);
|
|
175
|
+
tempCreated = true;
|
|
176
|
+
fsImpl.writeFileSync(fd, data, 'utf8');
|
|
177
|
+
if (typeof fsImpl.fsyncSync === 'function') {
|
|
178
|
+
fsImpl.fsyncSync(fd);
|
|
179
|
+
}
|
|
180
|
+
fsImpl.closeSync(fd);
|
|
181
|
+
fd = undefined;
|
|
182
|
+
fsImpl.renameSync(tmpPath, statePath);
|
|
183
|
+
fsyncDirectoryBestEffort(fsImpl, dir);
|
|
184
|
+
return nextState;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (fd !== undefined) {
|
|
187
|
+
try {
|
|
188
|
+
fsImpl.closeSync(fd);
|
|
189
|
+
} catch (closeError) {
|
|
190
|
+
// Best effort cleanup.
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
if (tempCreated && tmpPath && fsImpl.existsSync(tmpPath)) {
|
|
195
|
+
fsImpl.unlinkSync(tmpPath);
|
|
196
|
+
}
|
|
197
|
+
} catch (unlinkError) {
|
|
198
|
+
// Best effort cleanup.
|
|
199
|
+
}
|
|
200
|
+
throw schemaError('SCHEMA_STATE_WRITE_FAILED', 'State file could not be written atomically.', {
|
|
201
|
+
details: { operation: 'atomic_write' },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function createAtomicTempPath(targetPath, options = {}) {
|
|
207
|
+
const dir = path.dirname(targetPath);
|
|
208
|
+
const base = path.basename(targetPath);
|
|
209
|
+
const pid = options.atomicProcessId === undefined ? process.pid : options.atomicProcessId;
|
|
210
|
+
const now = options.atomicNow === undefined ? Date.now() : options.atomicNow;
|
|
211
|
+
const nonce = options.atomicNonce || crypto.randomBytes(12).toString('hex');
|
|
212
|
+
if (
|
|
213
|
+
!Number.isInteger(pid) || pid <= 0 ||
|
|
214
|
+
!Number.isSafeInteger(now) || now < 0 ||
|
|
215
|
+
!ATOMIC_NONCE_PATTERN.test(nonce)
|
|
216
|
+
) {
|
|
217
|
+
throw new Error('invalid atomic temp identity');
|
|
218
|
+
}
|
|
219
|
+
return path.join(dir, `.${base}.${pid}.${now}.${nonce}.tmp`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function prepareStateForWrite(state, options = {}) {
|
|
223
|
+
const nextState = clonePlain(state);
|
|
224
|
+
validateState(nextState, {
|
|
225
|
+
environment: options.environment,
|
|
226
|
+
registry: options.registry,
|
|
227
|
+
});
|
|
228
|
+
nextState.revision += options.incrementRevision === false ? 0 : 1;
|
|
229
|
+
validateState(nextState, {
|
|
230
|
+
environment: options.environment,
|
|
231
|
+
registry: options.registry,
|
|
232
|
+
});
|
|
233
|
+
return nextState;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function validateState(state, options = {}) {
|
|
237
|
+
const environment = options.environment
|
|
238
|
+
? createEnvironmentIdentity(options.environment, options)
|
|
239
|
+
: createEnvironmentIdentity(options.environmentInput, options);
|
|
240
|
+
validateStateShape(state, {
|
|
241
|
+
registry: options.registry,
|
|
242
|
+
});
|
|
243
|
+
assertEnvironmentMatches(state.environment, environment);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function validateStateShape(state, options = {}) {
|
|
247
|
+
const registry = options.registry || createDefaultRegistry();
|
|
248
|
+
if (!state || typeof state !== 'object' || Array.isArray(state)) {
|
|
249
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State file must be a JSON object.');
|
|
250
|
+
}
|
|
251
|
+
validateForbiddenStateKeys(state);
|
|
252
|
+
assertAllowedKeys(state, ['kind', 'contractVersion', 'revision', 'environment', 'manifestHash', 'resources'], '/');
|
|
253
|
+
|
|
254
|
+
if (state.kind !== STATE_KIND) {
|
|
255
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State kind must be "openyida_resource_state".', {
|
|
256
|
+
path: '/kind',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (state.contractVersion !== STATE_CONTRACT_VERSION) {
|
|
260
|
+
throw schemaError('SCHEMA_STATE_VERSION_UNSUPPORTED', 'State contractVersion is not supported.', {
|
|
261
|
+
path: '/contractVersion',
|
|
262
|
+
details: { contractVersion: state.contractVersion },
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (!Number.isInteger(state.revision) || state.revision < 0) {
|
|
266
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State revision must be a non-negative integer.', {
|
|
267
|
+
path: '/revision',
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
validateEnvironmentShape(state.environment);
|
|
271
|
+
if (state.manifestHash !== undefined) {
|
|
272
|
+
assertHash(state.manifestHash, 'manifestHash', '/manifestHash');
|
|
273
|
+
}
|
|
274
|
+
validateResourcesShape(state.resources, registry);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function upsertResourceState(state, resourceState, options = {}) {
|
|
278
|
+
const nextState = clonePlain(state);
|
|
279
|
+
validateStateShape(nextState, { registry: options.registry });
|
|
280
|
+
|
|
281
|
+
const resourceType = resourceState && resourceState.resourceType;
|
|
282
|
+
const key = resourceState && resourceState.key;
|
|
283
|
+
if (!resourceType || !key) {
|
|
284
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'Resource state must include resourceType and key.');
|
|
285
|
+
}
|
|
286
|
+
if (!nextState.resources[resourceType]) {
|
|
287
|
+
nextState.resources[resourceType] = {};
|
|
288
|
+
}
|
|
289
|
+
nextState.resources[resourceType][key] = buildPersistedResourceState(resourceState);
|
|
290
|
+
validateStateShape(nextState, { registry: options.registry });
|
|
291
|
+
return nextState;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function getResourceState(state, resourceType, key) {
|
|
295
|
+
return state &&
|
|
296
|
+
state.resources &&
|
|
297
|
+
state.resources[resourceType] &&
|
|
298
|
+
state.resources[resourceType][key] || null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function buildPersistedResourceState(resourceState) {
|
|
302
|
+
const persisted = {
|
|
303
|
+
adapterVersion: resourceState.adapterVersion,
|
|
304
|
+
bindings: clonePlain(resourceState.bindings || {}),
|
|
305
|
+
};
|
|
306
|
+
if (resourceState.lastAppliedHash !== undefined) {
|
|
307
|
+
assertHash(resourceState.lastAppliedHash, 'lastAppliedHash');
|
|
308
|
+
persisted.lastAppliedHash = resourceState.lastAppliedHash;
|
|
309
|
+
}
|
|
310
|
+
if (resourceState.observedManagedHash !== undefined) {
|
|
311
|
+
assertHash(resourceState.observedManagedHash, 'observedManagedHash');
|
|
312
|
+
persisted.observedManagedHash = resourceState.observedManagedHash;
|
|
313
|
+
}
|
|
314
|
+
if (resourceState.remoteSchemaHash !== undefined) {
|
|
315
|
+
assertHash(resourceState.remoteSchemaHash, 'remoteSchemaHash');
|
|
316
|
+
persisted.remoteSchemaHash = resourceState.remoteSchemaHash;
|
|
317
|
+
}
|
|
318
|
+
if (resourceState.lastApplied !== undefined) {
|
|
319
|
+
persisted.lastApplied = clonePlain(resourceState.lastApplied);
|
|
320
|
+
}
|
|
321
|
+
return persisted;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function validateResourcesShape(resources, registry) {
|
|
325
|
+
if (!resources || typeof resources !== 'object' || Array.isArray(resources)) {
|
|
326
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State resources must be an object.', {
|
|
327
|
+
path: '/resources',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
for (const resourceType of Object.keys(resources)) {
|
|
332
|
+
const adapter = registry.get(resourceType);
|
|
333
|
+
const resourceMap = resources[resourceType];
|
|
334
|
+
const resourceMapPath = `/resources/${escapePointer(resourceType)}`;
|
|
335
|
+
if (!resourceMap || typeof resourceMap !== 'object' || Array.isArray(resourceMap)) {
|
|
336
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State resource type entries must be objects.', {
|
|
337
|
+
path: resourceMapPath,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
for (const key of Object.keys(resourceMap)) {
|
|
341
|
+
validateResourceEntry(resourceMap[key], {
|
|
342
|
+
adapter,
|
|
343
|
+
key,
|
|
344
|
+
path: `${resourceMapPath}/${escapePointer(key)}`,
|
|
345
|
+
resourceType,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function validateResourceEntry(entry, context) {
|
|
352
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
353
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State resource entry must be an object.', {
|
|
354
|
+
path: context.path,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
assertAllowedKeys(entry, [
|
|
358
|
+
'adapterVersion',
|
|
359
|
+
'bindings',
|
|
360
|
+
'lastAppliedHash',
|
|
361
|
+
'observedManagedHash',
|
|
362
|
+
'remoteSchemaHash',
|
|
363
|
+
'lastApplied',
|
|
364
|
+
], context.path);
|
|
365
|
+
|
|
366
|
+
if (!Number.isInteger(entry.adapterVersion) || entry.adapterVersion < 1) {
|
|
367
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State resource adapterVersion must be a supported integer.', {
|
|
368
|
+
path: `${context.path}/adapterVersion`,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (entry.adapterVersion !== context.adapter.adapterVersion) {
|
|
372
|
+
throw schemaError('SCHEMA_STATE_ADAPTER_VERSION_UNSUPPORTED', 'State resource adapterVersion is not supported by the current adapter.', {
|
|
373
|
+
path: `${context.path}/adapterVersion`,
|
|
374
|
+
details: {
|
|
375
|
+
resourceType: context.resourceType,
|
|
376
|
+
key: context.key,
|
|
377
|
+
stateAdapterVersion: entry.adapterVersion,
|
|
378
|
+
adapterVersion: context.adapter.adapterVersion,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (!entry.bindings || typeof entry.bindings !== 'object' || Array.isArray(entry.bindings)) {
|
|
383
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State resource bindings must be an object.', {
|
|
384
|
+
path: `${context.path}/bindings`,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
for (const hashKey of ['lastAppliedHash', 'observedManagedHash', 'remoteSchemaHash']) {
|
|
388
|
+
if (entry[hashKey] !== undefined) {
|
|
389
|
+
assertHash(entry[hashKey], hashKey, `${context.path}/${hashKey}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (entry.lastApplied !== undefined && (!entry.lastApplied || typeof entry.lastApplied !== 'object' || Array.isArray(entry.lastApplied))) {
|
|
393
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State lastApplied must be a compact managed object.', {
|
|
394
|
+
path: `${context.path}/lastApplied`,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (typeof context.adapter.validateStateResource === 'function') {
|
|
398
|
+
context.adapter.validateStateResource({
|
|
399
|
+
key: context.key,
|
|
400
|
+
resourceType: context.resourceType,
|
|
401
|
+
state: entry,
|
|
402
|
+
path: context.path,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function validateEnvironmentShape(environment) {
|
|
408
|
+
if (!environment || typeof environment !== 'object' || Array.isArray(environment)) {
|
|
409
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State environment must be an object.', {
|
|
410
|
+
path: '/environment',
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
assertAllowedKeys(environment, ['environmentKey', 'corpIdHash'], '/environment');
|
|
414
|
+
assertHash(environment.environmentKey, 'environmentKey', '/environment/environmentKey');
|
|
415
|
+
assertHash(environment.corpIdHash, 'corpIdHash', '/environment/corpIdHash');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function validateForbiddenStateKeys(value, pathSegments = []) {
|
|
419
|
+
if (!value || typeof value !== 'object') {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (Array.isArray(value)) {
|
|
423
|
+
value.forEach((item, index) => validateForbiddenStateKeys(item, pathSegments.concat(String(index))));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
for (const [key, child] of Object.entries(value)) {
|
|
428
|
+
const canonical = canonicalizeKey(key);
|
|
429
|
+
if (SENSITIVE_STATE_KEY_CANONICALS.has(canonical) || isForbiddenRealIdLocation(pathSegments, key, canonical)) {
|
|
430
|
+
throw schemaError('SCHEMA_STATE_FORBIDDEN_FIELD', 'State must not contain credentials, internal paths, or remote IDs outside adapter bindings.', {
|
|
431
|
+
path: formatJsonPointer(pathSegments.concat(key)),
|
|
432
|
+
details: { key },
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
validateForbiddenStateKeys(child, pathSegments.concat(key));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function isForbiddenRealIdLocation(pathSegments, key, canonical) {
|
|
440
|
+
if (!REAL_ID_KEY_CANONICALS.has(canonical)) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
const segments = pathSegments.concat(key);
|
|
444
|
+
if (segments.length < 5 || segments[0] !== 'resources') {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
const resourceType = segments[1];
|
|
448
|
+
if (canonical === 'apptype') {
|
|
449
|
+
return !(
|
|
450
|
+
segments.length === 5 &&
|
|
451
|
+
segments[3] === 'bindings' &&
|
|
452
|
+
segments[4] === key &&
|
|
453
|
+
(resourceType === 'app' || resourceType === 'form' || resourceType === 'page' || resourceType === 'process')
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (canonical === 'formuuid') {
|
|
457
|
+
return !(
|
|
458
|
+
segments.length === 5 &&
|
|
459
|
+
(resourceType === 'form' || resourceType === 'page' || resourceType === 'process') &&
|
|
460
|
+
segments[3] === 'bindings' &&
|
|
461
|
+
segments[4] === key
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
if (canonical === 'fieldid') {
|
|
465
|
+
return !(segments.length === 7 && resourceType === 'form' && segments[3] === 'bindings' && segments[4] === 'fieldBindings' && segments[6] === key);
|
|
466
|
+
}
|
|
467
|
+
if (canonical === 'processcode') {
|
|
468
|
+
return !(
|
|
469
|
+
segments.length === 5 &&
|
|
470
|
+
segments[3] === 'bindings' &&
|
|
471
|
+
segments[4] === key
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
if (canonical === 'processid' || canonical === 'processversion') {
|
|
475
|
+
return !(segments.length === 5 && segments[3] === 'bindings' && segments[4] === key);
|
|
476
|
+
}
|
|
477
|
+
if (canonical === 'nodeid') {
|
|
478
|
+
return !(
|
|
479
|
+
segments.length === 7 &&
|
|
480
|
+
segments[3] === 'bindings' &&
|
|
481
|
+
segments[4] === 'nodeBindings' &&
|
|
482
|
+
segments[6] === key
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function assertEnvironmentMatches(actual, expected) {
|
|
489
|
+
if (
|
|
490
|
+
actual.environmentKey !== expected.environmentKey ||
|
|
491
|
+
actual.corpIdHash !== expected.corpIdHash
|
|
492
|
+
) {
|
|
493
|
+
throw schemaError('SCHEMA_STATE_ENVIRONMENT_MISMATCH', 'State belongs to a different environment.', {
|
|
494
|
+
path: '/environment',
|
|
495
|
+
details: {
|
|
496
|
+
environmentKey: actual.environmentKey,
|
|
497
|
+
corpIdHash: actual.corpIdHash,
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function assertAllowedKeys(value, allowedKeys, pointer) {
|
|
504
|
+
const allowed = new Set(allowedKeys);
|
|
505
|
+
for (const key of Object.keys(value || {})) {
|
|
506
|
+
if (!allowed.has(key)) {
|
|
507
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'State contains an unknown property.', {
|
|
508
|
+
path: pointer === '/' ? `/${escapePointer(key)}` : `${pointer}/${escapePointer(key)}`,
|
|
509
|
+
details: { property: key },
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function assertHash(value, name, pointer) {
|
|
516
|
+
if (typeof value !== 'string' || !HASH_PATTERN.test(value)) {
|
|
517
|
+
throw schemaError('SCHEMA_STATE_INVALID', `${name} must be a sha256 hash.`, {
|
|
518
|
+
path: pointer,
|
|
519
|
+
details: { field: name },
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function hasNonEmptyString(value) {
|
|
525
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function normalizeEndpoint(endpoint) {
|
|
529
|
+
return String(endpoint || '').trim().replace(/\/+$/, '').toLowerCase();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function fsyncDirectoryBestEffort(fsImpl, dir) {
|
|
533
|
+
if (typeof fsImpl.openSync !== 'function' || typeof fsImpl.fsyncSync !== 'function') {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
let dirFd;
|
|
537
|
+
try {
|
|
538
|
+
dirFd = fsImpl.openSync(dir, 'r');
|
|
539
|
+
fsImpl.fsyncSync(dirFd);
|
|
540
|
+
} catch (error) {
|
|
541
|
+
// Directory fsync is not portable across all filesystems.
|
|
542
|
+
} finally {
|
|
543
|
+
if (dirFd !== undefined) {
|
|
544
|
+
try {
|
|
545
|
+
fsImpl.closeSync(dirFd);
|
|
546
|
+
} catch (closeError) {
|
|
547
|
+
// Best effort cleanup.
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function clonePlain(value) {
|
|
554
|
+
if (value === undefined || value === null || typeof value !== 'object') {
|
|
555
|
+
return value;
|
|
556
|
+
}
|
|
557
|
+
return JSON.parse(JSON.stringify(value));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function formatJsonPointer(segments) {
|
|
561
|
+
if (!segments || segments.length === 0) {
|
|
562
|
+
return '/';
|
|
563
|
+
}
|
|
564
|
+
return '/' + segments.map(escapePointer).join('/');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function escapePointer(value) {
|
|
568
|
+
return String(value).replace(/~/g, '~0').replace(/\//g, '~1');
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function compareResourceIds(left, right) {
|
|
572
|
+
return compareCodePoints(`${left.resourceType}:${left.key}`, `${right.resourceType}:${right.key}`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
module.exports = {
|
|
576
|
+
STATE_KIND,
|
|
577
|
+
STATE_CONTRACT_VERSION,
|
|
578
|
+
compareResourceIds,
|
|
579
|
+
createEmptyState,
|
|
580
|
+
createEnvironmentIdentity,
|
|
581
|
+
createSha256,
|
|
582
|
+
getResourceState,
|
|
583
|
+
hashStable,
|
|
584
|
+
readState,
|
|
585
|
+
resolveDefaultStatePath,
|
|
586
|
+
upsertResourceState,
|
|
587
|
+
validateState,
|
|
588
|
+
writeStateAtomic,
|
|
589
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openyida",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.21",
|
|
4
4
|
"description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"openyida": "bin/yida.js",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"homepage": "https://github.com/openyida/openyida#readme",
|
|
86
86
|
"dependencies": {
|
|
87
87
|
"@babel/standalone": "^7.15.1",
|
|
88
|
-
"
|
|
88
|
+
"ajv": "^8.20.0",
|
|
89
89
|
"uglify-js": "^3.19.3"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
package/scripts/postinstall.js
CHANGED
|
@@ -224,28 +224,24 @@ npm install -g openyida@latest
|
|
|
224
224
|
若登录态无效,执行:
|
|
225
225
|
|
|
226
226
|
\`\`\`bash
|
|
227
|
-
openyida login
|
|
227
|
+
openyida login
|
|
228
228
|
\`\`\`
|
|
229
229
|
|
|
230
|
-
登录完成后再次运行 \`openyida login --check-only --json\`
|
|
230
|
+
登录完成后再次运行 \`openyida login --check-only --json\` 验证 token session,再继续真实资源操作。
|
|
231
231
|
|
|
232
232
|
## Codex Browser 边界
|
|
233
233
|
|
|
234
234
|
Codex App 的 in-app browser / \`@Browser\` 适合打开本地开发服务器、file-backed preview 和无需登录的公开页面,用于截图、点击和检查渲染状态。
|
|
235
235
|
|
|
236
|
-
不要把 Codex in-app browser 用作宜搭登录 Cookie
|
|
236
|
+
不要把 Codex in-app browser 用作宜搭登录 Cookie 来源:OpenYida CLI 登录使用 OAuth loopback + token session,不需要导出或手写 Cookie。
|
|
237
237
|
|
|
238
|
-
需要登录并获得 CLI
|
|
238
|
+
需要登录并获得 CLI token session 时,优先运行:
|
|
239
239
|
|
|
240
240
|
\`\`\`bash
|
|
241
|
-
openyida login
|
|
241
|
+
openyida login
|
|
242
242
|
\`\`\`
|
|
243
243
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
在 Codex 中如果 \`openyida login --codex-poll\` 返回 \`need_corp_selection\`,优先调用 OpenYida MCP 工具
|
|
247
|
-
\`select_yida_login_organization\`,传入返回值里的 \`session_file\`。该工具会通过 MCP \`elicitation/create\`
|
|
248
|
-
展示 Codex 原生组织单选控件,并在用户选择后完成 \`openyida login --codex-select\`。
|
|
244
|
+
\`openyida login\` 会通过 OAuth loopback 打开常规本地浏览器;如需抑制自动打开浏览器,可设置环境变量 \`OPENYIDA_NO_BROWSER=1\`。只有在需要纯浏览器预览或检查公开页面时才使用 \`@Browser\`。
|
|
249
245
|
|
|
250
246
|
## 工作目录
|
|
251
247
|
|
|
@@ -279,8 +275,8 @@ fast_build 页面源码默认不得使用 \`this.dataSourceMap.*\`,除非本
|
|
|
279
275
|
| \`yida-skills/analytics\` | 报表、统计、图表、ECharts、看板、驾驶舱 | \`yida-report\`, \`yida-chart\`, \`yida-dashboard\` |
|
|
280
276
|
| \`yida-skills/integration\` | 连接器、外部 API、数据源、集成自动化 | \`yida-integration\`, \`yida-connector\`, \`yida-connector-safe-actions\`, \`yida-data-source-connectors\` |
|
|
281
277
|
| \`yida-skills/access\` | 平台/应用/表单/页面权限、公开访问、分享 | \`yida-corp-manager\`, \`yida-app-permission\`, \`yida-form-permission\`, \`yida-page-config\` |
|
|
282
|
-
| \`yida-skills/ops\` |
|
|
283
|
-
| \`yida-skills/agent\` |
|
|
278
|
+
| \`yida-skills/ops\` | Sequence、VOC | \`yida-db-seq-fix\`, \`yida-voc\` |
|
|
279
|
+
| \`yida-skills/agent\` | 导出对话、会议纪要/闪记转 PRD | \`yida-export-conversation\`, \`yida-flash-note-to-prd\` |
|
|
284
280
|
|
|
285
281
|
## 执行规则
|
|
286
282
|
|
|
@@ -12,7 +12,7 @@ const path = require('path');
|
|
|
12
12
|
// per-file cap stays fixed to catch accidental large-blob embeds.
|
|
13
13
|
const MAX_TARBALL_BYTES = 1792 * 1024;
|
|
14
14
|
const MAX_UNPACKED_BYTES = 5632 * 1024;
|
|
15
|
-
const MAX_ENTRY_COUNT =
|
|
15
|
+
const MAX_ENTRY_COUNT = 433;
|
|
16
16
|
const MAX_SINGLE_FILE_BYTES = 512 * 1024;
|
|
17
17
|
|
|
18
18
|
function formatBytes(bytes) {
|