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.
- package/README.md +9 -4
- package/bin/yida.js +6 -0
- package/lib/app/canvas-compile.js +2 -0
- package/lib/app/create-app.js +11 -115
- package/lib/app/create-form/batch.js +204 -0
- package/lib/app/create-form.js +1 -0
- package/lib/app/inline-css-guard.js +79 -0
- package/lib/app/page-linter.js +162 -0
- package/lib/app/services/canvas-page-schema-builder.js +18 -5
- package/lib/app/theme-from-design.js +88 -0
- package/lib/app/theme-presets.js +7 -0
- package/lib/app/update-app.js +103 -43
- package/lib/core/agent-capabilities.js +14 -0
- package/lib/core/command-manifest.js +87 -10
- package/lib/core/locales/en.js +19 -6
- package/lib/core/locales/zh.js +19 -6
- package/lib/core/query-data.js +31 -9
- package/lib/core/sample.js +80 -5
- package/lib/design-plan/design-plan.js +92 -0
- package/lib/design-plan/files.js +73 -0
- package/lib/design-plan/init.js +112 -0
- package/lib/design-plan/materialize.js +898 -0
- package/lib/design-plan/normalize.js +275 -0
- package/lib/design-plan/parallel.js +56 -0
- package/lib/design-plan/patch.js +174 -0
- package/lib/design-plan/preview.js +154 -0
- package/lib/design-plan/validate.js +133 -0
- package/lib/samples/openyida-scaffold/canvas-dialog.canvas.jsx +124 -0
- package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +265 -68
- package/lib/samples/openyida-scaffold/canvas-nav/data.jsx +60 -0
- package/lib/samples/openyida-scaffold/canvas-nav/dock.jsx +13 -0
- package/lib/samples/openyida-scaffold/canvas-nav/mixed.jsx +34 -0
- package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +43 -0
- package/lib/samples/openyida-scaffold/canvas-nav/side.jsx +19 -0
- package/lib/samples/openyida-scaffold/canvas-nav/sidebar.jsx +96 -0
- package/lib/samples/openyida-scaffold/canvas-nav/tabs.jsx +31 -0
- package/lib/samples/openyida-scaffold/canvas-nav/top.jsx +31 -0
- package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +2 -1
- package/lib/samples/yida-rechart/trend-combo.canvas.jsx +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +5 -3
- package/yida-skills/SKILL.md +37 -6
- package/yida-skills/references/field-and-url-reference.md +11 -1
- package/yida-skills/skills/yida-app/SKILL.md +42 -7
- package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
- package/yida-skills/skills/yida-app/workflow/incremental-preview.md +42 -0
- package/yida-skills/skills/yida-app/workflow/parallel-work.md +69 -0
- package/yida-skills/skills/yida-app/workflow/plan/step-1-understand.md +5 -0
- package/yida-skills/skills/yida-app/workflow/plan/step-2-confirm.md +7 -0
- package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +69 -0
- package/yida-skills/skills/yida-app/workflow/plan/workflow.md +45 -0
- package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +2 -0
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +24 -26
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +24 -7
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +8 -2
- package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +2 -0
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +12 -4
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +20 -10
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +8 -4
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +9 -6
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +55 -38
- package/yida-skills/skills/yida-canvas-custom-page/references/dialog-guide.md +35 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +45 -93
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +6 -0
- package/yida-skills/skills/yida-canvas-table-form/SKILL.md +2 -2
- package/yida-skills/skills/yida-create-app/SKILL.md +33 -23
- package/yida-skills/skills/yida-create-form-page/SKILL.md +4 -0
- package/yida-skills/skills/yida-create-form-page/references/batch-forms.md +55 -0
- package/yida-skills/skills/yida-create-page/SKILL.md +2 -0
- package/yida-skills/skills/yida-custom-page/references/coding-guide.md +2 -0
- package/yida-skills/skills/yida-design/SKILL.md +31 -36
- package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +187 -0
- package/yida-skills/skills/yida-design/references/design-mode.md +14 -0
- package/yida-skills/skills/yida-design/references/style-design-selection.md +3 -3
- package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +9 -1
- package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +43 -1
- package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +1 -0
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +26 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/agents/openai.yaml +4 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/README.md +214 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +1181 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/business-flows.png +0 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/data-models.png +0 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-normal.svg +4 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-process.svg +6 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-fit.svg +3 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-relations.svg +7 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-in.svg +4 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-out.svg +4 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/overview.png +0 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/pages.png +0 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/visual-style.png +0 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +138 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +456 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/page-patterns.md +204 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +137 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +168 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +1172 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +190 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +98 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +339 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +348 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +344 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +345 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +355 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +377 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +352 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +389 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +364 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/index.json +329 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +348 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +345 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +379 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +347 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +352 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +386 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +384 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +388 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +341 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +361 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/page-patterns/index.json +63 -0
- package/yida-skills/skills/yida-design/workflow/output-design.md +39 -5
- package/yida-skills/skills/yida-design/workflow/output-prd.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +3 -3
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +7 -11
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +6 -0
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +3 -3
- package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +4 -4
- package/yida-skills/skills/yida-nav-group/SKILL.md +1 -0
- package/yida-skills/skills/yida-nav-shell/SKILL.md +21 -18
- package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +212 -266
- package/yida-skills/skills/yida-prd/SKILL.md +21 -32
- package/yida-skills/skills/yida-prd/references/plan-content.md +169 -0
- package/yida-skills/skills/yida-prd/workflow/output-prd.md +36 -8
- package/yida-skills/skills/yida-prd/workflow/plan-business.md +12 -0
- package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +7 -3
- package/yida-skills/skills/yida-publish-page/SKILL.md +1 -1
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +17 -19
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +65 -0
- package/yida-skills/skills-index.json +11 -10
- 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
|
|
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>
|
|
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
|
|
package/lib/app/create-app.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* create-app.js - 宜搭应用创建命令
|
|
3
3
|
*
|
|
4
|
-
* 用法:openyida create-app "<appName>" [description] [icon] [iconColor]
|
|
5
|
-
* openyida create-app --name "<appName>"
|
|
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
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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 >
|
|
286
|
-
throw new Error(
|
|
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
|
-
|
|
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 };
|
package/lib/app/create-form.js
CHANGED
|
@@ -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 };
|