@coderpp/yunxiao-cli 0.1.0 → 0.1.5

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 (3) hide show
  1. package/README.md +51 -2
  2. package/dist/src/cli.js +60 -14
  3. package/package.json +13 -1
package/README.md CHANGED
@@ -28,7 +28,7 @@ npx @coderpp/yunxiao-cli mr list --state opened
28
28
  也可以指定版本:
29
29
 
30
30
  ```bash
31
- npx @coderpp/yunxiao-cli@0.1.0 mr list --json
31
+ npx @coderpp/yunxiao-cli@0.1.4 mr list --output json
32
32
  ```
33
33
 
34
34
  包内的可执行命令名仍然是 `yunxiao`,全局安装后可这样使用:
@@ -38,6 +38,37 @@ npm install -g @coderpp/yunxiao-cli
38
38
  yunxiao mr list --state opened
39
39
  ```
40
40
 
41
+ ## CI/CD
42
+
43
+ 仓库包含两个 GitHub Actions 工作流:
44
+
45
+ - `CI`:在 pull request 和 `main` 分支 push 时运行 `npm ci`、`npm test`、`npm run pack:dry-run`。
46
+ - `Publish`:在推送 `v*.*.*` tag 或手动触发时发布 npm 包。
47
+
48
+ 自动发布使用 npm Trusted Publishing,不需要在 GitHub 保存长期 npm token。npm 包设置中需要配置 Trusted Publisher:
49
+
50
+ - Provider:GitHub Actions
51
+ - Organization or user:`coderpp`
52
+ - Repository:`yunxiao-cli`
53
+ - Workflow filename:`publish.yml`
54
+ - Environment name:留空
55
+
56
+ Trusted Publishing 要求发布环境使用 npm CLI 11.5.1 或更高版本,workflow 的发布 job 使用 Node.js 24。
57
+
58
+ 发布新版本的流程:
59
+
60
+ ```bash
61
+ npm version patch
62
+ git push origin main --follow-tags
63
+ ```
64
+
65
+ 如果手动创建 tag,需要确保 tag 和 `package.json` 版本一致,例如:
66
+
67
+ ```bash
68
+ npm version 0.1.1
69
+ git push origin main v0.1.1
70
+ ```
71
+
41
72
  ## 配置
42
73
 
43
74
  不要把个人访问令牌写入代码或提交到仓库。建议通过环境变量提供:
@@ -66,7 +97,7 @@ node dist/src/index.js mr list --state opened --project-ids 2813489 --per-page 2
66
97
  输出 JSON:
67
98
 
68
99
  ```bash
69
- node dist/src/index.js mr list --state opened --json
100
+ node dist/src/index.js mr list --state opened --output json
70
101
  ```
71
102
 
72
103
  评审通过指定合并请求:
@@ -101,6 +132,24 @@ node dist/src/index.js mr tree 2813489 12 \
101
132
  node dist/src/index.js mr patches 2813489 12
102
133
  ```
103
134
 
135
+ ## 输出格式
136
+
137
+ 所有会输出 API 结果的命令都支持 `--output`:
138
+
139
+ ```bash
140
+ node dist/src/index.js mr list --output table
141
+ node dist/src/index.js mr list --output json
142
+ node dist/src/index.js mr review 2813489 12 --output table
143
+ node dist/src/index.js mr patches 2813489 12 --output json
144
+ ```
145
+
146
+ 支持的格式:
147
+
148
+ - `table`:普通表格文本,默认值。
149
+ - `json`:格式化 JSON。
150
+
151
+ `--json` 仍然可用,等价于 `--output json`。
152
+
104
153
  ## 参数补充
105
154
 
106
155
  `repositoryId` 可以是代码库 ID,也可以是仓库完整路径,工具会自动做 URL 编码。
package/dist/src/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { YunxiaoClient } from "./client.js";
2
2
  const mergeTypes = new Set(["ff-only", "no-fast-forward", "squash", "rebase"]);
3
+ const outputFormats = new Set(["table", "json"]);
3
4
  export async function runCli(args = process.argv.slice(2), env = process.env, io = {}) {
4
5
  const stdout = io.stdout ?? ((text) => process.stdout.write(text));
5
6
  const stderr = io.stderr ?? ((text) => process.stderr.write(text));
@@ -13,11 +14,12 @@ export async function runCli(args = process.argv.slice(2), env = process.env, io
13
14
  if (scope !== "mr" || !command) {
14
15
  throw new Error(`Unknown command: ${parsed.positionals.join(" ")}`);
15
16
  }
17
+ const outputFormat = outputFormatFromFlags(parsed.flags);
16
18
  const client = createClient(parsed.flags, env, io.fetcher);
17
19
  let result;
18
20
  if (command === "list") {
19
21
  result = await client.listChangeRequests(listOptions(parsed.flags));
20
- printResult(result, Boolean(parsed.flags.json), stdout);
22
+ printResult(result, outputFormat, stdout);
21
23
  return 0;
22
24
  }
23
25
  if (!repositoryId || !localIdRaw) {
@@ -53,7 +55,7 @@ export async function runCli(args = process.argv.slice(2), env = process.env, io
53
55
  default:
54
56
  throw new Error(`Unknown mr command: ${command}`);
55
57
  }
56
- printResult(result, Boolean(parsed.flags.json), stdout);
58
+ printResult(result, outputFormat, stdout);
57
59
  return 0;
58
60
  }
59
61
  catch (error) {
@@ -126,8 +128,19 @@ function requireConfirmation(flags) {
126
128
  throw new Error("This command changes repository state. Re-run with --yes after you have reviewed the merge request.");
127
129
  }
128
130
  }
129
- function printResult(result, asJson, stdout) {
130
- if (asJson) {
131
+ function outputFormatFromFlags(flags) {
132
+ const requestedOutput = optionalString(flags.output);
133
+ if (flags.json && requestedOutput && requestedOutput !== "json") {
134
+ throw new Error("--json cannot be combined with --output table.");
135
+ }
136
+ const output = flags.json ? "json" : requestedOutput ?? "table";
137
+ if (!outputFormats.has(output)) {
138
+ throw new Error(`Invalid --output: ${output}. Supported formats: table, json.`);
139
+ }
140
+ return output;
141
+ }
142
+ function printResult(result, outputFormat, stdout) {
143
+ if (outputFormat === "json") {
131
144
  stdout(`${JSON.stringify(result, null, 2)}\n`);
132
145
  return;
133
146
  }
@@ -135,22 +148,50 @@ function printResult(result, asJson, stdout) {
135
148
  stdout(formatTable(result));
136
149
  return;
137
150
  }
138
- stdout(`${JSON.stringify(result, null, 2)}\n`);
151
+ if (isRecord(result)) {
152
+ stdout(formatKeyValueTable(result));
153
+ return;
154
+ }
155
+ if (result === null || result === undefined) {
156
+ stdout("No result.\n");
157
+ return;
158
+ }
159
+ stdout(`${String(result)}\n`);
139
160
  }
140
161
  function formatTable(rows) {
141
162
  if (rows.length === 0) {
142
163
  return "No records found.\n";
143
164
  }
144
165
  const normalizedRows = rows.map((row) => (isRecord(row) ? row : { value: row }));
145
- const keys = ["localId", "projectId", "title", "state", "sourceBranch", "targetBranch", "detailUrl"].filter((key) => normalizedRows.some((row) => row[key] !== undefined));
146
- const widths = keys.map((key) => Math.max(key.length, ...normalizedRows.map((row) => String(row[key] ?? "").length)));
166
+ const preferredKeys = ["localId", "projectId", "title", "state", "sourceBranch", "targetBranch", "detailUrl"].filter((key) => normalizedRows.some((row) => row[key] !== undefined));
167
+ const keys = preferredKeys.length > 0 ? preferredKeys : uniqueKeys(normalizedRows);
168
+ const widths = keys.map((key) => Math.max(key.length, ...normalizedRows.map((row) => formatCellValue(row[key]).length)));
147
169
  const line = (values) => values.map((value, index) => value.padEnd(widths[index])).join(" ");
148
170
  return [
149
171
  line(keys),
150
172
  line(keys.map((_, index) => "-".repeat(widths[index]))),
151
- ...normalizedRows.map((row) => line(keys.map((key) => String(row[key] ?? ""))))
173
+ ...normalizedRows.map((row) => line(keys.map((key) => formatCellValue(row[key]))))
152
174
  ].join("\n") + "\n";
153
175
  }
176
+ function uniqueKeys(rows) {
177
+ return Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
178
+ }
179
+ function formatKeyValueTable(record) {
180
+ const rows = Object.entries(record).map(([key, value]) => ({
181
+ key,
182
+ value: formatCellValue(value)
183
+ }));
184
+ return formatTable(rows);
185
+ }
186
+ function formatCellValue(value) {
187
+ if (value === null || value === undefined) {
188
+ return "";
189
+ }
190
+ if (typeof value === "object") {
191
+ return JSON.stringify(value);
192
+ }
193
+ return String(value);
194
+ }
154
195
  function parseLocalId(value) {
155
196
  const localId = Number(value);
156
197
  if (!Number.isInteger(localId) || localId <= 0) {
@@ -188,12 +229,17 @@ function helpText() {
188
229
  return `yunxiao - Yunxiao Codeup merge request helper
189
230
 
190
231
  Usage:
191
- yunxiao mr list [--state opened] [--project-ids 2813489] [--json]
192
- yunxiao mr review <repositoryId> <localId> [--comment "LGTM"]
193
- yunxiao mr merge <repositoryId> <localId> --yes [--merge-type no-fast-forward]
194
- yunxiao mr approve-and-merge <repositoryId> <localId> --yes [--merge-type no-fast-forward]
195
- yunxiao mr tree <repositoryId> <localId> --from-patch-set-id <id> --to-patch-set-id <id>
196
- yunxiao mr patches <repositoryId> <localId>
232
+ yunxiao mr list [--state opened] [--project-ids 2813489] [--output table|json]
233
+ yunxiao mr review <repositoryId> <localId> [--comment "LGTM"] [--output table|json]
234
+ yunxiao mr merge <repositoryId> <localId> --yes [--merge-type no-fast-forward] [--output table|json]
235
+ yunxiao mr approve-and-merge <repositoryId> <localId> --yes [--merge-type no-fast-forward] [--output table|json]
236
+ yunxiao mr tree <repositoryId> <localId> --from-patch-set-id <id> --to-patch-set-id <id> [--output table|json]
237
+ yunxiao mr patches <repositoryId> <localId> [--output table|json]
238
+
239
+ Output:
240
+ --output table Plain text table output, default
241
+ --output json Pretty JSON output
242
+ --json Alias for --output json
197
243
 
198
244
  Config:
199
245
  YUNXIAO_TOKEN Personal access token
package/package.json CHANGED
@@ -1,16 +1,28 @@
1
1
  {
2
2
  "name": "@coderpp/yunxiao-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "A CLI for Yunxiao Codeup merge request review and merge workflows.",
5
5
  "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/coderpp/yunxiao-cli.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/coderpp/yunxiao-cli/issues"
12
+ },
13
+ "homepage": "https://github.com/coderpp/yunxiao-cli#readme",
6
14
  "bin": {
7
15
  "yunxiao": "dist/src/index.js"
8
16
  },
9
17
  "scripts": {
10
18
  "build": "tsc -p tsconfig.json",
19
+ "pack:dry-run": "npm pack --dry-run",
11
20
  "prepack": "npm run build",
12
21
  "test": "tsc -p tsconfig.json && node --test dist/test/*.test.js"
13
22
  },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
14
26
  "devDependencies": {
15
27
  "@types/node": "^20.12.12",
16
28
  "typescript": "^5.4.5"