openyida 2026.9.7-beta.0 → 2026.9.7
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/lib/app/canvas-compile.js +4 -37
- package/lib/app/display-page-readback.js +5 -63
- package/lib/app/get-schema.js +5 -158
- package/lib/app/publish.js +5 -127
- package/lib/app/update-app.js +8 -2
- package/lib/core/locales/en.js +0 -9
- package/lib/core/locales/zh.js +0 -9
- package/lib/core/utils.js +0 -358
- package/package.json +1 -1
|
@@ -24,13 +24,12 @@
|
|
|
24
24
|
* 3) 第三方依赖以 `window.<别名>` 形式引用(antd→window.antd、react→window.React …),
|
|
25
25
|
* 这些 UMD 依赖由画布运行时依据 importedModules 白名单按需注入。
|
|
26
26
|
*
|
|
27
|
-
* 因此本地编译 = Babel 把 JSX/TS 转成
|
|
28
|
-
* 把 export default 改写成画布入口 `YidaComp` →
|
|
27
|
+
* 因此本地编译 = Babel 把 JSX/TS 转成 ES5 → 把 import 改写成 window 别名引用、
|
|
28
|
+
* 把 export default 改写成画布入口 `YidaComp` → 正则抽出依赖包名。
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
const Babel = require('@babel/standalone');
|
|
32
32
|
const globals = require('globals');
|
|
33
|
-
const UglifyJS = require('uglify-js');
|
|
34
33
|
const {
|
|
35
34
|
assertNoEmojiInArtifactName,
|
|
36
35
|
assertNoEmojiInText,
|
|
@@ -878,36 +877,6 @@ function assertCanvasRuntimeParseable(runtimeCode, options = {}) {
|
|
|
878
877
|
}
|
|
879
878
|
}
|
|
880
879
|
|
|
881
|
-
/**
|
|
882
|
-
* 压缩画布运行态代码,并显式保留装配器读取的 YidaComp 入口名。
|
|
883
|
-
*/
|
|
884
|
-
function minifyCanvasRuntime(runtimeCode, options = {}) {
|
|
885
|
-
let result;
|
|
886
|
-
try {
|
|
887
|
-
result = UglifyJS.minify(runtimeCode, {
|
|
888
|
-
compress: true,
|
|
889
|
-
mangle: {
|
|
890
|
-
reserved: ['YidaComp'],
|
|
891
|
-
},
|
|
892
|
-
});
|
|
893
|
-
} catch (error) {
|
|
894
|
-
result = { error };
|
|
895
|
-
}
|
|
896
|
-
if (!result || result.error || typeof result.code !== 'string' || !result.code) {
|
|
897
|
-
const detail = result && result.error && result.error.message
|
|
898
|
-
? result.error.message
|
|
899
|
-
: '压缩结果为空';
|
|
900
|
-
throw new CliError(`Code Canvas runtimeCode 压缩失败: ${detail}`, {
|
|
901
|
-
code: 'OPENYIDA_CANVAS_MINIFY_FAILED',
|
|
902
|
-
details: {
|
|
903
|
-
stage: 'canvas_minify',
|
|
904
|
-
sourcePath: options.sourcePath || '',
|
|
905
|
-
},
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
return result.code;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
880
|
/**
|
|
912
881
|
* 本地编译 Code Canvas 源码。
|
|
913
882
|
* @param {string} source 原始 React/JSX/TSX 源码
|
|
@@ -1015,12 +984,11 @@ function compileCanvasLocal(source, options = {}) {
|
|
|
1015
984
|
configFile: false,
|
|
1016
985
|
});
|
|
1017
986
|
|
|
1018
|
-
const
|
|
1019
|
-
assertNoEmojiInText(
|
|
987
|
+
const runtimeCode = stage2.code || '';
|
|
988
|
+
assertNoEmojiInText(runtimeCode, {
|
|
1020
989
|
artifact: options.sourcePath ? options.sourcePath + ' runtime' : 'Code Canvas runtime',
|
|
1021
990
|
code: 'OPENYIDA_CANVAS_SOURCE_EMOJI_FORBIDDEN',
|
|
1022
991
|
});
|
|
1023
|
-
const runtimeCode = minifyCanvasRuntime(unminifiedRuntimeCode, options);
|
|
1024
992
|
assertCanvasRuntimeParseable(runtimeCode, options);
|
|
1025
993
|
assertDependencyManifestConsistent(runtimeCode, importedModules, options);
|
|
1026
994
|
return {
|
|
@@ -1088,7 +1056,6 @@ module.exports = {
|
|
|
1088
1056
|
resolveWindowAlias,
|
|
1089
1057
|
shouldAllowUnsupportedBareImports,
|
|
1090
1058
|
assertCanvasRuntimeParseable,
|
|
1091
|
-
minifyCanvasRuntime,
|
|
1092
1059
|
findBareDependencyGlobalIssues,
|
|
1093
1060
|
findSelfReferentialDependencyBindingIssues,
|
|
1094
1061
|
findDependencyManifestIssues,
|
|
@@ -23,30 +23,10 @@ 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
|
-
|
|
43
26
|
function createEmptyDisplayInfo() {
|
|
44
27
|
return {
|
|
45
28
|
hasYidaCodeCanvas: false,
|
|
46
|
-
hasCodeBundle: false,
|
|
47
29
|
hasNativeJsx: false,
|
|
48
|
-
codeBundleCount: 0,
|
|
49
|
-
bundleIds: [],
|
|
50
30
|
runtimeCodeBytes: 0,
|
|
51
31
|
sourceCodeBytes: 0,
|
|
52
32
|
compiledCodeBytes: 0,
|
|
@@ -54,8 +34,6 @@ function createEmptyDisplayInfo() {
|
|
|
54
34
|
componentCount: 0,
|
|
55
35
|
canvasRuntimeCode: '',
|
|
56
36
|
canvasSourceCode: '',
|
|
57
|
-
canvasRuntimeSha256: '',
|
|
58
|
-
canvasSourceSha256: '',
|
|
59
37
|
nativeCompiledCode: '',
|
|
60
38
|
nativeSourceCode: '',
|
|
61
39
|
};
|
|
@@ -101,37 +79,12 @@ function traverseDisplayNodes(node, info) {
|
|
|
101
79
|
|
|
102
80
|
if (node.componentName === 'YidaCodeCanvas') {
|
|
103
81
|
const props = node.props || {};
|
|
104
|
-
const codeBundle = props.codeBundle && typeof props.codeBundle === 'object'
|
|
105
|
-
? props.codeBundle
|
|
106
|
-
: null;
|
|
107
82
|
info.hasYidaCodeCanvas = true;
|
|
108
83
|
info.componentCount++;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
if (typeof props.code === 'string' && props.code) {
|
|
113
|
-
info.canvasSourceCode = props.code;
|
|
114
|
-
}
|
|
84
|
+
info.canvasRuntimeCode = props.runtimeCode || '';
|
|
85
|
+
info.canvasSourceCode = props.code || '';
|
|
115
86
|
info.runtimeCodeBytes += codeBytes(props.runtimeCode);
|
|
116
87
|
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
|
-
}
|
|
135
88
|
addImportedModules(info.importedModules, props.importedModules);
|
|
136
89
|
} else if (node.componentName === 'Jsx') {
|
|
137
90
|
info.hasNativeJsx = true;
|
|
@@ -180,9 +133,7 @@ function hasExpectedDisplayComponent(info, publishMode) {
|
|
|
180
133
|
return false;
|
|
181
134
|
}
|
|
182
135
|
if (publishMode === 'canvas') {
|
|
183
|
-
return info.hasYidaCodeCanvas &&
|
|
184
|
-
info.runtimeCodeBytes > 0 || !!info.canvasRuntimeSha256
|
|
185
|
-
);
|
|
136
|
+
return info.hasYidaCodeCanvas && info.runtimeCodeBytes > 0;
|
|
186
137
|
}
|
|
187
138
|
return info.hasNativeJsx && info.compiledCodeBytes > 0;
|
|
188
139
|
}
|
|
@@ -193,10 +144,7 @@ function summarizeDisplayPublishInfo(info) {
|
|
|
193
144
|
}
|
|
194
145
|
return {
|
|
195
146
|
hasYidaCodeCanvas: info.hasYidaCodeCanvas,
|
|
196
|
-
hasCodeBundle: info.hasCodeBundle,
|
|
197
147
|
hasNativeJsx: info.hasNativeJsx,
|
|
198
|
-
codeBundleCount: info.codeBundleCount,
|
|
199
|
-
bundleIds: info.bundleIds.slice(),
|
|
200
148
|
runtimeCodeBytes: info.runtimeCodeBytes,
|
|
201
149
|
sourceCodeBytes: info.sourceCodeBytes,
|
|
202
150
|
compiledCodeBytes: info.compiledCodeBytes,
|
|
@@ -211,13 +159,8 @@ function verifyPublishedContentMatch(readbackSchema, expectedSchemaContent, publ
|
|
|
211
159
|
const displayComponentPresent = hasExpectedDisplayComponent(readbackInfo, publishMode);
|
|
212
160
|
const readbackArtifact = getPublishArtifact(readbackInfo, publishMode);
|
|
213
161
|
const expectedArtifact = getPublishArtifact(expectedInfo, publishMode);
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
? readbackInfo.canvasRuntimeSha256
|
|
217
|
-
: fingerprint(readbackArtifact);
|
|
218
|
-
const expectedFingerprint = codeBundleReadback
|
|
219
|
-
? rawFingerprint(expectedArtifact)
|
|
220
|
-
: fingerprint(expectedArtifact);
|
|
162
|
+
const readbackFingerprint = fingerprint(readbackArtifact);
|
|
163
|
+
const expectedFingerprint = fingerprint(expectedArtifact);
|
|
221
164
|
|
|
222
165
|
return {
|
|
223
166
|
readbackInfo,
|
|
@@ -237,7 +180,6 @@ function verifyPublishedContentMatch(readbackSchema, expectedSchemaContent, publ
|
|
|
237
180
|
module.exports = {
|
|
238
181
|
extractDisplayPublishInfo,
|
|
239
182
|
fingerprint,
|
|
240
|
-
rawFingerprint,
|
|
241
183
|
hasExpectedDisplayComponent,
|
|
242
184
|
parseSchemaContent,
|
|
243
185
|
summarizeDisplayPublishInfo,
|
package/lib/app/get-schema.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
-
const crypto = require('crypto');
|
|
13
12
|
const fs = require('fs');
|
|
14
13
|
const path = require('path');
|
|
15
14
|
const {
|
|
@@ -17,7 +16,6 @@ const {
|
|
|
17
16
|
triggerLogin,
|
|
18
17
|
resolveBaseUrl,
|
|
19
18
|
httpGet,
|
|
20
|
-
httpGetCodeBundleText,
|
|
21
19
|
requestWithAutoLogin,
|
|
22
20
|
} = require('../core/utils');
|
|
23
21
|
const { t } = require('../core/i18n');
|
|
@@ -229,122 +227,6 @@ function resolveSchemaContent(schemaResult) {
|
|
|
229
227
|
return parseJsonObject(content);
|
|
230
228
|
}
|
|
231
229
|
|
|
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) => httpGetCodeBundleText(
|
|
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
|
-
|
|
348
230
|
function addImportedModules(target, value) {
|
|
349
231
|
let modules = value;
|
|
350
232
|
if (typeof modules === 'string') {
|
|
@@ -387,10 +269,7 @@ function extractDisplayPageSummary(schemaResult) {
|
|
|
387
269
|
|
|
388
270
|
const displayPage = {
|
|
389
271
|
hasYidaCodeCanvas: false,
|
|
390
|
-
hasCodeBundle: false,
|
|
391
272
|
hasNativeJsx: false,
|
|
392
|
-
codeBundleCount: 0,
|
|
393
|
-
bundleIds: [],
|
|
394
273
|
runtimeCodeBytes: 0,
|
|
395
274
|
sourceCodeBytes: 0,
|
|
396
275
|
compiledCodeBytes: 0,
|
|
@@ -412,29 +291,10 @@ function extractDisplayPageSummary(schemaResult) {
|
|
|
412
291
|
|
|
413
292
|
if (node.componentName === 'YidaCodeCanvas' && isComponentInstance(node)) {
|
|
414
293
|
const props = node.props || {};
|
|
415
|
-
const codeBundle = props.codeBundle && typeof props.codeBundle === 'object'
|
|
416
|
-
? props.codeBundle
|
|
417
|
-
: null;
|
|
418
294
|
displayPage.hasYidaCodeCanvas = true;
|
|
419
295
|
displayPage.componentCount++;
|
|
420
296
|
displayPage.runtimeCodeBytes += codeBytes(props.runtimeCode);
|
|
421
297
|
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
|
-
}
|
|
438
298
|
addImportedModules(displayPage.importedModules, props.importedModules);
|
|
439
299
|
} else if (node.componentName === 'Jsx' && isComponentInstance(node)) {
|
|
440
300
|
displayPage.hasNativeJsx = true;
|
|
@@ -721,15 +581,12 @@ async function mapLimit(items, limit, iterator) {
|
|
|
721
581
|
return results;
|
|
722
582
|
}
|
|
723
583
|
|
|
724
|
-
async function fetchSchemaRecord(appType, form, authRef, retries
|
|
584
|
+
async function fetchSchemaRecord(appType, form, authRef, retries) {
|
|
725
585
|
let lastError = null;
|
|
726
586
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
727
587
|
try {
|
|
728
588
|
const result = await fetchSchema(appType, form.formUuid, authRef);
|
|
729
589
|
if (isSuccessfulSchemaResult(result)) {
|
|
730
|
-
const resolvedResult = options.resolveCodeBundles
|
|
731
|
-
? await resolveCodeBundleSchema(result, appType, form.formUuid, authRef)
|
|
732
|
-
: result;
|
|
733
590
|
const record = {
|
|
734
591
|
formUuid: form.formUuid,
|
|
735
592
|
formName: form.formName,
|
|
@@ -737,10 +594,10 @@ async function fetchSchemaRecord(appType, form, authRef, retries, options = {})
|
|
|
737
594
|
pathName: form.pathName,
|
|
738
595
|
success: true,
|
|
739
596
|
attempts: attempt + 1,
|
|
740
|
-
fieldSummary: extractFieldSummary(
|
|
741
|
-
schema:
|
|
597
|
+
fieldSummary: extractFieldSummary(result),
|
|
598
|
+
schema: result,
|
|
742
599
|
};
|
|
743
|
-
const displayPage = extractDisplayPageSummary(
|
|
600
|
+
const displayPage = extractDisplayPageSummary(result);
|
|
744
601
|
if (displayPage) {
|
|
745
602
|
record.displayPage = displayPage;
|
|
746
603
|
}
|
|
@@ -837,10 +694,6 @@ async function runSingle(parsed, authRef) {
|
|
|
837
694
|
throwCommandError(errorMsg);
|
|
838
695
|
}
|
|
839
696
|
|
|
840
|
-
if (!parsed.field && !parsed.compact && !parsed.summaryJson) {
|
|
841
|
-
result = await resolveCodeBundleSchema(result, parsed.appType, parsed.formUuid, authRef);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
697
|
// 保留既有 --field 返回结构;新 contract 仅由 --compact / --resolve-fields 触发。
|
|
845
698
|
if (parsed.field) {
|
|
846
699
|
const allFieldNodes = collectFieldNodes(result);
|
|
@@ -929,9 +782,7 @@ async function runBatch(parsed, authRef) {
|
|
|
929
782
|
info(` 批量获取 ${forms.length} 个表单 Schema,并发 ${parsed.concurrency},重试 ${parsed.retries}`);
|
|
930
783
|
|
|
931
784
|
const records = await mapLimit(forms, parsed.concurrency, async (form) => {
|
|
932
|
-
const record = await fetchSchemaRecord(parsed.appType, form, authRef, parsed.retries
|
|
933
|
-
resolveCodeBundles: !parsed.summaryJson,
|
|
934
|
-
});
|
|
785
|
+
const record = await fetchSchemaRecord(parsed.appType, form, authRef, parsed.retries);
|
|
935
786
|
if (record.success && parsed.analysisJson) {
|
|
936
787
|
record.semanticAnalysis = buildSemanticAnalysis(
|
|
937
788
|
parsed.appType,
|
|
@@ -997,9 +848,5 @@ module.exports = {
|
|
|
997
848
|
fetchSchemaRecord,
|
|
998
849
|
collectFieldNodes,
|
|
999
850
|
findFieldNode,
|
|
1000
|
-
validateCodeBundleDescriptor,
|
|
1001
|
-
collectCodeBundleCanvases,
|
|
1002
|
-
downloadCodeBundleArtifact,
|
|
1003
|
-
resolveCodeBundleSchema,
|
|
1004
851
|
run,
|
|
1005
852
|
};
|
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,将 source/compiled 填入 actions.module
|
|
14
14
|
* 3. 读取 token session,在写前确认登录态
|
|
15
|
-
* 4.
|
|
15
|
+
* 4. 通过 HTTP POST 调用 saveFormSchema,token 失效时尝试 token refresh
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const fs = require('fs');
|
|
@@ -21,7 +21,6 @@ const querystring = require('querystring');
|
|
|
21
21
|
const {
|
|
22
22
|
findProjectRoot,
|
|
23
23
|
httpPost,
|
|
24
|
-
httpPostMultipart,
|
|
25
24
|
httpGet,
|
|
26
25
|
requestWithAutoLogin,
|
|
27
26
|
} = require('../core/utils');
|
|
@@ -60,8 +59,6 @@ const {
|
|
|
60
59
|
const SCHEMA_VERSION = 'V5';
|
|
61
60
|
const DOMAIN_CODE = 'tEXDRG';
|
|
62
61
|
const PREFIX = '_view';
|
|
63
|
-
const MAX_CODE_BUNDLE_BYTES = 5 * 1024 * 1024;
|
|
64
|
-
const MAX_CANVAS_SCHEMA_SKELETON_BYTES = 1024 * 1024;
|
|
65
62
|
|
|
66
63
|
// ── 参数解析 ─────────────────────────────────────────
|
|
67
64
|
|
|
@@ -288,61 +285,6 @@ function buildCanvasSchemaContent(sourceCode, runtimeCode, importedModules, form
|
|
|
288
285
|
return buildCanvasPageSchemaContent(sourceCode, runtimeCode, importedModules, formUuid);
|
|
289
286
|
}
|
|
290
287
|
|
|
291
|
-
function buildCanvasSavePayload(schemaContent, sourceCode, runtimeCode) {
|
|
292
|
-
if (typeof sourceCode !== 'string' || sourceCode.length === 0
|
|
293
|
-
|| typeof runtimeCode !== 'string' || runtimeCode.length === 0) {
|
|
294
|
-
throw new Error('Canvas source/runtime 不能为空');
|
|
295
|
-
}
|
|
296
|
-
const totalCodeBytes = Buffer.byteLength(sourceCode, 'utf8')
|
|
297
|
-
+ Buffer.byteLength(runtimeCode, 'utf8');
|
|
298
|
-
if (totalCodeBytes > MAX_CODE_BUNDLE_BYTES) {
|
|
299
|
-
throw new Error('Canvas source/runtime 总大小不能超过 5 MiB');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
let schema;
|
|
303
|
-
try {
|
|
304
|
-
schema = JSON.parse(schemaContent);
|
|
305
|
-
} catch {
|
|
306
|
-
throw new Error('Canvas Schema 不是有效 JSON');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const canvases = [];
|
|
310
|
-
function traverse(node) {
|
|
311
|
-
if (Array.isArray(node)) {
|
|
312
|
-
node.forEach(traverse);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
if (!node || typeof node !== 'object') {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
if (node.componentName === 'YidaCodeCanvas' && node.id) {
|
|
319
|
-
canvases.push(node);
|
|
320
|
-
}
|
|
321
|
-
Object.values(node).forEach(traverse);
|
|
322
|
-
}
|
|
323
|
-
traverse(schema);
|
|
324
|
-
if (canvases.length !== 1) {
|
|
325
|
-
throw new Error('Canvas Schema 必须且只能包含一个 YidaCodeCanvas 节点');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const canvas = canvases[0];
|
|
329
|
-
canvas.props = canvas.props && typeof canvas.props === 'object' ? canvas.props : {};
|
|
330
|
-
delete canvas.props.code;
|
|
331
|
-
delete canvas.props.runtimeCode;
|
|
332
|
-
delete canvas.props.codeBundle;
|
|
333
|
-
|
|
334
|
-
const skeletonContent = JSON.stringify(schema);
|
|
335
|
-
if (Buffer.byteLength(skeletonContent, 'utf8') > MAX_CANVAS_SCHEMA_SKELETON_BYTES) {
|
|
336
|
-
throw new Error('Canvas Schema 骨架不能超过 1 MiB');
|
|
337
|
-
}
|
|
338
|
-
return {
|
|
339
|
-
canvasNodeId: canvas.id,
|
|
340
|
-
content: skeletonContent,
|
|
341
|
-
sourceCode,
|
|
342
|
-
runtimeCode,
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
|
|
346
288
|
|
|
347
289
|
// ── 4. 发送 saveFormSchema 请求 ──────────────────────
|
|
348
290
|
|
|
@@ -372,40 +314,6 @@ function sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serv
|
|
|
372
314
|
return httpPost(authRef.baseUrl, saveSchemaPath, postData, { silentStatus: true });
|
|
373
315
|
}
|
|
374
316
|
|
|
375
|
-
function sendCanvasSaveRequestWithAuth(authRef, payload, appType, formUuid, serverRevision) {
|
|
376
|
-
if (
|
|
377
|
-
!authRef ||
|
|
378
|
-
typeof authRef.baseUrl !== 'string' ||
|
|
379
|
-
authRef.baseUrl.length === 0 ||
|
|
380
|
-
!isTokenAuthRef(authRef)
|
|
381
|
-
) {
|
|
382
|
-
const authError = new Error('Publish Canvas write authentication is not ready.');
|
|
383
|
-
authError.code = 'PUBLISH_SCHEMA_WRITE_PRECHECK_FAILED';
|
|
384
|
-
return Promise.reject(authError);
|
|
385
|
-
}
|
|
386
|
-
const gmtModified = requireSchemaServerRevision({ gmtModified: serverRevision });
|
|
387
|
-
const savePath = `/alibaba/web/${appType}/query/codeBundle/save.json?_stamp=${Date.now()}`;
|
|
388
|
-
return httpPostMultipart(authRef.baseUrl, savePath, {
|
|
389
|
-
_csrf_token: authRef.csrfToken,
|
|
390
|
-
formUuid,
|
|
391
|
-
gmtModified,
|
|
392
|
-
canvasNodeId: payload.canvasNodeId,
|
|
393
|
-
content: payload.content,
|
|
394
|
-
importSchema: true,
|
|
395
|
-
}, {
|
|
396
|
-
source: {
|
|
397
|
-
content: payload.sourceCode,
|
|
398
|
-
fileName: 'source.jsx',
|
|
399
|
-
contentType: 'text/plain; charset=UTF-8',
|
|
400
|
-
},
|
|
401
|
-
runtime: {
|
|
402
|
-
content: payload.runtimeCode,
|
|
403
|
-
fileName: 'runtime.js',
|
|
404
|
-
contentType: 'application/javascript; charset=UTF-8',
|
|
405
|
-
},
|
|
406
|
-
}, { silentStatus: true });
|
|
407
|
-
}
|
|
408
|
-
|
|
409
317
|
function walkFiles(dir, results, limit) {
|
|
410
318
|
if (!fs.existsSync(dir) || results.length >= limit) {
|
|
411
319
|
return;
|
|
@@ -716,8 +624,6 @@ async function runMain(argv) {
|
|
|
716
624
|
baseUrl = authRef.baseUrl;
|
|
717
625
|
|
|
718
626
|
let schemaContent;
|
|
719
|
-
let expectedSchemaContent;
|
|
720
|
-
let canvasSavePayload;
|
|
721
627
|
let serverRevision;
|
|
722
628
|
if (isCanvas) {
|
|
723
629
|
try {
|
|
@@ -727,18 +633,7 @@ async function runMain(argv) {
|
|
|
727
633
|
process.exit(1);
|
|
728
634
|
}
|
|
729
635
|
// canvasResult 已在登录前本地编译完成,这里只需据其装配 Schema content。
|
|
730
|
-
|
|
731
|
-
sourceCode,
|
|
732
|
-
canvasResult.runtimeCode,
|
|
733
|
-
canvasResult.importedModules,
|
|
734
|
-
formUuid
|
|
735
|
-
);
|
|
736
|
-
canvasSavePayload = buildCanvasSavePayload(
|
|
737
|
-
expectedSchemaContent,
|
|
738
|
-
sourceCode,
|
|
739
|
-
canvasResult.runtimeCode
|
|
740
|
-
);
|
|
741
|
-
schemaContent = canvasSavePayload.content;
|
|
636
|
+
schemaContent = buildCanvasSchemaContent(sourceCode, canvasResult.runtimeCode, canvasResult.importedModules, formUuid);
|
|
742
637
|
} else {
|
|
743
638
|
let existingDataSource = null;
|
|
744
639
|
try {
|
|
@@ -751,7 +646,6 @@ async function runMain(argv) {
|
|
|
751
646
|
}
|
|
752
647
|
baseUrl = authRef.baseUrl;
|
|
753
648
|
schemaContent = buildSchemaContent(sourceCode, compiledCode, formUuid, { existingDataSource });
|
|
754
|
-
expectedSchemaContent = schemaContent;
|
|
755
649
|
}
|
|
756
650
|
success(t('publish.schema_built'));
|
|
757
651
|
|
|
@@ -762,15 +656,7 @@ async function runMain(argv) {
|
|
|
762
656
|
label('Source:', sourcePath);
|
|
763
657
|
label('Compiled:', compiledPath);
|
|
764
658
|
step(3, t('publish.step_publish'));
|
|
765
|
-
const response =
|
|
766
|
-
? await sendCanvasSaveRequestWithAuth(
|
|
767
|
-
authRef,
|
|
768
|
-
canvasSavePayload,
|
|
769
|
-
appType,
|
|
770
|
-
formUuid,
|
|
771
|
-
serverRevision
|
|
772
|
-
)
|
|
773
|
-
: await sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serverRevision);
|
|
659
|
+
const response = await sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serverRevision);
|
|
774
660
|
|
|
775
661
|
if (!response || !response.success) {
|
|
776
662
|
const errorMsg = response ? response.errorMsg || t('common.unknown_error') : t('common.request_failed');
|
|
@@ -784,9 +670,6 @@ async function runMain(argv) {
|
|
|
784
670
|
const content = response.content || {};
|
|
785
671
|
const savedFormUuid = content.formUuid || formUuid;
|
|
786
672
|
const version = content.version || 0;
|
|
787
|
-
const storageMode = isCanvas ? content.storageMode || null : undefined;
|
|
788
|
-
const bundleId = isCanvas ? content.bundleId || null : undefined;
|
|
789
|
-
const fallbackReason = isCanvas ? content.fallbackReason || null : undefined;
|
|
790
673
|
success(t('publish.schema_published'));
|
|
791
674
|
label('Form UUID:', savedFormUuid);
|
|
792
675
|
label('Version:', String(version));
|
|
@@ -805,7 +688,7 @@ async function runMain(argv) {
|
|
|
805
688
|
appType,
|
|
806
689
|
savedFormUuid,
|
|
807
690
|
authRef,
|
|
808
|
-
|
|
691
|
+
schemaContent,
|
|
809
692
|
isCanvas ? 'canvas' : 'native'
|
|
810
693
|
);
|
|
811
694
|
} catch (healthCheckError) {
|
|
@@ -871,9 +754,6 @@ async function runMain(argv) {
|
|
|
871
754
|
version,
|
|
872
755
|
url: pageUrl,
|
|
873
756
|
publishMode,
|
|
874
|
-
storageMode,
|
|
875
|
-
bundleId,
|
|
876
|
-
fallbackReason,
|
|
877
757
|
publishReadbackVerified: !!(healthCheckResult && healthCheckResult.ok === true),
|
|
878
758
|
runtimeSmokeVerified: false,
|
|
879
759
|
runtimeSmokeStatus: 'not_checked',
|
|
@@ -914,7 +794,5 @@ if (require.main === module) {
|
|
|
914
794
|
module.exports.countCustomPageDataSources = countCustomPageDataSources;
|
|
915
795
|
module.exports.buildSchemaContent = buildSchemaContent;
|
|
916
796
|
module.exports.buildCanvasSchemaContent = buildCanvasSchemaContent;
|
|
917
|
-
module.exports.buildCanvasSavePayload = buildCanvasSavePayload;
|
|
918
797
|
module.exports.sendSaveRequestWithAuth = sendSaveRequestWithAuth;
|
|
919
|
-
module.exports.sendCanvasSaveRequestWithAuth = sendCanvasSaveRequestWithAuth;
|
|
920
798
|
}
|
package/lib/app/update-app.js
CHANGED
|
@@ -294,6 +294,8 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
|
294
294
|
|
|
295
295
|
const currentThemeColor = pickAppField(currentApp, 'themeColor', 'THEME_COLOR');
|
|
296
296
|
const currentCustomThemeStyle = pickAppField(currentApp, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
|
|
297
|
+
const addWaterMark = pickAppField(currentApp, 'addWaterMark', 'ADDWATERMARK');
|
|
298
|
+
const sentryMode = pickAppField(currentApp, 'sentryMode', 'SENTRY_MODE');
|
|
297
299
|
if (params.colour === 'custom' && !params.themeColor && !currentThemeColor) {
|
|
298
300
|
throw new Error(t('update_app.custom_theme_color_required'));
|
|
299
301
|
}
|
|
@@ -342,10 +344,14 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
|
342
344
|
navigation: currentApp.navigation || (currentApp.config && currentApp.config.NAVIGATION) || 'TODO,DONE,SUBMIT',
|
|
343
345
|
pageHeader: currentApp.pageHeader || '',
|
|
344
346
|
pageFooter: currentApp.pageFooter || '',
|
|
345
|
-
addWaterMark: currentApp.addWaterMark || (currentApp.config && currentApp.config.ADDWATERMARK) || 'y',
|
|
346
|
-
sentryMode: currentApp.sentryMode || (currentApp.config && currentApp.config.SENTRY_MODE) || 'y',
|
|
347
347
|
};
|
|
348
348
|
|
|
349
|
+
if (addWaterMark !== undefined && addWaterMark !== null) {
|
|
350
|
+
postDataObj.addWaterMark = addWaterMark;
|
|
351
|
+
}
|
|
352
|
+
if (sentryMode !== undefined && sentryMode !== null) {
|
|
353
|
+
postDataObj.sentryMode = sentryMode;
|
|
354
|
+
}
|
|
349
355
|
if (params.hideAppNav !== null && params.hideAppNav !== undefined) {
|
|
350
356
|
postDataObj.hideAppNav = params.hideAppNav;
|
|
351
357
|
} else {
|
package/lib/core/locales/en.js
CHANGED
|
@@ -902,15 +902,6 @@ Examples:
|
|
|
902
902
|
response_body: ' Response body: {0}',
|
|
903
903
|
response_detail: ' Response detail: {0}',
|
|
904
904
|
response_not_json: 'response is not JSON',
|
|
905
|
-
code_bundle_download_failed: 'CodeBundle download failed: {0} ({1})',
|
|
906
|
-
code_bundle_forbidden: 'permission verification or OSS access was denied',
|
|
907
|
-
code_bundle_not_found: 'the file does not exist or has been removed',
|
|
908
|
-
code_bundle_http_error: 'HTTP {0}',
|
|
909
|
-
code_bundle_html_response: 'received an HTML error page; the environment or route may be incorrect',
|
|
910
|
-
code_bundle_json_response: 'the service returned a JSON error response: {0}',
|
|
911
|
-
code_bundle_unexpected_content_type: 'unexpected response Content-Type: {0}',
|
|
912
|
-
code_bundle_invalid_utf8: 'the response is not valid UTF-8 text',
|
|
913
|
-
code_bundle_integrity_failed: 'CodeBundle {0} integrity verification failed ({1})',
|
|
914
905
|
login_expired: ' Login session expired: {0}',
|
|
915
906
|
csrf_expired: ' CSRF token expired: {0}',
|
|
916
907
|
csrf_refreshed: ' csrf_token refreshed',
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -873,15 +873,6 @@ openyida - 宜搭命令行工具
|
|
|
873
873
|
response_body: ' 响应内容: {0}',
|
|
874
874
|
response_detail: ' 响应详情: {0}',
|
|
875
875
|
response_not_json: '响应非 JSON',
|
|
876
|
-
code_bundle_download_failed: 'CodeBundle 下载失败:{0}({1})',
|
|
877
|
-
code_bundle_forbidden: '权限校验或 OSS 访问被拒绝',
|
|
878
|
-
code_bundle_not_found: '文件不存在或已被清理',
|
|
879
|
-
code_bundle_http_error: 'HTTP {0}',
|
|
880
|
-
code_bundle_html_response: '返回了 HTML 错误页,可能是环境或路由错误',
|
|
881
|
-
code_bundle_json_response: '服务端返回 JSON 错误响应:{0}',
|
|
882
|
-
code_bundle_unexpected_content_type: '响应 Content-Type 不符合预期:{0}',
|
|
883
|
-
code_bundle_invalid_utf8: '响应内容不是有效 UTF-8 文本',
|
|
884
|
-
code_bundle_integrity_failed: 'CodeBundle {0} 完整性校验失败({1})',
|
|
885
876
|
login_expired: ' 检测到登录过期: {0}',
|
|
886
877
|
csrf_expired: ' 检测到 csrf_token 过期: {0}',
|
|
887
878
|
csrf_refreshed: ' csrf_token 已刷新',
|
package/lib/core/utils.js
CHANGED
|
@@ -1080,362 +1080,6 @@ function createNonJsonResponseResult(statusCode, data) {
|
|
|
1080
1080
|
};
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
|
-
function buildRequestUrl(baseUrl, requestPath, queryParams) {
|
|
1084
|
-
const normalizedBaseUrl = String(baseUrl || '').replace(/\/+$/, '') + '/';
|
|
1085
|
-
const requestUrl = new URL(requestPath, normalizedBaseUrl);
|
|
1086
|
-
if (queryParams) {
|
|
1087
|
-
Object.entries(queryParams).forEach(([key, value]) => {
|
|
1088
|
-
if (value !== undefined && value !== null) {
|
|
1089
|
-
requestUrl.searchParams.set(key, String(value));
|
|
1090
|
-
}
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
return requestUrl;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
async function fetchWithTimeout(requestUrl, requestOptions, timeout) {
|
|
1097
|
-
const controller = new AbortController();
|
|
1098
|
-
const timer = setTimeout(() => controller.abort(), timeout || 30000);
|
|
1099
|
-
try {
|
|
1100
|
-
return await fetch(requestUrl, {
|
|
1101
|
-
...requestOptions,
|
|
1102
|
-
signal: controller.signal,
|
|
1103
|
-
});
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
if (error && error.name === 'AbortError') {
|
|
1106
|
-
throw new Error(t('common.request_timeout'));
|
|
1107
|
-
}
|
|
1108
|
-
throw error;
|
|
1109
|
-
} finally {
|
|
1110
|
-
clearTimeout(timer);
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
async function parseJsonFetchResponse(response, options = {}) {
|
|
1115
|
-
const data = await response.text();
|
|
1116
|
-
if (!options.silentStatus) {
|
|
1117
|
-
warn(t('common.http_status', response.status));
|
|
1118
|
-
}
|
|
1119
|
-
if (isHttpRedirectStatus(response.status) || isHttpAuthStatus(response.status)) {
|
|
1120
|
-
return {
|
|
1121
|
-
__needLogin: true,
|
|
1122
|
-
__httpStatus: response.status,
|
|
1123
|
-
__location: response.headers.get('location') || '',
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
try {
|
|
1127
|
-
const parsed = JSON.parse(data);
|
|
1128
|
-
if (isLoginExpired(parsed)) {
|
|
1129
|
-
return { __needLogin: true };
|
|
1130
|
-
}
|
|
1131
|
-
if (isCsrfTokenExpired(parsed)) {
|
|
1132
|
-
return { __csrfExpired: true };
|
|
1133
|
-
}
|
|
1134
|
-
return parsed;
|
|
1135
|
-
} catch {
|
|
1136
|
-
if (!options.silentStatus) {
|
|
1137
|
-
warn(t('common.http_response', data.substring(0, 500)));
|
|
1138
|
-
}
|
|
1139
|
-
return createNonJsonResponseResult(response.status, data);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
function appendMultipartFile(form, fieldName, file) {
|
|
1144
|
-
if (!file || file.content === undefined || file.content === null) {
|
|
1145
|
-
throw new TypeError(`multipart file ${fieldName} is required`);
|
|
1146
|
-
}
|
|
1147
|
-
const content = Buffer.isBuffer(file.content)
|
|
1148
|
-
? file.content
|
|
1149
|
-
: Buffer.from(String(file.content), 'utf8');
|
|
1150
|
-
form.append(
|
|
1151
|
-
fieldName,
|
|
1152
|
-
new Blob([content], { type: file.contentType || 'application/octet-stream' }),
|
|
1153
|
-
file.fileName || fieldName
|
|
1154
|
-
);
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
/**
|
|
1158
|
-
* 发送 multipart/form-data POST。Content-Type boundary 由 Node.js FormData 生成。
|
|
1159
|
-
*/
|
|
1160
|
-
async function httpPostMultipart(baseUrl, requestPath, fields, files, optionsOrLegacyCookies, maybeOptions) {
|
|
1161
|
-
if (typeof fetch !== 'function' || typeof FormData !== 'function' || typeof Blob !== 'function') {
|
|
1162
|
-
throw new Error('当前 Node.js 环境缺少 fetch/FormData/Blob,请使用 Node.js 18+');
|
|
1163
|
-
}
|
|
1164
|
-
const optionsOverride = resolveRequestOptions(optionsOrLegacyCookies, maybeOptions);
|
|
1165
|
-
const authHeaders = await resolveRequestAuthHeaders(optionsOverride);
|
|
1166
|
-
const form = new FormData();
|
|
1167
|
-
Object.entries(fields || {}).forEach(([key, value]) => {
|
|
1168
|
-
if (value !== undefined && value !== null) {
|
|
1169
|
-
form.append(key, String(value));
|
|
1170
|
-
}
|
|
1171
|
-
});
|
|
1172
|
-
Object.entries(files || {}).forEach(([key, file]) => appendMultipartFile(form, key, file));
|
|
1173
|
-
|
|
1174
|
-
const requestUrl = buildRequestUrl(baseUrl, requestPath);
|
|
1175
|
-
const response = await fetchWithTimeout(requestUrl, {
|
|
1176
|
-
method: 'POST',
|
|
1177
|
-
redirect: 'manual',
|
|
1178
|
-
headers: {
|
|
1179
|
-
Accept: 'application/json, text/plain, */*',
|
|
1180
|
-
Origin: requestUrl.origin,
|
|
1181
|
-
Referer: optionsOverride.referer || requestUrl.origin + '/',
|
|
1182
|
-
'x-requested-with': 'XMLHttpRequest',
|
|
1183
|
-
...authHeaders,
|
|
1184
|
-
},
|
|
1185
|
-
body: form,
|
|
1186
|
-
}, optionsOverride.timeout);
|
|
1187
|
-
return parseJsonFetchResponse(response, optionsOverride);
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
async function readFetchResponseBuffer(response, maxBytes, timeout) {
|
|
1191
|
-
const limit = Number(maxBytes);
|
|
1192
|
-
const timeoutMs = Number(timeout) || 30000;
|
|
1193
|
-
const contentLength = Number(response.headers.get('content-length') || 0);
|
|
1194
|
-
if (Number.isFinite(limit) && limit > 0 && contentLength > limit) {
|
|
1195
|
-
const error = new Error(`响应内容超过允许大小 ${limit} bytes`);
|
|
1196
|
-
error.code = 'HTTP_RESPONSE_TOO_LARGE';
|
|
1197
|
-
throw error;
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
1201
|
-
let timer;
|
|
1202
|
-
const arrayBuffer = await Promise.race([
|
|
1203
|
-
response.arrayBuffer(),
|
|
1204
|
-
new Promise((resolve, reject) => {
|
|
1205
|
-
void resolve;
|
|
1206
|
-
timer = setTimeout(() => reject(new Error(t('common.request_timeout'))), timeoutMs);
|
|
1207
|
-
}),
|
|
1208
|
-
]).finally(() => clearTimeout(timer));
|
|
1209
|
-
const buffer = Buffer.from(arrayBuffer);
|
|
1210
|
-
if (Number.isFinite(limit) && limit > 0 && buffer.length > limit) {
|
|
1211
|
-
const error = new Error(`响应内容超过允许大小 ${limit} bytes`);
|
|
1212
|
-
error.code = 'HTTP_RESPONSE_TOO_LARGE';
|
|
1213
|
-
throw error;
|
|
1214
|
-
}
|
|
1215
|
-
return buffer;
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
const chunks = [];
|
|
1219
|
-
let size = 0;
|
|
1220
|
-
const reader = response.body.getReader();
|
|
1221
|
-
let timedOut = false;
|
|
1222
|
-
const timer = setTimeout(() => {
|
|
1223
|
-
timedOut = true;
|
|
1224
|
-
void reader.cancel();
|
|
1225
|
-
}, timeoutMs);
|
|
1226
|
-
try {
|
|
1227
|
-
for (;;) {
|
|
1228
|
-
const { done, value } = await reader.read();
|
|
1229
|
-
if (done) {
|
|
1230
|
-
break;
|
|
1231
|
-
}
|
|
1232
|
-
const chunk = Buffer.from(value);
|
|
1233
|
-
size += chunk.length;
|
|
1234
|
-
if (Number.isFinite(limit) && limit > 0 && size > limit) {
|
|
1235
|
-
await reader.cancel();
|
|
1236
|
-
const error = new Error(`响应内容超过允许大小 ${limit} bytes`);
|
|
1237
|
-
error.code = 'HTTP_RESPONSE_TOO_LARGE';
|
|
1238
|
-
throw error;
|
|
1239
|
-
}
|
|
1240
|
-
chunks.push(chunk);
|
|
1241
|
-
}
|
|
1242
|
-
if (timedOut) {
|
|
1243
|
-
throw new Error(t('common.request_timeout'));
|
|
1244
|
-
}
|
|
1245
|
-
} finally {
|
|
1246
|
-
clearTimeout(timer);
|
|
1247
|
-
reader.releaseLock();
|
|
1248
|
-
}
|
|
1249
|
-
return Buffer.concat(chunks, size);
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
function normalizeResponseContentType(value) {
|
|
1253
|
-
return String(value || '').split(';')[0].trim().toLowerCase();
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
function responseRequestId(response) {
|
|
1257
|
-
return response.headers.get('x-request-id') || '';
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
function buildCodeBundleResponseMetadata(requestUrl, response) {
|
|
1261
|
-
const metadata = {
|
|
1262
|
-
baseUrl: requestUrl.origin,
|
|
1263
|
-
finalHost: requestUrl.host,
|
|
1264
|
-
status: response.status,
|
|
1265
|
-
contentType: normalizeResponseContentType(response.headers.get('content-type')),
|
|
1266
|
-
eagleeyeTraceId: response.headers.get('eagleeye-traceid') || '',
|
|
1267
|
-
requestId: responseRequestId(response),
|
|
1268
|
-
};
|
|
1269
|
-
metadata.context = [
|
|
1270
|
-
`baseUrl=${metadata.baseUrl}`,
|
|
1271
|
-
`finalHost=${metadata.finalHost}`,
|
|
1272
|
-
`status=${metadata.status}`,
|
|
1273
|
-
`contentType=${metadata.contentType || 'missing'}`,
|
|
1274
|
-
metadata.eagleeyeTraceId ? `eagleeyeTraceId=${metadata.eagleeyeTraceId}` : '',
|
|
1275
|
-
metadata.requestId ? `requestId=${metadata.requestId}` : '',
|
|
1276
|
-
].filter(Boolean).join(', ');
|
|
1277
|
-
return metadata;
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
function createCodeBundleDownloadError(code, reason, metadata, responsePreview) {
|
|
1281
|
-
const suffix = responsePreview ? `; response=${responsePreview}` : '';
|
|
1282
|
-
const error = new Error(t('common.code_bundle_download_failed', reason, metadata.context) + suffix);
|
|
1283
|
-
error.code = code;
|
|
1284
|
-
error.details = {
|
|
1285
|
-
...metadata,
|
|
1286
|
-
responsePreview: responsePreview || undefined,
|
|
1287
|
-
};
|
|
1288
|
-
return error;
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
function compactResponsePreview(buffer) {
|
|
1292
|
-
return buffer.toString('utf8').replace(/\s+/g, ' ').trim().substring(0, 500);
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
async function readErrorResponsePreview(response, timeout) {
|
|
1296
|
-
try {
|
|
1297
|
-
return compactResponsePreview(await readFetchResponseBuffer(response, 4096, timeout));
|
|
1298
|
-
} catch {
|
|
1299
|
-
return '';
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
function looksLikeHtml(buffer) {
|
|
1304
|
-
const prefix = buffer.toString('utf8', 0, Math.min(buffer.length, 256)).trimStart().toLowerCase();
|
|
1305
|
-
return prefix.startsWith('<!doctype html')
|
|
1306
|
-
|| prefix.startsWith('<html')
|
|
1307
|
-
|| prefix.startsWith('<head')
|
|
1308
|
-
|| prefix.startsWith('<body');
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
function expectedContentTypeMatches(actualContentType, expectedContentTypes) {
|
|
1312
|
-
if (!Array.isArray(expectedContentTypes) || expectedContentTypes.length === 0) {
|
|
1313
|
-
return true;
|
|
1314
|
-
}
|
|
1315
|
-
return expectedContentTypes.some(value => normalizeResponseContentType(value) === actualContentType);
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
function jsonResponseReason(preview) {
|
|
1319
|
-
if (!preview) {
|
|
1320
|
-
return t('common.code_bundle_json_response', t('common.unknown_error'));
|
|
1321
|
-
}
|
|
1322
|
-
try {
|
|
1323
|
-
const payload = JSON.parse(preview);
|
|
1324
|
-
const message = payload && (payload.errorMsg || payload.message || payload.throwable || payload.errorCode);
|
|
1325
|
-
return t('common.code_bundle_json_response', message || t('common.unknown_error'));
|
|
1326
|
-
} catch {
|
|
1327
|
-
return t('common.code_bundle_json_response', t('common.unknown_error'));
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
/**
|
|
1332
|
-
* 从宜搭首方接口直接获取 CodeBundle 文本。
|
|
1333
|
-
*/
|
|
1334
|
-
async function httpGetCodeBundleText(baseUrl, requestPath, queryParams, optionsOrLegacyCookies, maybeOptions) {
|
|
1335
|
-
if (typeof fetch !== 'function') {
|
|
1336
|
-
throw new Error('当前 Node.js 环境缺少 fetch,请使用 Node.js 18+');
|
|
1337
|
-
}
|
|
1338
|
-
const optionsOverride = resolveRequestOptions(optionsOrLegacyCookies, maybeOptions);
|
|
1339
|
-
const authHeaders = await resolveRequestAuthHeaders(optionsOverride);
|
|
1340
|
-
const requestUrl = buildRequestUrl(baseUrl, requestPath, queryParams);
|
|
1341
|
-
const maxBytes = Number(optionsOverride.maxBytes) || 5 * 1024 * 1024;
|
|
1342
|
-
const response = await fetchWithTimeout(requestUrl, {
|
|
1343
|
-
method: 'GET',
|
|
1344
|
-
redirect: 'manual',
|
|
1345
|
-
headers: {
|
|
1346
|
-
Accept: 'text/plain, application/javascript, */*',
|
|
1347
|
-
Origin: requestUrl.origin,
|
|
1348
|
-
Referer: requestUrl.origin + '/',
|
|
1349
|
-
'x-requested-with': 'XMLHttpRequest',
|
|
1350
|
-
...authHeaders,
|
|
1351
|
-
},
|
|
1352
|
-
}, optionsOverride.timeout);
|
|
1353
|
-
const metadata = buildCodeBundleResponseMetadata(requestUrl, response);
|
|
1354
|
-
|
|
1355
|
-
if (response.status === 401) {
|
|
1356
|
-
return { __needLogin: true, __httpStatus: response.status };
|
|
1357
|
-
}
|
|
1358
|
-
if (response.status < 200 || response.status >= 300) {
|
|
1359
|
-
const preview = await readErrorResponsePreview(response, optionsOverride.timeout);
|
|
1360
|
-
if (response.status === 403) {
|
|
1361
|
-
throw createCodeBundleDownloadError(
|
|
1362
|
-
'CODE_BUNDLE_DOWNLOAD_FORBIDDEN',
|
|
1363
|
-
t('common.code_bundle_forbidden'),
|
|
1364
|
-
metadata,
|
|
1365
|
-
preview
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
if (response.status === 404) {
|
|
1369
|
-
throw createCodeBundleDownloadError(
|
|
1370
|
-
'CODE_BUNDLE_DOWNLOAD_NOT_FOUND',
|
|
1371
|
-
t('common.code_bundle_not_found'),
|
|
1372
|
-
metadata,
|
|
1373
|
-
preview
|
|
1374
|
-
);
|
|
1375
|
-
}
|
|
1376
|
-
throw createCodeBundleDownloadError(
|
|
1377
|
-
'CODE_BUNDLE_DOWNLOAD_HTTP_ERROR',
|
|
1378
|
-
t('common.code_bundle_http_error', response.status),
|
|
1379
|
-
metadata,
|
|
1380
|
-
preview
|
|
1381
|
-
);
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
const contentType = metadata.contentType;
|
|
1385
|
-
const htmlResponse = contentType === 'text/html' || contentType === 'application/xhtml+xml';
|
|
1386
|
-
const jsonResponse = contentType === 'application/json' || contentType.endsWith('+json');
|
|
1387
|
-
if (htmlResponse || jsonResponse
|
|
1388
|
-
|| !expectedContentTypeMatches(contentType, optionsOverride.expectedContentTypes)) {
|
|
1389
|
-
const preview = await readErrorResponsePreview(response, optionsOverride.timeout);
|
|
1390
|
-
if (htmlResponse) {
|
|
1391
|
-
throw createCodeBundleDownloadError(
|
|
1392
|
-
'CODE_BUNDLE_DOWNLOAD_HTML_RESPONSE',
|
|
1393
|
-
t('common.code_bundle_html_response'),
|
|
1394
|
-
metadata,
|
|
1395
|
-
preview
|
|
1396
|
-
);
|
|
1397
|
-
}
|
|
1398
|
-
if (jsonResponse) {
|
|
1399
|
-
throw createCodeBundleDownloadError(
|
|
1400
|
-
'CODE_BUNDLE_DOWNLOAD_JSON_RESPONSE',
|
|
1401
|
-
jsonResponseReason(preview),
|
|
1402
|
-
metadata,
|
|
1403
|
-
preview
|
|
1404
|
-
);
|
|
1405
|
-
}
|
|
1406
|
-
throw createCodeBundleDownloadError(
|
|
1407
|
-
'CODE_BUNDLE_DOWNLOAD_UNEXPECTED_CONTENT_TYPE',
|
|
1408
|
-
t('common.code_bundle_unexpected_content_type', contentType || 'missing'),
|
|
1409
|
-
metadata,
|
|
1410
|
-
preview
|
|
1411
|
-
);
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
const buffer = await readFetchResponseBuffer(response, maxBytes, optionsOverride.timeout);
|
|
1415
|
-
if (looksLikeHtml(buffer)) {
|
|
1416
|
-
throw createCodeBundleDownloadError(
|
|
1417
|
-
'CODE_BUNDLE_DOWNLOAD_HTML_RESPONSE',
|
|
1418
|
-
t('common.code_bundle_html_response'),
|
|
1419
|
-
metadata,
|
|
1420
|
-
compactResponsePreview(buffer)
|
|
1421
|
-
);
|
|
1422
|
-
}
|
|
1423
|
-
let value;
|
|
1424
|
-
try {
|
|
1425
|
-
value = new TextDecoder('utf-8', { fatal: true }).decode(buffer);
|
|
1426
|
-
} catch {
|
|
1427
|
-
throw createCodeBundleDownloadError(
|
|
1428
|
-
'CODE_BUNDLE_DOWNLOAD_INVALID_UTF8',
|
|
1429
|
-
t('common.code_bundle_invalid_utf8'),
|
|
1430
|
-
metadata
|
|
1431
|
-
);
|
|
1432
|
-
}
|
|
1433
|
-
if (typeof optionsOverride.onResponseMetadata === 'function') {
|
|
1434
|
-
optionsOverride.onResponseMetadata(metadata);
|
|
1435
|
-
}
|
|
1436
|
-
return value;
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
1083
|
/**
|
|
1440
1084
|
* 发送 HTTP POST 请求(application/x-www-form-urlencoded)
|
|
1441
1085
|
* @param {string} baseUrl
|
|
@@ -1860,10 +1504,8 @@ module.exports = {
|
|
|
1860
1504
|
isLoginExpired,
|
|
1861
1505
|
isCsrfTokenExpired,
|
|
1862
1506
|
httpPost,
|
|
1863
|
-
httpPostMultipart,
|
|
1864
1507
|
httpPostJson,
|
|
1865
1508
|
httpGet,
|
|
1866
|
-
httpGetCodeBundleText,
|
|
1867
1509
|
requestWithAutoLogin,
|
|
1868
1510
|
requestNonIdempotentWithAuthPreflight,
|
|
1869
1511
|
getNpmExecutable,
|