openyida 2026.6.22-beta.0 → 2026.7.6

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 (102) hide show
  1. package/README.md +58 -3
  2. package/bin/yida.js +41 -21
  3. package/lib/app/create-form.js +34 -4
  4. package/lib/app/er.js +679 -0
  5. package/lib/app/nav-group.js +85 -0
  6. package/lib/app/page-compat.js +241 -2
  7. package/lib/app/page-linter.js +101 -6
  8. package/lib/app/publish.js +11 -0
  9. package/lib/auth/auth.js +25 -14
  10. package/lib/auth/login.js +63 -7
  11. package/lib/auth/qr-login.js +12 -0
  12. package/lib/conversation/collector.js +2 -0
  13. package/lib/core/command-manifest.js +5 -2
  14. package/lib/core/env.js +5 -1
  15. package/lib/core/locales/ar.js +29 -22
  16. package/lib/core/locales/de.js +29 -22
  17. package/lib/core/locales/en.js +28 -1
  18. package/lib/core/locales/es.js +29 -22
  19. package/lib/core/locales/fr.js +29 -22
  20. package/lib/core/locales/hi.js +29 -22
  21. package/lib/core/locales/ja.js +30 -22
  22. package/lib/core/locales/ko.js +29 -22
  23. package/lib/core/locales/pt.js +29 -22
  24. package/lib/core/locales/vi.js +29 -22
  25. package/lib/core/locales/zh-HK.js +9 -1
  26. package/lib/core/locales/zh.js +28 -1
  27. package/lib/core/utils.js +93 -15
  28. package/lib/formula/evaluate.js +75 -0
  29. package/lib/integration/integration-api.js +11 -7
  30. package/lib/integration/integration-create.js +303 -67
  31. package/lib/integration/integration-process-builder.js +106 -24
  32. package/lib/integration/integration-spec-builder.js +977 -0
  33. package/lib/integration/integration-view-builder.js +53 -11
  34. package/package.json +8 -2
  35. package/scripts/build-skills-package.js +47 -3
  36. package/scripts/e2e-real/skill-coverage.js +1 -0
  37. package/scripts/eval/VERIFY.md +169 -0
  38. package/scripts/eval/agent.js +148 -0
  39. package/scripts/eval/config.js +276 -0
  40. package/scripts/eval/dashboard/index.html +389 -0
  41. package/scripts/eval/dashboard/server.js +344 -0
  42. package/scripts/eval/eval.config.json +8 -0
  43. package/scripts/eval/generate.js +362 -0
  44. package/scripts/eval/guardrail.js +130 -0
  45. package/scripts/eval/manifest.js +117 -0
  46. package/scripts/eval/report.js +262 -0
  47. package/scripts/eval/routing.js +190 -0
  48. package/scripts/eval/runner.js +318 -0
  49. package/scripts/eval/scenarios/generation/generation-core.json +32 -0
  50. package/scripts/eval/scenarios/routing-core.json +86 -0
  51. package/scripts/eval/score.js +197 -0
  52. package/scripts/eval/screenshot.js +193 -0
  53. package/scripts/postinstall.js +10 -2
  54. package/scripts/validate-package-size.js +1 -1
  55. package/scripts/validate-skills.js +14 -2
  56. package/yida-skills/SKILL.md +128 -308
  57. package/yida-skills/references/development-rules.md +80 -0
  58. package/yida-skills/references/field-and-url-reference.md +42 -0
  59. package/yida-skills/references/setup-and-env.md +90 -0
  60. package/yida-skills/skills/large-file-write/SKILL.md +1 -1
  61. package/yida-skills/skills/yida-agent-center/SKILL.md +1 -7
  62. package/yida-skills/skills/yida-app/SKILL.md +47 -10
  63. package/yida-skills/skills/yida-app-permission/SKILL.md +1 -8
  64. package/yida-skills/skills/yida-basic-info/SKILL.md +1 -1
  65. package/yida-skills/skills/yida-business-rule/SKILL.md +1 -1
  66. package/yida-skills/skills/yida-chart/SKILL.md +114 -1509
  67. package/yida-skills/skills/yida-connector/SKILL.md +1 -6
  68. package/yida-skills/skills/yida-connector-safe-actions/SKILL.md +1 -1
  69. package/yida-skills/skills/yida-corp-efficiency/SKILL.md +2 -3
  70. package/yida-skills/skills/yida-corp-manager/SKILL.md +1 -7
  71. package/yida-skills/skills/yida-create-app/SKILL.md +1 -1
  72. package/yida-skills/skills/yida-create-form-page/SKILL.md +69 -7
  73. package/yida-skills/skills/yida-create-page/SKILL.md +1 -6
  74. package/yida-skills/skills/yida-create-process/SKILL.md +1 -5
  75. package/yida-skills/skills/yida-custom-page/SKILL.md +21 -321
  76. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +38 -0
  77. package/yida-skills/skills/yida-custom-page/references/runtime-guardrails.md +55 -0
  78. package/yida-skills/skills/yida-dashboard/SKILL.md +5 -1
  79. package/yida-skills/skills/yida-data-management/SKILL.md +1 -6
  80. package/yida-skills/skills/yida-data-source-connectors/SKILL.md +2 -6
  81. package/yida-skills/skills/yida-density/SKILL.md +1 -1
  82. package/yida-skills/skills/yida-export-conversation/SKILL.md +2 -5
  83. package/yida-skills/skills/yida-flash-note-to-prd/SKILL.md +2 -5
  84. package/yida-skills/skills/yida-form-detail/SKILL.md +1 -3
  85. package/yida-skills/skills/yida-form-permission/SKILL.md +1 -6
  86. package/yida-skills/skills/yida-formula/SKILL.md +1 -19
  87. package/yida-skills/skills/yida-formula-evaluate/SKILL.md +1 -9
  88. package/yida-skills/skills/yida-get-schema/SKILL.md +1 -5
  89. package/yida-skills/skills/yida-integration/SKILL.md +93 -7
  90. package/yida-skills/skills/yida-login/SKILL.md +6 -5
  91. package/yida-skills/skills/yida-logout/SKILL.md +1 -1
  92. package/yida-skills/skills/yida-nav-group/SKILL.md +26 -1
  93. package/yida-skills/skills/yida-openyida-publish-guard/SKILL.md +88 -0
  94. package/yida-skills/skills/yida-page-config/SKILL.md +1 -5
  95. package/yida-skills/skills/yida-ppt-slider/SKILL.md +98 -1400
  96. package/yida-skills/skills/yida-ppt-slider/references/dark-tech-theme.md +204 -0
  97. package/yida-skills/skills/yida-ppt-slider/references/echarts-race-example.md +330 -0
  98. package/yida-skills/skills/yida-process-rule/SKILL.md +1 -5
  99. package/yida-skills/skills/yida-publish-page/SKILL.md +2 -4
  100. package/yida-skills/skills/yida-report/SKILL.md +3 -6
  101. package/yida-skills/skills/yida-table-form/SKILL.md +2 -6
  102. package/yida-skills/skills/yida-voc/SKILL.md +1 -9
package/README.md CHANGED
@@ -69,7 +69,7 @@ OpenYida detects the active agent environment, workspace path, login state, and
69
69
  openyida login
70
70
  ```
71
71
 
72
- In Codex, QoderWork, Qoder, Wukong, Claude Code, OpenCode, Cursor, and other detected AI tools, OpenYida first tries local Chrome/Edge/Chromium CDP when no valid cached login exists. If local CDP is unavailable, it falls back to an AI-dialog QR handoff. The agent should render `qr_image_markdown` or paste `agent_response_markdown` directly in the conversation so the QR code is visible, then run `poll_command` after the user scans it with DingTalk. If image rendering is unavailable, fall back to `qr_url`. The explicit `openyida login --browser` command still prefers CDP first and uses Playwright as an optional browser fallback.
72
+ In Codex, QoderWork, Qoder, Wukong, Claude Code, MuleRun, OpenCode, Cursor, and other detected AI tools, OpenYida first tries local Chrome/Edge/Chromium CDP when no valid cached login exists. If local CDP is unavailable, it falls back to an AI-dialog QR handoff. The agent should render `qr_image_markdown` or paste `agent_response_markdown` directly in the conversation so the QR code is visible, then run `poll_command` after the user scans it with DingTalk. If image rendering is unavailable, fall back to `qr_url`. The explicit `openyida login --browser` command still prefers CDP first and uses Playwright as an optional browser fallback.
73
73
 
74
74
  When the user names a target Yida entry URL, pass it to the login command so OpenYida can select the matching environment and cookie file. For example, Alibaba intranet Yida uses `cookies-alibaba.json`:
75
75
 
@@ -126,6 +126,7 @@ export PATH="$HOME/.real/.bin/node/bin:$PATH"
126
126
  |------|---------|
127
127
  | [Codex](https://openai.com/codex/) | Full support |
128
128
  | [Claude Code](https://claude.ai/code) | Full support |
129
+ | [MuleRun](https://mulerun.com) | Full support |
129
130
  | [Aone Copilot](https://copilot.code.alibaba-inc.com) | Full support |
130
131
  | [OpenCode](https://opencode.ai) | Full support |
131
132
  | [Cursor](https://cursor.com/) | Full support |
@@ -263,6 +264,59 @@ Useful options:
263
264
 
264
265
  Use `npm run test:e2e:real:cleanup` to list recorded disposable resources. OpenYida does not yet expose a safe app/form deletion command, so cleanup is intentionally a registry-backed audit step rather than an automatic destructive action.
265
266
 
267
+ ### Skill Evaluation Harness
268
+
269
+ The E2E paths above prove the *CLI* works. The eval harness under `scripts/eval/` measures whether the *agent* still routes natural-language requests to the right sub-skill and produces good output after you edit `yida-skills/SKILL.md`. It is the feedback loop for harness engineering.
270
+
271
+ ```bash
272
+ # Routing eval — "did it pick the right sub-skill?" No real resources, no login.
273
+ # Drives a headless `claude -p` agent with each scenario prompt and checks
274
+ # the selected sub-skill against the golden set.
275
+ npm run eval:routing
276
+
277
+ # Tool-pipeline baseline (end-to-end) — wraps the real-environment runner for one
278
+ # sub-skill, adds guardrail assertions, screenshots the published page, and writes a
279
+ # human scoring template. Runs deterministic CLI commands (no agent) so it serves as
280
+ # a control that proves the build→publish→screenshot→score plumbing itself works.
281
+ # Requires OPENYIDA_E2E=1 and a valid cookie cache.
282
+ OPENYIDA_E2E=1 npm run eval:e2e -- --skill yida-dashboard --screenshot
283
+
284
+ # Same, plus automatic screenshot scoring via the local multimodal `claude -p`.
285
+ OPENYIDA_E2E=1 npm run eval:e2e -- --skill yida-dashboard --screenshot --auto-score
286
+
287
+ # Real-generation eval — "can one sentence really build a usable app?" Feeds a
288
+ # natural-language request ("帮我创建一个订单管理系统") to a headless `claude -p`
289
+ # that actually reads the openyida skill and runs the CLI to build a real app, then
290
+ # reuses the screenshot + scoring + report pipeline. Creates real 宜搭 resources, so
291
+ # it needs OPENYIDA_E2E=1 + an authenticated agent. Unlike the deterministic baseline
292
+ # above, here the agent self-orchestrates.
293
+ OPENYIDA_E2E=1 npm run eval:generate -- --screenshot
294
+
295
+ # Run all three in one pass (routing + tool-pipeline baseline + real generation).
296
+ OPENYIDA_E2E=1 npm run eval:all -- --skill yida-dashboard --screenshot
297
+
298
+ # Optional: a zero-dependency local web console (buttons + live streamed output)
299
+ # that runs the tasks above on click. Binds to 127.0.0.1 only; tasks are a fixed
300
+ # whitelist. Use the Node version you start it with, so run `nvm use 20` first.
301
+ npm run eval:dashboard # http://127.0.0.1:4500
302
+ ```
303
+
304
+ Configuration precedence is `CLI flag > env (OPENYIDA_EVAL_*) > scripts/eval/eval.config.json > defaults`.
305
+
306
+ | Flag | Purpose |
307
+ |------|---------|
308
+ | `--mode e2e\|routing\|generate\|all` | Which evals to run (default `e2e`; `all` = routing + tool-pipeline baseline + real generation) |
309
+ | `--skill <name>` | Restrict the e2e run to one sub-skill; stages are reverse-looked-up from the `SKILL_COVERAGE` matrix |
310
+ | `--stages a,b` | Explicit stage list, overriding the skill reverse-lookup |
311
+ | `--screenshot` / `--no-screenshot` | Capture the published page (default on; skipped gracefully if Playwright is absent) |
312
+ | `--auto-score` / `--no-auto-score` | Score screenshots with the local `claude -p` agent (default off → human template only) |
313
+ | `--scenarios <dir>` | Routing golden-set directory (default `scripts/eval/scenarios`) |
314
+ | `--gen-scenarios <dir>` | Real-generation golden-set directory (default `scripts/eval/scenarios/generation`) |
315
+
316
+ Results are written back into the existing `acceptance-manifest.json` under a new `eval` section (guardrails, screenshots, scores) plus two sibling artifacts: a `scoring.md` human-scoring template and a self-contained `eval-report.html` visual report (screenshots inlined as base64, so the single file opens anywhere). Routing results land in `project/.cache/eval/routing-report.json`; generation results (with their own `scoring.md` + `eval-report.html`) land in `project/.cache/eval/generate/gen-<timestamp>/`. The harness degrades gracefully: a missing Playwright skips screenshots (enable with `npm install --no-save playwright && npx playwright install chromium`), and a missing `claude` CLI skips auto-scoring while keeping the human template. A guardrail failure (for example a resource-creating command issued before a successful `login --check-only`) fails the run.
317
+
318
+ A zero-dependency local dashboard (`npm run eval:dashboard`, then open `http://127.0.0.1:4500`) drives these runs from buttons and streams output live; its "📊 查看最新报告" button opens the most recent `eval-report.html`.
319
+
266
320
  ### Connectors, Integrations, and Reports
267
321
 
268
322
  ```bash
@@ -301,7 +355,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
301
355
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
302
356
  | `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
303
357
  | `openyida update-app <appType> [--name "..."] [--layout slide\|ver] [--theme deepBlue]` | Update app info |
304
- | `openyida nav-group <list\|create\|rename\|delete\|move\|hide\|show> <appType> ...` | Manage app sidebar navigation groups |
358
+ | `openyida nav-group <list\|create\|rename\|delete\|move\|order\|hide\|show> <appType> ...` | Manage app sidebar navigation groups |
305
359
  | `openyida app-permission <get\|set\|add\|remove\|search-user> ...` | Manage app primary, data, and developer admins |
306
360
  | `openyida i18n <overview\|config\|languages\|list\|upsert\|delete\|translate\|translate-all\|upgrade> <appType> ...` | Manage app multilingual copy and language config |
307
361
  | `openyida export <appType> [output]` | Export app (generate migration package) |
@@ -322,6 +376,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
322
376
  | `openyida list-forms <appType> [--keyword <text>]` | List forms/pages in an app |
323
377
  | `openyida aggregate-table <list\|create-empty\|inspect\|preview\|save\|publish\|status> <appType> ...` | Manage aggregate tables (virtualView) |
324
378
  | `openyida get-schema <appType> <formUuid\|--all>` | Get one form Schema or all form Schemas |
379
+ | `openyida er <appType> [--format mermaid\|json] [--output file] [--include-system] [--include-pages]` | Export app entity relationship diagram |
325
380
  | `openyida create-page <appType> "<name>" [--mode dashboard] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a custom display page |
326
381
  | `openyida generate-page <template>` | Generate page from curated template |
327
382
  | `openyida build-page <sourceFile> [--output file\|--write]` | Build Yida-compatible page source |
@@ -389,7 +444,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
389
444
 
390
445
  | Command | Description |
391
446
  |---------|-------------|
392
- | `openyida integration create <appType> ...` | Create integration automation flow |
447
+ | `openyida integration create <appType> ... [--spec file.json]` | Create integration automation flow |
393
448
  | `openyida integration list <appType> [--form-uuid <uuid>] [--status y\|n] [--json]` | List integration automation flows |
394
449
  | `openyida integration enable <appType> <formUuid> <processCode>` | Enable integration automation flow |
395
450
  | `openyida integration disable <appType> <formUuid> <processCode>` | Disable integration automation flow |
package/bin/yida.js CHANGED
@@ -27,6 +27,8 @@ function isAgentEnvironment(env) {
27
27
  env.CODEX_HOME ||
28
28
  env.CLAUDE_CODE ||
29
29
  env.CLAUDE_CODE_ENTRYPOINT ||
30
+ env.MULERUN_CHAT_ID ||
31
+ env.MULE_DATA_DIR ||
30
32
  env.OPENCODE ||
31
33
  env.OPENCODE_CLIENT ||
32
34
  env.QODER_IDE ||
@@ -212,6 +214,16 @@ function handleFirstRunGuide() {
212
214
  function printLoginResult(result) {
213
215
  noteLoginCommandResult(result);
214
216
 
217
+ if (!result) {
218
+ console.log(JSON.stringify({
219
+ ok: false,
220
+ status: 'login_failed',
221
+ can_auto_use: false,
222
+ error: 'login_failed',
223
+ }));
224
+ return;
225
+ }
226
+
215
227
  if (result && (result.status === 'need_qr_scan' || result.status === 'need_corp_selection')) {
216
228
  console.log(JSON.stringify(result));
217
229
  return;
@@ -243,6 +255,11 @@ function printLoginResult(result) {
243
255
  return;
244
256
  }
245
257
 
258
+ if (result.status && result.status !== 'ok' && !result.csrf_token) {
259
+ console.log(JSON.stringify(result));
260
+ return;
261
+ }
262
+
246
263
  const summary = {
247
264
  ok: true,
248
265
  base_url: result && result.base_url,
@@ -311,9 +328,11 @@ function shouldUsePlaywrightFallbackInAgentLogin() {
311
328
  return hasDesktopEnvironment() || process.env.OPENYIDA_AGENT_PLAYWRIGHT_FALLBACK === '1';
312
329
  }
313
330
 
314
- function shouldUseDesktopBrowserLogin() {
315
- const { hasDesktopEnvironment } = require('../lib/core/utils');
316
- return hasDesktopEnvironment();
331
+ function shouldUseLocalBrowserLogin(options = {}) {
332
+ const { hasLocalBrowserLoginCapability } = require('../lib/auth/login');
333
+ return hasLocalBrowserLoginCapability({
334
+ playwrightFallback: options.playwrightFallback,
335
+ });
317
336
  }
318
337
 
319
338
  function shouldUseCodexQrLogin(cliArgs) {
@@ -540,6 +559,7 @@ async function main() {
540
559
  case 'login': {
541
560
  const { checkLoginOnly } = require('../lib/auth/login');
542
561
  const loginArgs = applyLoginEnvironmentFlags(args, { inferTargetUrl: true });
562
+ const { isInjectedAuthMode } = require('../lib/core/utils');
543
563
  if (loginArgs.includes('--agent-poll') || loginArgs.includes('--codex-poll')) {
544
564
  const sessionFile = getArgValue(loginArgs, '--agent-poll') || getArgValue(loginArgs, '--codex-poll');
545
565
  const { pollCodexQrLogin } = require('../lib/auth/qr-login');
@@ -554,9 +574,12 @@ async function main() {
554
574
  corpId: getArgValue(loginArgs, '--corp-id'),
555
575
  });
556
576
  printLoginResult(result);
557
- } else if (loginArgs[0] === '--check-only') {
577
+ } else if (loginArgs.includes('--check-only')) {
558
578
  const result = checkLoginOnly({ includeSecrets: loginArgs.includes('--with-cookies') });
559
579
  console.log(JSON.stringify(result, null, 2));
580
+ } else if (isInjectedAuthMode()) {
581
+ const result = checkLoginOnly({ includeSecrets: true });
582
+ printLoginResult(result);
560
583
  } else if (shouldUseCodexQrLogin(loginArgs)) {
561
584
  const { startCodexQrLogin } = require('../lib/auth/qr-login');
562
585
  const result = await startCodexQrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
@@ -578,26 +601,17 @@ async function main() {
578
601
  if (cachedResult.status === 'ok') {
579
602
  printLoginResult(cachedResult);
580
603
  } else {
581
- const { detectActiveTool } = require('../lib/core/utils');
582
- const activeTool = detectActiveTool();
583
604
  const { interactiveLogin } = require('../lib/auth/login');
584
- const browserResult = interactiveLogin({
585
- playwrightFallback: shouldUsePlaywrightFallbackInAgentLogin(),
586
- });
605
+ const playwrightFallback = shouldUsePlaywrightFallbackInAgentLogin();
606
+ const browserResult = shouldUseLocalBrowserLogin({ playwrightFallback })
607
+ ? interactiveLogin({ playwrightFallback })
608
+ : null;
587
609
  if (browserResult) {
588
610
  printLoginResult(browserResult);
589
611
  } else {
590
- // CDP/Playwright 失败后的兜底策略:
591
- // Wukong/QoderWork in-app browser,优先使用 browser handoff;其余走 AI 对话框 QR handoff。
592
- if (activeTool && (activeTool.tool === 'wukong' || activeTool.tool === 'qoderwork')) {
593
- const { codexLogin } = require('../lib/auth/codex-login');
594
- const result = await codexLogin({ tool: activeTool.tool });
595
- printLoginResult(result);
596
- } else {
597
- const { startCodexQrLogin } = require('../lib/auth/qr-login');
598
- const result = await startCodexQrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
599
- printLoginResult(result);
600
- }
612
+ const { startCodexQrLogin } = require('../lib/auth/qr-login');
613
+ const result = await startCodexQrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
614
+ printLoginResult(result);
601
615
  }
602
616
  }
603
617
  } else if (shouldUseBrowserHandoffLogin(loginArgs)) {
@@ -615,7 +629,7 @@ async function main() {
615
629
  printLoginResult(cachedResult);
616
630
  break;
617
631
  }
618
- if (shouldUseDesktopBrowserLogin()) {
632
+ if (shouldUseLocalBrowserLogin({ playwrightFallback: true })) {
619
633
  const { interactiveLogin } = require('../lib/auth/login');
620
634
  const browserResult = interactiveLogin({ playwrightFallback: true });
621
635
  if (browserResult) {
@@ -758,6 +772,12 @@ async function main() {
758
772
  break;
759
773
  }
760
774
 
775
+ case 'er': {
776
+ const { run } = require('../lib/app/er');
777
+ await run(args);
778
+ break;
779
+ }
780
+
761
781
  case 'formula': {
762
782
  const subCommand = args[0];
763
783
  const subArgs = args.slice(1);
@@ -66,6 +66,7 @@ const { buildYidaI18n, normalizeYidaLocale, resolveContentLocale } = require('..
66
66
  const { banner, step, label, success, fail, warn, info, error, result, usage, hint, listItem } = require('../core/chalk');
67
67
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
68
68
  const { default: babelTransform } = require('../core/babel-transform');
69
+ const { normalizeFormulaFieldRefs } = require('../formula/evaluate');
69
70
 
70
71
  // ── 选项类字段类型 ───────────────────────────────────
71
72
  const OPTION_FIELD_TYPES = ['RadioField', 'SelectField', 'CheckboxField', 'MultiSelectField'];
@@ -3601,13 +3602,34 @@ function jsString(value) {
3601
3602
  return JSON.stringify(value === undefined ? '' : value);
3602
3603
  }
3603
3604
 
3605
+ function compileCustomValidateSource(source) {
3606
+ const funcSource = String(source || 'function validateRule(value) { return true; }').trim();
3607
+ return 'function main(){\n \n "use strict";\n\nvar __compiledFunc__ = '
3608
+ + funcSource
3609
+ + ';\n return __compiledFunc__.apply(this, arguments);\n }';
3610
+ }
3611
+
3604
3612
  function normalizeCustomValidateParam(param) {
3605
- if (param && typeof param === 'object' && param.type === 'JSExpression') {
3606
- return param;
3613
+ if (param && typeof param === 'object' && param.type === 'js') {
3614
+ const source = param.source || 'function validateRule(value) { return true; }';
3615
+ return {
3616
+ compiled: param.compiled || compileCustomValidateSource(source),
3617
+ source: source,
3618
+ type: 'js',
3619
+ error: param.error || {},
3620
+ };
3621
+ }
3622
+ let funcSource = typeof param === 'string' ? param
3623
+ : (param && typeof param === 'object' && param.type === 'JSExpression') ? String(param.value || '')
3624
+ : String(param || '');
3625
+ if (!funcSource || !/function/.test(funcSource)) {
3626
+ funcSource = 'function validateRule(value) { return true; }';
3607
3627
  }
3608
3628
  return {
3609
- type: 'JSExpression',
3610
- value: String(param || 'function validateRule(value) { return true; }'),
3629
+ compiled: compileCustomValidateSource(funcSource),
3630
+ source: funcSource,
3631
+ type: 'js',
3632
+ error: {},
3611
3633
  };
3612
3634
  }
3613
3635
 
@@ -3617,6 +3639,9 @@ function buildCustomValidateExpressionParam(rule) {
3617
3639
 
3618
3640
  function buildCustomValidateParam(rule) {
3619
3641
  if (rule.type === 'customValidate' && rule.param) {
3642
+ if (typeof rule.param === 'object' && rule.param.type === 'js') {
3643
+ return rule.param;
3644
+ }
3620
3645
  return typeof rule.param === 'object' && rule.param.type === 'JSExpression'
3621
3646
  ? String(rule.param.value || '')
3622
3647
  : String(rule.param);
@@ -4357,6 +4382,11 @@ async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, ver
4357
4382
  const saveStep = stepOffset || 4;
4358
4383
  const configStep = saveStep + 1;
4359
4384
 
4385
+ const fixedRefs = normalizeFormulaFieldRefs(schema);
4386
+ if (fixedRefs > 0) {
4387
+ info(t('create_form.formula_prefix_fixed', fixedRefs));
4388
+ }
4389
+
4360
4390
  step(saveStep, t('create_form.step_save_schema', saveStep));
4361
4391
  info(t('create_form.sending_save'));
4362
4392