openyida 2026.9.3 → 2026.9.4

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 (143) hide show
  1. package/README.md +9 -4
  2. package/bin/yida.js +6 -0
  3. package/lib/app/canvas-compile.js +2 -0
  4. package/lib/app/create-app.js +11 -115
  5. package/lib/app/create-form/batch.js +204 -0
  6. package/lib/app/create-form.js +1 -0
  7. package/lib/app/inline-css-guard.js +79 -0
  8. package/lib/app/page-linter.js +162 -0
  9. package/lib/app/services/canvas-page-schema-builder.js +18 -5
  10. package/lib/app/theme-from-design.js +88 -0
  11. package/lib/app/theme-presets.js +7 -0
  12. package/lib/app/update-app.js +103 -43
  13. package/lib/core/agent-capabilities.js +14 -0
  14. package/lib/core/command-manifest.js +87 -10
  15. package/lib/core/locales/en.js +19 -6
  16. package/lib/core/locales/zh.js +19 -6
  17. package/lib/core/query-data.js +31 -9
  18. package/lib/core/sample.js +80 -5
  19. package/lib/design-plan/design-plan.js +92 -0
  20. package/lib/design-plan/files.js +73 -0
  21. package/lib/design-plan/init.js +112 -0
  22. package/lib/design-plan/materialize.js +898 -0
  23. package/lib/design-plan/normalize.js +275 -0
  24. package/lib/design-plan/parallel.js +56 -0
  25. package/lib/design-plan/patch.js +174 -0
  26. package/lib/design-plan/preview.js +154 -0
  27. package/lib/design-plan/validate.js +133 -0
  28. package/lib/samples/openyida-scaffold/canvas-dialog.canvas.jsx +124 -0
  29. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +265 -68
  30. package/lib/samples/openyida-scaffold/canvas-nav/data.jsx +60 -0
  31. package/lib/samples/openyida-scaffold/canvas-nav/dock.jsx +13 -0
  32. package/lib/samples/openyida-scaffold/canvas-nav/mixed.jsx +34 -0
  33. package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +43 -0
  34. package/lib/samples/openyida-scaffold/canvas-nav/side.jsx +19 -0
  35. package/lib/samples/openyida-scaffold/canvas-nav/sidebar.jsx +96 -0
  36. package/lib/samples/openyida-scaffold/canvas-nav/tabs.jsx +31 -0
  37. package/lib/samples/openyida-scaffold/canvas-nav/top.jsx +31 -0
  38. package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +2 -1
  39. package/lib/samples/yida-rechart/trend-combo.canvas.jsx +1 -1
  40. package/package.json +1 -1
  41. package/scripts/postinstall.js +5 -3
  42. package/yida-skills/SKILL.md +37 -6
  43. package/yida-skills/references/field-and-url-reference.md +11 -1
  44. package/yida-skills/skills/yida-app/SKILL.md +42 -7
  45. package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
  46. package/yida-skills/skills/yida-app/workflow/incremental-preview.md +42 -0
  47. package/yida-skills/skills/yida-app/workflow/parallel-work.md +69 -0
  48. package/yida-skills/skills/yida-app/workflow/plan/step-1-understand.md +5 -0
  49. package/yida-skills/skills/yida-app/workflow/plan/step-2-confirm.md +7 -0
  50. package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +69 -0
  51. package/yida-skills/skills/yida-app/workflow/plan/workflow.md +45 -0
  52. package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +2 -0
  53. package/yida-skills/skills/yida-app/workflow/step-2-design.md +24 -26
  54. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +24 -7
  55. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +8 -2
  56. package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +2 -0
  57. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +12 -4
  58. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +20 -10
  59. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +8 -4
  60. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +9 -6
  61. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
  62. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +55 -38
  63. package/yida-skills/skills/yida-canvas-custom-page/references/dialog-guide.md +35 -0
  64. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +45 -93
  65. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +6 -0
  66. package/yida-skills/skills/yida-canvas-table-form/SKILL.md +2 -2
  67. package/yida-skills/skills/yida-create-app/SKILL.md +33 -23
  68. package/yida-skills/skills/yida-create-form-page/SKILL.md +4 -0
  69. package/yida-skills/skills/yida-create-form-page/references/batch-forms.md +55 -0
  70. package/yida-skills/skills/yida-create-page/SKILL.md +2 -0
  71. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +2 -0
  72. package/yida-skills/skills/yida-design/SKILL.md +31 -36
  73. package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +187 -0
  74. package/yida-skills/skills/yida-design/references/design-mode.md +14 -0
  75. package/yida-skills/skills/yida-design/references/style-design-selection.md +3 -3
  76. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +9 -1
  77. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +43 -1
  78. package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +1 -0
  79. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +2 -2
  80. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +26 -0
  81. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/agents/openai.yaml +4 -0
  82. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/README.md +214 -0
  83. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +1181 -0
  84. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/business-flows.png +0 -0
  85. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/data-models.png +0 -0
  86. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-normal.svg +4 -0
  87. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-process.svg +6 -0
  88. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-fit.svg +3 -0
  89. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-relations.svg +7 -0
  90. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-in.svg +4 -0
  91. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-out.svg +4 -0
  92. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/overview.png +0 -0
  93. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/pages.png +0 -0
  94. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/visual-style.png +0 -0
  95. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +138 -0
  96. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +456 -0
  97. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/page-patterns.md +204 -0
  98. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +137 -0
  99. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +168 -0
  100. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +1172 -0
  101. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +190 -0
  102. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +98 -0
  103. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +339 -0
  104. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +348 -0
  105. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +344 -0
  106. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +345 -0
  107. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +355 -0
  108. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +377 -0
  109. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +352 -0
  110. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +389 -0
  111. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +364 -0
  112. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/index.json +329 -0
  113. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +348 -0
  114. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +345 -0
  115. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +379 -0
  116. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +347 -0
  117. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +352 -0
  118. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +386 -0
  119. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +384 -0
  120. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +388 -0
  121. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +341 -0
  122. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +361 -0
  123. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/page-patterns/index.json +63 -0
  124. package/yida-skills/skills/yida-design/workflow/output-design.md +39 -5
  125. package/yida-skills/skills/yida-design/workflow/output-prd.md +1 -1
  126. package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +3 -3
  127. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +7 -11
  128. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +6 -0
  129. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +3 -3
  130. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +4 -4
  131. package/yida-skills/skills/yida-nav-group/SKILL.md +1 -0
  132. package/yida-skills/skills/yida-nav-shell/SKILL.md +21 -18
  133. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +212 -266
  134. package/yida-skills/skills/yida-prd/SKILL.md +21 -32
  135. package/yida-skills/skills/yida-prd/references/plan-content.md +169 -0
  136. package/yida-skills/skills/yida-prd/workflow/output-prd.md +36 -8
  137. package/yida-skills/skills/yida-prd/workflow/plan-business.md +12 -0
  138. package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +7 -3
  139. package/yida-skills/skills/yida-publish-page/SKILL.md +1 -1
  140. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +17 -19
  141. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +65 -0
  142. package/yida-skills/skills-index.json +11 -10
  143. package/yida-skills/skills/yida-prd/workflow/step-1-read-brief.md +0 -15
package/README.md CHANGED
@@ -166,9 +166,9 @@ For a user-facing list of supported features and matching CLI commands, see [Ope
166
166
  ### Application and Form Management
167
167
 
168
168
  ```bash
169
- openyida create-app "CRM"
169
+ openyida create-app --name "CRM" --desc "Customer management"
170
170
  openyida sample yida-design app-theme --output .cache/openyida/crm/app-theme.css
171
- openyida create-app --name "CRM" --desc "Customer management" --theme-file .cache/openyida/crm/app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
171
+ openyida update-app APP_XXX --theme-file .cache/openyida/crm/app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
172
172
  openyida app-list --size 20
173
173
  openyida corp-efficiency
174
174
  openyida create-form create APP_XXX "Customer" .cache/openyida/forms/customer-fields.json
@@ -391,6 +391,10 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
391
391
  | `openyida app-list [--size N]` | List my Yida apps |
392
392
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
393
393
  | `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
394
+ | `openyida design-plan init <requirement-brief.json> [--theme-id <id>] [--output-dir <dir>] [--json]` | Initialize a plan draft from confirmed requirements |
395
+ | `openyida design-plan preview <build-plan.json> --part-file <module.json> [--json]` | Update plan drafts by module |
396
+ | `openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--check] [--json]` | Generate and validate design-plan artifacts from build-plan.json |
397
+ | `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch a design plan by field path and invalidate prior confirmation |
394
398
  | `openyida update-app <appType> [--name "..."] [--theme-file <css>] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
395
399
  | `openyida app-online <appType> [--to-ding-app-center] [--show-app-center]` | Enable a Yida app |
396
400
  | `openyida app-offline <appType> [--to-ding-app-center] [--show-app-center]` | Disable a Yida app |
@@ -404,6 +408,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
404
408
 
405
409
  | Command | Description |
406
410
  |---------|-------------|
411
+ | `openyida create-form batch <appType> <plan.json> [--concurrency 1..4] [--check] [--json]` | Create forms concurrently by dependency |
407
412
  | `openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--icon auto\|<iconName>] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
408
413
  | `openyida create-form icons [--json]` | List available form navigation icons |
409
414
  | `openyida create-form validate-fields <fieldsJsonOrFile> [--json]` | Validate form field JSON locally |
@@ -424,7 +429,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
424
429
  | `openyida check-page <src> [--compat]` | Check custom page standards |
425
430
  | `openyida compile <src>` | Compile custom page locally |
426
431
  | `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--auto-nav-order] [--open\|--no-open]` | Compile and publish custom page |
427
- | `openyida update-form-config <appType> ...` | Update form configuration |
432
+ | `openyida update-form-config <appType> <formUuid> <true\|false\|keep> "<title>" [--locale zh_CN\|en_US\|ja_JP]` | Update form configuration |
428
433
  | `openyida get-form-config <appType> <formUuid> [--json]` | Query form configuration |
429
434
 
430
435
  ### Data & Permissions
@@ -510,7 +515,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
510
515
  | `openyida a2a <serve\|agent-card> [options]` | Start local read-only A2A adapter or print Agent Card |
511
516
  | `openyida bridge start [--token <pair-token>] [--port 6736] [--origin https://demo.aliwork.com] [--open\|--no-open]` | Start OpenYida local web bridge service |
512
517
  | `openyida copy [--force]` | Copy project working directory |
513
- | `openyida sample [--list]` | Output code samples/templates |
518
+ | `openyida sample [--list] [<skill> <name>] [--output <file>] [--var KEY=VALUE ...] [--design-file <design.md>]` | Output code samples/templates |
514
519
  | `openyida doctor [--fix]` | Environment diagnostics & auto-fix |
515
520
  | `openyida db-seq-fix [--fix]` | Detect and repair PostgreSQL sequence drift |
516
521
  | `openyida formula evaluate <formula\|file> [--schema file]` | Static-check Yida formula syntax and field refs |
package/bin/yida.js CHANGED
@@ -746,6 +746,12 @@ async function main() {
746
746
  break;
747
747
  }
748
748
 
749
+ case 'design-plan': {
750
+ const { run } = require('../lib/design-plan/design-plan');
751
+ await run(args);
752
+ break;
753
+ }
754
+
749
755
  case 'create-page': {
750
756
  const { run } = require('../lib/app/create-page');
751
757
  await run(args);
@@ -48,6 +48,7 @@ const {
48
48
  formatFormDetailLinkMessage,
49
49
  } = require('./form-detail-link-guard');
50
50
  const { t } = require('../core/i18n');
51
+ const { assertInlineCssStructure } = require('./inline-css-guard');
51
52
 
52
53
  /**
53
54
  * 依赖 → window 别名白名单,用于把 import 改写到运行时全局对象。
@@ -791,6 +792,7 @@ function compileCanvasLocal(source, options = {}) {
791
792
  assertNoUnavailableCanvasInstanceApis(source, options);
792
793
  assertNoBareDependencyGlobals(source, options);
793
794
  assertNoManualDependencyGlobals(source, options);
795
+ assertInlineCssStructure(source, options);
794
796
 
795
797
  const importedModules = extractImportedModules(source);
796
798
 
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * create-app.js - 宜搭应用创建命令
3
3
  *
4
- * 用法:openyida create-app "<appName>" [description] [icon] [iconColor] [colour] [navTheme] [layoutDirection]
5
- * openyida create-app --name "<appName>" --theme-file ./app-theme.css [--nav-theme light] [--logo-source appIcon] [--layout l_shape]
4
+ * 用法:openyida create-app "<appName>" [description] [icon] [iconColor]
5
+ * openyida create-app --name "<appName>" [--desc "<description>"]
6
6
  */
7
7
 
8
8
  'use strict';
@@ -19,19 +19,10 @@ const { t } = require('../core/i18n');
19
19
  const { buildYidaI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
20
20
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
21
21
  const { throwCommandError } = require('../core/command-errors');
22
- const { assertPresetThemeKey } = require('./theme-presets');
23
22
  const { resolveBuilderAiSource } = require('./builder-ai-source');
24
- const { normalizeCssColorToHex, readThemeCssFile } = require('./custom-theme');
25
- const {
26
- applyCustomThemeUpdate,
27
- assertLogoSource,
28
- assertNavTheme,
29
- normalizeLayoutDirection,
30
- } = require('./update-app');
31
23
 
32
24
  const DEFAULT_APP_OPTIONS = {
33
25
  iconColor: '#0089FF',
34
- colour: 'podBlue',
35
26
  };
36
27
 
37
28
  // 与 yida-next/src/components/IconSelect/Icon.js 保持一致。
@@ -61,25 +52,12 @@ const APP_ICON_NAMES = Object.freeze([
61
52
  'shenbao',
62
53
  'daka',
63
54
  ]);
64
- const DEFAULT_CREATE_LAYOUT_DIRECTION = 'l_shape';
65
55
 
66
56
  function selectRandomAppIcon(random = Math.random) {
67
57
  const index = Math.min(APP_ICON_NAMES.length - 1, Math.floor(random() * APP_ICON_NAMES.length));
68
58
  return APP_ICON_NAMES[Math.max(0, index)];
69
59
  }
70
60
 
71
- function applyThemeFileCreateDefaults(params, themeFileInfo) {
72
- if (!params || !params.themeFile || !themeFileInfo) {return params;}
73
- const iconColor = normalizeCssColorToHex(themeFileInfo.themeColor);
74
- if (!iconColor) {
75
- throw new Error(`--color-brand1-6 无法转换为应用图标色: ${themeFileInfo.themeColor}`);
76
- }
77
- return {
78
- ...params,
79
- iconColor,
80
- };
81
- }
82
-
83
61
  const INDUSTRY_APP_DEFAULTS = [
84
62
  {
85
63
  name: 'legal',
@@ -87,7 +65,6 @@ const INDUSTRY_APP_DEFAULTS = [
87
65
  options: {
88
66
  icon: 'xian-falv',
89
67
  iconColor: '#5C72FF',
90
- colour: 'podBlue',
91
68
  },
92
69
  },
93
70
  {
@@ -96,7 +73,6 @@ const INDUSTRY_APP_DEFAULTS = [
96
73
  options: {
97
74
  icon: 'xian-diqiu',
98
75
  iconColor: '#00B853',
99
- colour: 'podGreen',
100
76
  },
101
77
  },
102
78
  {
@@ -105,7 +81,6 @@ const INDUSTRY_APP_DEFAULTS = [
105
81
  options: {
106
82
  icon: 'xian-baogao',
107
83
  iconColor: '#14A9FF',
108
- colour: 'podBlue',
109
84
  },
110
85
  },
111
86
  {
@@ -114,7 +89,6 @@ const INDUSTRY_APP_DEFAULTS = [
114
89
  options: {
115
90
  icon: 'xian-qiye',
116
91
  iconColor: '#5C72FF',
117
- colour: 'podBlue',
118
92
  },
119
93
  },
120
94
  ];
@@ -206,11 +180,7 @@ function parseCreateAppArgs(args, random = Math.random) {
206
180
  description: null,
207
181
  icon: null,
208
182
  iconColor: null,
209
- colour: null,
210
- navTheme: null,
211
- logoSource: null,
212
- layoutDirection: null,
213
- themeFile: null,
183
+
214
184
  locale: null,
215
185
  };
216
186
  const positional = [];
@@ -241,30 +211,20 @@ function parseCreateAppArgs(args, random = Math.random) {
241
211
  break;
242
212
  case '--theme':
243
213
  case '--colour':
244
- params.colour = readOptionValue(args, index, arg);
245
- index++;
246
- break;
247
214
  case '--nav-theme':
248
215
  case '--navTheme':
249
- params.navTheme = readOptionValue(args, index, arg);
250
- index++;
251
- break;
252
216
  case '--logo-source':
253
217
  case '--logoSource':
254
- params.logoSource = readOptionValue(args, index, arg);
255
- index++;
256
- break;
257
218
  case '--theme-file':
258
219
  case '--custom-theme-file':
259
- params.themeFile = readOptionValue(args, index, arg);
260
- index++;
261
- break;
220
+ case '--theme-color':
221
+ case '--themeColor':
222
+ case '--hide-app-nav':
223
+ case '--show-app-nav':
262
224
  case '--layout':
263
225
  case '--layout-direction':
264
226
  case '--layoutDirection':
265
- params.layoutDirection = readOptionValue(args, index, arg);
266
- index++;
267
- break;
227
+ throw new Error(t('create_app.update_only_option', arg));
268
228
  case '--locale':
269
229
  case '--content-locale':
270
230
  case '--lang':
@@ -282,8 +242,8 @@ function parseCreateAppArgs(args, random = Math.random) {
282
242
  }
283
243
  }
284
244
 
285
- if (positional.length > 7) {
286
- throw new Error(`Too many positional arguments: ${positional.slice(7).join(' ')}`);
245
+ if (positional.length > 4) {
246
+ throw new Error(t('create_app.update_only_option', 'colour/navTheme/layoutDirection'));
287
247
  }
288
248
 
289
249
  params.appName = params.appName || positional[0] || null;
@@ -298,24 +258,6 @@ function parseCreateAppArgs(args, random = Math.random) {
298
258
  : inferredDefaults.icon
299
259
  );
300
260
  params.iconColor = params.iconColor || positional[3] || inferredDefaults.iconColor;
301
- params.colour = params.colour || positional[4] || inferredDefaults.colour;
302
- assertPresetThemeKey(params.colour);
303
- params.navTheme = params.navTheme || positional[5] || null;
304
- params.layoutDirection = normalizeLayoutDirection(
305
- params.layoutDirection || positional[6] || DEFAULT_CREATE_LAYOUT_DIRECTION
306
- );
307
- assertNavTheme(params.navTheme);
308
- assertLogoSource(params.logoSource);
309
- if (params.logoSource && !params.themeFile) {
310
- throw new Error('--logo-source must be used with --theme-file so the navigation theme is saved atomically');
311
- }
312
- if (params.themeFile) {
313
- params.navTheme = params.navTheme || 'light';
314
- params.logoSource = params.logoSource || 'appIcon';
315
- if (params.logoSource !== 'appIcon') {
316
- throw new Error('create-app only supports --logo-source appIcon because a new app has no homepageLogo image yet');
317
- }
318
- }
319
261
  params.industry = inferredDefaults.industry;
320
262
 
321
263
  return params;
@@ -337,9 +279,6 @@ function buildCreateAppPayload(params, auth, contentLocale, openExclusive, openP
337
279
  description,
338
280
  icon,
339
281
  iconColor,
340
- colour,
341
- navTheme,
342
- layoutDirection,
343
282
  } = params;
344
283
  const iconValue = `${icon}%%${iconColor}`;
345
284
  const payload = {
@@ -348,7 +287,6 @@ function buildCreateAppPayload(params, auth, contentLocale, openExclusive, openP
348
287
  description: JSON.stringify(buildYidaI18n(description, { en_US: description, ja_JP: description })),
349
288
  icon: iconValue,
350
289
  iconUrl: iconValue,
351
- colour,
352
290
  defaultLanguage: contentLocale,
353
291
  openExclusive,
354
292
  openPhysicColumn,
@@ -359,12 +297,6 @@ function buildCreateAppPayload(params, auth, contentLocale, openExclusive, openP
359
297
  builderAiSource: resolveBuilderAiSource(),
360
298
  createWithModernTheme: 'y',
361
299
  };
362
- if (navTheme) {
363
- payload.navTheme = navTheme;
364
- }
365
- if (layoutDirection) {
366
- payload.layoutDirection = layoutDirection;
367
- }
368
300
  return payload;
369
301
  }
370
302
 
@@ -390,13 +322,7 @@ async function run(args) {
390
322
  chalkError(t('create_app.usage'), { hint: t('create_app.example') });
391
323
  }
392
324
 
393
- // 在远端创建应用前先完成本地主题校验,避免 CSS 缺失或 token 非法时留下半成品应用。
394
- if (params.themeFile) {
395
- const themeFileInfo = readThemeCssFile(params.themeFile);
396
- Object.assign(params, applyThemeFileCreateDefaults(params, themeFileInfo));
397
- }
398
-
399
- const { appName, description, icon, iconColor, colour, navTheme, logoSource, layoutDirection, themeFile } = params;
325
+ const { appName, description, icon, iconColor } = params;
400
326
 
401
327
  const { c, banner, step, label, success: chalkSuccess, result: chalkResult } = require('../core/chalk');
402
328
 
@@ -404,7 +330,6 @@ async function run(args) {
404
330
  label('Name', appName);
405
331
  label('Desc', description);
406
332
  label('Icon', `${icon} ${c.dim}(${iconColor})${c.reset}`);
407
- label('Theme', `${themeFile || colour} / ${navTheme || 'default'} / ${logoSource || 'default'} / ${layoutDirection || 'default'}`);
408
333
 
409
334
  // Step 1: 读取登录态
410
335
  step(1, t('common.step_login', 1));
@@ -447,29 +372,6 @@ async function run(args) {
447
372
  const appUrl = `${authRef.baseUrl}/${appType}/workbench`;
448
373
  const corpId = authRef.corpId || (authRef.authData && authRef.authData.corp_id) || '';
449
374
 
450
- if (themeFile) {
451
- step(3, t('update_app.step_update'));
452
- const themeResponse = await applyCustomThemeUpdate(appType, {
453
- themeFile,
454
- navTheme,
455
- logoSource,
456
- layoutDirection,
457
- }, authRef);
458
- if (!themeResponse || !themeResponse.success) {
459
- const errorMsg = themeResponse
460
- ? themeResponse.errorMsg || t('common.unknown_error')
461
- : t('common.request_failed');
462
- console.log(JSON.stringify({
463
- success: false,
464
- appCreated: true,
465
- appType,
466
- themeApplied: false,
467
- error: errorMsg,
468
- }));
469
- throwCommandError(t('create_app.failed', errorMsg));
470
- }
471
- }
472
-
473
375
  chalkResult(true, t('create_app.success'), [
474
376
  ['appType', appType],
475
377
  ['Corp ID', corpId || t('common.unknown_error')],
@@ -486,10 +388,6 @@ async function run(args) {
486
388
  appName,
487
389
  corpId,
488
390
  url: appUrl,
489
- themeFile: themeFile || undefined,
490
- navTheme: themeFile ? navTheme : undefined,
491
- logoSource: themeFile ? logoSource : undefined,
492
- layoutDirection,
493
391
  },
494
392
  appUrl,
495
393
  { stage: 'create_app_success', title: appName },
@@ -505,11 +403,9 @@ async function run(args) {
505
403
 
506
404
  module.exports = {
507
405
  APP_ICON_NAMES,
508
- DEFAULT_CREATE_LAYOUT_DIRECTION,
509
406
  run,
510
407
  parseCreateAppArgs,
511
408
  inferAppDefaults,
512
409
  selectRandomAppIcon,
513
- applyThemeFileCreateDefaults,
514
410
  buildCreateAppPayload,
515
411
  };
@@ -0,0 +1,204 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const { execFile } = require('child_process');
7
+ const { CliError } = require('../../core/cli-error');
8
+ const { t } = require('../../core/i18n');
9
+
10
+ function invalid(reason) {
11
+ throw new CliError(t('create_form.batch_invalid'), { code: 'FORM_BATCH_INVALID', details: { reason } });
12
+ }
13
+
14
+ function parseArgs(args) {
15
+ const options = { concurrency: 3, check: false };
16
+ const positional = [];
17
+ for (let i = 0; i < args.length; i++) {
18
+ if (args[i] === '--concurrency') {
19
+ options.concurrency = Number(args[++i]);
20
+ } else if (args[i] === '--check') {
21
+ options.check = true;
22
+ } else if (args[i] !== '--json' && args[i] !== '--quiet') {
23
+ if (args[i].startsWith('--')) { invalid(args[i]); }
24
+ positional.push(args[i]);
25
+ }
26
+ }
27
+ if (positional.length !== 2 || !Number.isInteger(options.concurrency) || options.concurrency < 1 || options.concurrency > 4) {
28
+ invalid('create-form batch <appType> <plan.json> [--concurrency 1..4] [--check] [--json]');
29
+ }
30
+ return { ...options, appType: positional[0], file: path.resolve(positional[1]) };
31
+ }
32
+
33
+ // References occupy a complete JSON value; ordinary text is preserved.
34
+ function mapReferences(value, resolve) {
35
+ if (Array.isArray(value)) { return value.map(item => mapReferences(item, resolve)); }
36
+ if (!value || typeof value !== 'object') { return value; }
37
+ if ('$form' in value) {
38
+ if (typeof value.$form !== 'string' || Object.keys(value).some(key => !['$form', 'field'].includes(key)) ||
39
+ ('field' in value && (typeof value.field !== 'string' || !value.field.trim()))) { invalid('reference'); }
40
+ return resolve(value.$form, value.field);
41
+ }
42
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, mapReferences(item, resolve)]));
43
+ }
44
+
45
+ function loadPlan(file) {
46
+ const input = JSON.parse(fs.readFileSync(file, 'utf8'));
47
+ if (!Array.isArray(input.forms) || !input.forms.length) { invalid('forms'); }
48
+ const forms = input.forms.map(form => {
49
+ if (!form || !/^[a-zA-Z][\w-]*$/.test(form.key || '') || typeof form.title !== 'string' || !form.title.trim() ||
50
+ (form.fields === undefined) === (form.fieldsFile === undefined) ||
51
+ (form.dependsOn !== undefined && (!Array.isArray(form.dependsOn) || form.dependsOn.some(key => typeof key !== 'string')))) { invalid('form'); }
52
+ if (Object.keys(form).some(key => !['key', 'title', 'fields', 'fieldsFile', 'dependsOn', 'formUuid', 'icon', 'locale'].includes(key))) { invalid(form.key); }
53
+ if (form.formUuid !== undefined && (typeof form.formUuid !== 'string' || !form.formUuid.startsWith('FORM'))) { invalid(form.key); }
54
+ for (const key of ['icon', 'locale']) { if (form[key] !== undefined && typeof form[key] !== 'string') { invalid(key); } }
55
+ const fields = form.fieldsFile === undefined ? form.fields : JSON.parse(fs.readFileSync(path.resolve(path.dirname(file), form.fieldsFile), 'utf8'));
56
+ const dependencies = new Set(form.dependsOn || []);
57
+ mapReferences(fields, key => { dependencies.add(key); return 'FORM-PRECHECK'; });
58
+ return { ...form, fields, dependsOn: [...dependencies] };
59
+ });
60
+ const remaining = new Set(forms.map(form => form.key));
61
+ if (remaining.size !== forms.length) { invalid('duplicate key'); }
62
+ for (const form of forms) {
63
+ if (form.dependsOn.some(key => !remaining.has(key))) { invalid(`unknown dependency: ${form.key}`); }
64
+ }
65
+ const groups = [];
66
+ while (remaining.size) {
67
+ const group = forms.filter(form => remaining.has(form.key) && form.dependsOn.every(key => !remaining.has(key))).map(form => form.key);
68
+ if (!group.length) { invalid('cyclic dependency'); }
69
+ groups.push(group);
70
+ group.forEach(key => remaining.delete(key));
71
+ }
72
+ return { forms, groups };
73
+ }
74
+
75
+ function parseOutput(stdout) {
76
+ const text = String(stdout || '').trim();
77
+ try { return JSON.parse(text); } catch (_) { /* Some commands emit progress before JSON. */ }
78
+ let output = null;
79
+ for (const line of text.split('\n').reverse()) {
80
+ try {
81
+ const item = JSON.parse(line);
82
+ if (!output) { output = item; }
83
+ const formUuid = item.formUuid || item.details?.formUuid;
84
+ if (output && typeof output === 'object' && formUuid && !output.formUuid) { output.formUuid = formUuid; }
85
+ } catch (_) { /* Read the last result and preserve any earlier created ID. */ }
86
+ }
87
+ return output;
88
+ }
89
+
90
+ function execute(args) {
91
+ return new Promise((resolve, reject) => {
92
+ execFile(process.execPath, [path.resolve(__dirname, '../../../bin/yida.js'), ...args, '--quiet'], {
93
+ env: { ...process.env, YIDA_QUIET: '1' }, timeout: 180000, maxBuffer: 8 * 1024 * 1024,
94
+ }, (error, stdout) => {
95
+ const output = parseOutput(stdout);
96
+ if (error || !output || typeof output !== 'object' || output.success === false) {
97
+ reject(Object.assign(new Error(output?.errorMsg || output?.error || error?.message || 'Missing command result'), { output }));
98
+ } else { resolve(output); }
99
+ });
100
+ });
101
+ }
102
+
103
+ async function schedule(forms, concurrency, results, worker, save) {
104
+ const pending = new Set(forms.filter(form => !results[form.key] || results[form.key].status === 'blocked').map(form => form.key));
105
+ const active = new Map();
106
+ try {
107
+ while (pending.size || active.size) {
108
+ const pendingBefore = pending.size;
109
+ for (const form of forms) {
110
+ if (!pending.has(form.key)) { continue; }
111
+ const deps = form.dependsOn.map(key => results[key]?.status);
112
+ if (form.dependsOn.some(key => !pending.has(key) && !active.has(key) && ['failed', 'running', 'blocked'].includes(results[key]?.status))) {
113
+ results[form.key] = { status: 'blocked' }; pending.delete(form.key); save(); continue;
114
+ }
115
+ if (active.size >= concurrency || deps.some(status => status !== 'success')) { continue; }
116
+ pending.delete(form.key);
117
+ results[form.key] = { status: 'running' };
118
+ save(); // Record intent before the request; an interrupted create is never retried automatically.
119
+ const task = Promise.resolve().then(() => worker(form)).then(output => {
120
+ results[form.key] = { ...output, status: 'success' };
121
+ }, error => {
122
+ results[form.key] = { ...results[form.key], status: 'failed', error: error.message,
123
+ ...((error.output?.formUuid || error.output?.details?.formUuid) ? { formUuid: error.output.formUuid || error.output.details.formUuid } : {}) };
124
+ }).then(() => { save(); }).finally(() => active.delete(form.key));
125
+ active.set(form.key, task);
126
+ }
127
+ if (active.size) {
128
+ await Promise.race(active.values());
129
+ } else if (pending.size === pendingBefore && pending.size) { invalid('unresolved dependency'); }
130
+ }
131
+ } catch (error) {
132
+ await Promise.allSettled(active.values());
133
+ throw error;
134
+ }
135
+ return results;
136
+ }
137
+
138
+ async function run(args, dependencies = {}) {
139
+ const options = parseArgs(args);
140
+ const { forms, groups } = loadPlan(options.file);
141
+ const call = dependencies.execute || execute;
142
+ // Preflight all definitions before the first remote mutation.
143
+ await schedule(forms.map(form => ({ ...form, dependsOn: [] })), options.concurrency, {}, async form => {
144
+ const fields = mapReferences(form.fields, (_, field) => field ? 'textField_precheck' : 'FORM-PRECHECK');
145
+ return call(['create-form', 'validate-fields', JSON.stringify(fields), '--json']);
146
+ }, () => {}).then(results => {
147
+ if (Object.values(results).some(item => item.status !== 'success')) { invalid(results); }
148
+ });
149
+ if (options.check) {
150
+ const output = { success: true, checked: true, groups, concurrency: options.concurrency };
151
+ console.log(JSON.stringify(output)); return output;
152
+ }
153
+ const stateFile = `${options.file}.state.json`;
154
+ const lock = `${stateFile}.lock`;
155
+ const fingerprint = crypto.createHash('sha256').update(JSON.stringify({ appType: options.appType, forms })).digest('hex');
156
+ const fd = fs.openSync(lock, 'wx');
157
+ const save = state => {
158
+ fs.writeFileSync(`${stateFile}.tmp`, JSON.stringify(state, null, 2));
159
+ fs.renameSync(`${stateFile}.tmp`, stateFile);
160
+ };
161
+ try {
162
+ const state = fs.existsSync(stateFile) ? JSON.parse(fs.readFileSync(stateFile, 'utf8')) : { fingerprint, appType: options.appType, results: {} };
163
+ if (state.fingerprint !== fingerprint) { invalid('state belongs to a different plan; reconcile existing resources before preparing a new batch'); }
164
+ await call(['login', '--check-only', '--json']);
165
+ // Read back completed resources before their IDs can be used by dependent forms.
166
+ for (const form of forms.filter(item => state.results[item.key]?.status === 'success')) {
167
+ const item = state.results[form.key];
168
+ const schema = await call(['get-schema', options.appType, item.formUuid, '--field-map-json']);
169
+ if (schema.formUuid !== item.formUuid || !Array.isArray(schema.fields)) { invalid(`schema: ${form.key}`); }
170
+ item.fields = schema.fields;
171
+ }
172
+ const resolve = (key, field) => {
173
+ const item = state.results[key];
174
+ if (item?.status !== 'success') { invalid(`dependency: ${key}`); }
175
+ if (!field) { return item.formUuid; }
176
+ const matches = item.fields.filter(value => value.fieldId === field || value.label === field);
177
+ if (matches.length !== 1) { invalid(`field: ${key}.${field}`); }
178
+ return matches[0].fieldId;
179
+ };
180
+ await schedule(forms, options.concurrency, state.results, async form => {
181
+ let formUuid = form.formUuid;
182
+ if (!formUuid) {
183
+ const argv = ['create-form', 'create', options.appType, form.title, JSON.stringify(mapReferences(form.fields, resolve)), '--no-open'];
184
+ for (const key of ['icon', 'locale']) { if (form[key]) { argv.push(`--${key}`, form[key]); } }
185
+ const created = await call(argv);
186
+ if (typeof created.formUuid !== 'string' || !created.formUuid.startsWith('FORM')) { invalid(`create result: ${form.key}`); }
187
+ formUuid = created.formUuid;
188
+ }
189
+ state.results[form.key].formUuid = formUuid;
190
+ save(state);
191
+ const schema = await call(['get-schema', options.appType, formUuid, '--field-map-json']);
192
+ if (schema.formUuid !== formUuid || !Array.isArray(schema.fields)) { invalid(`schema: ${form.key}`); }
193
+ return { formUuid, fields: schema.fields };
194
+ }, () => save(state));
195
+ const output = { success: Object.values(state.results).every(item => item.status === 'success'), groups, stateFile, results: state.results };
196
+ console.log(JSON.stringify(output));
197
+ if (!output.success) { process.exitCode = 1; }
198
+ return output;
199
+ } finally {
200
+ fs.closeSync(fd); fs.unlinkSync(lock);
201
+ }
202
+ }
203
+
204
+ module.exports = { run, parseArgs, loadPlan, mapReferences, schedule, parseOutput };
@@ -6079,6 +6079,7 @@ async function mainUpdate(parsedArgs, authRef) {
6079
6079
  // ── 主入口 ────────────────────────────────────────────
6080
6080
 
6081
6081
  async function run(args) {
6082
+ if (args?.[0] === 'batch') { return require('./create-form/batch').run(args.slice(1)); }
6082
6083
  const parsedArgs = parseArgs(args);
6083
6084
  if (parsedArgs.help) {
6084
6085
  return parsedArgs;
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ const Babel = require('@babel/standalone');
4
+ const { CliError } = require('../core/cli-error');
5
+ const { t } = require('../core/i18n');
6
+
7
+ // 检查 CSS 的结构闭合,避免一个未闭合 var() 吞掉后续规则。
8
+ // 不执行页面代码,也不把动态插值当成完整 CSS 进行猜测性校验。
9
+ function findCssStructureIssue(css) {
10
+ const stack = [];
11
+ const pairs = { '(': ')', '[': ']', '{': '}' };
12
+ for (let index = 0; index < css.length; index++) {
13
+ const char = css[index];
14
+ if (char === '\\') { index++; continue; }
15
+ if (char === '/' && css[index + 1] === '*') {
16
+ const end = css.indexOf('*/', index + 2);
17
+ if (end < 0) { return { index, type: 'unclosed_comment' }; }
18
+ index = end + 1;
19
+ } else if (char === '"' || char === "'") {
20
+ const start = index;
21
+ for (index++; index < css.length; index++) {
22
+ if (css[index] === '\\') { index++; continue; }
23
+ if (css[index] === char) { break; }
24
+ }
25
+ if (index >= css.length) { return { index: start, type: 'unclosed_string' }; }
26
+ } else if (pairs[char]) {
27
+ stack.push({ char, index });
28
+ } else if (')]}'.includes(char)) {
29
+ const open = stack.pop();
30
+ if (!open || pairs[open.char] !== char) {
31
+ return { index: open ? open.index : index, type: 'mismatched_delimiter', expected: open && pairs[open.char], actual: char };
32
+ }
33
+ }
34
+ }
35
+ const open = stack.pop();
36
+ return open ? { index: open.index, type: 'unclosed_delimiter', expected: pairs[open.char] } : null;
37
+ }
38
+
39
+ function resolveStaticCss(pathRef, seen = new Set()) {
40
+ if (pathRef.isStringLiteral()) { return { text: pathRef.node.value, node: pathRef.node }; }
41
+ if (pathRef.isTemplateLiteral() && pathRef.node.expressions.length === 0) {
42
+ return { text: pathRef.node.quasis[0].value.cooked, node: pathRef.node };
43
+ }
44
+ if (pathRef.isIdentifier()) {
45
+ const binding = pathRef.scope.getBinding(pathRef.node.name);
46
+ if (binding && binding.constant && binding.path.isVariableDeclarator() && !seen.has(binding)) {
47
+ seen.add(binding);
48
+ return resolveStaticCss(binding.path.get('init'), seen);
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ function assertInlineCssStructure(source, options = {}) {
55
+ let ast;
56
+ try {
57
+ ast = Babel.packages.parser.parse(source, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
58
+ } catch { return; } // JSX/TS 解析错误由主编译器报告。
59
+ const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
60
+ traverse(ast, {
61
+ JSXElement(pathRef) {
62
+ const name = pathRef.node.openingElement.name;
63
+ if (name.type !== 'JSXIdentifier' || name.name !== 'style') { return; }
64
+ for (const child of pathRef.get('children')) {
65
+ const resolved = child.isJSXExpressionContainer() ? resolveStaticCss(child.get('expression')) : null;
66
+ if (!resolved || typeof resolved.text !== 'string') { continue; }
67
+ const issue = findCssStructureIssue(resolved.text);
68
+ if (!issue) { continue; }
69
+ const line = resolved.node.loc.start.line + resolved.text.slice(0, issue.index).split('\n').length - 1;
70
+ throw new CliError(t('publish.canvas_inline_css_invalid', line), {
71
+ code: 'OPENYIDA_CANVAS_INLINE_CSS_INVALID',
72
+ details: { stage: 'canvas_compile', sourcePath: options.sourcePath || '', line, ...issue },
73
+ });
74
+ }
75
+ },
76
+ });
77
+ }
78
+
79
+ module.exports = { assertInlineCssStructure, findCssStructureIssue };