openyida 2026.7.19 → 2026.7.21

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 (139) hide show
  1. package/README.md +14 -0
  2. package/bin/yida.js +107 -18
  3. package/lib/app/create-app.js +12 -1
  4. package/lib/app/create-form/args.js +13 -0
  5. package/lib/app/create-form.js +154 -67
  6. package/lib/app/create-page.js +45 -9
  7. package/lib/app/get-schema.js +65 -14
  8. package/lib/app/import-app.js +92 -41
  9. package/lib/app/publish.js +289 -556
  10. package/lib/app/schema-field-resolution.js +370 -0
  11. package/lib/app/services/app-reader.js +85 -0
  12. package/lib/app/services/app-service.js +235 -0
  13. package/lib/app/services/canvas-page-compiler.js +91 -0
  14. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  15. package/lib/app/services/field-bindings.js +68 -0
  16. package/lib/app/services/form-compiler.js +1922 -0
  17. package/lib/app/services/form-mode-service.js +182 -0
  18. package/lib/app/services/form-schema-patcher.js +333 -0
  19. package/lib/app/services/form-schema-reader.js +59 -0
  20. package/lib/app/services/form-service.js +379 -0
  21. package/lib/app/services/form-validation.js +562 -0
  22. package/lib/app/services/native-page-compiler.js +92 -0
  23. package/lib/app/services/native-page-schema-builder.js +428 -0
  24. package/lib/app/services/page-resource-service.js +185 -0
  25. package/lib/app/update-app.js +18 -6
  26. package/lib/auth/token-auth.js +34 -3
  27. package/lib/auth/token-store.js +22 -4
  28. package/lib/bridge/bridge.js +37 -5
  29. package/lib/core/agent-capabilities.js +5 -3
  30. package/lib/core/command-manifest.js +40 -0
  31. package/lib/core/doctor.js +15 -4
  32. package/lib/core/env-cmd.js +1 -1
  33. package/lib/core/env.js +7 -6
  34. package/lib/core/legacy-schema-guard.js +408 -0
  35. package/lib/core/locales/en.js +8 -46
  36. package/lib/core/locales/zh.js +8 -46
  37. package/lib/core/sample.js +1 -1
  38. package/lib/core/utils.js +334 -66
  39. package/lib/core/yida-client.js +104 -3
  40. package/lib/formula/evaluate.js +8 -66
  41. package/lib/formula/field-refs.js +83 -0
  42. package/lib/process/configure-process.js +44 -1992
  43. package/lib/process/create-process.js +54 -87
  44. package/lib/process/services/process-compiler.js +2056 -0
  45. package/lib/process/services/process-reader.js +232 -0
  46. package/lib/process/services/process-resource-service.js +571 -0
  47. package/lib/process/services/process-service.js +122 -0
  48. package/lib/process/services/process-stage-checkpoint.js +246 -0
  49. package/lib/report/append.js +7 -26
  50. package/lib/report/http.js +31 -5
  51. package/lib/report/index.js +10 -2
  52. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  53. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  54. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  55. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
  56. package/lib/schema/adapters/app-adapter.js +210 -0
  57. package/lib/schema/adapters/form-adapter.js +1362 -0
  58. package/lib/schema/adapters/keys.js +19 -0
  59. package/lib/schema/adapters/page-adapter.js +566 -0
  60. package/lib/schema/adapters/process-adapter.js +519 -0
  61. package/lib/schema/applier.js +1878 -0
  62. package/lib/schema/apply-store.js +1083 -0
  63. package/lib/schema/command.js +645 -0
  64. package/lib/schema/dependency-graph.js +83 -0
  65. package/lib/schema/errors.js +481 -0
  66. package/lib/schema/hash.js +9 -0
  67. package/lib/schema/manifest-limits.js +177 -0
  68. package/lib/schema/manifest-loader.js +309 -0
  69. package/lib/schema/manifest-schema-v1.json +193 -0
  70. package/lib/schema/normalize-manifest.js +281 -0
  71. package/lib/schema/page-canvas-foundation.js +447 -0
  72. package/lib/schema/page-data-source-builder.js +561 -0
  73. package/lib/schema/page-foundation.js +556 -0
  74. package/lib/schema/page-source-loader.js +314 -0
  75. package/lib/schema/planner.js +691 -0
  76. package/lib/schema/remote-dispatch-boundary.js +39 -0
  77. package/lib/schema/remote-missing.js +21 -0
  78. package/lib/schema/remote-reader.js +189 -0
  79. package/lib/schema/resource-registry.js +157 -0
  80. package/lib/schema/server-revision.js +71 -0
  81. package/lib/schema/sort.js +28 -0
  82. package/lib/schema/state-store.js +589 -0
  83. package/package.json +2 -2
  84. package/scripts/postinstall.js +8 -12
  85. package/scripts/validate-package-size.js +1 -1
  86. package/yida-skills/SKILL.md +114 -29
  87. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  88. package/yida-skills/references/setup-and-env.md +59 -59
  89. package/yida-skills/references/task-retrospective.md +6 -6
  90. package/yida-skills/skills/yida-app/SKILL.md +60 -14
  91. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  92. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  93. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
  94. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  95. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  96. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  97. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  98. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  99. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  101. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  102. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  103. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  104. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  105. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  106. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  107. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  108. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  109. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  110. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  111. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  112. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  113. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  114. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  115. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  116. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  117. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  118. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  119. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  120. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  121. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  122. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  123. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  124. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  125. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  126. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  127. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  128. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  129. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  130. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  131. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  132. package/yida-skills/skills/yida-report/SKILL.md +7 -1
  133. package/yida-skills/skills-index.json +14 -46
  134. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  135. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  136. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  137. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  138. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  139. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
package/README.md CHANGED
@@ -174,6 +174,7 @@ openyida corp-efficiency
174
174
  openyida create-form create APP_XXX "Customer" .cache/openyida/forms/customer-fields.json
175
175
  openyida create-form update APP_XXX FORM_XXX .cache/openyida/forms/customer-changes.json
176
176
  openyida get-schema APP_XXX FORM_XXX
177
+ openyida get-schema APP_XXX FORM_XXX --compact --resolve-fields "Customer Name,Status"
177
178
  openyida get-schema APP_XXX --all --output-dir .cache/schemas
178
179
  ```
179
180
 
@@ -240,6 +241,7 @@ Most checks should stay offline, but OpenYida also includes an explicit real-env
240
241
  OPENYIDA_E2E=1 npm run test:e2e:real
241
242
  OPENYIDA_E2E=1 npm run test:e2e:real:full
242
243
  OPENYIDA_E2E=1 OPENYIDA_E2E_FULL_STAGES=auth,app,form,process npm run test:e2e:real:full
244
+ npm run test:e2e:real:schema-source
243
245
  npm run test:e2e:real:skills
244
246
  ```
245
247
 
@@ -249,6 +251,8 @@ The runner creates a disposable app, form, and custom page with an `OY_E2E_*` pr
249
251
 
250
252
  `test:e2e:real:skills` enforces coverage for every directory under `yida-skills/skills/`. Each skill must be classified as real E2E, offline/unit, opt-in, or deprecated with an explicit reason. This prevents new skills from quietly bypassing the real-environment test plan.
251
253
 
254
+ `test:e2e:real:schema-source` is a local process-level Schema-as-Code E2E for agent/source checkout validation. It reads `yida-skills/` in place, creates a temporary `openyida` launcher that executes the current checkout `bin/yida.js`, and runs CRM app/form/page/process create, managed-update, and noop scenarios through `schema validate -> plan -> apply -> noop` against a mock Yida service, including customer field updates and approval-flow/page changes. Report and integration automation manifests remain compatibility-boundary scenarios and must return unsupported.
255
+
252
256
  Each successful full run leaves a human-inspectable result app in the target organization. The final step publishes a dedicated `Full E2E Dashboard` custom page, renames the app to `OY_E2E_*_PASSED` by default, and prints direct links for the app, form, dashboard page, and report; the same links are saved under `resultApp` in the registry JSON.
253
257
 
254
258
  Useful options:
@@ -354,6 +358,8 @@ openyida append-chart APP_XXX REPORT_XXX .cache/openyida/reports/chart.json
354
358
 
355
359
  Run `openyida --help` or `openyida <command> --help` for detailed usage.
356
360
 
361
+ Schema-as-Code Phase 1 manages app, form, process, and native/default display page resources. Use the single [Phase 1 Manifest guide](yida-skills/references/schema-as-code-phase1.md): create any referenced page source first, run `validate`, run the read-only `plan`, review its current `planId`, then explicitly approve and run `apply`. Error `nextAction`/`ask_human` never grants the first write; `stale_replanned` only returns a replacement plan for another explicit review. State is the only persisted bindings authority; report, automation, and page config/delete/pull remain outside Phase 1.
362
+
357
363
  <!-- OPENYIDA_COMMANDS_START -->
358
364
  <!-- This section is generated by `npm run docs:commands`. Do not edit command rows by hand. -->
359
365
 
@@ -406,6 +412,14 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
406
412
  | `openyida update-form-config <appType> ...` | Update form configuration |
407
413
  | `openyida get-form-config <appType> <formUuid> [--json]` | Query form configuration |
408
414
 
415
+ ### Schema-as-Code
416
+
417
+ | Command | Description |
418
+ |---------|-------------|
419
+ | `openyida schema validate <manifest> --json --quiet` | Validate Schema-as-Code manifest locally |
420
+ | `openyida schema plan <manifest> --state <path> --json --quiet` | Plan Schema-as-Code changes with read-only remote observation |
421
+ | `openyida schema apply <manifest> --state <path> --plan-id <planId> --json --quiet` | Apply a reviewed Schema-as-Code plan with checkpoints and recovery guards |
422
+
409
423
  ### Data & Permissions
410
424
 
411
425
  | Command | Description |
package/bin/yida.js CHANGED
@@ -18,6 +18,7 @@ const { COMMAND_GROUPS, buildCommandManifest } = require('../lib/core/command-ma
18
18
 
19
19
  const command = process.argv[2];
20
20
  const args = process.argv.slice(3);
21
+ const rawArgs = process.argv.slice(3);
21
22
 
22
23
  function isAgentEnvironment(env) {
23
24
  return !!(
@@ -55,6 +56,9 @@ function shouldRunUpdateCheck() {
55
56
  if (!command || command === '--help' || command === '-h' || command === '--version' || command === '-v') {
56
57
  return false;
57
58
  }
59
+ if (args.includes('--help') || args.includes('-h')) {
60
+ return false;
61
+ }
58
62
  if (command === 'commands' || command === 'agent-capabilities' || command === 'mcp') {
59
63
  return false;
60
64
  }
@@ -250,12 +254,15 @@ function printLoginResult(result) {
250
254
  return;
251
255
  }
252
256
 
253
- const tokenUsable = result.can_auto_use !== false && !!result.access_token;
257
+ const tokenUsable = result.ok !== undefined
258
+ ? !!result.ok
259
+ : (result.can_auto_use !== false && !!result.access_token);
254
260
  const summary = {
255
261
  ok: tokenUsable,
256
262
  status: result.status || (tokenUsable ? 'ok' : 'token_not_issued'),
257
263
  auth_mode: 'token',
258
- can_auto_use: tokenUsable,
264
+ auth_source: result.auth_source || result.authSource,
265
+ can_auto_use: result.can_auto_use !== undefined ? !!result.can_auto_use : tokenUsable,
259
266
  token_type: result.token_type || 'Bearer',
260
267
  access_token: maskSensitiveAuthOutput({ access_token: result.access_token }).access_token,
261
268
  expires_at: result.expires_at ? new Date(result.expires_at).toISOString() : undefined,
@@ -263,6 +270,7 @@ function printLoginResult(result) {
263
270
  corp_id: result.corp_id,
264
271
  user_id: result.user_id,
265
272
  user_name: result.user_name,
273
+ failure_reason: result.failure_reason,
266
274
  message: result.message,
267
275
  raw: tokenUsable ? undefined : result.raw,
268
276
  };
@@ -308,6 +316,30 @@ function getArgValue(cliArgs, name) {
308
316
  return cliArgs[index + 1];
309
317
  }
310
318
 
319
+ const UNSUPPORTED_LEGACY_LOGIN_FLAGS = new Set([
320
+ '--qr',
321
+ '--qr-code',
322
+ '--agent-qr',
323
+ '--codex-qr',
324
+ '--agent-poll',
325
+ '--codex-poll',
326
+ '--browser',
327
+ '--codex',
328
+ '--qoder',
329
+ '--wukong',
330
+ ]);
331
+
332
+ function findUnsupportedLegacyLoginFlag(...argLists) {
333
+ for (const argList of argLists) {
334
+ for (const arg of argList || []) {
335
+ if (UNSUPPORTED_LEGACY_LOGIN_FLAGS.has(arg)) {
336
+ return arg;
337
+ }
338
+ }
339
+ }
340
+ return null;
341
+ }
342
+
311
343
  function parseLoginTargetArg(value) {
312
344
  const raw = String(value || '').trim();
313
345
  if (!raw) {return null;}
@@ -445,6 +477,34 @@ function throwCliUsage(...lines) {
445
477
  });
446
478
  }
447
479
 
480
+ function hasHelpFlag(cliArgs = []) {
481
+ return cliArgs.includes('--help') || cliArgs.includes('-h');
482
+ }
483
+
484
+ function printCommandUsage(...lines) {
485
+ console.log(lines.filter(Boolean).join('\n'));
486
+ }
487
+
488
+ function printLoginHelp() {
489
+ printCommandUsage(t('cli.login_usage'), t('cli.login_example'));
490
+ }
491
+
492
+ function printAuthHelp() {
493
+ printCommandUsage(t('cli.auth_usage'), t('cli.auth_example'));
494
+ }
495
+
496
+ function assertNoUnsupportedLegacyLoginFlags(...argLists) {
497
+ const flag = findUnsupportedLegacyLoginFlag(...argLists);
498
+ if (!flag) {
499
+ return;
500
+ }
501
+ throwCliUsage(
502
+ t('cli.login_unsupported_option', flag),
503
+ t('cli.login_usage'),
504
+ t('cli.login_example')
505
+ );
506
+ }
507
+
448
508
  async function main() {
449
509
  applyQuietFlag();
450
510
  applyGlobalEnvironmentFlags();
@@ -516,15 +576,23 @@ async function main() {
516
576
 
517
577
  case 'login': {
518
578
  const loginArgs = applyLoginEnvironmentFlags(args, { inferTargetUrl: true });
519
- const { tokenLogin, tokenStatus } = require('../lib/auth/token-auth');
520
- if (loginArgs.includes('--check-only')) {
521
- console.log(JSON.stringify(tokenStatus(), null, 2));
579
+ const { getAuthStatus, isEnvAuthMode } = require('../lib/core/utils');
580
+ if (hasHelpFlag(loginArgs)) {
581
+ printLoginHelp();
522
582
  } else {
523
- const result = await tokenLogin({
524
- clientId: getArgValue(loginArgs, '--client-id'),
525
- quiet: loginArgs.includes('--quiet'),
526
- });
527
- printLoginResult(result);
583
+ assertNoUnsupportedLegacyLoginFlags(rawArgs, loginArgs);
584
+ if (loginArgs.includes('--check-only')) {
585
+ console.log(JSON.stringify(getAuthStatus(), null, 2));
586
+ } else if (isEnvAuthMode()) {
587
+ printLoginResult(getAuthStatus());
588
+ } else {
589
+ const { tokenLogin } = require('../lib/auth/token-auth');
590
+ const result = await tokenLogin({
591
+ clientId: getArgValue(loginArgs, '--client-id'),
592
+ quiet: loginArgs.includes('--quiet'),
593
+ });
594
+ printLoginResult(result);
595
+ }
528
596
  }
529
597
  break;
530
598
  }
@@ -538,15 +606,25 @@ async function main() {
538
606
  case 'auth': {
539
607
  const subCommand = args[0];
540
608
  const authArgs = applyLoginEnvironmentFlags(args.slice(1), { inferTargetUrl: true });
541
- const { tokenLogin, tokenLogout, tokenRefresh, tokenStatus } = require('../lib/auth/token-auth');
542
- if (subCommand === 'status') {
543
- console.log(JSON.stringify(tokenStatus(), null, 2));
609
+ const { getAuthStatus, isEnvAuthMode } = require('../lib/core/utils');
610
+ const { tokenLogin, tokenLogout, tokenRefresh } = require('../lib/auth/token-auth');
611
+ if (!subCommand || subCommand === '--help' || subCommand === '-h') {
612
+ printAuthHelp();
613
+ } else if (subCommand === 'status') {
614
+ console.log(JSON.stringify(getAuthStatus(), null, 2));
544
615
  } else if (subCommand === 'login') {
545
- const result = await tokenLogin({
546
- clientId: getArgValue(authArgs, '--client-id'),
547
- quiet: authArgs.includes('--quiet'),
548
- });
549
- printLoginResult(result);
616
+ if (hasHelpFlag(authArgs)) {
617
+ printLoginHelp();
618
+ } else {
619
+ assertNoUnsupportedLegacyLoginFlags(rawArgs.slice(1), authArgs);
620
+ const result = isEnvAuthMode()
621
+ ? getAuthStatus()
622
+ : await tokenLogin({
623
+ clientId: getArgValue(authArgs, '--client-id'),
624
+ quiet: authArgs.includes('--quiet'),
625
+ });
626
+ printLoginResult(result);
627
+ }
550
628
  } else if (subCommand === 'refresh') {
551
629
  console.log(JSON.stringify(maskSensitiveAuthOutput(await tokenRefresh()), null, 2));
552
630
  } else if (subCommand === 'logout') {
@@ -624,6 +702,12 @@ async function main() {
624
702
  break;
625
703
  }
626
704
 
705
+ case 'schema': {
706
+ const { run } = require('../lib/schema/command');
707
+ await run(args);
708
+ break;
709
+ }
710
+
627
711
  case 'formula': {
628
712
  const subCommand = args[0];
629
713
  const subArgs = args.slice(1);
@@ -664,6 +748,11 @@ async function main() {
664
748
  }
665
749
 
666
750
  case 'publish': {
751
+ if (args.includes('--help') || args.includes('-h')) {
752
+ const publishMain = require('../lib/app/publish');
753
+ await publishMain(args);
754
+ break;
755
+ }
667
756
  const passThroughFlags = new Set(['--skip-lint', '--health-check', '--check', '--open', '--no-open', '--compat', '--modern', '--force', '--canvas']);
668
757
  const filteredArgs = args.filter(arg => !passThroughFlags.has(arg));
669
758
  if (filteredArgs.length < 3) {
@@ -18,6 +18,10 @@ const { t } = require('../core/i18n');
18
18
  const { buildYidaI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
19
19
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
20
20
  const { throwCommandError } = require('../core/command-errors');
21
+ const {
22
+ assertLegacyDirectWriteAllowed,
23
+ extractLegacyGuardArgs,
24
+ } = require('../core/legacy-schema-guard');
21
25
  const { assertPresetThemeKey } = require('./theme-presets');
22
26
 
23
27
  const DEFAULT_APP_OPTIONS = {
@@ -281,7 +285,8 @@ function buildCreateAppPayload(params, auth, contentLocale, openExclusive, openP
281
285
 
282
286
  async function run(args) {
283
287
  const openOption = parseOpenOption(args);
284
- args = openOption.args;
288
+ const guardSplit = extractLegacyGuardArgs(openOption.args);
289
+ args = guardSplit.args;
285
290
 
286
291
  if (hasHelpFlag(args)) {
287
292
  printUsage();
@@ -301,6 +306,12 @@ async function run(args) {
301
306
  chalkError(t('create_app.usage'), { hint: t('create_app.example') });
302
307
  }
303
308
 
309
+ assertLegacyDirectWriteAllowed({
310
+ command: 'create-app',
311
+ resourceType: 'app',
312
+ action: 'create',
313
+ }, { guardOptions: guardSplit.guardOptions });
314
+
304
315
  const { appName, description, icon, iconColor, colour, navTheme, layoutDirection } = params;
305
316
 
306
317
  const { c, banner, step, label, success: chalkSuccess, result: chalkResult } = require('../core/chalk');
@@ -85,6 +85,7 @@ function createParseArgs(dependencies) {
85
85
  labelAlign: 'top',
86
86
  contentLocale: null,
87
87
  browserOpenMode: openOption.mode,
88
+ legacyGuardOptions: openOption.legacyGuardOptions || null,
88
89
  };
89
90
 
90
91
  const args = [...rawArgs];
@@ -118,6 +119,18 @@ function createParseArgs(dependencies) {
118
119
  }
119
120
  }
120
121
 
122
+ if (args.includes('--help') || args.includes('-h')) {
123
+ usage(t('create_form.usage_create_short'));
124
+ hint(t('create_form.usage_update_short'));
125
+ hint(t('create_form.example_create'));
126
+ hint(t('create_form.example_update'));
127
+ return {
128
+ mode: 'help',
129
+ help: true,
130
+ ...options
131
+ };
132
+ }
133
+
121
134
  const mode = args[0];
122
135
 
123
136
  if (mode === 'create') {
@@ -59,13 +59,18 @@
59
59
  const fs = require('fs');
60
60
  const path = require('path');
61
61
  const querystring = require('querystring');
62
- const { loadAuthData, triggerLogin, resolveBaseUrl, httpPost, httpGet, requestWithAutoLogin } = require('../core/utils');
62
+ const { httpPost, httpGet, requestWithAutoLogin } = require('../core/utils');
63
+ const { createAuthRef } = require('../core/yida-client');
63
64
  const { CliError } = require('../core/cli-error');
64
65
  const { t } = require('../core/i18n');
65
66
  const { safeParseJson } = require('../core/safe-json');
66
67
  const { buildYidaI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
67
68
  const { banner, step, label, success, fail, warn, info, error, result, usage, hint, listItem } = require('../core/chalk');
68
- const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
69
+ const { parseOpenOption: parseBrowserOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
70
+ const {
71
+ assertLegacyDirectWriteAllowed,
72
+ extractLegacyGuardArgs,
73
+ } = require('../core/legacy-schema-guard');
69
74
  const { default: babelTransform } = require('../core/babel-transform');
70
75
  const { normalizeFormulaFieldRefs } = require('../formula/evaluate');
71
76
  const { createParseArgs } = require('./create-form/args');
@@ -77,6 +82,15 @@ const { FORM_RULES_BLOCK_END, FORM_RULES_BLOCK_START } = require('./create-form/
77
82
  const { SMART_VALIDATION_BLOCK_END, SMART_VALIDATION_BLOCK_START } = require('./create-form/validation-builder');
78
83
  const { createFieldNormalizers } = require('./create-form/field-normalizers');
79
84
 
85
+ function parseOpenOption(inputArgs) {
86
+ const openOption = parseBrowserOpenOption(inputArgs);
87
+ const guardSplit = extractLegacyGuardArgs(openOption.args);
88
+ return Object.assign({}, openOption, {
89
+ args: guardSplit.args,
90
+ legacyGuardOptions: guardSplit.guardOptions,
91
+ });
92
+ }
93
+
80
94
  // ── 选项类字段类型 ───────────────────────────────────
81
95
  const OPTION_FIELD_TYPES = ['RadioField', 'SelectField', 'CheckboxField', 'MultiSelectField'];
82
96
 
@@ -3982,27 +3996,30 @@ function applyChangesToSchema(schema, changes) {
3982
3996
 
3983
3997
  // ── 发送 POST 请求(支持 302 自动重登录) ────────────
3984
3998
 
3985
- function sendPostRequest(baseUrl, requestPath, extraParams, formUuid) {
3986
- const postData = querystring.stringify(extraParams);
3999
+ function sendPostRequest(baseUrl, requestPath, extraParams, formUuid, authRef) {
4000
+ const authParams = authRef && authRef.csrfToken ? { _csrf_token: authRef.csrfToken } : {};
4001
+ const postData = querystring.stringify(Object.assign(authParams, extraParams));
3987
4002
  const referer = formUuid
3988
4003
  ? `${baseUrl}/alibaba/web/${extraParams.appType || ''}/design/pageDesigner?formUuid=${formUuid}`
3989
4004
  : baseUrl + '/';
3990
- return httpPost(baseUrl, requestPath, postData, { referer });
4005
+ return httpPost(baseUrl, requestPath, postData, authRef && authRef.cookies, { referer });
3991
4006
  }
3992
4007
 
3993
4008
  // ── 发送 updateFormConfig 请求 ───────────────────────
3994
4009
 
3995
- function sendUpdateConfigRequest(baseUrl, appType, formUuid, version, value) {
3996
- const postData = querystring.stringify({
4010
+ function sendUpdateConfigRequest(baseUrl, appType, formUuid, version, value, authRef) {
4011
+ const authParams = authRef && authRef.csrfToken ? { _csrf_token: authRef.csrfToken } : {};
4012
+ const postData = querystring.stringify(Object.assign(authParams, {
3997
4013
  formUuid: formUuid,
3998
4014
  version: version,
3999
4015
  configType: 'MINI_RESOURCE',
4000
4016
  value: value,
4001
- });
4017
+ }));
4002
4018
  return httpPost(
4003
4019
  baseUrl,
4004
4020
  `/dingtalk/web/${appType}/query/formdesign/updateFormConfig.json`,
4005
- postData
4021
+ postData,
4022
+ authRef && authRef.cookies
4006
4023
  );
4007
4024
  }
4008
4025
 
@@ -4013,6 +4030,33 @@ function resolveCorpId(authData) {
4013
4030
  return '';
4014
4031
  }
4015
4032
 
4033
+ function extractSchemaServerRevision(schemaResult) {
4034
+ const content = schemaResult && schemaResult.content && typeof schemaResult.content === 'object'
4035
+ ? schemaResult.content
4036
+ : null;
4037
+ const candidates = [
4038
+ schemaResult && schemaResult.gmtModified,
4039
+ schemaResult && schemaResult.serverRevision,
4040
+ content && content.gmtModified,
4041
+ content && content.serverRevision,
4042
+ content && content.version,
4043
+ schemaResult && schemaResult.version,
4044
+ ];
4045
+ for (const candidate of candidates) {
4046
+ if (candidate !== undefined && candidate !== null && String(candidate).trim() !== '') {
4047
+ return candidate;
4048
+ }
4049
+ }
4050
+ return undefined;
4051
+ }
4052
+
4053
+ function requireSchemaServerRevision(serverRevision, details) {
4054
+ if (serverRevision === undefined || serverRevision === null || String(serverRevision).trim() === '') {
4055
+ throwCreateFormError('Missing form schema server revision from exact readback.', 'CREATE_FORM_SCHEMA_REVISION_INVALID', details);
4056
+ }
4057
+ return serverRevision;
4058
+ }
4059
+
4016
4060
  // ── 保存 Schema 并更新表单配置(create/update 共用)──
4017
4061
  //
4018
4062
  // 封装了 saveFormSchema + updateFormConfig 两步,以及各自的 302 自动重登录重试。
@@ -4027,6 +4071,7 @@ async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, ver
4027
4071
  info(t('create_form.formula_prefix_fixed', fixedRefs));
4028
4072
  }
4029
4073
  ensureDividerThemeAction(schema);
4074
+ const serverRevision = requireSchemaServerRevision(version, { appType, formUuid });
4030
4075
 
4031
4076
  step(saveStep, t('create_form.step_save_schema', saveStep));
4032
4077
  info(t('create_form.sending_save'));
@@ -4035,8 +4080,9 @@ async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, ver
4035
4080
  return sendPostRequest(
4036
4081
  auth.baseUrl,
4037
4082
  buildApiPath(appType, 'saveFormSchema', { prefix: '_view' }),
4038
- { appType: appType, formUuid: formUuid, content: JSON.stringify(schema), schemaVersion: 'V5', prefix: '_view' },
4039
- formUuid
4083
+ { appType: appType, formUuid: formUuid, content: JSON.stringify(schema), schemaVersion: 'V5', prefix: '_view', gmtModified: serverRevision },
4084
+ formUuid,
4085
+ auth
4040
4086
  );
4041
4087
  }, authRef);
4042
4088
 
@@ -4063,7 +4109,7 @@ async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, ver
4063
4109
  info(t('create_form.sending_config'));
4064
4110
 
4065
4111
  const configResult = await requestWithAutoLogin(function (auth) {
4066
- return sendUpdateConfigRequest(auth.baseUrl, appType, formUuid, version || 1, 0);
4112
+ return sendUpdateConfigRequest(auth.baseUrl, appType, formUuid, version || 1, 0, auth);
4067
4113
  }, authRef);
4068
4114
 
4069
4115
  return { saveResult: saveResult, configResult: configResult };
@@ -4094,7 +4140,9 @@ async function mainCreate(parsedArgs, authRef) {
4094
4140
  return sendPostRequest(
4095
4141
  auth.baseUrl,
4096
4142
  buildApiPath(appType, 'saveFormSchemaInfo'),
4097
- { formType: 'receipt', title: JSON.stringify(i18n(formTitle)) }
4143
+ { formType: 'receipt', title: JSON.stringify(i18n(formTitle)) },
4144
+ undefined,
4145
+ auth
4098
4146
  );
4099
4147
  }, authRef);
4100
4148
 
@@ -4110,6 +4158,14 @@ async function mainCreate(parsedArgs, authRef) {
4110
4158
 
4111
4159
  const formUuid = createResult.content.formUuid || createResult.content;
4112
4160
  success(t('create_form.blank_created', formUuid));
4161
+ const shellSchemaResult = await requestWithAutoLogin(function (auth) {
4162
+ return sendGetRequest(
4163
+ auth.baseUrl,
4164
+ buildApiPath(appType, 'getFormSchema', { prefix: '_view', namespace: 'alibaba' }),
4165
+ { formUuid: formUuid, schemaVersion: 'V5' }
4166
+ );
4167
+ }, authRef);
4168
+ const serverRevision = extractSchemaServerRevision(shellSchemaResult);
4113
4169
 
4114
4170
  // Step 4 & 5: 生成 Schema 并保存,然后更新表单配置
4115
4171
  const corpId = resolveCorpId(authRef.authData);
@@ -4125,7 +4181,7 @@ async function mainCreate(parsedArgs, authRef) {
4125
4181
  const appliedValidations = applySmartValidations(schema, createValidationRules);
4126
4182
  info('已为创建表单写入 ' + appliedValidations.appliedRules.length + ' 条字段校验');
4127
4183
  }
4128
- const { configResult } = await saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, 1, 4);
4184
+ const { configResult } = await saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, serverRevision, 4);
4129
4185
 
4130
4186
  // 输出结果
4131
4187
  const formUrl = authRef.baseUrl + '/' + appType + '/workbench/' + formUuid;
@@ -4156,6 +4212,70 @@ async function mainCreate(parsedArgs, authRef) {
4156
4212
  }
4157
4213
  }
4158
4214
 
4215
+ async function createFormForLegacyProcess(context, input) {
4216
+ const authRef = context && context.authRef ? context.authRef : context;
4217
+ const parsedArgs = {
4218
+ mode: 'create',
4219
+ appType: input.appType,
4220
+ formTitle: input.formTitle,
4221
+ fieldsJsonOrFile: input.fieldsJsonFile,
4222
+ layout: input.layout || 'single',
4223
+ theme: input.theme || 'default',
4224
+ labelAlign: input.labelAlign || 'top',
4225
+ contentLocale: input.contentLocale || null,
4226
+ browserOpenMode: 'off',
4227
+ };
4228
+
4229
+ const { appType, formTitle, fieldsJsonOrFile, layout, theme, labelAlign } = parsedArgs;
4230
+ const { fields, validations } = readFieldsDefinition(fieldsJsonOrFile);
4231
+ const fieldCount = countDataFieldDefinitions(fields);
4232
+ const createResult = await requestWithAutoLogin(function (auth) {
4233
+ return sendPostRequest(
4234
+ auth.baseUrl,
4235
+ buildApiPath(appType, 'saveFormSchemaInfo'),
4236
+ { formType: 'receipt', title: JSON.stringify(i18n(formTitle)) },
4237
+ undefined,
4238
+ auth
4239
+ );
4240
+ }, authRef);
4241
+
4242
+ if (!createResult || !createResult.success || !createResult.content) {
4243
+ const errorMsg = createResult ? createResult.errorMsg || t('common.unknown_error') : t('common.request_failed');
4244
+ throwCreateFormError(errorMsg, 'CREATE_FORM_CREATE_BLANK_FAILED', {
4245
+ appType,
4246
+ result: createResult,
4247
+ });
4248
+ }
4249
+
4250
+ const formUuid = createResult.content.formUuid || createResult.content;
4251
+ const shellSchemaResult = await requestWithAutoLogin(function (auth) {
4252
+ return sendGetRequest(
4253
+ auth.baseUrl,
4254
+ buildApiPath(appType, 'getFormSchema', { prefix: '_view', namespace: 'alibaba' }),
4255
+ { formUuid: formUuid, schemaVersion: 'V5' }
4256
+ );
4257
+ }, authRef);
4258
+ const serverRevision = extractSchemaServerRevision(shellSchemaResult);
4259
+ const corpId = resolveCorpId(authRef.authData || authRef.cookieData || authRef);
4260
+ const schema = buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, theme, labelAlign);
4261
+ const createValidationRules = collectSmartValidationRulesFromFields(fields).concat(validations || []);
4262
+ if (createValidationRules.length > 0) {
4263
+ applySmartValidations(schema, createValidationRules);
4264
+ }
4265
+ const { saveResult, configResult } = await saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, serverRevision, 4);
4266
+ return {
4267
+ success: true,
4268
+ appType,
4269
+ formUuid,
4270
+ formTitle,
4271
+ fieldCount,
4272
+ createResult,
4273
+ saveResult,
4274
+ configResult,
4275
+ url: authRef.baseUrl + '/' + appType + '/workbench/' + formUuid,
4276
+ };
4277
+ }
4278
+
4159
4279
  // ── 为 SerialNumberField 补全 formula(递归处理子表)──
4160
4280
  //
4161
4281
  // 遍历字段列表,对每个 SerialNumberField:
@@ -4232,12 +4352,7 @@ async function mainAddOption(parsedArgs, authRef) {
4232
4352
  }
4233
4353
 
4234
4354
  let schema;
4235
- let version = 1;
4236
- if (schemaResult.content && typeof schemaResult.content === 'object' && schemaResult.content.version !== undefined) {
4237
- version = schemaResult.content.version;
4238
- } else if (schemaResult.version !== undefined) {
4239
- version = schemaResult.version;
4240
- }
4355
+ const version = extractSchemaServerRevision(schemaResult);
4241
4356
  if (schemaResult.content) {
4242
4357
  schema = typeof schemaResult.content === 'string' ? JSON.parse(schemaResult.content) : schemaResult.content;
4243
4358
  } else if (schemaResult.pages) {
@@ -4399,12 +4514,7 @@ async function mainBindDataSource(parsedArgs, authRef) {
4399
4514
  }
4400
4515
 
4401
4516
  let schema;
4402
- let version = 1;
4403
- if (schemaResult.content && typeof schemaResult.content === 'object' && schemaResult.content.version !== undefined) {
4404
- version = schemaResult.content.version;
4405
- } else if (schemaResult.version !== undefined) {
4406
- version = schemaResult.version;
4407
- }
4517
+ const version = extractSchemaServerRevision(schemaResult);
4408
4518
  if (schemaResult.content) {
4409
4519
  schema = typeof schemaResult.content === 'string' ? JSON.parse(schemaResult.content) : schemaResult.content;
4410
4520
  } else if (schemaResult.pages) {
@@ -4545,12 +4655,7 @@ async function mainRule(parsedArgs, authRef) {
4545
4655
  }
4546
4656
 
4547
4657
  let schema;
4548
- let version = 1;
4549
- if (schemaResult.content && typeof schemaResult.content === 'object' && schemaResult.content.version !== undefined) {
4550
- version = schemaResult.content.version;
4551
- } else if (schemaResult.version !== undefined) {
4552
- version = schemaResult.version;
4553
- }
4658
+ const version = extractSchemaServerRevision(schemaResult);
4554
4659
 
4555
4660
  if (schemaResult.content) {
4556
4661
  schema = typeof schemaResult.content === 'string' ? JSON.parse(schemaResult.content) : schemaResult.content;
@@ -4672,12 +4777,7 @@ async function mainPatch(parsedArgs, authRef) {
4672
4777
  }
4673
4778
 
4674
4779
  let schema;
4675
- let version = 1;
4676
- if (schemaResult.content && typeof schemaResult.content === 'object' && schemaResult.content.version !== undefined) {
4677
- version = schemaResult.content.version;
4678
- } else if (schemaResult.version !== undefined) {
4679
- version = schemaResult.version;
4680
- }
4780
+ const version = extractSchemaServerRevision(schemaResult);
4681
4781
 
4682
4782
  if (schemaResult.content) {
4683
4783
  schema = typeof schemaResult.content === 'string' ? JSON.parse(schemaResult.content) : schemaResult.content;
@@ -4784,12 +4884,7 @@ async function mainValidation(parsedArgs, authRef) {
4784
4884
  }
4785
4885
 
4786
4886
  let schema;
4787
- let version = 1;
4788
- if (schemaResult.content && typeof schemaResult.content === 'object' && schemaResult.content.version !== undefined) {
4789
- version = schemaResult.content.version;
4790
- } else if (schemaResult.version !== undefined) {
4791
- version = schemaResult.version;
4792
- }
4887
+ const version = extractSchemaServerRevision(schemaResult);
4793
4888
 
4794
4889
  if (schemaResult.content) {
4795
4890
  schema = typeof schemaResult.content === 'string' ? JSON.parse(schemaResult.content) : schemaResult.content;
@@ -4913,13 +5008,7 @@ async function mainUpdate(parsedArgs, authRef) {
4913
5008
 
4914
5009
  // 从返回结果中提取 schema 内容和版本号
4915
5010
  let schema;
4916
- let version = 1;
4917
-
4918
- if (schemaResult.content && typeof schemaResult.content === 'object' && schemaResult.content.version !== undefined) {
4919
- version = schemaResult.content.version;
4920
- } else if (schemaResult.version !== undefined) {
4921
- version = schemaResult.version;
4922
- }
5011
+ const version = extractSchemaServerRevision(schemaResult);
4923
5012
 
4924
5013
  if (schemaResult.content) {
4925
5014
  schema = typeof schemaResult.content === 'string' ? JSON.parse(schemaResult.content) : schemaResult.content;
@@ -5057,24 +5146,21 @@ async function mainUpdate(parsedArgs, authRef) {
5057
5146
 
5058
5147
  async function run(args) {
5059
5148
  const parsedArgs = parseArgs(args);
5149
+ if (parsedArgs.help) {
5150
+ return parsedArgs;
5151
+ }
5152
+ assertLegacyDirectWriteAllowed({
5153
+ command: `create-form ${parsedArgs.mode}`,
5154
+ resourceType: 'form',
5155
+ action: parsedArgs.mode === 'create' ? 'create' : 'update',
5156
+ appType: parsedArgs.appType,
5157
+ formUuid: parsedArgs.formUuid,
5158
+ }, { guardOptions: parsedArgs.legacyGuardOptions });
5060
5159
 
5061
5160
  step(1, t('common.step_login', 1));
5062
- let authData = loadAuthData();
5063
- if (!authData) {
5064
- warn(t('common.login_no_cache'));
5065
- authData = triggerLogin();
5066
- }
5067
- const baseUrl = resolveBaseUrl(authData);
5068
- const authRef = {
5069
- baseUrl,
5070
- authData,
5071
- authMode: authData.auth_mode || '',
5072
- authSource: authData.auth_source || '',
5073
- corpId: authData.corp_id || '',
5074
- userId: authData.user_id || '',
5075
- };
5076
- success(t('common.login_ready', baseUrl));
5077
- label('Locale:', resolveContentLocale({ locale: parsedArgs.contentLocale, baseUrl: baseUrl }));
5161
+ const authRef = createAuthRef();
5162
+ success(t('common.login_ready', authRef.baseUrl));
5163
+ label('Locale:', resolveContentLocale({ locale: parsedArgs.contentLocale, baseUrl: authRef.baseUrl }));
5078
5164
 
5079
5165
  return dispatchCreateFormCommand(
5080
5166
  parsedArgs,
@@ -5099,6 +5185,7 @@ if (require.main === module) {
5099
5185
  }
5100
5186
 
5101
5187
  module.exports = {
5188
+ createFormForLegacyProcess,
5102
5189
  run,
5103
5190
  parseArgs,
5104
5191
  _private: {