@cnbcool/cnb-api-generate 1.2.7 → 2.0.0

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 (36) hide show
  1. package/built/codegen/printer/printer-skills-client-core.js +10 -0
  2. package/built/utils/clean-array-desc.js +9 -0
  3. package/built/utils/collect-used-keys.js +16 -0
  4. package/built/utils/flat-option.js +2 -0
  5. package/built/utils/flatten-array-object-options.js +35 -0
  6. package/built/utils/flatten-nested-object-options.js +40 -0
  7. package/built/utils/flatten-tool-options.js +108 -0
  8. package/built/utils/generate-flat-options.js +50 -0
  9. package/built/utils/is-array-of-objects.js +10 -0
  10. package/built/utils/is-nested-object.js +9 -0
  11. package/built/utils/option-value-flag.js +14 -0
  12. package/built/utils/to-display-key.js +10 -0
  13. package/built/utils/trim-summary.js +12 -0
  14. package/client/index.ts +29 -504
  15. package/client/lib/execute-action.ts +126 -0
  16. package/client/lib/extra-help.ts +15 -0
  17. package/client/lib/flat-options-data.ts +13 -0
  18. package/client/lib/format-output.ts +78 -0
  19. package/client/lib/format-params.ts +220 -0
  20. package/client/lib/help-data.ts +13 -0
  21. package/client/lib/key-mapping-data.ts +13 -0
  22. package/client/lib/parsers.ts +130 -0
  23. package/client/lib/register-fallback.ts +14 -0
  24. package/client/lib/register-modules.ts +121 -0
  25. package/client/lib/summary-extractors.ts +189 -0
  26. package/client/lib/trim-summary.ts +11 -0
  27. package/client/shortcuts.ts +10 -198
  28. package/client/utils/build-nested-field-map.ts +38 -0
  29. package/client/utils/flat-key-from-segments.ts +8 -0
  30. package/client/utils/match-array-object-field.ts +19 -0
  31. package/client/utils/restore-original-keys.ts +23 -0
  32. package/package.json +3 -2
  33. package/skills-template/SKILL.md +28 -15
  34. package/client/modules.help.ts +0 -49
  35. package/client/schemaToJson.ts +0 -26
  36. package/client/tools.help.ts +0 -124
@@ -0,0 +1,189 @@
1
+ /**
2
+ * 摘要输出
3
+ * 对返回数据量大的快捷命令,默认只输出核心字段,减少 token 消耗
4
+ */
5
+
6
+ type SummaryExtractor = (item: any) => any;
7
+
8
+ /**
9
+ * 摘要提取器配置
10
+ * key: "module/tool",value: 摘要提取函数
11
+ * - 对象响应:直接对 data 应用提取函数
12
+ * - 数组响应:对 data 中每个元素应用提取函数
13
+ */
14
+ const SUMMARY_EXTRACTORS: Record<string, SummaryExtractor> = {
15
+ // Issue 详情摘要:保留标题、状态、标签和正文
16
+ // 过滤: number, state_reason, assignees, author, created_at, updated_at 等
17
+ 'issues/get-issue': (item) => ({
18
+ title: item.title,
19
+ state: item.state,
20
+ labels: item.labels?.map?.((l: any) => l.name).filter(Boolean) || [],
21
+ body: item.body,
22
+ }),
23
+
24
+ // Issue 更新摘要:只保留状态变更结果
25
+ // 过滤: title, body, labels, assignees, author, created_at, updated_at 等
26
+ 'issues/update-issue': (item) => ({
27
+ number: item.number,
28
+ state: item.state,
29
+ state_reason: item.state_reason,
30
+ }),
31
+
32
+ // Issue 评论列表摘要:只保留作者用户名、内容和创建时间
33
+ // 过滤: author 完整对象(avatar/email/freeze/is_npc/nickname), reactions, updated_at
34
+ 'issues/list-issue-comments': (item) => ({
35
+ id: item.id,
36
+ author: item.author?.username,
37
+ body: item.body,
38
+ created_at: item.created_at,
39
+ }),
40
+
41
+ // Issue 发表评论摘要:只保留评论 ID 和创建时间(body 已在请求中传入,无需重复)
42
+ // 过滤: body(请求已传入), author 完整对象(avatar/email/freeze/is_npc/nickname/username), reactions, updated_at
43
+ 'issues/post-issue-comment': (item) => ({
44
+ id: item.id,
45
+ created_at: item.created_at,
46
+ }),
47
+
48
+ // Issue 标签列表摘要:保留名称、颜色和描述
49
+ // 过滤: id
50
+ 'issues/list-issue-labels': (item) => ({
51
+ name: item.name,
52
+ color: item.color,
53
+ description: item.description,
54
+ }),
55
+
56
+ // Issue 添加标签摘要:只保留 ID 和颜色(name 已在请求中传入,无需重复)
57
+ // 过滤: name(请求已传入), description
58
+ 'issues/post-issue-labels': (item) => ({
59
+ id: item.id,
60
+ color: item.color,
61
+ }),
62
+
63
+ // Issue 处理人列表摘要:保留用户名和昵称
64
+ // 过滤: avatar, email, freeze, is_npc
65
+ 'issues/list-issue-assignees': (item) => ({
66
+ username: item.username,
67
+ nickname: item.nickname,
68
+ }),
69
+
70
+ // Issue 添加处理人摘要:保留处理人用户名和昵称列表
71
+ // 过滤: assignees 中每个用户的 avatar/email/freeze/is_npc,以及 issue 其他字段
72
+ 'issues/post-issue-assignees': (item) => ({
73
+ number: item.number,
74
+ assignees: item.assignees?.map?.((a: any) => ({ username: a.username, nickname: a.nickname })).filter((a: any) => a.username) || [],
75
+ }),
76
+
77
+ // PR 详情摘要:保留标题、状态、标签、正文和分支信息
78
+ // 过滤: number, author, assignees, reviewers, created_at, updated_at, base/head 完整对象等
79
+ 'pulls/get-pull': (item) => ({
80
+ title: item.title,
81
+ state: item.state,
82
+ labels: item.labels?.map?.((l: any) => l.name).filter(Boolean) || [],
83
+ base: item.base?.ref,
84
+ head: item.head?.ref,
85
+ body: item.body,
86
+ }),
87
+
88
+ // PR 文件列表摘要:保留文件名、sha、变更状态和增删行数
89
+ // 过滤: patch, blob_url, raw_url, contents_url, previous_filename
90
+ 'pulls/list-pull-files': (item) => ({
91
+ filename: item.filename,
92
+ sha: item.sha,
93
+ status: item.status,
94
+ additions: item.additions,
95
+ deletions: item.deletions,
96
+ }),
97
+
98
+ // PR 提交列表摘要:只保留 sha 前 8 位和 commit message
99
+ // 过滤: sha 完整值, commit 完整对象(author/committer/tree/verification), parents, url 等
100
+ 'pulls/list-pull-commits': (item) => ({
101
+ sha: typeof item.sha === 'string' ? item.sha.substring(0, 8) : item.sha,
102
+ message: item.commit?.message,
103
+ }),
104
+
105
+ // PR 评论列表摘要:保留作者用户名和昵称、内容和创建时间
106
+ // 过滤: author 完整对象(avatar/email/freeze/is_npc), reactions, updated_at
107
+ 'pulls/list-pull-comments': (item) => ({
108
+ id: item.id,
109
+ author: item.author?.username,
110
+ nickname: item.author?.nickname,
111
+ body: item.body,
112
+ created_at: item.created_at,
113
+ }),
114
+
115
+ // PR 发表评论摘要:只保留评论 ID 和创建时间(body 已在请求中传入,无需重复)
116
+ // 过滤: body(请求已传入), author 完整对象(avatar/email/freeze/is_npc/nickname/username), reactions, updated_at
117
+ 'pulls/post-pull-comment': (item) => ({
118
+ id: item.id,
119
+ created_at: item.created_at,
120
+ }),
121
+
122
+ // PR 标签列表摘要:保留 ID、名称、颜色和描述
123
+ // 过滤: 无
124
+ 'pulls/list-pull-labels': (item) => ({
125
+ id: item.id,
126
+ name: item.name,
127
+ color: item.color,
128
+ description: item.description,
129
+ }),
130
+
131
+ // PR 添加标签摘要:只保留 ID 和颜色(name 已在请求中传入,无需重复)
132
+ // 过滤: name(请求已传入), description
133
+ 'pulls/post-pull-labels': (item) => ({
134
+ id: item.id,
135
+ color: item.color,
136
+ }),
137
+
138
+ // PR CI 状态摘要:保留整体状态和各检查项的核心信息(sha 截断为前 8 位)
139
+ // 过滤: sha 完整值, statuses 中每项的 target_url/created_at/updated_at
140
+ 'pulls/list-pull-commit-statuses': (item) => ({
141
+ sha: typeof item.sha === 'string' ? item.sha.substring(0, 8) : item.sha,
142
+ state: item.state,
143
+ statuses: item.statuses?.map?.((s: any) => ({
144
+ context: s.context,
145
+ state: s.state,
146
+ description: s.description,
147
+ })) || [],
148
+ }),
149
+
150
+ // PR 评审列表摘要:保留作者用户名和昵称、状态和内容
151
+ // 过滤: author 完整对象(avatar/email/freeze/is_npc), created_at, updated_at
152
+ 'pulls/list-pull-reviews': (item) => ({
153
+ id: item.id,
154
+ author: item.author?.username,
155
+ nickname: item.author?.nickname,
156
+ state: item.state,
157
+ body: item.body,
158
+ }),
159
+
160
+ // PR 处理人列表摘要:保留用户名和昵称
161
+ // 过滤: avatar, email, freeze, is_npc
162
+ 'pulls/list-pull-assignees': (item) => ({
163
+ username: item.username,
164
+ nickname: item.nickname,
165
+ }),
166
+
167
+ // PR 添加处理人摘要:保留处理人用户名和昵称列表
168
+ // 过滤: assignees 中每个用户的 avatar/email/freeze/is_npc,以及 PR 其他字段
169
+ 'pulls/post-pull-assignees': (item) => ({
170
+ number: item.number,
171
+ assignees: item.assignees?.map?.((a: any) => ({ username: a.username, nickname: a.nickname })).filter((a: any) => a.username) || [],
172
+ }),
173
+ };
174
+
175
+ /**
176
+ * 对响应数据应用摘要提取(如果当前 tool 配置了摘要提取器)
177
+ * @param data 原始 data(对象或数组)
178
+ * @param toolKey 当前 tool 标识,格式为 "module/tool"
179
+ * @returns 摘要后的 data,如果无匹配提取器则返回 null
180
+ */
181
+ export function summarizeResponse(data: any, toolKey: string): any | null {
182
+ const extractor = SUMMARY_EXTRACTORS[toolKey];
183
+ if (!extractor) return null;
184
+ if (data == null) return data;
185
+ if (Array.isArray(data)) {
186
+ return data.map(extractor);
187
+ }
188
+ return extractor(data);
189
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 去除 summary 中的英文部分(保留中文句号前的内容)
3
+ */
4
+ export function trimSummary(summary: string): string {
5
+ if (!summary) return '';
6
+ // 匹配中文句号"。"后面跟英文的模式,只保留中文部分
7
+ const match = summary.match(/^(.*?[\u4e00-\u9fff].*?)[。.]\s*[A-Z]/);
8
+ if (match) return match[1];
9
+ // 没有匹配到则返回原文
10
+ return summary;
11
+ }
@@ -70,15 +70,15 @@ function getPRNumber(): string {
70
70
  const ISSUE_SHORTCUTS: ShortcutDefinition[] = [
71
71
  { shortName: 'get', realTool: 'get-issue', description: '获取详情' },
72
72
  { shortName: 'list-comments', realTool: 'list-issue-comments', description: '获取评论列表' },
73
- { shortName: 'comment', realTool: 'post-issue-comment', description: '评论', dataTip: "--data '{\"body\":\"内容\"}'" },
73
+ { shortName: 'comment', realTool: 'post-issue-comment', description: '评论', dataTip: "--body 内容" },
74
74
  { shortName: 'close', realTool: 'update-issue', description: '关闭', autoData: { state: 'closed', state_reason: 'completed' } },
75
75
  { shortName: 'open', realTool: 'update-issue', description: '打开', autoData: { state: 'open', state_reason: 'reopened' } },
76
76
  { shortName: 'list-labels', realTool: 'list-issue-labels', description: '查看标签' },
77
- { shortName: 'add-labels', realTool: 'post-issue-labels', description: '添加标签', dataTip: "--data '{\"labels\":[\"bug\",\"feature\"]}'" },
77
+ { shortName: 'add-labels', realTool: 'post-issue-labels', description: '添加标签', dataTip: "--labels bug --labels feature" },
78
78
  { shortName: 'list-assignees', realTool: 'list-issue-assignees', description: '查看处理人' },
79
- { shortName: 'add-assignees', realTool: 'post-issue-assignees', description: '添加处理人', dataTip: "--data '{\"assignees\":[\"username\"]}'" },
80
- { shortName: 'upload-file', realTool: 'post-issue-file-asset-upload-url', description: '上传文件', upload: true, dataTip: "--data '{\"file\":\"文件路径\"}'" },
81
- { shortName: 'upload-image', realTool: 'post-issue-image-asset-upload-url', description: '上传图片', upload: true, dataTip: "--data '{\"file\":\"图片路径\"}'" },
79
+ { shortName: 'add-assignees', realTool: 'post-issue-assignees', description: '添加处理人', dataTip: "--assignees username" },
80
+ { shortName: 'upload-file', realTool: 'post-issue-file-asset-upload-url', description: '上传文件', upload: true, dataTip: "--file 文件路径" },
81
+ { shortName: 'upload-image', realTool: 'post-issue-image-asset-upload-url', description: '上传图片', upload: true, dataTip: "--file 图片路径" },
82
82
  ];
83
83
 
84
84
  const PR_SHORTCUTS: ShortcutDefinition[] = [
@@ -86,14 +86,14 @@ const PR_SHORTCUTS: ShortcutDefinition[] = [
86
86
  { shortName: 'list-files', realTool: 'list-pull-files', description: '获取文件变更' },
87
87
  { shortName: 'list-commits', realTool: 'list-pull-commits', description: '获取提交记录' },
88
88
  { shortName: 'list-comments', realTool: 'list-pull-comments', description: '获取评论列表' },
89
- { shortName: 'comment', realTool: 'post-pull-comment', description: '评论', dataTip: "--data '{\"body\":\"内容\"}'" },
89
+ { shortName: 'comment', realTool: 'post-pull-comment', description: '评论', dataTip: "--body 内容" },
90
90
  { shortName: 'list-labels', realTool: 'list-pull-labels', description: '查看标签' },
91
- { shortName: 'add-labels', realTool: 'post-pull-labels', description: '添加标签', dataTip: "--data '{\"labels\":[\"ready\",\"approved\"]}'" },
91
+ { shortName: 'add-labels', realTool: 'post-pull-labels', description: '添加标签', dataTip: "--labels ready --labels approved" },
92
92
  { shortName: 'check-status', realTool: 'list-pull-commit-statuses', description: '查看 CI 状态' },
93
93
  { shortName: 'list-reviews', realTool: 'list-pull-reviews', description: '查看评审列表' },
94
94
  { shortName: 'list-assignees', realTool: 'list-pull-assignees', description: '查看处理人' },
95
- { shortName: 'upload-file', realTool: 'upload-files', description: '上传文件', repoOnly: true, upload: true, dataTip: "--data '{\"file\":\"文件路径\"}'" },
96
- { shortName: 'upload-image', realTool: 'upload-imgs', description: '上传图片', repoOnly: true, upload: true, dataTip: "--data '{\"file\":\"图片路径\"}'" },
95
+ { shortName: 'upload-file', realTool: 'upload-files', description: '上传文件', repoOnly: true, upload: true, dataTip: "--file 文件路径" },
96
+ { shortName: 'upload-image', realTool: 'upload-imgs', description: '上传图片', repoOnly: true, upload: true, dataTip: "--file 图片路径" },
97
97
  ];
98
98
 
99
99
  // ============================================================
@@ -199,7 +199,7 @@ ${prList}
199
199
  CNB_ISSUE_IID - Issue 编号(Issue 相关快捷命令)
200
200
  CNB_PULL_REQUEST_IID - PR 编号(PR 相关快捷命令)
201
201
 
202
- 默认只输出摘要信息,添加 --verbose 可输出全部信息
202
+ 默认只输出摘要信息,添加 --v 可输出全部信息
203
203
  `);
204
204
  }
205
205
  }
@@ -247,191 +247,3 @@ export function resolveShortcut(
247
247
  };
248
248
  }
249
249
 
250
- // ============================================================
251
- // 摘要输出
252
- // 对返回数据量大的快捷命令,默认只输出核心字段,减少 token 消耗
253
- // ============================================================
254
-
255
- type SummaryExtractor = (item: any) => any;
256
-
257
- /**
258
- * 摘要提取器配置
259
- * key: "module/tool",value: 摘要提取函数
260
- * - 对象响应:直接对 data 应用提取函数
261
- * - 数组响应:对 data 中每个元素应用提取函数
262
- */
263
- const SUMMARY_EXTRACTORS: Record<string, SummaryExtractor> = {
264
- // Issue 详情摘要:保留标题、状态、标签和正文
265
- // 过滤: number, state_reason, assignees, author, created_at, updated_at 等
266
- 'issues/get-issue': (item) => ({
267
- title: item.title,
268
- state: item.state,
269
- labels: item.labels?.map?.((l: any) => l.name).filter(Boolean) || [],
270
- body: item.body,
271
- }),
272
-
273
- // Issue 更新摘要:只保留状态变更结果
274
- // 过滤: title, body, labels, assignees, author, created_at, updated_at 等
275
- 'issues/update-issue': (item) => ({
276
- number: item.number,
277
- state: item.state,
278
- state_reason: item.state_reason,
279
- }),
280
-
281
- // Issue 评论列表摘要:只保留作者用户名、内容和创建时间
282
- // 过滤: author 完整对象(avatar/email/freeze/is_npc/nickname), reactions, updated_at
283
- 'issues/list-issue-comments': (item) => ({
284
- id: item.id,
285
- author: item.author?.username,
286
- body: item.body,
287
- created_at: item.created_at,
288
- }),
289
-
290
- // Issue 发表评论摘要:只保留评论 ID 和创建时间(body 已在请求中传入,无需重复)
291
- // 过滤: body(请求已传入), author 完整对象(avatar/email/freeze/is_npc/nickname/username), reactions, updated_at
292
- 'issues/post-issue-comment': (item) => ({
293
- id: item.id,
294
- created_at: item.created_at,
295
- }),
296
-
297
- // Issue 标签列表摘要:保留名称、颜色和描述
298
- // 过滤: id
299
- 'issues/list-issue-labels': (item) => ({
300
- name: item.name,
301
- color: item.color,
302
- description: item.description,
303
- }),
304
-
305
- // Issue 添加标签摘要:只保留 ID 和颜色(name 已在请求中传入,无需重复)
306
- // 过滤: name(请求已传入), description
307
- 'issues/post-issue-labels': (item) => ({
308
- id: item.id,
309
- color: item.color,
310
- }),
311
-
312
- // Issue 处理人列表摘要:保留用户名和昵称
313
- // 过滤: avatar, email, freeze, is_npc
314
- 'issues/list-issue-assignees': (item) => ({
315
- username: item.username,
316
- nickname: item.nickname,
317
- }),
318
-
319
- // Issue 添加处理人摘要:保留处理人用户名和昵称列表
320
- // 过滤: assignees 中每个用户的 avatar/email/freeze/is_npc,以及 issue 其他字段
321
- 'issues/post-issue-assignees': (item) => ({
322
- number: item.number,
323
- assignees: item.assignees?.map?.((a: any) => ({ username: a.username, nickname: a.nickname })).filter((a: any) => a.username) || [],
324
- }),
325
-
326
- // PR 详情摘要:保留标题、状态、标签、正文和分支信息
327
- // 过滤: number, author, assignees, reviewers, created_at, updated_at, base/head 完整对象等
328
- 'pulls/get-pull': (item) => ({
329
- title: item.title,
330
- state: item.state,
331
- labels: item.labels?.map?.((l: any) => l.name).filter(Boolean) || [],
332
- base: item.base?.ref,
333
- head: item.head?.ref,
334
- body: item.body,
335
- }),
336
-
337
- // PR 文件列表摘要:保留文件名、sha、变更状态和增删行数
338
- // 过滤: patch, blob_url, raw_url, contents_url, previous_filename
339
- 'pulls/list-pull-files': (item) => ({
340
- filename: item.filename,
341
- sha: item.sha,
342
- status: item.status,
343
- additions: item.additions,
344
- deletions: item.deletions,
345
- }),
346
-
347
- // PR 提交列表摘要:只保留 sha 前 8 位和 commit message
348
- // 过滤: sha 完整值, commit 完整对象(author/committer/tree/verification), parents, url 等
349
- 'pulls/list-pull-commits': (item) => ({
350
- sha: typeof item.sha === 'string' ? item.sha.substring(0, 8) : item.sha,
351
- message: item.commit?.message,
352
- }),
353
-
354
- // PR 评论列表摘要:保留作者用户名和昵称、内容和创建时间
355
- // 过滤: author 完整对象(avatar/email/freeze/is_npc), reactions, updated_at
356
- 'pulls/list-pull-comments': (item) => ({
357
- id: item.id,
358
- author: item.author?.username,
359
- nickname: item.author?.nickname,
360
- body: item.body,
361
- created_at: item.created_at,
362
- }),
363
-
364
- // PR 发表评论摘要:只保留评论 ID 和创建时间(body 已在请求中传入,无需重复)
365
- // 过滤: body(请求已传入), author 完整对象(avatar/email/freeze/is_npc/nickname/username), reactions, updated_at
366
- 'pulls/post-pull-comment': (item) => ({
367
- id: item.id,
368
- created_at: item.created_at,
369
- }),
370
-
371
- // PR 标签列表摘要:保留 ID、名称、颜色和描述
372
- // 过滤: 无
373
- 'pulls/list-pull-labels': (item) => ({
374
- id: item.id,
375
- name: item.name,
376
- color: item.color,
377
- description: item.description,
378
- }),
379
-
380
- // PR 添加标签摘要:只保留 ID 和颜色(name 已在请求中传入,无需重复)
381
- // 过滤: name(请求已传入), description
382
- 'pulls/post-pull-labels': (item) => ({
383
- id: item.id,
384
- color: item.color,
385
- }),
386
-
387
- // PR CI 状态摘要:保留整体状态和各检查项的核心信息(sha 截断为前 8 位)
388
- // 过滤: sha 完整值, statuses 中每项的 target_url/created_at/updated_at
389
- 'pulls/list-pull-commit-statuses': (item) => ({
390
- sha: typeof item.sha === 'string' ? item.sha.substring(0, 8) : item.sha,
391
- state: item.state,
392
- statuses: item.statuses?.map?.((s: any) => ({
393
- context: s.context,
394
- state: s.state,
395
- description: s.description,
396
- })) || [],
397
- }),
398
-
399
- // PR 评审列表摘要:保留作者用户名和昵称、状态和内容
400
- // 过滤: author 完整对象(avatar/email/freeze/is_npc), created_at, updated_at
401
- 'pulls/list-pull-reviews': (item) => ({
402
- id: item.id,
403
- author: item.author?.username,
404
- nickname: item.author?.nickname,
405
- state: item.state,
406
- body: item.body,
407
- }),
408
-
409
- // PR 处理人列表摘要:保留用户名和昵称
410
- // 过滤: avatar, email, freeze, is_npc
411
- 'pulls/list-pull-assignees': (item) => ({
412
- username: item.username,
413
- nickname: item.nickname,
414
- }),
415
-
416
- // PR 添加处理人摘要:保留处理人用户名和昵称列表
417
- // 过滤: assignees 中每个用户的 avatar/email/freeze/is_npc,以及 PR 其他字段
418
- 'pulls/post-pull-assignees': (item) => ({
419
- number: item.number,
420
- assignees: item.assignees?.map?.((a: any) => ({ username: a.username, nickname: a.nickname })).filter((a: any) => a.username) || [],
421
- }),
422
- };
423
-
424
- /**
425
- * 对响应数据应用摘要提取(如果当前 tool 配置了摘要提取器)
426
- * @param data 原始 data(对象或数组)
427
- * @param toolKey 当前 tool 标识,格式为 "module/tool"
428
- * @returns 摘要后的 data,如果无匹配提取器则返回 null
429
- */
430
- export function summarizeResponse(data: any, toolKey: string): any | null {
431
- const extractor = SUMMARY_EXTRACTORS[toolKey];
432
- if (!extractor) return null;
433
- if (Array.isArray(data)) {
434
- return data.map(extractor);
435
- }
436
- return extractor(data);
437
- }
@@ -0,0 +1,38 @@
1
+ export interface NestedFieldMapping {
2
+ pathKeys: string[]; // schema 路径,如 ["testBody", "critical_count", "id"]
3
+ leafSchema: any; // 叶子节点的 schema 定义
4
+ }
5
+
6
+ /**
7
+ * 递归遍历嵌套对象的 schema,生成所有叶子节点的 flat key 映射
8
+ * @param props 当前层级的 properties
9
+ * @param prefix 当前层级的 CLI flat key 前缀(用 @ 连接),如 "testBody"
10
+ * @param pathKeys 当前层级的 schema 路径数组,如 ["testBody"]
11
+ * @returns Map<flatKey, NestedFieldMapping>
12
+ */
13
+ export function buildNestedFieldMap(
14
+ props: Record<string, any>,
15
+ prefix: string,
16
+ pathKeys: string[],
17
+ ): Map<string, NestedFieldMapping> {
18
+ const map = new Map<string, NestedFieldMapping>();
19
+
20
+ for (const [subKey, subProp] of Object.entries(props) as [string, any][]) {
21
+ const flatKey = `${prefix}@${subKey}`;
22
+ const currentPath = [...pathKeys, subKey];
23
+
24
+ if (subProp.type === 'object' && subProp.properties && Object.keys(subProp.properties).length > 0) {
25
+ // 嵌套对象:继续递归
26
+ const subMap = buildNestedFieldMap(subProp.properties, flatKey, currentPath);
27
+ for (const [k, v] of subMap) {
28
+ map.set(k, v);
29
+ }
30
+ } else {
31
+ // 叶子节点:注册 flat key
32
+ const camelKey = flatKey;
33
+ map.set(camelKey, { pathKeys: currentPath, leafSchema: subProp });
34
+ }
35
+ }
36
+
37
+ return map;
38
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 生成 CLI flat key:按 '@' 连接层级
3
+ * 例如 "testBody@critical_count@id"
4
+ * Commander 不会对 '@' 做 camelCase 转换,所以 key 保持原样
5
+ */
6
+ export function flatKeyFromSegments(str: string): string {
7
+ return str;
8
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 检测是否为 array<object> 展开字段(格式:bodyArrayKey@subPropName)
3
+ * Commander 不会对 '@' 做 camelCase,所以直接匹配原始 key
4
+ */
5
+ export function matchArrayObjectField(
6
+ camelKey: string,
7
+ bodyProps: Record<string, any>,
8
+ ): { arrayKey: string; subKey: string } | null {
9
+ for (const [rootKey, rootProp] of Object.entries(bodyProps) as [string, any][]) {
10
+ if (rootProp.type !== 'array' || !rootProp.items?.properties) continue;
11
+ for (const subKey of Object.keys(rootProp.items.properties)) {
12
+ const flatKey = `${rootKey}@${subKey}`;
13
+ if (flatKey === camelKey) {
14
+ return { arrayKey: rootKey, subKey };
15
+ }
16
+ }
17
+ }
18
+ return null;
19
+ }
@@ -0,0 +1,23 @@
1
+ import { keyMappingData } from '../lib/key-mapping-data';
2
+
3
+ /**
4
+ * 将 CLI 传入的带 '-' 的参数名还原为原始的 '_' 或 '@' 参数名
5
+ * 通过 key-mapping.json 中的映射表进行查找还原
6
+ */
7
+ export function restoreOriginalKeys(
8
+ params: Record<string, string | string[] | boolean | undefined>,
9
+ ): Record<string, string | string[] | boolean | undefined> {
10
+ const moduleName = params.module as string;
11
+ const toolName = params.tool as string;
12
+ if (!moduleName || !toolName) return params;
13
+
14
+ const toolMapping = keyMappingData[moduleName]?.[toolName];
15
+ if (!toolMapping) return params;
16
+
17
+ const restored: Record<string, string | string[] | boolean | undefined> = {};
18
+ for (const [key, value] of Object.entries(params)) {
19
+ const originalKey = toolMapping[key];
20
+ restored[originalKey || key] = value;
21
+ }
22
+ return restored;
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cnbcool/cnb-api-generate",
3
- "version": "1.2.7",
3
+ "version": "2.0.0",
4
4
  "main": "./built/index.js",
5
5
  "module": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -37,6 +37,7 @@
37
37
  "@types/lodash": "4.17.20",
38
38
  "babel-plugin-transform-define": "^2.1.4",
39
39
  "cli-progress": "^3.12.0",
40
+ "commander": "^14.0.3",
40
41
  "debug": "4.4.1",
41
42
  "glob": "^13.0.1",
42
43
  "lodash": "^4.17.23",
@@ -47,7 +48,6 @@
47
48
  },
48
49
  "devDependencies": {
49
50
  "@reduxjs/toolkit": "^1.9.5",
50
- "eslint-config-tencent": "^1.1.3",
51
51
  "@types/debug": "4.1.12",
52
52
  "@types/jest": "^29.5.3",
53
53
  "@types/node": "24.0.12",
@@ -56,6 +56,7 @@
56
56
  "axios": "^1.13.6",
57
57
  "eslint": "8.57.0",
58
58
  "eslint-config-prettier": "9.1.0",
59
+ "eslint-config-tencent": "^1.1.3",
59
60
  "eslint-config-turbo": "2.2.3",
60
61
  "eslint-plugin-prettier": "5.2.1",
61
62
  "jest": "^29.6.1",
@@ -9,26 +9,39 @@ description: CNB OpenAPI 交互能力,支持仓库、Issue、PR、流水线、
9
9
 
10
10
  ## 快捷命令(优先使用)
11
11
 
12
- 可用快捷命令:
13
- - `issues`: `get`, `list-comments`, `comment`, `close`, `open`, `list-labels`, `add-labels`, `list-assignees`, `add-assignees`, `upload-file`, `upload-image`
14
- - `pulls`: `get`, `list-files`, `list-commits`, `list-comments`, `comment`, `list-labels`, `add-labels`, `check-status`, `list-reviews`, `list-assignees`, `upload-file`, `upload-image`
12
+ issues 快捷命令:
13
+ - `<$CNB_CLI_CMD$> issues get` 获取 Issue 详情
14
+ - `<$CNB_CLI_CMD$> issues list-comments` 列出 Issue 评论
15
+ - `<$CNB_CLI_CMD$> issues comment --data '{"body":"内容"}'` — 发表 Issue 评论
16
+ - `<$CNB_CLI_CMD$> issues close` — 关闭 Issue
17
+ - `<$CNB_CLI_CMD$> issues open` — 打开 Issue
18
+ - `<$CNB_CLI_CMD$> issues list-labels` — 列出 Issue 标签
19
+ - `<$CNB_CLI_CMD$> issues add-labels --data '{"labels":["标签"]}'` — 添加 Issue 标签
20
+ - `<$CNB_CLI_CMD$> issues list-assignees` — 查看 Issue 处理人
21
+ - `<$CNB_CLI_CMD$> issues add-assignees --data '{"assignees":["用户名"]}'` — 添加 Issue 处理人
22
+ - `<$CNB_CLI_CMD$> issues upload-file --data '{"file":"本地文件路径"}'` — 上传文件到 Issue
23
+ - `<$CNB_CLI_CMD$> issues upload-image --data '{"file":"本地图片路径"}'` — 上传图片到 Issue
24
+
25
+ pulls 快捷命令:
26
+ - `<$CNB_CLI_CMD$> pulls get` — 获取 PR 详情
27
+ - `<$CNB_CLI_CMD$> pulls list-files` — 列出 PR 变更文件
28
+ - `<$CNB_CLI_CMD$> pulls list-commits` — 列出 PR 提交记录
29
+ - `<$CNB_CLI_CMD$> pulls list-comments` — 列出 PR 评论
30
+ - `<$CNB_CLI_CMD$> pulls comment --data '{"body":"内容"}'` — 发表 PR 评论
31
+ - `<$CNB_CLI_CMD$> pulls list-labels` — 列出 PR 标签
32
+ - `<$CNB_CLI_CMD$> pulls add-labels --data '{"labels":["标签"]}'` — 添加 PR 标签
33
+ - `<$CNB_CLI_CMD$> pulls check-status` — 查看 CI 状态
34
+ - `<$CNB_CLI_CMD$> pulls list-reviews` — 查看评审列表
35
+ - `<$CNB_CLI_CMD$> pulls list-assignees` — 查看 PR 处理人
36
+ - `<$CNB_CLI_CMD$> pulls add-assignees --data '{"assignees":["用户名"]}'` — 添加 PR 处理人
37
+ - `<$CNB_CLI_CMD$> pulls upload-file --data '{"file":"本地文件路径"}'` — 上传文件到 PR
38
+ - `<$CNB_CLI_CMD$> pulls upload-image --data '{"file":"本地图片路径"}'` — 上传图片到 PR
15
39
 
16
40
  注意:
17
- - **路径参数自动注入**:仓库 slug、Issue/PR 编号优先从环境变量自动获取,无需额外传入
41
+ - **路径参数自动注入**:仓库 slug、Issue/PR 编号优先从环境变量自动获取,无需额外传入任何其他参数
18
42
  - **默认只输出摘要**:会精简响应输出结果,只返回核心字段。加 `--verbose` 输出完整数据。
19
43
  - 快捷命令只能操作当前仓库的当前Issue或PR。跨仓库或跨编号操作请使用其他 API 命令。
20
44
 
21
- 用法: `<$CNB_CLI_CMD$> <module> <command>`
22
- - ✅ `<$CNB_CLI_CMD$> issues get`
23
- - ✅ `<$CNB_CLI_CMD$> pulls comment --data '{"body":"内容"}'`
24
-
25
- 需要 `--data` 的快捷命令:
26
- - `comment`: `--data '{"body":"内容"}'`
27
- - `add-labels`: `--data '{"labels":["标签"]}'`
28
- - `add-assignees`: `--data '{"assignees":["用户名"]}'`
29
- - `upload-file`: `--data '{"file":"本地文件路径"}'`(自动完成上传全流程)
30
- - `upload-image`: `--data '{"file":"本地图片路径"}'`(自动完成上传全流程)
31
-
32
45
  ## 其他 API(快捷命令不满足时使用)
33
46
 
34
47
  1. `<$CNB_CLI_CMD$> --help` 查看所有模块