openyida 2026.5.21 → 2026.5.25

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 (51) hide show
  1. package/README.md +5 -1
  2. package/bin/yida.js +7 -1
  3. package/lib/app/app-list.js +20 -1
  4. package/lib/app/check-page.js +2 -2
  5. package/lib/app/compile.js +3 -2
  6. package/lib/app/externalize-form.js +642 -0
  7. package/lib/app/import-app.js +39 -11
  8. package/lib/app/page-compat.js +258 -2
  9. package/lib/app/page-compiler.js +4 -1
  10. package/lib/app/page-linter.js +271 -0
  11. package/lib/app/publish.js +3 -2
  12. package/lib/auth/cdp-browser-login.js +7 -3
  13. package/lib/auth/login.js +2 -3
  14. package/lib/core/command-manifest.js +3 -0
  15. package/lib/core/copy.js +50 -8
  16. package/lib/core/env-manager.js +24 -16
  17. package/lib/core/locales/ar.js +7 -0
  18. package/lib/core/locales/de.js +7 -0
  19. package/lib/core/locales/en.js +7 -0
  20. package/lib/core/locales/es.js +7 -0
  21. package/lib/core/locales/fr.js +7 -0
  22. package/lib/core/locales/hi.js +7 -0
  23. package/lib/core/locales/ja.js +7 -0
  24. package/lib/core/locales/ko.js +7 -0
  25. package/lib/core/locales/pt.js +7 -0
  26. package/lib/core/locales/vi.js +7 -0
  27. package/lib/core/locales/zh-HK.js +7 -0
  28. package/lib/core/locales/zh.js +7 -0
  29. package/lib/core/utils.js +2 -2
  30. package/lib/process/configure-process.js +552 -20
  31. package/package.json +1 -1
  32. package/project/pages/src/demo-agent-chatbox.oyd.jsx +78 -3
  33. package/scripts/e2e-real/full-runner.js +257 -8
  34. package/scripts/e2e-real/skill-coverage.js +2 -2
  35. package/yida-skills/SKILL.md +1 -1
  36. package/yida-skills/skills/yida-chart/SKILL.md +1 -1
  37. package/yida-skills/skills/yida-create-process/SKILL.md +3 -2
  38. package/yida-skills/skills/yida-custom-page/SKILL.md +7 -2
  39. package/yida-skills/skills/yida-custom-page/examples/attachment-upload.js +14 -12
  40. package/yida-skills/skills/yida-custom-page/references/attachment-upload-guide.md +3 -1
  41. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +4 -0
  42. package/yida-skills/skills/yida-custom-page/references/component-jsx-guide.md +31 -22
  43. package/yida-skills/skills/yida-dashboard/SKILL.md +10 -9
  44. package/yida-skills/skills/yida-dashboard/references/interaction-patterns.md +2 -0
  45. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +13 -4
  46. package/yida-skills/skills/yida-dashboard/references/structure-and-layout.md +1 -1
  47. package/yida-skills/skills/yida-ppt-slider/SKILL.md +47 -37
  48. package/yida-skills/skills/yida-ppt-slider/references/examples.md +5 -4
  49. package/yida-skills/skills/yida-process-rule/SKILL.md +93 -3
  50. package/yida-skills/skills/yida-process-rule/references/official-component-nodes.md +93 -0
  51. package/yida-skills/skills/yida-publish-page/SKILL.md +6 -4
@@ -52,15 +52,26 @@ async function createApp(appName, authRef) {
52
52
  return result.content; // appType
53
53
  }
54
54
 
55
- // ── 创建空白表单页面 ──────────────────────────────────
55
+ function normalizeImportFormType(formType) {
56
+ const normalized = String(formType || '').trim().toLowerCase();
57
+ if (!normalized || normalized === 'form') {
58
+ return 'receipt';
59
+ }
60
+ return normalized;
61
+ }
56
62
 
57
- async function createBlankForm(appType, formTitle, authRef) {
58
- const postData = querystring.stringify({
59
- _csrf_token: authRef.csrfToken,
60
- formType: 'receipt',
63
+ function buildCreateFormPostData(csrfToken, formTitle, formType = 'receipt') {
64
+ return querystring.stringify({
65
+ _csrf_token: csrfToken,
66
+ formType: normalizeImportFormType(formType),
61
67
  title: JSON.stringify(buildYidaI18n(formTitle, { en_US: formTitle, ja_JP: formTitle })),
62
68
  });
69
+ }
63
70
 
71
+ // ── 创建空白表单/页面/报表 ──────────────────────────────
72
+
73
+ async function createBlankForm(appType, formTitle, authRef, formType = 'receipt') {
74
+ const postData = buildCreateFormPostData(authRef.csrfToken, formTitle, formType);
64
75
  const result = await requestWithAutoLogin((auth) => {
65
76
  return httpPost(
66
77
  auth.baseUrl,
@@ -80,14 +91,18 @@ async function createBlankForm(appType, formTitle, authRef) {
80
91
 
81
92
  // ── 保存表单 Schema ───────────────────────────────────
82
93
 
83
- async function saveFormSchema(appType, formUuid, schema, authRef) {
84
- const postData = querystring.stringify({
94
+ async function saveFormSchema(appType, formUuid, schema, authRef, formType = 'receipt') {
95
+ const payload = {
85
96
  _csrf_token: authRef.csrfToken,
86
97
  appType,
87
98
  formUuid,
88
99
  content: JSON.stringify(schema),
89
100
  schemaVersion: 'V5',
90
- });
101
+ };
102
+ if (normalizeImportFormType(formType) === 'report') {
103
+ payload.importSchema = 'true';
104
+ }
105
+ const postData = querystring.stringify(payload);
91
106
 
92
107
  const result = await requestWithAutoLogin((auth) => {
93
108
  return httpPost(
@@ -234,12 +249,13 @@ async function run(args) {
234
249
 
235
250
  for (const form of forms) {
236
251
  const { formUuid: oldFormUuid, name: formName, schema: formSchemaResult } = form;
252
+ const formType = normalizeImportFormType(form.formType);
237
253
  info(t('import.migrating', formName, oldFormUuid));
238
254
 
239
255
  // 4.1 创建空白表单
240
256
  let newFormUuid;
241
257
  try {
242
- newFormUuid = await createBlankForm(newAppType, formName, authRef);
258
+ newFormUuid = await createBlankForm(newAppType, formName, authRef, formType);
243
259
  success(t('import.blank_form_created', newFormUuid));
244
260
  } catch (err) {
245
261
  fail(t('import.create_form_failed', err.message));
@@ -247,6 +263,7 @@ async function run(args) {
247
263
  oldFormUuid,
248
264
  newFormUuid: null,
249
265
  name: formName,
266
+ formType,
250
267
  status: 'failed',
251
268
  error: err.message,
252
269
  });
@@ -262,6 +279,7 @@ async function run(args) {
262
279
  oldFormUuid,
263
280
  newFormUuid,
264
281
  name: formName,
282
+ formType,
265
283
  status: 'skipped',
266
284
  error: t('import.schema_empty_msg'),
267
285
  });
@@ -279,7 +297,7 @@ async function run(args) {
279
297
  );
280
298
 
281
299
  // 4.3 保存 Schema
282
- const saveResult = await saveFormSchema(newAppType, newFormUuid, adaptedSchema, authRef);
300
+ const saveResult = await saveFormSchema(newAppType, newFormUuid, adaptedSchema, authRef, formType);
283
301
  if (!saveResult || !saveResult.success) {
284
302
  const errorMsg = saveResult ? saveResult.errorMsg || t('common.unknown_error') : t('common.request_failed');
285
303
  fail(t('import.save_schema_failed', errorMsg));
@@ -287,6 +305,7 @@ async function run(args) {
287
305
  oldFormUuid,
288
306
  newFormUuid,
289
307
  name: formName,
308
+ formType,
290
309
  status: 'failed',
291
310
  error: t('import.save_schema_failed', errorMsg),
292
311
  });
@@ -308,6 +327,7 @@ async function run(args) {
308
327
  oldFormUuid,
309
328
  newFormUuid,
310
329
  name: formName,
330
+ formType,
311
331
  status: 'success',
312
332
  });
313
333
  successCount++;
@@ -352,4 +372,12 @@ async function run(args) {
352
372
  );
353
373
  }
354
374
 
355
- module.exports = { run };
375
+ module.exports = {
376
+ run,
377
+ __test__: {
378
+ normalizeImportFormType,
379
+ buildCreateFormPostData,
380
+ adaptSerialNumberFormulas,
381
+ extractSchemaContent,
382
+ },
383
+ };
@@ -30,6 +30,37 @@ const SUPPORTED_REMOVABLE_IMPORTS = new Set([
30
30
  ]);
31
31
 
32
32
  const SUPPORTED_HOOKS = new Set(['useState', 'useEffect']);
33
+ const REQUIRED_RUNTIME_EXPORTS = {
34
+ getCustomState: [
35
+ 'export function getCustomState(key) {',
36
+ ' if (typeof _customState === "undefined") {',
37
+ ' return key ? undefined : {};',
38
+ ' }',
39
+ ' if (key) {',
40
+ ' return _customState[key];',
41
+ ' }',
42
+ ' return Object.assign({}, _customState);',
43
+ '}',
44
+ ].join('\n'),
45
+ setCustomState: [
46
+ 'export function setCustomState(newState) {',
47
+ ' if (typeof _customState === "undefined") {',
48
+ ' return;',
49
+ ' }',
50
+ ' Object.keys(newState || {}).forEach(function(key) {',
51
+ ' _customState[key] = newState[key];',
52
+ ' });',
53
+ ' this.forceUpdate();',
54
+ '}',
55
+ ].join('\n'),
56
+ forceUpdate: [
57
+ 'export function forceUpdate() {',
58
+ ' this.setState({ timestamp: new Date().getTime() });',
59
+ '}',
60
+ ].join('\n'),
61
+ didMount: 'export function didMount() {}',
62
+ didUnmount: 'export function didUnmount() {}',
63
+ };
33
64
 
34
65
  function parseSource(sourceCode) {
35
66
  return parser.parse(sourceCode, PARSER_OPTIONS);
@@ -53,11 +84,21 @@ function hasYidaRenderExport(ast) {
53
84
  });
54
85
  }
55
86
 
87
+ function sourceHasYidaRenderExport(sourceCode) {
88
+ return /export\s+function\s+renderJsx\s*\(/.test(sourceCode || '');
89
+ }
90
+
56
91
  function isAuthoringPath(sourcePath) {
57
92
  return /\.oyd\.(jsx?|tsx?)$/i.test(sourcePath || '') ||
58
93
  /\.openyida\.(jsx?|tsx?)$/i.test(sourcePath || '');
59
94
  }
60
95
 
96
+ function shouldBuildPageSource(sourceCode, sourcePath = '', options = {}) {
97
+ return options.modern === true ||
98
+ isAuthoringPath(sourcePath) ||
99
+ (!sourceHasYidaRenderExport(sourceCode) && /export\s+default\b/.test(sourceCode || ''));
100
+ }
101
+
61
102
  function isSupportedRemovableImport(sourceValue) {
62
103
  return SUPPORTED_REMOVABLE_IMPORTS.has(String(sourceValue || '').toLowerCase());
63
104
  }
@@ -324,6 +365,73 @@ function removeSupportedImports(ast, fixes, errors) {
324
365
  });
325
366
  }
326
367
 
368
+ function collectExportedFunctionNames(ast) {
369
+ const names = new Set();
370
+ ast.program.body.forEach((statement) => {
371
+ if (
372
+ statement.type === 'ExportNamedDeclaration' &&
373
+ statement.declaration &&
374
+ statement.declaration.type === 'FunctionDeclaration' &&
375
+ statement.declaration.id
376
+ ) {
377
+ names.add(statement.declaration.id.name);
378
+ }
379
+ });
380
+ return names;
381
+ }
382
+
383
+ function hasTopLevelCustomState(ast) {
384
+ return ast.program.body.some((statement) => {
385
+ return statement.type === 'VariableDeclaration' &&
386
+ statement.declarations.some((declarator) => {
387
+ return declarator.id &&
388
+ declarator.id.type === 'Identifier' &&
389
+ declarator.id.name === '_customState';
390
+ });
391
+ });
392
+ }
393
+
394
+ function ensureYidaRuntimeContract(sourceCode) {
395
+ const ast = parseSource(sourceCode);
396
+ const exportedNames = collectExportedFunctionNames(ast);
397
+ const fixes = [];
398
+ const prepend = [];
399
+ const append = [];
400
+ const needsStateHelper = !exportedNames.has('getCustomState') || !exportedNames.has('setCustomState');
401
+
402
+ if (needsStateHelper && !hasTopLevelCustomState(ast)) {
403
+ prepend.push('var _customState = {};');
404
+ fixes.push({
405
+ rule: 'runtime-custom-state',
406
+ message: 'Inserted default _customState store for Yida runtime helpers',
407
+ });
408
+ }
409
+
410
+ Object.keys(REQUIRED_RUNTIME_EXPORTS).forEach((name) => {
411
+ if (!exportedNames.has(name)) {
412
+ append.push(REQUIRED_RUNTIME_EXPORTS[name]);
413
+ fixes.push({
414
+ rule: 'runtime-export-' + name,
415
+ message: `Inserted missing export function ${name} for Yida runtime contract`,
416
+ });
417
+ }
418
+ });
419
+
420
+ if (prepend.length === 0 && append.length === 0) {
421
+ return { code: sourceCode, fixes };
422
+ }
423
+
424
+ return {
425
+ code: [
426
+ prepend.join('\n'),
427
+ sourceCode.trimEnd(),
428
+ append.join('\n\n'),
429
+ '',
430
+ ].filter(Boolean).join('\n\n'),
431
+ fixes,
432
+ };
433
+ }
434
+
327
435
  function fixYidaSource(sourceCode) {
328
436
  const ast = parseSource(sourceCode);
329
437
  const fixes = [];
@@ -335,9 +443,11 @@ function fixYidaSource(sourceCode) {
335
443
  fixVariableDeclarations(ast, fixes);
336
444
  fixRenderTimestamp(ast, fixes);
337
445
 
446
+ const runtimeResult = ensureYidaRuntimeContract(generateCode(ast));
447
+
338
448
  return {
339
- code: generateCode(ast),
340
- fixes,
449
+ code: runtimeResult.code,
450
+ fixes: fixes.concat(runtimeResult.fixes),
341
451
  errors,
342
452
  mode: 'yida-source',
343
453
  };
@@ -529,6 +639,132 @@ function replaceStateSettersInStatement(statement, stateSetters) {
529
639
  return wrapper.program.body[0];
530
640
  }
531
641
 
642
+ function addBindingNames(pattern, names) {
643
+ if (!pattern) {
644
+ return;
645
+ }
646
+ if (pattern.type === 'Identifier') {
647
+ names.add(pattern.name);
648
+ return;
649
+ }
650
+ if (pattern.type === 'ArrayPattern') {
651
+ pattern.elements.forEach(element => addBindingNames(element, names));
652
+ return;
653
+ }
654
+ if (pattern.type === 'ObjectPattern') {
655
+ pattern.properties.forEach((property) => {
656
+ addBindingNames(property.value || property.argument || property.key, names);
657
+ });
658
+ return;
659
+ }
660
+ if (pattern.type === 'RestElement') {
661
+ addBindingNames(pattern.argument, names);
662
+ return;
663
+ }
664
+ if (pattern.type === 'AssignmentPattern') {
665
+ addBindingNames(pattern.left, names);
666
+ }
667
+ }
668
+
669
+ function collectComponentBindingNames(componentBody) {
670
+ const names = new Set();
671
+ componentBody.forEach((statement) => {
672
+ if (statement.type === 'VariableDeclaration') {
673
+ statement.declarations.forEach(declarator => addBindingNames(declarator.id, names));
674
+ } else if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') && statement.id) {
675
+ names.add(statement.id.name);
676
+ }
677
+ });
678
+ return names;
679
+ }
680
+
681
+ function collectUseStateSetterNames(componentBody) {
682
+ const names = new Set();
683
+ componentBody.forEach((statement) => {
684
+ if (statement.type !== 'VariableDeclaration') {
685
+ return;
686
+ }
687
+ statement.declarations.forEach((declarator) => {
688
+ if (isUseStateDeclarator(declarator)) {
689
+ names.add(declarator.id.elements[1].name);
690
+ }
691
+ });
692
+ });
693
+ return names;
694
+ }
695
+
696
+ function collectReferencedIdentifiersFromStatements(statements) {
697
+ const names = new Set();
698
+ if (!statements || statements.length === 0) {
699
+ return names;
700
+ }
701
+ const wrapper = t.file(t.program(statements.map(statement => t.cloneNode(statement))));
702
+ traverse(wrapper, {
703
+ Identifier(pathRef) {
704
+ if (typeof pathRef.isReferencedIdentifier === 'function') {
705
+ if (!pathRef.isReferencedIdentifier()) {
706
+ return;
707
+ }
708
+ } else if (
709
+ pathRef.parent.type === 'MemberExpression' &&
710
+ pathRef.parent.property === pathRef.node &&
711
+ !pathRef.parent.computed
712
+ ) {
713
+ return;
714
+ }
715
+ names.add(pathRef.node.name);
716
+ },
717
+ });
718
+ return names;
719
+ }
720
+
721
+ function collectStatementBindingNames(statements) {
722
+ const names = new Set();
723
+ (statements || []).forEach((statement) => {
724
+ if (statement.type === 'VariableDeclaration') {
725
+ statement.declarations.forEach(declarator => addBindingNames(declarator.id, names));
726
+ } else if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration') && statement.id) {
727
+ names.add(statement.id.name);
728
+ }
729
+ });
730
+ return names;
731
+ }
732
+
733
+ function addUnsupportedEffectLocalReferenceErrors(effect, componentBindings, allowedBindings, errors, reportedNames, line) {
734
+ const referencedNames = collectReferencedIdentifiersFromStatements(effect.mount.concat(effect.unmount));
735
+ referencedNames.forEach((name) => {
736
+ if (!componentBindings.has(name) || allowedBindings.has(name) || reportedNames.has(name)) {
737
+ return;
738
+ }
739
+ reportedNames.add(name);
740
+ errors.push({
741
+ code: 'UNSUPPORTED_EFFECT_LOCAL_REFERENCE',
742
+ message: `useEffect(..., []) references local component binding "${name}", which would be undefined inside Yida didMount/didUnmount. Move the logic inline, use a supported state setter functional update, or write native export function didMount().`,
743
+ line,
744
+ });
745
+ });
746
+ }
747
+
748
+ function addUnsupportedEffectCleanupReferenceErrors(effect, errors, reportedNames, line) {
749
+ const mountBindings = collectStatementBindingNames(effect.mount);
750
+ if (mountBindings.size === 0 || effect.unmount.length === 0) {
751
+ return;
752
+ }
753
+
754
+ const cleanupRefs = collectReferencedIdentifiersFromStatements(effect.unmount);
755
+ cleanupRefs.forEach((name) => {
756
+ if (!mountBindings.has(name) || reportedNames.has(name)) {
757
+ return;
758
+ }
759
+ reportedNames.add(name);
760
+ errors.push({
761
+ code: 'UNSUPPORTED_EFFECT_CLEANUP_REFERENCE',
762
+ message: `useEffect cleanup references effect-local binding "${name}", which would be undefined inside Yida didUnmount. Store it on this (for native didMount/didUnmount) or avoid cleanup-local captures in authoring mode.`,
763
+ line,
764
+ });
765
+ });
766
+ }
767
+
532
768
  function statementContainsUseEffect(statement) {
533
769
  return statement.type === 'ExpressionStatement' &&
534
770
  statement.expression &&
@@ -539,6 +775,10 @@ function statementContainsUseEffect(statement) {
539
775
  function splitComponentBody(componentBody, errors) {
540
776
  const stateItems = [];
541
777
  const stateSetters = new Map();
778
+ const componentBindings = collectComponentBindingNames(componentBody);
779
+ const allowedEffectBindings = collectUseStateSetterNames(componentBody);
780
+ const reportedEffectRefs = new Set();
781
+ const reportedCleanupRefs = new Set();
542
782
  const renderStatements = [];
543
783
  const didMountStatements = [];
544
784
  const didUnmountStatements = [];
@@ -573,6 +813,20 @@ function splitComponentBody(componentBody, errors) {
573
813
  if (statementContainsUseEffect(statement)) {
574
814
  const effect = extractEffect(statement.expression, errors);
575
815
  if (effect) {
816
+ addUnsupportedEffectLocalReferenceErrors(
817
+ effect,
818
+ componentBindings,
819
+ allowedEffectBindings,
820
+ errors,
821
+ reportedEffectRefs,
822
+ statement.loc && statement.loc.start ? statement.loc.start.line : undefined
823
+ );
824
+ addUnsupportedEffectCleanupReferenceErrors(
825
+ effect,
826
+ errors,
827
+ reportedCleanupRefs,
828
+ statement.loc && statement.loc.start ? statement.loc.start.line : undefined
829
+ );
576
830
  didMountStatements.push(...effect.mount);
577
831
  didUnmountStatements.push(...effect.unmount);
578
832
  }
@@ -801,7 +1055,9 @@ function buildPageFile(sourcePath, options = {}) {
801
1055
  module.exports = {
802
1056
  buildPageSource,
803
1057
  buildPageFile,
1058
+ ensureYidaRuntimeContract,
804
1059
  fixYidaSource,
805
1060
  getCompatOutputPath,
806
1061
  isAuthoringPath,
1062
+ shouldBuildPageSource,
807
1063
  };
@@ -7,6 +7,7 @@ const { default: babelTransform } = require('../core/babel-transform');
7
7
  const { findProjectRoot } = require('../core/utils');
8
8
  const { t } = require('../core/i18n');
9
9
  const { info, success, error } = require('../core/chalk');
10
+ const { ensureYidaRuntimeContract } = require('./page-compat');
10
11
 
11
12
  /**
12
13
  * 编译宜搭自定义页面 JSX/JS 源码。
@@ -20,7 +21,9 @@ function compileSource(sourcePath) {
20
21
  const compiledPath = path.join(findProjectRoot(), 'pages', 'dist', compiledFileName);
21
22
 
22
23
  info(t('publish.reading_source', sourceFileName));
23
- const sourceCode = fs.readFileSync(sourcePath, 'utf-8');
24
+ const rawSourceCode = fs.readFileSync(sourcePath, 'utf-8');
25
+ const runtimeResult = ensureYidaRuntimeContract(rawSourceCode);
26
+ const sourceCode = runtimeResult.code;
24
27
 
25
28
  info(t('publish.compiling', sourceFileName));
26
29
  const babelResult = babelTransform(sourceCode, {}, false, { RE_VERSION: '7.4.0' });