openyida 2026.9.17 → 2026.9.18-1

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 (164) hide show
  1. package/README.md +7 -6
  2. package/bin/yida.js +7 -3
  3. package/lib/app/canvas-compile.js +12 -12
  4. package/lib/app/create-form.js +8 -16
  5. package/lib/app/create-page.js +84 -17
  6. package/lib/app/custom-theme.js +11 -0
  7. package/lib/app/form-field-validator.js +26 -5
  8. package/lib/app/form-open-guard.js +97 -10
  9. package/lib/app/get-form-config.js +1 -0
  10. package/lib/app/inline-css-guard.js +1 -31
  11. package/lib/app/theme-from-design.js +110 -39
  12. package/lib/asset/ai-image.js +2 -0
  13. package/lib/asset/asset-cmd.js +4 -1
  14. package/lib/asset/asset-plan.js +36 -5
  15. package/lib/asset/asset-resolve.js +4 -0
  16. package/lib/core/agent-capabilities.js +2 -0
  17. package/lib/core/command-manifest.js +121 -15
  18. package/lib/core/css-structure.js +37 -0
  19. package/lib/core/locales/en.js +31 -10
  20. package/lib/core/locales/zh.js +31 -10
  21. package/lib/core/sample.js +10 -2
  22. package/lib/design/check-design.js +51 -0
  23. package/lib/design/document.js +457 -0
  24. package/lib/design/icon-contrast.js +85 -0
  25. package/lib/design/page-content.js +11 -0
  26. package/lib/design-plan/artifact-updates.js +117 -0
  27. package/lib/design-plan/entry-navigation.js +2 -2
  28. package/lib/design-plan/init.js +27 -17
  29. package/lib/design-plan/materialize.js +291 -274
  30. package/lib/design-plan/normalize.js +10 -16
  31. package/lib/design-plan/patch.js +8 -3
  32. package/lib/design-plan/preview.js +3 -6
  33. package/lib/design-plan/themes.js +86 -0
  34. package/lib/design-plan/validate.js +26 -6
  35. package/lib/design-plan/visual-policy.js +1 -1
  36. package/lib/process/create-process.js +84 -50
  37. package/package.json +2 -1
  38. package/yida-skills/SKILL.md +4 -3
  39. package/yida-skills/skills/yida-app/SKILL.md +4 -4
  40. package/yida-skills/skills/yida-app/references/entry-navigation.md +25 -5
  41. package/yida-skills/skills/yida-app/workflow/parallel-work.md +4 -4
  42. package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +8 -2
  43. package/yida-skills/skills/yida-app/workflow/plan/workflow.md +5 -3
  44. package/yida-skills/skills/yida-app/workflow/step-2-design.md +6 -4
  45. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +2 -2
  46. package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +1 -1
  47. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +5 -5
  48. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +12 -9
  49. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +6 -4
  50. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +5 -5
  51. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +43 -35
  52. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-theme-provider.md +1 -0
  53. package/yida-skills/skills/yida-canvas-custom-page/references/component-library-guide.md +2 -0
  54. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +2 -2
  55. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +21 -21
  56. package/yida-skills/skills/yida-canvas-custom-page/scripts/canvas-theme-provider.template.jsx +7 -0
  57. package/yida-skills/skills/yida-create-form-page/SKILL.md +4 -4
  58. package/yida-skills/skills/yida-create-form-page/references/field-definition-guide.md +3 -3
  59. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +50 -9
  60. package/yida-skills/skills/yida-create-page/SKILL.md +14 -6
  61. package/yida-skills/skills/yida-create-process/SKILL.md +13 -2
  62. package/yida-skills/skills/yida-dashboard/SKILL.md +5 -4
  63. package/yida-skills/skills/yida-data-management/SKILL.md +4 -2
  64. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +4 -4
  65. package/yida-skills/skills/yida-design/SKILL.md +17 -10
  66. package/yida-skills/skills/yida-design/references/application-theme-consistency.md +109 -0
  67. package/yida-skills/skills/yida-design/references/design-mode.md +1 -1
  68. package/yida-skills/skills/yida-design/references/navigation-decision.md +2 -1
  69. package/yida-skills/skills/yida-design/references/page-continuity.md +1 -3
  70. package/yida-skills/skills/yida-design/references/page-quality-gates.md +26 -38
  71. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +243 -434
  72. package/yida-skills/skills/yida-design/references/theme-selection.md +66 -0
  73. package/yida-skills/skills/yida-design/references/visual-decision-engine.md +19 -120
  74. package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +15 -27
  75. package/yida-skills/skills/yida-design/scripts/validate_design_themes.py +259 -0
  76. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +4 -0
  77. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +11 -7
  78. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/README.md +14 -10
  79. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +9 -4
  80. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +13 -14
  81. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +6 -5
  82. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +32 -119
  83. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +12 -29
  84. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +15 -48
  85. package/yida-skills/skills/yida-design/templates/design-themes/README.md +68 -0
  86. package/yida-skills/skills/yida-design/{sub_skill/yida-design-plan/templates → templates}/design-themes/basic-tokens.json +21 -5
  87. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline.md +333 -0
  88. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines.md +324 -0
  89. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels.md +324 -0
  90. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid.md +321 -0
  91. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands.md +308 -0
  92. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks.md +357 -0
  93. package/yida-skills/skills/yida-design/templates/design-themes/index.json +96 -0
  94. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels.md +325 -0
  95. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm.md +350 -0
  96. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone.md +335 -0
  97. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces.md +320 -0
  98. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm.md +417 -0
  99. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band.md +340 -0
  100. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels.md +337 -0
  101. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels.md +354 -0
  102. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels.md +331 -0
  103. package/yida-skills/skills/yida-design/workflow/output-design.md +115 -378
  104. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +21 -26
  105. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +2 -0
  106. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +22 -106
  107. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +15 -22
  108. package/yida-skills/skills/yida-image-assets/SKILL.md +1 -1
  109. package/yida-skills/skills/yida-image-assets/references/manifest-contract.md +31 -0
  110. package/yida-skills/skills/yida-image-assets/references/source-policy.md +1 -1
  111. package/yida-skills/skills/yida-nav-shell/SKILL.md +2 -2
  112. package/yida-skills/skills/yida-page-config/SKILL.md +1 -1
  113. package/yida-skills/skills/yida-prd/SKILL.md +2 -2
  114. package/yida-skills/skills/yida-prd/references/app/blueprint.md +3 -3
  115. package/yida-skills/skills/yida-prd/references/app/role-journey.md +5 -3
  116. package/yida-skills/skills/yida-prd/workflow/output-prd.md +2 -1
  117. package/yida-skills/skills/yida-prd/workflow/plan-business.md +2 -2
  118. package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +3 -2
  119. package/yida-skills/skills/yida-publish-page/SKILL.md +5 -1
  120. package/yida-skills/skills/yida-requirement-analysis/references/experience-groups.md +20 -0
  121. package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +1 -1
  122. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +2 -2
  123. package/yida-skills/skills-index.json +30 -10
  124. package/yida-skills/skills/yida-design/references/style-design-selection.md +0 -149
  125. package/yida-skills/skills/yida-design/references/style-designs/_design-md-template.md +0 -185
  126. package/yida-skills/skills/yida-design/references/style-designs/aqua-service-progress-dashboard.md +0 -242
  127. package/yida-skills/skills/yida-design/references/style-designs/blue-insight-operations-dashboard.md +0 -243
  128. package/yida-skills/skills/yida-design/references/style-designs/blue-productivity-insight-workbench.md +0 -242
  129. package/yida-skills/skills/yida-design/references/style-designs/command-filter-card-console.md +0 -304
  130. package/yida-skills/skills/yida-design/references/style-designs/contrast-command-analytics-workbench.md +0 -261
  131. package/yida-skills/skills/yida-design/references/style-designs/dark-stage-analytic-dashboard.md +0 -369
  132. package/yida-skills/skills/yida-design/references/style-designs/filterable-card-catalog.md +0 -354
  133. package/yida-skills/skills/yida-design/references/style-designs/green-timeline-progress-workbench.md +0 -250
  134. package/yida-skills/skills/yida-design/references/style-designs/registry.md +0 -117
  135. package/yida-skills/skills/yida-design/references/style-designs/soft-analytic-workbench.md +0 -348
  136. package/yida-skills/skills/yida-design/references/style-designs/soft-blue-grid-analytic-dashboard.md +0 -658
  137. package/yida-skills/skills/yida-design/references/style-designs/soft-bordered-analytic-workbench.md +0 -385
  138. package/yida-skills/skills/yida-design/references/style-designs/soft-curated-filter-gallery.md +0 -300
  139. package/yida-skills/skills/yida-design/references/style-designs/soft-modular-analytic-workbench.md +0 -404
  140. package/yida-skills/skills/yida-design/references/style-designs/soft-progress-analytics-workbench.md +0 -382
  141. package/yida-skills/skills/yida-design/references/style-designs/soft-timeline-analytics-workbench.md +0 -367
  142. package/yida-skills/skills/yida-design/references/style-designs/teal-rail-analytics-workbench.md +0 -341
  143. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +0 -219
  144. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +0 -110
  145. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +0 -338
  146. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +0 -330
  147. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +0 -320
  148. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +0 -341
  149. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +0 -327
  150. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +0 -369
  151. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +0 -345
  152. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +0 -378
  153. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +0 -339
  154. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/index.json +0 -329
  155. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +0 -343
  156. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +0 -337
  157. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +0 -381
  158. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +0 -340
  159. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +0 -341
  160. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +0 -374
  161. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +0 -378
  162. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +0 -380
  163. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +0 -339
  164. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +0 -358
package/README.md CHANGED
@@ -399,8 +399,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
399
399
  | `openyida design-plan catalog [--json]` | List available planning themes and page patterns |
400
400
  | `openyida design-plan init <requirement-brief.json> [--theme-id <id>] [--output-dir <dir>] [--json]` | Initialize a plan draft from confirmed requirements |
401
401
  | `openyida design-plan preview <build-plan.json> --part-file <module.json> [--json]` | Update plan drafts by module |
402
- | `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 |
403
- | `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch a design plan; asset progress preserves existing approval |
402
+ | `openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--check] [--json]` | Generate or update plan documents and theme with consistency checks |
403
+ | `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch plan fields and optionally sync documents and theme |
404
404
  | `openyida update-app <appType> [--name "..."] [--desc "..."] [--icon <name>] [--icon-color <color>] [--colour <key>] [--theme-color <color>] [--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 |
405
405
  | `openyida app-online <appType> [--to-ding-app-center] [--show-app-center]` | Enable a Yida app |
406
406
  | `openyida app-offline <appType> [--to-ding-app-center] [--show-app-center]` | Disable a Yida app |
@@ -430,12 +430,13 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
430
430
  | `openyida aggregate-table <list\|create-empty\|inspect\|preview\|save\|publish\|status> <appType> ...` | Manage aggregate tables (virtualView) |
431
431
  | `openyida get-schema <appType> <formUuid\|--all> [--summary-json\|--field-map-json\|--analysis-json]` | Get one form Schema or all form Schemas |
432
432
  | `openyida check-prd-completeness <prd.md> --app-type <appType> [--build-manifest <file>] [--json]` | Check PRD page/resource count risk |
433
+ | `openyida check-design <design.md> [--prd <prd.md>] [--base-dir <dir>] [--json]` | Validate design documents, theme tokens and PRD handoff |
433
434
  | `openyida er <appType> [--format mermaid\|json] [--output file] [--include-system] [--include-pages]` | Export app entity relationship diagram |
434
435
  | `openyida create-page <appType> "<name>" [--mode dashboard] [--hide-nav] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a custom display page |
435
436
  | `openyida build-page <sourceFile> [--output file\|--write]` | Build Yida-compatible page source |
436
- | `openyida check-page <src> [--compat]` | Check custom page standards |
437
- | `openyida compile <src> [--canvas] [--json]` | Compile custom page locally |
438
- | `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--auto-nav-order] [--open\|--no-open]` | Compile and publish custom page |
437
+ | `openyida check-page <src> [--compat] [--json]` | Check custom page standards |
438
+ | `openyida compile <src> [--canvas] [--compat] [--skip-lint] [--json]` | Compile custom page locally |
439
+ | `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--compat] [--skip-lint] [--auto-nav-order] [--open\|--no-open] [--json]` | Compile and publish custom page |
439
440
  | `openyida update-form-config <appType> <formUuid> <true\|false\|keep> "<title>" [--locale zh_CN\|en_US\|ja_JP]` | Update form configuration |
440
441
  | `openyida get-form-config <appType> <formUuid> [--json]` | Query form configuration |
441
442
 
@@ -458,7 +459,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
458
459
  | Command | Description |
459
460
  |---------|-------------|
460
461
  | `openyida configure-process <appType> <formUuid> <definition> [processCode] [--replace]` | Configure and publish process rules; Supports append/forward via JSON nodes[].actions.normalActions/appendActions |
461
- | `openyida create-process <appType> ... [--replace]` | Create process form (all-in-one); Supports append/forward via JSON nodes[].actions.normalActions/appendActions |
462
+ | `openyida create-process <appType> <formTitle> <fieldsJsonFile> <processDefinitionFile> [--replace] \| create-process <appType> --formUuid <formUuid> <processDefinitionFile> [--replace]` | Create process form (all-in-one); Supports append/forward via JSON nodes[].actions.normalActions/appendActions |
462
463
  | `openyida ai-form-setting <get\|fields\|models\|enable\|disable\|save> <appType> ...` | Manage process form AI approval prompts |
463
464
  | `openyida process preview <appType> ...` | Preview process instance (visual flowchart) |
464
465
 
package/bin/yida.js CHANGED
@@ -526,6 +526,7 @@ function printManifestCommandHelp(commandName) {
526
526
  printCommandUsage(...entries.flatMap((entry) => [
527
527
  entry.usage,
528
528
  entry.description,
529
+ ...(entry.notes || []),
529
530
  ...(entry.examples || []),
530
531
  ]));
531
532
  return true;
@@ -798,6 +799,12 @@ async function main() {
798
799
  break;
799
800
  }
800
801
 
802
+ case 'check-design': {
803
+ const { run } = require('../lib/design/check-design');
804
+ await run(args);
805
+ break;
806
+ }
807
+
801
808
  case 'er': {
802
809
  const { run } = require('../lib/app/er');
803
810
  await run(args);
@@ -1009,9 +1016,6 @@ async function main() {
1009
1016
  }
1010
1017
 
1011
1018
  case 'create-process': {
1012
- if (args.length < 2) {
1013
- throwCliUsage(t('cli.create_process_usage'), t('cli.create_process_example'));
1014
- }
1015
1019
  const { run: runCreateProcess } = require('../lib/process/create-process');
1016
1020
  await runCreateProcess(args);
1017
1021
  break;
@@ -940,29 +940,29 @@ function compileCanvasLocal(source, options = {}) {
940
940
  },
941
941
  });
942
942
  }
943
- const directFormOpenIssues = findDirectFormOpenIssues(source);
944
- if (directFormOpenIssues.length > 0) {
945
- const firstIssue = directFormOpenIssues[0];
946
- throw new CliError(formatDirectFormOpenMessage(), {
947
- code: 'OPENYIDA_CANVAS_FORM_OPEN_CONTAINER_REQUIRED',
943
+ const formDetailLinkIssues = findFormDetailLinkIssues(source);
944
+ if (formDetailLinkIssues.length > 0) {
945
+ const firstIssue = formDetailLinkIssues[0];
946
+ throw new CliError(formatFormDetailLinkMessage(firstIssue), {
947
+ code: 'OPENYIDA_CANVAS_FORM_DETAIL_LINK_INVALID',
948
948
  details: {
949
949
  stage: 'canvas_compile',
950
950
  sourcePath: options.sourcePath || '',
951
951
  line: firstIssue.line,
952
- callee: firstIssue.callee,
952
+ issueType: firstIssue.type,
953
953
  },
954
954
  });
955
955
  }
956
- const formDetailLinkIssues = findFormDetailLinkIssues(source);
957
- if (formDetailLinkIssues.length > 0) {
958
- const firstIssue = formDetailLinkIssues[0];
959
- throw new CliError(formatFormDetailLinkMessage(firstIssue), {
960
- code: 'OPENYIDA_CANVAS_FORM_DETAIL_LINK_INVALID',
956
+ const directFormOpenIssues = findDirectFormOpenIssues(source);
957
+ if (directFormOpenIssues.length > 0) {
958
+ const firstIssue = directFormOpenIssues[0];
959
+ throw new CliError(formatDirectFormOpenMessage(), {
960
+ code: 'OPENYIDA_CANVAS_FORM_OPEN_CONTAINER_REQUIRED',
961
961
  details: {
962
962
  stage: 'canvas_compile',
963
963
  sourcePath: options.sourcePath || '',
964
964
  line: firstIssue.line,
965
- issueType: firstIssue.type,
965
+ callee: firstIssue.callee,
966
966
  },
967
967
  });
968
968
  }
@@ -101,6 +101,8 @@ const { SMART_VALIDATION_BLOCK_END, SMART_VALIDATION_BLOCK_START } = require('./
101
101
  const { createFieldNormalizers } = require('./create-form/field-normalizers');
102
102
  const {
103
103
  validateFormFieldDefinitions: collectFormFieldValidationDiagnostics,
104
+ DIVIDER_TYPES,
105
+ DEFAULT_DIVIDER_TYPE,
104
106
  } = require('./form-field-validator');
105
107
 
106
108
  function parseOpenOption(inputArgs) {
@@ -293,21 +295,6 @@ const FORM_PRESENTATION_TYPE_ALIAS = {
293
295
  };
294
296
 
295
297
  const FORM_PRESENTATION_COMPONENTS = ['Divider', 'ColumnsLayout', 'Column', 'PageSection'];
296
- const DIVIDER_TYPE_PRIORITY = [
297
- 'bold-with-thin',
298
- 'double-color-trapezoid',
299
- 'left-dot-title',
300
- 'solid',
301
- 'dashed',
302
- 'thick',
303
- 'dotted',
304
- ];
305
- const DEFAULT_DIVIDER_TYPE = DIVIDER_TYPE_PRIORITY[0];
306
- const LEGACY_STRONG_DIVIDER_TYPE = 'multi-parallelograms-end';
307
- const ALLOWED_DIVIDER_TYPES = new Set([
308
- ...DIVIDER_TYPE_PRIORITY,
309
- LEGACY_STRONG_DIVIDER_TYPE,
310
- ]);
311
298
 
312
299
  const COMPONENT_ALIAS_META = Symbol('openyida.componentAlias');
313
300
 
@@ -574,7 +561,12 @@ function normalizeDividerType(type) {
574
561
  return DEFAULT_DIVIDER_TYPE;
575
562
  }
576
563
  const normalized = String(type).trim();
577
- return ALLOWED_DIVIDER_TYPES.has(normalized) ? normalized : DEFAULT_DIVIDER_TYPE;
564
+ if (!DIVIDER_TYPES.includes(normalized)) {
565
+ throwCreateFormError(t('create_form.divider_type_invalid', normalized, DIVIDER_TYPES.join(', ')), 'CREATE_FORM_INVALID_DIVIDER_TYPE', {
566
+ actual: type, expected: DIVIDER_TYPES,
567
+ });
568
+ }
569
+ return normalized;
578
570
  }
579
571
 
580
572
  function buildDividerComponent(field) {
@@ -19,6 +19,7 @@ const { buildYidaI18n, buildYidaTitleI18n, normalizeYidaLocale, resolveContentLo
19
19
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
20
20
  const { throwCommandError } = require('../core/command-errors');
21
21
  const { buildApiPath } = require('./create-form/api-path');
22
+ const { fetchFormSchemaInfo } = require('./get-form-config');
22
23
 
23
24
  function parseArgs(args) {
24
25
  const openOption = parseOpenOption(args);
@@ -91,13 +92,13 @@ function parseBooleanOption(value, optionName) {
91
92
  throw new Error(`Invalid boolean value for ${optionName}: ${value}`);
92
93
  }
93
94
 
94
- function buildPageInfoPostData(formUuid, pageName, isRenderNav, csrfToken) {
95
+ function buildPageInfoPostData(formUuid, pageName, isRenderNav, csrfToken, currentConfig) {
95
96
  const titleJson = JSON.stringify(buildYidaTitleI18n(pageName, {
96
97
  en_US: pageName,
97
98
  ja_JP: pageName,
98
99
  }));
99
100
 
100
- return querystring.stringify({
101
+ const payload = {
101
102
  _api: 'Form.updateFormSchemaInfo',
102
103
  _csrf_token: csrfToken,
103
104
  _locale_time_zone_offset: '28800000',
@@ -125,17 +126,64 @@ function buildPageInfoPostData(formUuid, pageName, isRenderNav, csrfToken) {
125
126
  displayType: 'RE',
126
127
  isRenderNav: isRenderNav ? 'true' : 'false',
127
128
  manageCustomActionInfo: '[]',
128
- });
129
+ };
130
+ // 与 update-form-config 一样,保留当前配置,只覆盖标题和导航。
131
+ for (const key of Object.keys(payload)) {
132
+ if (key.startsWith('_') || ['formUuid', 'title', 'isRenderNav'].includes(key)) {continue;}
133
+ const value = currentConfig?.[key];
134
+ if (value !== undefined && value !== null) {
135
+ payload[key] = typeof value === 'object' ? JSON.stringify(value) : value;
136
+ }
137
+ }
138
+ return querystring.stringify(payload);
139
+ }
140
+
141
+ function readRenderNav(config) {
142
+ const value = config && Object.prototype.hasOwnProperty.call(config, 'renderNav')
143
+ ? config.renderNav : config?.isRenderNav;
144
+ if (value === false || value === 'false') {return false;}
145
+ if (value === true || value === 'true') {return true;}
146
+ return null;
129
147
  }
130
148
 
131
149
  async function configureNavigationVisibility(authRef, appType, pageId, pageName, isRenderNav) {
132
- return requestWithAutoLogin((auth) => {
133
- return httpPost(
134
- auth.baseUrl,
135
- `/dingtalk/web/${appType}/query/formdesign/updateFormSchemaInfo.json`,
136
- buildPageInfoPostData(pageId, pageName, isRenderNav, auth.csrfToken)
137
- );
138
- }, authRef);
150
+ const verification = { requestedRenderNav: isRenderNav, renderNav: null, verified: false, attempts: 0 };
151
+ let currentConfig = null;
152
+ const readConfig = async () => {
153
+ const response = await fetchFormSchemaInfo(authRef, appType, pageId);
154
+ if (!response || response.success === false || response.__needLogin || response.__csrfExpired) {
155
+ throw new Error(response?.errorMsg || t('common.request_failed'));
156
+ }
157
+ currentConfig = response.content || response.result || response.data || {};
158
+ verification.renderNav = readRenderNav(currentConfig);
159
+ };
160
+ try {
161
+ await readConfig();
162
+ for (let attempt = 0; attempt < 2; attempt++) {
163
+ verification.attempts++;
164
+ // 写入后旧读值不再证明当前状态;回读失败必须报告未知。
165
+ verification.renderNav = null;
166
+ const response = await requestWithAutoLogin((auth) => httpPost(
167
+ auth.baseUrl,
168
+ buildApiPath(appType, 'updateFormSchemaInfo'),
169
+ buildPageInfoPostData(pageId, pageName, isRenderNav, auth.csrfToken, currentConfig)
170
+ ), authRef);
171
+ await readConfig();
172
+ if (verification.renderNav === isRenderNav) {
173
+ verification.verified = true;
174
+ return verification;
175
+ }
176
+ if (!response?.success || response.__needLogin || response.__csrfExpired) {
177
+ verification.error = response?.errorMsg || t('common.request_failed');
178
+ return verification;
179
+ }
180
+ // 只补写已创建页面的配置,不重试创建;未知状态也不能当作隐藏成功。
181
+ }
182
+ verification.error = t('create_page.navigation_unverified');
183
+ } catch (err) {
184
+ verification.error = err.message;
185
+ }
186
+ return verification;
139
187
  }
140
188
 
141
189
  async function run(args) {
@@ -213,30 +261,49 @@ async function run(args) {
213
261
  const workbenchUrl = `${authRef.baseUrl}/${appType}/workbench/${pageId}`;
214
262
  let pageUrl = workbenchUrl;
215
263
  let dashboardConfigWarning = null;
264
+ let navigationVerification = null;
216
265
 
217
266
  if (options.hideNav) {
218
267
  step(3, t('create_page.step_dashboard_config'));
219
- const configResponse = await configureNavigationVisibility(authRef, appType, pageId, pageName, false);
220
- if (configResponse && configResponse.success) {
268
+ navigationVerification = await configureNavigationVisibility(authRef, appType, pageId, pageName, false);
269
+ if (navigationVerification.verified) {
221
270
  chalkSuccess(t('create_page.dashboard_config_ok'));
222
271
  pageUrl = `${authRef.baseUrl}/${appType}/custom/${pageId}?isRenderNav=false`;
223
272
  } else {
224
- dashboardConfigWarning = configResponse ? configResponse.errorMsg || t('common.unknown_error') : t('common.request_failed');
273
+ dashboardConfigWarning = navigationVerification.error;
225
274
  warn(t('create_page.dashboard_config_failed', dashboardConfigWarning));
226
275
  }
227
276
  }
228
277
 
229
- chalkResult(true, t('create_page.success'), [
278
+ const complete = !options.hideNav || navigationVerification.verified;
279
+ const result = {
280
+ success: complete, pageCreated: true, pageId, pageName, appType, mode,
281
+ hideNavRequested: options.hideNav,
282
+ hideNav: options.hideNav ? (navigationVerification.renderNav === null ? null : !navigationVerification.renderNav) : false,
283
+ chromeless: options.hideNav && navigationVerification.verified,
284
+ navigationVerification, url: pageUrl, workbenchUrl, dashboardConfigWarning,
285
+ };
286
+ if (!complete) {
287
+ result.errorCode = 'CREATE_PAGE_NAVIGATION_NOT_VERIFIED';
288
+ result.recovery = {
289
+ command: 'update-form-config', args: [appType, pageId, 'false', pageName],
290
+ verifyCommand: 'get-form-config', verifyArgs: [appType, pageId, '--json'],
291
+ };
292
+ }
293
+ chalkResult(complete, complete ? t('create_page.success') : t('create_page.navigation_unverified'), [
230
294
  ['Page ID', pageId],
231
295
  ['URL', `${c.cyan}${pageUrl}${c.reset}`],
232
296
  ]);
233
297
 
234
- console.log(JSON.stringify(withBrowserHandoff(
235
- { success: true, pageId, pageName, appType, mode, hideNav: options.hideNav, chromeless: options.hideNav && !dashboardConfigWarning, url: pageUrl, workbenchUrl, dashboardConfigWarning },
298
+ console.log(JSON.stringify(complete ? withBrowserHandoff(
299
+ result,
236
300
  pageUrl,
237
301
  { stage: 'create_page_success', title: pageName },
238
302
  options.openMode
239
- )));
303
+ ) : result));
304
+ if (!complete) {
305
+ throwCommandError(t('create_page.navigation_unverified'), { code: result.errorCode, details: result });
306
+ }
240
307
  } else {
241
308
  const errorMsg = response ? response.errorMsg || response.error || t('common.unknown_error') : t('common.request_failed');
242
309
  const errorCode = response && response.errorCode ? response.errorCode : 'CREATE_PAGE_FAILED';
@@ -3,6 +3,9 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { getAccessToken } = require('../auth/token-auth');
6
+ const { CliError } = require('../core/cli-error');
7
+ const { t } = require('../core/i18n');
8
+ const { findCssStructureIssue } = require('../core/css-structure');
6
9
 
7
10
  const MAX_THEME_FILE_BYTES = 20 * 1024 * 1024;
8
11
  const REQUIRED_BRAND_SCALE_TOKENS = Object.freeze([
@@ -147,6 +150,14 @@ function extractThemeColor(cssText) {
147
150
 
148
151
  function validateThemeCssContent(cssText) {
149
152
  const source = String(cssText || '');
153
+ // Check the original text before stripping comments or rewriting token scopes.
154
+ const issue = findCssStructureIssue(source);
155
+ if (issue) {
156
+ const line = source.slice(0, issue.index).split('\n').length;
157
+ throw new CliError(t('design_document.theme_css_invalid', line), {
158
+ code: 'THEME_CSS_STRUCTURE_INVALID', details: { line, ...issue },
159
+ });
160
+ }
150
161
  const sourceWithoutComments = source.replace(/\/\*[\s\S]*?\*\//g, '');
151
162
  for (const rule of UNSAFE_THEME_PATTERNS) {
152
163
  if (rule.pattern.test(sourceWithoutComments)) {
@@ -1,5 +1,19 @@
1
1
  'use strict';
2
2
 
3
+ const { t } = require('../core/i18n');
4
+
5
+ // vc-deep-yida: components/vs-divider-style + components/yida-divider.
6
+ // 这里只定义平台能力,不按业务场景排序;样式由页面设计选择。
7
+ const DIVIDER_TYPES = Object.freeze([
8
+ 'solid', 'dashed', 'thick', 'dotted', 'bold-with-thin',
9
+ 'solid-center', 'solid-left', 'left-dot-title', 'light-left-bar',
10
+ 'light-bar', 'dark-bar', 'light-left-title', 'light-center-title',
11
+ 'light-house-title', 'light-hexagon-title', 'hexagon-title', 'badge-line',
12
+ 'multi-parallelograms-end', 'center-title-with-bar', 'double-color-trapezoid',
13
+ 'arrow-right-title', 'square-blocks-title', 'inner-ellipse-title',
14
+ ]);
15
+ const DEFAULT_DIVIDER_TYPE = 'bold-with-thin';
16
+
3
17
  const FIELD_TYPE_ALIAS = Object.freeze({
4
18
  TextAreaField: 'TextareaField',
5
19
  Textareafield: 'TextareaField',
@@ -232,14 +246,19 @@ function validateDivider(field, path, diagnostics) {
232
246
  'Use a plain string title or omit title.'
233
247
  );
234
248
  }
235
- if (field.dividerType !== undefined && typeof field.dividerType !== 'string') {
249
+ const styleInputs = ['dividerType', 'dividerStyle', 'styleType', 'typeStyle']
250
+ .map(key => [key, field[key]]);
251
+ styleInputs.push(['props.type', field.props?.type]);
252
+ for (const [key, value] of styleInputs) {
253
+ if (value === undefined || value === null || value === '') {continue;}
254
+ if (typeof value === 'string' && DIVIDER_TYPES.includes(value.trim())) {continue;}
236
255
  pushDiagnostic(
237
256
  diagnostics,
238
257
  'INVALID_DIVIDER_TYPE',
239
- path + '.dividerType',
240
- 'string',
241
- describeActual(field.dividerType),
242
- 'Divider.dividerType must be a string when provided.',
258
+ path + '.' + key,
259
+ DIVIDER_TYPES.join(' | '),
260
+ typeof value === 'string' ? value : describeActual(value),
261
+ t('create_form.divider_type_invalid', String(value), DIVIDER_TYPES.join(', ')),
243
262
  'Use a supported divider style string or omit dividerType.'
244
263
  );
245
264
  }
@@ -557,4 +576,6 @@ module.exports = {
557
576
  normalizeDefinitionType,
558
577
  BUSINESS_FIELD_TYPES,
559
578
  PRESENTATION_FIELD_TYPES,
579
+ DIVIDER_TYPES,
580
+ DEFAULT_DIVIDER_TYPE,
560
581
  };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const Babel = require('@babel/standalone');
4
+ const { t } = require('../core/i18n');
4
5
 
5
6
  const parser = Babel.packages.parser;
6
7
  const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
@@ -77,6 +78,13 @@ function isWindowOpenCall(callee) {
77
78
  );
78
79
  }
79
80
 
81
+ function isLocationCall(callee) {
82
+ return callee?.type === 'MemberExpression'
83
+ && ['assign', 'replace'].includes(callee.property?.name)
84
+ && (callee.object?.name === 'location'
85
+ || (callee.object?.object?.name === 'window' && callee.object?.property?.name === 'location'));
86
+ }
87
+
80
88
  function isOpenPageCall(callee) {
81
89
  return !!(
82
90
  callee &&
@@ -114,18 +122,26 @@ function isLocationHrefAssignment(node) {
114
122
  }
115
123
 
116
124
  function isMobileGuarded(pathRef, sourceCode) {
125
+ const mobileCondition = node => {
126
+ if (node?.type === 'UnaryExpression' && node.operator === '!') {return -mobileCondition(node.argument);}
127
+ const text = getNodeText(sourceCode, node);
128
+ if (node?.type === 'Identifier' || node?.type === 'MemberExpression' || node?.type === 'CallExpression') {
129
+ return MOBILE_GUARD_PATTERN.test(text) && !/min-width/.test(text) ? 1 : 0;
130
+ }
131
+ return 0;
132
+ };
117
133
  let cursor = pathRef;
118
134
  while (cursor && cursor.parentPath) {
119
135
  const parent = cursor.parentPath;
120
- if (parent.node && parent.node.type === 'IfStatement' && nodeContains(parent.node.consequent, pathRef.node)) {
121
- if (MOBILE_GUARD_PATTERN.test(getNodeText(sourceCode, parent.node.test))) {
122
- return true;
123
- }
136
+ if (['IfStatement', 'ConditionalExpression'].includes(parent.node?.type)) {
137
+ const direction = mobileCondition(parent.node.test);
138
+ if ((direction === 1 && nodeContains(parent.node.consequent, pathRef.node))
139
+ || (direction === -1 && nodeContains(parent.node.alternate, pathRef.node))) {return true;}
124
140
  }
125
- if (parent.node && parent.node.type === 'ConditionalExpression') {
126
- if (MOBILE_GUARD_PATTERN.test(getNodeText(sourceCode, parent.node.test))) {
127
- return true;
128
- }
141
+ if (parent.node?.type === 'LogicalExpression' && nodeContains(parent.node.right, pathRef.node)) {
142
+ const direction = mobileCondition(parent.node.left);
143
+ if ((parent.node.operator === '&&' && direction === 1)
144
+ || (parent.node.operator === '||' && direction === -1)) {return true;}
129
145
  }
130
146
  cursor = parent;
131
147
  }
@@ -161,9 +177,17 @@ function findDirectFormOpenIssues(sourceCode, options = {}) {
161
177
  }
162
178
 
163
179
  const knownFormUrlNames = new Set();
180
+ const containerNames = new Set(['formOpenContainer']);
164
181
  traverse(ast, {
165
182
  VariableDeclarator(pathRef) {
166
183
  const id = pathRef.node.id;
184
+ if (id?.type === 'ObjectPattern' && pathRef.node.init?.callee?.name === 'useYidaFormOpen') {
185
+ for (const property of id.properties) {
186
+ if (property.key?.name === 'formOpenContainer' && property.value?.type === 'Identifier') {
187
+ containerNames.add(property.value.name);
188
+ }
189
+ }
190
+ }
167
191
  if (!id || id.type !== 'Identifier') {
168
192
  return;
169
193
  }
@@ -174,10 +198,30 @@ function findDirectFormOpenIssues(sourceCode, options = {}) {
174
198
  });
175
199
 
176
200
  const issues = [];
201
+ const templateParts = new Map();
202
+ const templateUses = [];
203
+ let containerRendered = false;
204
+ const functionName = pathRef => {
205
+ const owner = pathRef.getFunctionParent();
206
+ return owner?.node.id?.name || owner?.parentPath?.node.id?.name;
207
+ };
208
+ const addTemplatePart = (owner, part) => {
209
+ if (!templateParts.has(owner)) {templateParts.set(owner, new Set());}
210
+ templateParts.get(owner).add(part);
211
+ };
177
212
  traverse(ast, {
178
213
  CallExpression(pathRef) {
179
214
  const callee = pathRef.node.callee;
180
- if (!isWindowOpenCall(callee) && !isOpenPageCall(callee)) {
215
+ if (callee.type === 'Identifier' && callee.name === 'useYidaFormOpen') {
216
+ templateUses.push(pathRef.node);
217
+ }
218
+ if (callee.type === 'Identifier' && ['openForm', 'setFormRequest'].includes(callee.name)
219
+ && pathRef.node.arguments[0]?.type === 'ObjectExpression'
220
+ && pathRef.node.arguments[0].properties.some(property => (property.key?.name || property.key?.value) === 'type'
221
+ && ['submission', 'detail'].includes(property.value?.value))) {
222
+ templateUses.push(pathRef.node);
223
+ }
224
+ if (!isWindowOpenCall(callee) && !isOpenPageCall(callee) && !isLocationCall(callee)) {
181
225
  return;
182
226
  }
183
227
  const firstArg = pathRef.node.arguments && pathRef.node.arguments[0];
@@ -207,13 +251,56 @@ function findDirectFormOpenIssues(sourceCode, options = {}) {
207
251
  callee: getNodeText(sourceCode, pathRef.node.left),
208
252
  });
209
253
  },
254
+ JSXExpressionContainer(pathRef) {
255
+ if ((pathRef.parentPath.isJSXElement() || pathRef.parentPath.isJSXFragment())
256
+ && containerNames.has(pathRef.node.expression?.name)) {
257
+ containerRendered = true;
258
+ }
259
+ },
260
+ JSXOpeningElement(pathRef) {
261
+ const name = pathRef.node.name?.name;
262
+ const owner = functionName(pathRef);
263
+ addTemplatePart(owner, name);
264
+ if (name === 'FormOpenContainer' && owner !== 'useYidaFormOpen') {
265
+ templateUses.push(pathRef.node);
266
+ containerRendered = true;
267
+ }
268
+ const attribute = key => pathRef.node.attributes.find(item => item.type === 'JSXAttribute' && item.name?.name === key)?.value;
269
+ const value = node => node?.type === 'JSXExpressionContainer' ? node.expression : node;
270
+ if (owner === 'CanvasDrawer' && attribute('className')?.value === 'oy-drawer-resize'
271
+ && attribute('onPointerDown') && attribute('onPointerMove') && attribute('onPointerUp') && attribute('onKeyDown')) {
272
+ addTemplatePart(owner, 'resize-handle');
273
+ }
274
+ if (name === 'a' && expressionLooksLikeFormUrl(value(attribute('href')), sourceCode, knownFormUrlNames)) {
275
+ issues.push({ line: getNodeLine(pathRef.node), callee: 'a[href]' });
276
+ }
277
+ if (!['iframe', 'YidaCanvasIframe'].includes(name) || owner === 'FormOpenContainer') {return;}
278
+ if (!expressionLooksLikeFormUrl(value(attribute('src')), sourceCode, knownFormUrlNames)) {return;}
279
+ // 导航主内容可以嵌原生页;弹层内的表单必须使用提供的容器。
280
+ const overlay = pathRef.findParent(parent => parent.isJSXElement() && (
281
+ ['Drawer', 'Modal', 'CanvasDrawer'].includes(parent.node.openingElement.name?.name)
282
+ || parent.node.openingElement.attributes.some(item => item.type === 'JSXAttribute'
283
+ && ((item.name?.name === 'role' && item.value?.value === 'dialog')
284
+ || (item.name?.name === 'style' && /position\s*:\s*['"]fixed['"]/.test(getNodeText(sourceCode, item.value)))))));
285
+ if (overlay) {issues.push({ line: getNodeLine(pathRef.node), callee: `${name} outside FormOpenContainer` });}
286
+ },
210
287
  });
211
288
 
289
+ if (templateUses.length) {
290
+ const hasParts = (owner, parts) => parts.every(part => templateParts.get(owner)?.has(part));
291
+ const complete = hasParts('FormOpenContainer', ['CanvasDrawer', 'iframe'])
292
+ && hasParts('CanvasDrawer', ['Drawer', 'ExternalLink', 'Maximize2', 'Minimize2', 'X', 'resize-handle'])
293
+ && hasParts('useYidaFormOpen', ['FormOpenContainer']) && containerRendered;
294
+ if (!complete) {
295
+ issues.push({ line: getNodeLine(templateUses[0]), callee: 'incomplete FormOpenContainer template' });
296
+ }
297
+ }
298
+
212
299
  return issues;
213
300
  }
214
301
 
215
302
  function formatDirectFormOpenMessage() {
216
- return '自定义页内打开表单提交/详情只能使用 FormOpenContainer:PC 端用 50vw 抽屉 iframe,移动端才整页或新页打开。按钮事件请调用 openForm({ type: "submission" | "detail", ... })。';
303
+ return t('publish.lint_form_open_container');
217
304
  }
218
305
 
219
306
  module.exports = {
@@ -24,6 +24,7 @@ const { throwCommandError } = require('../core/command-errors');
24
24
 
25
25
  // 常用配置字段白名单(人读摘要),--json 会输出完整对象
26
26
  const SUMMARY_FIELDS = [
27
+ 'renderNav',
27
28
  'isRenderNav',
28
29
  'submissionRule',
29
30
  'defaultOrder',
@@ -4,37 +4,7 @@ const Babel = require('@babel/standalone');
4
4
  const { CliError } = require('../core/cli-error');
5
5
  const { t } = require('../core/i18n');
6
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
- }
7
+ const { findCssStructureIssue } = require('../core/css-structure');
38
8
 
39
9
  function resolveStaticCss(pathRef, seen = new Set()) {
40
10
  if (pathRef.isStringLiteral()) { return { text: pathRef.node.value, node: pathRef.node }; }