@forge-glance/sdk 0.1.1 → 0.2.1
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/dist/GitHubProvider.d.ts +14 -3
- package/dist/GitHubProvider.js +100 -3
- package/dist/GitLabProvider.d.ts +21 -4
- package/dist/GitLabProvider.js +194 -11
- package/dist/GitProvider.d.ts +64 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.js +294 -14
- package/dist/providers.js +294 -14
- package/dist/types.d.ts +64 -1
- package/package.json +1 -1
- package/src/GitHubProvider.ts +431 -153
- package/src/GitLabProvider.ts +437 -87
- package/src/GitProvider.ts +113 -21
- package/src/index.ts +22 -15
- package/src/types.ts +70 -1
package/dist/providers.js
CHANGED
|
@@ -92,6 +92,10 @@ var MR_DASHBOARD_FRAGMENT = `
|
|
|
92
92
|
approvalsLeft
|
|
93
93
|
resolvableDiscussionsCount
|
|
94
94
|
resolvedDiscussionsCount
|
|
95
|
+
autoMergeEnabled
|
|
96
|
+
autoMergeStrategy
|
|
97
|
+
mergeUser { id username name avatarUrl }
|
|
98
|
+
mergeAfter
|
|
95
99
|
headPipeline {
|
|
96
100
|
id iid status
|
|
97
101
|
createdAt
|
|
@@ -212,7 +216,11 @@ function toMR(gql, role, baseURL) {
|
|
|
212
216
|
approved: gql.approved ?? false,
|
|
213
217
|
approvedBy: gql.approvedBy.nodes.map(toUserRef),
|
|
214
218
|
diffStats,
|
|
215
|
-
detailedMergeStatus: gql.detailedMergeStatus ?? null
|
|
219
|
+
detailedMergeStatus: gql.detailedMergeStatus ?? null,
|
|
220
|
+
autoMergeEnabled: gql.autoMergeEnabled ?? false,
|
|
221
|
+
autoMergeStrategy: gql.autoMergeStrategy ?? null,
|
|
222
|
+
mergeUser: gql.mergeUser ? toUserRef(gql.mergeUser) : null,
|
|
223
|
+
mergeAfter: gql.mergeAfter ?? null
|
|
216
224
|
};
|
|
217
225
|
}
|
|
218
226
|
var MR_DETAIL_QUERY = `
|
|
@@ -236,8 +244,20 @@ class GitLabProvider {
|
|
|
236
244
|
this.baseURL = baseURL.replace(/\/$/, "");
|
|
237
245
|
this.token = token;
|
|
238
246
|
this.log = options.logger ?? noopLogger;
|
|
239
|
-
this.mrDetailFetcher = new MRDetailFetcher(this.baseURL, token, {
|
|
247
|
+
this.mrDetailFetcher = new MRDetailFetcher(this.baseURL, token, {
|
|
248
|
+
logger: this.log
|
|
249
|
+
});
|
|
240
250
|
}
|
|
251
|
+
capabilities = {
|
|
252
|
+
canMerge: true,
|
|
253
|
+
canApprove: true,
|
|
254
|
+
canUnapprove: true,
|
|
255
|
+
canRebase: true,
|
|
256
|
+
canAutoMerge: true,
|
|
257
|
+
canResolveDiscussions: true,
|
|
258
|
+
canRetryPipeline: true,
|
|
259
|
+
canRequestReReview: true
|
|
260
|
+
};
|
|
241
261
|
async validateToken() {
|
|
242
262
|
const url = `${this.baseURL}/api/v4/user`;
|
|
243
263
|
const res = await fetch(url, {
|
|
@@ -343,7 +363,11 @@ class GitLabProvider {
|
|
|
343
363
|
headers: { "PRIVATE-TOKEN": this.token }
|
|
344
364
|
});
|
|
345
365
|
if (!res.ok) {
|
|
346
|
-
this.log.warn("fetchPullRequestByBranch failed", {
|
|
366
|
+
this.log.warn("fetchPullRequestByBranch failed", {
|
|
367
|
+
projectPath,
|
|
368
|
+
sourceBranch,
|
|
369
|
+
status: res.status
|
|
370
|
+
});
|
|
347
371
|
return null;
|
|
348
372
|
}
|
|
349
373
|
const mrs = await res.json();
|
|
@@ -381,10 +405,7 @@ class GitLabProvider {
|
|
|
381
405
|
throw new Error(`createPullRequest failed: ${res.status} ${text}`);
|
|
382
406
|
}
|
|
383
407
|
const created = await res.json();
|
|
384
|
-
|
|
385
|
-
if (!pr)
|
|
386
|
-
throw new Error("Created MR but failed to fetch it back");
|
|
387
|
-
return pr;
|
|
408
|
+
return this.fetchSingleMRWithRetry(input.projectPath, created.iid, "Created MR but failed to fetch it back");
|
|
388
409
|
}
|
|
389
410
|
async updatePullRequest(projectPath, mrIid, input) {
|
|
390
411
|
const encoded = encodeURIComponent(projectPath);
|
|
@@ -417,10 +438,7 @@ class GitLabProvider {
|
|
|
417
438
|
const text = await res.text();
|
|
418
439
|
throw new Error(`updatePullRequest failed: ${res.status} ${text}`);
|
|
419
440
|
}
|
|
420
|
-
|
|
421
|
-
if (!pr)
|
|
422
|
-
throw new Error("Updated MR but failed to fetch it back");
|
|
423
|
-
return pr;
|
|
441
|
+
return this.fetchSingleMRWithRetry(projectPath, mrIid, "Updated MR but failed to fetch it back");
|
|
424
442
|
}
|
|
425
443
|
async restRequest(method, path, body) {
|
|
426
444
|
const url = `${this.baseURL}${path}`;
|
|
@@ -436,6 +454,171 @@ class GitLabProvider {
|
|
|
436
454
|
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
437
455
|
});
|
|
438
456
|
}
|
|
457
|
+
async mergePullRequest(projectPath, mrIid, input) {
|
|
458
|
+
const encoded = encodeURIComponent(projectPath);
|
|
459
|
+
const body = {};
|
|
460
|
+
if (input?.commitMessage != null)
|
|
461
|
+
body.merge_commit_message = input.commitMessage;
|
|
462
|
+
if (input?.squashCommitMessage != null)
|
|
463
|
+
body.squash_commit_message = input.squashCommitMessage;
|
|
464
|
+
if (input?.squash != null)
|
|
465
|
+
body.squash = input.squash;
|
|
466
|
+
if (input?.shouldRemoveSourceBranch != null)
|
|
467
|
+
body.should_remove_source_branch = input.shouldRemoveSourceBranch;
|
|
468
|
+
if (input?.sha != null)
|
|
469
|
+
body.sha = input.sha;
|
|
470
|
+
if (input?.mergeMethod === "squash" && input?.squash == null)
|
|
471
|
+
body.squash = true;
|
|
472
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}/merge`, {
|
|
473
|
+
method: "PUT",
|
|
474
|
+
headers: {
|
|
475
|
+
"PRIVATE-TOKEN": this.token,
|
|
476
|
+
"Content-Type": "application/json"
|
|
477
|
+
},
|
|
478
|
+
body: JSON.stringify(body)
|
|
479
|
+
});
|
|
480
|
+
if (!res.ok) {
|
|
481
|
+
const text = await res.text();
|
|
482
|
+
throw new Error(`mergePullRequest failed: ${res.status} ${text}`);
|
|
483
|
+
}
|
|
484
|
+
return this.fetchSingleMRWithRetry(projectPath, mrIid, "Merged MR but failed to fetch it back");
|
|
485
|
+
}
|
|
486
|
+
async approvePullRequest(projectPath, mrIid) {
|
|
487
|
+
const encoded = encodeURIComponent(projectPath);
|
|
488
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}/approve`, {
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: { "PRIVATE-TOKEN": this.token }
|
|
491
|
+
});
|
|
492
|
+
if (!res.ok) {
|
|
493
|
+
const text = await res.text().catch(() => "");
|
|
494
|
+
throw new Error(`approvePullRequest failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async unapprovePullRequest(projectPath, mrIid) {
|
|
498
|
+
const encoded = encodeURIComponent(projectPath);
|
|
499
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}/unapprove`, {
|
|
500
|
+
method: "POST",
|
|
501
|
+
headers: { "PRIVATE-TOKEN": this.token }
|
|
502
|
+
});
|
|
503
|
+
if (!res.ok) {
|
|
504
|
+
const text = await res.text().catch(() => "");
|
|
505
|
+
throw new Error(`unapprovePullRequest failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async rebasePullRequest(projectPath, mrIid) {
|
|
509
|
+
const encoded = encodeURIComponent(projectPath);
|
|
510
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}/rebase`, {
|
|
511
|
+
method: "PUT",
|
|
512
|
+
headers: { "PRIVATE-TOKEN": this.token }
|
|
513
|
+
});
|
|
514
|
+
if (!res.ok) {
|
|
515
|
+
const text = await res.text().catch(() => "");
|
|
516
|
+
throw new Error(`rebasePullRequest failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async setAutoMerge(projectPath, mrIid) {
|
|
520
|
+
const encoded = encodeURIComponent(projectPath);
|
|
521
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}/merge`, {
|
|
522
|
+
method: "PUT",
|
|
523
|
+
headers: {
|
|
524
|
+
"PRIVATE-TOKEN": this.token,
|
|
525
|
+
"Content-Type": "application/json"
|
|
526
|
+
},
|
|
527
|
+
body: JSON.stringify({ merge_when_pipeline_succeeds: true })
|
|
528
|
+
});
|
|
529
|
+
if (!res.ok) {
|
|
530
|
+
const text = await res.text().catch(() => "");
|
|
531
|
+
throw new Error(`setAutoMerge failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async cancelAutoMerge(projectPath, mrIid) {
|
|
535
|
+
const encoded = encodeURIComponent(projectPath);
|
|
536
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}/cancel_merge_when_pipeline_succeeds`, {
|
|
537
|
+
method: "POST",
|
|
538
|
+
headers: { "PRIVATE-TOKEN": this.token }
|
|
539
|
+
});
|
|
540
|
+
if (!res.ok) {
|
|
541
|
+
const text = await res.text().catch(() => "");
|
|
542
|
+
throw new Error(`cancelAutoMerge failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async resolveDiscussion(projectPath, mrIid, discussionId) {
|
|
546
|
+
const encoded = encodeURIComponent(projectPath);
|
|
547
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}/discussions/${discussionId}`, {
|
|
548
|
+
method: "PUT",
|
|
549
|
+
headers: {
|
|
550
|
+
"PRIVATE-TOKEN": this.token,
|
|
551
|
+
"Content-Type": "application/json"
|
|
552
|
+
},
|
|
553
|
+
body: JSON.stringify({ resolved: true })
|
|
554
|
+
});
|
|
555
|
+
if (!res.ok) {
|
|
556
|
+
const text = await res.text().catch(() => "");
|
|
557
|
+
throw new Error(`resolveDiscussion failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async unresolveDiscussion(projectPath, mrIid, discussionId) {
|
|
561
|
+
const encoded = encodeURIComponent(projectPath);
|
|
562
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}/discussions/${discussionId}`, {
|
|
563
|
+
method: "PUT",
|
|
564
|
+
headers: {
|
|
565
|
+
"PRIVATE-TOKEN": this.token,
|
|
566
|
+
"Content-Type": "application/json"
|
|
567
|
+
},
|
|
568
|
+
body: JSON.stringify({ resolved: false })
|
|
569
|
+
});
|
|
570
|
+
if (!res.ok) {
|
|
571
|
+
const text = await res.text().catch(() => "");
|
|
572
|
+
throw new Error(`unresolveDiscussion failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async retryPipeline(projectPath, pipelineId) {
|
|
576
|
+
const encoded = encodeURIComponent(projectPath);
|
|
577
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/pipelines/${pipelineId}/retry`, {
|
|
578
|
+
method: "POST",
|
|
579
|
+
headers: { "PRIVATE-TOKEN": this.token }
|
|
580
|
+
});
|
|
581
|
+
if (!res.ok) {
|
|
582
|
+
const text = await res.text().catch(() => "");
|
|
583
|
+
throw new Error(`retryPipeline failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async requestReReview(projectPath, mrIid, _reviewerUsernames) {
|
|
587
|
+
const encoded = encodeURIComponent(projectPath);
|
|
588
|
+
const mrRes = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}`, { headers: { "PRIVATE-TOKEN": this.token } });
|
|
589
|
+
if (!mrRes.ok) {
|
|
590
|
+
const text = await mrRes.text().catch(() => "");
|
|
591
|
+
throw new Error(`requestReReview: failed to fetch MR: ${mrRes.status}${text ? ` — ${text}` : ""}`);
|
|
592
|
+
}
|
|
593
|
+
const mr = await mrRes.json();
|
|
594
|
+
const reviewerIds = mr.reviewers?.map((r) => r.id) ?? [];
|
|
595
|
+
if (reviewerIds.length === 0) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const res = await fetch(`${this.baseURL}/api/v4/projects/${encoded}/merge_requests/${mrIid}`, {
|
|
599
|
+
method: "PUT",
|
|
600
|
+
headers: {
|
|
601
|
+
"PRIVATE-TOKEN": this.token,
|
|
602
|
+
"Content-Type": "application/json"
|
|
603
|
+
},
|
|
604
|
+
body: JSON.stringify({ reviewer_ids: reviewerIds })
|
|
605
|
+
});
|
|
606
|
+
if (!res.ok) {
|
|
607
|
+
const text = await res.text().catch(() => "");
|
|
608
|
+
throw new Error(`requestReReview failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async fetchSingleMRWithRetry(projectPath, mrIid, errorMessage) {
|
|
612
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
613
|
+
if (attempt > 0) {
|
|
614
|
+
await new Promise((r) => setTimeout(r, attempt * 300));
|
|
615
|
+
}
|
|
616
|
+
const pr = await this.fetchSingleMR(projectPath, mrIid, null);
|
|
617
|
+
if (pr)
|
|
618
|
+
return pr;
|
|
619
|
+
}
|
|
620
|
+
throw new Error(errorMessage);
|
|
621
|
+
}
|
|
439
622
|
async runQuery(query, variables) {
|
|
440
623
|
const url = `${this.baseURL}/api/graphql`;
|
|
441
624
|
const body = JSON.stringify({ query, variables: variables ?? {} });
|
|
@@ -551,6 +734,16 @@ class GitHubProvider {
|
|
|
551
734
|
this.apiBase = `${this.baseURL}/api/v3`;
|
|
552
735
|
}
|
|
553
736
|
}
|
|
737
|
+
capabilities = {
|
|
738
|
+
canMerge: true,
|
|
739
|
+
canApprove: true,
|
|
740
|
+
canUnapprove: false,
|
|
741
|
+
canRebase: false,
|
|
742
|
+
canAutoMerge: false,
|
|
743
|
+
canResolveDiscussions: false,
|
|
744
|
+
canRetryPipeline: true,
|
|
745
|
+
canRequestReReview: true
|
|
746
|
+
};
|
|
554
747
|
async validateToken() {
|
|
555
748
|
const res = await this.api("GET", "/user");
|
|
556
749
|
if (!res.ok) {
|
|
@@ -592,7 +785,9 @@ class GitHubProvider {
|
|
|
592
785
|
]);
|
|
593
786
|
return this.toPullRequest(pr, prRoles, reviews, checkRuns);
|
|
594
787
|
}));
|
|
595
|
-
this.log.debug("GitHubProvider.fetchPullRequests", {
|
|
788
|
+
this.log.debug("GitHubProvider.fetchPullRequests", {
|
|
789
|
+
count: results.length
|
|
790
|
+
});
|
|
596
791
|
return results;
|
|
597
792
|
}
|
|
598
793
|
async fetchSingleMR(projectPath, mrIid, _currentUserNumericId) {
|
|
@@ -705,7 +900,11 @@ class GitHubProvider {
|
|
|
705
900
|
async fetchPullRequestByBranch(projectPath, sourceBranch) {
|
|
706
901
|
const res = await this.api("GET", `/repos/${projectPath}/pulls?head=${projectPath.split("/")[0]}:${encodeURIComponent(sourceBranch)}&state=open&per_page=1`);
|
|
707
902
|
if (!res.ok) {
|
|
708
|
-
this.log.warn("fetchPullRequestByBranch failed", {
|
|
903
|
+
this.log.warn("fetchPullRequestByBranch failed", {
|
|
904
|
+
projectPath,
|
|
905
|
+
sourceBranch,
|
|
906
|
+
status: res.status
|
|
907
|
+
});
|
|
709
908
|
return null;
|
|
710
909
|
}
|
|
711
910
|
const prs = await res.json();
|
|
@@ -789,6 +988,83 @@ class GitHubProvider {
|
|
|
789
988
|
async restRequest(method, path, body) {
|
|
790
989
|
return this.api(method, path, body);
|
|
791
990
|
}
|
|
991
|
+
async mergePullRequest(projectPath, mrIid, input) {
|
|
992
|
+
const body = {};
|
|
993
|
+
if (input?.commitMessage != null)
|
|
994
|
+
body.commit_title = input.commitMessage;
|
|
995
|
+
if (input?.squashCommitMessage != null)
|
|
996
|
+
body.commit_title = input.squashCommitMessage;
|
|
997
|
+
if (input?.shouldRemoveSourceBranch != null)
|
|
998
|
+
body.delete_branch = input.shouldRemoveSourceBranch;
|
|
999
|
+
if (input?.sha != null)
|
|
1000
|
+
body.sha = input.sha;
|
|
1001
|
+
if (input?.mergeMethod) {
|
|
1002
|
+
body.merge_method = input.mergeMethod;
|
|
1003
|
+
} else if (input?.squash) {
|
|
1004
|
+
body.merge_method = "squash";
|
|
1005
|
+
}
|
|
1006
|
+
const res = await this.api("PUT", `/repos/${projectPath}/pulls/${mrIid}/merge`, body);
|
|
1007
|
+
if (!res.ok) {
|
|
1008
|
+
const text = await res.text();
|
|
1009
|
+
throw new Error(`mergePullRequest failed: ${res.status} ${text}`);
|
|
1010
|
+
}
|
|
1011
|
+
const pr = await this.fetchSingleMR(projectPath, mrIid, null);
|
|
1012
|
+
if (!pr)
|
|
1013
|
+
throw new Error("Merged PR but failed to fetch it back");
|
|
1014
|
+
return pr;
|
|
1015
|
+
}
|
|
1016
|
+
async approvePullRequest(projectPath, mrIid) {
|
|
1017
|
+
const res = await this.api("POST", `/repos/${projectPath}/pulls/${mrIid}/reviews`, {
|
|
1018
|
+
event: "APPROVE"
|
|
1019
|
+
});
|
|
1020
|
+
if (!res.ok) {
|
|
1021
|
+
const text = await res.text().catch(() => "");
|
|
1022
|
+
throw new Error(`approvePullRequest failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
async unapprovePullRequest(_projectPath, _mrIid) {
|
|
1026
|
+
throw new Error("unapprovePullRequest is not supported by GitHub. " + "Check provider.capabilities.canUnapprove before calling.");
|
|
1027
|
+
}
|
|
1028
|
+
async rebasePullRequest(_projectPath, _mrIid) {
|
|
1029
|
+
throw new Error("rebasePullRequest is not supported by GitHub. " + "Check provider.capabilities.canRebase before calling.");
|
|
1030
|
+
}
|
|
1031
|
+
async setAutoMerge(_projectPath, _mrIid) {
|
|
1032
|
+
throw new Error("setAutoMerge is not supported by the GitHub REST API. " + "Check provider.capabilities.canAutoMerge before calling.");
|
|
1033
|
+
}
|
|
1034
|
+
async cancelAutoMerge(_projectPath, _mrIid) {
|
|
1035
|
+
throw new Error("cancelAutoMerge is not supported by the GitHub REST API. " + "Check provider.capabilities.canAutoMerge before calling.");
|
|
1036
|
+
}
|
|
1037
|
+
async resolveDiscussion(_projectPath, _mrIid, _discussionId) {
|
|
1038
|
+
throw new Error("resolveDiscussion is not supported by the GitHub REST API. " + "Check provider.capabilities.canResolveDiscussions before calling.");
|
|
1039
|
+
}
|
|
1040
|
+
async unresolveDiscussion(_projectPath, _mrIid, _discussionId) {
|
|
1041
|
+
throw new Error("unresolveDiscussion is not supported by the GitHub REST API. " + "Check provider.capabilities.canResolveDiscussions before calling.");
|
|
1042
|
+
}
|
|
1043
|
+
async retryPipeline(projectPath, pipelineId) {
|
|
1044
|
+
const res = await this.api("POST", `/repos/${projectPath}/actions/runs/${pipelineId}/rerun`);
|
|
1045
|
+
if (!res.ok) {
|
|
1046
|
+
const text = await res.text().catch(() => "");
|
|
1047
|
+
throw new Error(`retryPipeline failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
async requestReReview(projectPath, mrIid, reviewerUsernames) {
|
|
1051
|
+
if (!reviewerUsernames?.length) {
|
|
1052
|
+
const prRes = await this.api("GET", `/repos/${projectPath}/pulls/${mrIid}`);
|
|
1053
|
+
if (!prRes.ok) {
|
|
1054
|
+
throw new Error(`requestReReview: failed to fetch PR: ${prRes.status}`);
|
|
1055
|
+
}
|
|
1056
|
+
const pr = await prRes.json();
|
|
1057
|
+
reviewerUsernames = pr.requested_reviewers.map((r) => r.login);
|
|
1058
|
+
if (!reviewerUsernames.length) {
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const res = await this.api("POST", `/repos/${projectPath}/pulls/${mrIid}/requested_reviewers`, { reviewers: reviewerUsernames });
|
|
1063
|
+
if (!res.ok) {
|
|
1064
|
+
const text = await res.text().catch(() => "");
|
|
1065
|
+
throw new Error(`requestReReview failed: ${res.status} ${res.statusText}${text ? ` — ${text}` : ""}`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
792
1068
|
async api(method, path, body) {
|
|
793
1069
|
const url = `${this.apiBase}${path}`;
|
|
794
1070
|
const headers = {
|
|
@@ -903,7 +1179,11 @@ class GitHubProvider {
|
|
|
903
1179
|
approved: approvedBy.length > 0 && changesRequested === 0,
|
|
904
1180
|
approvedBy,
|
|
905
1181
|
diffStats,
|
|
906
|
-
detailedMergeStatus: null
|
|
1182
|
+
detailedMergeStatus: null,
|
|
1183
|
+
autoMergeEnabled: pr.auto_merge != null,
|
|
1184
|
+
autoMergeStrategy: pr.auto_merge?.merge_method ?? null,
|
|
1185
|
+
mergeUser: pr.auto_merge ? toUserRef2(pr.auto_merge.enabled_by) : null,
|
|
1186
|
+
mergeAfter: null
|
|
907
1187
|
};
|
|
908
1188
|
}
|
|
909
1189
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -75,6 +75,14 @@ export interface PullRequest {
|
|
|
75
75
|
* "not_approved", "discussions_not_resolved". Null for non-GitLab providers.
|
|
76
76
|
*/
|
|
77
77
|
detailedMergeStatus: string | null;
|
|
78
|
+
/** Whether auto-merge (merge when pipeline succeeds) is currently enabled. */
|
|
79
|
+
autoMergeEnabled: boolean;
|
|
80
|
+
/** The merge strategy that will be used when auto-merge fires, e.g. "merge", "squash", "rebase_merge". */
|
|
81
|
+
autoMergeStrategy: string | null;
|
|
82
|
+
/** The user who merged or enabled auto-merge. */
|
|
83
|
+
mergeUser: UserRef | null;
|
|
84
|
+
/** ISO timestamp after which the MR is eligible to merge (scheduled merge). */
|
|
85
|
+
mergeAfter: string | null;
|
|
78
86
|
}
|
|
79
87
|
/** Input for creating a new merge request / pull request. */
|
|
80
88
|
export interface CreatePullRequestInput {
|
|
@@ -107,7 +115,62 @@ export interface UpdatePullRequestInput {
|
|
|
107
115
|
/** Labels (replaces current set). */
|
|
108
116
|
labels?: string[];
|
|
109
117
|
/** Set MR state: "close" or "reopen". */
|
|
110
|
-
stateEvent?:
|
|
118
|
+
stateEvent?: 'close' | 'reopen';
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Merge strategy override.
|
|
122
|
+
* - "merge" — Standard merge commit.
|
|
123
|
+
* - "squash" — Squash all commits into one before merging.
|
|
124
|
+
* - "rebase" — Rebase the source branch onto the target (fast-forward).
|
|
125
|
+
*
|
|
126
|
+
* When omitted, the provider uses the project's configured default merge method.
|
|
127
|
+
*/
|
|
128
|
+
export type MergeMethod = 'merge' | 'squash' | 'rebase';
|
|
129
|
+
/**
|
|
130
|
+
* Input for merging (accepting) a pull request / merge request.
|
|
131
|
+
*
|
|
132
|
+
* All fields are **optional**. Omitting them defers to the project-level
|
|
133
|
+
* settings configured in the forge UI (merge method, squash policy,
|
|
134
|
+
* delete-source-branch, etc.). This matches how the web UI works.
|
|
135
|
+
*/
|
|
136
|
+
export interface MergePullRequestInput {
|
|
137
|
+
/** Merge commit message. Omit to use the provider/project default. */
|
|
138
|
+
commitMessage?: string;
|
|
139
|
+
/** Squash commit message (when merge method is "squash"). */
|
|
140
|
+
squashCommitMessage?: string;
|
|
141
|
+
/** Whether to squash commits. Omit to use the MR / project default. */
|
|
142
|
+
squash?: boolean;
|
|
143
|
+
/** Merge strategy override. Omit to use the project's default merge method. */
|
|
144
|
+
mergeMethod?: MergeMethod;
|
|
145
|
+
/** Delete source branch after merge. Omit to use project default. */
|
|
146
|
+
shouldRemoveSourceBranch?: boolean;
|
|
147
|
+
/** SHA that HEAD must match for the merge to proceed (optimistic locking). */
|
|
148
|
+
sha?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reports which mutation operations a provider supports.
|
|
152
|
+
*
|
|
153
|
+
* Callers should check these flags before invoking vendor-specific methods
|
|
154
|
+
* so they can conditionally show/hide UI affordances without
|
|
155
|
+
* knowing which provider they're talking to.
|
|
156
|
+
*/
|
|
157
|
+
export interface ProviderCapabilities {
|
|
158
|
+
/** Can merge / accept a pull request. */
|
|
159
|
+
canMerge: boolean;
|
|
160
|
+
/** Can approve a pull request. */
|
|
161
|
+
canApprove: boolean;
|
|
162
|
+
/** Can revoke an existing approval. */
|
|
163
|
+
canUnapprove: boolean;
|
|
164
|
+
/** Can rebase the source branch onto the target. */
|
|
165
|
+
canRebase: boolean;
|
|
166
|
+
/** Can enable automatic merge when pipeline succeeds. */
|
|
167
|
+
canAutoMerge: boolean;
|
|
168
|
+
/** Can resolve / unresolve discussion threads. */
|
|
169
|
+
canResolveDiscussions: boolean;
|
|
170
|
+
/** Can retry a pipeline. */
|
|
171
|
+
canRetryPipeline: boolean;
|
|
172
|
+
/** Can re-request review attention from reviewers. */
|
|
173
|
+
canRequestReReview: boolean;
|
|
111
174
|
}
|
|
112
175
|
/** Branch protection rule (provider-agnostic). */
|
|
113
176
|
export interface BranchProtectionRule {
|