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
@@ -16,21 +16,39 @@ const { t } = require('../core/i18n');
16
16
  const { buildYidaI18n, buildYidaTitleI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
17
17
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
18
18
  const { throwCommandError } = require('../core/command-errors');
19
+ const {
20
+ assertLegacyDirectWriteAllowed,
21
+ extractLegacyGuardArgs,
22
+ } = require('../core/legacy-schema-guard');
19
23
 
20
24
  function parseArgs(args) {
21
25
  const openOption = parseOpenOption(args);
26
+ const guardSplit = extractLegacyGuardArgs(openOption.args);
22
27
  const filteredArgs = [];
23
28
  let mode = 'default';
24
29
  let locale = null;
25
30
 
26
- for (let i = 0; i < openOption.args.length; i++) {
27
- const arg = openOption.args[i];
28
- if (arg === '--mode' && openOption.args[i + 1]) {
29
- mode = openOption.args[++i];
31
+ if (guardSplit.args.includes('--help') || guardSplit.args.includes('-h')) {
32
+ return {
33
+ args: [],
34
+ appType: null,
35
+ pageName: null,
36
+ mode,
37
+ locale,
38
+ help: true,
39
+ openMode: openOption.mode,
40
+ legacyGuardOptions: guardSplit.guardOptions,
41
+ };
42
+ }
43
+
44
+ for (let i = 0; i < guardSplit.args.length; i++) {
45
+ const arg = guardSplit.args[i];
46
+ if (arg === '--mode' && guardSplit.args[i + 1]) {
47
+ mode = guardSplit.args[++i];
30
48
  continue;
31
49
  }
32
- if ((arg === '--locale' || arg === '--content-locale' || arg === '--lang') && openOption.args[i + 1]) {
33
- locale = openOption.args[++i];
50
+ if ((arg === '--locale' || arg === '--content-locale' || arg === '--lang') && guardSplit.args[i + 1]) {
51
+ locale = guardSplit.args[++i];
34
52
  if (!normalizeYidaLocale(locale)) {
35
53
  throw new Error(`Unsupported locale: ${locale}`);
36
54
  }
@@ -46,10 +64,11 @@ function parseArgs(args) {
46
64
  mode,
47
65
  locale,
48
66
  openMode: openOption.mode,
67
+ legacyGuardOptions: guardSplit.guardOptions,
49
68
  };
50
69
  }
51
70
 
52
- function buildPageInfoPostData(formUuid, pageName, isRenderNav) {
71
+ function buildPageInfoPostData(formUuid, pageName, isRenderNav, csrfToken) {
53
72
  const titleJson = JSON.stringify(buildYidaTitleI18n(pageName, {
54
73
  en_US: pageName,
55
74
  ja_JP: pageName,
@@ -57,6 +76,7 @@ function buildPageInfoPostData(formUuid, pageName, isRenderNav) {
57
76
 
58
77
  return querystring.stringify({
59
78
  _api: 'Form.updateFormSchemaInfo',
79
+ _csrf_token: csrfToken,
60
80
  _locale_time_zone_offset: '28800000',
61
81
  formUuid,
62
82
  serialSwitch: 'n',
@@ -90,7 +110,8 @@ async function configureDashboardMode(authRef, appType, pageId, pageName) {
90
110
  return httpPost(
91
111
  auth.baseUrl,
92
112
  `/dingtalk/web/${appType}/query/formdesign/updateFormSchemaInfo.json`,
93
- buildPageInfoPostData(pageId, pageName, false)
113
+ buildPageInfoPostData(pageId, pageName, false, auth.csrfToken),
114
+ auth.cookies
94
115
  );
95
116
  }, authRef);
96
117
  }
@@ -104,6 +125,13 @@ async function run(args) {
104
125
  chalkError(err.message, { hint: t('create_page.usage') });
105
126
  }
106
127
 
128
+ if (options.help) {
129
+ const { usage, hint } = require('../core/chalk');
130
+ usage(t('create_page.usage'), t('create_page.example'));
131
+ hint(t('create_page.mode_hint'));
132
+ return options;
133
+ }
134
+
107
135
  if (options.args.length < 2) {
108
136
  const { error: chalkError } = require('../core/chalk');
109
137
  chalkError(t('create_page.usage'), { hint: t('create_page.example') });
@@ -112,6 +140,12 @@ async function run(args) {
112
140
  const appType = options.appType;
113
141
  const pageName = options.pageName;
114
142
  const mode = options.mode;
143
+ assertLegacyDirectWriteAllowed({
144
+ command: 'create-page',
145
+ resourceType: 'page',
146
+ action: 'create',
147
+ appType,
148
+ }, { guardOptions: options.legacyGuardOptions });
115
149
  if (mode !== 'default' && mode !== 'dashboard') {
116
150
  const { error: chalkError } = require('../core/chalk');
117
151
  chalkError(t('create_page.err_mode_invalid', mode), { hint: t('create_page.mode_hint') });
@@ -138,13 +172,15 @@ async function run(args) {
138
172
 
139
173
  const response = await requestWithAutoLogin((auth) => {
140
174
  const postData = querystring.stringify({
175
+ _csrf_token: auth.csrfToken,
141
176
  formType: 'display',
142
177
  title: JSON.stringify(buildYidaI18n(pageName, { en_US: pageName, ja_JP: pageName })),
143
178
  });
144
179
  return httpPost(
145
180
  auth.baseUrl,
146
181
  `/dingtalk/web/${appType}/query/formdesign/saveFormSchemaInfo.json`,
147
- postData
182
+ postData,
183
+ auth.cookies
148
184
  );
149
185
  }, authRef);
150
186
 
@@ -2,6 +2,7 @@
2
2
  * get-schema.js - 宜搭表单 Schema 获取命令
3
3
  *
4
4
  * 用法:
5
+ * openyida get-schema <appType> <formUuid> [--compact] [--resolve-fields <labelOrFieldId,...>]
5
6
  * openyida get-schema <appType> <formUuid> [--summary-json|--field-map-json]
6
7
  * openyida get-schema <appType> --all [--summary-json] [--output-dir <dir>] [--concurrency N] [--retries N]
7
8
  */
@@ -20,6 +21,7 @@ const {
20
21
  const { t } = require('../core/i18n');
21
22
  const { throwCommandError } = require('../core/command-errors');
22
23
  const { fetchFormPageList } = require('./form-navigation');
24
+ const { buildFieldResolution } = require('./schema-field-resolution');
23
25
 
24
26
  // 需要在报表 fieldCode 中加 _value 后缀的字段类型
25
27
  const FIELD_TYPES_NEEDING_VALUE_SUFFIX = new Set([
@@ -202,8 +204,9 @@ function parseArgs(args) {
202
204
  concurrency: 3,
203
205
  retries: 1,
204
206
  keyword: '',
205
- // --field <labelOrFieldId>:单字段过滤模式,命中时跳过 runBatch 分支,
206
- // 仅返回目标字段的 {componentName, fieldId, label, props}
207
+ compact: false,
208
+ fields: [],
209
+ // 保留已有单字段参数,作为 --fields 的单值兼容别名。
207
210
  field: '',
208
211
  json: false,
209
212
  summaryJson: false,
@@ -225,7 +228,15 @@ function parseArgs(args) {
225
228
  } else if (arg === '--keyword' && args[index + 1]) {
226
229
  parsed.keyword = args[index + 1];
227
230
  index++;
228
- } else if (arg === '--field' && args[index + 1]) {
231
+ } else if (arg === '--compact') {
232
+ parsed.compact = true;
233
+ } else if (arg === '--resolve-fields') {
234
+ parsed.compact = true;
235
+ if (args[index + 1] && !args[index + 1].startsWith('--')) {
236
+ appendFieldQueries(parsed.fields, args[index + 1]);
237
+ index++;
238
+ }
239
+ } else if (arg === '--field' && args[index + 1] && !args[index + 1].startsWith('--')) {
229
240
  parsed.field = args[index + 1];
230
241
  index++;
231
242
  } else if (arg === '--json') {
@@ -240,15 +251,27 @@ function parseArgs(args) {
240
251
  return parsed;
241
252
  }
242
253
 
254
+ function appendFieldQueries(target, value) {
255
+ String(value || '')
256
+ .split(/[,,]/)
257
+ .map(item => item.trim())
258
+ .filter(Boolean)
259
+ .forEach((item) => {
260
+ if (!target.includes(item)) {
261
+ target.push(item);
262
+ }
263
+ });
264
+ }
265
+
243
266
  function ensureUsage(parsed) {
244
267
  if (!parsed.appType || (!parsed.all && !parsed.formUuid)) {
245
268
  const { error: chalkError } = require('../core/chalk');
246
269
  chalkError(t('get_schema.usage'), { hint: t('get_schema.example') });
247
270
  }
248
- // --field 模式必须同时提供 formUuid(单字段过滤不支持 --all 批量)
249
- if (parsed.field && parsed.all) {
271
+ // contract v1 resource 对应单个 form,不与 --all 批量模式混用。
272
+ if ((parsed.compact || parsed.field) && parsed.all) {
250
273
  const { error: chalkError } = require('../core/chalk');
251
- chalkError('--field 不支持与 --all 同时使用,请改为单 form 模式');
274
+ chalkError(t('get_schema.usage'), { hint: t('get_schema.example') });
252
275
  }
253
276
  }
254
277
 
@@ -503,21 +526,37 @@ async function runSingle(parsed, authRef) {
503
526
  banner(t('get_schema.title'));
504
527
  label('App', parsed.appType);
505
528
  label('Form', parsed.formUuid);
506
- if (parsed.field) {label('Field', parsed.field);}
529
+ if (parsed.field) {
530
+ label('Field', parsed.field);
531
+ } else if (parsed.fields.length > 0) {
532
+ label('Fields', parsed.fields.join(', '));
533
+ }
507
534
 
508
535
  // Step 2: 获取表单 Schema
509
536
  step(2, t('get_schema.step_get'));
510
537
  info(t('get_schema.sending'));
511
538
 
512
- const result = await fetchSchema(parsed.appType, parsed.formUuid, authRef);
539
+ let result;
540
+ try {
541
+ result = await fetchSchema(parsed.appType, parsed.formUuid, authRef);
542
+ } catch (error) {
543
+ if (!parsed.compact) {
544
+ throw error;
545
+ }
546
+ chalkResult(false, t('get_schema.failed', t('common.request_failed')));
547
+ process.exit(1);
548
+ return;
549
+ }
513
550
 
514
551
  if (!isSuccessfulSchemaResult(result)) {
515
- const errorMsg = result ? result.errorMsg || t('common.unknown_error') : t('common.request_failed');
552
+ const errorMsg = parsed.compact
553
+ ? t('common.request_failed')
554
+ : result ? result.errorMsg || t('common.unknown_error') : t('common.request_failed');
516
555
  chalkResult(false, t('get_schema.failed', errorMsg));
517
556
  throwCommandError(errorMsg);
518
557
  }
519
558
 
520
- // --field 模式:只返回单个字段的详细 props(含 SelectField.dataSource 等)
559
+ // 保留既有 --field 返回结构;新 contract 仅由 --compact / --resolve-fields 触发。
521
560
  if (parsed.field) {
522
561
  const allFieldNodes = collectFieldNodes(result);
523
562
  const aliasMaps = buildComponentAliasMaps(result);
@@ -531,18 +570,29 @@ async function runSingle(parsed, authRef) {
531
570
  }
532
571
  const props = matched.props || {};
533
572
  const labelRaw = props.label;
534
- const label = labelRaw
573
+ const fieldLabel = labelRaw
535
574
  ? (typeof labelRaw === 'object' ? (labelRaw.zh_CN || labelRaw.en_US || '') : String(labelRaw))
536
575
  : '';
537
- const compact = {
576
+ const legacyField = {
538
577
  componentName: matched.componentName,
539
578
  fieldId: props.fieldId || '',
540
579
  alias: aliasMaps.aliasByFieldId[props.fieldId] || '',
541
- label,
580
+ label: fieldLabel,
542
581
  props,
543
582
  };
544
583
  chalkSuccess(t('get_schema.success'));
545
- console.log(JSON.stringify(compact, null, 2));
584
+ console.log(JSON.stringify(legacyField, null, 2));
585
+ return;
586
+ }
587
+
588
+ if (parsed.compact) {
589
+ chalkSuccess(t('get_schema.success'));
590
+ console.log(JSON.stringify(buildFieldResolution(
591
+ parsed.appType,
592
+ parsed.formUuid,
593
+ result,
594
+ parsed.fields
595
+ ), null, 2));
546
596
  return;
547
597
  }
548
598
 
@@ -631,6 +681,7 @@ module.exports = {
631
681
  buildSchemaSummary,
632
682
  buildComponentAliasMaps,
633
683
  parseArgs,
684
+ appendFieldQueries,
634
685
  filterForms,
635
686
  mapLimit,
636
687
  fetchSchemaRecord,
@@ -15,32 +15,37 @@ const {
15
15
  httpPost,
16
16
  requestWithAutoLogin,
17
17
  } = require('../core/utils');
18
- const { createAuthRef } = require('../core/yida-client');
18
+ const { createAuthRef, isTokenAuthRef } = require('../core/yida-client');
19
19
  const { t } = require('../core/i18n');
20
20
  const { buildYidaI18n, resolveContentLocale } = require('../core/yida-i18n');
21
21
  const { banner, step, label, success, fail, warn, info, error, result, usage, hint } = require('../core/chalk');
22
22
  const { throwUsage } = require('../core/command-errors');
23
+ const { getSchemaServerRevision } = require('../schema/server-revision');
24
+ const {
25
+ assertLegacyDirectWriteAllowed,
26
+ extractLegacyGuardArgs,
27
+ } = require('../core/legacy-schema-guard');
23
28
 
24
29
  // ── 创建新应用 ────────────────────────────────────────
25
30
 
26
31
  async function createApp(appName, authRef) {
27
32
  const contentLocale = resolveContentLocale({ baseUrl: authRef.baseUrl });
28
- const postData = querystring.stringify({
29
- appName: JSON.stringify(buildYidaI18n(appName, { en_US: appName, ja_JP: appName })),
30
- description: JSON.stringify(buildYidaI18n(appName, { en_US: appName, ja_JP: appName })),
31
- icon: 'xian-yingyong%%#0089FF',
32
- iconUrl: 'xian-yingyong%%#0089FF',
33
- colour: 'blue',
34
- defaultLanguage: contentLocale,
35
- openExclusive: 'n',
36
- openPhysicColumn: 'n',
37
- openIsolationDatabase: 'n',
38
- openExclusiveUnit: 'n',
39
- group: 'ALL',
40
- });
41
-
42
33
  const result = await requestWithAutoLogin((auth) => {
43
- return httpPost(auth.baseUrl, '/query/app/registerApp.json', postData);
34
+ const postData = querystring.stringify({
35
+ _csrf_token: auth.csrfToken,
36
+ appName: JSON.stringify(buildYidaI18n(appName, { en_US: appName, ja_JP: appName })),
37
+ description: JSON.stringify(buildYidaI18n(appName, { en_US: appName, ja_JP: appName })),
38
+ icon: 'xian-yingyong%%#0089FF',
39
+ iconUrl: 'xian-yingyong%%#0089FF',
40
+ colour: 'blue',
41
+ defaultLanguage: contentLocale,
42
+ openExclusive: 'n',
43
+ openPhysicColumn: 'n',
44
+ openIsolationDatabase: 'n',
45
+ openExclusiveUnit: 'n',
46
+ group: 'ALL',
47
+ });
48
+ return httpPost(auth.baseUrl, '/query/app/registerApp.json', postData, auth.cookies);
44
49
  }, authRef);
45
50
 
46
51
  if (!result || !result.success || !result.content) {
@@ -58,22 +63,30 @@ function normalizeImportFormType(formType) {
58
63
  return normalized;
59
64
  }
60
65
 
61
- function buildCreateFormPostData(formTitle, formType = 'receipt') {
62
- return querystring.stringify({
66
+ function buildCreateFormPayload(formTitle, formType = 'receipt') {
67
+ return {
63
68
  formType: normalizeImportFormType(formType),
64
69
  title: JSON.stringify(buildYidaI18n(formTitle, { en_US: formTitle, ja_JP: formTitle })),
65
- });
70
+ };
71
+ }
72
+
73
+ function buildCreateFormPostData(formTitle, formType = 'receipt') {
74
+ return querystring.stringify(buildCreateFormPayload(formTitle, formType));
66
75
  }
67
76
 
68
77
  // ── 创建空白表单/页面/报表 ──────────────────────────────
69
78
 
70
79
  async function createBlankForm(appType, formTitle, authRef, formType = 'receipt') {
71
- const postData = buildCreateFormPostData(formTitle, formType);
72
80
  const result = await requestWithAutoLogin((auth) => {
81
+ const postData = querystring.stringify({
82
+ _csrf_token: auth.csrfToken,
83
+ ...buildCreateFormPayload(formTitle, formType),
84
+ });
73
85
  return httpPost(
74
86
  auth.baseUrl,
75
87
  `/dingtalk/web/${appType}/query/formdesign/saveFormSchemaInfo.json`,
76
- postData
88
+ postData,
89
+ auth.cookies
77
90
  );
78
91
  }, authRef);
79
92
 
@@ -87,44 +100,72 @@ async function createBlankForm(appType, formTitle, authRef, formType = 'receipt'
87
100
 
88
101
  // ── 保存表单 Schema ───────────────────────────────────
89
102
 
90
- async function saveFormSchema(appType, formUuid, schema, authRef, formType = 'receipt') {
103
+ async function saveFormSchema(appType, formUuid, schema, authRef, formType = 'receipt', serverRevision) {
104
+ const tokenReady = isTokenAuthRef(authRef);
105
+ const cookieReady = !!(
106
+ authRef &&
107
+ typeof authRef.csrfToken === 'string' &&
108
+ authRef.csrfToken &&
109
+ Array.isArray(authRef.cookies)
110
+ );
111
+ if (!authRef || !authRef.baseUrl || (!tokenReady && !cookieReady)) {
112
+ const err = new Error('Import schema save requires an authenticated request context.');
113
+ err.code = 'IMPORT_SCHEMA_WRITE_PRECHECK_FAILED';
114
+ throw err;
115
+ }
116
+ if (!schema || typeof schema !== 'object' || !Array.isArray(schema.pages)) {
117
+ const err = new Error('Imported schema content must include a pages array before writing.');
118
+ err.code = 'SCHEMA_REMOTE_READ_FAILED';
119
+ throw err;
120
+ }
121
+ if (!Number.isFinite(serverRevision) || serverRevision < 0) {
122
+ const err = new Error('Imported schema revision is missing or invalid.');
123
+ err.code = 'SCHEMA_REMOTE_READ_FAILED';
124
+ throw err;
125
+ }
126
+
91
127
  const payload = {
92
128
  appType,
93
129
  formUuid,
94
130
  content: JSON.stringify(schema),
95
131
  schemaVersion: 'V5',
132
+ gmtModified: serverRevision,
96
133
  };
97
134
  if (normalizeImportFormType(formType) === 'report') {
98
135
  payload.importSchema = 'true';
99
136
  }
100
- const postData = querystring.stringify(payload);
101
-
102
- const result = await requestWithAutoLogin((auth) => {
103
- return httpPost(
104
- auth.baseUrl,
105
- `/alibaba/web/${appType}/_view/query/formdesign/saveFormSchema.json`,
106
- postData
107
- );
108
- }, authRef);
109
137
 
110
- return result;
138
+ const postData = querystring.stringify(Object.assign(
139
+ {},
140
+ authRef.csrfToken ? { _csrf_token: authRef.csrfToken } : {},
141
+ payload
142
+ ));
143
+ const authOptions = { projectRoot: authRef.projectRoot };
144
+ return httpPost(
145
+ authRef.baseUrl,
146
+ `/alibaba/web/${appType}/_view/query/formdesign/saveFormSchema.json`,
147
+ postData,
148
+ Array.isArray(authRef.cookies) ? authRef.cookies : authOptions,
149
+ Array.isArray(authRef.cookies) ? authOptions : undefined
150
+ );
111
151
  }
112
152
 
113
153
  // ── 更新表单配置(发布表单)─────────────────────────────
114
154
 
115
155
  async function updateFormConfig(appType, formUuid, authRef) {
116
- const postData = querystring.stringify({
117
- appType,
118
- formUuid,
119
- setting: JSON.stringify({ MINI_RESOURCE: 0 }),
120
- version: 1,
121
- });
122
-
123
156
  const result = await requestWithAutoLogin((auth) => {
157
+ const postData = querystring.stringify({
158
+ _csrf_token: auth.csrfToken,
159
+ appType,
160
+ formUuid,
161
+ setting: JSON.stringify({ MINI_RESOURCE: 0 }),
162
+ version: 1,
163
+ });
124
164
  return httpPost(
125
165
  auth.baseUrl,
126
166
  `/dingtalk/web/${appType}/query/formdesign/updateFormConfig.json`,
127
- postData
167
+ postData,
168
+ auth.cookies
128
169
  );
129
170
  }, authRef);
130
171
 
@@ -178,6 +219,8 @@ function extractSchemaContent(schemaResult) {
178
219
  // ── 主逻辑 ────────────────────────────────────────────
179
220
 
180
221
  async function run(args) {
222
+ const guardSplit = extractLegacyGuardArgs(args || []);
223
+ args = guardSplit.args;
181
224
  if (args.length < 1) {
182
225
  usage(t('import.usage'));
183
226
  hint(t('import.example1'));
@@ -216,6 +259,11 @@ async function run(args) {
216
259
  step(2, t('common.step_login_label'));
217
260
  const authRef = createAuthRef();
218
261
  success(t('common.login_ready', authRef.baseUrl));
262
+ assertLegacyDirectWriteAllowed({
263
+ command: 'import-app',
264
+ resourceType: 'app',
265
+ action: 'create',
266
+ }, { guardOptions: guardSplit.guardOptions });
219
267
 
220
268
  step(3, t('import.step_create_app'));
221
269
  let newAppType;
@@ -288,9 +336,10 @@ async function run(args) {
288
336
  oldFormUuid,
289
337
  newFormUuid
290
338
  );
339
+ const serverRevision = getSchemaServerRevision(formSchemaResult);
291
340
 
292
341
  // 4.3 保存 Schema
293
- const saveResult = await saveFormSchema(newAppType, newFormUuid, adaptedSchema, authRef, formType);
342
+ const saveResult = await saveFormSchema(newAppType, newFormUuid, adaptedSchema, authRef, formType, serverRevision);
294
343
  if (!saveResult || !saveResult.success) {
295
344
  const errorMsg = saveResult ? saveResult.errorMsg || t('common.unknown_error') : t('common.request_failed');
296
345
  fail(t('import.save_schema_failed', errorMsg));
@@ -369,7 +418,9 @@ module.exports = {
369
418
  run,
370
419
  __test__: {
371
420
  normalizeImportFormType,
421
+ buildCreateFormPayload,
372
422
  buildCreateFormPostData,
423
+ saveFormSchema,
373
424
  adaptSchemaIdentifiers,
374
425
  extractSchemaContent,
375
426
  },