@coderpp/yunxiao-cli 0.1.4 → 0.1.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.
- package/README.md +30 -2
- package/dist/src/cli.js +80 -14
- package/dist/src/client.d.ts +56 -1
- package/dist/src/client.js +104 -5
- package/package.json +1 -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.
|
|
31
|
+
npx @coderpp/yunxiao-cli@0.1.4 mr list --output json
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
包内的可执行命令名仍然是 `yunxiao`,全局安装后可这样使用:
|
|
@@ -97,7 +97,7 @@ node dist/src/index.js mr list --state opened --project-ids 2813489 --per-page 2
|
|
|
97
97
|
输出 JSON:
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
|
-
node dist/src/index.js mr list --state opened --json
|
|
100
|
+
node dist/src/index.js mr list --state opened --output json
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
评审通过指定合并请求:
|
|
@@ -118,6 +118,14 @@ node dist/src/index.js mr merge 2813489 12 --yes
|
|
|
118
118
|
node dist/src/index.js mr approve-and-merge 2813489 12 --yes --merge-type squash --remove-source-branch
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
创建发布合并请求、自动评审通过并合并:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
node dist/src/index.js mr release yunxiao-cli release/1.2.3 master --yes
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
这个命令会先按仓库名称查询代码库 ID,再用当前 token 对应的用户作为评审人创建合并请求,随后评审通过并合并。合并时固定保留源分支,不会删除源分支。
|
|
128
|
+
|
|
121
129
|
查询变更文件树:
|
|
122
130
|
|
|
123
131
|
```bash
|
|
@@ -132,6 +140,25 @@ node dist/src/index.js mr tree 2813489 12 \
|
|
|
132
140
|
node dist/src/index.js mr patches 2813489 12
|
|
133
141
|
```
|
|
134
142
|
|
|
143
|
+
## 输出格式
|
|
144
|
+
|
|
145
|
+
所有会输出 API 结果的命令都支持 `--output`:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node dist/src/index.js mr list --output table
|
|
149
|
+
node dist/src/index.js mr list --output json
|
|
150
|
+
node dist/src/index.js mr review 2813489 12 --output table
|
|
151
|
+
node dist/src/index.js mr release yunxiao-cli release/1.2.3 master --yes --output json
|
|
152
|
+
node dist/src/index.js mr patches 2813489 12 --output json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
支持的格式:
|
|
156
|
+
|
|
157
|
+
- `table`:普通表格文本,默认值。
|
|
158
|
+
- `json`:格式化 JSON。
|
|
159
|
+
|
|
160
|
+
`--json` 仍然可用,等价于 `--output json`。
|
|
161
|
+
|
|
135
162
|
## 参数补充
|
|
136
163
|
|
|
137
164
|
`repositoryId` 可以是代码库 ID,也可以是仓库完整路径,工具会自动做 URL 编码。
|
|
@@ -140,6 +167,7 @@ node dist/src/index.js mr patches 2813489 12
|
|
|
140
167
|
|
|
141
168
|
- `mr merge`
|
|
142
169
|
- `mr approve-and-merge`
|
|
170
|
+
- `mr release`
|
|
143
171
|
|
|
144
172
|
这是为了避免误合并。
|
|
145
173
|
|
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,16 +14,30 @@ 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,
|
|
22
|
+
printResult(result, outputFormat, stdout);
|
|
21
23
|
return 0;
|
|
22
24
|
}
|
|
23
25
|
if (!repositoryId || !localIdRaw) {
|
|
24
26
|
throw new Error(`Command mr ${command} requires <repositoryId> and <localId>.`);
|
|
25
27
|
}
|
|
28
|
+
if (command === "release") {
|
|
29
|
+
requireConfirmation(parsed.flags);
|
|
30
|
+
result = await client.releaseByRepositoryName({
|
|
31
|
+
repositoryName: repositoryId,
|
|
32
|
+
sourceBranch: localIdRaw,
|
|
33
|
+
targetBranch: requiredPositional(parsed.positionals[4], "targetBranch"),
|
|
34
|
+
title: optionalString(parsed.flags.title),
|
|
35
|
+
description: optionalString(parsed.flags.description),
|
|
36
|
+
mergeType: mergeOptions(parsed.flags).mergeType
|
|
37
|
+
});
|
|
38
|
+
printResult(result, outputFormat, stdout);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
26
41
|
const localId = parseLocalId(localIdRaw);
|
|
27
42
|
switch (command) {
|
|
28
43
|
case "review":
|
|
@@ -53,7 +68,7 @@ export async function runCli(args = process.argv.slice(2), env = process.env, io
|
|
|
53
68
|
default:
|
|
54
69
|
throw new Error(`Unknown mr command: ${command}`);
|
|
55
70
|
}
|
|
56
|
-
printResult(result,
|
|
71
|
+
printResult(result, outputFormat, stdout);
|
|
57
72
|
return 0;
|
|
58
73
|
}
|
|
59
74
|
catch (error) {
|
|
@@ -126,8 +141,19 @@ function requireConfirmation(flags) {
|
|
|
126
141
|
throw new Error("This command changes repository state. Re-run with --yes after you have reviewed the merge request.");
|
|
127
142
|
}
|
|
128
143
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
144
|
+
function outputFormatFromFlags(flags) {
|
|
145
|
+
const requestedOutput = optionalString(flags.output);
|
|
146
|
+
if (flags.json && requestedOutput && requestedOutput !== "json") {
|
|
147
|
+
throw new Error("--json cannot be combined with --output table.");
|
|
148
|
+
}
|
|
149
|
+
const output = flags.json ? "json" : requestedOutput ?? "table";
|
|
150
|
+
if (!outputFormats.has(output)) {
|
|
151
|
+
throw new Error(`Invalid --output: ${output}. Supported formats: table, json.`);
|
|
152
|
+
}
|
|
153
|
+
return output;
|
|
154
|
+
}
|
|
155
|
+
function printResult(result, outputFormat, stdout) {
|
|
156
|
+
if (outputFormat === "json") {
|
|
131
157
|
stdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
132
158
|
return;
|
|
133
159
|
}
|
|
@@ -135,22 +161,50 @@ function printResult(result, asJson, stdout) {
|
|
|
135
161
|
stdout(formatTable(result));
|
|
136
162
|
return;
|
|
137
163
|
}
|
|
138
|
-
|
|
164
|
+
if (isRecord(result)) {
|
|
165
|
+
stdout(formatKeyValueTable(result));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (result === null || result === undefined) {
|
|
169
|
+
stdout("No result.\n");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
stdout(`${String(result)}\n`);
|
|
139
173
|
}
|
|
140
174
|
function formatTable(rows) {
|
|
141
175
|
if (rows.length === 0) {
|
|
142
176
|
return "No records found.\n";
|
|
143
177
|
}
|
|
144
178
|
const normalizedRows = rows.map((row) => (isRecord(row) ? row : { value: row }));
|
|
145
|
-
const
|
|
146
|
-
const
|
|
179
|
+
const preferredKeys = ["localId", "projectId", "title", "state", "sourceBranch", "targetBranch", "detailUrl"].filter((key) => normalizedRows.some((row) => row[key] !== undefined));
|
|
180
|
+
const keys = preferredKeys.length > 0 ? preferredKeys : uniqueKeys(normalizedRows);
|
|
181
|
+
const widths = keys.map((key) => Math.max(key.length, ...normalizedRows.map((row) => formatCellValue(row[key]).length)));
|
|
147
182
|
const line = (values) => values.map((value, index) => value.padEnd(widths[index])).join(" ");
|
|
148
183
|
return [
|
|
149
184
|
line(keys),
|
|
150
185
|
line(keys.map((_, index) => "-".repeat(widths[index]))),
|
|
151
|
-
...normalizedRows.map((row) => line(keys.map((key) =>
|
|
186
|
+
...normalizedRows.map((row) => line(keys.map((key) => formatCellValue(row[key]))))
|
|
152
187
|
].join("\n") + "\n";
|
|
153
188
|
}
|
|
189
|
+
function uniqueKeys(rows) {
|
|
190
|
+
return Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
|
|
191
|
+
}
|
|
192
|
+
function formatKeyValueTable(record) {
|
|
193
|
+
const rows = Object.entries(record).map(([key, value]) => ({
|
|
194
|
+
key,
|
|
195
|
+
value: formatCellValue(value)
|
|
196
|
+
}));
|
|
197
|
+
return formatTable(rows);
|
|
198
|
+
}
|
|
199
|
+
function formatCellValue(value) {
|
|
200
|
+
if (value === null || value === undefined) {
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
if (typeof value === "object") {
|
|
204
|
+
return JSON.stringify(value);
|
|
205
|
+
}
|
|
206
|
+
return String(value);
|
|
207
|
+
}
|
|
154
208
|
function parseLocalId(value) {
|
|
155
209
|
const localId = Number(value);
|
|
156
210
|
if (!Number.isInteger(localId) || localId <= 0) {
|
|
@@ -158,6 +212,12 @@ function parseLocalId(value) {
|
|
|
158
212
|
}
|
|
159
213
|
return localId;
|
|
160
214
|
}
|
|
215
|
+
function requiredPositional(value, name) {
|
|
216
|
+
if (!value) {
|
|
217
|
+
throw new Error(`Missing required positional argument <${name}>.`);
|
|
218
|
+
}
|
|
219
|
+
return value;
|
|
220
|
+
}
|
|
161
221
|
function requiredFlag(flags, name) {
|
|
162
222
|
const value = optionalString(flags[name]);
|
|
163
223
|
if (!value) {
|
|
@@ -188,12 +248,18 @@ function helpText() {
|
|
|
188
248
|
return `yunxiao - Yunxiao Codeup merge request helper
|
|
189
249
|
|
|
190
250
|
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
|
|
196
|
-
yunxiao mr
|
|
251
|
+
yunxiao mr list [--state opened] [--project-ids 2813489] [--output table|json]
|
|
252
|
+
yunxiao mr review <repositoryId> <localId> [--comment "LGTM"] [--output table|json]
|
|
253
|
+
yunxiao mr merge <repositoryId> <localId> --yes [--merge-type no-fast-forward] [--output table|json]
|
|
254
|
+
yunxiao mr approve-and-merge <repositoryId> <localId> --yes [--merge-type no-fast-forward] [--output table|json]
|
|
255
|
+
yunxiao mr release <repositoryName> <sourceBranch> <targetBranch> --yes [--title "Release"] [--output table|json]
|
|
256
|
+
yunxiao mr tree <repositoryId> <localId> --from-patch-set-id <id> --to-patch-set-id <id> [--output table|json]
|
|
257
|
+
yunxiao mr patches <repositoryId> <localId> [--output table|json]
|
|
258
|
+
|
|
259
|
+
Output:
|
|
260
|
+
--output table Plain text table output, default
|
|
261
|
+
--output json Pretty JSON output
|
|
262
|
+
--json Alias for --output json
|
|
197
263
|
|
|
198
264
|
Config:
|
|
199
265
|
YUNXIAO_TOKEN Personal access token
|
package/dist/src/client.d.ts
CHANGED
|
@@ -34,12 +34,61 @@ export interface ChangeRequestTreeOptions {
|
|
|
34
34
|
fromPatchSetId: string;
|
|
35
35
|
toPatchSetId: string;
|
|
36
36
|
}
|
|
37
|
+
export interface ListRepositoriesOptions {
|
|
38
|
+
page?: number;
|
|
39
|
+
perPage?: number;
|
|
40
|
+
orderBy?: "created_at" | "updated_at" | "last_activity_at" | string;
|
|
41
|
+
sort?: "asc" | "desc" | string;
|
|
42
|
+
search?: string;
|
|
43
|
+
archived?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface RepositorySummary {
|
|
46
|
+
id: number | string;
|
|
47
|
+
name?: string;
|
|
48
|
+
path?: string;
|
|
49
|
+
nameWithNamespace?: string;
|
|
50
|
+
pathWithNamespace?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface CurrentUser {
|
|
53
|
+
id: string;
|
|
54
|
+
name?: string;
|
|
55
|
+
username?: string;
|
|
56
|
+
email?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface CreateChangeRequestOptions {
|
|
59
|
+
sourceBranch: string;
|
|
60
|
+
sourceProjectId: number | string;
|
|
61
|
+
targetBranch: string;
|
|
62
|
+
targetProjectId: number | string;
|
|
63
|
+
title: string;
|
|
64
|
+
createFrom?: "WEB" | "COMMAND_LINE";
|
|
65
|
+
description?: string;
|
|
66
|
+
reviewerUserIds?: string[];
|
|
67
|
+
triggerAIReviewRun?: boolean;
|
|
68
|
+
workItemIds?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface ReleaseByRepositoryNameOptions {
|
|
71
|
+
repositoryName: string;
|
|
72
|
+
sourceBranch: string;
|
|
73
|
+
targetBranch: string;
|
|
74
|
+
title?: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
mergeType?: MergeChangeRequestOptions["mergeType"];
|
|
77
|
+
}
|
|
78
|
+
export interface ReleaseResult {
|
|
79
|
+
repository: RepositorySummary;
|
|
80
|
+
changeRequest: unknown;
|
|
81
|
+
review: unknown;
|
|
82
|
+
merge: unknown;
|
|
83
|
+
}
|
|
37
84
|
export interface ApproveAndMergeOptions extends ReviewChangeRequestOptions, MergeChangeRequestOptions {
|
|
38
85
|
}
|
|
39
86
|
export declare class YunxiaoApiError extends Error {
|
|
40
87
|
readonly status: number;
|
|
41
88
|
readonly responseBody: string;
|
|
42
|
-
|
|
89
|
+
readonly method: string;
|
|
90
|
+
readonly url: string;
|
|
91
|
+
constructor(message: string, status: number, responseBody: string, method: string, url: string);
|
|
43
92
|
}
|
|
44
93
|
export declare class YunxiaoClient {
|
|
45
94
|
private readonly token;
|
|
@@ -49,12 +98,18 @@ export declare class YunxiaoClient {
|
|
|
49
98
|
private readonly fetcher;
|
|
50
99
|
constructor(options: YunxiaoClientOptions);
|
|
51
100
|
listChangeRequests(options?: ListChangeRequestsOptions): Promise<unknown>;
|
|
101
|
+
getCurrentUser(): Promise<CurrentUser>;
|
|
102
|
+
listRepositories(options?: ListRepositoriesOptions): Promise<RepositorySummary[]>;
|
|
103
|
+
createChangeRequest(repositoryId: string, options: CreateChangeRequestOptions): Promise<unknown>;
|
|
52
104
|
reviewChangeRequest(repositoryId: string, localId: number, options?: ReviewChangeRequestOptions): Promise<unknown>;
|
|
53
105
|
mergeChangeRequest(repositoryId: string, localId: number, options?: MergeChangeRequestOptions): Promise<unknown>;
|
|
54
106
|
approveAndMerge(repositoryId: string, localId: number, options?: ApproveAndMergeOptions): Promise<unknown>;
|
|
55
107
|
getChangeRequestTree(repositoryId: string, localId: number, options: ChangeRequestTreeOptions): Promise<unknown>;
|
|
56
108
|
listChangeRequestPatchSets(repositoryId: string, localId: number): Promise<unknown>;
|
|
109
|
+
releaseByRepositoryName(options: ReleaseByRepositoryNameOptions): Promise<ReleaseResult>;
|
|
57
110
|
private changeRequestsPath;
|
|
111
|
+
private repositoriesPath;
|
|
112
|
+
private repositoryPath;
|
|
58
113
|
private repositoryChangeRequestPath;
|
|
59
114
|
private request;
|
|
60
115
|
}
|
package/dist/src/client.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export class YunxiaoApiError extends Error {
|
|
2
2
|
status;
|
|
3
3
|
responseBody;
|
|
4
|
-
|
|
4
|
+
method;
|
|
5
|
+
url;
|
|
6
|
+
constructor(message, status, responseBody, method, url) {
|
|
5
7
|
super(message);
|
|
6
8
|
this.status = status;
|
|
7
9
|
this.responseBody = responseBody;
|
|
10
|
+
this.method = method;
|
|
11
|
+
this.url = url;
|
|
8
12
|
this.name = "YunxiaoApiError";
|
|
9
13
|
}
|
|
10
14
|
}
|
|
@@ -30,6 +34,26 @@ export class YunxiaoClient {
|
|
|
30
34
|
listChangeRequests(options = {}) {
|
|
31
35
|
return this.request("GET", this.changeRequestsPath(), options);
|
|
32
36
|
}
|
|
37
|
+
getCurrentUser() {
|
|
38
|
+
return this.request("GET", "/oapi/v1/platform/user");
|
|
39
|
+
}
|
|
40
|
+
listRepositories(options = {}) {
|
|
41
|
+
return this.request("GET", this.repositoriesPath(), options);
|
|
42
|
+
}
|
|
43
|
+
createChangeRequest(repositoryId, options) {
|
|
44
|
+
return this.request("POST", `${this.repositoryPath(repositoryId)}/changeRequests`, undefined, {
|
|
45
|
+
createFrom: options.createFrom ?? "COMMAND_LINE",
|
|
46
|
+
description: options.description,
|
|
47
|
+
reviewerUserIds: options.reviewerUserIds,
|
|
48
|
+
sourceBranch: options.sourceBranch,
|
|
49
|
+
sourceProjectId: normalizeProjectId(options.sourceProjectId),
|
|
50
|
+
targetBranch: options.targetBranch,
|
|
51
|
+
targetProjectId: normalizeProjectId(options.targetProjectId),
|
|
52
|
+
title: options.title,
|
|
53
|
+
triggerAIReviewRun: options.triggerAIReviewRun ?? false,
|
|
54
|
+
workItemIds: options.workItemIds
|
|
55
|
+
});
|
|
56
|
+
}
|
|
33
57
|
reviewChangeRequest(repositoryId, localId, options = {}) {
|
|
34
58
|
return this.request("POST", `${this.repositoryChangeRequestPath(repositoryId, localId)}/review`, undefined, {
|
|
35
59
|
...compactBody({
|
|
@@ -66,18 +90,67 @@ export class YunxiaoClient {
|
|
|
66
90
|
listChangeRequestPatchSets(repositoryId, localId) {
|
|
67
91
|
return this.request("GET", `${this.repositoryChangeRequestPath(repositoryId, localId)}/diffs/patches`);
|
|
68
92
|
}
|
|
93
|
+
async releaseByRepositoryName(options) {
|
|
94
|
+
const [currentUser, repositories] = await Promise.all([
|
|
95
|
+
this.getCurrentUser(),
|
|
96
|
+
this.listRepositories({
|
|
97
|
+
page: 1,
|
|
98
|
+
perPage: 20,
|
|
99
|
+
search: options.repositoryName,
|
|
100
|
+
archived: false
|
|
101
|
+
})
|
|
102
|
+
]);
|
|
103
|
+
const repository = findRepositoryByName(repositories, options.repositoryName);
|
|
104
|
+
if (!repository) {
|
|
105
|
+
throw new Error(`Repository not found by name: ${options.repositoryName}`);
|
|
106
|
+
}
|
|
107
|
+
const repositoryId = String(repository.id);
|
|
108
|
+
const title = options.title ?? `Release ${options.sourceBranch} into ${options.targetBranch}`;
|
|
109
|
+
const changeRequest = await this.createChangeRequest(repositoryId, {
|
|
110
|
+
sourceBranch: options.sourceBranch,
|
|
111
|
+
targetBranch: options.targetBranch,
|
|
112
|
+
sourceProjectId: repository.id,
|
|
113
|
+
targetProjectId: repository.id,
|
|
114
|
+
reviewerUserIds: [currentUser.id],
|
|
115
|
+
title,
|
|
116
|
+
description: options.description
|
|
117
|
+
});
|
|
118
|
+
const localId = localIdFromChangeRequest(changeRequest);
|
|
119
|
+
const review = await this.reviewChangeRequest(repositoryId, localId, {
|
|
120
|
+
reviewComment: "Approved by yunxiao-cli release workflow"
|
|
121
|
+
});
|
|
122
|
+
const merge = await this.mergeChangeRequest(repositoryId, localId, {
|
|
123
|
+
mergeType: options.mergeType,
|
|
124
|
+
removeSourceBranch: false
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
repository,
|
|
128
|
+
changeRequest,
|
|
129
|
+
review,
|
|
130
|
+
merge
|
|
131
|
+
};
|
|
132
|
+
}
|
|
69
133
|
changeRequestsPath() {
|
|
70
134
|
if (this.edition === "center") {
|
|
71
135
|
return `/oapi/v1/codeup/organizations/${encodePathSegment(this.organizationId ?? "")}/changeRequests`;
|
|
72
136
|
}
|
|
73
137
|
return "/oapi/v1/codeup/changeRequests";
|
|
74
138
|
}
|
|
75
|
-
|
|
139
|
+
repositoriesPath() {
|
|
140
|
+
if (this.edition === "center") {
|
|
141
|
+
return `/oapi/v1/codeup/organizations/${encodePathSegment(this.organizationId ?? "")}/repositories`;
|
|
142
|
+
}
|
|
143
|
+
return "/oapi/v1/codeup/repositories";
|
|
144
|
+
}
|
|
145
|
+
repositoryPath(repositoryId) {
|
|
76
146
|
const encodedRepositoryId = encodePathSegment(repositoryId);
|
|
77
147
|
if (this.edition === "center") {
|
|
78
|
-
return `/oapi/v1/codeup/organizations/${encodePathSegment(this.organizationId ?? "")}/repositories/${encodedRepositoryId}
|
|
148
|
+
return `/oapi/v1/codeup/organizations/${encodePathSegment(this.organizationId ?? "")}/repositories/${encodedRepositoryId}`;
|
|
79
149
|
}
|
|
80
|
-
return `/oapi/v1/codeup/repositories/${encodedRepositoryId}
|
|
150
|
+
return `/oapi/v1/codeup/repositories/${encodedRepositoryId}`;
|
|
151
|
+
}
|
|
152
|
+
repositoryChangeRequestPath(repositoryId, localId) {
|
|
153
|
+
return `${this.repositoryPath(repositoryId)}/changeRequests/${localId}`;
|
|
81
154
|
}
|
|
82
155
|
async request(method, path, query, body) {
|
|
83
156
|
const url = new URL(`https://${this.domain}${path}`);
|
|
@@ -99,7 +172,7 @@ export class YunxiaoClient {
|
|
|
99
172
|
const response = await this.fetcher(url.toString(), init);
|
|
100
173
|
const text = await response.text();
|
|
101
174
|
if (!response.ok) {
|
|
102
|
-
throw new YunxiaoApiError(`Yunxiao API request failed with HTTP ${response.status}`, response.status, text);
|
|
175
|
+
throw new YunxiaoApiError(`Yunxiao API request failed with HTTP ${response.status}: ${method} ${url.toString()}${text ? `\n${text}` : ""}`, response.status, text, method, url.toString());
|
|
103
176
|
}
|
|
104
177
|
if (!text) {
|
|
105
178
|
return null;
|
|
@@ -116,3 +189,29 @@ function encodePathSegment(value) {
|
|
|
116
189
|
function compactBody(body) {
|
|
117
190
|
return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined && value !== null && value !== ""));
|
|
118
191
|
}
|
|
192
|
+
function normalizeProjectId(value) {
|
|
193
|
+
const numberValue = typeof value === "string" ? Number(value) : value;
|
|
194
|
+
return Number.isInteger(numberValue) ? numberValue : value;
|
|
195
|
+
}
|
|
196
|
+
function findRepositoryByName(repositories, repositoryName) {
|
|
197
|
+
const normalizedName = normalizeRepositoryName(repositoryName);
|
|
198
|
+
const matches = repositories.filter((repository) => [repository.name, repository.path, repository.nameWithNamespace, repository.pathWithNamespace]
|
|
199
|
+
.filter((value) => typeof value === "string")
|
|
200
|
+
.some((value) => normalizeRepositoryName(value) === normalizedName));
|
|
201
|
+
if (matches.length > 1) {
|
|
202
|
+
throw new Error(`Multiple repositories matched name: ${repositoryName}. Use a more specific path.`);
|
|
203
|
+
}
|
|
204
|
+
return matches[0];
|
|
205
|
+
}
|
|
206
|
+
function normalizeRepositoryName(value) {
|
|
207
|
+
return value.replace(/\s+\/\s+/g, "/").trim().toLowerCase();
|
|
208
|
+
}
|
|
209
|
+
function localIdFromChangeRequest(changeRequest) {
|
|
210
|
+
if (typeof changeRequest === "object" && changeRequest !== null && "localId" in changeRequest) {
|
|
211
|
+
const localId = Number(changeRequest.localId);
|
|
212
|
+
if (Number.isInteger(localId) && localId > 0) {
|
|
213
|
+
return localId;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
throw new Error("Create change request response did not include a valid localId.");
|
|
217
|
+
}
|