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.
Files changed (34) hide show
  1. package/lib/app/canvas-compile.js +37 -4
  2. package/lib/app/display-page-readback.js +63 -5
  3. package/lib/app/get-schema.js +158 -5
  4. package/lib/app/page-linter.js +162 -0
  5. package/lib/app/publish.js +138 -6
  6. package/lib/core/command-manifest.js +2 -2
  7. package/lib/core/locales/en.js +10 -0
  8. package/lib/core/locales/zh.js +10 -0
  9. package/lib/core/query-data.js +31 -9
  10. package/lib/core/utils.js +358 -0
  11. package/package.json +1 -1
  12. package/scripts/postinstall.js +3 -3
  13. package/yida-skills/SKILL.md +5 -5
  14. package/yida-skills/skills/yida-app/SKILL.md +5 -5
  15. package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
  16. package/yida-skills/skills/yida-app/workflow/step-2-design.md +13 -13
  17. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +1 -1
  18. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +3 -3
  19. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +4 -3
  20. package/yida-skills/skills/yida-create-app/SKILL.md +2 -2
  21. package/yida-skills/skills/yida-design/SKILL.md +12 -12
  22. package/yida-skills/skills/yida-design/references/style-design-selection.md +1 -1
  23. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +1 -1
  24. package/yida-skills/skills/yida-design/workflow/output-design.md +2 -2
  25. package/yida-skills/skills/yida-design/workflow/output-prd.md +1 -1
  26. package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +2 -2
  27. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +1 -1
  28. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +1 -1
  29. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +4 -4
  30. package/yida-skills/skills/yida-prd/SKILL.md +10 -10
  31. package/yida-skills/skills/yida-prd/workflow/output-prd.md +1 -1
  32. package/yida-skills/skills/yida-prd/workflow/step-1-read-brief.md +2 -2
  33. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +5 -5
  34. 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 转成 ES5 → 把 import 改写成 window 别名引用、
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 runtimeCode = stage2.code || '';
843
- assertNoEmojiInText(runtimeCode, {
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
- info.canvasRuntimeCode = props.runtimeCode || '';
85
- info.canvasSourceCode = props.code || '';
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 && info.runtimeCodeBytes > 0;
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 readbackFingerprint = fingerprint(readbackArtifact);
163
- const expectedFingerprint = fingerprint(expectedArtifact);
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,
@@ -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(result),
598
- schema: result,
740
+ fieldSummary: extractFieldSummary(resolvedResult),
741
+ schema: resolvedResult,
599
742
  };
600
- const displayPage = extractDisplayPageSummary(result);
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
  };
@@ -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') {