openyida 2026.7.7 → 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.
- package/README.md +4 -2
- package/bin/yida.js +4 -4
- package/lib/app/canvas-compile.js +303 -0
- package/lib/app/publish.js +190 -39
- package/lib/auth/auth.js +11 -3
- package/lib/auth/login.js +34 -15
- package/lib/core/command-manifest.js +1 -1
- package/lib/core/env.js +19 -4
- package/lib/core/locales/ar.js +6 -2
- package/lib/core/locales/de.js +6 -2
- package/lib/core/locales/en.js +7 -3
- package/lib/core/locales/es.js +6 -2
- package/lib/core/locales/fr.js +6 -2
- package/lib/core/locales/hi.js +6 -2
- package/lib/core/locales/ja.js +7 -3
- package/lib/core/locales/ko.js +6 -2
- package/lib/core/locales/pt.js +6 -2
- package/lib/core/locales/vi.js +6 -2
- package/lib/core/locales/zh-HK.js +7 -3
- package/lib/core/locales/zh.js +7 -3
- package/lib/core/utils.js +195 -15
- package/lib/samples/yida-custom-page/custom-page-template.js +6 -6
- package/lib/samples/yida-custom-page/design-tokens.js +14 -12
- package/package.json +1 -1
- package/scripts/e2e-real/skill-coverage.js +4 -0
- package/scripts/validate-ci.sh +6 -0
- package/scripts/validate-package-size.js +12 -1
- package/yida-skills/SKILL.md +19 -5
- package/yida-skills/references/development-rules.md +2 -2
- package/yida-skills/references/field-and-url-reference.md +14 -1
- package/yida-skills/skills/yida-app/SKILL.md +3 -2
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +113 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +163 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-design-system.md +148 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +115 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/dependencies-and-cdn.md +45 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +68 -0
- package/yida-skills/skills/yida-canvas-upgrade/SKILL.md +99 -0
- package/yida-skills/skills/yida-canvas-upgrade/references/migration-examples.md +69 -0
- package/yida-skills/skills/yida-custom-page/SKILL.md +7 -2
- package/yida-skills/skills/yida-custom-page/examples/attachment-upload.js +1 -1
- package/yida-skills/skills/yida-custom-page/references/coding-guide.md +3 -3
- package/yida-skills/skills/yida-custom-page/references/component-jsx-guide.md +3 -2
- package/yida-skills/skills/yida-custom-page/references/design-system.md +30 -13
- package/yida-skills/skills/yida-nav-shell/SKILL.md +119 -0
- package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +302 -0
- package/yida-skills/skills/yida-page-config/SKILL.md +1 -0
- package/yida-skills/skills/yida-page-uiux/SKILL.md +68 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/dashboard.md +128 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/detail.md +79 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/landing.md +101 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/list.md +78 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/workbench.md +74 -0
- package/yida-skills/skills/yida-page-uiux/references/visual-decision-engine.md +151 -0
- package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +36 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-0-nav-shape.md +39 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-1-page-type.md +25 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-2-intent-decode.md +25 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-3-scene-routing.md +29 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-4-visual-decision.md +32 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-5-icon-and-assets.md +36 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-6-deai-selfcheck.md +37 -0
- 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
|
[](https://www.npmjs.com/package/openyida)
|
|
14
14
|
[](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
|
@@ -266,7 +266,7 @@ function printLoginResult(result) {
|
|
|
266
266
|
corp_id: result && result.corp_id,
|
|
267
267
|
user_id: result && result.user_id,
|
|
268
268
|
csrf_token: result && result.csrf_token ? `${result.csrf_token.slice(0, 16)}...` : undefined,
|
|
269
|
-
cookies_count: Array.isArray(result && result.cookies) ? result.cookies.length : 0,
|
|
269
|
+
cookies_count: Array.isArray(result && result.cookies) ? result.cookies.length : (result.cookies_count || 0),
|
|
270
270
|
};
|
|
271
271
|
console.log(JSON.stringify(summary));
|
|
272
272
|
}
|
|
@@ -559,7 +559,7 @@ async function main() {
|
|
|
559
559
|
case 'login': {
|
|
560
560
|
const { checkLoginOnly } = require('../lib/auth/login');
|
|
561
561
|
const loginArgs = applyLoginEnvironmentFlags(args, { inferTargetUrl: true });
|
|
562
|
-
const {
|
|
562
|
+
const { isEnvAuthMode } = require('../lib/core/utils');
|
|
563
563
|
if (loginArgs.includes('--agent-poll') || loginArgs.includes('--codex-poll')) {
|
|
564
564
|
const sessionFile = getArgValue(loginArgs, '--agent-poll') || getArgValue(loginArgs, '--codex-poll');
|
|
565
565
|
const { pollCodexQrLogin } = require('../lib/auth/qr-login');
|
|
@@ -577,7 +577,7 @@ async function main() {
|
|
|
577
577
|
} else if (loginArgs.includes('--check-only')) {
|
|
578
578
|
const result = checkLoginOnly({ includeSecrets: loginArgs.includes('--with-cookies') });
|
|
579
579
|
console.log(JSON.stringify(result, null, 2));
|
|
580
|
-
} else if (
|
|
580
|
+
} else if (isEnvAuthMode()) {
|
|
581
581
|
const result = checkLoginOnly({ includeSecrets: true });
|
|
582
582
|
printLoginResult(result);
|
|
583
583
|
} else if (shouldUseCodexQrLogin(loginArgs)) {
|
|
@@ -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
|
+
};
|
package/lib/app/publish.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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.
|
|
1025
|
+
success(t('publish.canvas_compile_done'));
|
|
911
1026
|
} else {
|
|
912
|
-
|
|
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
|
-
|
|
916
|
-
const
|
|
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
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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
|
}
|