@coderpp/yunxiao-cli 0.1.7 → 0.1.9
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 +258 -3
- package/dist/src/cli.js +531 -70
- package/dist/src/client.d.ts +109 -2
- package/dist/src/client.js +280 -0
- package/package.json +7 -2
package/dist/src/cli.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { YunxiaoClient } from "./client.js";
|
|
2
2
|
const mergeTypes = new Set(["ff-only", "no-fast-forward", "squash", "rebase"]);
|
|
3
3
|
const outputFormats = new Set(["table", "json"]);
|
|
4
|
+
const booleanFlags = new Set(["all-pages", "create-only", "help", "json", "remove-source-branch", "yes"]);
|
|
5
|
+
const workitemStateNames = new Map([
|
|
6
|
+
["todo", ["TODO", "待处理", "待办"]],
|
|
7
|
+
["opened", ["TODO", "待处理", "待办"]],
|
|
8
|
+
["doing", ["DOING", "IN_PROGRESS", "PROCESSING", "进行中", "处理中"]],
|
|
9
|
+
["in-progress", ["DOING", "IN_PROGRESS", "PROCESSING", "进行中", "处理中"]],
|
|
10
|
+
["done", ["DONE", "COMPLETED", "CLOSED", "已完成", "完成", "已关闭"]],
|
|
11
|
+
["completed", ["DONE", "COMPLETED", "CLOSED", "已完成", "完成", "已关闭"]]
|
|
12
|
+
]);
|
|
4
13
|
export async function runCli(args = process.argv.slice(2), env = process.env, io = {}) {
|
|
5
14
|
const stdout = io.stdout ?? ((text) => process.stdout.write(text));
|
|
6
15
|
const stderr = io.stderr ?? ((text) => process.stderr.write(text));
|
|
@@ -10,64 +19,13 @@ export async function runCli(args = process.argv.slice(2), env = process.env, io
|
|
|
10
19
|
stdout(helpText());
|
|
11
20
|
return 0;
|
|
12
21
|
}
|
|
13
|
-
const [scope, command
|
|
14
|
-
if (
|
|
22
|
+
const [scope, command] = parsed.positionals;
|
|
23
|
+
if (!command) {
|
|
15
24
|
throw new Error(`Unknown command: ${parsed.positionals.join(" ")}`);
|
|
16
25
|
}
|
|
17
26
|
const outputFormat = outputFormatFromFlags(parsed.flags);
|
|
18
27
|
const client = createClient(parsed.flags, env, io.fetcher);
|
|
19
|
-
|
|
20
|
-
if (command === "list") {
|
|
21
|
-
result = await client.listChangeRequests(listOptions(parsed.flags));
|
|
22
|
-
printResult(result, outputFormat, stdout);
|
|
23
|
-
return 0;
|
|
24
|
-
}
|
|
25
|
-
if (!repositoryId || !localIdRaw) {
|
|
26
|
-
throw new Error(`Command mr ${command} requires <repositoryId> and <localId>.`);
|
|
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
|
-
}
|
|
41
|
-
const localId = parseLocalId(localIdRaw);
|
|
42
|
-
switch (command) {
|
|
43
|
-
case "review":
|
|
44
|
-
result = await client.reviewChangeRequest(repositoryId, localId, {
|
|
45
|
-
reviewComment: optionalString(parsed.flags.comment)
|
|
46
|
-
});
|
|
47
|
-
break;
|
|
48
|
-
case "merge":
|
|
49
|
-
requireConfirmation(parsed.flags);
|
|
50
|
-
result = await client.mergeChangeRequest(repositoryId, localId, mergeOptions(parsed.flags));
|
|
51
|
-
break;
|
|
52
|
-
case "approve-and-merge":
|
|
53
|
-
requireConfirmation(parsed.flags);
|
|
54
|
-
result = await client.approveAndMerge(repositoryId, localId, {
|
|
55
|
-
reviewComment: optionalString(parsed.flags.comment),
|
|
56
|
-
...mergeOptions(parsed.flags)
|
|
57
|
-
});
|
|
58
|
-
break;
|
|
59
|
-
case "tree":
|
|
60
|
-
result = await client.getChangeRequestTree(repositoryId, localId, {
|
|
61
|
-
fromPatchSetId: requiredFlag(parsed.flags, "from-patch-set-id"),
|
|
62
|
-
toPatchSetId: requiredFlag(parsed.flags, "to-patch-set-id")
|
|
63
|
-
});
|
|
64
|
-
break;
|
|
65
|
-
case "patches":
|
|
66
|
-
result = await client.listChangeRequestPatchSets(repositoryId, localId);
|
|
67
|
-
break;
|
|
68
|
-
default:
|
|
69
|
-
throw new Error(`Unknown mr command: ${command}`);
|
|
70
|
-
}
|
|
28
|
+
const result = await runCommand(scope, command, parsed, client, env);
|
|
71
29
|
printResult(result, outputFormat, stdout);
|
|
72
30
|
return 0;
|
|
73
31
|
}
|
|
@@ -76,6 +34,173 @@ export async function runCli(args = process.argv.slice(2), env = process.env, io
|
|
|
76
34
|
return 1;
|
|
77
35
|
}
|
|
78
36
|
}
|
|
37
|
+
async function runCommand(scope, command, parsed, client, env) {
|
|
38
|
+
switch (scope) {
|
|
39
|
+
case "mr":
|
|
40
|
+
return runMergeRequestCommand(command, parsed, client);
|
|
41
|
+
case "project":
|
|
42
|
+
return runProjectCommand(command, parsed, client);
|
|
43
|
+
case "member":
|
|
44
|
+
return runMemberCommand(command, parsed, client);
|
|
45
|
+
case "pipeline":
|
|
46
|
+
case "pl":
|
|
47
|
+
return runPipelineCommand(command, parsed, client);
|
|
48
|
+
case "workitem":
|
|
49
|
+
case "wi":
|
|
50
|
+
return runWorkitemCommand(command, parsed, client, env);
|
|
51
|
+
default:
|
|
52
|
+
throw new Error(`Unknown command scope: ${scope}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function runMergeRequestCommand(command, parsed, client) {
|
|
56
|
+
const [, , repositoryId, localIdRaw] = parsed.positionals;
|
|
57
|
+
if (command === "list") {
|
|
58
|
+
return client.listChangeRequests(listOptions(parsed.flags));
|
|
59
|
+
}
|
|
60
|
+
if (!repositoryId || !localIdRaw) {
|
|
61
|
+
throw new Error(`Command mr ${command} requires <repositoryId> and <localId>.`);
|
|
62
|
+
}
|
|
63
|
+
if (command === "release") {
|
|
64
|
+
requireConfirmation(parsed.flags);
|
|
65
|
+
return client.releaseByRepositoryName({
|
|
66
|
+
repositoryName: repositoryId,
|
|
67
|
+
sourceBranch: localIdRaw,
|
|
68
|
+
targetBranch: requiredPositional(parsed.positionals[4], "targetBranch"),
|
|
69
|
+
createOnly: hasFlag(parsed.flags, "create-only"),
|
|
70
|
+
title: optionalString(parsed.flags.title),
|
|
71
|
+
description: optionalString(parsed.flags.description),
|
|
72
|
+
mergeType: mergeOptions(parsed.flags).mergeType
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const localId = parseLocalId(localIdRaw);
|
|
76
|
+
switch (command) {
|
|
77
|
+
case "review":
|
|
78
|
+
return client.reviewChangeRequest(repositoryId, localId, {
|
|
79
|
+
reviewComment: optionalString(parsed.flags.comment)
|
|
80
|
+
});
|
|
81
|
+
case "merge":
|
|
82
|
+
requireConfirmation(parsed.flags);
|
|
83
|
+
return client.mergeChangeRequest(repositoryId, localId, mergeOptions(parsed.flags));
|
|
84
|
+
case "approve-and-merge":
|
|
85
|
+
requireConfirmation(parsed.flags);
|
|
86
|
+
return client.approveAndMerge(repositoryId, localId, {
|
|
87
|
+
reviewComment: optionalString(parsed.flags.comment),
|
|
88
|
+
...mergeOptions(parsed.flags)
|
|
89
|
+
});
|
|
90
|
+
case "tree":
|
|
91
|
+
return client.getChangeRequestTree(repositoryId, localId, {
|
|
92
|
+
fromPatchSetId: requiredFlag(parsed.flags, "from-patch-set-id"),
|
|
93
|
+
toPatchSetId: requiredFlag(parsed.flags, "to-patch-set-id")
|
|
94
|
+
});
|
|
95
|
+
case "patches":
|
|
96
|
+
return client.listChangeRequestPatchSets(repositoryId, localId);
|
|
97
|
+
default:
|
|
98
|
+
throw new Error(`Unknown mr command: ${command}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function runProjectCommand(command, parsed, client) {
|
|
102
|
+
const projectId = parsed.positionals[2];
|
|
103
|
+
switch (command) {
|
|
104
|
+
case "list":
|
|
105
|
+
case "search":
|
|
106
|
+
return client.searchProjects(projectSearchOptions(parsed.flags));
|
|
107
|
+
case "get":
|
|
108
|
+
return client.getProject(requiredPositional(projectId, "projectId"));
|
|
109
|
+
case "members":
|
|
110
|
+
return client.listProjectMembers(requiredPositional(projectId, "projectId"), {
|
|
111
|
+
name: optionalString(parsed.flags.name),
|
|
112
|
+
roleId: optionalString(parsed.flags["role-id"])
|
|
113
|
+
});
|
|
114
|
+
case "member-add":
|
|
115
|
+
case "add-member":
|
|
116
|
+
requireConfirmation(parsed.flags);
|
|
117
|
+
return client.createProjectMember(requiredPositional(projectId, "projectId"), {
|
|
118
|
+
operatorId: optionalString(parsed.flags["operator-id"]),
|
|
119
|
+
roleId: optionalString(parsed.flags["role-id"]) ?? "project.participant",
|
|
120
|
+
userIds: requiredCsvFlag(parsed.flags, "user-ids")
|
|
121
|
+
});
|
|
122
|
+
case "member-remove":
|
|
123
|
+
case "remove-member":
|
|
124
|
+
requireConfirmation(parsed.flags);
|
|
125
|
+
return client.deleteProjectMember(requiredPositional(projectId, "projectId"), {
|
|
126
|
+
operatorId: optionalString(parsed.flags["operator-id"]),
|
|
127
|
+
roleId: optionalString(parsed.flags["role-id"]) ?? "project.participant",
|
|
128
|
+
userIds: requiredCsvFlag(parsed.flags, "user-ids")
|
|
129
|
+
});
|
|
130
|
+
default:
|
|
131
|
+
throw new Error(`Unknown project command: ${command}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function runMemberCommand(command, parsed, client) {
|
|
135
|
+
switch (command) {
|
|
136
|
+
case "list":
|
|
137
|
+
return client.listMembers({
|
|
138
|
+
page: optionalNumber(parsed.flags.page),
|
|
139
|
+
perPage: optionalNumber(parsed.flags["per-page"])
|
|
140
|
+
});
|
|
141
|
+
case "get":
|
|
142
|
+
return client.getMember(requiredPositional(parsed.positionals[2], "memberId"));
|
|
143
|
+
default:
|
|
144
|
+
throw new Error(`Unknown member command: ${command}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function runPipelineCommand(command, parsed, client) {
|
|
148
|
+
if (command === "failed-runs" || command === "failed" || command === "latest-failed-runs") {
|
|
149
|
+
return client.listLatestFailedPipelineRuns(latestFailedPipelineRunsOptions(parsed.flags));
|
|
150
|
+
}
|
|
151
|
+
const pipelineId = requiredPositional(parsed.positionals[2], "pipelineId");
|
|
152
|
+
const pipelineRunId = parsed.positionals[3];
|
|
153
|
+
const jobId = parsed.positionals[4];
|
|
154
|
+
switch (command) {
|
|
155
|
+
case "runs":
|
|
156
|
+
case "list-runs":
|
|
157
|
+
return client.listPipelineRuns(pipelineId, pipelineRunsOptions(parsed.flags));
|
|
158
|
+
case "run":
|
|
159
|
+
case "get-run":
|
|
160
|
+
case "get":
|
|
161
|
+
return client.getPipelineRun(pipelineId, requiredPositional(pipelineRunId, "pipelineRunId"));
|
|
162
|
+
case "retry-job":
|
|
163
|
+
case "retry":
|
|
164
|
+
requireConfirmation(parsed.flags);
|
|
165
|
+
return client.retryPipelineJobRun(pipelineId, requiredPositional(pipelineRunId, "pipelineRunId"), requiredPositional(jobId, "jobId"));
|
|
166
|
+
default:
|
|
167
|
+
throw new Error(`Unknown pipeline command: ${command}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function runWorkitemCommand(command, parsed, client, env) {
|
|
171
|
+
const workitemId = parsed.positionals[2];
|
|
172
|
+
switch (command) {
|
|
173
|
+
case "list":
|
|
174
|
+
case "search":
|
|
175
|
+
return searchWorkitems(client, parsed.flags, env);
|
|
176
|
+
case "mine": {
|
|
177
|
+
const currentUser = await client.getCurrentUser();
|
|
178
|
+
return searchWorkitems(client, { ...parsed.flags, "assigned-to": currentUser.id }, env);
|
|
179
|
+
}
|
|
180
|
+
case "get":
|
|
181
|
+
return client.getWorkitem(requiredPositional(workitemId, "workitemId"));
|
|
182
|
+
case "update":
|
|
183
|
+
requireConfirmation(parsed.flags);
|
|
184
|
+
return client.updateWorkitem(requiredPositional(workitemId, "workitemId"), workitemUpdateBody(parsed.flags));
|
|
185
|
+
case "activities":
|
|
186
|
+
return client.listWorkitemActivities(requiredPositional(workitemId, "workitemId"));
|
|
187
|
+
case "comments":
|
|
188
|
+
return client.listWorkitemComments(requiredPositional(workitemId, "workitemId"));
|
|
189
|
+
case "comment":
|
|
190
|
+
case "comment-create":
|
|
191
|
+
requireConfirmation(parsed.flags);
|
|
192
|
+
return client.createWorkitemComment(requiredPositional(workitemId, "workitemId"), {
|
|
193
|
+
content: requiredFlag(parsed.flags, "content"),
|
|
194
|
+
operatorId: optionalString(parsed.flags["operator-id"]),
|
|
195
|
+
parentId: optionalString(parsed.flags["parent-id"])
|
|
196
|
+
});
|
|
197
|
+
case "timeline":
|
|
198
|
+
case "history":
|
|
199
|
+
return client.getWorkitemTimeline(requiredPositional(workitemId, "workitemId"));
|
|
200
|
+
default:
|
|
201
|
+
throw new Error(`Unknown workitem command: ${command}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
79
204
|
function createClient(flags, env, fetcher) {
|
|
80
205
|
return new YunxiaoClient({
|
|
81
206
|
token: stringFlag(flags.token) ?? env.YUNXIAO_TOKEN ?? "",
|
|
@@ -96,20 +221,28 @@ function parseArgs(args) {
|
|
|
96
221
|
}
|
|
97
222
|
const [rawName, inlineValue] = arg.slice(2).split("=", 2);
|
|
98
223
|
if (inlineValue !== undefined) {
|
|
99
|
-
flags
|
|
224
|
+
setFlag(flags, rawName, inlineValue);
|
|
100
225
|
continue;
|
|
101
226
|
}
|
|
102
227
|
const next = args[index + 1];
|
|
103
|
-
if (next && !next.startsWith("--") && !
|
|
104
|
-
flags
|
|
228
|
+
if (next && !next.startsWith("--") && !booleanFlags.has(rawName)) {
|
|
229
|
+
setFlag(flags, rawName, next);
|
|
105
230
|
index += 1;
|
|
106
231
|
}
|
|
107
232
|
else {
|
|
108
|
-
flags
|
|
233
|
+
setFlag(flags, rawName, true);
|
|
109
234
|
}
|
|
110
235
|
}
|
|
111
236
|
return { positionals, flags };
|
|
112
237
|
}
|
|
238
|
+
function setFlag(flags, name, value) {
|
|
239
|
+
const existing = flags[name];
|
|
240
|
+
if (existing === undefined) {
|
|
241
|
+
flags[name] = value;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
flags[name] = [...flagValues(existing), ...flagValues(value)];
|
|
245
|
+
}
|
|
113
246
|
function listOptions(flags) {
|
|
114
247
|
return {
|
|
115
248
|
page: optionalNumber(flags.page),
|
|
@@ -125,6 +258,153 @@ function listOptions(flags) {
|
|
|
125
258
|
createdAfter: optionalString(flags["created-after"])
|
|
126
259
|
};
|
|
127
260
|
}
|
|
261
|
+
function projectSearchOptions(flags) {
|
|
262
|
+
return {
|
|
263
|
+
conditions: optionalString(flags.conditions) ?? projectNameConditions(optionalString(flags.name)),
|
|
264
|
+
extraConditions: optionalString(flags["extra-conditions"]),
|
|
265
|
+
orderBy: optionalString(flags["order-by"]) ?? "gmtCreate",
|
|
266
|
+
page: optionalNumber(flags.page) ?? 1,
|
|
267
|
+
perPage: optionalNumber(flags["per-page"]) ?? 20,
|
|
268
|
+
sort: optionalString(flags.sort) ?? "desc"
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function pipelineRunsOptions(flags) {
|
|
272
|
+
return {
|
|
273
|
+
page: optionalNumber(flags.page),
|
|
274
|
+
perPage: optionalNumber(flags["per-page"]),
|
|
275
|
+
startTime: optionalNumber(flags["start-time"]),
|
|
276
|
+
endTime: optionalNumber(flags["end-time"]) ?? optionalNumber(flags["end-tme"]),
|
|
277
|
+
status: optionalString(flags.status),
|
|
278
|
+
triggerMode: optionalNumber(flags["trigger-mode"])
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function latestFailedPipelineRunsOptions(flags) {
|
|
282
|
+
return {
|
|
283
|
+
hours: optionalNumber(flags.hours) ?? 24,
|
|
284
|
+
startTime: optionalNumber(flags["start-time"]),
|
|
285
|
+
endTime: optionalNumber(flags["end-time"]) ?? optionalNumber(flags["end-tme"]),
|
|
286
|
+
perPage: optionalNumber(flags["per-page"]) ?? 30
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async function searchWorkitems(client, flags, env) {
|
|
290
|
+
const projectIds = workitemProjectIds(flags, env);
|
|
291
|
+
const perPage = optionalNumber(flags["per-page"]) ?? 20;
|
|
292
|
+
const firstPage = optionalNumber(flags.page) ?? 1;
|
|
293
|
+
const results = [];
|
|
294
|
+
for (const projectId of projectIds) {
|
|
295
|
+
let page = firstPage;
|
|
296
|
+
while (true) {
|
|
297
|
+
const response = await client.searchWorkitems(workitemSearchOptions(flags, projectId, page, perPage, env));
|
|
298
|
+
const rows = Array.isArray(response) ? response : [response];
|
|
299
|
+
results.push(...rows.map((row) => addQueryProjectId(row, projectId)));
|
|
300
|
+
if (!hasFlag(flags, "all-pages") || !Array.isArray(response) || response.length < perPage) {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
page += 1;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return filterWorkitems(results, flags);
|
|
307
|
+
}
|
|
308
|
+
function workitemSearchOptions(flags, projectId, page, perPage, env) {
|
|
309
|
+
return {
|
|
310
|
+
category: optionalString(flags.category) ?? env.YUNXIAO_WORKITEM_CATEGORIES ?? "Req,Task,Bug",
|
|
311
|
+
conditions: workitemConditions(flags),
|
|
312
|
+
orderBy: optionalString(flags["order-by"]) ?? "gmtCreate",
|
|
313
|
+
page,
|
|
314
|
+
perPage,
|
|
315
|
+
sort: optionalString(flags.sort) ?? "desc",
|
|
316
|
+
spaceId: projectId,
|
|
317
|
+
spaceType: optionalString(flags["space-type"]) ?? "Project"
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function workitemConditions(flags) {
|
|
321
|
+
const rawConditions = optionalString(flags.conditions);
|
|
322
|
+
const filters = [];
|
|
323
|
+
const assignedTo = optionalString(flags["assigned-to"]) ?? optionalString(flags.assignee);
|
|
324
|
+
const creator = optionalString(flags.creator);
|
|
325
|
+
const statusIds = optionalString(flags["status-ids"]);
|
|
326
|
+
const subject = optionalString(flags.subject);
|
|
327
|
+
const from = optionalString(flags.from) ?? optionalString(flags["created-after"]);
|
|
328
|
+
const to = optionalString(flags.to) ?? optionalString(flags["created-before"]);
|
|
329
|
+
if (rawConditions && (assignedTo || creator || statusIds || subject || from || to)) {
|
|
330
|
+
throw new Error("--conditions cannot be combined with shortcut workitem filters.");
|
|
331
|
+
}
|
|
332
|
+
if (rawConditions) {
|
|
333
|
+
return rawConditions;
|
|
334
|
+
}
|
|
335
|
+
if (assignedTo) {
|
|
336
|
+
filters.push(userCondition("assignedTo", csvValues(assignedTo)));
|
|
337
|
+
}
|
|
338
|
+
if (creator) {
|
|
339
|
+
filters.push(userCondition("creator", csvValues(creator)));
|
|
340
|
+
}
|
|
341
|
+
if (statusIds) {
|
|
342
|
+
filters.push({
|
|
343
|
+
fieldIdentifier: "status",
|
|
344
|
+
operator: "CONTAINS",
|
|
345
|
+
value: csvValues(statusIds),
|
|
346
|
+
toValue: null,
|
|
347
|
+
className: "status",
|
|
348
|
+
format: "list"
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (subject) {
|
|
352
|
+
filters.push({
|
|
353
|
+
fieldIdentifier: "subject",
|
|
354
|
+
operator: "CONTAINS",
|
|
355
|
+
value: [subject],
|
|
356
|
+
toValue: null,
|
|
357
|
+
className: "string",
|
|
358
|
+
format: "input"
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
if (from || to) {
|
|
362
|
+
filters.push({
|
|
363
|
+
fieldIdentifier: optionalString(flags["date-field"]) ?? "gmtCreate",
|
|
364
|
+
operator: "BETWEEN",
|
|
365
|
+
value: from ? [from] : [],
|
|
366
|
+
toValue: to ?? null,
|
|
367
|
+
className: "dateTime",
|
|
368
|
+
format: "input"
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return filters.length > 0 ? JSON.stringify({ conditionGroups: [filters] }) : undefined;
|
|
372
|
+
}
|
|
373
|
+
function userCondition(fieldIdentifier, value) {
|
|
374
|
+
return {
|
|
375
|
+
fieldIdentifier,
|
|
376
|
+
operator: "CONTAINS",
|
|
377
|
+
value,
|
|
378
|
+
toValue: null,
|
|
379
|
+
className: "user",
|
|
380
|
+
format: "list"
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function workitemUpdateBody(flags) {
|
|
384
|
+
const body = {
|
|
385
|
+
...jsonObjectFlag(flags.fields)
|
|
386
|
+
};
|
|
387
|
+
for (const field of flagStrings(flags.field)) {
|
|
388
|
+
const [key, value] = parseKeyValue(field, "--field");
|
|
389
|
+
body[key] = parseValue(value);
|
|
390
|
+
}
|
|
391
|
+
const assignedTo = optionalString(flags["assigned-to"]) ?? optionalString(flags.assignee);
|
|
392
|
+
if (assignedTo) {
|
|
393
|
+
body.assignedTo = assignedTo;
|
|
394
|
+
}
|
|
395
|
+
const participants = optionalString(flags.participants);
|
|
396
|
+
if (participants) {
|
|
397
|
+
body.participants = csvValues(participants);
|
|
398
|
+
}
|
|
399
|
+
const dueDate = optionalString(flags["due-date"]) ?? optionalString(flags["plan-finish-at"]);
|
|
400
|
+
if (dueDate) {
|
|
401
|
+
body[optionalString(flags["due-field"]) ?? "dueDate"] = dueDate;
|
|
402
|
+
}
|
|
403
|
+
if (Object.keys(body).length === 0) {
|
|
404
|
+
throw new Error("No workitem fields to update. Use --assigned-to, --participants, --due-date, --fields, or --field.");
|
|
405
|
+
}
|
|
406
|
+
return body;
|
|
407
|
+
}
|
|
128
408
|
function mergeOptions(flags) {
|
|
129
409
|
const mergeType = optionalString(flags["merge-type"]) ?? "no-fast-forward";
|
|
130
410
|
if (!mergeTypes.has(mergeType)) {
|
|
@@ -133,20 +413,20 @@ function mergeOptions(flags) {
|
|
|
133
413
|
return {
|
|
134
414
|
mergeMessage: optionalString(flags.message),
|
|
135
415
|
mergeType: mergeType,
|
|
136
|
-
removeSourceBranch:
|
|
416
|
+
removeSourceBranch: hasFlag(flags, "remove-source-branch")
|
|
137
417
|
};
|
|
138
418
|
}
|
|
139
419
|
function requireConfirmation(flags) {
|
|
140
|
-
if (!flags
|
|
141
|
-
throw new Error("This command changes
|
|
420
|
+
if (!hasFlag(flags, "yes")) {
|
|
421
|
+
throw new Error("This command changes state. Re-run with --yes after you have reviewed the operation.");
|
|
142
422
|
}
|
|
143
423
|
}
|
|
144
424
|
function outputFormatFromFlags(flags) {
|
|
145
425
|
const requestedOutput = optionalString(flags.output);
|
|
146
|
-
if (flags
|
|
426
|
+
if (hasFlag(flags, "json") && requestedOutput && requestedOutput !== "json") {
|
|
147
427
|
throw new Error("--json cannot be combined with --output table.");
|
|
148
428
|
}
|
|
149
|
-
const output = flags
|
|
429
|
+
const output = hasFlag(flags, "json") ? "json" : requestedOutput ?? "table";
|
|
150
430
|
if (!outputFormats.has(output)) {
|
|
151
431
|
throw new Error(`Invalid --output: ${output}. Supported formats: table, json.`);
|
|
152
432
|
}
|
|
@@ -176,7 +456,36 @@ function formatTable(rows) {
|
|
|
176
456
|
return "No records found.\n";
|
|
177
457
|
}
|
|
178
458
|
const normalizedRows = rows.map((row) => (isRecord(row) ? row : { value: row }));
|
|
179
|
-
const preferredKeys = [
|
|
459
|
+
const preferredKeys = [
|
|
460
|
+
"pipelineName",
|
|
461
|
+
"pipelineRunId",
|
|
462
|
+
"pipelineId",
|
|
463
|
+
"localId",
|
|
464
|
+
"projectId",
|
|
465
|
+
"queryProjectId",
|
|
466
|
+
"id",
|
|
467
|
+
"serialNumber",
|
|
468
|
+
"name",
|
|
469
|
+
"subject",
|
|
470
|
+
"title",
|
|
471
|
+
"state",
|
|
472
|
+
"status",
|
|
473
|
+
"triggerMode",
|
|
474
|
+
"triggerModeName",
|
|
475
|
+
"startTime",
|
|
476
|
+
"startTimeText",
|
|
477
|
+
"endTime",
|
|
478
|
+
"endTimeText",
|
|
479
|
+
"createTime",
|
|
480
|
+
"assignedTo",
|
|
481
|
+
"userId",
|
|
482
|
+
"userName",
|
|
483
|
+
"roleName",
|
|
484
|
+
"email",
|
|
485
|
+
"sourceBranch",
|
|
486
|
+
"targetBranch",
|
|
487
|
+
"detailUrl"
|
|
488
|
+
].filter((key) => normalizedRows.some((row) => row[key] !== undefined));
|
|
180
489
|
const keys = preferredKeys.length > 0 ? preferredKeys : uniqueKeys(normalizedRows);
|
|
181
490
|
const widths = keys.map((key) => Math.max(key.length, ...normalizedRows.map((row) => formatCellValue(row[key]).length)));
|
|
182
491
|
const line = (values) => values.map((value, index) => value.padEnd(widths[index])).join(" ");
|
|
@@ -200,7 +509,14 @@ function formatCellValue(value) {
|
|
|
200
509
|
if (value === null || value === undefined) {
|
|
201
510
|
return "";
|
|
202
511
|
}
|
|
203
|
-
if (
|
|
512
|
+
if (Array.isArray(value)) {
|
|
513
|
+
return value.map(formatCellValue).join(",");
|
|
514
|
+
}
|
|
515
|
+
if (isRecord(value)) {
|
|
516
|
+
const displayValue = value.displayName ?? value.name ?? value.userName ?? value.title ?? value.id;
|
|
517
|
+
if (displayValue !== undefined) {
|
|
518
|
+
return String(displayValue);
|
|
519
|
+
}
|
|
204
520
|
return JSON.stringify(value);
|
|
205
521
|
}
|
|
206
522
|
return String(value);
|
|
@@ -225,19 +541,143 @@ function requiredFlag(flags, name) {
|
|
|
225
541
|
}
|
|
226
542
|
return value;
|
|
227
543
|
}
|
|
544
|
+
function requiredCsvFlag(flags, name) {
|
|
545
|
+
return csvValues(requiredFlag(flags, name));
|
|
546
|
+
}
|
|
547
|
+
function projectNameConditions(name) {
|
|
548
|
+
if (!name) {
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
return JSON.stringify({
|
|
552
|
+
conditionGroups: [
|
|
553
|
+
[
|
|
554
|
+
{
|
|
555
|
+
className: "string",
|
|
556
|
+
fieldIdentifier: "name",
|
|
557
|
+
format: "input",
|
|
558
|
+
operator: "CONTAINS",
|
|
559
|
+
toValue: null,
|
|
560
|
+
value: [name]
|
|
561
|
+
}
|
|
562
|
+
]
|
|
563
|
+
]
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
function workitemProjectIds(flags, env) {
|
|
567
|
+
const value = optionalString(flags["project-ids"]) ?? optionalString(flags["project-id"]) ?? env.YUNXIAO_PROJECT_IDS;
|
|
568
|
+
if (!value) {
|
|
569
|
+
throw new Error("Missing workitem project scope. Set --project-id, --project-ids, or YUNXIAO_PROJECT_IDS.");
|
|
570
|
+
}
|
|
571
|
+
return csvValues(value);
|
|
572
|
+
}
|
|
573
|
+
function addQueryProjectId(row, projectId) {
|
|
574
|
+
return isRecord(row) ? { queryProjectId: projectId, ...row } : row;
|
|
575
|
+
}
|
|
576
|
+
function filterWorkitems(rows, flags) {
|
|
577
|
+
const state = optionalString(flags.state);
|
|
578
|
+
const statusNameEn = optionalString(flags["status-name-en"]);
|
|
579
|
+
const statusName = optionalString(flags["status-name"]);
|
|
580
|
+
return rows.filter((row) => {
|
|
581
|
+
if (!isRecord(row)) {
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
if (statusNameEn && statusText(row.status, "nameEn") !== statusNameEn) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
if (statusName && statusText(row.status, "name") !== statusName && statusText(row.status, "displayName") !== statusName) {
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
if (state && state !== "all") {
|
|
591
|
+
const expected = workitemStateNames.get(state);
|
|
592
|
+
if (!expected) {
|
|
593
|
+
throw new Error(`Invalid --state: ${state}. Supported values: todo, doing, in-progress, done, completed, all.`);
|
|
594
|
+
}
|
|
595
|
+
const actualValues = [
|
|
596
|
+
statusText(row.status, "nameEn"),
|
|
597
|
+
statusText(row.status, "name"),
|
|
598
|
+
statusText(row.status, "displayName")
|
|
599
|
+
].filter((value) => Boolean(value));
|
|
600
|
+
return actualValues.some((actual) => expected.includes(actual));
|
|
601
|
+
}
|
|
602
|
+
return true;
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
function statusText(value, key) {
|
|
606
|
+
if (!isRecord(value)) {
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
const text = value[key];
|
|
610
|
+
return typeof text === "string" ? text : undefined;
|
|
611
|
+
}
|
|
612
|
+
function jsonObjectFlag(value) {
|
|
613
|
+
const raw = optionalString(value);
|
|
614
|
+
if (!raw) {
|
|
615
|
+
return {};
|
|
616
|
+
}
|
|
617
|
+
const parsed = JSON.parse(raw);
|
|
618
|
+
if (!isRecord(parsed) || Array.isArray(parsed)) {
|
|
619
|
+
throw new Error("--fields must be a JSON object.");
|
|
620
|
+
}
|
|
621
|
+
return parsed;
|
|
622
|
+
}
|
|
623
|
+
function parseKeyValue(value, flagName) {
|
|
624
|
+
const separatorIndex = value.indexOf("=");
|
|
625
|
+
if (separatorIndex <= 0) {
|
|
626
|
+
throw new Error(`${flagName} expects key=value, got: ${value}`);
|
|
627
|
+
}
|
|
628
|
+
return [value.slice(0, separatorIndex), value.slice(separatorIndex + 1)];
|
|
629
|
+
}
|
|
630
|
+
function parseValue(value) {
|
|
631
|
+
if (value === "true") {
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
if (value === "false") {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
if (value === "null") {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
if (/^[\[{]/.test(value)) {
|
|
641
|
+
return JSON.parse(value);
|
|
642
|
+
}
|
|
643
|
+
return value;
|
|
644
|
+
}
|
|
645
|
+
function csvValues(value) {
|
|
646
|
+
return value
|
|
647
|
+
.split(",")
|
|
648
|
+
.map((item) => item.trim())
|
|
649
|
+
.filter(Boolean);
|
|
650
|
+
}
|
|
651
|
+
function flagValues(value) {
|
|
652
|
+
return Array.isArray(value) ? value : [value];
|
|
653
|
+
}
|
|
654
|
+
function flagStrings(value) {
|
|
655
|
+
if (value === undefined) {
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
return flagValues(value).filter((item) => typeof item === "string");
|
|
659
|
+
}
|
|
660
|
+
function hasFlag(flags, name) {
|
|
661
|
+
const value = flags[name];
|
|
662
|
+
return value === true || (Array.isArray(value) && value.includes(true));
|
|
663
|
+
}
|
|
228
664
|
function optionalString(value) {
|
|
665
|
+
if (Array.isArray(value)) {
|
|
666
|
+
return [...value].reverse().find((item) => typeof item === "string");
|
|
667
|
+
}
|
|
229
668
|
return typeof value === "string" ? value : undefined;
|
|
230
669
|
}
|
|
231
670
|
function stringFlag(value) {
|
|
232
671
|
return optionalString(value);
|
|
233
672
|
}
|
|
234
673
|
function optionalNumber(value) {
|
|
235
|
-
|
|
674
|
+
const stringValue = optionalString(value);
|
|
675
|
+
if (!stringValue) {
|
|
236
676
|
return undefined;
|
|
237
677
|
}
|
|
238
|
-
const number = Number(
|
|
678
|
+
const number = Number(stringValue);
|
|
239
679
|
if (!Number.isInteger(number) || number <= 0) {
|
|
240
|
-
throw new Error(`Expected a positive integer, got: ${
|
|
680
|
+
throw new Error(`Expected a positive integer, got: ${stringValue}`);
|
|
241
681
|
}
|
|
242
682
|
return number;
|
|
243
683
|
}
|
|
@@ -245,16 +685,35 @@ function isRecord(value) {
|
|
|
245
685
|
return typeof value === "object" && value !== null;
|
|
246
686
|
}
|
|
247
687
|
function helpText() {
|
|
248
|
-
return `yunxiao - Yunxiao
|
|
688
|
+
return `yunxiao - Yunxiao DevOps command helper
|
|
249
689
|
|
|
250
690
|
Usage:
|
|
251
691
|
yunxiao mr list [--state opened] [--project-ids 2813489] [--output table|json]
|
|
252
692
|
yunxiao mr review <repositoryId> <localId> [--comment "LGTM"] [--output table|json]
|
|
253
693
|
yunxiao mr merge <repositoryId> <localId> --yes [--merge-type no-fast-forward] [--output table|json]
|
|
254
694
|
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]
|
|
695
|
+
yunxiao mr release <repositoryName> <sourceBranch> <targetBranch> --yes [--create-only] [--title "Release"] [--output table|json]
|
|
256
696
|
yunxiao mr tree <repositoryId> <localId> --from-patch-set-id <id> --to-patch-set-id <id> [--output table|json]
|
|
257
697
|
yunxiao mr patches <repositoryId> <localId> [--output table|json]
|
|
698
|
+
yunxiao project list [--name keyword] [--output table|json]
|
|
699
|
+
yunxiao project get <projectId> [--output table|json]
|
|
700
|
+
yunxiao project members <projectId> [--name keyword] [--role-id project.admin] [--output table|json]
|
|
701
|
+
yunxiao project member-add <projectId> --user-ids user1,user2 [--role-id project.participant] --yes
|
|
702
|
+
yunxiao project member-remove <projectId> --user-ids user1,user2 [--role-id project.participant] --yes
|
|
703
|
+
yunxiao member list [--page 1] [--per-page 100] [--output table|json]
|
|
704
|
+
yunxiao member get <memberId> [--output table|json]
|
|
705
|
+
yunxiao pipeline failed-runs [--hours 24] [--output table|json]
|
|
706
|
+
yunxiao pipeline runs <pipelineId> [--status FAIL] [--start-time 1729178040000] [--end-time 1729181640000] [--output table|json]
|
|
707
|
+
yunxiao pipeline run <pipelineId> <pipelineRunId> [--output table|json]
|
|
708
|
+
yunxiao pipeline retry-job <pipelineId> <pipelineRunId> <jobId> --yes [--output table|json]
|
|
709
|
+
yunxiao workitem list --project-ids p1,p2 [--assigned-to userId] [--state todo|doing|done] [--from "2026-04-01 00:00:00"] [--to "2026-04-30 23:59:59"]
|
|
710
|
+
yunxiao workitem mine --project-ids p1,p2 [--state todo|doing|done] [--output table|json]
|
|
711
|
+
yunxiao workitem get <workitemId> [--output table|json]
|
|
712
|
+
yunxiao workitem update <workitemId> --yes [--assigned-to userId] [--participants user1,user2] [--due-date "2026-05-01 18:00:00"] [--field key=value]
|
|
713
|
+
yunxiao workitem activities <workitemId> [--output table|json]
|
|
714
|
+
yunxiao workitem comments <workitemId> [--output table|json]
|
|
715
|
+
yunxiao workitem comment <workitemId> --content "comment" --yes [--output table|json]
|
|
716
|
+
yunxiao workitem timeline <workitemId> [--output table|json]
|
|
258
717
|
|
|
259
718
|
Output:
|
|
260
719
|
--output table Plain text table output, default
|
|
@@ -266,5 +725,7 @@ Config:
|
|
|
266
725
|
YUNXIAO_DOMAIN API domain, default openapi-rdc.aliyuncs.com
|
|
267
726
|
YUNXIAO_ORGANIZATION_ID Required for center edition
|
|
268
727
|
YUNXIAO_EDITION center or region
|
|
728
|
+
YUNXIAO_PROJECT_IDS Default comma-separated project IDs for workitem commands
|
|
729
|
+
YUNXIAO_WORKITEM_CATEGORIES Default workitem categories, default Req,Task,Bug
|
|
269
730
|
`;
|
|
270
731
|
}
|