openyida 2026.9.1 → 2026.9.2-beta.0
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 +2 -2
- package/lib/app/display-page-readback.js +63 -5
- package/lib/app/get-schema.js +168 -14
- package/lib/app/publish.js +138 -6
- package/lib/auth/token-store.js +13 -1
- package/lib/core/command-manifest.js +3 -3
- package/lib/core/locales/en.js +14 -0
- package/lib/core/locales/zh.js +14 -0
- package/lib/core/query-data.js +215 -23
- package/lib/core/utils.js +409 -0
- package/lib/report/append.js +3 -1
- package/lib/report/contract.js +7 -0
- package/lib/report/index.js +94 -5
- package/lib/report/inspect.js +32 -3
- package/lib/report/runtime-probe.js +248 -0
- package/lib/report/url.js +13 -0
- package/package.json +1 -1
- package/yida-skills/references/report-field-config-guide.md +10 -28
- package/yida-skills/skills/yida-app/SKILL.md +3 -2
- package/yida-skills/skills/yida-app/workflow/step-5-seed-records.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +9 -7
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +5 -2
- package/yida-skills/skills/yida-chart/references/echarts-bindding-guide.md +1 -1
- package/yida-skills/skills/yida-data-management/SKILL.md +17 -16
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +1 -1
- package/yida-skills/skills/yida-report/SKILL.md +6 -11
- package/yida-skills/skills/yida-report/references/examples.md +11 -18
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ openyida configure-process APP_XXX FORM_XXX .cache/openyida/process/process.json
|
|
|
200
200
|
openyida process preview APP_XXX PROC_INST_XXX --output .cache/openyida/process/process.html
|
|
201
201
|
openyida data query form APP_XXX FORM_XXX --page 1 --size 20
|
|
202
202
|
openyida data query form APP_XXX FORM_XXX --dynamic-order '{"dateField_xxx":"-"}'
|
|
203
|
-
openyida data create form APP_XXX FORM_XXX --data-file .cache/openyida/data-import/record.json
|
|
203
|
+
openyida data create form APP_XXX FORM_XXX --expect-form-name 客户 --expect-form-type receipt --data-file .cache/openyida/data-import/record.json
|
|
204
204
|
openyida get-permission APP_XXX FORM_XXX
|
|
205
205
|
```
|
|
206
206
|
|
|
@@ -430,7 +430,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
430
430
|
|
|
431
431
|
| Command | Description |
|
|
432
432
|
|---------|-------------|
|
|
433
|
-
| `openyida data <query\|get\|create\|update> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --confirm [--json]` | Unified data management (form/process/task/subform) |
|
|
433
|
+
| `openyida data <query\|get\|create\|update> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --expect-form-name <name> --expect-form-type receipt --confirm [--json]` | Unified data management (form/process/task/subform) |
|
|
434
434
|
| `openyida task-center <type> [options]` | Global task center (todo/processed/cc etc.) |
|
|
435
435
|
| `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
|
|
436
436
|
| `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
|
|
@@ -23,10 +23,30 @@ function fingerprint(value) {
|
|
|
23
23
|
return crypto.createHash('sha256').update(normalized).digest('hex');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function rawFingerprint(value) {
|
|
27
|
+
if (typeof value !== 'string' || !value) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
return crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function positiveSize(value) {
|
|
34
|
+
const size = Number(value);
|
|
35
|
+
return Number.isFinite(size) && size > 0 ? size : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function sha256(value) {
|
|
39
|
+
const digest = String(value || '').toLowerCase();
|
|
40
|
+
return /^[a-f0-9]{64}$/.test(digest) ? digest : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
function createEmptyDisplayInfo() {
|
|
27
44
|
return {
|
|
28
45
|
hasYidaCodeCanvas: false,
|
|
46
|
+
hasCodeBundle: false,
|
|
29
47
|
hasNativeJsx: false,
|
|
48
|
+
codeBundleCount: 0,
|
|
49
|
+
bundleIds: [],
|
|
30
50
|
runtimeCodeBytes: 0,
|
|
31
51
|
sourceCodeBytes: 0,
|
|
32
52
|
compiledCodeBytes: 0,
|
|
@@ -34,6 +54,8 @@ function createEmptyDisplayInfo() {
|
|
|
34
54
|
componentCount: 0,
|
|
35
55
|
canvasRuntimeCode: '',
|
|
36
56
|
canvasSourceCode: '',
|
|
57
|
+
canvasRuntimeSha256: '',
|
|
58
|
+
canvasSourceSha256: '',
|
|
37
59
|
nativeCompiledCode: '',
|
|
38
60
|
nativeSourceCode: '',
|
|
39
61
|
};
|
|
@@ -79,12 +101,37 @@ function traverseDisplayNodes(node, info) {
|
|
|
79
101
|
|
|
80
102
|
if (node.componentName === 'YidaCodeCanvas') {
|
|
81
103
|
const props = node.props || {};
|
|
104
|
+
const codeBundle = props.codeBundle && typeof props.codeBundle === 'object'
|
|
105
|
+
? props.codeBundle
|
|
106
|
+
: null;
|
|
82
107
|
info.hasYidaCodeCanvas = true;
|
|
83
108
|
info.componentCount++;
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
if (typeof props.runtimeCode === 'string' && props.runtimeCode) {
|
|
110
|
+
info.canvasRuntimeCode = props.runtimeCode;
|
|
111
|
+
}
|
|
112
|
+
if (typeof props.code === 'string' && props.code) {
|
|
113
|
+
info.canvasSourceCode = props.code;
|
|
114
|
+
}
|
|
86
115
|
info.runtimeCodeBytes += codeBytes(props.runtimeCode);
|
|
87
116
|
info.sourceCodeBytes += codeBytes(props.code);
|
|
117
|
+
if (codeBundle) {
|
|
118
|
+
const bundleId = String(codeBundle.bundleId || '');
|
|
119
|
+
const runtime = codeBundle.runtime && typeof codeBundle.runtime === 'object'
|
|
120
|
+
? codeBundle.runtime
|
|
121
|
+
: {};
|
|
122
|
+
const source = codeBundle.source && typeof codeBundle.source === 'object'
|
|
123
|
+
? codeBundle.source
|
|
124
|
+
: {};
|
|
125
|
+
info.hasCodeBundle = true;
|
|
126
|
+
info.codeBundleCount++;
|
|
127
|
+
if (bundleId && !info.bundleIds.includes(bundleId)) {
|
|
128
|
+
info.bundleIds.push(bundleId);
|
|
129
|
+
}
|
|
130
|
+
info.runtimeCodeBytes += positiveSize(runtime.size);
|
|
131
|
+
info.sourceCodeBytes += positiveSize(source.size);
|
|
132
|
+
info.canvasRuntimeSha256 = sha256(runtime.sha256) || info.canvasRuntimeSha256;
|
|
133
|
+
info.canvasSourceSha256 = sha256(source.sha256) || info.canvasSourceSha256;
|
|
134
|
+
}
|
|
88
135
|
addImportedModules(info.importedModules, props.importedModules);
|
|
89
136
|
} else if (node.componentName === 'Jsx') {
|
|
90
137
|
info.hasNativeJsx = true;
|
|
@@ -133,7 +180,9 @@ function hasExpectedDisplayComponent(info, publishMode) {
|
|
|
133
180
|
return false;
|
|
134
181
|
}
|
|
135
182
|
if (publishMode === 'canvas') {
|
|
136
|
-
return info.hasYidaCodeCanvas &&
|
|
183
|
+
return info.hasYidaCodeCanvas && (
|
|
184
|
+
info.runtimeCodeBytes > 0 || !!info.canvasRuntimeSha256
|
|
185
|
+
);
|
|
137
186
|
}
|
|
138
187
|
return info.hasNativeJsx && info.compiledCodeBytes > 0;
|
|
139
188
|
}
|
|
@@ -144,7 +193,10 @@ function summarizeDisplayPublishInfo(info) {
|
|
|
144
193
|
}
|
|
145
194
|
return {
|
|
146
195
|
hasYidaCodeCanvas: info.hasYidaCodeCanvas,
|
|
196
|
+
hasCodeBundle: info.hasCodeBundle,
|
|
147
197
|
hasNativeJsx: info.hasNativeJsx,
|
|
198
|
+
codeBundleCount: info.codeBundleCount,
|
|
199
|
+
bundleIds: info.bundleIds.slice(),
|
|
148
200
|
runtimeCodeBytes: info.runtimeCodeBytes,
|
|
149
201
|
sourceCodeBytes: info.sourceCodeBytes,
|
|
150
202
|
compiledCodeBytes: info.compiledCodeBytes,
|
|
@@ -159,8 +211,13 @@ function verifyPublishedContentMatch(readbackSchema, expectedSchemaContent, publ
|
|
|
159
211
|
const displayComponentPresent = hasExpectedDisplayComponent(readbackInfo, publishMode);
|
|
160
212
|
const readbackArtifact = getPublishArtifact(readbackInfo, publishMode);
|
|
161
213
|
const expectedArtifact = getPublishArtifact(expectedInfo, publishMode);
|
|
162
|
-
const
|
|
163
|
-
const
|
|
214
|
+
const codeBundleReadback = publishMode === 'canvas' && readbackInfo && readbackInfo.hasCodeBundle;
|
|
215
|
+
const readbackFingerprint = codeBundleReadback
|
|
216
|
+
? readbackInfo.canvasRuntimeSha256
|
|
217
|
+
: fingerprint(readbackArtifact);
|
|
218
|
+
const expectedFingerprint = codeBundleReadback
|
|
219
|
+
? rawFingerprint(expectedArtifact)
|
|
220
|
+
: fingerprint(expectedArtifact);
|
|
164
221
|
|
|
165
222
|
return {
|
|
166
223
|
readbackInfo,
|
|
@@ -180,6 +237,7 @@ function verifyPublishedContentMatch(readbackSchema, expectedSchemaContent, publ
|
|
|
180
237
|
module.exports = {
|
|
181
238
|
extractDisplayPublishInfo,
|
|
182
239
|
fingerprint,
|
|
240
|
+
rawFingerprint,
|
|
183
241
|
hasExpectedDisplayComponent,
|
|
184
242
|
parseSchemaContent,
|
|
185
243
|
summarizeDisplayPublishInfo,
|
package/lib/app/get-schema.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
+
const crypto = require('crypto');
|
|
12
13
|
const fs = require('fs');
|
|
13
14
|
const path = require('path');
|
|
14
15
|
const {
|
|
@@ -16,6 +17,7 @@ const {
|
|
|
16
17
|
triggerLogin,
|
|
17
18
|
resolveBaseUrl,
|
|
18
19
|
httpGet,
|
|
20
|
+
httpGetRedirectText,
|
|
19
21
|
requestWithAutoLogin,
|
|
20
22
|
} = require('../core/utils');
|
|
21
23
|
const { t } = require('../core/i18n');
|
|
@@ -24,7 +26,7 @@ const { fetchFormPageList } = require('./form-navigation');
|
|
|
24
26
|
const { buildFieldResolution } = require('./schema-field-resolution');
|
|
25
27
|
const { buildSemanticAnalysis } = require('./schema-semantic-analysis');
|
|
26
28
|
|
|
27
|
-
//
|
|
29
|
+
// 报表运行时可能暴露 raw fieldId 或 _value;最终值必须由报表查询探针确认。
|
|
28
30
|
const FIELD_TYPES_NEEDING_VALUE_SUFFIX = new Set([
|
|
29
31
|
'SelectField',
|
|
30
32
|
'MultiSelectField',
|
|
@@ -44,9 +46,9 @@ const FIELD_COMPONENT_NAMES = new Set([
|
|
|
44
46
|
]);
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
|
-
* 从 Schema
|
|
49
|
+
* 从 Schema 中提取字段摘要。表单 Schema 只能提供报表字段候选,不能代替 cube 运行时元数据。
|
|
48
50
|
* @param {object} schemaResult - getFormSchema API 返回结果
|
|
49
|
-
* @returns {Array<{label, componentName, fieldId, alias, reportFieldCode, options}>}
|
|
51
|
+
* @returns {Array<{label, componentName, fieldId, alias, reportFieldCode, reportFieldCodeCandidates, options}>}
|
|
50
52
|
*/
|
|
51
53
|
function extractFieldSummary(schemaResult) {
|
|
52
54
|
const fields = [];
|
|
@@ -67,9 +69,9 @@ function extractFieldSummary(schemaResult) {
|
|
|
67
69
|
? (typeof labelRaw === 'object' ? (labelRaw.zh_CN || labelRaw.en_US || '') : String(labelRaw))
|
|
68
70
|
: '';
|
|
69
71
|
const fieldId = props.fieldId || '';
|
|
70
|
-
const
|
|
71
|
-
? `${fieldId}_value`
|
|
72
|
-
: fieldId;
|
|
72
|
+
const reportFieldCodeCandidates = FIELD_TYPES_NEEDING_VALUE_SUFFIX.has(node.componentName)
|
|
73
|
+
? [fieldId, `${fieldId}_value`]
|
|
74
|
+
: [fieldId];
|
|
73
75
|
if (fieldId) {
|
|
74
76
|
const options = extractOptionSummary(props);
|
|
75
77
|
const optionSource = getOptionSource(props) || [];
|
|
@@ -87,7 +89,8 @@ function extractFieldSummary(schemaResult) {
|
|
|
87
89
|
componentName: node.componentName,
|
|
88
90
|
fieldId,
|
|
89
91
|
alias: aliasMaps.aliasByFieldId[fieldId] || '',
|
|
90
|
-
reportFieldCode,
|
|
92
|
+
reportFieldCode: fieldId,
|
|
93
|
+
reportFieldCodeCandidates,
|
|
91
94
|
options,
|
|
92
95
|
optionCount: optionSource.length,
|
|
93
96
|
optionsTruncated: optionSource.length > options.length,
|
|
@@ -226,6 +229,122 @@ function resolveSchemaContent(schemaResult) {
|
|
|
226
229
|
return parseJsonObject(content);
|
|
227
230
|
}
|
|
228
231
|
|
|
232
|
+
function validateCodeBundleDescriptor(codeBundle) {
|
|
233
|
+
const bundleId = String(codeBundle && codeBundle.bundleId || '');
|
|
234
|
+
const source = codeBundle && codeBundle.source;
|
|
235
|
+
const runtime = codeBundle && codeBundle.runtime;
|
|
236
|
+
const validSha256 = value => /^[a-f0-9]{64}$/.test(String(value || ''));
|
|
237
|
+
const validSize = value => Number.isFinite(Number(value)) && Number(value) > 0
|
|
238
|
+
&& Number(value) <= 5 * 1024 * 1024;
|
|
239
|
+
if (!validSha256(bundleId)
|
|
240
|
+
|| !source || !runtime
|
|
241
|
+
|| !validSha256(source.sha256) || !validSize(source.size)
|
|
242
|
+
|| !validSha256(runtime.sha256) || !validSize(runtime.size)
|
|
243
|
+
|| Number(source.size) + Number(runtime.size) > 5 * 1024 * 1024) {
|
|
244
|
+
throw new Error('YidaCodeCanvas codeBundle 描述不合法');
|
|
245
|
+
}
|
|
246
|
+
return { bundleId, source, runtime };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function collectCodeBundleCanvases(content) {
|
|
250
|
+
const canvases = [];
|
|
251
|
+
function traverse(node) {
|
|
252
|
+
if (Array.isArray(node)) {
|
|
253
|
+
node.forEach(traverse);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!node || typeof node !== 'object') {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (node.componentName === 'YidaCodeCanvas' && node.id && node.props
|
|
260
|
+
&& node.props.codeBundle && typeof node.props.codeBundle === 'object') {
|
|
261
|
+
canvases.push({ props: node.props, descriptor: validateCodeBundleDescriptor(node.props.codeBundle) });
|
|
262
|
+
}
|
|
263
|
+
Object.values(node).forEach(traverse);
|
|
264
|
+
}
|
|
265
|
+
traverse(content);
|
|
266
|
+
return canvases;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function downloadCodeBundleArtifact(appType, formUuid, bundleId, artifact, descriptor, authRef) {
|
|
270
|
+
let responseMetadata = null;
|
|
271
|
+
const expectedContentTypes = artifact === 'source'
|
|
272
|
+
? ['text/plain']
|
|
273
|
+
: ['application/javascript', 'text/javascript', 'application/x-javascript'];
|
|
274
|
+
const value = await requestWithAutoLogin((auth) => httpGetRedirectText(
|
|
275
|
+
auth.baseUrl,
|
|
276
|
+
`/alibaba/web/${appType}/query/codeBundle/download.json`,
|
|
277
|
+
{ formUuid, bundleId, artifact },
|
|
278
|
+
{
|
|
279
|
+
silentStatus: true,
|
|
280
|
+
maxBytes: 5 * 1024 * 1024,
|
|
281
|
+
expectedContentTypes,
|
|
282
|
+
onResponseMetadata: metadata => {
|
|
283
|
+
responseMetadata = metadata;
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
), authRef);
|
|
287
|
+
if (typeof value !== 'string') {
|
|
288
|
+
const message = value && (value.errorMsg || value.message);
|
|
289
|
+
throw new Error(message || `下载 CodeBundle ${artifact} 失败`);
|
|
290
|
+
}
|
|
291
|
+
const size = Buffer.byteLength(value, 'utf8');
|
|
292
|
+
const digest = crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
293
|
+
if (size !== Number(descriptor.size) || digest !== descriptor.sha256) {
|
|
294
|
+
const context = responseMetadata && responseMetadata.context
|
|
295
|
+
? responseMetadata.context
|
|
296
|
+
: `baseUrl=${authRef && authRef.baseUrl || 'unknown'}`;
|
|
297
|
+
const error = new Error(t('common.code_bundle_integrity_failed', artifact, context));
|
|
298
|
+
error.code = 'CODE_BUNDLE_INTEGRITY_MISMATCH';
|
|
299
|
+
error.details = {
|
|
300
|
+
artifact,
|
|
301
|
+
expectedSize: Number(descriptor.size),
|
|
302
|
+
actualSize: size,
|
|
303
|
+
expectedSha256: descriptor.sha256,
|
|
304
|
+
actualSha256: digest,
|
|
305
|
+
...(responseMetadata || {}),
|
|
306
|
+
};
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
return value;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function resolveCodeBundleSchema(schemaResult, appType, formUuid, authRef) {
|
|
313
|
+
const content = resolveSchemaContent(schemaResult);
|
|
314
|
+
if (!content) {
|
|
315
|
+
return schemaResult;
|
|
316
|
+
}
|
|
317
|
+
const canvases = collectCodeBundleCanvases(content);
|
|
318
|
+
if (canvases.length === 0) {
|
|
319
|
+
return schemaResult;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const downloaded = new Map();
|
|
323
|
+
for (const canvas of canvases) {
|
|
324
|
+
const { bundleId, source, runtime } = canvas.descriptor;
|
|
325
|
+
let code = downloaded.get(bundleId);
|
|
326
|
+
if (!code) {
|
|
327
|
+
const [sourceCode, runtimeCode] = await Promise.all([
|
|
328
|
+
downloadCodeBundleArtifact(appType, formUuid, bundleId, 'source', source, authRef),
|
|
329
|
+
downloadCodeBundleArtifact(appType, formUuid, bundleId, 'runtime', runtime, authRef),
|
|
330
|
+
]);
|
|
331
|
+
code = { sourceCode, runtimeCode };
|
|
332
|
+
downloaded.set(bundleId, code);
|
|
333
|
+
}
|
|
334
|
+
canvas.props.code = code.sourceCode;
|
|
335
|
+
canvas.props.runtimeCode = code.runtimeCode;
|
|
336
|
+
delete canvas.props.codeBundle;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (schemaResult && Object.prototype.hasOwnProperty.call(schemaResult, 'content')) {
|
|
340
|
+
schemaResult.content = typeof schemaResult.content === 'string'
|
|
341
|
+
? JSON.stringify(content)
|
|
342
|
+
: content;
|
|
343
|
+
return schemaResult;
|
|
344
|
+
}
|
|
345
|
+
return content;
|
|
346
|
+
}
|
|
347
|
+
|
|
229
348
|
function addImportedModules(target, value) {
|
|
230
349
|
let modules = value;
|
|
231
350
|
if (typeof modules === 'string') {
|
|
@@ -268,7 +387,10 @@ function extractDisplayPageSummary(schemaResult) {
|
|
|
268
387
|
|
|
269
388
|
const displayPage = {
|
|
270
389
|
hasYidaCodeCanvas: false,
|
|
390
|
+
hasCodeBundle: false,
|
|
271
391
|
hasNativeJsx: false,
|
|
392
|
+
codeBundleCount: 0,
|
|
393
|
+
bundleIds: [],
|
|
272
394
|
runtimeCodeBytes: 0,
|
|
273
395
|
sourceCodeBytes: 0,
|
|
274
396
|
compiledCodeBytes: 0,
|
|
@@ -290,10 +412,29 @@ function extractDisplayPageSummary(schemaResult) {
|
|
|
290
412
|
|
|
291
413
|
if (node.componentName === 'YidaCodeCanvas' && isComponentInstance(node)) {
|
|
292
414
|
const props = node.props || {};
|
|
415
|
+
const codeBundle = props.codeBundle && typeof props.codeBundle === 'object'
|
|
416
|
+
? props.codeBundle
|
|
417
|
+
: null;
|
|
293
418
|
displayPage.hasYidaCodeCanvas = true;
|
|
294
419
|
displayPage.componentCount++;
|
|
295
420
|
displayPage.runtimeCodeBytes += codeBytes(props.runtimeCode);
|
|
296
421
|
displayPage.sourceCodeBytes += codeBytes(props.code);
|
|
422
|
+
if (codeBundle) {
|
|
423
|
+
const source = codeBundle.source && typeof codeBundle.source === 'object'
|
|
424
|
+
? codeBundle.source
|
|
425
|
+
: {};
|
|
426
|
+
const runtime = codeBundle.runtime && typeof codeBundle.runtime === 'object'
|
|
427
|
+
? codeBundle.runtime
|
|
428
|
+
: {};
|
|
429
|
+
const bundleId = String(codeBundle.bundleId || '');
|
|
430
|
+
displayPage.hasCodeBundle = true;
|
|
431
|
+
displayPage.codeBundleCount++;
|
|
432
|
+
displayPage.sourceCodeBytes += Number(source.size) || 0;
|
|
433
|
+
displayPage.runtimeCodeBytes += Number(runtime.size) || 0;
|
|
434
|
+
if (bundleId && !displayPage.bundleIds.includes(bundleId)) {
|
|
435
|
+
displayPage.bundleIds.push(bundleId);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
297
438
|
addImportedModules(displayPage.importedModules, props.importedModules);
|
|
298
439
|
} else if (node.componentName === 'Jsx' && isComponentInstance(node)) {
|
|
299
440
|
displayPage.hasNativeJsx = true;
|
|
@@ -512,7 +653,7 @@ function printFieldSummary(result) {
|
|
|
512
653
|
return;
|
|
513
654
|
}
|
|
514
655
|
|
|
515
|
-
process.stderr.write(`\n ${c.bold}${c.cyan}📋 字段摘要${c.reset} ${c.dim}
|
|
656
|
+
process.stderr.write(`\n ${c.bold}${c.cyan}📋 字段摘要${c.reset} ${c.dim}(报表字段最终以运行时查询探针为准)${c.reset}\n`);
|
|
516
657
|
process.stderr.write(` ${c.dim}${'─'.repeat(80)}${c.reset}\n`);
|
|
517
658
|
process.stderr.write(
|
|
518
659
|
` ${c.bold}${'label'.padEnd(16)}${'alias'.padEnd(18)}${'componentName'.padEnd(20)}${'fieldId'.padEnd(28)}reportFieldCode${c.reset}\n`
|
|
@@ -524,7 +665,7 @@ function printFieldSummary(result) {
|
|
|
524
665
|
);
|
|
525
666
|
}
|
|
526
667
|
process.stderr.write(` ${c.dim}${'─'.repeat(80)}${c.reset}\n`);
|
|
527
|
-
process.stderr.write(` ${c.dim}注:
|
|
668
|
+
process.stderr.write(` ${c.dim}注:Select/Employee 等字段同时返回 raw 与 _value 候选;create-report 会用真实图表查询验证并在原 reportId 内窄修复${c.reset}\n\n`);
|
|
528
669
|
}
|
|
529
670
|
|
|
530
671
|
function buildSchemaSummary(appType, formUuid, schemaResult, meta = {}) {
|
|
@@ -580,12 +721,15 @@ async function mapLimit(items, limit, iterator) {
|
|
|
580
721
|
return results;
|
|
581
722
|
}
|
|
582
723
|
|
|
583
|
-
async function fetchSchemaRecord(appType, form, authRef, retries) {
|
|
724
|
+
async function fetchSchemaRecord(appType, form, authRef, retries, options = {}) {
|
|
584
725
|
let lastError = null;
|
|
585
726
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
586
727
|
try {
|
|
587
728
|
const result = await fetchSchema(appType, form.formUuid, authRef);
|
|
588
729
|
if (isSuccessfulSchemaResult(result)) {
|
|
730
|
+
const resolvedResult = options.resolveCodeBundles
|
|
731
|
+
? await resolveCodeBundleSchema(result, appType, form.formUuid, authRef)
|
|
732
|
+
: result;
|
|
589
733
|
const record = {
|
|
590
734
|
formUuid: form.formUuid,
|
|
591
735
|
formName: form.formName,
|
|
@@ -593,10 +737,10 @@ async function fetchSchemaRecord(appType, form, authRef, retries) {
|
|
|
593
737
|
pathName: form.pathName,
|
|
594
738
|
success: true,
|
|
595
739
|
attempts: attempt + 1,
|
|
596
|
-
fieldSummary: extractFieldSummary(
|
|
597
|
-
schema:
|
|
740
|
+
fieldSummary: extractFieldSummary(resolvedResult),
|
|
741
|
+
schema: resolvedResult,
|
|
598
742
|
};
|
|
599
|
-
const displayPage = extractDisplayPageSummary(
|
|
743
|
+
const displayPage = extractDisplayPageSummary(resolvedResult);
|
|
600
744
|
if (displayPage) {
|
|
601
745
|
record.displayPage = displayPage;
|
|
602
746
|
}
|
|
@@ -693,6 +837,10 @@ async function runSingle(parsed, authRef) {
|
|
|
693
837
|
throwCommandError(errorMsg);
|
|
694
838
|
}
|
|
695
839
|
|
|
840
|
+
if (!parsed.field && !parsed.compact && !parsed.summaryJson) {
|
|
841
|
+
result = await resolveCodeBundleSchema(result, parsed.appType, parsed.formUuid, authRef);
|
|
842
|
+
}
|
|
843
|
+
|
|
696
844
|
// 保留既有 --field 返回结构;新 contract 仅由 --compact / --resolve-fields 触发。
|
|
697
845
|
if (parsed.field) {
|
|
698
846
|
const allFieldNodes = collectFieldNodes(result);
|
|
@@ -781,7 +929,9 @@ async function runBatch(parsed, authRef) {
|
|
|
781
929
|
info(` 批量获取 ${forms.length} 个表单 Schema,并发 ${parsed.concurrency},重试 ${parsed.retries}`);
|
|
782
930
|
|
|
783
931
|
const records = await mapLimit(forms, parsed.concurrency, async (form) => {
|
|
784
|
-
const record = await fetchSchemaRecord(parsed.appType, form, authRef, parsed.retries
|
|
932
|
+
const record = await fetchSchemaRecord(parsed.appType, form, authRef, parsed.retries, {
|
|
933
|
+
resolveCodeBundles: !parsed.summaryJson,
|
|
934
|
+
});
|
|
785
935
|
if (record.success && parsed.analysisJson) {
|
|
786
936
|
record.semanticAnalysis = buildSemanticAnalysis(
|
|
787
937
|
parsed.appType,
|
|
@@ -847,5 +997,9 @@ module.exports = {
|
|
|
847
997
|
fetchSchemaRecord,
|
|
848
998
|
collectFieldNodes,
|
|
849
999
|
findFieldNode,
|
|
1000
|
+
validateCodeBundleDescriptor,
|
|
1001
|
+
collectCodeBundleCanvases,
|
|
1002
|
+
downloadCodeBundleArtifact,
|
|
1003
|
+
resolveCodeBundleSchema,
|
|
850
1004
|
run,
|
|
851
1005
|
};
|
package/lib/app/publish.js
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* 流程:
|
|
12
12
|
* 1. 读取源文件,通过内置 babel-transform 编译 + UglifyJS 压缩
|
|
13
|
-
* 2. 用代码动态构建 Schema
|
|
13
|
+
* 2. 用代码动态构建 Schema;Canvas 页面拆出 source/runtime 后统一保存
|
|
14
14
|
* 3. 读取 token session,在写前确认登录态
|
|
15
|
-
* 4.
|
|
15
|
+
* 4. Canvas 调统一保存接口;普通 JSX 页面仍调用原 saveFormSchema
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const fs = require('fs');
|
|
@@ -21,6 +21,7 @@ const querystring = require('querystring');
|
|
|
21
21
|
const {
|
|
22
22
|
findProjectRoot,
|
|
23
23
|
httpPost,
|
|
24
|
+
httpPostMultipart,
|
|
24
25
|
httpGet,
|
|
25
26
|
requestWithAutoLogin,
|
|
26
27
|
} = require('../core/utils');
|
|
@@ -58,6 +59,8 @@ const {
|
|
|
58
59
|
const SCHEMA_VERSION = 'V5';
|
|
59
60
|
const DOMAIN_CODE = 'tEXDRG';
|
|
60
61
|
const PREFIX = '_view';
|
|
62
|
+
const MAX_CODE_BUNDLE_BYTES = 5 * 1024 * 1024;
|
|
63
|
+
const MAX_CANVAS_SCHEMA_SKELETON_BYTES = 1024 * 1024;
|
|
61
64
|
|
|
62
65
|
// ── 参数解析 ─────────────────────────────────────────
|
|
63
66
|
|
|
@@ -284,6 +287,61 @@ function buildCanvasSchemaContent(sourceCode, runtimeCode, importedModules, form
|
|
|
284
287
|
return buildCanvasPageSchemaContent(sourceCode, runtimeCode, importedModules, formUuid);
|
|
285
288
|
}
|
|
286
289
|
|
|
290
|
+
function buildCanvasSavePayload(schemaContent, sourceCode, runtimeCode) {
|
|
291
|
+
if (typeof sourceCode !== 'string' || sourceCode.length === 0
|
|
292
|
+
|| typeof runtimeCode !== 'string' || runtimeCode.length === 0) {
|
|
293
|
+
throw new Error('Canvas source/runtime 不能为空');
|
|
294
|
+
}
|
|
295
|
+
const totalCodeBytes = Buffer.byteLength(sourceCode, 'utf8')
|
|
296
|
+
+ Buffer.byteLength(runtimeCode, 'utf8');
|
|
297
|
+
if (totalCodeBytes > MAX_CODE_BUNDLE_BYTES) {
|
|
298
|
+
throw new Error('Canvas source/runtime 总大小不能超过 5 MiB');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let schema;
|
|
302
|
+
try {
|
|
303
|
+
schema = JSON.parse(schemaContent);
|
|
304
|
+
} catch {
|
|
305
|
+
throw new Error('Canvas Schema 不是有效 JSON');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const canvases = [];
|
|
309
|
+
function traverse(node) {
|
|
310
|
+
if (Array.isArray(node)) {
|
|
311
|
+
node.forEach(traverse);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (!node || typeof node !== 'object') {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (node.componentName === 'YidaCodeCanvas' && node.id) {
|
|
318
|
+
canvases.push(node);
|
|
319
|
+
}
|
|
320
|
+
Object.values(node).forEach(traverse);
|
|
321
|
+
}
|
|
322
|
+
traverse(schema);
|
|
323
|
+
if (canvases.length !== 1) {
|
|
324
|
+
throw new Error('Canvas Schema 必须且只能包含一个 YidaCodeCanvas 节点');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const canvas = canvases[0];
|
|
328
|
+
canvas.props = canvas.props && typeof canvas.props === 'object' ? canvas.props : {};
|
|
329
|
+
delete canvas.props.code;
|
|
330
|
+
delete canvas.props.runtimeCode;
|
|
331
|
+
delete canvas.props.codeBundle;
|
|
332
|
+
|
|
333
|
+
const skeletonContent = JSON.stringify(schema);
|
|
334
|
+
if (Buffer.byteLength(skeletonContent, 'utf8') > MAX_CANVAS_SCHEMA_SKELETON_BYTES) {
|
|
335
|
+
throw new Error('Canvas Schema 骨架不能超过 1 MiB');
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
canvasNodeId: canvas.id,
|
|
339
|
+
content: skeletonContent,
|
|
340
|
+
sourceCode,
|
|
341
|
+
runtimeCode,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
287
345
|
|
|
288
346
|
// ── 4. 发送 saveFormSchema 请求 ──────────────────────
|
|
289
347
|
|
|
@@ -313,6 +371,40 @@ function sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serv
|
|
|
313
371
|
return httpPost(authRef.baseUrl, saveSchemaPath, postData, { silentStatus: true });
|
|
314
372
|
}
|
|
315
373
|
|
|
374
|
+
function sendCanvasSaveRequestWithAuth(authRef, payload, appType, formUuid, serverRevision) {
|
|
375
|
+
if (
|
|
376
|
+
!authRef ||
|
|
377
|
+
typeof authRef.baseUrl !== 'string' ||
|
|
378
|
+
authRef.baseUrl.length === 0 ||
|
|
379
|
+
!isTokenAuthRef(authRef)
|
|
380
|
+
) {
|
|
381
|
+
const authError = new Error('Publish Canvas write authentication is not ready.');
|
|
382
|
+
authError.code = 'PUBLISH_SCHEMA_WRITE_PRECHECK_FAILED';
|
|
383
|
+
return Promise.reject(authError);
|
|
384
|
+
}
|
|
385
|
+
const gmtModified = requireSchemaServerRevision({ gmtModified: serverRevision });
|
|
386
|
+
const savePath = `/alibaba/web/${appType}/query/codeBundle/save.json?_stamp=${Date.now()}`;
|
|
387
|
+
return httpPostMultipart(authRef.baseUrl, savePath, {
|
|
388
|
+
_csrf_token: authRef.csrfToken,
|
|
389
|
+
formUuid,
|
|
390
|
+
gmtModified,
|
|
391
|
+
canvasNodeId: payload.canvasNodeId,
|
|
392
|
+
content: payload.content,
|
|
393
|
+
importSchema: true,
|
|
394
|
+
}, {
|
|
395
|
+
source: {
|
|
396
|
+
content: payload.sourceCode,
|
|
397
|
+
fileName: 'source.jsx',
|
|
398
|
+
contentType: 'text/plain; charset=UTF-8',
|
|
399
|
+
},
|
|
400
|
+
runtime: {
|
|
401
|
+
content: payload.runtimeCode,
|
|
402
|
+
fileName: 'runtime.js',
|
|
403
|
+
contentType: 'application/javascript; charset=UTF-8',
|
|
404
|
+
},
|
|
405
|
+
}, { silentStatus: true });
|
|
406
|
+
}
|
|
407
|
+
|
|
316
408
|
function walkFiles(dir, results, limit) {
|
|
317
409
|
if (!fs.existsSync(dir) || results.length >= limit) {
|
|
318
410
|
return;
|
|
@@ -622,6 +714,8 @@ async function runMain(argv) {
|
|
|
622
714
|
baseUrl = authRef.baseUrl;
|
|
623
715
|
|
|
624
716
|
let schemaContent;
|
|
717
|
+
let expectedSchemaContent;
|
|
718
|
+
let canvasSavePayload;
|
|
625
719
|
let serverRevision;
|
|
626
720
|
if (isCanvas) {
|
|
627
721
|
try {
|
|
@@ -631,7 +725,18 @@ async function runMain(argv) {
|
|
|
631
725
|
process.exit(1);
|
|
632
726
|
}
|
|
633
727
|
// canvasResult 已在登录前本地编译完成,这里只需据其装配 Schema content。
|
|
634
|
-
|
|
728
|
+
expectedSchemaContent = buildCanvasSchemaContent(
|
|
729
|
+
sourceCode,
|
|
730
|
+
canvasResult.runtimeCode,
|
|
731
|
+
canvasResult.importedModules,
|
|
732
|
+
formUuid
|
|
733
|
+
);
|
|
734
|
+
canvasSavePayload = buildCanvasSavePayload(
|
|
735
|
+
expectedSchemaContent,
|
|
736
|
+
sourceCode,
|
|
737
|
+
canvasResult.runtimeCode
|
|
738
|
+
);
|
|
739
|
+
schemaContent = canvasSavePayload.content;
|
|
635
740
|
} else {
|
|
636
741
|
let existingDataSource = null;
|
|
637
742
|
try {
|
|
@@ -644,6 +749,7 @@ async function runMain(argv) {
|
|
|
644
749
|
}
|
|
645
750
|
baseUrl = authRef.baseUrl;
|
|
646
751
|
schemaContent = buildSchemaContent(sourceCode, compiledCode, formUuid, { existingDataSource });
|
|
752
|
+
expectedSchemaContent = schemaContent;
|
|
647
753
|
}
|
|
648
754
|
success(t('publish.schema_built'));
|
|
649
755
|
|
|
@@ -654,7 +760,15 @@ async function runMain(argv) {
|
|
|
654
760
|
label('Source:', sourcePath);
|
|
655
761
|
label('Compiled:', compiledPath);
|
|
656
762
|
step(3, t('publish.step_publish'));
|
|
657
|
-
const response =
|
|
763
|
+
const response = isCanvas
|
|
764
|
+
? await sendCanvasSaveRequestWithAuth(
|
|
765
|
+
authRef,
|
|
766
|
+
canvasSavePayload,
|
|
767
|
+
appType,
|
|
768
|
+
formUuid,
|
|
769
|
+
serverRevision
|
|
770
|
+
)
|
|
771
|
+
: await sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serverRevision);
|
|
658
772
|
|
|
659
773
|
if (!response || !response.success) {
|
|
660
774
|
const errorMsg = response ? response.errorMsg || t('common.unknown_error') : t('common.request_failed');
|
|
@@ -668,6 +782,9 @@ async function runMain(argv) {
|
|
|
668
782
|
const content = response.content || {};
|
|
669
783
|
const savedFormUuid = content.formUuid || formUuid;
|
|
670
784
|
const version = content.version || 0;
|
|
785
|
+
const storageMode = isCanvas ? content.storageMode || null : undefined;
|
|
786
|
+
const bundleId = isCanvas ? content.bundleId || null : undefined;
|
|
787
|
+
const fallbackReason = isCanvas ? content.fallbackReason || null : undefined;
|
|
671
788
|
success(t('publish.schema_published'));
|
|
672
789
|
label('Form UUID:', savedFormUuid);
|
|
673
790
|
label('Version:', String(version));
|
|
@@ -686,7 +803,7 @@ async function runMain(argv) {
|
|
|
686
803
|
appType,
|
|
687
804
|
savedFormUuid,
|
|
688
805
|
authRef,
|
|
689
|
-
|
|
806
|
+
expectedSchemaContent,
|
|
690
807
|
isCanvas ? 'canvas' : 'native'
|
|
691
808
|
);
|
|
692
809
|
} catch (healthCheckError) {
|
|
@@ -733,7 +850,20 @@ async function runMain(argv) {
|
|
|
733
850
|
['URL', pageUrl],
|
|
734
851
|
]);
|
|
735
852
|
console.log(JSON.stringify(withBrowserHandoff(
|
|
736
|
-
{
|
|
853
|
+
{
|
|
854
|
+
success: true,
|
|
855
|
+
appType,
|
|
856
|
+
formUuid: savedFormUuid,
|
|
857
|
+
version,
|
|
858
|
+
url: pageUrl,
|
|
859
|
+
publishMode,
|
|
860
|
+
storageMode,
|
|
861
|
+
bundleId,
|
|
862
|
+
fallbackReason,
|
|
863
|
+
healthCheck: healthCheckResult,
|
|
864
|
+
navOrder: navOrderResult,
|
|
865
|
+
navOrderWarning,
|
|
866
|
+
},
|
|
737
867
|
pageUrl,
|
|
738
868
|
{ stage: 'publish_page_success', title: savedFormUuid },
|
|
739
869
|
browserOpenMode
|
|
@@ -767,5 +897,7 @@ if (require.main === module) {
|
|
|
767
897
|
module.exports.countCustomPageDataSources = countCustomPageDataSources;
|
|
768
898
|
module.exports.buildSchemaContent = buildSchemaContent;
|
|
769
899
|
module.exports.buildCanvasSchemaContent = buildCanvasSchemaContent;
|
|
900
|
+
module.exports.buildCanvasSavePayload = buildCanvasSavePayload;
|
|
770
901
|
module.exports.sendSaveRequestWithAuth = sendSaveRequestWithAuth;
|
|
902
|
+
module.exports.sendCanvasSaveRequestWithAuth = sendCanvasSaveRequestWithAuth;
|
|
771
903
|
}
|