@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.
@@ -42,6 +42,85 @@ export interface ListRepositoriesOptions {
42
42
  search?: string;
43
43
  archived?: boolean;
44
44
  }
45
+ export interface SearchProjectsOptions {
46
+ conditions?: string;
47
+ extraConditions?: string;
48
+ orderBy?: "gmtCreate" | "name" | string;
49
+ page?: number;
50
+ perPage?: number;
51
+ sort?: "asc" | "desc" | string;
52
+ }
53
+ export interface ListProjectMembersOptions {
54
+ name?: string;
55
+ roleId?: string;
56
+ }
57
+ export interface ChangeProjectMemberOptions {
58
+ operatorId?: string;
59
+ roleId: string;
60
+ userIds: string[];
61
+ }
62
+ export interface DeleteProjectMemberOptions {
63
+ operatorId?: string;
64
+ roleId: string;
65
+ userIds: string[];
66
+ }
67
+ export interface ListMembersOptions {
68
+ page?: number;
69
+ perPage?: number;
70
+ }
71
+ export interface SearchWorkitemsOptions {
72
+ category: string;
73
+ conditions?: string;
74
+ orderBy?: "gmtCreate" | "gmtModified" | "updateStatusAt" | "name" | string;
75
+ page?: number;
76
+ perPage?: number;
77
+ sort?: "asc" | "desc" | string;
78
+ spaceId: string;
79
+ spaceType?: "Project" | "Program" | string;
80
+ }
81
+ export interface CreateWorkitemCommentOptions {
82
+ content: string;
83
+ operatorId?: string;
84
+ parentId?: string;
85
+ }
86
+ export type UpdateWorkitemOptions = Record<string, unknown>;
87
+ export interface ListPipelineRunsOptions {
88
+ page?: number;
89
+ perPage?: number;
90
+ startTime?: number;
91
+ endTime?: number;
92
+ status?: "FAIL" | "SUCCESS" | "RUNNING" | string;
93
+ triggerMode?: number;
94
+ }
95
+ export interface ListPipelinesOptions {
96
+ createStartTime?: number;
97
+ createEndTime?: number;
98
+ executeStartTime?: number;
99
+ executeEndTime?: number;
100
+ pipelineName?: string;
101
+ statusList?: string;
102
+ page?: number;
103
+ perPage?: number;
104
+ }
105
+ export interface ListLatestFailedPipelineRunsOptions {
106
+ hours?: number;
107
+ startTime?: number;
108
+ endTime?: number;
109
+ perPage?: number;
110
+ }
111
+ export interface LatestFailedPipelineRun {
112
+ pipelineId: number | string;
113
+ pipelineName: string;
114
+ pipelineRunId: number | string;
115
+ status?: string;
116
+ triggerMode?: number | string;
117
+ triggerModeName: string;
118
+ startTime?: number | string;
119
+ startTimeText: string;
120
+ endTime?: number | string;
121
+ endTimeText: string;
122
+ creatorAccountId?: string;
123
+ }
45
124
  export interface RepositorySummary {
46
125
  id: number | string;
47
126
  name?: string;
@@ -71,6 +150,7 @@ export interface ReleaseByRepositoryNameOptions {
71
150
  repositoryName: string;
72
151
  sourceBranch: string;
73
152
  targetBranch: string;
153
+ createOnly?: boolean;
74
154
  title?: string;
75
155
  description?: string;
76
156
  mergeType?: MergeChangeRequestOptions["mergeType"];
@@ -78,8 +158,8 @@ export interface ReleaseByRepositoryNameOptions {
78
158
  export interface ReleaseResult {
79
159
  repository: RepositorySummary;
80
160
  changeRequest: unknown;
81
- review: unknown;
82
- merge: unknown;
161
+ review: unknown | null;
162
+ merge: unknown | null;
83
163
  }
84
164
  export interface ApproveAndMergeOptions extends ReviewChangeRequestOptions, MergeChangeRequestOptions {
85
165
  }
@@ -100,6 +180,25 @@ export declare class YunxiaoClient {
100
180
  listChangeRequests(options?: ListChangeRequestsOptions): Promise<unknown>;
101
181
  getCurrentUser(): Promise<CurrentUser>;
102
182
  listRepositories(options?: ListRepositoriesOptions): Promise<RepositorySummary[]>;
183
+ searchProjects(options?: SearchProjectsOptions): Promise<unknown>;
184
+ getProject(projectId: string): Promise<unknown>;
185
+ listProjectMembers(projectId: string, options?: ListProjectMembersOptions): Promise<unknown>;
186
+ createProjectMember(projectId: string, options: ChangeProjectMemberOptions): Promise<unknown>;
187
+ deleteProjectMember(projectId: string, options: DeleteProjectMemberOptions): Promise<unknown>;
188
+ listMembers(options?: ListMembersOptions): Promise<unknown>;
189
+ getMember(memberId: string): Promise<unknown>;
190
+ searchWorkitems(options: SearchWorkitemsOptions): Promise<unknown>;
191
+ getWorkitem(workitemId: string): Promise<unknown>;
192
+ updateWorkitem(workitemId: string, options: UpdateWorkitemOptions): Promise<unknown>;
193
+ listWorkitemActivities(workitemId: string): Promise<unknown>;
194
+ listWorkitemComments(workitemId: string): Promise<unknown>;
195
+ createWorkitemComment(workitemId: string, options: CreateWorkitemCommentOptions): Promise<unknown>;
196
+ getWorkitemTimeline(workitemId: string): Promise<unknown>;
197
+ listPipelineRuns(pipelineId: string, options?: ListPipelineRunsOptions): Promise<unknown>;
198
+ listPipelines(options?: ListPipelinesOptions): Promise<unknown>;
199
+ listLatestFailedPipelineRuns(options?: ListLatestFailedPipelineRunsOptions): Promise<LatestFailedPipelineRun[]>;
200
+ getPipelineRun(pipelineId: string, pipelineRunId: string): Promise<unknown>;
201
+ retryPipelineJobRun(pipelineId: string, pipelineRunId: string, jobId: string): Promise<unknown>;
103
202
  createChangeRequest(repositoryId: string, options: CreateChangeRequestOptions): Promise<unknown>;
104
203
  reviewChangeRequest(repositoryId: string, localId: number, options?: ReviewChangeRequestOptions): Promise<unknown>;
105
204
  mergeChangeRequest(repositoryId: string, localId: number, options?: MergeChangeRequestOptions): Promise<unknown>;
@@ -111,5 +210,13 @@ export declare class YunxiaoClient {
111
210
  private repositoriesPath;
112
211
  private repositoryPath;
113
212
  private repositoryChangeRequestPath;
213
+ private projexPath;
214
+ private workitemsSearchPath;
215
+ private projectPath;
216
+ private platformMembersPath;
217
+ private pipelinesPath;
218
+ private pipelinePath;
219
+ private listAllPipelines;
220
+ private listAllPipelineRuns;
114
221
  private request;
115
222
  }
@@ -40,6 +40,150 @@ export class YunxiaoClient {
40
40
  listRepositories(options = {}) {
41
41
  return this.request("GET", this.repositoriesPath(), options);
42
42
  }
43
+ searchProjects(options = {}) {
44
+ return this.request("POST", this.projexPath("/projects:search"), undefined, options);
45
+ }
46
+ getProject(projectId) {
47
+ return this.request("GET", this.projectPath(projectId));
48
+ }
49
+ listProjectMembers(projectId, options = {}) {
50
+ return this.request("GET", `${this.projectPath(projectId)}/members`, options);
51
+ }
52
+ createProjectMember(projectId, options) {
53
+ return this.request("POST", `${this.projectPath(projectId)}/members`, undefined, {
54
+ operatorId: options.operatorId,
55
+ roleId: options.roleId,
56
+ userIds: options.userIds
57
+ });
58
+ }
59
+ deleteProjectMember(projectId, options) {
60
+ return this.request("DELETE", `${this.projectPath(projectId)}/members`, undefined, {
61
+ operatorId: options.operatorId,
62
+ roleIds: [options.roleId],
63
+ userId: options.userIds.length === 1 ? options.userIds[0] : undefined,
64
+ userIds: options.userIds.length > 1 ? options.userIds : undefined
65
+ });
66
+ }
67
+ listMembers(options = {}) {
68
+ return this.request("GET", this.platformMembersPath(), options);
69
+ }
70
+ getMember(memberId) {
71
+ return this.request("GET", `${this.platformMembersPath()}/${encodePathSegment(memberId)}`);
72
+ }
73
+ searchWorkitems(options) {
74
+ return this.request("POST", this.workitemsSearchPath(), undefined, {
75
+ category: options.category,
76
+ conditions: options.conditions,
77
+ orderBy: options.orderBy,
78
+ page: options.page,
79
+ perPage: options.perPage,
80
+ sort: options.sort,
81
+ spaceId: options.spaceId,
82
+ spaceType: options.spaceType ?? "Project"
83
+ });
84
+ }
85
+ getWorkitem(workitemId) {
86
+ return this.request("GET", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}`);
87
+ }
88
+ updateWorkitem(workitemId, options) {
89
+ return this.request("PUT", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}`, undefined, options);
90
+ }
91
+ listWorkitemActivities(workitemId) {
92
+ return this.request("GET", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}/activities`);
93
+ }
94
+ listWorkitemComments(workitemId) {
95
+ return this.request("GET", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}/comments`);
96
+ }
97
+ createWorkitemComment(workitemId, options) {
98
+ return this.request("POST", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}/comments`, undefined, {
99
+ content: options.content,
100
+ operatorId: options.operatorId,
101
+ parentId: options.parentId
102
+ });
103
+ }
104
+ async getWorkitemTimeline(workitemId) {
105
+ const [workitem, activities, comments] = await Promise.all([
106
+ this.getWorkitem(workitemId),
107
+ this.listWorkitemActivities(workitemId),
108
+ this.listWorkitemComments(workitemId)
109
+ ]);
110
+ return { workitem, activities, comments };
111
+ }
112
+ listPipelineRuns(pipelineId, options = {}) {
113
+ return this.request("GET", `${this.pipelinePath(pipelineId)}/runs`, {
114
+ page: options.page,
115
+ perPage: options.perPage,
116
+ startTime: options.startTime,
117
+ endTme: options.endTime,
118
+ status: options.status,
119
+ triggerMode: options.triggerMode
120
+ });
121
+ }
122
+ listPipelines(options = {}) {
123
+ return this.request("GET", this.pipelinesPath(), {
124
+ page: options.page,
125
+ perPage: options.perPage,
126
+ executeStartTime: options.executeStartTime,
127
+ executeEndTime: options.executeEndTime,
128
+ statusList: options.statusList,
129
+ pipelineName: options.pipelineName,
130
+ createStartTime: options.createStartTime,
131
+ createEndTime: options.createEndTime
132
+ });
133
+ }
134
+ async listLatestFailedPipelineRuns(options = {}) {
135
+ const endTime = options.endTime ?? Date.now();
136
+ const startTime = options.startTime ?? endTime - (options.hours ?? 24) * 60 * 60 * 1000;
137
+ const perPage = options.perPage ?? 30;
138
+ const pipelines = await this.listAllPipelines({
139
+ page: 1,
140
+ perPage,
141
+ executeStartTime: startTime,
142
+ executeEndTime: endTime,
143
+ statusList: "FAIL"
144
+ });
145
+ const uniquePipelines = uniqueBy(pipelines, (pipeline) => String(recordValue(pipeline, "pipelineId") ?? recordValue(pipeline, "id")));
146
+ const results = [];
147
+ for (const pipeline of uniquePipelines) {
148
+ const pipelineId = recordValue(pipeline, "pipelineId") ?? recordValue(pipeline, "id");
149
+ if (pipelineId === undefined || pipelineId === null || pipelineId === "") {
150
+ continue;
151
+ }
152
+ const runs = await this.listAllPipelineRuns(String(pipelineId), {
153
+ page: 1,
154
+ perPage,
155
+ startTime,
156
+ endTime,
157
+ status: "FAIL"
158
+ });
159
+ const latestRun = runs
160
+ .filter((run) => timestampInRange(recordValue(run, "startTime"), startTime, endTime))
161
+ .sort((left, right) => Number(recordValue(right, "startTime") ?? 0) - Number(recordValue(left, "startTime") ?? 0))[0];
162
+ if (!latestRun) {
163
+ continue;
164
+ }
165
+ results.push({
166
+ pipelineId: normalizeNumericId(pipelineId),
167
+ pipelineName: stringRecordValue(pipeline, "pipelineName") ?? stringRecordValue(pipeline, "name") ?? "",
168
+ pipelineRunId: normalizeNumericId(recordValue(latestRun, "pipelineRunId") ?? ""),
169
+ status: stringRecordValue(latestRun, "status"),
170
+ triggerMode: numericOrStringValue(recordValue(latestRun, "triggerMode")),
171
+ triggerModeName: triggerModeName(recordValue(latestRun, "triggerMode")),
172
+ startTime: numericOrStringValue(recordValue(latestRun, "startTime")),
173
+ startTimeText: formatTimestamp(recordValue(latestRun, "startTime")),
174
+ endTime: numericOrStringValue(recordValue(latestRun, "endTime")),
175
+ endTimeText: formatTimestamp(recordValue(latestRun, "endTime")),
176
+ creatorAccountId: stringRecordValue(latestRun, "creatorAccountId")
177
+ });
178
+ }
179
+ return results.sort((left, right) => Number(right.startTime ?? 0) - Number(left.startTime ?? 0));
180
+ }
181
+ getPipelineRun(pipelineId, pipelineRunId) {
182
+ return this.request("GET", `${this.pipelinePath(pipelineId)}/runs/${encodePathSegment(pipelineRunId)}`);
183
+ }
184
+ retryPipelineJobRun(pipelineId, pipelineRunId, jobId) {
185
+ return this.request("PUT", `${this.pipelinePath(pipelineId)}/pipelineRuns/${encodePathSegment(pipelineRunId)}/jobs/${encodePathSegment(jobId)}/retry`);
186
+ }
43
187
  createChangeRequest(repositoryId, options) {
44
188
  return this.request("POST", `${this.repositoryPath(repositoryId)}/changeRequests`, undefined, {
45
189
  createFrom: options.createFrom ?? "WEB",
@@ -116,6 +260,14 @@ export class YunxiaoClient {
116
260
  description: options.description
117
261
  });
118
262
  const localId = localIdFromChangeRequest(changeRequest);
263
+ if (options.createOnly) {
264
+ return {
265
+ repository,
266
+ changeRequest,
267
+ review: null,
268
+ merge: null
269
+ };
270
+ }
119
271
  const review = await this.reviewChangeRequest(repositoryId, localId, {
120
272
  reviewComment: "Approved by yunxiao-cli release workflow"
121
273
  });
@@ -152,6 +304,65 @@ export class YunxiaoClient {
152
304
  repositoryChangeRequestPath(repositoryId, localId) {
153
305
  return `${this.repositoryPath(repositoryId)}/changeRequests/${localId}`;
154
306
  }
307
+ projexPath(path) {
308
+ if (this.edition === "center") {
309
+ return `/oapi/v1/projex/organizations/${encodePathSegment(this.organizationId ?? "")}${path}`;
310
+ }
311
+ return `/oapi/v1/projex${path}`;
312
+ }
313
+ workitemsSearchPath() {
314
+ if (this.edition === "center") {
315
+ return this.projexPath("/workitems:search");
316
+ }
317
+ return this.organizationId
318
+ ? `/oapi/v1/projex/${encodePathSegment(this.organizationId)}/workitems:search`
319
+ : "/oapi/v1/projex/workitems:search";
320
+ }
321
+ projectPath(projectId) {
322
+ return `${this.projexPath("/projects")}/${encodePathSegment(projectId)}`;
323
+ }
324
+ platformMembersPath() {
325
+ if (this.edition === "center") {
326
+ return `/oapi/v1/platform/organizations/${encodePathSegment(this.organizationId ?? "")}/members`;
327
+ }
328
+ return "/oapi/v1/platform/members";
329
+ }
330
+ pipelinesPath() {
331
+ if (this.edition === "center") {
332
+ return `/oapi/v1/flow/organizations/${encodePathSegment(this.organizationId ?? "")}/pipelines`;
333
+ }
334
+ return "/oapi/v1/flow/pipelines";
335
+ }
336
+ pipelinePath(pipelineId) {
337
+ const encodedPipelineId = encodePathSegment(pipelineId);
338
+ return `${this.pipelinesPath()}/${encodedPipelineId}`;
339
+ }
340
+ async listAllPipelines(options) {
341
+ const rows = [];
342
+ const perPage = options.perPage ?? 30;
343
+ for (let page = options.page ?? 1;; page += 1) {
344
+ const response = await this.listPipelines({ ...options, page, perPage });
345
+ const pageRows = arrayRecords(response);
346
+ rows.push(...pageRows);
347
+ if (pageRows.length < perPage) {
348
+ break;
349
+ }
350
+ }
351
+ return rows;
352
+ }
353
+ async listAllPipelineRuns(pipelineId, options) {
354
+ const rows = [];
355
+ const perPage = options.perPage ?? 30;
356
+ for (let page = options.page ?? 1;; page += 1) {
357
+ const response = await this.listPipelineRuns(pipelineId, { ...options, page, perPage });
358
+ const pageRows = arrayRecords(response);
359
+ rows.push(...pageRows);
360
+ if (pageRows.length < perPage) {
361
+ break;
362
+ }
363
+ }
364
+ return rows;
365
+ }
155
366
  async request(method, path, query, body) {
156
367
  const url = new URL(`https://${this.domain}${path}`);
157
368
  for (const [key, value] of Object.entries(query ?? {})) {
@@ -186,6 +397,75 @@ function stripProtocol(domain) {
186
397
  function encodePathSegment(value) {
187
398
  return encodeURIComponent(value);
188
399
  }
400
+ function arrayRecords(value) {
401
+ return Array.isArray(value) ? value.filter(isRecord) : [];
402
+ }
403
+ function isRecord(value) {
404
+ return typeof value === "object" && value !== null && !Array.isArray(value);
405
+ }
406
+ function recordValue(record, key) {
407
+ return record[key];
408
+ }
409
+ function stringRecordValue(record, key) {
410
+ const value = record[key];
411
+ return typeof value === "string" ? value : undefined;
412
+ }
413
+ function uniqueBy(values, keyFn) {
414
+ const seen = new Set();
415
+ const result = [];
416
+ for (const value of values) {
417
+ const key = keyFn(value);
418
+ if (!key || seen.has(key)) {
419
+ continue;
420
+ }
421
+ seen.add(key);
422
+ result.push(value);
423
+ }
424
+ return result;
425
+ }
426
+ function timestampInRange(value, startTime, endTime) {
427
+ const timestamp = Number(value);
428
+ return Number.isFinite(timestamp) && timestamp >= startTime && timestamp <= endTime;
429
+ }
430
+ function normalizeNumericId(value) {
431
+ const numberValue = Number(value);
432
+ return Number.isInteger(numberValue) ? numberValue : String(value);
433
+ }
434
+ function numericOrStringValue(value) {
435
+ if (value === undefined || value === null || value === "") {
436
+ return undefined;
437
+ }
438
+ const numberValue = Number(value);
439
+ return Number.isFinite(numberValue) ? numberValue : String(value);
440
+ }
441
+ function formatTimestamp(value) {
442
+ const timestamp = Number(value);
443
+ if (!Number.isFinite(timestamp)) {
444
+ return "";
445
+ }
446
+ const date = new Date(timestamp);
447
+ if (Number.isNaN(date.getTime())) {
448
+ return "";
449
+ }
450
+ const pad = (part) => String(part).padStart(2, "0");
451
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
452
+ }
453
+ function triggerModeName(value) {
454
+ switch (Number(value)) {
455
+ case 1:
456
+ return "人工触发";
457
+ case 2:
458
+ return "定时触发";
459
+ case 3:
460
+ return "代码提交触发";
461
+ case 5:
462
+ return "流水线触发";
463
+ case 6:
464
+ return "Webhook触发";
465
+ default:
466
+ return value === undefined || value === null ? "" : String(value);
467
+ }
468
+ }
189
469
  function compactBody(body) {
190
470
  return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined && value !== null && value !== ""));
191
471
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coderpp/yunxiao-cli",
3
- "version": "0.1.7",
4
- "description": "A CLI for Yunxiao Codeup merge request review and merge workflows.",
3
+ "version": "0.1.9",
4
+ "description": "A CLI for Yunxiao merge request, pipeline retry, project member, and workitem workflows.",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -18,6 +18,11 @@
18
18
  "build": "tsc -p tsconfig.json",
19
19
  "pack:dry-run": "npm pack --dry-run",
20
20
  "prepack": "npm run build",
21
+ "skills:sync": "scripts/sync-skills.sh",
22
+ "skills:watch": "scripts/watch-skills.sh",
23
+ "skills:watch:start": "scripts/start-skill-sync-watch.sh",
24
+ "skills:watch:status": "scripts/status-skill-sync-watch.sh",
25
+ "skills:watch:stop": "scripts/stop-skill-sync-watch.sh",
21
26
  "test": "tsc -p tsconfig.json && node --test dist/test/*.test.js"
22
27
  },
23
28
  "publishConfig": {