openyida 2026.7.25-beta.2 → 2026.7.25-beta.3

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.
@@ -17,9 +17,10 @@ const { warnLargePageSource } = require('./page-size-guard');
17
17
  /**
18
18
  * 编译宜搭自定义页面 JSX/JS 源码。
19
19
  * @param {string} sourcePath
20
+ * @param {{ sourceCode?: string }} [options]
20
21
  * @returns {{ sourceCode: string, compiledCode: string, compiledPath: string }}
21
22
  */
22
- function compileSource(sourcePath) {
23
+ function compileSource(sourcePath, options = {}) {
23
24
  assertNoEmojiInArtifactName(sourcePath, {
24
25
  code: 'OPENYIDA_PAGE_FILENAME_EMOJI_FORBIDDEN',
25
26
  });
@@ -29,7 +30,9 @@ function compileSource(sourcePath) {
29
30
  const compiledPath = path.join(findProjectRoot(), 'pages', 'dist', compiledFileName);
30
31
 
31
32
  info(t('publish.reading_source', sourceFileName));
32
- const rawSourceCode = fs.readFileSync(sourcePath, 'utf-8');
33
+ const rawSourceCode = options.sourceCode === undefined
34
+ ? fs.readFileSync(sourcePath, 'utf-8')
35
+ : String(options.sourceCode);
33
36
  warnLargePageSource(rawSourceCode, sourcePath);
34
37
  const runtimeResult = ensureYidaRuntimeContract(rawSourceCode);
35
38
  const sourceCode = runtimeResult.code;
@@ -40,6 +40,7 @@ const { warnLargePageSource } = require('./page-size-guard');
40
40
  const { fetchFormPageList } = require('./form-navigation');
41
41
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
42
42
  const { assertNoEmojiInValue } = require('../core/no-emoji-guard');
43
+ const { loadPageSource } = require('./services/page-source-loader');
43
44
  const {
44
45
  buildDefaultPageDataSource,
45
46
  buildNativePageSchemaContent,
@@ -143,6 +144,31 @@ function buildMissingSourceHints(sourceFile, cwd = process.cwd()) {
143
144
  .filter(candidate => fs.existsSync(path.resolve(cwd, candidate)));
144
145
  }
145
146
 
147
+ function isPathInside(rootPath, targetPath) {
148
+ const relative = path.relative(rootPath, targetPath);
149
+ return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
150
+ }
151
+
152
+ function loadPublishSource(sourcePath, options = {}) {
153
+ const absolutePath = path.resolve(sourcePath);
154
+ const workspaceRoot = path.resolve(options.workspaceRoot || process.cwd());
155
+
156
+ if (isPathInside(workspaceRoot, absolutePath)) {
157
+ const relativePath = path.relative(workspaceRoot, absolutePath) || path.basename(absolutePath);
158
+ const loaded = loadPageSource(relativePath, { workspaceRoot });
159
+ return Object.assign({}, loaded, { absolutePath });
160
+ }
161
+
162
+ return {
163
+ absolutePath,
164
+ byteLength: fs.statSync(absolutePath).size,
165
+ profile: 'absolute/raw-file',
166
+ relativePath: absolutePath,
167
+ source: fs.readFileSync(absolutePath, 'utf-8'),
168
+ sourceHash: null,
169
+ };
170
+ }
171
+
146
172
  // ── 2. 读取并构建 Schema ────────────────────────────
147
173
 
148
174
  async function fetchExistingSchemaContent(appType, formUuid, authRef) {
@@ -679,7 +705,9 @@ async function main(argv) {
679
705
  // --canvas 作为显式覆盖(用于扩展名不规范但确为 canvas 源码的场景)。
680
706
  const isCanvas = canvasFlag || /\.canvas\.(jsx|tsx)$/i.test(sourcePath);
681
707
 
682
- const initialSourceCode = fs.readFileSync(sourcePath, 'utf-8');
708
+ const initialSource = loadPublishSource(sourcePath);
709
+ sourcePath = initialSource.absolutePath;
710
+ const initialSourceCode = initialSource.source;
683
711
  warnLargePageSource(initialSourceCode, sourcePath);
684
712
 
685
713
  let sourceCode = initialSourceCode;
@@ -712,12 +740,13 @@ async function main(argv) {
712
740
  success(t('build_page.output', sourcePath));
713
741
  }
714
742
  warnDuplicateSourceMismatches(sourcePath);
743
+ const currentSource = loadPublishSource(sourcePath);
744
+ sourcePath = currentSource.absolutePath;
715
745
 
716
746
  // Step 0: 宜搭编码规范预检(可通过 --skip-lint 跳过)
717
747
  if (!skipLint) {
718
748
  step(0, t('publish.step_lint'));
719
- const lintSource = fs.readFileSync(sourcePath, 'utf-8');
720
- const lintPassed = runLintCheck(lintSource, sourcePath, { successMessage: false });
749
+ const lintPassed = runLintCheck(currentSource.source, sourcePath, { successMessage: false });
721
750
  if (!lintPassed) {
722
751
  process.exit(1);
723
752
  }
@@ -727,7 +756,7 @@ async function main(argv) {
727
756
  }
728
757
 
729
758
  step(1, t('publish.step_compile'));
730
- const compiled = compileSource(sourcePath);
759
+ const compiled = compileSource(sourcePath, { sourceCode: currentSource.source });
731
760
  sourceCode = compiled.sourceCode;
732
761
  compiledCode = compiled.compiledCode;
733
762
  }
@@ -864,6 +893,7 @@ if (require.main === module) {
864
893
  module.exports.normalizePublishArgs = normalizePublishArgs;
865
894
  module.exports.buildMissingSourceHints = buildMissingSourceHints;
866
895
  module.exports.findDuplicateSourceMismatches = findDuplicateSourceMismatches;
896
+ module.exports.loadPublishSource = loadPublishSource;
867
897
  module.exports.sendHealthCheckRequest = sendHealthCheckRequest;
868
898
  module.exports.normalizeFormType = normalizeFormType;
869
899
  module.exports.findPublishTarget = findPublishTarget;
@@ -26,7 +26,19 @@ function toErrorPayload(error) {
26
26
  };
27
27
 
28
28
  if (isCliError(error) && error.details !== undefined) {
29
- payload.details = redactSensitive(error.details);
29
+ const details = redactSensitive(error.details);
30
+ if (details && typeof details === 'object' && !Array.isArray(details)) {
31
+ if (details.stage) {
32
+ payload.stage = details.stage;
33
+ }
34
+ if (Array.isArray(details.completedStages)) {
35
+ payload.completedStages = details.completedStages.slice();
36
+ }
37
+ if (details.nextStep) {
38
+ payload.nextStep = details.nextStep;
39
+ }
40
+ }
41
+ payload.details = details;
30
42
  }
31
43
 
32
44
  return payload;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const SENSITIVE_KEY_PATTERN = /(^|[_-])(authorization|auth|cookie|csrf|token|secret|password|passwd|api[_-]?key|access[_-]?key|signature|sign)([_-]|$)/i;
4
+ const SENSITIVE_CAMEL_KEY_PATTERN = /^(?:authorization|auth|cookie|csrf|token|secret|password|passwd|apiKey|accessKey|signature|sign)(?:[A-Z]|$)|(?:Authorization|Auth|Cookie|Csrf|Token|Secret|Password|Passwd|ApiKey|AccessKey|Signature|Sign)$/;
4
5
  const EMAIL_PATTERN = /([A-Z0-9._%+-])([A-Z0-9._%+-]*)(@[A-Z0-9.-]+\.[A-Z]{2,})/gi;
5
6
  const MAINLAND_PHONE_PATTERN = /(^|[^\d])(1[3-9]\d)(\d{4})(\d{4})(?!\d)/g;
6
7
 
@@ -42,7 +43,8 @@ function isPlainObject(value) {
42
43
  }
43
44
 
44
45
  function shouldMaskKey(key) {
45
- return SENSITIVE_KEY_PATTERN.test(String(key || ''));
46
+ const text = String(key || '');
47
+ return SENSITIVE_KEY_PATTERN.test(text) || SENSITIVE_CAMEL_KEY_PATTERN.test(text);
46
48
  }
47
49
 
48
50
  function redactSensitive(value, options = {}) {
@@ -11,6 +11,7 @@ const { CliError } = require('../core/cli-error');
11
11
  const { createAuthRef } = require('../core/yida-client');
12
12
  const { t } = require('../core/i18n');
13
13
  const { warn } = require('../core/chalk');
14
+ const { createStageTracker, summarizeRemoteResult } = require('./process-diagnostics');
14
15
  const {
15
16
  buildApproverConfig,
16
17
  buildProcessAndViewJson,
@@ -46,7 +47,7 @@ function formatApiError(result) {
46
47
  if (!result) {
47
48
  return 'empty response';
48
49
  }
49
- return result.errorMsg || result.message || JSON.stringify(result);
50
+ return result.errorMsg || result.message || JSON.stringify(summarizeRemoteResult(result));
50
51
  }
51
52
 
52
53
  // ── 主流程 ───────────────────────────────────────────
@@ -57,6 +58,21 @@ async function run(args) {
57
58
  const formUuid = parsed.formUuid;
58
59
  const processDefinitionFile = parsed.processDefinitionFile;
59
60
  const processCodeArg = parsed.processCodeArg;
61
+ const stageTracker = createStageTracker();
62
+ let processCode = processCodeArg;
63
+ let newProcessId = null;
64
+ let newVersion = null;
65
+
66
+ function context(extra) {
67
+ return Object.assign({
68
+ appType,
69
+ formUuid,
70
+ processCode: processCode || null,
71
+ processId: newProcessId || null,
72
+ processVersion: newVersion,
73
+ processDefinitionFile,
74
+ }, extra || {});
75
+ }
60
76
 
61
77
  warn('🔧 ' + t('configure_process.title'));
62
78
  warn(' ' + t('configure_process.app_id') + ': ' + appType);
@@ -72,7 +88,9 @@ async function run(args) {
72
88
  warn(' ❌ ' + t('configure_process.file_not_found') + ': ' + processDefinitionFile);
73
89
  throw new CliError(t('configure_process.file_not_found') + ': ' + processDefinitionFile, {
74
90
  code: 'CONFIGURE_PROCESS_FILE_NOT_FOUND',
75
- details: { processDefinitionFile },
91
+ details: stageTracker.failure('read_definition', {
92
+ context: context(),
93
+ }),
76
94
  });
77
95
  }
78
96
 
@@ -83,49 +101,65 @@ async function run(args) {
83
101
  warn(' ❌ ' + t('configure_process.parse_failed') + ': ' + e.message);
84
102
  throw new CliError(t('configure_process.parse_failed') + ': ' + e.message, {
85
103
  code: 'CONFIGURE_PROCESS_INVALID_JSON',
86
- details: { processDefinitionFile },
104
+ details: stageTracker.failure('read_definition', {
105
+ context: context(),
106
+ cause: e,
107
+ }),
87
108
  });
88
109
  }
89
110
  warn(' ✅ ' + t('configure_process.definition_loaded') + ' (' + (definition.nodes || []).length + ' ' + t('configure_process.nodes') + ')');
111
+ stageTracker.complete('read_definition');
90
112
 
91
113
  // 2. 读取登录态
92
114
  warn('\n🔑 ' + t('configure_process.loading_auth') + '...');
93
115
  const authRef = createAuthRef();
94
116
  warn(' ✅ ' + t('configure_process.auth_loaded') + ', baseUrl: ' + authRef.baseUrl);
117
+ stageTracker.complete('load_auth');
95
118
 
96
119
  // 3. 获取 processCode
97
120
  warn('\n🔍 ' + t('configure_process.getting_process_code') + '...');
98
- let processCode = processCodeArg;
99
121
 
100
122
  if (!processCode) {
101
- // 确保表单是流程表单
102
- warn(' ' + t('configure_process.switching_form_type') + '...');
103
- const switchResult = await switchFormType(authRef, appType, formUuid);
104
- if (switchResult.success) {
105
- warn(' ✅ ' + t('configure_process.switch_success'));
106
- } else {
107
- const switchMsg = switchResult.errorMsg || '';
108
- if (switchMsg.indexOf('已转换') >= 0 || switchMsg.indexOf('已经是') >= 0) {
109
- warn(' ✅ ' + t('configure_process.already_process'));
123
+ try {
124
+ // 确保表单是流程表单
125
+ warn(' ' + t('configure_process.switching_form_type') + '...');
126
+ const switchResult = await switchFormType(authRef, appType, formUuid);
127
+ if (switchResult.success) {
128
+ warn(' ✅ ' + t('configure_process.switch_success'));
129
+ stageTracker.complete('switch_form_type');
110
130
  } else {
111
- warn(' ⚠️ ' + t('configure_process.switch_warning') + ': ' + switchMsg);
131
+ const switchMsg = switchResult.errorMsg || '';
132
+ if (switchMsg.indexOf('已转换') >= 0 || switchMsg.indexOf('已经是') >= 0) {
133
+ warn(' ✅ ' + t('configure_process.already_process'));
134
+ stageTracker.complete('switch_form_type');
135
+ } else {
136
+ warn(' ⚠️ ' + t('configure_process.switch_warning') + ': ' + switchMsg);
137
+ }
112
138
  }
113
- }
114
139
 
115
- // 方法 1: 从精确表单流程绑定接口提取
116
- warn(' ' + t('configure_process.method1') + '...');
117
- processCode = await getProcessCodeFromAppParam(authRef, appType, formUuid);
118
- if (processCode) {
119
- warn(' ✅ ' + t('configure_process.got_process_code') + ': ' + processCode);
120
- }
121
-
122
- // 方法 2: 从 getFormSchema 中提取
123
- if (!processCode) {
124
- warn(' ' + t('configure_process.method2') + '...');
125
- processCode = await getProcessCodeFromSchema(authRef, appType, formUuid);
140
+ // 方法 1: 从精确表单流程绑定接口提取
141
+ warn(' ' + t('configure_process.method1') + '...');
142
+ processCode = await getProcessCodeFromAppParam(authRef, appType, formUuid);
126
143
  if (processCode) {
127
- warn(' ✅ ' + t('configure_process.got_from_schema') + ': ' + processCode);
144
+ warn(' ✅ ' + t('configure_process.got_process_code') + ': ' + processCode);
128
145
  }
146
+
147
+ // 方法 2: 从 getFormSchema 中提取
148
+ if (!processCode) {
149
+ warn(' ' + t('configure_process.method2') + '...');
150
+ processCode = await getProcessCodeFromSchema(authRef, appType, formUuid);
151
+ if (processCode) {
152
+ warn(' ✅ ' + t('configure_process.got_from_schema') + ': ' + processCode);
153
+ }
154
+ }
155
+ } catch (lookupError) {
156
+ throw new CliError(t('configure_process.no_process_code') + ': ' + lookupError.message, {
157
+ code: 'CONFIGURE_PROCESS_CODE_LOOKUP_FAILED',
158
+ details: stageTracker.failure('resolve_process_code', {
159
+ context: context(),
160
+ cause: lookupError,
161
+ }),
162
+ });
129
163
  }
130
164
  }
131
165
 
@@ -134,42 +168,55 @@ async function run(args) {
134
168
  warn(' 💡 ' + t('configure_process.manual_hint'));
135
169
  throw new CliError(t('configure_process.no_process_code'), {
136
170
  code: 'CONFIGURE_PROCESS_CODE_NOT_FOUND',
137
- details: { appType, formUuid },
171
+ details: stageTracker.failure('resolve_process_code', {
172
+ context: context(),
173
+ }),
138
174
  });
139
175
  }
140
176
  warn(' ✅ processCode: ' + processCode);
177
+ stageTracker.complete('resolve_process_code');
141
178
 
142
179
  // 4. 查询流程版本列表
143
180
  warn('\n🔍 ' + t('configure_process.querying_versions') + '...');
144
- const publishedResult = await queryProcessVersions(authRef, appType, processCode, 'PUBLISHED');
181
+ let publishedResult;
145
182
 
146
183
  let latestProcessId = null;
147
184
  let latestVersion = 0;
148
185
 
149
- if (publishedResult.success && publishedResult.content && publishedResult.content.data && publishedResult.content.data.length > 0) {
150
- const publishedVersion = publishedResult.content.data[0];
151
- latestProcessId = publishedVersion.id;
152
- latestVersion = parseInt(publishedVersion.version, 10);
153
- warn(' ✅ ' + t('configure_process.found_published') + ': processId=' + latestProcessId + ', version=' + latestVersion);
154
- } else {
155
- warn(' ℹ️ ' + t('configure_process.no_published') + '...');
156
- const allVersionsResult = await queryProcessVersions(authRef, appType, processCode, '');
157
- if (allVersionsResult.success && allVersionsResult.content && allVersionsResult.content.data && allVersionsResult.content.data.length > 0) {
158
- const latestItem = allVersionsResult.content.data[0];
159
- latestProcessId = latestItem.id;
160
- latestVersion = parseInt(latestItem.version, 10);
161
- warn(' ✅ ' + t('configure_process.found_latest') + ': processId=' + latestProcessId + ', version=' + latestVersion);
186
+ try {
187
+ publishedResult = await queryProcessVersions(authRef, appType, processCode, 'PUBLISHED');
188
+ if (publishedResult.success && publishedResult.content && publishedResult.content.data && publishedResult.content.data.length > 0) {
189
+ const publishedVersion = publishedResult.content.data[0];
190
+ latestProcessId = publishedVersion.id;
191
+ latestVersion = parseInt(publishedVersion.version, 10);
192
+ warn(' ✅ ' + t('configure_process.found_published') + ': processId=' + latestProcessId + ', version=' + latestVersion);
193
+ } else {
194
+ warn(' ℹ️ ' + t('configure_process.no_published') + '...');
195
+ const allVersionsResult = await queryProcessVersions(authRef, appType, processCode, '');
196
+ if (allVersionsResult.success && allVersionsResult.content && allVersionsResult.content.data && allVersionsResult.content.data.length > 0) {
197
+ const latestItem = allVersionsResult.content.data[0];
198
+ latestProcessId = latestItem.id;
199
+ latestVersion = parseInt(latestItem.version, 10);
200
+ warn(' ✅ ' + t('configure_process.found_latest') + ': processId=' + latestProcessId + ', version=' + latestVersion);
201
+ }
162
202
  }
203
+ } catch (versionError) {
204
+ throw new CliError(t('configure_process.querying_versions') + ': ' + versionError.message, {
205
+ code: 'CONFIGURE_PROCESS_QUERY_VERSIONS_FAILED',
206
+ details: stageTracker.failure('query_process_versions', {
207
+ context: context(),
208
+ cause: versionError,
209
+ }),
210
+ });
163
211
  }
212
+ stageTracker.complete('query_process_versions');
164
213
 
165
- const newVersion = latestVersion + 1;
214
+ newVersion = latestVersion + 1;
166
215
 
167
216
  // 5. 创建新流程版本草稿
168
217
  warn('\n📝 ' + t('configure_process.creating_draft') + '...');
169
218
  const draftResult = await newDraftProcess(authRef, appType, processCode, formUuid, latestProcessId, newVersion);
170
219
 
171
- let newProcessId = null;
172
-
173
220
  if (draftResult.success && draftResult.content && draftResult.content.processId) {
174
221
  // content 是对象,包含 processId 字段
175
222
  newProcessId = draftResult.content.processId;
@@ -194,7 +241,10 @@ async function run(args) {
194
241
  warn(' ❌ ' + t('configure_process.draft_failed') + ': ' + errorMsg);
195
242
  throw new CliError(t('configure_process.draft_failed') + ': ' + errorMsg, {
196
243
  code: 'CONFIGURE_PROCESS_DRAFT_FAILED',
197
- details: { appType, formUuid, processCode, result: draftResult },
244
+ details: stageTracker.failure('create_draft', {
245
+ context: context(),
246
+ cause: draftResult,
247
+ }),
198
248
  });
199
249
  }
200
250
 
@@ -202,24 +252,40 @@ async function run(args) {
202
252
  warn(' ❌ ' + t('configure_process.no_draft_id'));
203
253
  throw new CliError(t('configure_process.no_draft_id'), {
204
254
  code: 'CONFIGURE_PROCESS_DRAFT_ID_NOT_FOUND',
205
- details: { appType, formUuid, processCode, draftResult },
255
+ details: stageTracker.failure('create_draft', {
256
+ context: context(),
257
+ cause: draftResult,
258
+ }),
206
259
  });
207
260
  }
261
+ stageTracker.complete('create_draft');
208
262
 
209
263
  // 6. 构建 processJson 和 viewJson
210
264
  warn('\n🏗️ ' + t('configure_process.building_json') + '...');
211
- const result = buildProcessAndViewJson(
212
- definition,
213
- processCode,
214
- formUuid,
215
- authRef.baseUrl,
216
- appType,
217
- { onWarning: warn }
218
- );
265
+ let result;
266
+ try {
267
+ result = buildProcessAndViewJson(
268
+ definition,
269
+ processCode,
270
+ formUuid,
271
+ authRef.baseUrl,
272
+ appType,
273
+ { onWarning: warn }
274
+ );
275
+ } catch (buildError) {
276
+ throw new CliError(t('configure_process.building_json') + ': ' + buildError.message, {
277
+ code: 'CONFIGURE_PROCESS_BUILD_FAILED',
278
+ details: stageTracker.failure('build_definition', {
279
+ context: context(),
280
+ cause: buildError,
281
+ }),
282
+ });
283
+ }
219
284
  const processJsonStr = JSON.stringify(result.processJson);
220
285
  const viewJsonStr = JSON.stringify(result.viewJson);
221
286
  warn(' ✅ processJson: ' + processJsonStr.length + ' chars');
222
287
  warn(' ✅ viewJson: ' + viewJsonStr.length + ' chars');
288
+ stageTracker.complete('build_definition');
223
289
 
224
290
  // 7. 保存流程
225
291
  warn('\n💾 ' + t('configure_process.saving') + '...');
@@ -235,9 +301,13 @@ async function run(args) {
235
301
  warn(' ❌ ' + t('configure_process.save_failed') + ': ' + errorMsg);
236
302
  throw new CliError(t('configure_process.save_failed') + ': ' + errorMsg, {
237
303
  code: 'CONFIGURE_PROCESS_SAVE_FAILED',
238
- details: { appType, formUuid, processCode, processId: newProcessId, result: saveResult },
304
+ details: stageTracker.failure('save_definition', {
305
+ context: context(),
306
+ cause: saveResult,
307
+ }),
239
308
  });
240
309
  }
310
+ stageTracker.complete('save_definition');
241
311
 
242
312
  // 8. 发布流程
243
313
  warn('\n🚀 ' + t('configure_process.publishing') + '...');
@@ -252,9 +322,13 @@ async function run(args) {
252
322
  warn(' ❌ ' + t('configure_process.publish_failed') + ': ' + errorMsg);
253
323
  throw new CliError(t('configure_process.publish_failed') + ': ' + errorMsg, {
254
324
  code: 'CONFIGURE_PROCESS_PUBLISH_FAILED',
255
- details: { appType, formUuid, processCode, processId: newProcessId, result: publishResult },
325
+ details: stageTracker.failure('publish_process', {
326
+ context: context(),
327
+ cause: publishResult,
328
+ }),
256
329
  });
257
330
  }
331
+ stageTracker.complete('publish_process');
258
332
 
259
333
  // 9. 输出结果
260
334
  const output = {
@@ -20,6 +20,7 @@ const { createAuthRef, isAuthRefReady } = require('../core/yida-client');
20
20
  const { t } = require('../core/i18n');
21
21
  const { warn } = require('../core/chalk');
22
22
  const { createFormForLegacyProcess } = require('../app/create-form');
23
+ const { createStageTracker, summarizeRemoteResult } = require('./process-diagnostics');
23
24
  const {
24
25
  getProcessCodeFromAppParam,
25
26
  getProcessCodeFromSchema,
@@ -86,6 +87,50 @@ async function run(args) {
86
87
  const useExistingForm = !!existingFormUuid;
87
88
  const fieldsJsonFile = parsed.fieldsJsonFile ? path.resolve(parsed.fieldsJsonFile) : null;
88
89
  const processDefinitionFile = path.resolve(parsed.processDefinitionFile);
90
+ const stageTracker = createStageTracker();
91
+ let formUuid;
92
+ let fieldCount = 0;
93
+
94
+ function context(extra) {
95
+ return Object.assign({
96
+ appType,
97
+ formUuid: formUuid || existingFormUuid || null,
98
+ processDefinitionFile,
99
+ }, extra || {});
100
+ }
101
+
102
+ function buildRetryCommand() {
103
+ return 'openyida create-process ' + appType + ' --formUuid ' + formUuid + ' ' + path.basename(processDefinitionFile);
104
+ }
105
+
106
+ function buildConfigureFailureDetails(configError) {
107
+ const retryCommand = buildRetryCommand();
108
+ const outerDetails = stageTracker.failure('configure_process', {
109
+ context: context({ processCode, retryCommand }),
110
+ cause: configError,
111
+ });
112
+ const innerDetails = configError && configError.isCliError && configError.details && configError.details.stage
113
+ ? configError.details
114
+ : null;
115
+ if (!innerDetails) {
116
+ return outerDetails;
117
+ }
118
+ const configureProcess = {
119
+ stage: innerDetails.stage,
120
+ completedStages: Array.isArray(innerDetails.completedStages)
121
+ ? innerDetails.completedStages.slice()
122
+ : [],
123
+ };
124
+ if (innerDetails.nextStep) {
125
+ configureProcess.nextStep = innerDetails.nextStep;
126
+ }
127
+ return Object.assign({}, outerDetails, {
128
+ stage: innerDetails.stage,
129
+ nextStep: innerDetails.nextStep || outerDetails.nextStep,
130
+ createProcessStage: 'configure_process',
131
+ configureProcess,
132
+ });
133
+ }
89
134
 
90
135
  warn('═'.repeat(60));
91
136
  warn(' 🔧 ' + t('create_process.title'));
@@ -107,14 +152,18 @@ async function run(args) {
107
152
  warn(' ❌ ' + t('create_process.fields_not_found') + ': ' + fieldsJsonFile);
108
153
  throw new CliError(t('create_process.fields_not_found') + ': ' + fieldsJsonFile, {
109
154
  code: 'CREATE_PROCESS_FIELDS_NOT_FOUND',
110
- details: { fieldsJsonFile },
155
+ details: stageTracker.failure('validate_inputs', {
156
+ context: context({ fieldsJsonFile }),
157
+ }),
111
158
  });
112
159
  }
113
160
  if (!fs.existsSync(processDefinitionFile)) {
114
161
  warn(' ❌ ' + t('create_process.process_def_not_found') + ': ' + processDefinitionFile);
115
162
  throw new CliError(t('create_process.process_def_not_found') + ': ' + processDefinitionFile, {
116
163
  code: 'CREATE_PROCESS_DEFINITION_NOT_FOUND',
117
- details: { processDefinitionFile },
164
+ details: stageTracker.failure('validate_inputs', {
165
+ context: context(),
166
+ }),
118
167
  });
119
168
  }
120
169
 
@@ -124,18 +173,20 @@ async function run(args) {
124
173
  if (!isAuthRefReady(authRef)) {
125
174
  throw new CliError('未获取到有效宜搭登录态,请先执行 openyida login', {
126
175
  code: 'NEED_LOGIN',
176
+ details: stageTracker.failure('load_auth', {
177
+ context: context(),
178
+ }),
127
179
  });
128
180
  }
129
181
  warn(' ✅ ' + t('create_process.auth_loaded') + ', baseUrl: ' + authRef.baseUrl);
182
+ stageTracker.complete('load_auth');
130
183
 
131
184
  // Step 1: 创建或复用表单
132
- let formUuid;
133
- let fieldCount = 0;
134
-
135
185
  if (useExistingForm) {
136
186
  warn('\n📋 Step 1: ' + t('create_process.reusing_form') + '...');
137
187
  formUuid = existingFormUuid;
138
188
  warn(' ✅ ' + t('create_process.using_form') + ': ' + formUuid);
189
+ stageTracker.complete('reuse_form');
139
190
  } else {
140
191
  warn('\n📋 Step 1: ' + t('create_process.creating_form') + '...');
141
192
  try {
@@ -146,23 +197,34 @@ async function run(args) {
146
197
  });
147
198
 
148
199
  if (!createFormResult || !createFormResult.success || !createFormResult.formUuid) {
149
- warn(' ❌ ' + t('create_process.create_form_failed') + ': ' + JSON.stringify(createFormResult));
200
+ warn(' ❌ ' + t('create_process.create_form_failed') + ': ' + JSON.stringify(summarizeRemoteResult(createFormResult)));
150
201
  throw new CliError(t('create_process.create_form_failed'), {
151
202
  code: 'CREATE_PROCESS_CREATE_FORM_FAILED',
152
- details: createFormResult,
203
+ details: stageTracker.failure('create_form', {
204
+ context: context({ formTitle, fieldsJsonFile }),
205
+ cause: createFormResult,
206
+ }),
153
207
  });
154
208
  }
155
209
 
156
210
  formUuid = createFormResult.formUuid;
157
211
  fieldCount = createFormResult.fieldCount || 0;
158
212
  warn(' ✅ ' + t('create_process.form_created') + ': ' + formUuid);
213
+ stageTracker.complete('create_form');
159
214
  } catch (createError) {
160
215
  warn(' ❌ ' + t('create_process.create_form_failed') + ': ' + createError.message);
161
216
  if (createError.stderr) {
162
217
  warn(' ' + createError.stderr.substring(0, 1000));
163
218
  }
219
+ if (createError && createError.isCliError && createError.details && createError.details.stage) {
220
+ throw createError;
221
+ }
164
222
  throw new CliError(t('create_process.create_form_failed') + ': ' + createError.message, {
165
223
  code: 'CREATE_PROCESS_CREATE_FORM_FAILED',
224
+ details: stageTracker.failure('create_form', {
225
+ context: context({ formTitle, fieldsJsonFile }),
226
+ cause: createError,
227
+ }),
166
228
  });
167
229
  }
168
230
  }
@@ -180,32 +242,49 @@ async function run(args) {
180
242
  warn(' ❌ ' + t('create_process.switch_failed') + ': ' + switchMsg);
181
243
  throw new CliError(t('create_process.switch_failed') + ': ' + switchMsg, {
182
244
  code: 'CREATE_PROCESS_SWITCH_FAILED',
183
- details: switchResult,
245
+ details: stageTracker.failure('switch_form_type', {
246
+ context: context(),
247
+ cause: switchResult,
248
+ }),
184
249
  });
185
250
  }
186
251
  }
252
+ stageTracker.complete('switch_form_type');
187
253
 
188
254
  // Step 3: 获取 processCode
189
255
  warn('\n🔍 Step 3: ' + t('create_process.getting_process_code') + '...');
190
256
  let processCode = null;
191
257
 
192
- // 方法 1: 从精确表单流程绑定接口提取
193
- warn(' ' + t('create_process.method1') + '...');
194
- processCode = await getProcessCodeFromAppParam(authRef, appType, formUuid);
195
- if (processCode) {
196
- warn(' ✅ ' + t('create_process.got_process_code') + ': ' + processCode);
197
- }
198
-
199
- // 方法 2: 从 getFormSchema 中提取
200
- if (!processCode) {
201
- warn(' ' + t('create_process.method2') + '...');
202
- processCode = await getProcessCodeFromSchema(authRef, appType, formUuid);
258
+ try {
259
+ // 方法 1: 从精确表单流程绑定接口提取
260
+ warn(' ' + t('create_process.method1') + '...');
261
+ processCode = await getProcessCodeFromAppParam(authRef, appType, formUuid);
203
262
  if (processCode) {
204
- warn(' ✅ ' + t('create_process.got_from_schema') + ': ' + processCode);
263
+ warn(' ✅ ' + t('create_process.got_process_code') + ': ' + processCode);
264
+ }
265
+
266
+ // 方法 2: 从 getFormSchema 中提取
267
+ if (!processCode) {
268
+ warn(' ' + t('create_process.method2') + '...');
269
+ processCode = await getProcessCodeFromSchema(authRef, appType, formUuid);
270
+ if (processCode) {
271
+ warn(' ✅ ' + t('create_process.got_from_schema') + ': ' + processCode);
272
+ }
205
273
  }
274
+ } catch (lookupError) {
275
+ throw new CliError(t('create_process.no_process_code') + ': ' + lookupError.message, {
276
+ code: 'CREATE_PROCESS_CODE_LOOKUP_FAILED',
277
+ details: stageTracker.failure('resolve_process_code', {
278
+ context: context(),
279
+ cause: lookupError,
280
+ }),
281
+ });
206
282
  }
207
283
 
208
284
  if (!processCode) {
285
+ const failureDetails = stageTracker.failure('resolve_process_code', {
286
+ context: context(),
287
+ });
209
288
  warn(' ❌ ' + t('create_process.no_process_code'));
210
289
  warn(' 💡 ' + t('create_process.manual_hint', formUuid));
211
290
  console.log(JSON.stringify({
@@ -215,24 +294,30 @@ async function run(args) {
215
294
  appType: appType,
216
295
  fieldCount: fieldCount,
217
296
  error: t('create_process.no_process_code'),
297
+ stage: failureDetails.stage,
298
+ completedStages: failureDetails.completedStages,
299
+ nextStep: failureDetails.nextStep,
218
300
  }));
219
301
  throw new CliError(t('create_process.no_process_code'), {
220
302
  code: 'CREATE_PROCESS_CODE_NOT_FOUND',
221
- details: { appType, formUuid },
303
+ details: failureDetails,
222
304
  });
223
305
  }
306
+ stageTracker.complete('resolve_process_code');
224
307
 
225
308
  // Step 4: 配置并发布流程
226
309
  warn('\n⚙️ Step 4: ' + t('create_process.configuring_process') + '...');
227
310
  try {
228
311
  const { run: runConfigureProcess } = require('./configure-process');
229
312
  await runConfigureProcess([appType, formUuid, processDefinitionFile, processCode]);
313
+ stageTracker.complete('configure_process');
230
314
  } catch (configError) {
315
+ const failureDetails = buildConfigureFailureDetails(configError);
231
316
  warn(' ❌ ' + t('create_process.configure_failed') + ': ' + configError.message);
232
317
  // 提示用户可以使用 --formUuid 复用已创建的表单
233
318
  warn('');
234
319
  warn(' 💡 ' + t('create_process.retry_hint'));
235
- warn(' openyida create-process ' + appType + ' --formUuid ' + formUuid + ' ' + path.basename(processDefinitionFile));
320
+ warn(' ' + buildRetryCommand());
236
321
  warn('');
237
322
  console.log(JSON.stringify({
238
323
  success: false,
@@ -241,10 +326,15 @@ async function run(args) {
241
326
  appType: appType,
242
327
  fieldCount: fieldCount,
243
328
  error: t('create_process.configure_failed') + ': ' + configError.message,
329
+ stage: failureDetails.stage,
330
+ completedStages: failureDetails.completedStages,
331
+ nextStep: failureDetails.nextStep,
332
+ retryCommand: failureDetails.context && failureDetails.context.retryCommand,
333
+ configureProcess: failureDetails.configureProcess,
244
334
  }));
245
335
  throw new CliError(t('create_process.configure_failed') + ': ' + configError.message, {
246
336
  code: 'CREATE_PROCESS_CONFIGURE_FAILED',
247
- details: { appType, formUuid, processCode },
337
+ details: failureDetails,
248
338
  });
249
339
  }
250
340
 
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_TEXT_LIMIT = 240;
4
+ const DEFAULT_KEY_LIMIT = 12;
5
+
6
+ function truncateText(value, limit = DEFAULT_TEXT_LIMIT) {
7
+ const text = value === undefined || value === null ? '' : String(value);
8
+ if (text.length <= limit) {
9
+ return text;
10
+ }
11
+ return text.slice(0, Math.max(0, limit - 3)) + '...';
12
+ }
13
+
14
+ function isPrimitive(value) {
15
+ return value === null || ['string', 'number', 'boolean'].includes(typeof value);
16
+ }
17
+
18
+ function summarizeContent(content) {
19
+ if (content === undefined) {
20
+ return undefined;
21
+ }
22
+ if (content === null) {
23
+ return null;
24
+ }
25
+ if (typeof content === 'string') {
26
+ return { type: 'string', sample: truncateText(content) };
27
+ }
28
+ if (Array.isArray(content)) {
29
+ return { type: 'array', length: content.length };
30
+ }
31
+ if (typeof content !== 'object') {
32
+ return content;
33
+ }
34
+
35
+ const summary = { type: 'object' };
36
+ ['processId', 'processCode', 'formUuid', 'version', 'id', 'status'].forEach((key) => {
37
+ if (isPrimitive(content[key])) {
38
+ summary[key] = content[key];
39
+ }
40
+ });
41
+ if (Array.isArray(content.data)) {
42
+ summary.dataLength = content.data.length;
43
+ }
44
+ const keys = Object.keys(content).slice(0, DEFAULT_KEY_LIMIT);
45
+ if (keys.length > 0) {
46
+ summary.keys = keys;
47
+ }
48
+ return summary;
49
+ }
50
+
51
+ function summarizeRemoteResult(result) {
52
+ if (result === undefined) {
53
+ return undefined;
54
+ }
55
+ if (result === null) {
56
+ return null;
57
+ }
58
+ if (result instanceof Error) {
59
+ const summary = {
60
+ name: result.name,
61
+ message: truncateText(result.message),
62
+ };
63
+ if (result.code) {
64
+ summary.code = result.code;
65
+ }
66
+ if (result.details && typeof result.details === 'object') {
67
+ ['stage', 'nextStep'].forEach((key) => {
68
+ if (isPrimitive(result.details[key])) {
69
+ summary[key] = result.details[key];
70
+ }
71
+ });
72
+ if (Array.isArray(result.details.completedStages)) {
73
+ summary.completedStages = result.details.completedStages.slice();
74
+ }
75
+ }
76
+ return summary;
77
+ }
78
+ if (isPrimitive(result)) {
79
+ return truncateText(result);
80
+ }
81
+ if (Array.isArray(result)) {
82
+ return { type: 'array', length: result.length };
83
+ }
84
+ if (typeof result !== 'object') {
85
+ return String(result);
86
+ }
87
+
88
+ const summary = {};
89
+ [
90
+ 'success',
91
+ 'errorCode',
92
+ 'errorMsg',
93
+ 'message',
94
+ 'code',
95
+ 'status',
96
+ 'statusCode',
97
+ 'throwable',
98
+ 'requestId',
99
+ '__needLogin',
100
+ '__csrfExpired',
101
+ 'stage',
102
+ 'nextStep',
103
+ ].forEach((key) => {
104
+ if (isPrimitive(result[key])) {
105
+ summary[key] = typeof result[key] === 'string'
106
+ ? truncateText(result[key])
107
+ : result[key];
108
+ }
109
+ });
110
+ if (Array.isArray(result.completedStages)) {
111
+ summary.completedStages = result.completedStages.slice();
112
+ }
113
+ if (result.content !== undefined) {
114
+ summary.content = summarizeContent(result.content);
115
+ }
116
+ const included = new Set(Object.keys(summary).concat(['content']));
117
+ const keys = Object.keys(result).filter(key => !included.has(key)).slice(0, DEFAULT_KEY_LIMIT);
118
+ if (keys.length > 0) {
119
+ summary.keys = keys;
120
+ }
121
+ return summary;
122
+ }
123
+
124
+ function createStageTracker() {
125
+ const completedStages = [];
126
+ return {
127
+ complete(stage) {
128
+ if (stage && !completedStages.includes(stage)) {
129
+ completedStages.push(stage);
130
+ }
131
+ },
132
+ getCompletedStages() {
133
+ return completedStages.slice();
134
+ },
135
+ failure(stage, options = {}) {
136
+ const details = {
137
+ stage,
138
+ completedStages: completedStages.slice(),
139
+ nextStep: options.nextStep || defaultNextStep(stage),
140
+ };
141
+ if (options.context && typeof options.context === 'object') {
142
+ details.context = options.context;
143
+ }
144
+ if (options.cause !== undefined) {
145
+ details.cause = summarizeRemoteResult(options.cause);
146
+ }
147
+ return details;
148
+ },
149
+ };
150
+ }
151
+
152
+ function defaultNextStep(stage) {
153
+ switch (stage) {
154
+ case 'validate_inputs':
155
+ return '检查命令参数和本地 JSON 文件路径后重试。';
156
+ case 'load_auth':
157
+ return '执行 openyida login 后重试。';
158
+ case 'create_form':
159
+ return '检查表单字段 JSON 后重试;如果已创建表单,可改用 --formUuid 复用。';
160
+ case 'reuse_form':
161
+ return '确认 formUuid 属于当前应用后重试。';
162
+ case 'switch_form_type':
163
+ return '确认当前账号有表单管理权限,或在宜搭控制台检查表单类型。';
164
+ case 'resolve_process_code':
165
+ return '确认表单已转为流程表单;必要时先运行 configure-process 并显式传入 processCode。';
166
+ case 'configure_process':
167
+ return '修正流程定义后使用 --formUuid 复用已创建表单重试。';
168
+ case 'read_definition':
169
+ return '检查流程定义 JSON 文件内容后重试。';
170
+ case 'query_process_versions':
171
+ return '确认 processCode 有效,并检查账号流程管理权限。';
172
+ case 'create_draft':
173
+ return '确认流程可编辑并检查账号流程管理权限后重试。';
174
+ case 'build_definition':
175
+ return '修正流程节点 DSL 后重试。';
176
+ case 'save_definition':
177
+ return '检查流程节点配置、字段引用和账号权限后重试。';
178
+ case 'publish_process':
179
+ return '确认流程草稿已保存成功,检查发布权限后重试。';
180
+ default:
181
+ return '按错误信息修正后重试。';
182
+ }
183
+ }
184
+
185
+ module.exports = {
186
+ createStageTracker,
187
+ summarizeRemoteResult,
188
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.7.25-beta.2",
3
+ "version": "2026.7.25-beta.3",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",