@cnbcool/cnb-api-generate 2.7.4 → 2.7.6
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.
|
@@ -84,12 +84,9 @@ function flattenToolOptions(toolInfo) {
|
|
|
84
84
|
? (0, clean_array_desc_1.cleanArrayDesc)((0, trim_summary_1.trimSummary)(prop.description || ''))
|
|
85
85
|
: (0, trim_summary_1.trimSummary)(prop.description || '');
|
|
86
86
|
const arrayHint = isArray ? ' (可多次传入)' : '';
|
|
87
|
-
const fileRefHint = !isArray && prop.type === 'string' && key === 'body'
|
|
88
|
-
? ' (支持 @file 引用)'
|
|
89
|
-
: '';
|
|
90
87
|
const desc = isRequired
|
|
91
|
-
? `[必填] ${rawDesc}${arrayHint}
|
|
92
|
-
: `${rawDesc}${arrayHint}
|
|
88
|
+
? `[必填] ${rawDesc}${arrayHint}`
|
|
89
|
+
: `${rawDesc}${arrayHint}`;
|
|
93
90
|
const opt = {
|
|
94
91
|
optKey,
|
|
95
92
|
valuePlaceholder,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getToolParamDefs } from './format-params';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 根据 formatParams 的产物构造调用 toolFunction 的位置参数列表。
|
|
5
|
+
*
|
|
6
|
+
* 关键约定(与代码生成器 generate-function-params-ast-node.ts 编译期一致):
|
|
7
|
+
* - swagger pathParams.length > 1 || queryParams.length > 0 → 第 1 形参是对象
|
|
8
|
+
* `{ ...path, ...query }`
|
|
9
|
+
* - 否则 → 第 1 形参是标量(path 字段值),
|
|
10
|
+
* 或没有第 1 形参(pathParams.length === 0)
|
|
11
|
+
*
|
|
12
|
+
* 这是编译期 swagger 决定的形态,**与运行期 query 是否传值无关**。
|
|
13
|
+
* 例:`GET /{repo}/-/issues?state=open` 即便用户没传 --state,toolFunction
|
|
14
|
+
* 仍期望 `{ repo, state? }` 对象,绝不能把 path 拆成标量。
|
|
15
|
+
*
|
|
16
|
+
* 历史 bug:误把单 path 的 tool 包成对象传入,模板字符串 `${对象}` 渲染成
|
|
17
|
+
* `[object Object]`,URL encode 后变 `[object%20Object]`,触发 404。
|
|
18
|
+
*/
|
|
19
|
+
export function buildToolParams(formattedParams: {
|
|
20
|
+
module?: string;
|
|
21
|
+
tool?: string;
|
|
22
|
+
path?: Record<string, any>;
|
|
23
|
+
query?: Record<string, any>;
|
|
24
|
+
data?: any;
|
|
25
|
+
}): any[] {
|
|
26
|
+
const { module: moduleName, tool, path, query, data } = formattedParams;
|
|
27
|
+
const queryHasValue =
|
|
28
|
+
!!query && typeof query === 'object' && Object.keys(query).length > 0;
|
|
29
|
+
|
|
30
|
+
// 1) 决定第 1 形参形态。优先用 swagger 定义;拿不到时按运行期信号兜底(旧行为)。
|
|
31
|
+
let isObjectShape: boolean;
|
|
32
|
+
const defs = moduleName && tool ? getToolParamDefs(moduleName, tool) : null;
|
|
33
|
+
if (defs) {
|
|
34
|
+
const pathDefCount = defs.path ? Object.keys(defs.path).length : 0;
|
|
35
|
+
const queryDefCount = defs.query ? Object.keys(defs.query).length : 0;
|
|
36
|
+
isObjectShape = pathDefCount > 1 || queryDefCount > 0;
|
|
37
|
+
} else {
|
|
38
|
+
// 兜底:path 多于 1 个、或运行期 query 真有值,则按对象形态合并
|
|
39
|
+
const pathKeyCount = path ? Object.keys(path).length : 0;
|
|
40
|
+
isObjectShape = pathKeyCount > 1 || queryHasValue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2) 按形态构造第 1 个参数
|
|
44
|
+
let firstParam: Record<string, any> | string | null = null;
|
|
45
|
+
if (isObjectShape) {
|
|
46
|
+
// 对象形态:合并 path 与运行期 query;任一为空都允许,toolFunction 内部解构是可选的
|
|
47
|
+
if (path || queryHasValue) {
|
|
48
|
+
firstParam = { ...(path ?? {}), ...(query ?? {}) };
|
|
49
|
+
}
|
|
50
|
+
} else if (path) {
|
|
51
|
+
// 标量形态:仅当 path 恰好 1 个字段时拆出值;其它情形(含异常的多字段)不构造,
|
|
52
|
+
// 避免悄悄把不该传的字段塞给 toolFunction
|
|
53
|
+
const keys = Object.keys(path);
|
|
54
|
+
if (keys.length === 1) firstParam = path[keys[0]];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 3) 组装最终位置参数列表(与历史行为一致:firstParam 真值才 push;data 真值才 push)
|
|
58
|
+
const toolsParam: any[] = [];
|
|
59
|
+
if (firstParam) toolsParam.push(firstParam);
|
|
60
|
+
if (data) toolsParam.push(data);
|
|
61
|
+
return toolsParam;
|
|
62
|
+
}
|
|
@@ -5,6 +5,7 @@ import { diagnoseBuild } from './build-diagnostics';
|
|
|
5
5
|
import { analyzeBuildTiming } from './build-timing';
|
|
6
6
|
import { formatParams } from './format-params';
|
|
7
7
|
import { formatOutput } from './format-output';
|
|
8
|
+
import { buildToolParams } from './build-tool-params';
|
|
8
9
|
// @ts-ignore
|
|
9
10
|
import {getLoader} from '../../loader';
|
|
10
11
|
/**
|
|
@@ -116,38 +117,7 @@ export async function executeAction(
|
|
|
116
117
|
|
|
117
118
|
const toolFunction = loadToolFunction(formattedParams.module, formattedParams.tool);
|
|
118
119
|
|
|
119
|
-
const toolsParam
|
|
120
|
-
let pathAndQueryParams: Record<string, any> | string | null = null;
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
formattedParams.path &&
|
|
124
|
-
Object.keys(formattedParams.path).length === 1 &&
|
|
125
|
-
!formattedParams.query
|
|
126
|
-
) {
|
|
127
|
-
pathAndQueryParams =
|
|
128
|
-
formattedParams.path[Object.keys(formattedParams.path)[0]];
|
|
129
|
-
} else {
|
|
130
|
-
if (formattedParams.path) {
|
|
131
|
-
pathAndQueryParams = { ...formattedParams.path };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (formattedParams.query) {
|
|
135
|
-
pathAndQueryParams = {
|
|
136
|
-
...(pathAndQueryParams && typeof pathAndQueryParams === 'object'
|
|
137
|
-
? pathAndQueryParams
|
|
138
|
-
: {}),
|
|
139
|
-
...formattedParams.query,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (pathAndQueryParams) {
|
|
145
|
-
toolsParam.push(pathAndQueryParams);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (formattedParams.data) {
|
|
149
|
-
toolsParam.push(formattedParams.data);
|
|
150
|
-
}
|
|
120
|
+
const toolsParam = buildToolParams(formattedParams);
|
|
151
121
|
const data = await toolFunction(...toolsParam);
|
|
152
122
|
|
|
153
123
|
const toolKey = `${formattedParams.module}/${formattedParams.tool}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { helpData } from './help-data';
|
|
2
|
-
import { tryParseJSON
|
|
2
|
+
import { tryParseJSON } from './parsers';
|
|
3
3
|
import { buildNestedFieldMap } from '../utils/build-nested-field-map';
|
|
4
4
|
import type { NestedFieldMapping } from '../utils/build-nested-field-map';
|
|
5
5
|
import { restoreOriginalKeys } from '../utils/restore-original-keys';
|
|
@@ -51,6 +51,32 @@ export function formatParams(
|
|
|
51
51
|
'module', 'tool', 'help', 'short', 'verbose', 'h', 'v',
|
|
52
52
|
]);
|
|
53
53
|
|
|
54
|
+
// --data 是 CLI 通用 fallback 选项(见 flatten-tool-options.ts,仅在 tool 有 body 时注册),
|
|
55
|
+
// 接收 JSON 字符串(或 @file 引用)作为 body 整体。
|
|
56
|
+
// 仅当 'data' 不是该 tool 真实的 path/query/body 字段名时才启用 fallback 语义,
|
|
57
|
+
// 否则按 swagger 定义当业务字段处理(保留 commit 120d557 引入的能力)。
|
|
58
|
+
//
|
|
59
|
+
// 与 swagger body schema 类型对齐:
|
|
60
|
+
// - object body(默认):parsed 必须是 object,作为 body base,逐个 body flag(如 --event)覆盖同名字段
|
|
61
|
+
// - array body(如 BatchPinDashboardStar):parsed 必须是 array,直接整体替换 body
|
|
62
|
+
// 类型不匹配(如 object body 接口收到 array)静默忽略,避免破坏 flag 已写入的字段
|
|
63
|
+
const dataIsFallback =
|
|
64
|
+
!!paramDefs.body && !pathDef.data && !queryDef.data && !bodyProps.data;
|
|
65
|
+
const bodyIsArrayType = paramDefs.body?.schema?.type === 'array';
|
|
66
|
+
let dataBaseFromFlag: Record<string, any> | undefined;
|
|
67
|
+
let dataArrayFromFlag: any[] | undefined;
|
|
68
|
+
if (dataIsFallback) {
|
|
69
|
+
reservedKeys.add('data');
|
|
70
|
+
if (typeof params.data === 'string' && params.data.length > 0) {
|
|
71
|
+
const parsed = tryParseJSON(tryReadFileRef(params.data));
|
|
72
|
+
if (bodyIsArrayType && Array.isArray(parsed)) {
|
|
73
|
+
dataArrayFromFlag = parsed;
|
|
74
|
+
} else if (!bodyIsArrayType && parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
75
|
+
dataBaseFromFlag = parsed as Record<string, any>;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
54
80
|
// 收集 array<object> 展开字段的临时存储:{ arrayName: { propName: string[] } }
|
|
55
81
|
const arrayObjectCollector: Record<string, Record<string, string[]>> = {};
|
|
56
82
|
|
|
@@ -156,8 +182,7 @@ export function formatParams(
|
|
|
156
182
|
} else if (bodyProps[key].type === 'string') {
|
|
157
183
|
// schema 定义为 string 类型时,保持原始字符串,不做 JSON 解析
|
|
158
184
|
// 避免纯数字字符串(如 "123")被转为 number
|
|
159
|
-
|
|
160
|
-
formatted.data[key] = tryReadFileRef(value as string);
|
|
185
|
+
formatted.data[key] = value;
|
|
161
186
|
} else {
|
|
162
187
|
formatted.data[key] = tryParseJSON(value as string);
|
|
163
188
|
}
|
|
@@ -252,18 +277,19 @@ export function formatParams(
|
|
|
252
277
|
if (!formatted.data) formatted.data = {};
|
|
253
278
|
formatted.data[objectKey] = subProps;
|
|
254
279
|
}
|
|
255
|
-
}
|
|
256
280
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
formatted.
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
formatted.query = {};
|
|
281
|
+
// 合并 --data 提供的 body:
|
|
282
|
+
// - 数组 body:整体替换 formatted.data(数组 body 接口没有逐字段 flag,直接覆盖)
|
|
283
|
+
// - 对象 body:作为 base,逐个 body flag(如 --event)已写入 formatted.data,flag 优先
|
|
284
|
+
if (dataArrayFromFlag) {
|
|
285
|
+
formatted.data = dataArrayFromFlag;
|
|
286
|
+
} else if (dataBaseFromFlag) {
|
|
287
|
+
formatted.data = { ...dataBaseFromFlag, ...(formatted.data || {}) };
|
|
265
288
|
}
|
|
266
289
|
}
|
|
267
290
|
|
|
291
|
+
// formatted.query 仅在用户真传了 query 值时才存在;toolFunction 第 1 形参形态
|
|
292
|
+
// 由 buildToolParams 通过 swagger 定义判定,不依赖运行期 query 是否有值。
|
|
293
|
+
|
|
268
294
|
return formatted;
|
|
269
295
|
}
|
package/package.json
CHANGED
package/skills-template/SKILL.md
CHANGED
|
@@ -16,7 +16,6 @@ description: CNB 平台交互命令,支持仓库、Issue、PR、流水线、
|
|
|
16
16
|
- **参数自动识别**:快捷命令中的 Issue/PR 编号会自动从环境变量识别,无需额外传递。
|
|
17
17
|
- **默认仅需摘要**:默认会精简响应输出结果,只返回核心字段。添加 `--verbose` 输出完整数据。
|
|
18
18
|
- **单引号传参**:传递多行文本参数时,使用单引号可防止命令注入攻击,并减少不必要的转义。
|
|
19
|
-
- **长文本传参**:当 `--body` 内容超过 3 行或包含复杂格式时,**必须**先将内容写入临时文件,再用 `--body @/tmp/comment.md` 文件引用方式传递,避免 shell 参数截断或转义问题。
|
|
20
19
|
- **快捷命令适用范围**: 快捷命令只能操作当前仓库的当前 Issue/PR,跨仓库或跨编号操作请参考 `更多 API`。
|
|
21
20
|
- **npc提及和召唤的区别**: 评论中直接 @npc 会召唤 npc 干活,如果只提及不召唤,应该去掉 `@` 符号,或使用反引号包裹 `@npc`。
|
|
22
21
|
|