@coderpp/yunxiao-cli 0.1.11 → 0.2.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.
- package/README.md +162 -2
- package/dist/src/arguments.d.ts +11 -0
- package/dist/src/arguments.js +271 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +276 -180
- package/dist/src/client.d.ts +17 -7
- package/dist/src/client.js +94 -141
- package/dist/src/config.d.ts +4 -0
- package/dist/src/config.js +106 -0
- package/dist/src/errors.d.ts +14 -0
- package/dist/src/errors.js +42 -0
- package/dist/src/mr.d.ts +13 -0
- package/dist/src/mr.js +202 -0
- package/dist/src/pagination.d.ts +10 -0
- package/dist/src/pagination.js +35 -0
- package/dist/src/pipeline.d.ts +3 -0
- package/dist/src/pipeline.js +50 -0
- package/dist/src/release.d.ts +2 -0
- package/dist/src/release.js +203 -0
- package/dist/src/transport.d.ts +16 -0
- package/dist/src/transport.js +119 -0
- package/dist/src/workitem-content.d.ts +1 -0
- package/dist/src/workitem-content.js +39 -18
- package/package.json +2 -2
package/dist/src/client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { YunxiaoApiError } from "./transport.js";
|
|
1
2
|
export type YunxiaoEdition = "center" | "region";
|
|
2
3
|
export type Fetcher = (url: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
3
4
|
export interface YunxiaoClientOptions {
|
|
@@ -6,6 +7,8 @@ export interface YunxiaoClientOptions {
|
|
|
6
7
|
organizationId?: string;
|
|
7
8
|
edition?: YunxiaoEdition;
|
|
8
9
|
fetcher?: Fetcher;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
retries?: number;
|
|
9
12
|
}
|
|
10
13
|
export interface ListChangeRequestsOptions {
|
|
11
14
|
page?: number;
|
|
@@ -151,11 +154,13 @@ export interface ReleaseByRepositoryNameOptions {
|
|
|
151
154
|
sourceBranch: string;
|
|
152
155
|
targetBranch: string;
|
|
153
156
|
createOnly?: boolean;
|
|
157
|
+
resume?: number;
|
|
154
158
|
title?: string;
|
|
155
159
|
description?: string;
|
|
156
160
|
mergeType?: MergeChangeRequestOptions["mergeType"];
|
|
157
161
|
}
|
|
158
162
|
export interface ReleaseResult {
|
|
163
|
+
status?: string;
|
|
159
164
|
repository: RepositorySummary;
|
|
160
165
|
changeRequest: unknown;
|
|
161
166
|
review: unknown | null;
|
|
@@ -163,14 +168,9 @@ export interface ReleaseResult {
|
|
|
163
168
|
}
|
|
164
169
|
export interface ApproveAndMergeOptions extends ReviewChangeRequestOptions, MergeChangeRequestOptions {
|
|
165
170
|
}
|
|
166
|
-
export declare class YunxiaoApiError extends Error {
|
|
167
|
-
readonly status: number;
|
|
168
|
-
readonly responseBody: string;
|
|
169
|
-
readonly method: string;
|
|
170
|
-
readonly url: string;
|
|
171
|
-
constructor(message: string, status: number, responseBody: string, method: string, url: string);
|
|
172
|
-
}
|
|
173
171
|
export declare class YunxiaoClient {
|
|
172
|
+
private readonly timeoutMs;
|
|
173
|
+
private readonly retries;
|
|
174
174
|
private readonly token;
|
|
175
175
|
private readonly domain;
|
|
176
176
|
private readonly organizationId?;
|
|
@@ -201,6 +201,16 @@ export declare class YunxiaoClient {
|
|
|
201
201
|
listLatestFailedPipelineRuns(options?: ListLatestFailedPipelineRunsOptions): Promise<LatestFailedPipelineRun[]>;
|
|
202
202
|
getPipelineRun(pipelineId: string, pipelineRunId: string): Promise<unknown>;
|
|
203
203
|
retryPipelineJobRun(pipelineId: string, pipelineRunId: string, jobId: string): Promise<unknown>;
|
|
204
|
+
getChangeRequest(repositoryId: string, localId: number): Promise<unknown>;
|
|
205
|
+
updateChangeRequest(repositoryId: string, localId: number, body: Record<string, unknown>): Promise<unknown>;
|
|
206
|
+
closeChangeRequest(repositoryId: string, localId: number): Promise<unknown>;
|
|
207
|
+
listChangeRequestComments(repositoryId: string, localId: number, body?: Record<string, unknown>): Promise<unknown>;
|
|
208
|
+
createChangeRequestComment(repositoryId: string, localId: number, body: Record<string, unknown>): Promise<unknown>;
|
|
209
|
+
compare(repositoryId: string, from: string, to: string, straight: boolean): Promise<unknown>;
|
|
210
|
+
getRepository(repositoryId: string): Promise<unknown>;
|
|
211
|
+
getPipelineJobLog(pipelineId: string, runId: string, jobId: string): Promise<unknown>;
|
|
212
|
+
startPipeline(pipelineId: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
213
|
+
cancelPipeline(pipelineId: string, runId: string): Promise<unknown>;
|
|
204
214
|
createChangeRequest(repositoryId: string, options: CreateChangeRequestOptions): Promise<unknown>;
|
|
205
215
|
reviewChangeRequest(repositoryId: string, localId: number, options?: ReviewChangeRequestOptions): Promise<unknown>;
|
|
206
216
|
mergeChangeRequest(repositoryId: string, localId: number, options?: MergeChangeRequestOptions): Promise<unknown>;
|
package/dist/src/client.js
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
constructor(message, status, responseBody, method, url) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.status = status;
|
|
9
|
-
this.responseBody = responseBody;
|
|
10
|
-
this.method = method;
|
|
11
|
-
this.url = url;
|
|
12
|
-
this.name = "YunxiaoApiError";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
1
|
+
import { release } from "./release.js";
|
|
2
|
+
import { requestJson } from "./transport.js";
|
|
3
|
+
export { YunxiaoApiError } from "./transport.js";
|
|
4
|
+
import { paginate } from "./pagination.js";
|
|
5
|
+
import { CliError } from "./errors.js";
|
|
15
6
|
export class YunxiaoClient {
|
|
7
|
+
timeoutMs;
|
|
8
|
+
retries;
|
|
16
9
|
token;
|
|
17
10
|
domain;
|
|
18
11
|
organizationId;
|
|
@@ -20,15 +13,28 @@ export class YunxiaoClient {
|
|
|
20
13
|
fetcher;
|
|
21
14
|
constructor(options) {
|
|
22
15
|
if (!options.token) {
|
|
23
|
-
throw new
|
|
16
|
+
throw new CliError("INVALID_CONFIG", "Yunxiao token is required. Set YUNXIAO_TOKEN or pass --token.");
|
|
24
17
|
}
|
|
18
|
+
this.timeoutMs = options.timeoutMs ?? 30000;
|
|
19
|
+
this.retries = options.retries ?? 2;
|
|
20
|
+
if (!Number.isSafeInteger(this.timeoutMs) ||
|
|
21
|
+
this.timeoutMs < 1 ||
|
|
22
|
+
!Number.isSafeInteger(this.retries) ||
|
|
23
|
+
this.retries < 0 ||
|
|
24
|
+
this.retries > 10)
|
|
25
|
+
throw new CliError("INVALID_CONFIG", "Invalid timeout or retry count.");
|
|
25
26
|
this.token = options.token;
|
|
26
27
|
this.domain = stripProtocol(options.domain ?? "openapi-rdc.aliyuncs.com");
|
|
27
28
|
this.organizationId = options.organizationId;
|
|
28
|
-
this.edition =
|
|
29
|
+
this.edition =
|
|
30
|
+
options.edition ?? (options.organizationId ? "center" : "region");
|
|
29
31
|
this.fetcher = options.fetcher ?? fetch;
|
|
32
|
+
if (!["center", "region"].includes(this.edition))
|
|
33
|
+
throw new CliError("INVALID_CONFIG", "Invalid edition.");
|
|
34
|
+
if (!/^[a-zA-Z0-9.-]+(?::[0-9]+)?$/.test(this.domain))
|
|
35
|
+
throw new CliError("INVALID_CONFIG", "Domain must be an HTTPS hostname, optionally with port.");
|
|
30
36
|
if (this.edition === "center" && !this.organizationId) {
|
|
31
|
-
throw new
|
|
37
|
+
throw new CliError("INVALID_CONFIG", "Center edition requires organizationId. Set YUNXIAO_ORGANIZATION_ID or pass --organization-id.");
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
listChangeRequests(options = {}) {
|
|
@@ -53,7 +59,7 @@ export class YunxiaoClient {
|
|
|
53
59
|
return this.request("POST", `${this.projectPath(projectId)}/members`, undefined, {
|
|
54
60
|
operatorId: options.operatorId,
|
|
55
61
|
roleId: options.roleId,
|
|
56
|
-
userIds: options.userIds
|
|
62
|
+
userIds: options.userIds,
|
|
57
63
|
});
|
|
58
64
|
}
|
|
59
65
|
deleteProjectMember(projectId, options) {
|
|
@@ -61,7 +67,7 @@ export class YunxiaoClient {
|
|
|
61
67
|
operatorId: options.operatorId,
|
|
62
68
|
roleIds: [options.roleId],
|
|
63
69
|
userId: options.userIds.length === 1 ? options.userIds[0] : undefined,
|
|
64
|
-
userIds: options.userIds.length > 1 ? options.userIds : undefined
|
|
70
|
+
userIds: options.userIds.length > 1 ? options.userIds : undefined,
|
|
65
71
|
});
|
|
66
72
|
}
|
|
67
73
|
listMembers(options = {}) {
|
|
@@ -79,7 +85,7 @@ export class YunxiaoClient {
|
|
|
79
85
|
perPage: options.perPage,
|
|
80
86
|
sort: options.sort,
|
|
81
87
|
spaceId: options.spaceId,
|
|
82
|
-
spaceType: options.spaceType ?? "Project"
|
|
88
|
+
spaceType: options.spaceType ?? "Project",
|
|
83
89
|
});
|
|
84
90
|
}
|
|
85
91
|
getWorkitem(workitemId) {
|
|
@@ -104,14 +110,14 @@ export class YunxiaoClient {
|
|
|
104
110
|
return this.request("POST", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}/comments`, undefined, {
|
|
105
111
|
content: options.content,
|
|
106
112
|
operatorId: options.operatorId,
|
|
107
|
-
parentId: options.parentId
|
|
113
|
+
parentId: options.parentId,
|
|
108
114
|
});
|
|
109
115
|
}
|
|
110
116
|
async getWorkitemTimeline(workitemId) {
|
|
111
117
|
const [workitem, activities, comments] = await Promise.all([
|
|
112
118
|
this.getWorkitem(workitemId),
|
|
113
119
|
this.listWorkitemActivities(workitemId),
|
|
114
|
-
this.listWorkitemComments(workitemId)
|
|
120
|
+
this.listWorkitemComments(workitemId),
|
|
115
121
|
]);
|
|
116
122
|
return { workitem, activities, comments };
|
|
117
123
|
}
|
|
@@ -122,7 +128,7 @@ export class YunxiaoClient {
|
|
|
122
128
|
startTime: options.startTime,
|
|
123
129
|
endTme: options.endTime,
|
|
124
130
|
status: options.status,
|
|
125
|
-
triggerMode: options.triggerMode
|
|
131
|
+
triggerMode: options.triggerMode,
|
|
126
132
|
});
|
|
127
133
|
}
|
|
128
134
|
listPipelines(options = {}) {
|
|
@@ -134,7 +140,7 @@ export class YunxiaoClient {
|
|
|
134
140
|
statusList: options.statusList,
|
|
135
141
|
pipelineName: options.pipelineName,
|
|
136
142
|
createStartTime: options.createStartTime,
|
|
137
|
-
createEndTime: options.createEndTime
|
|
143
|
+
createEndTime: options.createEndTime,
|
|
138
144
|
});
|
|
139
145
|
}
|
|
140
146
|
async listLatestFailedPipelineRuns(options = {}) {
|
|
@@ -146,13 +152,15 @@ export class YunxiaoClient {
|
|
|
146
152
|
perPage,
|
|
147
153
|
executeStartTime: startTime,
|
|
148
154
|
executeEndTime: endTime,
|
|
149
|
-
statusList: "FAIL"
|
|
155
|
+
statusList: "FAIL",
|
|
150
156
|
});
|
|
151
157
|
const uniquePipelines = uniqueBy(pipelines, (pipeline) => String(recordValue(pipeline, "pipelineId") ?? recordValue(pipeline, "id")));
|
|
152
158
|
const results = [];
|
|
153
159
|
for (const pipeline of uniquePipelines) {
|
|
154
160
|
const pipelineId = recordValue(pipeline, "pipelineId") ?? recordValue(pipeline, "id");
|
|
155
|
-
if (pipelineId === undefined ||
|
|
161
|
+
if (pipelineId === undefined ||
|
|
162
|
+
pipelineId === null ||
|
|
163
|
+
pipelineId === "") {
|
|
156
164
|
continue;
|
|
157
165
|
}
|
|
158
166
|
const runs = await this.listAllPipelineRuns(String(pipelineId), {
|
|
@@ -160,17 +168,20 @@ export class YunxiaoClient {
|
|
|
160
168
|
perPage,
|
|
161
169
|
startTime,
|
|
162
170
|
endTime,
|
|
163
|
-
status: "FAIL"
|
|
171
|
+
status: "FAIL",
|
|
164
172
|
});
|
|
165
173
|
const latestRun = runs
|
|
166
174
|
.filter((run) => timestampInRange(recordValue(run, "startTime"), startTime, endTime))
|
|
167
|
-
.sort((left, right) => Number(recordValue(right, "startTime") ?? 0) -
|
|
175
|
+
.sort((left, right) => Number(recordValue(right, "startTime") ?? 0) -
|
|
176
|
+
Number(recordValue(left, "startTime") ?? 0))[0];
|
|
168
177
|
if (!latestRun) {
|
|
169
178
|
continue;
|
|
170
179
|
}
|
|
171
180
|
results.push({
|
|
172
181
|
pipelineId: normalizeNumericId(pipelineId),
|
|
173
|
-
pipelineName: stringRecordValue(pipeline, "pipelineName") ??
|
|
182
|
+
pipelineName: stringRecordValue(pipeline, "pipelineName") ??
|
|
183
|
+
stringRecordValue(pipeline, "name") ??
|
|
184
|
+
"",
|
|
174
185
|
pipelineRunId: normalizeNumericId(recordValue(latestRun, "pipelineRunId") ?? ""),
|
|
175
186
|
status: stringRecordValue(latestRun, "status"),
|
|
176
187
|
triggerMode: numericOrStringValue(recordValue(latestRun, "triggerMode")),
|
|
@@ -179,7 +190,7 @@ export class YunxiaoClient {
|
|
|
179
190
|
startTimeText: formatTimestamp(recordValue(latestRun, "startTime")),
|
|
180
191
|
endTime: numericOrStringValue(recordValue(latestRun, "endTime")),
|
|
181
192
|
endTimeText: formatTimestamp(recordValue(latestRun, "endTime")),
|
|
182
|
-
creatorAccountId: stringRecordValue(latestRun, "creatorAccountId")
|
|
193
|
+
creatorAccountId: stringRecordValue(latestRun, "creatorAccountId"),
|
|
183
194
|
});
|
|
184
195
|
}
|
|
185
196
|
return results.sort((left, right) => Number(right.startTime ?? 0) - Number(left.startTime ?? 0));
|
|
@@ -190,6 +201,36 @@ export class YunxiaoClient {
|
|
|
190
201
|
retryPipelineJobRun(pipelineId, pipelineRunId, jobId) {
|
|
191
202
|
return this.request("PUT", `${this.pipelinePath(pipelineId)}/pipelineRuns/${encodePathSegment(pipelineRunId)}/jobs/${encodePathSegment(jobId)}/retry`);
|
|
192
203
|
}
|
|
204
|
+
getChangeRequest(repositoryId, localId) {
|
|
205
|
+
return this.request("GET", this.repositoryChangeRequestPath(repositoryId, localId));
|
|
206
|
+
}
|
|
207
|
+
updateChangeRequest(repositoryId, localId, body) {
|
|
208
|
+
return this.request("PUT", this.repositoryChangeRequestPath(repositoryId, localId), undefined, body);
|
|
209
|
+
}
|
|
210
|
+
closeChangeRequest(repositoryId, localId) {
|
|
211
|
+
return this.request("POST", `${this.repositoryChangeRequestPath(repositoryId, localId)}/close`);
|
|
212
|
+
}
|
|
213
|
+
listChangeRequestComments(repositoryId, localId, body = {}) {
|
|
214
|
+
return this.request("POST", `${this.repositoryChangeRequestPath(repositoryId, localId)}/comments/list`, undefined, body);
|
|
215
|
+
}
|
|
216
|
+
createChangeRequestComment(repositoryId, localId, body) {
|
|
217
|
+
return this.request("POST", `${this.repositoryChangeRequestPath(repositoryId, localId)}/comments`, undefined, body);
|
|
218
|
+
}
|
|
219
|
+
compare(repositoryId, from, to, straight) {
|
|
220
|
+
return this.request("GET", `${this.repositoryPath(repositoryId)}/compares`, { from, to, straight });
|
|
221
|
+
}
|
|
222
|
+
getRepository(repositoryId) {
|
|
223
|
+
return this.request("GET", this.repositoryPath(repositoryId));
|
|
224
|
+
}
|
|
225
|
+
getPipelineJobLog(pipelineId, runId, jobId) {
|
|
226
|
+
return this.request("GET", `${this.pipelinePath(pipelineId)}/runs/${encodePathSegment(runId)}/job/${encodePathSegment(jobId)}/log`);
|
|
227
|
+
}
|
|
228
|
+
startPipeline(pipelineId, params) {
|
|
229
|
+
return this.request("POST", `${this.pipelinePath(pipelineId)}/runs`, undefined, { params: params === undefined ? undefined : JSON.stringify(params) });
|
|
230
|
+
}
|
|
231
|
+
cancelPipeline(pipelineId, runId) {
|
|
232
|
+
return this.request("PUT", `${this.pipelinePath(pipelineId)}/runs/${encodePathSegment(runId)}`);
|
|
233
|
+
}
|
|
193
234
|
createChangeRequest(repositoryId, options) {
|
|
194
235
|
return this.request("POST", `${this.repositoryPath(repositoryId)}/changeRequests`, undefined, {
|
|
195
236
|
createFrom: options.createFrom ?? "WEB",
|
|
@@ -201,37 +242,37 @@ export class YunxiaoClient {
|
|
|
201
242
|
targetProjectId: normalizeProjectId(options.targetProjectId),
|
|
202
243
|
title: options.title,
|
|
203
244
|
triggerAIReviewRun: options.triggerAIReviewRun ?? false,
|
|
204
|
-
workItemIds: options.workItemIds
|
|
245
|
+
workItemIds: options.workItemIds,
|
|
205
246
|
});
|
|
206
247
|
}
|
|
207
248
|
reviewChangeRequest(repositoryId, localId, options = {}) {
|
|
208
249
|
return this.request("POST", `${this.repositoryChangeRequestPath(repositoryId, localId)}/review`, undefined, {
|
|
209
250
|
...compactBody({
|
|
210
251
|
reviewComment: options.reviewComment,
|
|
211
|
-
submitDraftCommentIds: options.submitDraftCommentIds
|
|
252
|
+
submitDraftCommentIds: options.submitDraftCommentIds,
|
|
212
253
|
}),
|
|
213
|
-
reviewOpinion: options.reviewOpinion ?? "PASS"
|
|
254
|
+
reviewOpinion: options.reviewOpinion ?? "PASS",
|
|
214
255
|
});
|
|
215
256
|
}
|
|
216
257
|
mergeChangeRequest(repositoryId, localId, options = {}) {
|
|
217
258
|
return this.request("POST", `${this.repositoryChangeRequestPath(repositoryId, localId)}/merge`, undefined, {
|
|
218
259
|
...compactBody({
|
|
219
260
|
mergeMessage: options.mergeMessage,
|
|
220
|
-
removeSourceBranch: options.removeSourceBranch
|
|
261
|
+
removeSourceBranch: options.removeSourceBranch,
|
|
221
262
|
}),
|
|
222
|
-
mergeType: options.mergeType ?? "no-fast-forward"
|
|
263
|
+
mergeType: options.mergeType ?? "no-fast-forward",
|
|
223
264
|
});
|
|
224
265
|
}
|
|
225
266
|
async approveAndMerge(repositoryId, localId, options = {}) {
|
|
226
267
|
await this.reviewChangeRequest(repositoryId, localId, {
|
|
227
268
|
reviewComment: options.reviewComment,
|
|
228
269
|
reviewOpinion: options.reviewOpinion ?? "PASS",
|
|
229
|
-
submitDraftCommentIds: options.submitDraftCommentIds
|
|
270
|
+
submitDraftCommentIds: options.submitDraftCommentIds,
|
|
230
271
|
});
|
|
231
272
|
return this.mergeChangeRequest(repositoryId, localId, {
|
|
232
273
|
mergeMessage: options.mergeMessage,
|
|
233
274
|
mergeType: options.mergeType,
|
|
234
|
-
removeSourceBranch: options.removeSourceBranch
|
|
275
|
+
removeSourceBranch: options.removeSourceBranch,
|
|
235
276
|
});
|
|
236
277
|
}
|
|
237
278
|
getChangeRequestTree(repositoryId, localId, options) {
|
|
@@ -241,52 +282,7 @@ export class YunxiaoClient {
|
|
|
241
282
|
return this.request("GET", `${this.repositoryChangeRequestPath(repositoryId, localId)}/diffs/patches`);
|
|
242
283
|
}
|
|
243
284
|
async releaseByRepositoryName(options) {
|
|
244
|
-
|
|
245
|
-
this.getCurrentUser(),
|
|
246
|
-
this.listRepositories({
|
|
247
|
-
page: 1,
|
|
248
|
-
perPage: 20,
|
|
249
|
-
search: options.repositoryName,
|
|
250
|
-
archived: false
|
|
251
|
-
})
|
|
252
|
-
]);
|
|
253
|
-
const repository = findRepositoryByName(repositories, options.repositoryName);
|
|
254
|
-
if (!repository) {
|
|
255
|
-
throw new Error(`Repository not found by name: ${options.repositoryName}`);
|
|
256
|
-
}
|
|
257
|
-
const repositoryId = String(repository.id);
|
|
258
|
-
const title = options.title ?? `Release ${options.sourceBranch} into ${options.targetBranch}`;
|
|
259
|
-
const changeRequest = await this.createChangeRequest(repositoryId, {
|
|
260
|
-
sourceBranch: options.sourceBranch,
|
|
261
|
-
targetBranch: options.targetBranch,
|
|
262
|
-
sourceProjectId: repository.id,
|
|
263
|
-
targetProjectId: repository.id,
|
|
264
|
-
reviewerUserIds: [currentUser.id],
|
|
265
|
-
title,
|
|
266
|
-
description: options.description
|
|
267
|
-
});
|
|
268
|
-
const localId = localIdFromChangeRequest(changeRequest);
|
|
269
|
-
if (options.createOnly) {
|
|
270
|
-
return {
|
|
271
|
-
repository,
|
|
272
|
-
changeRequest,
|
|
273
|
-
review: null,
|
|
274
|
-
merge: null
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
const review = await this.reviewChangeRequest(repositoryId, localId, {
|
|
278
|
-
reviewComment: "Approved by yunxiao-cli release workflow"
|
|
279
|
-
});
|
|
280
|
-
const merge = await this.mergeChangeRequest(repositoryId, localId, {
|
|
281
|
-
mergeType: options.mergeType,
|
|
282
|
-
removeSourceBranch: false
|
|
283
|
-
});
|
|
284
|
-
return {
|
|
285
|
-
repository,
|
|
286
|
-
changeRequest,
|
|
287
|
-
review,
|
|
288
|
-
merge
|
|
289
|
-
};
|
|
285
|
+
return release(this, options);
|
|
290
286
|
}
|
|
291
287
|
changeRequestsPath() {
|
|
292
288
|
if (this.edition === "center") {
|
|
@@ -344,35 +340,15 @@ export class YunxiaoClient {
|
|
|
344
340
|
return `${this.pipelinesPath()}/${encodedPipelineId}`;
|
|
345
341
|
}
|
|
346
342
|
async listAllPipelines(options) {
|
|
347
|
-
|
|
348
|
-
const perPage = options.perPage ?? 30;
|
|
349
|
-
for (let page = options.page ?? 1;; page += 1) {
|
|
350
|
-
const response = await this.listPipelines({ ...options, page, perPage });
|
|
351
|
-
const pageRows = arrayRecords(response);
|
|
352
|
-
rows.push(...pageRows);
|
|
353
|
-
if (pageRows.length < perPage) {
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return rows;
|
|
343
|
+
return (await paginate((page, perPage) => this.listPipelines({ ...options, page, perPage }), { ...options, allPages: true }));
|
|
358
344
|
}
|
|
359
345
|
async listAllPipelineRuns(pipelineId, options) {
|
|
360
|
-
|
|
361
|
-
const perPage = options.perPage ?? 30;
|
|
362
|
-
for (let page = options.page ?? 1;; page += 1) {
|
|
363
|
-
const response = await this.listPipelineRuns(pipelineId, { ...options, page, perPage });
|
|
364
|
-
const pageRows = arrayRecords(response);
|
|
365
|
-
rows.push(...pageRows);
|
|
366
|
-
if (pageRows.length < perPage) {
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return rows;
|
|
346
|
+
return (await paginate((page, perPage) => this.listPipelineRuns(pipelineId, { ...options, page, perPage }), { ...options, allPages: true }));
|
|
371
347
|
}
|
|
372
348
|
async request(method, path, query, body) {
|
|
373
349
|
const url = new URL(`https://${this.domain}${path}`);
|
|
374
350
|
for (const [key, value] of Object.entries(query ?? {})) {
|
|
375
|
-
if (value !== undefined && value !== null
|
|
351
|
+
if (value !== undefined && value !== null) {
|
|
376
352
|
url.searchParams.set(key, String(value));
|
|
377
353
|
}
|
|
378
354
|
}
|
|
@@ -380,21 +356,20 @@ export class YunxiaoClient {
|
|
|
380
356
|
method,
|
|
381
357
|
headers: {
|
|
382
358
|
"content-type": "application/json",
|
|
383
|
-
"x-yunxiao-token": this.token
|
|
384
|
-
}
|
|
359
|
+
"x-yunxiao-token": this.token,
|
|
360
|
+
},
|
|
385
361
|
};
|
|
386
362
|
if (body) {
|
|
387
363
|
init.body = JSON.stringify(compactBody(body));
|
|
388
364
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
return JSON.parse(text);
|
|
365
|
+
return requestJson(this.fetcher, url, init, {
|
|
366
|
+
timeoutMs: this.timeoutMs,
|
|
367
|
+
retries: this.retries,
|
|
368
|
+
token: this.token,
|
|
369
|
+
readOnly: method === "GET" ||
|
|
370
|
+
path.endsWith(":search") ||
|
|
371
|
+
path.endsWith("/comments/list"),
|
|
372
|
+
});
|
|
398
373
|
}
|
|
399
374
|
}
|
|
400
375
|
function stripProtocol(domain) {
|
|
@@ -431,7 +406,7 @@ function uniqueBy(values, keyFn) {
|
|
|
431
406
|
}
|
|
432
407
|
function timestampInRange(value, startTime, endTime) {
|
|
433
408
|
const timestamp = Number(value);
|
|
434
|
-
return Number.isFinite(timestamp) && timestamp >= startTime && timestamp <= endTime;
|
|
409
|
+
return (Number.isFinite(timestamp) && timestamp >= startTime && timestamp <= endTime);
|
|
435
410
|
}
|
|
436
411
|
function normalizeNumericId(value) {
|
|
437
412
|
const numberValue = Number(value);
|
|
@@ -473,31 +448,9 @@ function triggerModeName(value) {
|
|
|
473
448
|
}
|
|
474
449
|
}
|
|
475
450
|
function compactBody(body) {
|
|
476
|
-
return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined && value !== null
|
|
451
|
+
return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined && value !== null));
|
|
477
452
|
}
|
|
478
453
|
function normalizeProjectId(value) {
|
|
479
454
|
const numberValue = typeof value === "string" ? Number(value) : value;
|
|
480
455
|
return Number.isInteger(numberValue) ? numberValue : value;
|
|
481
456
|
}
|
|
482
|
-
function findRepositoryByName(repositories, repositoryName) {
|
|
483
|
-
const normalizedName = normalizeRepositoryName(repositoryName);
|
|
484
|
-
const matches = repositories.filter((repository) => [repository.name, repository.path, repository.nameWithNamespace, repository.pathWithNamespace]
|
|
485
|
-
.filter((value) => typeof value === "string")
|
|
486
|
-
.some((value) => normalizeRepositoryName(value) === normalizedName));
|
|
487
|
-
if (matches.length > 1) {
|
|
488
|
-
throw new Error(`Multiple repositories matched name: ${repositoryName}. Use a more specific path.`);
|
|
489
|
-
}
|
|
490
|
-
return matches[0];
|
|
491
|
-
}
|
|
492
|
-
function normalizeRepositoryName(value) {
|
|
493
|
-
return value.replace(/\s+\/\s+/g, "/").trim().toLowerCase();
|
|
494
|
-
}
|
|
495
|
-
function localIdFromChangeRequest(changeRequest) {
|
|
496
|
-
if (typeof changeRequest === "object" && changeRequest !== null && "localId" in changeRequest) {
|
|
497
|
-
const localId = Number(changeRequest.localId);
|
|
498
|
-
if (Number.isInteger(localId) && localId > 0) {
|
|
499
|
-
return localId;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
throw new Error("Create change request response did not include a valid localId.");
|
|
503
|
-
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Flags } from "./arguments.js";
|
|
2
|
+
export declare function configPath(f: Flags, env: NodeJS.ProcessEnv): string;
|
|
3
|
+
export declare function resolveConfig(f: Flags, env: NodeJS.ProcessEnv): Promise<NodeJS.ProcessEnv>;
|
|
4
|
+
export declare function configCommand(command: string, name: string | undefined, f: Flags, env: NodeJS.ProcessEnv): Promise<unknown>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readFile, mkdir, writeFile, rename } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { CliError } from "./errors.js";
|
|
6
|
+
const keys = {
|
|
7
|
+
domain: "YUNXIAO_DOMAIN",
|
|
8
|
+
"organization-id": "YUNXIAO_ORGANIZATION_ID",
|
|
9
|
+
edition: "YUNXIAO_EDITION",
|
|
10
|
+
"project-ids": "YUNXIAO_PROJECT_IDS",
|
|
11
|
+
category: "YUNXIAO_WORKITEM_CATEGORIES",
|
|
12
|
+
};
|
|
13
|
+
export function configPath(f, env) {
|
|
14
|
+
return String(f.config ??
|
|
15
|
+
env.YUNXIAO_CONFIG ??
|
|
16
|
+
join(env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "yunxiao", "config.json"));
|
|
17
|
+
}
|
|
18
|
+
async function readConfig(file) {
|
|
19
|
+
try {
|
|
20
|
+
const c = JSON.parse(await readFile(file, "utf8"));
|
|
21
|
+
if (!c ||
|
|
22
|
+
!c.profiles ||
|
|
23
|
+
typeof c.profiles !== "object" ||
|
|
24
|
+
Array.isArray(c.profiles))
|
|
25
|
+
throw Error();
|
|
26
|
+
for (const [name, p] of Object.entries(c.profiles)) {
|
|
27
|
+
if (!name || !p || typeof p !== "object" || Array.isArray(p))
|
|
28
|
+
throw Error();
|
|
29
|
+
for (const [k, v] of Object.entries(p))
|
|
30
|
+
if (![...Object.keys(keys), "token-env"].includes(k) ||
|
|
31
|
+
typeof v !== "string")
|
|
32
|
+
throw Error();
|
|
33
|
+
}
|
|
34
|
+
if (c.current !== undefined &&
|
|
35
|
+
(typeof c.current !== "string" || !Object.hasOwn(c.profiles, c.current)))
|
|
36
|
+
throw Error();
|
|
37
|
+
return c;
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
if (e.code === "ENOENT")
|
|
41
|
+
return { profiles: {} };
|
|
42
|
+
throw new CliError("INVALID_CONFIG", "Invalid configuration file; expected profiles with supported string fields.");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function resolveConfig(f, env) {
|
|
46
|
+
const c = await readConfig(configPath(f, env));
|
|
47
|
+
const name = String(f.profile ?? env.YUNXIAO_PROFILE ?? c.current ?? "");
|
|
48
|
+
if (!name)
|
|
49
|
+
return { ...env };
|
|
50
|
+
if (!Object.hasOwn(c.profiles, name))
|
|
51
|
+
throw new CliError("INVALID_CONFIG", `Profile not found: ${name}`);
|
|
52
|
+
const p = c.profiles[name];
|
|
53
|
+
const result = {};
|
|
54
|
+
for (const [k, v] of Object.entries(keys))
|
|
55
|
+
if (p[k])
|
|
56
|
+
result[v] = p[k];
|
|
57
|
+
if (p["token-env"])
|
|
58
|
+
result.YUNXIAO_TOKEN = env[p["token-env"]];
|
|
59
|
+
for (const [k, v] of Object.entries(env))
|
|
60
|
+
if (v !== undefined)
|
|
61
|
+
result[k] = v;
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
export async function configCommand(command, name, f, env) {
|
|
65
|
+
const file = configPath(f, env);
|
|
66
|
+
const c = await readConfig(file);
|
|
67
|
+
if (command === "list")
|
|
68
|
+
return Object.keys(c.profiles).map((name) => ({
|
|
69
|
+
name,
|
|
70
|
+
current: name === c.current,
|
|
71
|
+
}));
|
|
72
|
+
if (command === "show") {
|
|
73
|
+
const selected = String(f.profile ?? env.YUNXIAO_PROFILE ?? c.current ?? "");
|
|
74
|
+
if (!Object.hasOwn(c.profiles, selected))
|
|
75
|
+
throw new CliError("INVALID_CONFIG", "Select an existing profile.");
|
|
76
|
+
return { name: selected, ...c.profiles[selected] };
|
|
77
|
+
}
|
|
78
|
+
if (!name ||
|
|
79
|
+
!/^[-\w.]+$/.test(name) ||
|
|
80
|
+
["__proto__", "constructor", "prototype"].includes(name))
|
|
81
|
+
throw new CliError("INVALID_CONFIG", "Invalid profile name.");
|
|
82
|
+
if (command === "use") {
|
|
83
|
+
if (!Object.hasOwn(c.profiles, name))
|
|
84
|
+
throw new CliError("INVALID_CONFIG", `Profile not found: ${name}`);
|
|
85
|
+
c.current = name;
|
|
86
|
+
}
|
|
87
|
+
else if (command === "set") {
|
|
88
|
+
if (f.token !== undefined)
|
|
89
|
+
throw new CliError("INVALID_CONFIG", "Use --token-env to reference a credential environment variable.");
|
|
90
|
+
const p = { ...(Object.hasOwn(c.profiles, name) ? c.profiles[name] : {}) };
|
|
91
|
+
for (const k of [...Object.keys(keys), "token-env"])
|
|
92
|
+
if (f[k] !== undefined)
|
|
93
|
+
p[k] = String(f[k]);
|
|
94
|
+
if (p["token-env"] && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(p["token-env"]))
|
|
95
|
+
throw new CliError("INVALID_CONFIG", "Invalid --token-env.");
|
|
96
|
+
c.profiles[name] = p;
|
|
97
|
+
}
|
|
98
|
+
await mkdir(dirname(file), { recursive: true, mode: 0o700 });
|
|
99
|
+
const temp = `${file}.${randomUUID()}.tmp`;
|
|
100
|
+
await writeFile(temp, JSON.stringify(c, null, 2) + "\n", {
|
|
101
|
+
mode: 0o600,
|
|
102
|
+
flag: "wx",
|
|
103
|
+
});
|
|
104
|
+
await rename(temp, file);
|
|
105
|
+
return { file, profile: name, current: c.current };
|
|
106
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class CliError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly exitCode: number;
|
|
4
|
+
readonly details?: unknown | undefined;
|
|
5
|
+
constructor(code: string, message: string, exitCode?: number, details?: unknown | undefined);
|
|
6
|
+
}
|
|
7
|
+
export declare function redact(text: string, secrets?: string[]): string;
|
|
8
|
+
export declare function errorInfo(error: unknown): {
|
|
9
|
+
code: string;
|
|
10
|
+
message: string;
|
|
11
|
+
exitCode: number;
|
|
12
|
+
details?: unknown;
|
|
13
|
+
requestId?: string;
|
|
14
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class CliError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
exitCode;
|
|
4
|
+
details;
|
|
5
|
+
constructor(code, message, exitCode = 2, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.exitCode = exitCode;
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function redact(text, secrets = []) {
|
|
13
|
+
for (const secret of secrets)
|
|
14
|
+
if (secret)
|
|
15
|
+
text = text.split(secret).join("[REDACTED]");
|
|
16
|
+
return text
|
|
17
|
+
.replace(/pt-[\w.-]+/g, "[REDACTED]")
|
|
18
|
+
.replace(/https?:\/\/[^\s"<>]+\?[^\s"<>]+/g, "[URL query redacted]");
|
|
19
|
+
}
|
|
20
|
+
export function errorInfo(error) {
|
|
21
|
+
if (error instanceof CliError)
|
|
22
|
+
return error;
|
|
23
|
+
const e = error;
|
|
24
|
+
const status = e?.status;
|
|
25
|
+
const exitCode = status === 401 || status === 403
|
|
26
|
+
? 3
|
|
27
|
+
: status === 404
|
|
28
|
+
? 4
|
|
29
|
+
: status === 409 || status === 412
|
|
30
|
+
? 5
|
|
31
|
+
: status === 429 || (status ?? 0) >= 500
|
|
32
|
+
? 6
|
|
33
|
+
: status && status >= 400
|
|
34
|
+
? 2
|
|
35
|
+
: 1;
|
|
36
|
+
return {
|
|
37
|
+
code: e?.apiCode ?? (status ? `HTTP_${status}` : "ERROR"),
|
|
38
|
+
message: e?.message ?? String(error),
|
|
39
|
+
exitCode,
|
|
40
|
+
requestId: e?.requestId,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/src/mr.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { YunxiaoClient } from "./client.js";
|
|
2
|
+
import type { Flags } from "./arguments.js";
|
|
3
|
+
export declare function record(v: unknown): Record<string, unknown>;
|
|
4
|
+
export declare function records(v: unknown): Record<string, unknown>[];
|
|
5
|
+
export declare function required(f: Flags, key: string): string;
|
|
6
|
+
export declare function confirmed(f: Flags): void;
|
|
7
|
+
export declare function latestPatch(patches: Record<string, unknown>[], type: string): Record<string, unknown>;
|
|
8
|
+
export declare function patchPair(client: YunxiaoClient, repo: string, id: number, f: Flags): Promise<{
|
|
9
|
+
from: Record<string, unknown>;
|
|
10
|
+
to: Record<string, unknown>;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function mrDiff(client: YunxiaoClient, repo: string, id: number, f: Flags): Promise<unknown>;
|
|
13
|
+
export declare function runExtendedMr(command: string, p: string[], f: Flags, c: YunxiaoClient): Promise<unknown>;
|