openyida 2026.9.4 → 2026.9.7
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 +7 -6
- package/bin/yida.js +1 -1
- package/lib/app/app-list.js +78 -53
- package/lib/app/canvas-compile.js +147 -0
- package/lib/app/page-linter.js +24 -0
- package/lib/app/publish.js +14 -1
- package/lib/app/services/canvas-page-schema-builder.js +175 -0
- package/lib/app/update-app.js +8 -2
- package/lib/bridge/bridge.js +15 -3
- package/lib/connector/api.js +3 -1
- package/lib/connector/connector-add-action.js +2 -0
- package/lib/connector/connector-create-connection.js +119 -16
- package/lib/connector/connector-create.js +40 -32
- package/lib/connector/connector-detail.js +2 -1
- package/lib/connector/connector-gen-template.js +5 -17
- package/lib/connector/connector-list-connections.js +5 -4
- package/lib/connector/connector-test.js +48 -2
- package/lib/connector/contract.js +132 -10
- package/lib/connector/operation-normalizer.js +20 -4
- package/lib/connector/yida-system-token.js +280 -0
- package/lib/core/agent-capabilities.js +24 -2
- package/lib/core/command-manifest.js +8 -8
- package/lib/core/locales/en.js +43 -2
- package/lib/core/locales/zh.js +43 -2
- package/lib/core/redact.js +4 -3
- package/lib/integration/connector-presets.js +95 -9
- package/lib/integration/integration-connector-schema.js +79 -15
- package/lib/integration/integration-create.js +131 -5
- package/lib/integration/integration-readback.js +136 -0
- package/lib/process/configure-process.js +69 -16
- package/package.json +1 -1
- package/scripts/postinstall.js +1 -1
- package/yida-skills/SKILL.md +3 -1
- package/yida-skills/references/resource-context.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +3 -1
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +4 -3
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +3 -1
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +2 -0
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +19 -3
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +25 -22
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +19 -18
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +41 -37
- package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +2 -2
- package/yida-skills/skills/yida-canvas-custom-page/references/native-components-bridge.md +1 -1
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +3 -3
- package/yida-skills/skills/yida-canvas-data-binding/references/connector-binding.md +23 -0
- package/yida-skills/skills/yida-connector/SKILL.md +15 -8
- package/yida-skills/skills/yida-connector/examples/operations-search-formdata-v2.json +2 -2
- package/yida-skills/skills/yida-connector/references/connector-action-format.md +9 -0
- package/yida-skills/skills/yida-connector/templates/api-document-template.md +6 -19
- package/yida-skills/skills/yida-data-management/SKILL.md +8 -4
- package/yida-skills/skills/yida-design/SKILL.md +1 -1
- package/yida-skills/skills/yida-design/workflow/output-design.md +2 -0
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +4 -3
- package/yida-skills/skills/yida-dingtalk-openapi/SKILL.md +75 -0
- package/yida-skills/skills/yida-dingtalk-openapi/references/api-contract.md +98 -0
- package/yida-skills/skills/yida-dingtalk-openapi/references/connector-mapping.md +27 -0
- package/yida-skills/skills/yida-integration/SKILL.md +29 -0
- package/yida-skills/skills/yida-publish-page/SKILL.md +3 -2
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +2 -1
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +3 -0
- package/yida-skills/skills-index.json +54 -0
package/README.md
CHANGED
|
@@ -168,8 +168,9 @@ For a user-facing list of supported features and matching CLI commands, see [Ope
|
|
|
168
168
|
```bash
|
|
169
169
|
openyida create-app --name "CRM" --desc "Customer management"
|
|
170
170
|
openyida sample yida-design app-theme --output .cache/openyida/crm/app-theme.css
|
|
171
|
+
openyida create-app --name "CRM" --desc "Customer management" --theme-file .cache/openyida/crm/app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
|
|
172
|
+
openyida app-list --type managed --page 1 --size 16
|
|
171
173
|
openyida update-app APP_XXX --theme-file .cache/openyida/crm/app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
|
|
172
|
-
openyida app-list --size 20
|
|
173
174
|
openyida corp-efficiency
|
|
174
175
|
openyida create-form create APP_XXX "Customer" .cache/openyida/forms/customer-fields.json
|
|
175
176
|
openyida create-form update APP_XXX FORM_XXX .cache/openyida/forms/customer-changes.json
|
|
@@ -388,7 +389,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
388
389
|
|
|
389
390
|
| Command | Description |
|
|
390
391
|
|---------|-------------|
|
|
391
|
-
| `openyida app-list [--size N]` |
|
|
392
|
+
| `openyida app-list [--type managed\|created] [--page N] [--size N]` | Page through apps I manage or created |
|
|
392
393
|
| `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
|
|
393
394
|
| `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
|
|
394
395
|
| `openyida design-plan init <requirement-brief.json> [--theme-id <id>] [--output-dir <dir>] [--json]` | Initialize a plan draft from confirmed requirements |
|
|
@@ -484,9 +485,9 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
484
485
|
| `openyida connector update-action --connector-id <id> --action <operationId> --query-json JSON --confirm` | Safely update action query defaults |
|
|
485
486
|
| `openyida connector list-actions <id>` | List actions |
|
|
486
487
|
| `openyida connector delete-action <id> <operation-id>` | Delete an action |
|
|
487
|
-
| `openyida connector test --connector-id <id> --action <actionId> [--path-json JSON] [--query-json JSON] [--header-json JSON] [--body-json JSON] [--account-id <id>]` | Test an action |
|
|
488
|
+
| `openyida connector test --connector-id <id> --action <actionId> [--path-json JSON] [--query-json JSON] [--header-json JSON] [--body-json JSON] [--account-id <id>] [--system-token-app <appType>]` | Test an action |
|
|
488
489
|
| `openyida connector list-connections <id>` | List auth connections |
|
|
489
|
-
| `openyida connector create-connection <id> <name
|
|
490
|
+
| `openyida connector create-connection <id> <name> [--interactive]` | Create an auth connection |
|
|
490
491
|
| `openyida connector smart-create --curl "..."` | Generate a redacted action draft from cURL (no remote create) |
|
|
491
492
|
| `openyida connector parse-api [options]` | Parse API information |
|
|
492
493
|
| `openyida connector gen-template [output]` | Generate API document template |
|
|
@@ -495,7 +496,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
495
496
|
|
|
496
497
|
| Command | Description |
|
|
497
498
|
|---------|-------------|
|
|
498
|
-
| `openyida integration create <appType> ... [--spec file.json]` | Create integration automation flow |
|
|
499
|
+
| `openyida integration create <appType> ... [--spec file.json] [--connector-system-token-app <appType>]` | Create integration automation flow |
|
|
499
500
|
| `openyida integration update <appType> <formUuid> <processCode> --spec <desired-spec.json> [--publish]` | Probe integration update capability (currently blocked without full readback) |
|
|
500
501
|
| `openyida integration list <appType> [--flow-types 1,2,3,5,6] [--form-uuid <uuid>] [--status y\|n] [--json]` | List integration automation flows |
|
|
501
502
|
| `openyida integration enable <appType> <formUuid> <processCode>` | Enable integration automation flow |
|
|
@@ -568,7 +569,7 @@ The CLI package ships the core UI languages `zh` and `en` by default. Other CLI
|
|
|
568
569
|
|
|
569
570
|
#### Forms and Pages
|
|
570
571
|
|
|
571
|
-
Form field definitions can include `alias` or `componentAlias` to populate Yida designer component aliases, stored as `pages[0].componentAlias.items`. Yida runtime resolves these aliases in page JS, so `this.$('phone')` can be used instead of `this.$('textField_xxx')`; OpenYida form rules, validations, and `openyida data ... --resolve-aliases` JSON inputs also accept aliases as field references. For server-side DingTalk OpenAPI calls, use `GET /v2.0/yida/forms/component/alias/{appType}/{formUuid}` to read the `{ fieldId, alias }` mapping, then translate aliases before sending form data/search JSON. That endpoint requires `systemToken`, `userId`, an access token, and the Yida form data read permission
|
|
572
|
+
Form field definitions can include `alias` or `componentAlias` to populate Yida designer component aliases, stored as `pages[0].componentAlias.items`. Yida runtime resolves these aliases in page JS, so `this.$('phone')` can be used instead of `this.$('textField_xxx')`; OpenYida form rules, validations, and `openyida data ... --resolve-aliases` JSON inputs also accept aliases as field references. For server-side DingTalk OpenAPI calls, use `GET /v2.0/yida/forms/component/alias/{appType}/{formUuid}` to read the `{ fieldId, alias }` mapping, then translate aliases before sending form data/search JSON. That endpoint requires `systemToken`, `userId`, an access token, and the Yida form data read permission. Use `connector test --system-token-app <appType>` for a transient test or `integration create --connector-system-token-app <appType>` for a server-side automation; OpenYida resolves `systemToken` internally and never prints it or stores it in page source and Action defaults.
|
|
572
573
|
|
|
573
574
|
`openyida publish` preserves existing custom page data sources by default. Before saving the new compiled JSX Schema, it reads the current page Schema and merges the Page-level `dataSource` with the built-in `urlParams` and `timestamp` sources, so manually configured data sources are not deleted during republish.
|
|
574
575
|
|
package/bin/yida.js
CHANGED
|
@@ -1119,7 +1119,7 @@ async function main() {
|
|
|
1119
1119
|
delete-action <connector-id> <operation-id> 删除执行动作
|
|
1120
1120
|
test --connector-id <id> --action <actionId> [结构化 JSON 参数] 测试执行动作
|
|
1121
1121
|
list-connections <connector-id> 列出鉴权账号
|
|
1122
|
-
create-connection <connector-id> <name>
|
|
1122
|
+
create-connection <connector-id> <name> [--interactive] 创建鉴权账号;钉钉凭据可在本机隐藏输入
|
|
1123
1123
|
smart-create --curl "curl命令" 生成脱敏连接器动作草稿(不创建远端资源)
|
|
1124
1124
|
parse-api [选项] 解析接口信息
|
|
1125
1125
|
gen-template [输出路径] 生成接口文档模板
|
package/lib/app/app-list.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* app-list.js - 查询我的应用列表
|
|
3
3
|
*
|
|
4
|
-
* 用法:openyida app-list [--page <页码>] [--size <每页数量>]
|
|
4
|
+
* 用法:openyida app-list [--type managed|created] [--page <页码>] [--size <每页数量>]
|
|
5
5
|
*
|
|
6
6
|
* 输出字段:
|
|
7
7
|
* appName - 应用名称(zh_CN)
|
|
@@ -12,61 +12,65 @@
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
14
|
const { createAuthRef, createYidaClient } = require('../core/yida-client');
|
|
15
|
-
const {
|
|
15
|
+
const { t } = require('../core/i18n');
|
|
16
|
+
const { throwCommandError, throwUsage } = require('../core/command-errors');
|
|
16
17
|
|
|
17
18
|
const API_PATH = '/query/app/getAppList.json';
|
|
19
|
+
const DEFAULT_PAGE_SIZE = 16;
|
|
20
|
+
const DEFAULT_PAGE_INDEX = 1;
|
|
21
|
+
const DEFAULT_TYPE = 'managed';
|
|
22
|
+
const APP_TYPES = Object.freeze({
|
|
23
|
+
managed: true,
|
|
24
|
+
created: false,
|
|
25
|
+
});
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
28
|
* 拉取单页应用列表
|
|
21
29
|
*/
|
|
22
|
-
function fetchAppListPage(auth, pageIndex, pageSize) {
|
|
30
|
+
function fetchAppListPage(auth, pageIndex, pageSize, isAdmin) {
|
|
23
31
|
return createYidaClient({ authRef: auth }).get(API_PATH, (ref) => ({
|
|
24
32
|
_api: 'nattyFetch',
|
|
25
33
|
_mock: 'false',
|
|
26
34
|
pageIndex,
|
|
27
35
|
pageSize,
|
|
28
36
|
creator: ref.userId,
|
|
37
|
+
isAdmin,
|
|
29
38
|
_stamp: Date.now(),
|
|
30
39
|
}));
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
async function fetchAllApps(auth, pageSize) {
|
|
37
|
-
const firstResult = await fetchAppListPage(auth, 1, pageSize);
|
|
38
|
-
|
|
39
|
-
// 如果首次请求就提示需要登录,直接返回原始结果让外层自动刷新 token 后重试。
|
|
40
|
-
if (firstResult.__needLogin) {
|
|
41
|
-
return firstResult;
|
|
42
|
+
function parsePositiveInteger(value, optionName) {
|
|
43
|
+
if (!/^\d+$/.test(String(value || '')) || Number(value) < 1) {
|
|
44
|
+
throwUsage(t('app_list.invalid_positive_integer', optionName, value || ''));
|
|
42
45
|
}
|
|
46
|
+
return Number(value);
|
|
47
|
+
}
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const totalPages = Math.ceil(totalCount / pageSize);
|
|
52
|
-
const remainingFetches = [];
|
|
53
|
-
|
|
54
|
-
for (let pageIndex = 2; pageIndex <= totalPages; pageIndex++) {
|
|
55
|
-
remainingFetches.push(fetchAppListPage(auth, pageIndex, pageSize));
|
|
56
|
-
}
|
|
49
|
+
function parseArgs(args = []) {
|
|
50
|
+
const options = {
|
|
51
|
+
type: DEFAULT_TYPE,
|
|
52
|
+
pageIndex: DEFAULT_PAGE_INDEX,
|
|
53
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
54
|
+
};
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
for (let index = 0; index < args.length; index++) {
|
|
57
|
+
const arg = args[index];
|
|
58
|
+
if (arg === '--type') {
|
|
59
|
+
const type = args[++index];
|
|
60
|
+
if (!Object.prototype.hasOwnProperty.call(APP_TYPES, type)) {
|
|
61
|
+
throwUsage(t('app_list.invalid_type', type || '', Object.keys(APP_TYPES).join(', ')));
|
|
62
|
+
}
|
|
63
|
+
options.type = type;
|
|
64
|
+
} else if (arg === '--page') {
|
|
65
|
+
options.pageIndex = parsePositiveInteger(args[++index], '--page');
|
|
66
|
+
} else if (arg === '--size') {
|
|
67
|
+
options.pageSize = parsePositiveInteger(args[++index], '--size');
|
|
68
|
+
} else {
|
|
69
|
+
throwUsage(t('app_list.invalid_argument', arg));
|
|
66
70
|
}
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
return
|
|
73
|
+
return options;
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
/**
|
|
@@ -86,11 +90,13 @@ function hasHelpFlag(args) {
|
|
|
86
90
|
|
|
87
91
|
function printUsage() {
|
|
88
92
|
process.stderr.write([
|
|
89
|
-
'
|
|
93
|
+
t('app_list.usage'),
|
|
90
94
|
'',
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
95
|
+
t('app_list.options'),
|
|
96
|
+
t('app_list.option_type'),
|
|
97
|
+
t('app_list.option_page'),
|
|
98
|
+
t('app_list.option_size'),
|
|
99
|
+
t('app_list.option_help'),
|
|
94
100
|
'',
|
|
95
101
|
].join('\n'));
|
|
96
102
|
}
|
|
@@ -105,41 +111,60 @@ async function run(args) {
|
|
|
105
111
|
return;
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
const
|
|
109
|
-
const pageSize = pageSizeIndex !== -1 && args[pageSizeIndex + 1]
|
|
110
|
-
? parseInt(args[pageSizeIndex + 1], 10)
|
|
111
|
-
: 20;
|
|
114
|
+
const options = parseArgs(args);
|
|
112
115
|
|
|
113
116
|
const authRef = createAuthRef();
|
|
114
117
|
|
|
115
|
-
let
|
|
118
|
+
let result;
|
|
116
119
|
try {
|
|
117
|
-
|
|
120
|
+
result = await fetchAppListPage(
|
|
121
|
+
authRef,
|
|
122
|
+
options.pageIndex,
|
|
123
|
+
options.pageSize,
|
|
124
|
+
APP_TYPES[options.type]
|
|
125
|
+
);
|
|
118
126
|
} catch (err) {
|
|
119
|
-
throwCommandError(
|
|
127
|
+
throwCommandError(t('app_list.query_failed', err.message));
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
// 如果重试后仍然返回登录失效标记,说明登录态确实不可用
|
|
123
|
-
if (
|
|
124
|
-
throwCommandError('
|
|
131
|
+
if (result && result.__needLogin) {
|
|
132
|
+
throwCommandError(t('app_list.auth_required'), { code: 'CLI_AUTH_REQUIRED' });
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
if (!
|
|
128
|
-
|
|
129
|
-
return;
|
|
135
|
+
if (!result || !result.success) {
|
|
136
|
+
throwCommandError(t('app_list.query_failed', (result && result.errorMsg) || t('app_list.unknown_error')));
|
|
130
137
|
}
|
|
131
138
|
|
|
139
|
+
const content = result.content || {};
|
|
140
|
+
const apps = Array.isArray(content.data) ? content.data : [];
|
|
141
|
+
const totalCount = Number.isFinite(Number(content.totalCount)) ? Number(content.totalCount) : apps.length;
|
|
142
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / options.pageSize));
|
|
143
|
+
const scopeLabel = t(`app_list.scope_${options.type}`);
|
|
132
144
|
const formattedApps = apps.map(formatApp);
|
|
133
145
|
|
|
134
146
|
// stdout 输出 JSON,方便 AI 工具解析
|
|
135
147
|
console.log(JSON.stringify(formattedApps, null, 2));
|
|
136
148
|
|
|
137
149
|
// stderr 输出人类可读摘要
|
|
138
|
-
const { c, success: chalkSuccess, listItem } = require('../core/chalk');
|
|
139
|
-
chalkSuccess(
|
|
150
|
+
const { c, success: chalkSuccess, hint, listItem } = require('../core/chalk');
|
|
151
|
+
chalkSuccess(t(
|
|
152
|
+
'app_list.found',
|
|
153
|
+
scopeLabel,
|
|
154
|
+
options.pageIndex,
|
|
155
|
+
totalPages,
|
|
156
|
+
formattedApps.length,
|
|
157
|
+
totalCount
|
|
158
|
+
));
|
|
140
159
|
for (const app of formattedApps) {
|
|
141
160
|
listItem(`${c.bold}${app.appName}${c.reset} ${c.dim}[${app.appType}]${c.reset} ${c.cyan}${app.systemLink}${c.reset}`);
|
|
142
161
|
}
|
|
162
|
+
if (options.pageIndex < totalPages) {
|
|
163
|
+
hint(t(
|
|
164
|
+
'app_list.next_page',
|
|
165
|
+
`openyida app-list --type ${options.type} --page ${options.pageIndex + 1} --size ${options.pageSize}`
|
|
166
|
+
));
|
|
167
|
+
}
|
|
143
168
|
}
|
|
144
169
|
|
|
145
|
-
module.exports = { run };
|
|
170
|
+
module.exports = { parseArgs, run };
|
|
@@ -75,6 +75,7 @@ const MODULE_ALIAS_MAP = {
|
|
|
75
75
|
|
|
76
76
|
const MANUAL_DEPENDENCY_GLOBALS = Object.freeze({
|
|
77
77
|
React: 'react',
|
|
78
|
+
ReactDOM: 'react-dom',
|
|
78
79
|
antd: 'antd',
|
|
79
80
|
icons: '@ant-design/icons',
|
|
80
81
|
ahooks: 'ahooks',
|
|
@@ -88,6 +89,12 @@ const MANUAL_DEPENDENCY_GLOBALS = Object.freeze({
|
|
|
88
89
|
YidaMarkdown: 'yida-plugin-markdown',
|
|
89
90
|
});
|
|
90
91
|
|
|
92
|
+
const DEPENDENCY_RUNTIME_ALIASES = Object.freeze(Object.assign(
|
|
93
|
+
{},
|
|
94
|
+
Object.fromEntries(Object.entries(MODULE_ALIAS_MAP).map(([packageName, alias]) => [alias, packageName])),
|
|
95
|
+
{ LucideReact: 'lucide-react' }
|
|
96
|
+
));
|
|
97
|
+
|
|
91
98
|
const BARE_DEPENDENCY_GLOBALS = Object.freeze({
|
|
92
99
|
antd: {
|
|
93
100
|
packageName: 'antd',
|
|
@@ -209,6 +216,141 @@ function assertNoManualDependencyGlobals(source, options = {}) {
|
|
|
209
216
|
);
|
|
210
217
|
}
|
|
211
218
|
|
|
219
|
+
function findSelfReferentialDependencyBindingIssues(source) {
|
|
220
|
+
const issues = [];
|
|
221
|
+
const dependencyNames = new Set(Object.keys(MANUAL_DEPENDENCY_GLOBALS));
|
|
222
|
+
|
|
223
|
+
const guardPlugin = () => ({
|
|
224
|
+
name: 'yida-self-referential-dependency-binding-guard',
|
|
225
|
+
visitor: {
|
|
226
|
+
VariableDeclarator(path) {
|
|
227
|
+
if (!path.get('id').isIdentifier() || !path.node.init) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const name = path.node.id.name;
|
|
231
|
+
if (!dependencyNames.has(name)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const binding = path.scope.getBinding(name);
|
|
235
|
+
let selfReference = null;
|
|
236
|
+
const initPath = path.get('init');
|
|
237
|
+
if (
|
|
238
|
+
initPath.isIdentifier({ name })
|
|
239
|
+
&& initPath.scope.getBinding(name) === binding
|
|
240
|
+
) {
|
|
241
|
+
selfReference = initPath.node;
|
|
242
|
+
}
|
|
243
|
+
initPath.traverse({
|
|
244
|
+
ReferencedIdentifier(referencePath) {
|
|
245
|
+
if (
|
|
246
|
+
!selfReference
|
|
247
|
+
&& referencePath.node.name === name
|
|
248
|
+
&& referencePath.scope.getBinding(name) === binding
|
|
249
|
+
) {
|
|
250
|
+
selfReference = referencePath.node;
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
if (selfReference) {
|
|
255
|
+
issues.push({
|
|
256
|
+
name,
|
|
257
|
+
packageName: MANUAL_DEPENDENCY_GLOBALS[name],
|
|
258
|
+
line: selfReference.loc && selfReference.loc.start ? selfReference.loc.start.line : 0,
|
|
259
|
+
column: selfReference.loc && selfReference.loc.start ? selfReference.loc.start.column : 0,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
Babel.transform(source, {
|
|
267
|
+
filename: 'canvas.tsx',
|
|
268
|
+
presets: [
|
|
269
|
+
['typescript', { allExtensions: true, isTSX: true, allowDeclareFields: true }],
|
|
270
|
+
['react', { runtime: 'classic' }],
|
|
271
|
+
],
|
|
272
|
+
plugins: [guardPlugin],
|
|
273
|
+
sourceType: 'module',
|
|
274
|
+
compact: false,
|
|
275
|
+
babelrc: false,
|
|
276
|
+
configFile: false,
|
|
277
|
+
});
|
|
278
|
+
return issues;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function assertNoSelfReferentialDependencyBindings(source, options = {}) {
|
|
282
|
+
const issues = findSelfReferentialDependencyBindingIssues(source);
|
|
283
|
+
if (issues.length === 0) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const firstIssue = issues[0];
|
|
287
|
+
throw new CliError(
|
|
288
|
+
`Code Canvas 依赖绑定 ${firstIssue.name} 不能引用自身。请从 "${firstIssue.packageName}" 使用标准 import 引入。`,
|
|
289
|
+
{
|
|
290
|
+
code: 'OPENYIDA_CANVAS_SELF_REFERENTIAL_DEPENDENCY_BINDING',
|
|
291
|
+
details: {
|
|
292
|
+
stage: 'canvas_compile',
|
|
293
|
+
sourcePath: options.sourcePath || '',
|
|
294
|
+
line: firstIssue.line,
|
|
295
|
+
column: firstIssue.column,
|
|
296
|
+
globalName: firstIssue.name,
|
|
297
|
+
packageName: firstIssue.packageName,
|
|
298
|
+
},
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function findDependencyManifestIssues(runtimeCode, importedModules) {
|
|
304
|
+
const importedBaseNames = new Set((importedModules || []).map(packageBaseName));
|
|
305
|
+
const observedAliases = new Set();
|
|
306
|
+
const guardPlugin = ({ types: types }) => ({
|
|
307
|
+
name: 'yida-runtime-dependency-manifest-guard',
|
|
308
|
+
visitor: {
|
|
309
|
+
MemberExpression(path) {
|
|
310
|
+
const node = path.node;
|
|
311
|
+
if (!types.isIdentifier(node.object, { name: 'window' })) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const alias = node.computed && types.isStringLiteral(node.property)
|
|
315
|
+
? node.property.value
|
|
316
|
+
: (!node.computed && types.isIdentifier(node.property) ? node.property.name : '');
|
|
317
|
+
if (DEPENDENCY_RUNTIME_ALIASES[alias]) {
|
|
318
|
+
observedAliases.add(alias);
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
Babel.transform(runtimeCode, {
|
|
324
|
+
filename: 'canvas-runtime.js',
|
|
325
|
+
plugins: [guardPlugin],
|
|
326
|
+
sourceType: 'script',
|
|
327
|
+
compact: false,
|
|
328
|
+
babelrc: false,
|
|
329
|
+
configFile: false,
|
|
330
|
+
});
|
|
331
|
+
return Array.from(observedAliases)
|
|
332
|
+
.filter(alias => !importedBaseNames.has(packageBaseName(DEPENDENCY_RUNTIME_ALIASES[alias])))
|
|
333
|
+
.map(alias => ({ alias, packageName: DEPENDENCY_RUNTIME_ALIASES[alias] }));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function assertDependencyManifestConsistent(runtimeCode, importedModules, options = {}) {
|
|
337
|
+
const issues = findDependencyManifestIssues(runtimeCode, importedModules);
|
|
338
|
+
if (issues.length === 0) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
throw new CliError(
|
|
342
|
+
`Code Canvas 运行时代码依赖 ${issues[0].alias},但 importedModules 缺少 "${issues[0].packageName}"。`,
|
|
343
|
+
{
|
|
344
|
+
code: 'OPENYIDA_CANVAS_DEPENDENCY_MANIFEST_MISMATCH',
|
|
345
|
+
details: {
|
|
346
|
+
stage: 'canvas_compile',
|
|
347
|
+
sourcePath: options.sourcePath || '',
|
|
348
|
+
issues,
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
212
354
|
function findBareDependencyGlobalIssues(source) {
|
|
213
355
|
const issues = [];
|
|
214
356
|
const names = new Set(Object.keys(BARE_DEPENDENCY_GLOBALS));
|
|
@@ -790,6 +932,7 @@ function compileCanvasLocal(source, options = {}) {
|
|
|
790
932
|
});
|
|
791
933
|
}
|
|
792
934
|
assertNoUnavailableCanvasInstanceApis(source, options);
|
|
935
|
+
assertNoSelfReferentialDependencyBindings(source, options);
|
|
793
936
|
assertNoBareDependencyGlobals(source, options);
|
|
794
937
|
assertNoManualDependencyGlobals(source, options);
|
|
795
938
|
assertInlineCssStructure(source, options);
|
|
@@ -847,6 +990,7 @@ function compileCanvasLocal(source, options = {}) {
|
|
|
847
990
|
code: 'OPENYIDA_CANVAS_SOURCE_EMOJI_FORBIDDEN',
|
|
848
991
|
});
|
|
849
992
|
assertCanvasRuntimeParseable(runtimeCode, options);
|
|
993
|
+
assertDependencyManifestConsistent(runtimeCode, importedModules, options);
|
|
850
994
|
return {
|
|
851
995
|
runtimeCode,
|
|
852
996
|
importedModules: JSON.stringify(importedModules),
|
|
@@ -913,5 +1057,8 @@ module.exports = {
|
|
|
913
1057
|
shouldAllowUnsupportedBareImports,
|
|
914
1058
|
assertCanvasRuntimeParseable,
|
|
915
1059
|
findBareDependencyGlobalIssues,
|
|
1060
|
+
findSelfReferentialDependencyBindingIssues,
|
|
1061
|
+
findDependencyManifestIssues,
|
|
1062
|
+
assertDependencyManifestConsistent,
|
|
916
1063
|
MODULE_ALIAS_MAP,
|
|
917
1064
|
};
|
package/lib/app/page-linter.js
CHANGED
|
@@ -877,6 +877,30 @@ function lintYidaSource(sourceCode, filePath) {
|
|
|
877
877
|
const lines = sourceCode.split('\n');
|
|
878
878
|
const disableMap = buildLineDisableMap(lines);
|
|
879
879
|
|
|
880
|
+
lines.forEach((line, index) => {
|
|
881
|
+
const lineNumber = index + 1;
|
|
882
|
+
const uncommented = stripCommentsOnly(line);
|
|
883
|
+
if (/\b(?:App\.)?getSystemToken(?:\.json)?\b/i.test(uncommented)
|
|
884
|
+
|| /\bsystemToken\b\s*[:=]/i.test(uncommented)) {
|
|
885
|
+
pushIssue(
|
|
886
|
+
errors,
|
|
887
|
+
lineNumber,
|
|
888
|
+
'system-token-frontend-forbidden',
|
|
889
|
+
t('publish.lint_system_token_frontend_forbidden'),
|
|
890
|
+
disableMap
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
if (/(?:\bconnectorId\b|['"]connectorId['"])\s*:\s*(?:(['"])\d+\1|\d+\b)/.test(uncommented)) {
|
|
894
|
+
pushIssue(
|
|
895
|
+
errors,
|
|
896
|
+
lineNumber,
|
|
897
|
+
'connector-runtime-name-required',
|
|
898
|
+
t('publish.lint_connector_runtime_name_required'),
|
|
899
|
+
disableMap
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
|
|
880
904
|
findEmojiInArtifactName(filePath, {
|
|
881
905
|
artifact: filePath || 'Yida page source',
|
|
882
906
|
maxIssues: 20,
|
package/lib/app/publish.js
CHANGED
|
@@ -747,7 +747,20 @@ async function runMain(argv) {
|
|
|
747
747
|
['URL', pageUrl],
|
|
748
748
|
]);
|
|
749
749
|
console.log(JSON.stringify(withBrowserHandoff(
|
|
750
|
-
{
|
|
750
|
+
{
|
|
751
|
+
success: true,
|
|
752
|
+
appType,
|
|
753
|
+
formUuid: savedFormUuid,
|
|
754
|
+
version,
|
|
755
|
+
url: pageUrl,
|
|
756
|
+
publishMode,
|
|
757
|
+
publishReadbackVerified: !!(healthCheckResult && healthCheckResult.ok === true),
|
|
758
|
+
runtimeSmokeVerified: false,
|
|
759
|
+
runtimeSmokeStatus: 'not_checked',
|
|
760
|
+
healthCheck: healthCheckResult,
|
|
761
|
+
navOrder: navOrderResult,
|
|
762
|
+
navOrderWarning,
|
|
763
|
+
},
|
|
751
764
|
pageUrl,
|
|
752
765
|
{ stage: 'publish_page_success', title: savedFormUuid },
|
|
753
766
|
browserOpenMode
|