openyida 2026.7.19 → 2026.7.21

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 (139) hide show
  1. package/README.md +14 -0
  2. package/bin/yida.js +107 -18
  3. package/lib/app/create-app.js +12 -1
  4. package/lib/app/create-form/args.js +13 -0
  5. package/lib/app/create-form.js +154 -67
  6. package/lib/app/create-page.js +45 -9
  7. package/lib/app/get-schema.js +65 -14
  8. package/lib/app/import-app.js +92 -41
  9. package/lib/app/publish.js +289 -556
  10. package/lib/app/schema-field-resolution.js +370 -0
  11. package/lib/app/services/app-reader.js +85 -0
  12. package/lib/app/services/app-service.js +235 -0
  13. package/lib/app/services/canvas-page-compiler.js +91 -0
  14. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  15. package/lib/app/services/field-bindings.js +68 -0
  16. package/lib/app/services/form-compiler.js +1922 -0
  17. package/lib/app/services/form-mode-service.js +182 -0
  18. package/lib/app/services/form-schema-patcher.js +333 -0
  19. package/lib/app/services/form-schema-reader.js +59 -0
  20. package/lib/app/services/form-service.js +379 -0
  21. package/lib/app/services/form-validation.js +562 -0
  22. package/lib/app/services/native-page-compiler.js +92 -0
  23. package/lib/app/services/native-page-schema-builder.js +428 -0
  24. package/lib/app/services/page-resource-service.js +185 -0
  25. package/lib/app/update-app.js +18 -6
  26. package/lib/auth/token-auth.js +34 -3
  27. package/lib/auth/token-store.js +22 -4
  28. package/lib/bridge/bridge.js +37 -5
  29. package/lib/core/agent-capabilities.js +5 -3
  30. package/lib/core/command-manifest.js +40 -0
  31. package/lib/core/doctor.js +15 -4
  32. package/lib/core/env-cmd.js +1 -1
  33. package/lib/core/env.js +7 -6
  34. package/lib/core/legacy-schema-guard.js +408 -0
  35. package/lib/core/locales/en.js +8 -46
  36. package/lib/core/locales/zh.js +8 -46
  37. package/lib/core/sample.js +1 -1
  38. package/lib/core/utils.js +334 -66
  39. package/lib/core/yida-client.js +104 -3
  40. package/lib/formula/evaluate.js +8 -66
  41. package/lib/formula/field-refs.js +83 -0
  42. package/lib/process/configure-process.js +44 -1992
  43. package/lib/process/create-process.js +54 -87
  44. package/lib/process/services/process-compiler.js +2056 -0
  45. package/lib/process/services/process-reader.js +232 -0
  46. package/lib/process/services/process-resource-service.js +571 -0
  47. package/lib/process/services/process-service.js +122 -0
  48. package/lib/process/services/process-stage-checkpoint.js +246 -0
  49. package/lib/report/append.js +7 -26
  50. package/lib/report/http.js +31 -5
  51. package/lib/report/index.js +10 -2
  52. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  53. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  54. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  55. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
  56. package/lib/schema/adapters/app-adapter.js +210 -0
  57. package/lib/schema/adapters/form-adapter.js +1362 -0
  58. package/lib/schema/adapters/keys.js +19 -0
  59. package/lib/schema/adapters/page-adapter.js +566 -0
  60. package/lib/schema/adapters/process-adapter.js +519 -0
  61. package/lib/schema/applier.js +1878 -0
  62. package/lib/schema/apply-store.js +1083 -0
  63. package/lib/schema/command.js +645 -0
  64. package/lib/schema/dependency-graph.js +83 -0
  65. package/lib/schema/errors.js +481 -0
  66. package/lib/schema/hash.js +9 -0
  67. package/lib/schema/manifest-limits.js +177 -0
  68. package/lib/schema/manifest-loader.js +309 -0
  69. package/lib/schema/manifest-schema-v1.json +193 -0
  70. package/lib/schema/normalize-manifest.js +281 -0
  71. package/lib/schema/page-canvas-foundation.js +447 -0
  72. package/lib/schema/page-data-source-builder.js +561 -0
  73. package/lib/schema/page-foundation.js +556 -0
  74. package/lib/schema/page-source-loader.js +314 -0
  75. package/lib/schema/planner.js +691 -0
  76. package/lib/schema/remote-dispatch-boundary.js +39 -0
  77. package/lib/schema/remote-missing.js +21 -0
  78. package/lib/schema/remote-reader.js +189 -0
  79. package/lib/schema/resource-registry.js +157 -0
  80. package/lib/schema/server-revision.js +71 -0
  81. package/lib/schema/sort.js +28 -0
  82. package/lib/schema/state-store.js +589 -0
  83. package/package.json +2 -2
  84. package/scripts/postinstall.js +8 -12
  85. package/scripts/validate-package-size.js +1 -1
  86. package/yida-skills/SKILL.md +114 -29
  87. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  88. package/yida-skills/references/setup-and-env.md +59 -59
  89. package/yida-skills/references/task-retrospective.md +6 -6
  90. package/yida-skills/skills/yida-app/SKILL.md +60 -14
  91. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  92. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  93. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
  94. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  95. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  96. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  97. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  98. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  99. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  101. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  102. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  103. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  104. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  105. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  106. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  107. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  108. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  109. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  110. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  111. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  112. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  113. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  114. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  115. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  116. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  117. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  118. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  119. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  120. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  121. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  122. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  123. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  124. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  125. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  126. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  127. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  128. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  129. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  130. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  131. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  132. package/yida-skills/skills/yida-report/SKILL.md +7 -1
  133. package/yida-skills/skills-index.json +14 -46
  134. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  135. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  136. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  137. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  138. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  139. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -11,8 +11,8 @@
11
11
  * 流程:
12
12
  * 1. 读取源文件,通过内置 babel-transform 编译 + UglifyJS 压缩
13
13
  * 2. 用代码动态构建 Schema,将 source/compiled 填入 actions.module
14
- * 3. 读取本地 token session 获取登录态;若未登录或 token 失效,则提示重新执行 openyida login
15
- * 4. 通过 HTTP POST 调用 saveFormSchema 接口发布 Schema
14
+ * 3. 读取 token session,在写前确认登录态
15
+ * 4. 通过 HTTP POST 调用 saveFormSchema,token 失效时尝试 token refresh
16
16
  */
17
17
 
18
18
  const fs = require('fs');
@@ -22,22 +22,38 @@ const http = require('http');
22
22
  const querystring = require('querystring');
23
23
  const {
24
24
  findProjectRoot,
25
- loadAuthData,
26
- triggerLogin,
25
+ isLoginExpired,
26
+ isCsrfTokenExpired,
27
27
  httpPost,
28
28
  httpGet,
29
29
  requestWithAutoLogin,
30
30
  } = require('../core/utils');
31
+ const { createAuthRef, isTokenAuthRef } = require('../core/yida-client');
32
+ const { requireSchemaServerRevision } = require('../schema/server-revision');
31
33
  const { t } = require('../core/i18n');
32
34
  const { banner, step, label, success, fail, warn, info, error, result, usage, hint } = require('../core/chalk');
33
35
  const { compileSource } = require('./page-compiler');
34
36
  const { compileCanvas } = require('./canvas-compile');
35
- const { stripSubtableFieldPrefix } = require('../formula/evaluate');
36
37
  const { runLintCheck } = require('./page-linter');
37
38
  const { buildPageFile, shouldBuildPageSource } = require('./page-compat');
38
39
  const { warnLargePageSource } = require('./page-size-guard');
39
40
  const { fetchFormPageList } = require('./form-navigation');
40
41
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
42
+ const {
43
+ assertLegacyDirectWriteAllowed,
44
+ extractLegacyGuardArgs,
45
+ } = require('../core/legacy-schema-guard');
46
+ const {
47
+ buildDefaultPageDataSource,
48
+ buildNativePageSchemaContent,
49
+ countCustomPageDataSources,
50
+ extractPageDataSource,
51
+ extractSchemaContent,
52
+ mergePageDataSource,
53
+ } = require('./services/native-page-schema-builder');
54
+ const {
55
+ buildCanvasPageSchemaContent,
56
+ } = require('./services/canvas-page-schema-builder');
41
57
 
42
58
  // ── 配置读取 ──────────────────────────────────────────
43
59
  const CONFIG = fs.existsSync(path.resolve(findProjectRoot(), 'config.json')) ? JSON.parse(fs.readFileSync(path.resolve(findProjectRoot(), 'config.json'), 'utf-8')) : {};
@@ -76,13 +92,20 @@ function normalizePublishArgs(positionals) {
76
92
 
77
93
  function parseArgs(argv = process.argv.slice(2)) {
78
94
  const openOption = parseOpenOption(argv);
79
- const args = openOption.args;
95
+ const guardSplit = extractLegacyGuardArgs(openOption.args);
96
+ const args = guardSplit.args;
80
97
  const skipLint = args.includes('--skip-lint');
81
98
  const healthCheck = args.includes('--health-check') || args.includes('--check');
82
99
  const compat = args.includes('--compat') || args.includes('--modern');
83
100
  const force = args.includes('--force');
84
101
  const canvas = args.includes('--canvas');
85
- const filteredArgs = args.filter(arg => arg !== '--skip-lint' && arg !== '--health-check' && arg !== '--check' && arg !== '--compat' && arg !== '--modern' && arg !== '--force' && arg !== '--canvas');
102
+ const help = args.includes('--help') || args.includes('-h');
103
+ const filteredArgs = args.filter(arg => arg !== '--skip-lint' && arg !== '--health-check' && arg !== '--check' && arg !== '--compat' && arg !== '--modern' && arg !== '--force' && arg !== '--canvas' && arg !== '--help' && arg !== '-h');
104
+
105
+ if (help) {
106
+ usage(t('publish.usage'), t('publish.example'));
107
+ return { help: true };
108
+ }
86
109
 
87
110
  if (filteredArgs.length < 3) {
88
111
  usage(t('publish.usage'), t('publish.example'));
@@ -99,230 +122,23 @@ function parseArgs(argv = process.argv.slice(2)) {
99
122
  force,
100
123
  canvas,
101
124
  browserOpenMode: openOption.mode,
125
+ legacyGuardOptions: guardSplit.guardOptions,
102
126
  };
103
127
  }
104
128
 
105
- // ── 从登录态解析 baseUrl ─────────────────────────────
106
-
107
- function resolveBaseUrl(authData) {
108
- return (authData.base_url || DEFAULT_BASE_URL).replace(/\/+$/, '');
109
- }
110
-
111
- // ── ID 生成工具 ──────────────────────────────────────
112
-
113
- /**
114
- * 创建一个独立的 nodeId 生成器,每次调用 buildSchemaContent 时应创建新实例,
115
- * 避免模块级全局计数器在多次调用时累加导致 ID 不可预期。
116
- */
117
- function createNodeIdGenerator() {
118
- let counter = 1;
119
- return function nextNodeId() {
120
- return 'node_oc' + Date.now().toString(36) + (counter++).toString(36);
121
- };
122
- }
123
-
124
- function generateSuffix() {
125
- return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
126
- }
127
-
128
- // ── 2. 构建 Schema ──────────────────────────────────
129
-
130
- function getGlobalDataSourceFitConfig() {
131
- const fitCompiled = "'use strict';\n\nvar __preParser__ = function fit(response) {\n var content = response.content !== undefined ? response.content : response;\n var error = {\n message: response.errorMsg || response.errors && response.errors[0] && response.errors[0].msg || response.content || '远程数据源请求出错,success is false'\n };\n var success = true;\n if (response.success !== undefined) {\n success = response.success;\n } else if (response.hasError !== undefined) {\n success = !response.hasError;\n }\n return {\n content: content,\n success: success,\n error: error\n };\n};";
132
- const fitSource = "function fit(response) {\r\n const content = (response.content !== undefined) ? response.content : response;\r\n const error = {\r\n message: response.errorMsg ||\r\n (response.errors && response.errors[0] && response.errors[0].msg) ||\r\n response.content || '远程数据源请求出错,success is false',\r\n };\r\n let success = true;\r\n if (response.success !== undefined) {\r\n success = response.success;\r\n } else if (response.hasError !== undefined) {\r\n success = !response.hasError;\r\n }\r\n return {\r\n content,\r\n success,\r\n error,\r\n };\r\n}";
133
-
134
- return {
135
- fit: {
136
- compiled: fitCompiled,
137
- source: fitSource,
138
- type: 'js',
139
- error: {},
140
- },
141
- };
142
- }
143
-
144
- function buildDefaultPageDataSource(formUuid) {
145
- const urlParamsDataSource = {
146
- id: 'VCB660714833IBHEOXK376TA7XJH2AXUWR8MMW',
147
- name: 'urlParams',
148
- description: '当前页面地址的参数:如 aliwork.com/APP_XXX/workbench?id=1&name=宜搭,可通过 this.state.urlParams.name 获取到宜搭',
149
- formUuid: formUuid,
150
- protocal: 'URI',
151
- isReadonly: true,
152
- };
153
- const timestampDataSource = {
154
- id: '',
155
- name: 'timestamp',
156
- description: '',
157
- formUuid: formUuid,
158
- protocal: 'VALUE',
159
- initialData: '',
160
- };
161
-
162
- return {
163
- offline: [],
164
- globalConfig: getGlobalDataSourceFitConfig(),
165
- online: [urlParamsDataSource, timestampDataSource],
166
- list: [urlParamsDataSource, timestampDataSource],
167
- sync: true,
168
- };
169
- }
170
-
171
- function cloneJson(value) {
172
- if (value === undefined || value === null) {
173
- return value;
174
- }
175
- return JSON.parse(JSON.stringify(value));
176
- }
177
-
178
- function getDataSourceIdentity(item) {
179
- if (!item || typeof item !== 'object') {
180
- return '';
181
- }
182
- if (isBuiltInPageDataSource(item)) {
183
- return 'builtin:' + item.name;
184
- }
185
- if (item.id) {
186
- return 'id:' + item.id;
187
- }
188
- if (item.name && item.protocal) {
189
- return 'name:' + item.name + '|protocal:' + item.protocal;
190
- }
191
- if (item.name) {
192
- return 'name:' + item.name;
193
- }
194
- return JSON.stringify(item);
195
- }
196
-
197
- function mergeDataSourceArray(existingItems, generatedItems) {
198
- const merged = Array.isArray(existingItems) ? cloneJson(existingItems) : [];
199
- const seen = new Set(merged.map(getDataSourceIdentity).filter(Boolean));
200
-
201
- (Array.isArray(generatedItems) ? generatedItems : []).forEach((item) => {
202
- const identity = getDataSourceIdentity(item);
203
- if (!identity || !seen.has(identity)) {
204
- merged.push(cloneJson(item));
205
- if (identity) {
206
- seen.add(identity);
207
- }
208
- }
209
- });
210
-
211
- return merged;
212
- }
213
-
214
- function mergePageDataSource(existingDataSource, generatedDataSource) {
215
- if (!existingDataSource || typeof existingDataSource !== 'object') {
216
- return cloneJson(generatedDataSource);
217
- }
218
-
219
- const existing = cloneJson(existingDataSource);
220
- const generated = cloneJson(generatedDataSource || {});
221
- const merged = Object.assign({}, generated, existing);
222
-
223
- merged.offline = mergeDataSourceArray(existing.offline, generated.offline);
224
- merged.online = mergeDataSourceArray(existing.online, generated.online);
225
- merged.list = mergeDataSourceArray(existing.list, generated.list);
226
- merged.globalConfig = Object.assign(
227
- {},
228
- generated.globalConfig || {},
229
- existing.globalConfig || {}
230
- );
231
- merged.sync = existing.sync !== undefined ? existing.sync : generated.sync;
232
-
233
- return merged;
234
- }
235
-
236
- function extractSchemaContent(schemaResult) {
237
- if (!schemaResult) {
238
- return null;
239
- }
240
-
241
- let content = schemaResult.content !== undefined ? schemaResult.content : schemaResult;
242
- if (typeof content === 'string') {
243
- try {
244
- content = JSON.parse(content);
245
- } catch {
246
- return null;
247
- }
248
- }
249
-
250
- if (content && typeof content === 'object' && content.pages) {
251
- return content;
252
- }
253
- if (schemaResult.pages) {
254
- return schemaResult;
255
- }
256
- return null;
257
- }
258
-
259
- function extractPageDataSource(schema) {
260
- if (!schema || !Array.isArray(schema.pages)) {
261
- return null;
262
- }
263
-
264
- function findPageDataSource(node) {
265
- if (!node || typeof node !== 'object') {
266
- return null;
267
- }
268
- if (node.componentName === 'Page' && node.dataSource) {
269
- return node.dataSource;
270
- }
271
- const children = Array.isArray(node.children) ? node.children : [];
272
- for (const child of children) {
273
- const dataSource = findPageDataSource(child);
274
- if (dataSource) {
275
- return dataSource;
276
- }
277
- }
278
- return null;
279
- }
280
-
281
- for (const page of schema.pages) {
282
- const tree = page && Array.isArray(page.componentsTree) ? page.componentsTree : [];
283
- for (const component of tree) {
284
- const dataSource = findPageDataSource(component);
285
- if (dataSource) {
286
- return dataSource;
287
- }
288
- }
289
- }
290
- return null;
291
- }
292
-
293
- function isBuiltInPageDataSource(item) {
294
- return !!(item && (item.name === 'urlParams' || item.name === 'timestamp'));
295
- }
296
-
297
- function countCustomPageDataSources(dataSource) {
298
- if (!dataSource || typeof dataSource !== 'object') {
299
- return 0;
300
- }
301
-
302
- const identities = new Set();
303
- ['offline', 'online', 'list'].forEach((key) => {
304
- (Array.isArray(dataSource[key]) ? dataSource[key] : []).forEach((item) => {
305
- if (!isBuiltInPageDataSource(item)) {
306
- const identity = getDataSourceIdentity(item);
307
- if (identity) {
308
- identities.add(identity);
309
- }
310
- }
311
- });
312
- });
313
- return identities.size;
314
- }
129
+ // ── 2. 读取并构建 Schema ────────────────────────────
315
130
 
316
131
  async function fetchExistingSchemaContent(appType, formUuid, authRef) {
317
132
  const result = await requestWithAutoLogin((auth) => {
318
133
  return httpGet(
319
134
  auth.baseUrl,
320
135
  `/alibaba/web/${appType}/${PREFIX}/query/formdesign/getFormSchema.json`,
321
- { formUuid, schemaVersion: SCHEMA_VERSION }
136
+ { formUuid, schemaVersion: SCHEMA_VERSION },
137
+ auth.cookies
322
138
  );
323
139
  }, authRef);
324
140
 
325
- if (!result || result.success === false || result.__needLogin) {
141
+ if (!result || result.success === false || result.__needLogin || result.__csrfExpired) {
326
142
  const errorMsg = result ? result.errorMsg || t('common.unknown_error') : t('common.request_failed');
327
143
  throw new Error(errorMsg);
328
144
  }
@@ -332,13 +148,16 @@ async function fetchExistingSchemaContent(appType, formUuid, authRef) {
332
148
  throw new Error(t('create_form.schema_parse_failed'));
333
149
  }
334
150
 
335
- return schema;
151
+ return {
152
+ schema,
153
+ serverRevision: requireSchemaServerRevision(result),
154
+ };
336
155
  }
337
156
 
338
157
  async function readExistingPageDataSource(appType, formUuid, authRef) {
339
158
  info(t('publish.data_source_fetching'));
340
- const existingSchema = await fetchExistingSchemaContent(appType, formUuid, authRef);
341
- const dataSource = extractPageDataSource(existingSchema);
159
+ const existing = await fetchExistingSchemaContent(appType, formUuid, authRef);
160
+ const dataSource = extractPageDataSource(existing.schema);
342
161
  const customCount = countCustomPageDataSources(dataSource);
343
162
 
344
163
  if (customCount > 0) {
@@ -347,338 +166,249 @@ async function readExistingPageDataSource(appType, formUuid, authRef) {
347
166
  info(t('publish.data_source_none'));
348
167
  }
349
168
 
350
- return dataSource;
169
+ return { dataSource, serverRevision: existing.serverRevision };
351
170
  }
352
171
 
353
172
  function buildSchemaContent(sourceCode, compiledCode, formUuid, options = {}) {
354
- info(t('publish.building_schema'));
355
-
356
- const sourceFix = stripSubtableFieldPrefix(sourceCode);
357
- const compiledFix = stripSubtableFieldPrefix(compiledCode);
358
- sourceCode = sourceFix.value;
359
- compiledCode = compiledFix.value;
360
- const fixedRefs = sourceFix.count + compiledFix.count;
361
- if (fixedRefs > 0) {
362
- info(t('publish.formula_prefix_fixed', fixedRefs));
363
- }
364
-
365
- const nextNodeId = createNodeIdGenerator();
366
-
367
- // 构造函数代码(固定模板)
368
- 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}";
369
- const pageDataSource = mergePageDataSource(
370
- options.existingDataSource,
371
- buildDefaultPageDataSource(formUuid)
372
- );
373
-
374
- const schema = {
375
- schemaType: 'superform',
376
- schemaVersion: '5.0',
377
- pages: [
378
- {
379
- utils: [
380
- {
381
- name: 'legaoBuiltin',
382
- type: 'npm',
383
- content: {
384
- package: '@ali/vu-legao-builtin',
385
- version: '3.0.0',
386
- exportName: 'legaoBuiltin',
387
- },
388
- },
389
- {
390
- name: 'yidaPlugin',
391
- type: 'npm',
392
- content: {
393
- package: '@ali/vu-yida-plugin',
394
- version: '1.1.0',
395
- exportName: 'yidaPlugin',
396
- },
397
- },
398
- ],
399
- componentsMap: [
400
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootHeader' },
401
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'Jsx' },
402
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootContent' },
403
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootFooter' },
404
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'Page' },
405
- ],
406
- componentsTree: [
407
- {
408
- componentName: 'Page',
409
- id: nextNodeId(),
410
- props: {
411
- contentBgColor: 'white',
412
- pageStyle: { backgroundColor: '#f2f3f5' },
413
- contentMargin: '0',
414
- contentPadding: '0',
415
- showTitle: false,
416
- contentPaddingMobile: '0',
417
- templateVersion: '1.0.0',
418
- contentMarginMobile: '0',
419
- className: 'page_' + generateSuffix(),
420
- contentBgColorMobile: 'white',
421
- },
422
- condition: true,
423
- 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}',
424
- methods: {
425
- __initMethods__: {
426
- type: 'js',
427
- source: 'function (exports, module) { /*set actions code here*/ }',
428
- compiled: 'function (exports, module) { /*set actions code here*/ }',
429
- },
430
- },
431
- dataSource: pageDataSource,
432
- lifeCycles: {
433
- constructor: {
434
- type: 'js',
435
- compiled: constructorCode,
436
- source: constructorCode,
437
- },
438
- componentWillUnmount: {
439
- name: 'didUnmount',
440
- id: 'didUnmount',
441
- type: 'actionRef',
442
- params: {},
443
- },
444
- componentDidMount: {
445
- name: 'didMount',
446
- id: 'didMount',
447
- params: {},
448
- type: 'actionRef',
449
- },
450
- },
451
- hidden: false,
452
- title: '',
453
- isLocked: false,
454
- conditionGroup: '',
455
- children: [
456
- {
457
- componentName: 'RootHeader',
458
- id: nextNodeId(),
459
- props: {},
460
- condition: true,
461
- hidden: false,
462
- title: '',
463
- isLocked: false,
464
- conditionGroup: '',
465
- },
466
- {
467
- componentName: 'RootContent',
468
- id: nextNodeId(),
469
- props: {},
470
- condition: true,
471
- hidden: false,
472
- title: '',
473
- isLocked: false,
474
- conditionGroup: '',
475
- children: [
476
- {
477
- componentName: 'Jsx',
478
- id: nextNodeId(),
479
- props: {
480
- render: {
481
- type: 'js',
482
- compiled: 'function main(){\n \n "use strict";\n\nvar __compiledFunc__ = function render() {\n return this.renderJsx();\n};\n return __compiledFunc__.apply(this, arguments);\n }',
483
- source: 'function render() {\n return this.renderJsx();\n}',
484
- error: {},
485
- },
486
- __style__: {},
487
- fieldId: 'jsx_' + generateSuffix(),
488
- },
489
- condition: true,
490
- hidden: false,
491
- title: '',
492
- isLocked: false,
493
- conditionGroup: '',
494
- },
495
- ],
496
- },
497
- {
498
- componentName: 'RootFooter',
499
- id: nextNodeId(),
500
- props: {},
501
- condition: true,
502
- hidden: false,
503
- title: '',
504
- isLocked: false,
505
- conditionGroup: '',
506
- },
507
- ],
508
- },
509
- ],
510
- id: formUuid,
511
- connectComponent: [],
512
- },
513
- ],
514
- // ★ 核心:source 和 compiled 由编译结果动态填入
515
- actions: {
516
- module: {
517
- compiled: compiledCode,
518
- source: sourceCode,
519
- },
520
- type: 'FUNCTION',
521
- list: [
522
- { id: 'getCustomState', title: 'getCustomState' },
523
- { id: 'setCustomState', title: 'setCustomState' },
524
- { id: 'forceUpdate', title: 'forceUpdate' },
525
- { id: 'didMount', title: 'didMount' },
526
- { id: 'didUnmount', title: 'didUnmount' },
527
- { id: 'renderJsx', title: 'renderJsx' },
528
- ],
529
- },
530
- config: {
531
- connectComponent: [],
173
+ const logInfo = options.silent === true ? () => {} : info;
174
+ return buildNativePageSchemaContent(sourceCode, compiledCode, formUuid, {
175
+ ...options,
176
+ onBuildingSchema: () => logInfo(t('publish.building_schema')),
177
+ onFormulaPrefixFixed: fixedRefs => {
178
+ logInfo(t('publish.formula_prefix_fixed', fixedRefs));
532
179
  },
533
- };
534
-
535
- return JSON.stringify(schema);
180
+ });
536
181
  }
537
182
 
538
183
  // ── 2b. 构建 Code Canvas Schema ─────────────────────
539
- // 与普通自定义页面 Schema 的差异仅在 content JSON:Page.children 直接挂一个 YidaCodeCanvas
184
+ // native 的差异仅在 content JSON:Page.children 直接挂一个 YidaCodeCanvas
540
185
  // 节点,无 RootHeader/RootContent/Jsx/RootFooter;actions.module 为默认 didMount
541
186
  // 样板;Page.dataSource 精简(canvas 无原生数据桥)。
542
- // wire 层(path/query/body/headers)完全复用 sendSaveRequest,零改动。
543
-
544
- // canvas 页无用户页面方法,actions.module 只需注册一个空 didMount。
545
- const CANVAS_ACTIONS_SOURCE = 'export function didMount() {}';
546
- const CANVAS_ACTIONS_COMPILED = '"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.didMount=didMount;function didMount(){}';
187
+ // wire 层(path/query/body/headers)复用单次 save transport。
547
188
 
548
189
  function buildCanvasSchemaContent(sourceCode, runtimeCode, importedModules, formUuid) {
549
190
  info(t('publish.building_schema'));
191
+ return buildCanvasPageSchemaContent(sourceCode, runtimeCode, importedModules, formUuid);
192
+ }
193
+
194
+
195
+ // ── 4. 发送 saveFormSchema 请求 ──────────────────────
196
+
197
+ function sendSaveRequestOnce(csrfToken, cookies, schemaContent, baseUrl, appType, formUuid, serverRevision) {
198
+ if (
199
+ typeof csrfToken !== 'string' ||
200
+ csrfToken.length === 0 ||
201
+ !Array.isArray(cookies) ||
202
+ typeof baseUrl !== 'string' ||
203
+ baseUrl.length === 0
204
+ ) {
205
+ const authError = new Error('Publish Schema write authentication is not ready.');
206
+ authError.code = 'PUBLISH_SCHEMA_WRITE_PRECHECK_FAILED';
207
+ return Promise.reject(authError);
208
+ }
209
+ const gmtModified = requireSchemaServerRevision({ gmtModified: serverRevision });
210
+ return new Promise((resolve, reject) => {
211
+ const saveSchemaPath = `/alibaba/web/${appType}/${PREFIX}/query/formdesign/saveFormSchema.json?_stamp=${Date.now()}`;
212
+
213
+ const postData = querystring.stringify({
214
+ _csrf_token: csrfToken,
215
+ prefix: PREFIX,
216
+ content: schemaContent,
217
+ formUuid: formUuid,
218
+ gmtModified,
219
+ schemaVersion: SCHEMA_VERSION,
220
+ domainCode: DOMAIN_CODE,
221
+ importSchema: true,
222
+ });
223
+
224
+ const cookieHeader = cookies
225
+ .map((cookie) => `${cookie.name}=${cookie.value}`)
226
+ .join('; ');
227
+
228
+ const parsedUrl = new URL(baseUrl);
229
+ const isHttps = parsedUrl.protocol === 'https:';
230
+ const requestModule = isHttps ? https : http;
550
231
 
551
- const nextNodeId = createNodeIdGenerator();
552
- 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}";
553
-
554
- const schema = {
555
- schemaType: 'superform',
556
- schemaVersion: '5.0',
557
- pages: [
558
- {
559
- utils: [
560
- {
561
- name: 'legaoBuiltin',
562
- type: 'npm',
563
- content: { package: '@ali/vu-legao-builtin', version: '3.0.0', exportName: 'legaoBuiltin' },
564
- },
565
- {
566
- name: 'yidaPlugin',
567
- type: 'npm',
568
- content: { package: '@ali/vu-yida-plugin', version: '1.1.0', exportName: 'yidaPlugin' },
569
- },
570
- ],
571
- componentsMap: [
572
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'YidaCodeCanvas' },
573
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootHeader' },
574
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootContent' },
575
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootFooter' },
576
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'Page' },
577
- ],
578
- componentsTree: [
579
- {
580
- componentName: 'Page',
581
- id: nextNodeId(),
582
- props: {
583
- contentBgColor: 'white',
584
- pageStyle: { backgroundColor: '#f2f3f5' },
585
- contentMargin: '0',
586
- contentPadding: '0',
587
- showTitle: false,
588
- contentPaddingMobile: '0',
589
- templateVersion: '1.0.0',
590
- contentMarginMobile: '0',
591
- className: 'page_' + generateSuffix(),
592
- contentBgColorMobile: 'white',
593
- },
594
- condition: true,
595
- 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}',
596
- methods: {
597
- __initMethods__: {
598
- type: 'js',
599
- source: 'function (exports, module) { /*set actions code here*/ }',
600
- compiled: 'function (exports, module) { /*set actions code here*/ }',
601
- },
602
- },
603
- dataSource: {
604
- offline: [],
605
- globalConfig: getGlobalDataSourceFitConfig(),
606
- online: [],
607
- list: [],
608
- sync: true,
609
- },
610
- lifeCycles: {
611
- constructor: { type: 'js', compiled: constructorCode, source: constructorCode },
612
- componentWillUnmount: '',
613
- componentDidMount: { name: 'didMount', id: 'didMount', params: {}, type: 'actionRef' },
614
- },
615
- hidden: false,
616
- title: '',
617
- isLocked: false,
618
- conditionGroup: '',
619
- children: [
620
- {
621
- componentName: 'YidaCodeCanvas',
622
- id: nextNodeId(),
623
- props: {
624
- code: sourceCode,
625
- runtimeCode: runtimeCode,
626
- pageType: 'application',
627
- isWebCCompiled: true,
628
- componentProps: {},
629
- importedModules: importedModules || '',
630
- },
631
- condition: true,
632
- hidden: false,
633
- title: '',
634
- isLocked: false,
635
- conditionGroup: '',
636
- },
637
- ],
638
- },
639
- ],
640
- id: formUuid,
641
- connectComponent: [],
232
+ const requestOptions = {
233
+ hostname: parsedUrl.hostname,
234
+ port: parsedUrl.port || (isHttps ? 443 : 80),
235
+ path: saveSchemaPath,
236
+ method: 'POST',
237
+ headers: {
238
+ 'Content-Type': 'application/x-www-form-urlencoded',
239
+ 'Content-Length': Buffer.byteLength(postData),
240
+ Origin: baseUrl,
241
+ Referer: `${baseUrl}/`,
242
+ Cookie: cookieHeader,
642
243
  },
643
- ],
644
- actions: {
645
- module: { compiled: CANVAS_ACTIONS_COMPILED, source: CANVAS_ACTIONS_SOURCE },
646
- type: 'FUNCTION',
647
- list: [{ id: 'didMount', title: 'didMount' }],
648
- },
649
- config: { connectComponent: [] },
650
- };
244
+ };
651
245
 
652
- return JSON.stringify(schema);
653
- }
246
+ const request = requestModule.request(requestOptions, (response) => {
247
+ let responseData = '';
248
+ response.on('data', (chunk) => { responseData += chunk; });
249
+ response.on('end', () => {
250
+ info(t('common.http_status', response.statusCode));
251
+ let parsed;
252
+ try {
253
+ parsed = JSON.parse(responseData);
254
+ } catch (parseError) {
255
+ warn(t('common.response_body', responseData.substring(0, 500)));
256
+ resolve({ success: false, errorMsg: 'HTTP ' + response.statusCode + ': ' + t('common.response_not_json') });
257
+ return;
258
+ }
259
+ // 检测登录过期(errorCode: "307")
260
+ if (isLoginExpired(parsed)) {
261
+ warn(t('common.login_expired', parsed.errorMsg));
262
+ resolve({ __needLogin: true });
263
+ return;
264
+ }
265
+ // 检测 csrf_token 过期(errorCode: "TIANSHU_000030")
266
+ if (isCsrfTokenExpired(parsed)) {
267
+ warn(t('common.csrf_expired', parsed.errorMsg));
268
+ resolve({ __csrfExpired: true });
269
+ return;
270
+ }
271
+ resolve(parsed);
272
+ });
273
+ });
654
274
 
275
+ request.on('error', (requestError) => { reject(requestError); });
655
276
 
656
- // ── 4. 发送 saveFormSchema 请求 ──────────────────────
277
+ request.write(postData);
278
+ request.end();
279
+ });
280
+ }
657
281
 
658
- function sendSaveRequest(schemaContent, baseUrl, appType, formUuid) {
282
+ function sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serverRevision) {
283
+ if (!authRef || typeof authRef.baseUrl !== 'string' || authRef.baseUrl.length === 0) {
284
+ const authError = new Error('Publish Schema write authentication is not ready.');
285
+ authError.code = 'PUBLISH_SCHEMA_WRITE_PRECHECK_FAILED';
286
+ return Promise.reject(authError);
287
+ }
288
+ if (!isTokenAuthRef(authRef)) {
289
+ return sendSaveRequestOnce(
290
+ authRef.csrfToken,
291
+ authRef.cookies,
292
+ schemaContent,
293
+ authRef.baseUrl,
294
+ appType,
295
+ formUuid,
296
+ serverRevision
297
+ );
298
+ }
299
+ const gmtModified = requireSchemaServerRevision({ gmtModified: serverRevision });
659
300
  const saveSchemaPath = `/alibaba/web/${appType}/${PREFIX}/query/formdesign/saveFormSchema.json?_stamp=${Date.now()}`;
660
301
  const postData = querystring.stringify({
302
+ _csrf_token: authRef.csrfToken,
661
303
  prefix: PREFIX,
662
304
  content: schemaContent,
663
- formUuid: formUuid,
305
+ formUuid,
306
+ gmtModified,
664
307
  schemaVersion: SCHEMA_VERSION,
665
308
  domainCode: DOMAIN_CODE,
666
309
  importSchema: true,
667
310
  });
668
- return httpPost(baseUrl, saveSchemaPath, postData);
311
+ return httpPost(authRef.baseUrl, saveSchemaPath, postData, authRef.cookies, { silentStatus: true });
669
312
  }
670
313
 
671
314
  // ── 5. 发送 updateFormConfig 请求 ────────────────────
672
315
 
673
- function sendUpdateConfigRequest(baseUrl, appType, formUuid, version, value) {
316
+ function sendUpdateConfigRequest(csrfToken, cookies, baseUrl, appType, formUuid, version, value) {
674
317
  const updateConfigPath = `/dingtalk/web/${appType}/query/formdesign/updateFormConfig.json`;
675
318
  const postData = querystring.stringify({
319
+ _csrf_token: csrfToken,
676
320
  formUuid: formUuid,
677
321
  version: version,
678
322
  configType: 'MINI_RESOURCE',
679
323
  value: value,
680
324
  });
681
- return httpPost(baseUrl, updateConfigPath, postData);
325
+ if (!Array.isArray(cookies) || cookies.length === 0) {
326
+ return httpPost(baseUrl, updateConfigPath, postData);
327
+ }
328
+
329
+ return new Promise((resolve, reject) => {
330
+ const cookieHeader = cookies
331
+ .map((cookie) => `${cookie.name}=${cookie.value}`)
332
+ .join('; ');
333
+
334
+ const parsedUrl = new URL(baseUrl);
335
+ const isHttps = parsedUrl.protocol === 'https:';
336
+ const requestModule = isHttps ? https : http;
337
+
338
+ const requestOptions = {
339
+ hostname: parsedUrl.hostname,
340
+ port: parsedUrl.port || (isHttps ? 443 : 80),
341
+ path: updateConfigPath,
342
+ method: 'POST',
343
+ headers: {
344
+ 'Content-Type': 'application/x-www-form-urlencoded',
345
+ 'Content-Length': Buffer.byteLength(postData),
346
+ Origin: baseUrl,
347
+ Referer: `${baseUrl}/`,
348
+ Cookie: cookieHeader,
349
+ },
350
+ };
351
+
352
+ const request = requestModule.request(requestOptions, (response) => {
353
+ let responseData = '';
354
+ response.on('data', (chunk) => { responseData += chunk; });
355
+ response.on('end', () => {
356
+ info(t('common.http_status', response.statusCode));
357
+ let parsed;
358
+ try {
359
+ parsed = JSON.parse(responseData);
360
+ } catch (parseError) {
361
+ warn(t('common.response_body', responseData.substring(0, 500)));
362
+ resolve({ success: false, errorMsg: 'HTTP ' + response.statusCode + ': ' + t('common.response_not_json') });
363
+ return;
364
+ }
365
+ // 检测登录过期(errorCode: "307")
366
+ if (isLoginExpired(parsed)) {
367
+ warn(t('common.login_expired', parsed.errorMsg));
368
+ resolve({ __needLogin: true });
369
+ return;
370
+ }
371
+ // 检测 csrf_token 过期(errorCode: "TIANSHU_000030")
372
+ if (isCsrfTokenExpired(parsed)) {
373
+ warn(t('common.csrf_expired', parsed.errorMsg));
374
+ resolve({ __csrfExpired: true });
375
+ return;
376
+ }
377
+ resolve(parsed);
378
+ });
379
+ });
380
+
381
+ request.on('error', (requestError) => { reject(requestError); });
382
+
383
+ request.write(postData);
384
+ request.end();
385
+ });
386
+ }
387
+
388
+ async function sendUpdateConfigRequestWithAuth(authRef, appType, formUuid, version, value) {
389
+ return requestWithAutoLogin(async (currentAuthRef) => {
390
+ const requestAuthRef = currentAuthRef || {};
391
+ const response = await sendUpdateConfigRequest(
392
+ requestAuthRef.csrfToken,
393
+ requestAuthRef.cookies,
394
+ requestAuthRef.baseUrl || DEFAULT_BASE_URL,
395
+ appType,
396
+ formUuid,
397
+ version,
398
+ value
399
+ );
400
+
401
+ if (response && response.__csrfExpired && isTokenAuthRef(requestAuthRef)) {
402
+ return {
403
+ ...response,
404
+ __needLogin: true,
405
+ errorCode: response.errorCode || 'TOKEN_AUTH_REQUIRED',
406
+ errorMsg: response.errorMsg || 'token_auth_required: token auth was rejected while updating page config. Run openyida auth refresh or openyida login.',
407
+ };
408
+ }
409
+
410
+ return response;
411
+ }, authRef);
682
412
  }
683
413
 
684
414
  function walkFiles(dir, results, limit) {
@@ -819,13 +549,14 @@ async function ensurePublishTargetOrExit(appType, formUuid, authRef, options = {
819
549
  return null;
820
550
  }
821
551
 
822
- async function sendHealthCheckRequest(pageUrl) {
823
- const { getAccessToken } = require('../auth/token-auth');
824
- const accessToken = await getAccessToken();
552
+ function sendHealthCheckRequest(pageUrl, cookies) {
825
553
  return new Promise((resolve) => {
826
554
  const parsedUrl = new URL(pageUrl);
827
555
  const isHttps = parsedUrl.protocol === 'https:';
828
556
  const requestModule = isHttps ? https : http;
557
+ const cookieHeader = cookies
558
+ .map((cookie) => `${cookie.name}=${cookie.value}`)
559
+ .join('; ');
829
560
  const requestOptions = {
830
561
  hostname: parsedUrl.hostname,
831
562
  port: parsedUrl.port || (isHttps ? 443 : 80),
@@ -833,7 +564,7 @@ async function sendHealthCheckRequest(pageUrl) {
833
564
  method: 'GET',
834
565
  headers: {
835
566
  Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
836
- Authorization: `Bearer ${accessToken}`,
567
+ Cookie: cookieHeader,
837
568
  },
838
569
  timeout: 30000,
839
570
  };
@@ -871,14 +602,25 @@ async function sendHealthCheckRequest(pageUrl) {
871
602
  // ── 主流程 ────────────────────────────────────────────
872
603
 
873
604
  async function main(argv) {
874
- const { appType, formUuid, sourceFile, skipLint, healthCheck, compat, force, canvas: canvasFlag, browserOpenMode } = parseArgs(argv);
605
+ const options = parseArgs(argv);
606
+ if (options.help) {
607
+ return options;
608
+ }
609
+ const { appType, formUuid, sourceFile, skipLint, healthCheck, compat, force, canvas: canvasFlag, browserOpenMode, legacyGuardOptions } = options;
610
+ assertLegacyDirectWriteAllowed({
611
+ command: 'publish',
612
+ resourceType: 'page',
613
+ action: 'update',
614
+ appType,
615
+ formUuid,
616
+ }, { guardOptions: legacyGuardOptions });
875
617
 
876
618
  let sourcePath = path.resolve(sourceFile);
877
619
  if (!fs.existsSync(sourcePath)) {
878
620
  error(t('publish.source_not_found', sourcePath));
879
621
  }
880
622
 
881
- // 路由:默认普通自定义页面;.canvas.jsx / .canvas.tsx 源文件自动走 Code Canvas 链路,
623
+ // 路由:默认 native;.canvas.jsx / .canvas.tsx 源文件自动走 Code Canvas 链路,
882
624
  // --canvas 作为显式覆盖(用于扩展名不规范但确为 canvas 源码的场景)。
883
625
  const isCanvas = canvasFlag || /\.canvas\.(jsx|tsx)$/i.test(sourcePath);
884
626
 
@@ -890,7 +632,7 @@ async function main(argv) {
890
632
  let canvasResult = null;
891
633
 
892
634
  if (isCanvas) {
893
- // Code Canvas 链路:跳过普通自定义页面编译/lint,改用本地 Babel 编译
635
+ // Code Canvas 链路:跳过 native 编译/lint,改用本地 Babel 编译
894
636
  // (JSX/TS → runtimeCode + importedModules),无需登录态,可在开浏览器前失败快。
895
637
  step(1, t('publish.step_canvas_compile'));
896
638
  info(t('publish.canvas_compiling'));
@@ -939,35 +681,35 @@ async function main(argv) {
939
681
  const compiledPath = path.join(findProjectRoot(), 'pages', 'dist', `${parsedSource.name}.js`);
940
682
 
941
683
  step(2, t('common.step_login', 2));
942
- let authData = loadAuthData();
943
- if (!authData) {
944
- warn(t('common.login_no_cache'));
945
- authData = triggerLogin();
946
- }
947
- let baseUrl = resolveBaseUrl(authData);
948
- const authRef = {
949
- baseUrl,
950
- authData,
951
- authMode: authData.auth_mode || '',
952
- authSource: authData.auth_source || '',
953
- corpId: authData.corp_id || '',
954
- userId: authData.user_id || '',
955
- };
684
+ const authRef = createAuthRef();
685
+ let cookies = authRef.cookies;
686
+ let baseUrl = authRef.baseUrl;
956
687
  await ensurePublishTargetOrExit(appType, formUuid, authRef, { force });
688
+ cookies = authRef.cookies;
957
689
  baseUrl = authRef.baseUrl;
958
690
 
959
691
  let schemaContent;
692
+ let serverRevision;
960
693
  if (isCanvas) {
694
+ try {
695
+ serverRevision = (await fetchExistingSchemaContent(appType, formUuid, authRef)).serverRevision;
696
+ } catch (schemaReadError) {
697
+ fail(t('publish.data_source_fetch_failed', schemaReadError.message));
698
+ process.exit(1);
699
+ }
961
700
  // canvasResult 已在登录前本地编译完成,这里只需据其装配 Schema content。
962
701
  schemaContent = buildCanvasSchemaContent(sourceCode, canvasResult.runtimeCode, canvasResult.importedModules, formUuid);
963
702
  } else {
964
703
  let existingDataSource = null;
965
704
  try {
966
- existingDataSource = await readExistingPageDataSource(appType, formUuid, authRef);
705
+ const existing = await readExistingPageDataSource(appType, formUuid, authRef);
706
+ existingDataSource = existing.dataSource;
707
+ serverRevision = existing.serverRevision;
967
708
  } catch (dataSourceError) {
968
709
  fail(t('publish.data_source_fetch_failed', dataSourceError.message));
969
710
  process.exit(1);
970
711
  }
712
+ cookies = authRef.cookies;
971
713
  baseUrl = authRef.baseUrl;
972
714
  schemaContent = buildSchemaContent(sourceCode, compiledCode, formUuid, { existingDataSource });
973
715
  }
@@ -980,18 +722,12 @@ async function main(argv) {
980
722
  label('Source:', sourcePath);
981
723
  label('Compiled:', compiledPath);
982
724
  step(3, t('publish.step_publish'));
983
- const response = await requestWithAutoLogin((ref) => sendSaveRequest(
984
- schemaContent,
985
- ref.baseUrl,
986
- appType,
987
- formUuid
988
- ), authRef);
989
- baseUrl = authRef.baseUrl;
725
+ const response = await sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serverRevision);
990
726
 
991
727
  if (!response || !response.success) {
992
728
  const errorMsg = response ? response.errorMsg || t('common.unknown_error') : t('common.request_failed');
993
729
  fail(t('publish.publish_failed', errorMsg));
994
- if (response && !response.__needLogin) {
730
+ if (response && !response.__needLogin && !response.__csrfExpired) {
995
731
  hint(t('common.response_detail', JSON.stringify(response, null, 2)));
996
732
  }
997
733
  process.exit(1);
@@ -1006,20 +742,15 @@ async function main(argv) {
1006
742
 
1007
743
  step(4, t('publish.step_config'));
1008
744
  info(t('publish.sending_config'));
1009
- const configResponse = await requestWithAutoLogin((ref) => sendUpdateConfigRequest(
1010
- ref.baseUrl,
1011
- appType,
1012
- savedFormUuid,
1013
- version,
1014
- 8
1015
- ), authRef);
1016
- baseUrl = authRef.baseUrl;
745
+ const configResponse = await sendUpdateConfigRequestWithAuth(authRef, appType, savedFormUuid, version, 8);
746
+ cookies = authRef.cookies;
747
+ baseUrl = authRef.baseUrl || baseUrl;
1017
748
 
1018
749
  const pageUrl = `${baseUrl}/${appType}/workbench/${savedFormUuid}`;
1019
750
  let healthCheckResult = null;
1020
751
  if (healthCheck) {
1021
752
  step(5, t('publish.step_health_check'));
1022
- healthCheckResult = await sendHealthCheckRequest(pageUrl);
753
+ healthCheckResult = await sendHealthCheckRequest(pageUrl, cookies);
1023
754
  if (healthCheckResult.ok) {
1024
755
  success(t('publish.health_check_ok', healthCheckResult.statusCode));
1025
756
  } else {
@@ -1081,4 +812,6 @@ if (require.main === module) {
1081
812
  module.exports.countCustomPageDataSources = countCustomPageDataSources;
1082
813
  module.exports.buildSchemaContent = buildSchemaContent;
1083
814
  module.exports.buildCanvasSchemaContent = buildCanvasSchemaContent;
815
+ module.exports.sendSaveRequestOnce = sendSaveRequestOnce;
816
+ module.exports.sendUpdateConfigRequestWithAuth = sendUpdateConfigRequestWithAuth;
1084
817
  }