openyida 2026.9.3 → 2026.9.4-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/lib/app/canvas-compile.js +37 -4
- package/lib/app/display-page-readback.js +63 -5
- package/lib/app/get-schema.js +158 -5
- package/lib/app/page-linter.js +162 -0
- package/lib/app/publish.js +138 -6
- package/lib/core/command-manifest.js +2 -2
- package/lib/core/locales/en.js +10 -0
- package/lib/core/locales/zh.js +10 -0
- package/lib/core/query-data.js +31 -9
- package/lib/core/utils.js +358 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +3 -3
- package/yida-skills/SKILL.md +5 -5
- package/yida-skills/skills/yida-app/SKILL.md +5 -5
- package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +13 -13
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +3 -3
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +4 -3
- package/yida-skills/skills/yida-create-app/SKILL.md +2 -2
- package/yida-skills/skills/yida-design/SKILL.md +12 -12
- package/yida-skills/skills/yida-design/references/style-design-selection.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +1 -1
- package/yida-skills/skills/yida-design/workflow/output-design.md +2 -2
- package/yida-skills/skills/yida-design/workflow/output-prd.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +2 -2
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +4 -4
- package/yida-skills/skills/yida-prd/SKILL.md +10 -10
- package/yida-skills/skills/yida-prd/workflow/output-prd.md +1 -1
- package/yida-skills/skills/yida-prd/workflow/step-1-read-brief.md +2 -2
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +5 -5
- package/yida-skills/skills-index.json +10 -10
|
@@ -24,12 +24,13 @@
|
|
|
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 转成 JS → 把 import 改写成 window 别名引用、
|
|
28
|
+
* 把 export default 改写成画布入口 `YidaComp` → UglifyJS 压缩 → 正则抽出依赖包名。
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
const Babel = require('@babel/standalone');
|
|
32
32
|
const globals = require('globals');
|
|
33
|
+
const UglifyJS = require('uglify-js');
|
|
33
34
|
const {
|
|
34
35
|
assertNoEmojiInArtifactName,
|
|
35
36
|
assertNoEmojiInText,
|
|
@@ -734,6 +735,36 @@ function assertCanvasRuntimeParseable(runtimeCode, options = {}) {
|
|
|
734
735
|
}
|
|
735
736
|
}
|
|
736
737
|
|
|
738
|
+
/**
|
|
739
|
+
* 压缩画布运行态代码,并显式保留装配器读取的 YidaComp 入口名。
|
|
740
|
+
*/
|
|
741
|
+
function minifyCanvasRuntime(runtimeCode, options = {}) {
|
|
742
|
+
let result;
|
|
743
|
+
try {
|
|
744
|
+
result = UglifyJS.minify(runtimeCode, {
|
|
745
|
+
compress: true,
|
|
746
|
+
mangle: {
|
|
747
|
+
reserved: ['YidaComp'],
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
} catch (error) {
|
|
751
|
+
result = { error };
|
|
752
|
+
}
|
|
753
|
+
if (!result || result.error || typeof result.code !== 'string' || !result.code) {
|
|
754
|
+
const detail = result && result.error && result.error.message
|
|
755
|
+
? result.error.message
|
|
756
|
+
: '压缩结果为空';
|
|
757
|
+
throw new CliError(`Code Canvas runtimeCode 压缩失败: ${detail}`, {
|
|
758
|
+
code: 'OPENYIDA_CANVAS_MINIFY_FAILED',
|
|
759
|
+
details: {
|
|
760
|
+
stage: 'canvas_minify',
|
|
761
|
+
sourcePath: options.sourcePath || '',
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return result.code;
|
|
766
|
+
}
|
|
767
|
+
|
|
737
768
|
/**
|
|
738
769
|
* 本地编译 Code Canvas 源码。
|
|
739
770
|
* @param {string} source 原始 React/JSX/TSX 源码
|
|
@@ -839,11 +870,12 @@ function compileCanvasLocal(source, options = {}) {
|
|
|
839
870
|
configFile: false,
|
|
840
871
|
});
|
|
841
872
|
|
|
842
|
-
const
|
|
843
|
-
assertNoEmojiInText(
|
|
873
|
+
const unminifiedRuntimeCode = stage2.code || '';
|
|
874
|
+
assertNoEmojiInText(unminifiedRuntimeCode, {
|
|
844
875
|
artifact: options.sourcePath ? options.sourcePath + ' runtime' : 'Code Canvas runtime',
|
|
845
876
|
code: 'OPENYIDA_CANVAS_SOURCE_EMOJI_FORBIDDEN',
|
|
846
877
|
});
|
|
878
|
+
const runtimeCode = minifyCanvasRuntime(unminifiedRuntimeCode, options);
|
|
847
879
|
assertCanvasRuntimeParseable(runtimeCode, options);
|
|
848
880
|
return {
|
|
849
881
|
runtimeCode,
|
|
@@ -910,6 +942,7 @@ module.exports = {
|
|
|
910
942
|
resolveWindowAlias,
|
|
911
943
|
shouldAllowUnsupportedBareImports,
|
|
912
944
|
assertCanvasRuntimeParseable,
|
|
945
|
+
minifyCanvasRuntime,
|
|
913
946
|
findBareDependencyGlobalIssues,
|
|
914
947
|
MODULE_ALIAS_MAP,
|
|
915
948
|
};
|
|
@@ -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
|
+
httpGetCodeBundleText,
|
|
19
21
|
requestWithAutoLogin,
|
|
20
22
|
} = require('../core/utils');
|
|
21
23
|
const { t } = require('../core/i18n');
|
|
@@ -227,6 +229,122 @@ function resolveSchemaContent(schemaResult) {
|
|
|
227
229
|
return parseJsonObject(content);
|
|
228
230
|
}
|
|
229
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) => 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
|
+
|
|
230
348
|
function addImportedModules(target, value) {
|
|
231
349
|
let modules = value;
|
|
232
350
|
if (typeof modules === 'string') {
|
|
@@ -269,7 +387,10 @@ function extractDisplayPageSummary(schemaResult) {
|
|
|
269
387
|
|
|
270
388
|
const displayPage = {
|
|
271
389
|
hasYidaCodeCanvas: false,
|
|
390
|
+
hasCodeBundle: false,
|
|
272
391
|
hasNativeJsx: false,
|
|
392
|
+
codeBundleCount: 0,
|
|
393
|
+
bundleIds: [],
|
|
273
394
|
runtimeCodeBytes: 0,
|
|
274
395
|
sourceCodeBytes: 0,
|
|
275
396
|
compiledCodeBytes: 0,
|
|
@@ -291,10 +412,29 @@ function extractDisplayPageSummary(schemaResult) {
|
|
|
291
412
|
|
|
292
413
|
if (node.componentName === 'YidaCodeCanvas' && isComponentInstance(node)) {
|
|
293
414
|
const props = node.props || {};
|
|
415
|
+
const codeBundle = props.codeBundle && typeof props.codeBundle === 'object'
|
|
416
|
+
? props.codeBundle
|
|
417
|
+
: null;
|
|
294
418
|
displayPage.hasYidaCodeCanvas = true;
|
|
295
419
|
displayPage.componentCount++;
|
|
296
420
|
displayPage.runtimeCodeBytes += codeBytes(props.runtimeCode);
|
|
297
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
|
+
}
|
|
298
438
|
addImportedModules(displayPage.importedModules, props.importedModules);
|
|
299
439
|
} else if (node.componentName === 'Jsx' && isComponentInstance(node)) {
|
|
300
440
|
displayPage.hasNativeJsx = true;
|
|
@@ -581,12 +721,15 @@ async function mapLimit(items, limit, iterator) {
|
|
|
581
721
|
return results;
|
|
582
722
|
}
|
|
583
723
|
|
|
584
|
-
async function fetchSchemaRecord(appType, form, authRef, retries) {
|
|
724
|
+
async function fetchSchemaRecord(appType, form, authRef, retries, options = {}) {
|
|
585
725
|
let lastError = null;
|
|
586
726
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
587
727
|
try {
|
|
588
728
|
const result = await fetchSchema(appType, form.formUuid, authRef);
|
|
589
729
|
if (isSuccessfulSchemaResult(result)) {
|
|
730
|
+
const resolvedResult = options.resolveCodeBundles
|
|
731
|
+
? await resolveCodeBundleSchema(result, appType, form.formUuid, authRef)
|
|
732
|
+
: result;
|
|
590
733
|
const record = {
|
|
591
734
|
formUuid: form.formUuid,
|
|
592
735
|
formName: form.formName,
|
|
@@ -594,10 +737,10 @@ async function fetchSchemaRecord(appType, form, authRef, retries) {
|
|
|
594
737
|
pathName: form.pathName,
|
|
595
738
|
success: true,
|
|
596
739
|
attempts: attempt + 1,
|
|
597
|
-
fieldSummary: extractFieldSummary(
|
|
598
|
-
schema:
|
|
740
|
+
fieldSummary: extractFieldSummary(resolvedResult),
|
|
741
|
+
schema: resolvedResult,
|
|
599
742
|
};
|
|
600
|
-
const displayPage = extractDisplayPageSummary(
|
|
743
|
+
const displayPage = extractDisplayPageSummary(resolvedResult);
|
|
601
744
|
if (displayPage) {
|
|
602
745
|
record.displayPage = displayPage;
|
|
603
746
|
}
|
|
@@ -694,6 +837,10 @@ async function runSingle(parsed, authRef) {
|
|
|
694
837
|
throwCommandError(errorMsg);
|
|
695
838
|
}
|
|
696
839
|
|
|
840
|
+
if (!parsed.field && !parsed.compact && !parsed.summaryJson) {
|
|
841
|
+
result = await resolveCodeBundleSchema(result, parsed.appType, parsed.formUuid, authRef);
|
|
842
|
+
}
|
|
843
|
+
|
|
697
844
|
// 保留既有 --field 返回结构;新 contract 仅由 --compact / --resolve-fields 触发。
|
|
698
845
|
if (parsed.field) {
|
|
699
846
|
const allFieldNodes = collectFieldNodes(result);
|
|
@@ -782,7 +929,9 @@ async function runBatch(parsed, authRef) {
|
|
|
782
929
|
info(` 批量获取 ${forms.length} 个表单 Schema,并发 ${parsed.concurrency},重试 ${parsed.retries}`);
|
|
783
930
|
|
|
784
931
|
const records = await mapLimit(forms, parsed.concurrency, async (form) => {
|
|
785
|
-
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
|
+
});
|
|
786
935
|
if (record.success && parsed.analysisJson) {
|
|
787
936
|
record.semanticAnalysis = buildSemanticAnalysis(
|
|
788
937
|
parsed.appType,
|
|
@@ -848,5 +997,9 @@ module.exports = {
|
|
|
848
997
|
fetchSchemaRecord,
|
|
849
998
|
collectFieldNodes,
|
|
850
999
|
findFieldNode,
|
|
1000
|
+
validateCodeBundleDescriptor,
|
|
1001
|
+
collectCodeBundleCanvases,
|
|
1002
|
+
downloadCodeBundleArtifact,
|
|
1003
|
+
resolveCodeBundleSchema,
|
|
851
1004
|
run,
|
|
852
1005
|
};
|
package/lib/app/page-linter.js
CHANGED
|
@@ -15,6 +15,7 @@ const THEN_CALLBACK_LINE_LIMIT = 50;
|
|
|
15
15
|
const CALLBACK_SCAN_LINE_LIMIT = 80;
|
|
16
16
|
const parser = Babel.packages.parser;
|
|
17
17
|
const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
|
|
18
|
+
const FORBIDDEN_SEARCH_FORM_DYNAMIC_ORDER_FIELDS = new Set(['gmtCreate']);
|
|
18
19
|
|
|
19
20
|
const PARSER_OPTIONS = {
|
|
20
21
|
sourceType: 'module',
|
|
@@ -404,6 +405,142 @@ function getNodeLine(node) {
|
|
|
404
405
|
return node && node.loc && node.loc.start && node.loc.start.line ? node.loc.start.line : 1;
|
|
405
406
|
}
|
|
406
407
|
|
|
408
|
+
function getStaticPropertyName(property) {
|
|
409
|
+
if (!property || (property.type !== 'ObjectProperty' && property.type !== 'ObjectMethod')) {
|
|
410
|
+
return '';
|
|
411
|
+
}
|
|
412
|
+
const key = property.key;
|
|
413
|
+
if (!key) {
|
|
414
|
+
return '';
|
|
415
|
+
}
|
|
416
|
+
if (key.type === 'Identifier' && !property.computed) {
|
|
417
|
+
return key.name;
|
|
418
|
+
}
|
|
419
|
+
if (key.type === 'StringLiteral') {
|
|
420
|
+
return key.value;
|
|
421
|
+
}
|
|
422
|
+
return '';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function getMemberPropertyName(member) {
|
|
426
|
+
if (!member || (member.type !== 'MemberExpression' && member.type !== 'OptionalMemberExpression') || !member.property) {
|
|
427
|
+
return '';
|
|
428
|
+
}
|
|
429
|
+
if (member.property.type === 'Identifier' && !member.computed) {
|
|
430
|
+
return member.property.name;
|
|
431
|
+
}
|
|
432
|
+
if (member.property.type === 'StringLiteral') {
|
|
433
|
+
return member.property.value;
|
|
434
|
+
}
|
|
435
|
+
return '';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function getStaticStringValue(node) {
|
|
439
|
+
if (!node) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
if (node.type === 'StringLiteral') {
|
|
443
|
+
return node.value;
|
|
444
|
+
}
|
|
445
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
446
|
+
return node.quasis.map(part => part.value.cooked || '').join('');
|
|
447
|
+
}
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function unwrapJsonStringify(node) {
|
|
452
|
+
if (!node || node.type !== 'CallExpression' || !node.callee || node.callee.type !== 'MemberExpression') {
|
|
453
|
+
return node;
|
|
454
|
+
}
|
|
455
|
+
const object = node.callee.object;
|
|
456
|
+
if (object && object.type === 'Identifier' && object.name === 'JSON' && getMemberPropertyName(node.callee) === 'stringify') {
|
|
457
|
+
return node.arguments && node.arguments[0] ? node.arguments[0] : node;
|
|
458
|
+
}
|
|
459
|
+
return node;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function resolveStaticBinding(node, pathRef) {
|
|
463
|
+
if (!node || node.type !== 'Identifier' || !pathRef || !pathRef.scope) {
|
|
464
|
+
return node;
|
|
465
|
+
}
|
|
466
|
+
const binding = pathRef.scope.getBinding(node.name);
|
|
467
|
+
if (!binding || !binding.constant || !binding.path || binding.path.node.type !== 'VariableDeclarator') {
|
|
468
|
+
return node;
|
|
469
|
+
}
|
|
470
|
+
return binding.path.node.init || node;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function findForbiddenDynamicOrderFields(node, pathRef) {
|
|
474
|
+
const resolved = resolveStaticBinding(node, pathRef);
|
|
475
|
+
const value = resolveStaticBinding(unwrapJsonStringify(resolved), pathRef);
|
|
476
|
+
if (value && value.type === 'ObjectExpression') {
|
|
477
|
+
return value.properties
|
|
478
|
+
.map(getStaticPropertyName)
|
|
479
|
+
.filter(field => FORBIDDEN_SEARCH_FORM_DYNAMIC_ORDER_FIELDS.has(field));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const staticValue = getStaticStringValue(value);
|
|
483
|
+
if (staticValue === null) {
|
|
484
|
+
return [];
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
const parsed = JSON.parse(staticValue);
|
|
488
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
return Object.keys(parsed).filter(field => FORBIDDEN_SEARCH_FORM_DYNAMIC_ORDER_FIELDS.has(field));
|
|
492
|
+
} catch {
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function findDynamicOrderProperty(params) {
|
|
498
|
+
if (!params || params.type !== 'ObjectExpression') {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
return params.properties.find(property => getStaticPropertyName(property) === 'dynamicOrder') || null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function reportForbiddenDynamicOrderValue(pathRef, value, issueNode, errors, disableMap) {
|
|
505
|
+
const forbiddenFields = findForbiddenDynamicOrderFields(value, pathRef);
|
|
506
|
+
if (forbiddenFields.length === 0) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
pushIssue(
|
|
510
|
+
errors,
|
|
511
|
+
getNodeLine(issueNode),
|
|
512
|
+
'searchformdata-dynamic-order-metadata',
|
|
513
|
+
t('publish.lint_searchformdata_dynamic_order_metadata', forbiddenFields.join(', ')),
|
|
514
|
+
disableMap
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function reportForbiddenDynamicOrder(pathRef, params, errors, disableMap) {
|
|
519
|
+
const resolvedParams = resolveStaticBinding(params, pathRef);
|
|
520
|
+
const dynamicOrderProperty = findDynamicOrderProperty(resolvedParams);
|
|
521
|
+
if (!dynamicOrderProperty) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
reportForbiddenDynamicOrderValue(
|
|
525
|
+
pathRef,
|
|
526
|
+
dynamicOrderProperty.value,
|
|
527
|
+
dynamicOrderProperty,
|
|
528
|
+
errors,
|
|
529
|
+
disableMap
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function isUrlSearchParamsInstance(node, pathRef) {
|
|
534
|
+
const resolved = resolveStaticBinding(node, pathRef);
|
|
535
|
+
return !!(
|
|
536
|
+
resolved &&
|
|
537
|
+
resolved.type === 'NewExpression' &&
|
|
538
|
+
resolved.callee &&
|
|
539
|
+
resolved.callee.type === 'Identifier' &&
|
|
540
|
+
resolved.callee.name === 'URLSearchParams'
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
407
544
|
function getJsxElementName(nameNode) {
|
|
408
545
|
if (!nameNode) {
|
|
409
546
|
return '';
|
|
@@ -633,6 +770,24 @@ function collectAstLintIssues(sourceCode, errors, warnings, disableMap) {
|
|
|
633
770
|
},
|
|
634
771
|
CallExpression(pathRef) {
|
|
635
772
|
const callee = pathRef.node.callee;
|
|
773
|
+
if (getMemberPropertyName(callee) === 'searchFormDatas') {
|
|
774
|
+
const params = pathRef.node.arguments && pathRef.node.arguments[0];
|
|
775
|
+
reportForbiddenDynamicOrder(pathRef, params, errors, disableMap);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const urlSearchParamsMethod = getMemberPropertyName(callee);
|
|
779
|
+
if (
|
|
780
|
+
(urlSearchParamsMethod === 'append' || urlSearchParamsMethod === 'set') &&
|
|
781
|
+
callee &&
|
|
782
|
+
callee.type === 'MemberExpression' &&
|
|
783
|
+
isUrlSearchParamsInstance(callee.object, pathRef)
|
|
784
|
+
) {
|
|
785
|
+
const args = pathRef.node.arguments || [];
|
|
786
|
+
if (getStaticStringValue(args[0]) === 'dynamicOrder') {
|
|
787
|
+
reportForbiddenDynamicOrderValue(pathRef, args[1], pathRef.node, errors, disableMap);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
636
791
|
const isSetState = callee &&
|
|
637
792
|
callee.type === 'MemberExpression' &&
|
|
638
793
|
(callee.object.type === 'ThisExpression' ||
|
|
@@ -670,6 +825,13 @@ function collectAstLintIssues(sourceCode, errors, warnings, disableMap) {
|
|
|
670
825
|
pushIssue(warnings, getNodeLine(pathRef.node), 'setState-non-timestamp', t('publish.lint_setstate_non_timestamp'), disableMap);
|
|
671
826
|
}
|
|
672
827
|
},
|
|
828
|
+
NewExpression(pathRef) {
|
|
829
|
+
const callee = pathRef.node.callee;
|
|
830
|
+
if (callee && callee.type === 'Identifier' && callee.name === 'URLSearchParams') {
|
|
831
|
+
const params = pathRef.node.arguments && pathRef.node.arguments[0];
|
|
832
|
+
reportForbiddenDynamicOrder(pathRef, params, errors, disableMap);
|
|
833
|
+
}
|
|
834
|
+
},
|
|
673
835
|
FunctionDeclaration(pathRef) {
|
|
674
836
|
const id = pathRef.node.id;
|
|
675
837
|
if (!id || id.name !== 'renderJsx') {
|