@cnbcool/cnb-api-generate 2.5.1 → 2.6.1

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.
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateShortcutsConfig = generateShortcutsConfig;
7
+ const debug_1 = __importDefault(require("debug"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const get_skills_codegen_target_1 = require("../utils/get-skills-codegen-target");
11
+ const logger = (0, debug_1.default)('csg:shortcuts-config');
12
+ /**
13
+ * 把解析后的快捷命令配置写入 client 运行时可读取的 JSON。
14
+ * 文件路径为 `<skillScriptDir>/core/shortcuts.config.json`,
15
+ * 与编译后的 `core/shortcuts.js` 同级,运行时通过相对路径读取。
16
+ *
17
+ * 写入的字段就是 ResolvedQuickCommand 透传后的内容(包含 module 冗余字段,
18
+ * 以及配置里给的 shortName / realTool / description / repoOnly / upload /
19
+ * custom / autoData / dataTip);下游 client/shortcuts.ts 只用其中的
20
+ * 运行时字段,文档相关字段被忽略也不影响。
21
+ */
22
+ async function generateShortcutsConfig(byModule) {
23
+ const skillScriptDir = (0, get_skills_codegen_target_1.getSkillScriptCodegenTarget)();
24
+ const skillScriptCoreDir = path_1.default.join(skillScriptDir, 'core');
25
+ if (!fs_1.default.existsSync(skillScriptCoreDir)) {
26
+ fs_1.default.mkdirSync(skillScriptCoreDir, { recursive: true });
27
+ }
28
+ const targetFile = path_1.default.join(skillScriptCoreDir, 'shortcuts.config.json');
29
+ fs_1.default.writeFileSync(targetFile, JSON.stringify(byModule, null, 2));
30
+ const total = Object.values(byModule).reduce((n, arr) => n + arr.length, 0);
31
+ logger(`wrote ${targetFile} (${Object.keys(byModule).length} modules, ${total} commands)`);
32
+ }
@@ -10,8 +10,14 @@ const fs_1 = __importDefault(require("fs"));
10
10
  const get_skills_codegen_target_1 = require("../utils/get-skills-codegen-target");
11
11
  const get_config_1 = require("../utils/get-config");
12
12
  const skills_1 = require("../constants/skills");
13
+ const render_quick_commands_md_1 = require("./render-quick-commands-md");
13
14
  const logger = (0, debug_1.default)('csg:skills');
14
- async function generateSkillMd(helpData, requestMap) {
15
+ async function generateSkillMd(
16
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
+ _helpData,
18
+ // requestMap 由调用方传入但当前未使用,保留以维持稳定签名
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ _requestMap, quickCommandsByModule) {
15
21
  const skillMDTempateDir = path_1.default.join(__dirname, '../../skills-template');
16
22
  const skillMDDir = path_1.default.join((0, get_skills_codegen_target_1.getSkillCodegenTarget)());
17
23
  // 复制skills.md
@@ -19,7 +25,16 @@ async function generateSkillMd(helpData, requestMap) {
19
25
  const targetSkillMD = path_1.default.join(skillMDDir, 'SKILL.md');
20
26
  const skillMDContent = fs_1.default.readFileSync(SKILL_MD, 'utf8');
21
27
  const config = (0, get_config_1.getConfig)();
22
- const newSkillMDContent = skillMDContent.replaceAll('<$CNB_CLI_CMD$>', config.skillsDev ? skills_1.CNB_CLI_DEV_CMD : skills_1.CNB_CLI_PROD_CMD);
28
+ const cliCmd = config.skillsDev ? skills_1.CNB_CLI_DEV_CMD : skills_1.CNB_CLI_PROD_CMD;
29
+ // 仅替换两类占位符:
30
+ // - <$QUICK_COMMANDS$>:配置驱动的快捷命令清单(标题/注意事项写在模板里即可)
31
+ // - <$CNB_CLI_CMD$> :CLI 命令前缀
32
+ const quickCommandsMd = (0, render_quick_commands_md_1.renderQuickCommandsMarkdown)(quickCommandsByModule, cliCmd);
33
+ const total = Object.values(quickCommandsByModule).reduce((n, arr) => n + arr.length, 0);
34
+ logger(`quick commands rendered: ${Object.keys(quickCommandsByModule).length} modules, ${total} commands`);
35
+ const newSkillMDContent = skillMDContent
36
+ .replaceAll('<$QUICK_COMMANDS$>', quickCommandsMd)
37
+ .replaceAll('<$CNB_CLI_CMD$>', cliCmd);
23
38
  fs_1.default.writeFileSync(targetSkillMD, newSkillMDContent);
24
39
  logger(`generate ${SKILL_MD} to ${targetSkillMD}`);
25
40
  }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderQuickCommandsMarkdown = renderQuickCommandsMarkdown;
4
+ /**
5
+ * 把解析后的快捷命令渲染为 markdown 文本,对应模板里的 <$QUICK_COMMANDS$> 占位符。
6
+ *
7
+ * 仅渲染命令列表本身——"## 快捷命令" 标题、"## 注意事项" 等静态文案直接
8
+ * 写在 skills-template/SKILL.md 模板里,没必要再加占位符。
9
+ *
10
+ * 渲染规则:
11
+ * - 每个模块先输出 `<moduleName>:` 标题行
12
+ * - 每条命令渲染为 ``- `<cliCmd> <module> <shortName>[ <dataTip>]` — <description>`` 的列表项
13
+ * - 没有任何模块/命令时返回空字符串(让模板里的占位符消失)
14
+ */
15
+ function renderQuickCommandsMarkdown(byModule, cliCmd) {
16
+ const moduleNames = Object.keys(byModule);
17
+ if (!moduleNames.length)
18
+ return '';
19
+ const lines = [];
20
+ for (const moduleName of moduleNames) {
21
+ const items = byModule[moduleName];
22
+ if (!items.length)
23
+ continue;
24
+ lines.push(`${moduleName}:`);
25
+ for (const cmd of items) {
26
+ const dataPart = cmd.dataTip ? ` ${cmd.dataTip}` : '';
27
+ const cmdLine = `${cliCmd} ${moduleName} ${cmd.shortName}${dataPart}`;
28
+ lines.push(`- \`${cmdLine}\` — ${cmd.description}`);
29
+ }
30
+ lines.push('');
31
+ }
32
+ // 去掉末尾多余空行
33
+ while (lines.length && lines[lines.length - 1] === '') {
34
+ lines.pop();
35
+ }
36
+ return lines.join('\n');
37
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveQuickCommands = resolveQuickCommands;
7
+ const debug_1 = __importDefault(require("debug"));
8
+ const get_config_1 = require("../utils/get-config");
9
+ const logger = (0, debug_1.default)('csg:quick-commands');
10
+ /**
11
+ * 把 cag.config.js 的 quickCommands 配置 + helpData 元数据,
12
+ * 解析为下游(client 产物 / SKILL.md 渲染)共用的结构化数据。
13
+ *
14
+ * 关键校验:
15
+ * - 非 custom 命令的 realTool 必须能在 helpData.modulesHelp 中命中,
16
+ * 否则立即抛错,防止 swagger 改名/下线后产物默默漂移。
17
+ * - 校验时按"任一 category 命中"匹配(realTool 在 swagger 里全局唯一即可,
18
+ * 不强制要求 module 名等于 swagger category)。
19
+ */
20
+ function resolveQuickCommands(helpData) {
21
+ const { quickCommands } = (0, get_config_1.getConfig)();
22
+ if (!quickCommands)
23
+ return {};
24
+ const { modulesHelp } = helpData;
25
+ // 建一个 realTool → true 的全局集合,便于 O(1) 校验
26
+ const allTools = new Set();
27
+ for (const category of Object.values(modulesHelp)) {
28
+ for (const filename of Object.keys(category)) {
29
+ allTools.add(filename);
30
+ }
31
+ }
32
+ const result = {};
33
+ for (const [moduleName, items] of Object.entries(quickCommands)) {
34
+ if (!Array.isArray(items)) {
35
+ throw new Error(`[quickCommands] 模块 "${moduleName}" 的值必须是数组,请检查 cag.config.js。`);
36
+ }
37
+ const resolved = [];
38
+ for (const item of items) {
39
+ if (!(item === null || item === void 0 ? void 0 : item.shortName) || !(item === null || item === void 0 ? void 0 : item.realTool) || !(item === null || item === void 0 ? void 0 : item.description)) {
40
+ throw new Error(`[quickCommands] 模块 "${moduleName}" 下存在缺失必填字段(shortName / realTool / description)的命令:` +
41
+ JSON.stringify(item));
42
+ }
43
+ // custom 命令跳过 swagger 校验
44
+ if (!item.custom && !allTools.has(item.realTool)) {
45
+ throw new Error(`[quickCommands] 命令 "${moduleName} ${item.shortName}" 的 realTool="${item.realTool}" ` +
46
+ `在 swagger 生成结果中不存在。请检查 cag.config.js 是否与最新 swagger 同步,` +
47
+ `或对自定义命令显式标记 custom: true。`);
48
+ }
49
+ resolved.push({ ...item, module: moduleName });
50
+ logger(`resolved: ${moduleName} ${item.shortName} -> ${item.realTool}`);
51
+ }
52
+ result[moduleName] = resolved;
53
+ }
54
+ return result;
55
+ }
package/built/skills.js CHANGED
@@ -13,6 +13,8 @@ const generate_skill_cli_scripts_1 = require("./skills-tools/generate-skill-cli-
13
13
  const generate_skill_md_1 = require("./skills-tools/generate-skill-md");
14
14
  const generate_config_1 = require("./skills-tools/generate-config");
15
15
  const printer_skills_client_loader_1 = require("./codegen/printer/printer-skills-client-loader");
16
+ const resolve_quick_commands_1 = require("./skills-tools/resolve-quick-commands");
17
+ const generate_shortcuts_config_1 = require("./skills-tools/generate-shortcuts-config");
16
18
  const logger = (0, debug_1.default)('csg:main');
17
19
  async function start() {
18
20
  process.env.CNB_GENERATE_MODE = 'skills';
@@ -31,8 +33,12 @@ async function start() {
31
33
  const helpData = (0, generate_skill_cli_help_1.generateSkillCliHelp)(requestMap, defintionsMap);
32
34
  // 编译core
33
35
  await (0, printer_skills_client_core_1.printerSkillsClientCore)(helpData);
34
- // 编译skills.md
35
- await (0, generate_skill_md_1.generateSkillMd)(helpData, requestMap);
36
+ // 解析快捷命令配置(cag.config.js → 校验 → 结构化),同时驱动 client 运行时数据与 SKILL.md 文档
37
+ const quickCommandsByModule = (0, resolve_quick_commands_1.resolveQuickCommands)(helpData);
38
+ // 写入 client 运行时配置(core/shortcuts.config.json),替代 client/shortcuts.ts 中的硬编码常量
39
+ await (0, generate_shortcuts_config_1.generateShortcutsConfig)(quickCommandsByModule);
40
+ // 编译skills.md(使用同一份解析结果渲染 <$QUICK_COMMANDS$>)
41
+ await (0, generate_skill_md_1.generateSkillMd)(helpData, requestMap, quickCommandsByModule);
36
42
  // 生成config文件
37
43
  await (0, generate_config_1.generateConfig)();
38
44
  }
package/client/core.ts CHANGED
@@ -135,6 +135,8 @@ async function clientFetch(data: any): Promise<any> {
135
135
  headers: {
136
136
  Authorization: `Bearer ${token}`,
137
137
  Accept: 'application/vnd.cnb.api+json',
138
+ // 当请求体存在时,附加 Content-Type,避免后端无法识别 JSON body
139
+ ...(data.data ? { 'Content-Type': 'application/vnd.cnb.api+json' } : {}),
138
140
  ...(data?.header || {}),
139
141
  },
140
142
  });
@@ -31,7 +31,10 @@ export async function fetchResponseHandler(fetchOriginParams: Record<string, any
31
31
  try {
32
32
  const converterExample = loadPlugin(converter);
33
33
  if (converterExample && typeof converterExample === 'function') {
34
- return handler(converterExample, fetchOriginParams, response.data);
34
+ // handler 返回的是处理后的 data,应回填到 response.data
35
+ // 然后返回完整的 response 对象(保留 status/trace/header/page 等包装字段)
36
+ const convertedData = await handler(converterExample, fetchOriginParams, response.data);
37
+ return { ...response, data: convertedData };
35
38
  }
36
39
  } catch (e) {
37
40
  console.error(`converter ${converter} not found`);
@@ -70,7 +70,10 @@ export function formatParams(
70
70
 
71
71
  for (const [key, value] of Object.entries(params)) {
72
72
  if (reservedKeys.has(key)) continue;
73
- // boolean 值需要特殊处理:嵌套对象的 boolean 子字段不能跳过
73
+ // boolean 值需要特殊处理:
74
+ // 1) 嵌套对象的 boolean 子字段应展开到 nestedObjectCollector
75
+ // 2) 顶层 body 中的 boolean 字段应直接写入 formatted.data
76
+ // 3) 其余未知 boolean 字段才跳过
74
77
  if (typeof value === 'boolean') {
75
78
  const nestedMapping = nestedFieldMap.get(key);
76
79
  if (nestedMapping && nestedMapping.leafSchema?.type === 'boolean') {
@@ -88,6 +91,23 @@ export function formatParams(
88
91
  target = target[subPathKeys[pi]];
89
92
  }
90
93
  target[subPathKeys[subPathKeys.length - 1]] = value;
94
+ continue;
95
+ }
96
+ // 顶层 body boolean 字段:直接写入 formatted.data
97
+ if (bodyProps[key] && bodyProps[key].type === 'boolean') {
98
+ if (!formatted.data) formatted.data = {};
99
+ formatted.data[key] = value;
100
+ continue;
101
+ }
102
+ // body 字段与 path/query 冲突时会被加 `d-` 前缀(详见 flatten-*-options.ts);
103
+ // `d_` 形式与同文件下方非 boolean 分支保持一致兼容
104
+ if (key.startsWith('d-') || key.startsWith('d_')) {
105
+ const originalKey = key.replace(/^d[-_]/, '');
106
+ if (bodyProps[originalKey] && bodyProps[originalKey].type === 'boolean') {
107
+ if (!formatted.data) formatted.data = {};
108
+ formatted.data[originalKey] = value;
109
+ continue;
110
+ }
91
111
  }
92
112
  continue;
93
113
  }
@@ -28,6 +28,8 @@ export interface ShortcutDefinition {
28
28
  autoData?: Record<string, any>;
29
29
  /** --data 的使用提示(展示用) */
30
30
  dataTip?: string;
31
+ /** 上传命令的资源种类(file/image),仅 upload 命令使用,默认 'file' */
32
+ kind?: 'file' | 'image';
31
33
  }
32
34
 
33
35
  export interface ResolvedShortcut {
@@ -41,6 +43,8 @@ export interface ResolvedShortcut {
41
43
  autoData: Record<string, any> | null;
42
44
  /** 是否为上传命令 */
43
45
  upload: boolean;
46
+ /** 上传命令的资源种类(file/image),仅 upload 时有意义 */
47
+ kind?: 'file' | 'image';
44
48
  }
45
49
 
46
50
  // ============================================================
@@ -69,36 +73,39 @@ function getPRNumber(): string {
69
73
  // 快捷命令定义
70
74
  // ============================================================
71
75
 
72
- export const ISSUE_SHORTCUTS: ShortcutDefinition[] = [
73
- { shortName: 'get', realTool: 'get-issue', description: '获取详情' },
74
- { shortName: 'list-comments', realTool: 'list-issue-comments', description: '获取评论列表' },
75
- { shortName: 'comment', realTool: 'post-issue-comment', description: '评论', dataTip: "--body 内容" },
76
- { shortName: 'close', realTool: 'update-issue', description: '关闭', autoData: { state: 'closed', state_reason: 'completed' } },
77
- { shortName: 'open', realTool: 'update-issue', description: '打开', autoData: { state: 'open', state_reason: 'reopened' } },
78
- { shortName: 'list-labels', realTool: 'list-issue-labels', description: '查看标签' },
79
- { shortName: 'add-labels', realTool: 'post-issue-labels', description: '添加标签', dataTip: "--labels bug --labels feature" },
80
- { shortName: 'list-assignees', realTool: 'list-issue-assignees', description: '查看处理人' },
81
- { shortName: 'add-assignees', realTool: 'post-issue-assignees', description: '添加处理人', dataTip: "--assignees username" },
82
- { shortName: 'upload-file', realTool: 'post-issue-file-asset-upload-url', description: '上传文件', upload: true, dataTip: "--file 文件路径" },
83
- { shortName: 'upload-image', realTool: 'post-issue-image-asset-upload-url', description: '上传图片', upload: true, dataTip: "--file 图片路径" },
84
- ];
85
-
86
- export const PR_SHORTCUTS: ShortcutDefinition[] = [
87
- { shortName: 'get', realTool: 'get-pull', description: '获取详情' },
88
- { shortName: 'list-files', realTool: 'list-pull-files', description: '获取文件变更' },
89
- { shortName: 'list-commits', realTool: 'list-pull-commits', description: '获取提交记录' },
90
- { shortName: 'list-comments', realTool: 'list-pull-comments', description: '获取评论列表' },
91
- { shortName: 'comment', realTool: 'post-pull-comment', description: '评论', dataTip: "--body 内容" },
92
- { shortName: 'list-labels', realTool: 'list-pull-labels', description: '查看标签' },
93
- { shortName: 'add-labels', realTool: 'post-pull-labels', description: '添加标签', dataTip: "--labels ready --labels approved" },
94
- { shortName: 'check-status', realTool: 'list-pull-commit-statuses', description: '查看 CI 状态' },
95
- { shortName: 'list-reviews', realTool: 'list-pull-reviews', description: '查看评审列表' },
96
- { shortName: 'list-assignees', realTool: 'list-pull-assignees', description: '查看处理人' },
97
- { shortName: 'upload-file', realTool: 'upload-files', description: '上传文件', repoOnly: true, upload: true, dataTip: "--file 文件路径" },
98
- { shortName: 'upload-image', realTool: 'upload-imgs', description: '上传图片', repoOnly: true, upload: true, dataTip: "--file 图片路径" },
99
- { shortName: 'get-ci-logs', realTool: '__get-ci-logs__', description: '获取 CI 失败日志', repoOnly: true, custom: true, dataTip: "--sn 构建号(可选)" },
100
- { shortName: 'get-ci-timing', realTool: '__get-ci-timing__', description: '分析 CI 耗时瓶颈', repoOnly: true, custom: true, dataTip: "--sn 构建号(可选)" },
101
- ];
76
+ // ============================================================
77
+ // 快捷命令定义
78
+ // ============================================================
79
+ //
80
+ // 数据由 csg 构建期从 cag.config.js quickCommands 字段派生,
81
+ // 写入同级目录下的 shortcuts.config.json。本文件仅负责加载并暴露给
82
+ // 其它模块(register-modules.ts / execute-action.ts / utils/upload.ts)。
83
+ //
84
+ // 编译产物中本文件位于 <skillScriptDir>/core/shortcuts.js,
85
+ // JSON 文件位于 <skillScriptDir>/core/shortcuts.config.json,二者同级。
86
+ // 源码态时 client/shortcuts.config.json 不存在(只在 csg 构建后生成),
87
+ // 因此用 try/catch 兜底,源码态返回空数组以便单元测试与类型检查。
88
+
89
+ interface ShortcutsConfigJson {
90
+ [moduleName: string]: ShortcutDefinition[];
91
+ }
92
+
93
+ function loadShortcutsConfig(): ShortcutsConfigJson {
94
+ try {
95
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
96
+ return require('./shortcuts.config.json') as ShortcutsConfigJson;
97
+ } catch {
98
+ // 源码/测试态没有产物 JSON,返回空配置。
99
+ return {};
100
+ }
101
+ }
102
+
103
+ const SHORTCUTS_CONFIG = loadShortcutsConfig();
104
+
105
+ export const ISSUE_SHORTCUTS: ShortcutDefinition[] =
106
+ SHORTCUTS_CONFIG.issues || [];
107
+
108
+ export const PR_SHORTCUTS: ShortcutDefinition[] = SHORTCUTS_CONFIG.pulls || [];
102
109
 
103
110
  // ============================================================
104
111
  // --short 帮助输出
@@ -267,6 +274,7 @@ export function resolveShortcut(
267
274
  autoPath,
268
275
  autoData,
269
276
  upload: !!matched.upload,
277
+ kind: matched.kind,
270
278
  };
271
279
  }
272
280
 
@@ -72,14 +72,14 @@ function mimeLookup(filePath: string): string {
72
72
  // 上传文件大小上限:100MB
73
73
  const MAX_FILE_SIZE = 100 * 1024 * 1024;
74
74
 
75
- /** 上传 API 的 path 参数 */
75
+ /** 上传 API 的 path 参数(issue 走 comment-asset-upload-url 流程需要 repo + number) */
76
76
  interface UploadPathParams {
77
77
  repo: string;
78
78
  number?: string;
79
79
  }
80
80
 
81
- /** Issue 上传请求体 */
82
- interface IssueUploadBody {
81
+ /** Issue comment-asset-upload-url 接口请求体(单文件,与 swagger PostIssueAssetUploadURLForm 对齐) */
82
+ interface IssueAssetUploadURLBody {
83
83
  name: string;
84
84
  size: number;
85
85
  content_type: string;
@@ -91,23 +91,27 @@ interface PullUploadBody {
91
91
  size: number;
92
92
  }
93
93
 
94
- /** 上传 API 函数签名 */
95
- type UploadApiFunction = (
96
- params: UploadPathParams,
97
- body: IssueUploadBody | PullUploadBody,
98
- ) => Promise<UploadApiResponse>;
99
-
100
- /** 上传 API 响应 */
101
- interface UploadApiResponse {
102
- status?: number;
103
- data?: {
104
- upload_url?: string;
105
- asset_link?: string;
106
- assets?: { name?: string; path?: string };
107
- token?: string;
108
- };
94
+ /** Issue comment-asset-upload-url 接口响应(与 swagger api.IssueAssetUploadURL 对齐) */
95
+ interface IssueAssetUploadURLResponseData {
96
+ name?: string;
97
+ path?: string;
98
+ asset_link?: string;
99
+ upload_url?: string;
109
100
  }
110
101
 
102
+ /** Pull 上传 API 响应 */
103
+ interface PullUploadResponseData {
104
+ upload_url?: string;
105
+ assets?: { name?: string; path?: string };
106
+ token?: string;
107
+ }
108
+
109
+ /** 上传 API 函数签名(issue 与 pull 分别由对应的 swagger 函数承担) */
110
+ type UploadApiFunction = (
111
+ firstArg: UploadPathParams | string,
112
+ body: IssueAssetUploadURLBody | PullUploadBody,
113
+ ) => Promise<{ status?: number; data?: IssueAssetUploadURLResponseData | PullUploadResponseData }>;
114
+
111
115
  /** 上传失败时的 data */
112
116
  interface UploadErrorData {
113
117
  error: string;
@@ -118,6 +122,7 @@ interface UploadErrorData {
118
122
  interface IssueUploadData {
119
123
  asset_link?: string;
120
124
  name: string;
125
+ path?: string;
121
126
  size: number;
122
127
  }
123
128
 
@@ -137,10 +142,22 @@ interface UploadResult {
137
142
 
138
143
  /**
139
144
  * 处理完整上传流程
140
- * @param shortcut 解析后的快捷命令
141
- * @param filePath 本地文件路径
142
- * @param toolFunction 原始上传 API 函数(获取 upload_url)
143
- * @param pathParams API 调用的 path 参数(repo 或 {repo, number}
145
+ *
146
+ * Issue 路径(走 comment-asset-upload-url 接口,按 file/image 分别调用):
147
+ * 1. POST /{repo}/-/issues/{number}/comment-file-asset-upload-url
148
+ * /{repo}/-/issues/{number}/comment-image-asset-upload-url
149
+ * 携带 { name, size, content_type } → 服务端返回 upload_url + asset_link
150
+ * 2. PUT 文件流到 upload_url
151
+ * 3. 返回 asset_link 给调用方拼到评论 body 里
152
+ *
153
+ * Pull 路径(保留原有的 upload-files / upload-imgs 一步上传):
154
+ * 1. POST /{repo}/upload-files(或 imgs) 拿 upload_url
155
+ * 2. PUT 文件流到 upload_url
156
+ *
157
+ * @param shortcut 解析后的快捷命令;shortcut.kind 决定 issue 上传走 file 还是 image 通道
158
+ * @param filePath 本地文件路径
159
+ * @param toolFunction 原始上传 API 函数
160
+ * @param pathParams API 调用的 path 参数(issue 需要 repo+number;pull 只需 repo)
144
161
  */
145
162
  export async function handleUpload(
146
163
  shortcut: ResolvedShortcut,
@@ -177,21 +194,46 @@ export async function handleUpload(
177
194
 
178
195
  // 2. 调用上传 API 获取 upload_url
179
196
  const isIssueUpload = shortcut.module === 'issues';
180
- const requestBody = isIssueUpload
181
- ? { name: fileName, size: fileSize, content_type: contentType }
182
- : { name: fileName, size: fileSize };
183
197
 
184
- // Issue API 签名: fn({ repo, number }, body)
185
- // PR API 签名: fn(repo, body)
186
- const apiFirstArg: any = isIssueUpload ? pathParams : pathParams.repo;
187
- const uploadResponse = await toolFunction(apiFirstArg, requestBody);
198
+ let uploadUrl: string | undefined;
199
+ let issueResponseData: IssueAssetUploadURLResponseData | undefined;
200
+ let pullResponseData: PullUploadResponseData | undefined;
201
+
202
+ if (isIssueUpload) {
203
+ // Issue: 调 post-issue-comment-{file|image}-asset-upload-url,
204
+ // 由 cag.config.js 的 quickCommands 通过 kind=file/image 选择 realTool。
205
+ // 函数签名:({ repo, number }, { name, size, content_type })
206
+ if (!pathParams.number) {
207
+ return { status: 400, data: { error: '缺少 issue number(请确认 CNB_ISSUE_IID 是否已设置)' } };
208
+ }
209
+ const requestBody: IssueAssetUploadURLBody = {
210
+ name: fileName,
211
+ size: fileSize,
212
+ content_type: contentType,
213
+ };
214
+
215
+ const apiResponse = await toolFunction(
216
+ { repo: pathParams.repo, number: pathParams.number },
217
+ requestBody,
218
+ );
219
+ issueResponseData = apiResponse?.data as IssueAssetUploadURLResponseData | undefined;
220
+ uploadUrl = issueResponseData?.upload_url;
188
221
 
189
- // 提取 upload_url
190
- const responseData = uploadResponse?.data;
191
- const uploadUrl = responseData?.upload_url;
222
+ if (!uploadUrl) {
223
+ // URL 失败:把原始响应作为错误返回,便于排查
224
+ return apiResponse as unknown as UploadResult;
225
+ }
226
+ } else {
227
+ // Pull: 沿用原有"一步走"上传接口
228
+ // 函数签名:(repo: string, { name, size }, ...)
229
+ const requestBody: PullUploadBody = { name: fileName, size: fileSize };
230
+ const apiResponse = await toolFunction(pathParams.repo, requestBody);
231
+ pullResponseData = apiResponse?.data as PullUploadResponseData | undefined;
232
+ uploadUrl = pullResponseData?.upload_url;
192
233
 
193
- if (!uploadUrl) {
194
- return uploadResponse as unknown as UploadResult; // 获取 URL 失败,直接返回原始错误
234
+ if (!uploadUrl) {
235
+ return apiResponse as unknown as UploadResult;
236
+ }
195
237
  }
196
238
 
197
239
  // 3. PUT 文件内容到 upload_url(流式读取,避免大文件撑爆内存)
@@ -230,8 +272,9 @@ export async function handleUpload(
230
272
  return {
231
273
  status: 200,
232
274
  data: {
233
- asset_link: responseData.asset_link,
234
- name: fileName,
275
+ asset_link: issueResponseData?.asset_link,
276
+ name: issueResponseData?.name || fileName,
277
+ path: issueResponseData?.path,
235
278
  size: fileSize,
236
279
  },
237
280
  };
@@ -240,10 +283,10 @@ export async function handleUpload(
240
283
  return {
241
284
  status: 200,
242
285
  data: {
243
- name: responseData.assets?.name || fileName,
244
- path: responseData.assets?.path,
286
+ name: pullResponseData?.assets?.name || fileName,
287
+ path: pullResponseData?.assets?.path,
245
288
  size: fileSize,
246
- token: responseData.token,
289
+ token: pullResponseData?.token,
247
290
  },
248
291
  };
249
292
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cnbcool/cnb-api-generate",
3
- "version": "2.5.1",
3
+ "version": "2.6.1",
4
4
  "main": "./built/index.js",
5
5
  "module": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -9,33 +9,7 @@ description: CNB 平台交互命令,支持仓库、Issue、PR、流水线、
9
9
 
10
10
  ## 快捷命令
11
11
 
12
- issues:
13
- - `<$CNB_CLI_CMD$> issues get` — 获取当前 Issue 详情
14
- - `<$CNB_CLI_CMD$> issues list-comments` — 列出当前 Issue 评论
15
- - `<$CNB_CLI_CMD$> issues comment --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 --labels bug --labels feature` — 添加标签到当前 Issue
20
- - `<$CNB_CLI_CMD$> issues list-assignees` — 查看当前 Issue 处理人
21
- - `<$CNB_CLI_CMD$> issues add-assignees --assignees username` — 添加处理人到当前 Issue
22
- - `<$CNB_CLI_CMD$> issues upload-file --file 文件路径` — 上传文件到当前 Issue
23
- - `<$CNB_CLI_CMD$> issues upload-image --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 --body '内容'` — 发表 PR 评论
31
- - `<$CNB_CLI_CMD$> pulls list-labels` — 列出当前 PR 标签
32
- - `<$CNB_CLI_CMD$> pulls add-labels --labels ready --labels approved` — 添加标签到当前 PR
33
- - `<$CNB_CLI_CMD$> pulls check-status` — 获取当前 PR 的 CI 状态
34
- - `<$CNB_CLI_CMD$> pulls get-ci-logs` — 获取当前 PR 的 CI 构建日志
35
- - `<$CNB_CLI_CMD$> pulls list-reviews` — 查看当前当前的评审列表
36
- - `<$CNB_CLI_CMD$> pulls list-assignees` — 查看当前 PR 处理人
37
- - `<$CNB_CLI_CMD$> pulls upload-file --file 文件路径` — 上传文件到当前 PR
38
- - `<$CNB_CLI_CMD$> pulls upload-image --file 图片路径` — 上传图片到当前 PR
12
+ <$QUICK_COMMANDS$>
39
13
 
40
14
  注意事项:
41
15
  - **链接结构**:Issue 链接格式为 `<host>/<slug>/-/issues/<number>`,PR 链接格式为 `<host>/<slug>/-/pulls/<number>`。在生成或引用链接时请遵循此结构。