openyida 2026.7.7-1 → 2026.7.8

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 (59) hide show
  1. package/README.md +4 -2
  2. package/bin/yida.js +1 -1
  3. package/lib/app/canvas-compile.js +303 -0
  4. package/lib/app/publish.js +190 -39
  5. package/lib/core/command-manifest.js +1 -1
  6. package/lib/core/locales/ar.js +6 -2
  7. package/lib/core/locales/de.js +6 -2
  8. package/lib/core/locales/en.js +7 -3
  9. package/lib/core/locales/es.js +6 -2
  10. package/lib/core/locales/fr.js +6 -2
  11. package/lib/core/locales/hi.js +6 -2
  12. package/lib/core/locales/ja.js +7 -3
  13. package/lib/core/locales/ko.js +6 -2
  14. package/lib/core/locales/pt.js +6 -2
  15. package/lib/core/locales/vi.js +6 -2
  16. package/lib/core/locales/zh-HK.js +7 -3
  17. package/lib/core/locales/zh.js +7 -3
  18. package/lib/samples/yida-custom-page/custom-page-template.js +6 -6
  19. package/lib/samples/yida-custom-page/design-tokens.js +14 -12
  20. package/package.json +1 -1
  21. package/scripts/e2e-real/skill-coverage.js +4 -0
  22. package/scripts/validate-ci.sh +6 -0
  23. package/scripts/validate-package-size.js +12 -1
  24. package/yida-skills/SKILL.md +19 -5
  25. package/yida-skills/references/development-rules.md +2 -2
  26. package/yida-skills/references/field-and-url-reference.md +14 -1
  27. package/yida-skills/skills/yida-app/SKILL.md +3 -2
  28. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +113 -0
  29. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +163 -0
  30. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-design-system.md +148 -0
  31. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +115 -0
  32. package/yida-skills/skills/yida-canvas-custom-page/references/dependencies-and-cdn.md +45 -0
  33. package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +68 -0
  34. package/yida-skills/skills/yida-canvas-upgrade/SKILL.md +99 -0
  35. package/yida-skills/skills/yida-canvas-upgrade/references/migration-examples.md +69 -0
  36. package/yida-skills/skills/yida-custom-page/SKILL.md +7 -2
  37. package/yida-skills/skills/yida-custom-page/examples/attachment-upload.js +1 -1
  38. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +3 -3
  39. package/yida-skills/skills/yida-custom-page/references/component-jsx-guide.md +3 -2
  40. package/yida-skills/skills/yida-custom-page/references/design-system.md +30 -13
  41. package/yida-skills/skills/yida-nav-shell/SKILL.md +119 -0
  42. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +302 -0
  43. package/yida-skills/skills/yida-page-config/SKILL.md +1 -0
  44. package/yida-skills/skills/yida-page-uiux/SKILL.md +68 -0
  45. package/yida-skills/skills/yida-page-uiux/references/scenes/dashboard.md +128 -0
  46. package/yida-skills/skills/yida-page-uiux/references/scenes/detail.md +79 -0
  47. package/yida-skills/skills/yida-page-uiux/references/scenes/landing.md +101 -0
  48. package/yida-skills/skills/yida-page-uiux/references/scenes/list.md +78 -0
  49. package/yida-skills/skills/yida-page-uiux/references/scenes/workbench.md +74 -0
  50. package/yida-skills/skills/yida-page-uiux/references/visual-decision-engine.md +151 -0
  51. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +36 -0
  52. package/yida-skills/skills/yida-page-uiux/workflow/step-0-nav-shape.md +39 -0
  53. package/yida-skills/skills/yida-page-uiux/workflow/step-1-page-type.md +25 -0
  54. package/yida-skills/skills/yida-page-uiux/workflow/step-2-intent-decode.md +25 -0
  55. package/yida-skills/skills/yida-page-uiux/workflow/step-3-scene-routing.md +29 -0
  56. package/yida-skills/skills/yida-page-uiux/workflow/step-4-visual-decision.md +32 -0
  57. package/yida-skills/skills/yida-page-uiux/workflow/step-5-icon-and-assets.md +36 -0
  58. package/yida-skills/skills/yida-page-uiux/workflow/step-6-deai-selfcheck.md +37 -0
  59. package/yida-skills/skills/yida-publish-page/SKILL.md +7 -3
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  OpenYida connects AI coding agents with Yida's low-code platform, so developers can create apps, forms, workflows, custom pages, reports, integrations, and deployment configuration from a normal chat-driven development workflow.
10
10
 
11
- [Quick Start](#quick-start) · [Capabilities](#capabilities) · [CLI Reference](#cli-reference) · [Examples](#examples) · [Contributing](./CONTRIBUTING.md) · [Changelog](./CHANGELOG.md)
11
+ [Quick Start](#quick-start) · [Capabilities](#capabilities) · [Full Capability List](./docs/capabilities.md) · [CLI Reference](#cli-reference) · [Examples](#examples) · [Contributing](./CONTRIBUTING.md) · [Changelog](./CHANGELOG.md)
12
12
 
13
13
  [![npm version](https://img.shields.io/npm/v/openyida?color=brightgreen&label=npm)](https://www.npmjs.com/package/openyida)
14
14
  [![npm downloads](https://img.shields.io/npm/dm/openyida?color=blue)](https://www.npmjs.com/package/openyida)
@@ -170,6 +170,8 @@ openyida/
170
170
 
171
171
  ## Capabilities
172
172
 
173
+ For a user-facing list of supported features and matching CLI commands, see [OpenYida 功能完整列表](./docs/capabilities.md).
174
+
173
175
  ### Application and Form Management
174
176
 
175
177
  ```bash
@@ -382,7 +384,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
382
384
  | `openyida build-page <sourceFile> [--output file\|--write]` | Build Yida-compatible page source |
383
385
  | `openyida check-page <src> [--compat]` | Check custom page standards |
384
386
  | `openyida compile <src>` | Compile custom page locally |
385
- | `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--open\|--no-open]` | Compile and publish custom page |
387
+ | `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--open\|--no-open]` | Compile and publish custom page |
386
388
  | `openyida update-form-config <appType> ...` | Update form configuration |
387
389
 
388
390
  ### Data & Permissions
package/bin/yida.js CHANGED
@@ -818,7 +818,7 @@ async function main() {
818
818
  }
819
819
 
820
820
  case 'publish': {
821
- const passThroughFlags = new Set(['--skip-lint', '--health-check', '--check', '--open', '--no-open', '--compat', '--modern', '--force']);
821
+ const passThroughFlags = new Set(['--skip-lint', '--health-check', '--check', '--open', '--no-open', '--compat', '--modern', '--force', '--canvas']);
822
822
  const filteredArgs = args.filter(arg => !passThroughFlags.has(arg));
823
823
  if (filteredArgs.length < 3) {
824
824
  throwCliUsage(t('cli.publish_usage'), t('cli.publish_example'));
@@ -0,0 +1,303 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * canvas-compile.js - Code Canvas 页面本地编译器
5
+ *
6
+ * 把原始 React/JSX/TSX 源码在本地(Node)编译成 Code Canvas 运行态代码
7
+ * (runtimeCode)+ 依赖清单(importedModules),无需调用任何线上编译服务,
8
+ * 因此不依赖登录态、不经过风控 WAF。
9
+ *
10
+ * ── 运行时契约(@ali/vc-deep-yida 的 YidaCodeCanvas 物料)──
11
+ * 画布物料在浏览器里这样消费 runtimeCode(factory.tsx):
12
+ *
13
+ * const wrapped = `
14
+ * return function(iframeWindow, parentWindow){
15
+ * const window = iframeWindow;
16
+ * ${runtimeCode}
17
+ * return YidaComp;
18
+ * }`;
19
+ * new Function(wrapped)()(window, window); // 取 Comp.YidaComp || Comp.default
20
+ *
21
+ * 由此推出 runtimeCode 必须满足:
22
+ * 1) 是 `new Function` 能解析的纯 JS —— 不能含 JSX,也不能含 ESM import/export 语法;
23
+ * 2) 执行结束时在作用域内留下一个 `YidaComp` 绑定;
24
+ * 3) 第三方依赖以 `window.<别名>` 形式引用(antd→window.antd、react→window.React …),
25
+ * 这些 UMD 依赖由画布运行时依据 importedModules 白名单按需注入。
26
+ *
27
+ * 因此本地编译 = Babel 把 JSX/TS 转成 ES5 → 把 import 改写成 window 别名引用、
28
+ * 把 export default 改写成 `var YidaComp = ...` → 正则抽出依赖包名。
29
+ */
30
+
31
+ const Babel = require('@babel/standalone');
32
+
33
+ /**
34
+ * 依赖 → window 别名白名单。
35
+ * 逐条镜像自 @ali/vc-deep-yida 的
36
+ * src/components/yida-code-canvas/dependencies.ts → getModuleAliasMap()
37
+ * 只保留运行时真正用到的 windowAlias(资源 URL 由画布运行时按别名注入,
38
+ * 本地编译不关心 CDN 地址)。若此处与物料白名单漂移,编译产物仍可运行,
39
+ * 只是未收录的包会在浏览器端 `${name} is not found in dependencies map` 告警。
40
+ * @type {Record<string, string>}
41
+ */
42
+ const MODULE_ALIAS_MAP = {
43
+ react: 'React',
44
+ 'react-dom': 'ReactDOM',
45
+ antd: 'antd',
46
+ ahooks: 'ahooks',
47
+ d3: 'd3',
48
+ '@ant-design/icons': 'icons',
49
+ dayjs: 'dayjs',
50
+ recharts: 'Recharts',
51
+ 'yida-plugin-markdown': 'YidaMarkdown',
52
+ '@radix-ui/themes': 'Radix',
53
+ 'lucide-react': 'DynamicIcon',
54
+ 'framer-motion': 'FramerMotion',
55
+ };
56
+
57
+ const IMPORT_PATTERN = /import\s+(?:[\w*\s{},]+\s+from\s+)?['"]([^'"]+)['"]/g;
58
+ const IMPORT_SIDE_EFFECT_PATTERN = /import\s+['"]([^'"]+)['"]/g;
59
+ const REQUIRE_PATTERN = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
60
+ const DYNAMIC_IMPORT_PATTERN = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
61
+
62
+ /**
63
+ * 从源码里正则抽取裸包名(过滤相对/绝对路径),去重排序。
64
+ * 对齐 dingtalk-ai-app 的 _extract_imported_modules 行为。
65
+ * @param {string} code
66
+ * @returns {string[]}
67
+ */
68
+ function extractImportedModules(code) {
69
+ const modules = new Set();
70
+ const patterns = [IMPORT_PATTERN, IMPORT_SIDE_EFFECT_PATTERN, REQUIRE_PATTERN, DYNAMIC_IMPORT_PATTERN];
71
+ for (const pattern of patterns) {
72
+ pattern.lastIndex = 0;
73
+ let match;
74
+ while ((match = pattern.exec(code)) !== null) {
75
+ const name = match[1];
76
+ if (name && !name.startsWith('.') && !name.startsWith('/')) {
77
+ modules.add(name);
78
+ }
79
+ }
80
+ }
81
+ return Array.from(modules).sort();
82
+ }
83
+
84
+ /**
85
+ * 把包名(可能是子路径,如 `antd/es/button` 或 `@scope/pkg/sub`)解析到根包,
86
+ * 再查白名单拿 windowAlias。命中返回别名,未命中返回 null。
87
+ * @param {string} pkg
88
+ * @returns {string|null}
89
+ */
90
+ function resolveWindowAlias(pkg) {
91
+ if (MODULE_ALIAS_MAP[pkg]) {
92
+ return MODULE_ALIAS_MAP[pkg];
93
+ }
94
+ const segments = pkg.split('/');
95
+ const base = pkg.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0];
96
+ return MODULE_ALIAS_MAP[base] || null;
97
+ }
98
+
99
+ /**
100
+ * Babel 插件:把 ESM import/export 改写成画布运行时约定。
101
+ * import X from 'react' → var _r = window.React; var X = _r && _r.__esModule ? _r.default : _r;
102
+ * import { Button } from 'antd' → var _a = window.antd; var { Button } = _a;
103
+ * import * as d3 from 'd3' → var d3 = window.d3;
104
+ * import 'some.css' → (删除;副作用依赖由运行时按 importedModules 注入)
105
+ * export default App → var YidaComp = App;
106
+ * export const x = ... → const x = ...(去掉 export 关键字)
107
+ * @param {{ types: import('@babel/types') }} babel
108
+ */
109
+ function esmToWindowPlugin({ types: t }) {
110
+ function moduleExpr(pkg) {
111
+ const alias = resolveWindowAlias(pkg);
112
+ if (alias) {
113
+ return t.memberExpression(t.identifier('window'), t.identifier(alias));
114
+ }
115
+ // 未收录包:退化为 window["pkg"],运行时若未注入会自然报错(与线上一致)。
116
+ return t.memberExpression(t.identifier('window'), t.stringLiteral(pkg), true);
117
+ }
118
+
119
+ return {
120
+ name: 'yida-esm-to-window',
121
+ visitor: {
122
+ ImportDeclaration(path) {
123
+ const pkg = path.node.source.value;
124
+ const specifiers = path.node.specifiers || [];
125
+
126
+ // 相对/绝对路径导入:画布沙箱内无法解析,直接丢弃。
127
+ if (pkg.startsWith('.') || pkg.startsWith('/')) {
128
+ path.remove();
129
+ return;
130
+ }
131
+ // 纯副作用导入(无 specifier):依赖由运行时注入,删除语句本身。
132
+ if (specifiers.length === 0) {
133
+ path.remove();
134
+ return;
135
+ }
136
+
137
+ const decls = [];
138
+ const tmp = path.scope.generateUidIdentifier(resolveWindowAlias(pkg) || 'mod');
139
+ decls.push(t.variableDeclarator(t.cloneNode(tmp), moduleExpr(pkg)));
140
+
141
+ const namedProps = [];
142
+ for (const spec of specifiers) {
143
+ if (t.isImportDefaultSpecifier(spec)) {
144
+ // interop:有 __esModule 取 .default,否则取模块本身。
145
+ const init = t.conditionalExpression(
146
+ t.logicalExpression(
147
+ '&&',
148
+ t.cloneNode(tmp),
149
+ t.memberExpression(t.cloneNode(tmp), t.identifier('__esModule'))
150
+ ),
151
+ t.memberExpression(t.cloneNode(tmp), t.identifier('default')),
152
+ t.cloneNode(tmp)
153
+ );
154
+ decls.push(t.variableDeclarator(t.identifier(spec.local.name), init));
155
+ } else if (t.isImportNamespaceSpecifier(spec)) {
156
+ decls.push(t.variableDeclarator(t.identifier(spec.local.name), t.cloneNode(tmp)));
157
+ } else if (t.isImportSpecifier(spec)) {
158
+ const importedName = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
159
+ const localName = spec.local.name;
160
+ namedProps.push(
161
+ t.objectProperty(
162
+ t.identifier(importedName),
163
+ t.identifier(localName),
164
+ false,
165
+ importedName === localName
166
+ )
167
+ );
168
+ }
169
+ }
170
+ if (namedProps.length) {
171
+ decls.push(t.variableDeclarator(t.objectPattern(namedProps), t.cloneNode(tmp)));
172
+ }
173
+ path.replaceWith(t.variableDeclaration('var', decls));
174
+ },
175
+
176
+ ExportDefaultDeclaration(path) {
177
+ const decl = path.node.declaration;
178
+ if (t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) {
179
+ if (decl.id) {
180
+ // 具名声明:保留声明本体,再补 `var YidaComp = <name>;`
181
+ const idName = decl.id.name;
182
+ path.replaceWithMultiple([
183
+ decl,
184
+ t.variableDeclaration('var', [
185
+ t.variableDeclarator(t.identifier('YidaComp'), t.identifier(idName)),
186
+ ]),
187
+ ]);
188
+ return;
189
+ }
190
+ const expr = t.isFunctionDeclaration(decl)
191
+ ? t.functionExpression(null, decl.params, decl.body, decl.generator, decl.async)
192
+ : t.classExpression(null, decl.superClass, decl.body, decl.decorators || []);
193
+ path.replaceWith(
194
+ t.variableDeclaration('var', [t.variableDeclarator(t.identifier('YidaComp'), expr)])
195
+ );
196
+ return;
197
+ }
198
+ // export default <表达式>
199
+ path.replaceWith(
200
+ t.variableDeclaration('var', [t.variableDeclarator(t.identifier('YidaComp'), decl)])
201
+ );
202
+ },
203
+
204
+ ExportNamedDeclaration(path) {
205
+ if (path.node.declaration) {
206
+ // export const/function/class ... → 去掉 export 关键字保留声明。
207
+ path.replaceWith(path.node.declaration);
208
+ } else {
209
+ // export { a, b } / re-export:画布不需要,删除。
210
+ path.remove();
211
+ }
212
+ },
213
+
214
+ ExportAllDeclaration(path) {
215
+ path.remove();
216
+ },
217
+ },
218
+ };
219
+ }
220
+
221
+ /**
222
+ * 本地编译 Code Canvas 源码。
223
+ * @param {string} source 原始 React/JSX/TSX 源码
224
+ * @returns {{ runtimeCode: string, importedModules: string }}
225
+ */
226
+ function compileCanvasLocal(source) {
227
+ const importedModules = extractImportedModules(source);
228
+
229
+ // 第一步:剥类型 + 转 JSX(classic runtime,产出 React.createElement,
230
+ // 引用外部标识符 React;不注入 jsx-runtime import,避免再引入 ESM)。
231
+ const stage1 = Babel.transform(source, {
232
+ filename: 'canvas.tsx',
233
+ presets: [
234
+ ['typescript', { allExtensions: true, isTSX: true, allowDeclareFields: true }],
235
+ ['react', { runtime: 'classic' }],
236
+ ],
237
+ sourceType: 'module',
238
+ compact: false,
239
+ babelrc: false,
240
+ configFile: false,
241
+ });
242
+
243
+ let intermediate = stage1.code || '';
244
+
245
+ // classic JSX 需要作用域内存在 React;若源码没显式 import react,补一行
246
+ // `import React from 'react'`,交给下一步统一改写为 window.React,并确保
247
+ // 'react' 进入依赖清单(运行时据此注入 React UMD)。
248
+ const usesJsxRuntime = /\bReact\.createElement\b|\bReact\.Fragment\b/.test(intermediate);
249
+ const hasReactBinding = /\b(var|let|const)\s+React\b/.test(intermediate) || /\bimport\s+React\b/.test(intermediate);
250
+ if (usesJsxRuntime && !hasReactBinding) {
251
+ intermediate = "import React from 'react';\n" + intermediate;
252
+ if (!importedModules.includes('react')) {
253
+ importedModules.push('react');
254
+ importedModules.sort();
255
+ }
256
+ }
257
+
258
+ // 第二步:把 import/export 改写成 window 别名 + YidaComp。
259
+ const stage2 = Babel.transform(intermediate, {
260
+ filename: 'canvas.js',
261
+ plugins: [esmToWindowPlugin],
262
+ sourceType: 'module',
263
+ compact: false,
264
+ babelrc: false,
265
+ configFile: false,
266
+ });
267
+
268
+ const runtimeCode = stage2.code || '';
269
+ return {
270
+ runtimeCode,
271
+ importedModules: JSON.stringify(importedModules),
272
+ };
273
+ }
274
+
275
+ /**
276
+ * 兼容既有调用方(publish.js `await compileCanvas(src, { cookies })`)的异步入口。
277
+ * 本地编译不需要 cookies/endpoint,options 保留但忽略。
278
+ * @param {string} source
279
+ * @param {object} [options] 兼容占位,未使用
280
+ * @returns {Promise<{ runtimeCode: string, importedModules: string }>}
281
+ */
282
+ function compileCanvas(source, options = {}) { // eslint-disable-line no-unused-vars
283
+ return new Promise((resolve, reject) => {
284
+ if (typeof source !== 'string' || source.trim() === '') {
285
+ reject(new Error('canvas 编译源码为空'));
286
+ return;
287
+ }
288
+ try {
289
+ resolve(compileCanvasLocal(source));
290
+ } catch (compileError) {
291
+ const detail = compileError && compileError.message ? compileError.message : String(compileError);
292
+ reject(new Error(`Code Canvas 本地编译失败: ${detail}`));
293
+ }
294
+ });
295
+ }
296
+
297
+ module.exports = {
298
+ compileCanvas,
299
+ compileCanvasLocal,
300
+ extractImportedModules,
301
+ resolveWindowAlias,
302
+ MODULE_ALIAS_MAP,
303
+ };
@@ -33,6 +33,7 @@ const {
33
33
  const { t } = require('../core/i18n');
34
34
  const { banner, step, label, success, fail, warn, info, error, result, usage, hint } = require('../core/chalk');
35
35
  const { compileSource } = require('./page-compiler');
36
+ const { compileCanvas } = require('./canvas-compile');
36
37
  const { stripSubtableFieldPrefix } = require('../formula/evaluate');
37
38
  const { runLintCheck } = require('./page-linter');
38
39
  const { buildPageFile, shouldBuildPageSource } = require('./page-compat');
@@ -82,7 +83,8 @@ function parseArgs(argv = process.argv.slice(2)) {
82
83
  const healthCheck = args.includes('--health-check') || args.includes('--check');
83
84
  const compat = args.includes('--compat') || args.includes('--modern');
84
85
  const force = args.includes('--force');
85
- const filteredArgs = args.filter(arg => arg !== '--skip-lint' && arg !== '--health-check' && arg !== '--check' && arg !== '--compat' && arg !== '--modern' && arg !== '--force');
86
+ const canvas = args.includes('--canvas');
87
+ const filteredArgs = args.filter(arg => arg !== '--skip-lint' && arg !== '--health-check' && arg !== '--check' && arg !== '--compat' && arg !== '--modern' && arg !== '--force' && arg !== '--canvas');
86
88
 
87
89
  if (filteredArgs.length < 3) {
88
90
  usage(t('publish.usage'), t('publish.example'));
@@ -97,6 +99,7 @@ function parseArgs(argv = process.argv.slice(2)) {
97
99
  healthCheck,
98
100
  compat,
99
101
  force,
102
+ canvas,
100
103
  browserOpenMode: openOption.mode,
101
104
  };
102
105
  }
@@ -535,6 +538,123 @@ function buildSchemaContent(sourceCode, compiledCode, formUuid, options = {}) {
535
538
  return JSON.stringify(schema);
536
539
  }
537
540
 
541
+ // ── 2b. 构建 Code Canvas Schema ─────────────────────
542
+ // 与 native 的差异仅在 content JSON:Page.children 直接挂一个 YidaCodeCanvas
543
+ // 节点,无 RootHeader/RootContent/Jsx/RootFooter;actions.module 为默认 didMount
544
+ // 样板;Page.dataSource 精简(canvas 无原生数据桥)。
545
+ // wire 层(path/query/body/headers)完全复用 sendSaveRequest,零改动。
546
+
547
+ // canvas 页无用户页面方法,actions.module 只需注册一个空 didMount。
548
+ const CANVAS_ACTIONS_SOURCE = 'export function didMount() {}';
549
+ const CANVAS_ACTIONS_COMPILED = '"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.didMount=didMount;function didMount(){}';
550
+
551
+ function buildCanvasSchemaContent(sourceCode, runtimeCode, importedModules, formUuid) {
552
+ info(t('publish.building_schema'));
553
+
554
+ const nextNodeId = createNodeIdGenerator();
555
+ const constructorCode = "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}";
556
+
557
+ const schema = {
558
+ schemaType: 'superform',
559
+ schemaVersion: '5.0',
560
+ pages: [
561
+ {
562
+ utils: [
563
+ {
564
+ name: 'legaoBuiltin',
565
+ type: 'npm',
566
+ content: { package: '@ali/vu-legao-builtin', version: '3.0.0', exportName: 'legaoBuiltin' },
567
+ },
568
+ {
569
+ name: 'yidaPlugin',
570
+ type: 'npm',
571
+ content: { package: '@ali/vu-yida-plugin', version: '1.1.0', exportName: 'yidaPlugin' },
572
+ },
573
+ ],
574
+ componentsMap: [
575
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'YidaCodeCanvas' },
576
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootHeader' },
577
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootContent' },
578
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootFooter' },
579
+ { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'Page' },
580
+ ],
581
+ componentsTree: [
582
+ {
583
+ componentName: 'Page',
584
+ id: nextNodeId(),
585
+ props: {
586
+ contentBgColor: 'white',
587
+ pageStyle: { backgroundColor: '#f2f3f5' },
588
+ contentMargin: '0',
589
+ contentPadding: '0',
590
+ showTitle: false,
591
+ contentPaddingMobile: '0',
592
+ templateVersion: '1.0.0',
593
+ contentMarginMobile: '0',
594
+ className: 'page_' + generateSuffix(),
595
+ contentBgColorMobile: 'white',
596
+ },
597
+ condition: true,
598
+ css: 'body{background-color:#f2f3f5}.vc-page-yida-page{--yida-form-content-padding:0;--yida-form-content-margin:0;--yida-layout-padding:0}.vc-deep-container-entry.vc-rootcontent{padding:0!important;margin-top:0!important;margin-right:0!important;margin-bottom:0!important;margin-left:0!important}',
599
+ methods: {
600
+ __initMethods__: {
601
+ type: 'js',
602
+ source: 'function (exports, module) { /*set actions code here*/ }',
603
+ compiled: 'function (exports, module) { /*set actions code here*/ }',
604
+ },
605
+ },
606
+ dataSource: {
607
+ offline: [],
608
+ globalConfig: getGlobalDataSourceFitConfig(),
609
+ online: [],
610
+ list: [],
611
+ sync: true,
612
+ },
613
+ lifeCycles: {
614
+ constructor: { type: 'js', compiled: constructorCode, source: constructorCode },
615
+ componentWillUnmount: '',
616
+ componentDidMount: { name: 'didMount', id: 'didMount', params: {}, type: 'actionRef' },
617
+ },
618
+ hidden: false,
619
+ title: '',
620
+ isLocked: false,
621
+ conditionGroup: '',
622
+ children: [
623
+ {
624
+ componentName: 'YidaCodeCanvas',
625
+ id: nextNodeId(),
626
+ props: {
627
+ code: sourceCode,
628
+ runtimeCode: runtimeCode,
629
+ pageType: 'application',
630
+ isWebCCompiled: true,
631
+ componentProps: {},
632
+ importedModules: importedModules || '',
633
+ },
634
+ condition: true,
635
+ hidden: false,
636
+ title: '',
637
+ isLocked: false,
638
+ conditionGroup: '',
639
+ },
640
+ ],
641
+ },
642
+ ],
643
+ id: formUuid,
644
+ connectComponent: [],
645
+ },
646
+ ],
647
+ actions: {
648
+ module: { compiled: CANVAS_ACTIONS_COMPILED, source: CANVAS_ACTIONS_SOURCE },
649
+ type: 'FUNCTION',
650
+ list: [{ id: 'didMount', title: 'didMount' }],
651
+ },
652
+ config: { connectComponent: [] },
653
+ };
654
+
655
+ return JSON.stringify(schema);
656
+ }
657
+
538
658
 
539
659
  // ── 4. 发送 saveFormSchema 请求 ──────────────────────
540
660
 
@@ -873,47 +993,72 @@ function sendHealthCheckRequest(pageUrl, cookies) {
873
993
  // ── 主流程 ────────────────────────────────────────────
874
994
 
875
995
  async function main(argv) {
876
- const { appType, formUuid, sourceFile, skipLint, healthCheck, compat, force, browserOpenMode } = parseArgs(argv);
996
+ const { appType, formUuid, sourceFile, skipLint, healthCheck, compat, force, canvas: canvasFlag, browserOpenMode } = parseArgs(argv);
877
997
 
878
998
  let sourcePath = path.resolve(sourceFile);
879
999
  if (!fs.existsSync(sourcePath)) {
880
1000
  error(t('publish.source_not_found', sourcePath));
881
1001
  }
882
1002
 
1003
+ // 路由:默认 native;.canvas.jsx / .canvas.tsx 源文件自动走 Code Canvas 链路,
1004
+ // --canvas 作为显式覆盖(用于扩展名不规范但确为 canvas 源码的场景)。
1005
+ const isCanvas = canvasFlag || /\.canvas\.(jsx|tsx)$/i.test(sourcePath);
1006
+
883
1007
  const initialSourceCode = fs.readFileSync(sourcePath, 'utf-8');
884
1008
  warnLargePageSource(initialSourceCode, sourcePath);
885
- if (shouldBuildPageSource(initialSourceCode, sourcePath, { modern: compat })) {
886
- step(0, t('build_page.step'));
887
- const buildResult = buildPageFile(sourcePath, { modern: compat, skipSizeWarning: true });
888
- if (!buildResult.ok) {
889
- buildResult.errors.forEach((issue) => warn(`${issue.code}: ${issue.message}`));
890
- const lintErrors = buildResult.lint && buildResult.lint.errors ? buildResult.lint.errors : [];
891
- lintErrors.forEach((issue) => warn(`${issue.rule}: ${issue.message}`));
892
- process.exit(1);
893
- }
894
- sourcePath = buildResult.outputPath;
895
- success(t('build_page.output', sourcePath));
896
- }
897
1009
 
898
- const parsedSource = path.parse(sourcePath);
899
- const compiledPath = path.join(findProjectRoot(), 'pages', 'dist', `${parsedSource.name}.js`);
900
- warnDuplicateSourceMismatches(sourcePath);
901
-
902
- // Step 0: 宜搭编码规范预检(可通过 --skip-lint 跳过)
903
- if (!skipLint) {
904
- step(0, t('publish.step_lint'));
905
- const sourceCode = fs.readFileSync(sourcePath, 'utf-8');
906
- const lintPassed = runLintCheck(sourceCode, sourcePath, { successMessage: false });
907
- if (!lintPassed) {
1010
+ let sourceCode = initialSourceCode;
1011
+ let compiledCode = '';
1012
+ let canvasResult = null;
1013
+
1014
+ if (isCanvas) {
1015
+ // Code Canvas 链路:跳过 native 编译/lint,改用本地 Babel 编译
1016
+ // (JSX/TS → runtimeCode + importedModules),无需登录态,可在开浏览器前失败快。
1017
+ step(1, t('publish.step_canvas_compile'));
1018
+ info(t('publish.canvas_compiling'));
1019
+ try {
1020
+ canvasResult = await compileCanvas(initialSourceCode);
1021
+ } catch (canvasCompileError) {
1022
+ fail(t('publish.canvas_compile_failed', canvasCompileError.message));
908
1023
  process.exit(1);
909
1024
  }
910
- success(t('publish.lint_passed'));
1025
+ success(t('publish.canvas_compile_done'));
911
1026
  } else {
912
- info(t('publish.lint_skipped'));
1027
+ if (shouldBuildPageSource(initialSourceCode, sourcePath, { modern: compat })) {
1028
+ step(0, t('build_page.step'));
1029
+ const buildResult = buildPageFile(sourcePath, { modern: compat, skipSizeWarning: true });
1030
+ if (!buildResult.ok) {
1031
+ buildResult.errors.forEach((issue) => warn(`${issue.code}: ${issue.message}`));
1032
+ const lintErrors = buildResult.lint && buildResult.lint.errors ? buildResult.lint.errors : [];
1033
+ lintErrors.forEach((issue) => warn(`${issue.rule}: ${issue.message}`));
1034
+ process.exit(1);
1035
+ }
1036
+ sourcePath = buildResult.outputPath;
1037
+ success(t('build_page.output', sourcePath));
1038
+ }
1039
+ warnDuplicateSourceMismatches(sourcePath);
1040
+
1041
+ // Step 0: 宜搭编码规范预检(可通过 --skip-lint 跳过)
1042
+ if (!skipLint) {
1043
+ step(0, t('publish.step_lint'));
1044
+ const lintSource = fs.readFileSync(sourcePath, 'utf-8');
1045
+ const lintPassed = runLintCheck(lintSource, sourcePath, { successMessage: false });
1046
+ if (!lintPassed) {
1047
+ process.exit(1);
1048
+ }
1049
+ success(t('publish.lint_passed'));
1050
+ } else {
1051
+ info(t('publish.lint_skipped'));
1052
+ }
1053
+
1054
+ step(1, t('publish.step_compile'));
1055
+ const compiled = compileSource(sourcePath);
1056
+ sourceCode = compiled.sourceCode;
1057
+ compiledCode = compiled.compiledCode;
913
1058
  }
914
1059
 
915
- step(1, t('publish.step_compile'));
916
- const { sourceCode, compiledCode } = compileSource(sourcePath);
1060
+ const parsedSource = path.parse(sourcePath);
1061
+ const compiledPath = path.join(findProjectRoot(), 'pages', 'dist', `${parsedSource.name}.js`);
917
1062
 
918
1063
  step(2, t('common.step_login', 2));
919
1064
  let cookieData = loadCookieData();
@@ -934,18 +1079,23 @@ async function main(argv) {
934
1079
  cookies = authRef.cookies;
935
1080
  baseUrl = authRef.baseUrl;
936
1081
 
937
- let existingDataSource = null;
938
- try {
939
- existingDataSource = await readExistingPageDataSource(appType, formUuid, authRef);
940
- } catch (dataSourceError) {
941
- fail(t('publish.data_source_fetch_failed', dataSourceError.message));
942
- process.exit(1);
1082
+ let schemaContent;
1083
+ if (isCanvas) {
1084
+ // canvasResult 已在登录前本地编译完成,这里只需据其装配 Schema content。
1085
+ schemaContent = buildCanvasSchemaContent(sourceCode, canvasResult.runtimeCode, canvasResult.importedModules, formUuid);
1086
+ } else {
1087
+ let existingDataSource = null;
1088
+ try {
1089
+ existingDataSource = await readExistingPageDataSource(appType, formUuid, authRef);
1090
+ } catch (dataSourceError) {
1091
+ fail(t('publish.data_source_fetch_failed', dataSourceError.message));
1092
+ process.exit(1);
1093
+ }
1094
+ csrfToken = authRef.csrfToken;
1095
+ cookies = authRef.cookies;
1096
+ baseUrl = authRef.baseUrl;
1097
+ schemaContent = buildSchemaContent(sourceCode, compiledCode, formUuid, { existingDataSource });
943
1098
  }
944
- csrfToken = authRef.csrfToken;
945
- cookies = authRef.cookies;
946
- baseUrl = authRef.baseUrl;
947
-
948
- const schemaContent = buildSchemaContent(sourceCode, compiledCode, formUuid, { existingDataSource });
949
1099
  success(t('publish.schema_built'));
950
1100
 
951
1101
  banner(t('publish.title'));
@@ -1078,4 +1228,5 @@ if (require.main === module) {
1078
1228
  module.exports.extractPageDataSource = extractPageDataSource;
1079
1229
  module.exports.countCustomPageDataSources = countCustomPageDataSources;
1080
1230
  module.exports.buildSchemaContent = buildSchemaContent;
1231
+ module.exports.buildCanvasSchemaContent = buildCanvasSchemaContent;
1081
1232
  }
@@ -86,7 +86,7 @@ const COMMAND_GROUPS = [
86
86
  command('build-page', ['build-page'], 'build-page <sourceFile> [--output file|--write]', 'help.cmd_build_page', { requiresLogin: false }),
87
87
  command('check-page', ['check-page'], 'check-page <src> [--compat]', 'help.cmd_check_page', { output: 'text|json' }),
88
88
  command('compile', ['compile'], 'compile <src>', 'help.cmd_compile', { requiresLogin: false }),
89
- command('publish', ['publish'], 'publish <src> <appType> <formUuid> [--health-check] [--force] [--open|--no-open]', 'help.cmd_publish'),
89
+ command('publish', ['publish'], 'publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--open|--no-open]', 'help.cmd_publish'),
90
90
  command('update-form-config', ['update-form-config'], 'update-form-config <appType> ...', 'help.cmd_update_form_config'),
91
91
  ],
92
92
  },
@@ -478,7 +478,7 @@ module.exports = {
478
478
  lint_array_callback_function: '.{0}(function ...) قد يفقد this داخل callbacks؛ استخدم بدلا من ذلك .{0}((item) => ...)',
479
479
  lint_foreach_callback_function: '.forEach(function ...) قد يفقد this داخل callbacks؛ يفضل استخدام .forEach((item) => ...)',
480
480
  lint_controlled_input: 'يستخدم input وضع value المتحكم به. يجب أن تستخدم صفحات Yida defaultValue + onChange لتحديث _customState',
481
- lint_native_select_ui: 'تم العثور على <select> أصلي. في صفحات الكود المخصصة يمكنك استخدام مكون Yida <SelectField> مباشرة (يتم حقنه عالميا في وقت التشغيل، ولا يحتاج import)، أو استخدام قائمة مخصصة div+button+onClick للحفاظ على تناسق التصميم وتوافق الجوال',
481
+ lint_native_select_ui: 'تم العثور على <select> أصلي. في الصفحات المخصصة العادية استخدم قائمة div+button+onClick للحفاظ على تناسق التصميم وتوافق الجوال. استخدم مكونات حقول Yida في Code Canvas فقط بعد التحقق من ربط التبعيات',
482
482
  lint_iframe_self_navigation: 'تعمل صفحات Yida المخصصة داخل iframe. استخدم target="_top"/target="_blank" أو window.top.location للتنقل إلى صفحات Yida لتجنب الصفحات المتداخلة.',
483
483
  lint_page_size_limit: 'pageSize={0} يتجاوز حد واجهة Yida API وهو 100؛ استخدم 100 أو أقل',
484
484
  lint_page_size_recommend: 'pageSize={0} كبير؛ القيمة الموصى بها هي 50 (أفضل أداء، الحد الأقصى 100). يفضل 10/20/50',
@@ -522,6 +522,10 @@ module.exports = {
522
522
  building_schema: '[4/4] جارٍ بناء المخطط...',
523
523
  formula_prefix_fixed: ' 🔧 تم تصحيح {0} من مراجع بادئة صيغة الجدول الفرعي تلقائيًا',
524
524
  schema_built: ' ✅ تم بناء المخطط بنجاح!',
525
+ step_canvas_compile: '\n📦 Step 1: تجميع مصدر Code Canvas\n',
526
+ canvas_compiling: ' 🎨 جارٍ تجميع مصدر Code Canvas محليًا...',
527
+ canvas_compile_done: ' ✅ تم تجميع Code Canvas!',
528
+ canvas_compile_failed: ' ❌ فشل تجميع Code Canvas: {0}',
525
529
  step_login: '\n🔑 Step 2: قراءة بيانات تسجيل الدخول',
526
530
  step_publish: '\n📤 Step 3: نشر المخطط\n',
527
531
  publish_failed: '\n❌ فشل النشر: {0}',
@@ -535,7 +539,7 @@ module.exports = {
535
539
  health_check_failed: ' ⚠️ Health check failed: HTTP {0} {1}',
536
540
  exception: '\n❌ خطأ في النشر: {0}',
537
541
  source_not_found: '❌ لم يتم العثور على ملف المصدر: {0}',
538
- usage: 'الاستخدام: openyida publish <ملفالمصدر> <appType> <formUuid> [--health-check]',
542
+ usage: 'الاستخدام: openyida publish <ملفالمصدر> <appType> <formUuid> [--health-check] [--canvas]',
539
543
  example: 'مثال: openyida publish pages/src/xxx.js APP_XXX FORM-XXX --health-check',
540
544
  },
541
545