@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.
- package/built/skills-tools/generate-shortcuts-config.js +32 -0
- package/built/skills-tools/generate-skill-md.js +17 -2
- package/built/skills-tools/render-quick-commands-md.js +37 -0
- package/built/skills-tools/resolve-quick-commands.js +55 -0
- package/built/skills.js +8 -2
- package/client/core.ts +2 -0
- package/client/fetch-response-handler.ts +4 -1
- package/client/lib/format-params.ts +21 -1
- package/client/shortcuts.ts +38 -30
- package/client/utils/upload.ts +82 -39
- package/package.json +1 -1
- package/skills-template/SKILL.md +1 -27
|
@@ -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(
|
|
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
|
|
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
|
-
//
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/client/shortcuts.ts
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
package/client/utils/upload.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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:
|
|
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:
|
|
244
|
-
path:
|
|
286
|
+
name: pullResponseData?.assets?.name || fileName,
|
|
287
|
+
path: pullResponseData?.assets?.path,
|
|
245
288
|
size: fileSize,
|
|
246
|
-
token:
|
|
289
|
+
token: pullResponseData?.token,
|
|
247
290
|
},
|
|
248
291
|
};
|
|
249
292
|
}
|
package/package.json
CHANGED
package/skills-template/SKILL.md
CHANGED
|
@@ -9,33 +9,7 @@ description: CNB 平台交互命令,支持仓库、Issue、PR、流水线、
|
|
|
9
9
|
|
|
10
10
|
## 快捷命令
|
|
11
11
|
|
|
12
|
-
|
|
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>`。在生成或引用链接时请遵循此结构。
|