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.
Files changed (63) hide show
  1. package/README.md +7 -6
  2. package/bin/yida.js +1 -1
  3. package/lib/app/app-list.js +78 -53
  4. package/lib/app/canvas-compile.js +147 -0
  5. package/lib/app/page-linter.js +24 -0
  6. package/lib/app/publish.js +14 -1
  7. package/lib/app/services/canvas-page-schema-builder.js +175 -0
  8. package/lib/app/update-app.js +8 -2
  9. package/lib/bridge/bridge.js +15 -3
  10. package/lib/connector/api.js +3 -1
  11. package/lib/connector/connector-add-action.js +2 -0
  12. package/lib/connector/connector-create-connection.js +119 -16
  13. package/lib/connector/connector-create.js +40 -32
  14. package/lib/connector/connector-detail.js +2 -1
  15. package/lib/connector/connector-gen-template.js +5 -17
  16. package/lib/connector/connector-list-connections.js +5 -4
  17. package/lib/connector/connector-test.js +48 -2
  18. package/lib/connector/contract.js +132 -10
  19. package/lib/connector/operation-normalizer.js +20 -4
  20. package/lib/connector/yida-system-token.js +280 -0
  21. package/lib/core/agent-capabilities.js +24 -2
  22. package/lib/core/command-manifest.js +8 -8
  23. package/lib/core/locales/en.js +43 -2
  24. package/lib/core/locales/zh.js +43 -2
  25. package/lib/core/redact.js +4 -3
  26. package/lib/integration/connector-presets.js +95 -9
  27. package/lib/integration/integration-connector-schema.js +79 -15
  28. package/lib/integration/integration-create.js +131 -5
  29. package/lib/integration/integration-readback.js +136 -0
  30. package/lib/process/configure-process.js +69 -16
  31. package/package.json +1 -1
  32. package/scripts/postinstall.js +1 -1
  33. package/yida-skills/SKILL.md +3 -1
  34. package/yida-skills/references/resource-context.md +1 -1
  35. package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +1 -1
  36. package/yida-skills/skills/yida-app/workflow/step-2-design.md +3 -1
  37. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +4 -3
  38. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +3 -1
  39. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +2 -0
  40. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +19 -3
  41. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +25 -22
  42. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +19 -18
  43. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +41 -37
  44. package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +2 -2
  45. package/yida-skills/skills/yida-canvas-custom-page/references/native-components-bridge.md +1 -1
  46. package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +3 -3
  47. package/yida-skills/skills/yida-canvas-data-binding/references/connector-binding.md +23 -0
  48. package/yida-skills/skills/yida-connector/SKILL.md +15 -8
  49. package/yida-skills/skills/yida-connector/examples/operations-search-formdata-v2.json +2 -2
  50. package/yida-skills/skills/yida-connector/references/connector-action-format.md +9 -0
  51. package/yida-skills/skills/yida-connector/templates/api-document-template.md +6 -19
  52. package/yida-skills/skills/yida-data-management/SKILL.md +8 -4
  53. package/yida-skills/skills/yida-design/SKILL.md +1 -1
  54. package/yida-skills/skills/yida-design/workflow/output-design.md +2 -0
  55. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +4 -3
  56. package/yida-skills/skills/yida-dingtalk-openapi/SKILL.md +75 -0
  57. package/yida-skills/skills/yida-dingtalk-openapi/references/api-contract.md +98 -0
  58. package/yida-skills/skills/yida-dingtalk-openapi/references/connector-mapping.md +27 -0
  59. package/yida-skills/skills/yida-integration/SKILL.md +29 -0
  60. package/yida-skills/skills/yida-publish-page/SKILL.md +3 -2
  61. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +2 -1
  62. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +3 -0
  63. 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]` | List my Yida apps |
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>` | Create an auth connection |
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; grant that permission in DingTalk developer console API permissions and publish the DingTalk app. Yida app code and app secret are available under app settings > deployment/maintenance.
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 [输出路径] 生成接口文档模板
@@ -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 { throwCommandError } = require('../core/command-errors');
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
- if (!firstResult.success) {
45
- throw new Error(firstResult.errorMsg || '查询应用列表失败');
46
- }
47
-
48
- const { data: firstPageData, totalCount } = firstResult.content;
49
- const allApps = [...firstPageData];
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
- const remainingResults = await Promise.all(remainingFetches);
59
- for (const result of remainingResults) {
60
- // 如果后续翻页也出现登录态问题,同样返回标记对象
61
- if (result.__needLogin) {
62
- return result;
63
- }
64
- if (result.success && result.content && result.content.data) {
65
- allApps.push(...result.content.data);
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 allApps;
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
- 'Usage: openyida app-list [--size N]',
93
+ t('app_list.usage'),
90
94
  '',
91
- 'Options:',
92
- ' --size N Page size used when fetching apps, default: 20',
93
- ' --help, -h Show this help',
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 pageSizeIndex = args.indexOf('--size');
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 apps;
118
+ let result;
116
119
  try {
117
- apps = await fetchAllApps(authRef, pageSize);
120
+ result = await fetchAppListPage(
121
+ authRef,
122
+ options.pageIndex,
123
+ options.pageSize,
124
+ APP_TYPES[options.type]
125
+ );
118
126
  } catch (err) {
119
- throwCommandError(`查询应用列表失败:${err.message}`);
127
+ throwCommandError(t('app_list.query_failed', err.message));
120
128
  }
121
129
 
122
130
  // 如果重试后仍然返回登录失效标记,说明登录态确实不可用
123
- if (apps && apps.__needLogin) {
124
- throwCommandError('登录态已失效,请重新登录', { code: 'CLI_AUTH_REQUIRED' });
131
+ if (result && result.__needLogin) {
132
+ throwCommandError(t('app_list.auth_required'), { code: 'CLI_AUTH_REQUIRED' });
125
133
  }
126
134
 
127
- if (!apps || apps.length === 0) {
128
- console.log('暂无应用');
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(`共找到 ${c.cyan}${formattedApps.length}${c.reset} 个应用`);
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
  };
@@ -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,
@@ -747,7 +747,20 @@ async function runMain(argv) {
747
747
  ['URL', pageUrl],
748
748
  ]);
749
749
  console.log(JSON.stringify(withBrowserHandoff(
750
- { success: true, appType, formUuid: savedFormUuid, version, url: pageUrl, publishMode, healthCheck: healthCheckResult, navOrder: navOrderResult, navOrderWarning },
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