@cnbcool/cnb-api-generate 2.7.6 → 2.8.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.
@@ -79,14 +79,18 @@ function flattenToolOptions(toolInfo) {
79
79
  const optKey = usedKeys.has(key) ? `d-${key}` : key;
80
80
  const isArray = prop.type === 'array';
81
81
  const itemType = isArray ? ((_c = prop.items) === null || _c === void 0 ? void 0 : _c.type) || 'string' : prop.type;
82
- const valuePlaceholder = (0, option_value_flag_1.optionValueFlag)(itemType);
82
+ const supportsFileRef = !isArray && prop.type === 'string' && key === 'body';
83
+ const valuePlaceholder = supportsFileRef
84
+ ? '<string|@file>'
85
+ : (0, option_value_flag_1.optionValueFlag)(itemType);
83
86
  const rawDesc = isArray
84
87
  ? (0, clean_array_desc_1.cleanArrayDesc)((0, trim_summary_1.trimSummary)(prop.description || ''))
85
88
  : (0, trim_summary_1.trimSummary)(prop.description || '');
86
89
  const arrayHint = isArray ? ' (可多次传入)' : '';
90
+ const fileRefHint = supportsFileRef ? ' (支持 @file 引用)' : '';
87
91
  const desc = isRequired
88
- ? `[必填] ${rawDesc}${arrayHint}`
89
- : `${rawDesc}${arrayHint}`;
92
+ ? `[必填] ${rawDesc}${arrayHint}${fileRefHint}`
93
+ : `${rawDesc}${arrayHint}${fileRefHint}`;
90
94
  const opt = {
91
95
  optKey,
92
96
  valuePlaceholder,
@@ -1,5 +1,5 @@
1
1
  import { helpData } from './help-data';
2
- import { tryParseJSON } from './parsers';
2
+ import { tryParseJSON, tryReadFileRef } 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';
@@ -14,6 +14,30 @@ export function getToolParamDefs(moduleName: string, toolName: string) {
14
14
  return toolHelp.help?.parameter || {};
15
15
  }
16
16
 
17
+ /**
18
+ * 将 body 字段值按 schema 类型写入 target。
19
+ *
20
+ * 行为约定(无冲突分支与 d- 前缀分支共用):
21
+ * - array 值:原样保留
22
+ * - string 字段:保留原始字符串,并支持 @file 语法(避免纯数字字符串被转 number、
23
+ * 避免长文本因 shell 参数限制被截断)
24
+ * - 其他字段:走 tryParseJSON 以兼容嵌套 JSON
25
+ */
26
+ function assignBodyValue(
27
+ target: Record<string, any>,
28
+ key: string,
29
+ value: any,
30
+ propType: string | undefined,
31
+ ): void {
32
+ if (Array.isArray(value)) {
33
+ target[key] = value;
34
+ } else if (propType === 'string') {
35
+ target[key] = tryReadFileRef(value as string);
36
+ } else {
37
+ target[key] = tryParseJSON(value as string);
38
+ }
39
+ }
40
+
17
41
  /**
18
42
  * 格式化参数
19
43
  * 支持新的 --key value 扁平格式,同时向后兼容旧的 --path/--query JSON 格式。
@@ -177,15 +201,7 @@ export function formatParams(
177
201
  } else if (bodyProps[key]) {
178
202
  // body 字段(无冲突,直接用原 key)
179
203
  if (!formatted.data) formatted.data = {};
180
- if (Array.isArray(value)) {
181
- formatted.data[key] = value;
182
- } else if (bodyProps[key].type === 'string') {
183
- // schema 定义为 string 类型时,保持原始字符串,不做 JSON 解析
184
- // 避免纯数字字符串(如 "123")被转为 number
185
- formatted.data[key] = value;
186
- } else {
187
- formatted.data[key] = tryParseJSON(value as string);
188
- }
204
+ assignBodyValue(formatted.data, key, value, bodyProps[key].type);
189
205
  } else {
190
206
  // 处理 d- 前缀(冲突时 CLI 以 d- 前缀传入)
191
207
  const stripped = (key.startsWith('d-') || key.startsWith('d_'))
@@ -235,7 +251,7 @@ export function formatParams(
235
251
  const originalKey = key.replace(/^d[-_]/, '');
236
252
  if (bodyProps[originalKey]) {
237
253
  if (!formatted.data) formatted.data = {};
238
- formatted.data[originalKey] = Array.isArray(value) ? value : tryParseJSON(value as string);
254
+ assignBodyValue(formatted.data, originalKey, value, bodyProps[originalKey].type);
239
255
  } else {
240
256
  if (!formatted.query) formatted.query = {};
241
257
  formatted.query[key] = typeof value === 'string' && !isNaN(Number(value)) ? Number(value) : value;
@@ -22,6 +22,10 @@ export function tryParseJSON(str: string | boolean | undefined): any {
22
22
 
23
23
  /**
24
24
  * 尝试从文件引用或 stdin 读取内容(类似 curl 的 @file / @- 语法)
25
+ *
26
+ * 注意:一旦用户显式使用 @ 前缀,就表示其意图是读取文件/stdin。
27
+ * 此时若文件不存在或读取失败,应直接报错退出,避免把 "@/path/xxx"
28
+ * 字面量当作正文发送出去导致数据错误。
25
29
  */
26
30
  export function tryReadFileRef(str: string | boolean | undefined) {
27
31
  if (typeof str !== 'string' || !str.startsWith('@')) return str;
@@ -33,17 +37,26 @@ export function tryReadFileRef(str: string | boolean | undefined) {
33
37
  try {
34
38
  return fs.readFileSync(0, 'utf8').trim();
35
39
  } catch (e) {
36
- console.error('从 stdin 读取失败:', e.message);
37
- return str;
40
+ const msg = e instanceof Error ? e.message : String(e);
41
+ console.error(`从 stdin 读取失败: ${msg}`);
42
+ process.exit(1);
38
43
  }
39
44
  }
40
45
 
41
46
  // @/path/to/file 表示从文件读取
42
- if (fs.existsSync(ref)) {
47
+ // 直接 readFileSync 并用 errno 区分错误,避免 existsSync + readFileSync 的 TOCTOU 竞态
48
+ try {
43
49
  return fs.readFileSync(ref, 'utf8').trim();
50
+ } catch (e) {
51
+ const { code } = e as NodeJS.ErrnoException;
52
+ if (code === 'ENOENT') {
53
+ console.error(`文件不存在: ${ref}`);
54
+ } else {
55
+ const msg = e instanceof Error ? e.message : String(e);
56
+ console.error(`读取文件失败: ${ref} - ${msg}`);
57
+ }
58
+ process.exit(1);
44
59
  }
45
-
46
- return str;
47
60
  }
48
61
 
49
62
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cnbcool/cnb-api-generate",
3
- "version": "2.7.6",
3
+ "version": "2.8.0",
4
4
  "main": "./built/index.js",
5
5
  "module": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: cnb-api
3
- description: CNB 平台交互命令,支持仓库、Issue、PR、流水线、制品库等操作。
3
+ description: CNB 平台交互命令,支持 Issue/PR 评论、提交 PRCI 日志查询、代码仓库/制品库读写等操作。
4
4
  ---
5
5
 
6
6
  # cnb-api
@@ -12,12 +12,17 @@ description: CNB 平台交互命令,支持仓库、Issue、PR、流水线、
12
12
  <$QUICK_COMMANDS$>
13
13
 
14
14
  注意事项:
15
- - **链接结构**:Issue 链接格式为 `<host>/<slug>/-/issues/<number>`,PR 链接格式为 `<host>/<slug>/-/pulls/<number>`。在生成或引用链接时请遵循此结构。
16
15
  - **参数自动识别**:快捷命令中的 Issue/PR 编号会自动从环境变量识别,无需额外传递。
17
- - **默认仅需摘要**:默认会精简响应输出结果,只返回核心字段。添加 `--verbose` 输出完整数据。
18
- - **单引号传参**:传递多行文本参数时,使用单引号可防止命令注入攻击,并减少不必要的转义。
16
+ - **默认仅需摘要**:默认会精简响应输出结果,添加 `--verbose` 输出完整数据。
17
+ - **单引号传参**:当 bash 的参数为多行文本时,使用单引号可减少防止命令注入攻击。
19
18
  - **快捷命令适用范围**: 快捷命令只能操作当前仓库的当前 Issue/PR,跨仓库或跨编号操作请参考 `更多 API`。
20
- - **npc提及和召唤的区别**: 评论中直接 @npc 会召唤 npc 干活,如果只提及不召唤,应该去掉 `@` 符号,或使用反引号包裹 `@npc`。
19
+ - **关于提及和召唤**: 评论中直接 @npc 会召唤 npc 干活,如果只提及不召唤,应使用反引号包裹 `@npc`。
20
+
21
+ ## 常用链接
22
+
23
+ 在生成链接时请遵循下面的结构:
24
+ - Issue: `<host>/<slug>/-/issues/<number>`
25
+ - PR: `<host>/<slug>/-/pulls/<number>`
21
26
 
22
27
  ## 更多 API
23
28