@rmartz/pr-lifecycle 5.0.0 → 7.0.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 +3 -2
- package/dist/bin/pr-lifecycle.js +1 -1
- package/dist/{chunk-RQEBRPZU.js → chunk-PJOTYSEK.js} +301 -95
- package/dist/index.d.ts +140 -24
- package/dist/index.js +21 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,8 +48,9 @@ pnpm run hygiene
|
|
|
48
48
|
|
|
49
49
|
Versioned by [semantic-release](.releaserc.json): a push to `main` analyzes the
|
|
50
50
|
Conventional-Commit history since the last `v*` tag and, when a release is
|
|
51
|
-
warranted, publishes the package to
|
|
52
|
-
GitHub Release.
|
|
51
|
+
warranted, publishes the package to npmjs (public, via OIDC trusted publishing
|
|
52
|
+
with provenance) and creates the tag and GitHub Release. Versions up to 5.0.0
|
|
53
|
+
were published to GitHub Packages and stay there for existing pins.
|
|
53
54
|
|
|
54
55
|
## License
|
|
55
56
|
|
package/dist/bin/pr-lifecycle.js
CHANGED
|
@@ -1,7 +1,66 @@
|
|
|
1
|
+
// src/release-diff.ts
|
|
2
|
+
var MANIFEST = ".release-please-manifest.json";
|
|
3
|
+
var CHANGELOG = "CHANGELOG.md";
|
|
4
|
+
var PACKAGE_JSON = "package.json";
|
|
5
|
+
var RELEASE_STATUSES = /* @__PURE__ */ new Set(["added", "modified"]);
|
|
6
|
+
var VERSION_LINE = /^[ \t]*"version"[ \t]*:[ \t]*"[^"\\\n]*"[,\t ]*$/;
|
|
7
|
+
function basename(path) {
|
|
8
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
9
|
+
}
|
|
10
|
+
function isVersionOnlyPatch(patch) {
|
|
11
|
+
let removed = 0;
|
|
12
|
+
let added = 0;
|
|
13
|
+
for (const line of patch.split("\n")) {
|
|
14
|
+
if (line.startsWith("@@") || line.startsWith("\\") || line.startsWith(" ") || line === "") {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const sign = line[0];
|
|
18
|
+
if (sign !== "+" && sign !== "-" || !VERSION_LINE.test(line.slice(1))) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (sign === "+") {
|
|
22
|
+
added += 1;
|
|
23
|
+
} else {
|
|
24
|
+
removed += 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return added === 1 && removed === 1;
|
|
28
|
+
}
|
|
29
|
+
function checkFile(file) {
|
|
30
|
+
if (!RELEASE_STATUSES.has(file.status)) {
|
|
31
|
+
return `${file.filename} was ${file.status}`;
|
|
32
|
+
}
|
|
33
|
+
const name = basename(file.filename);
|
|
34
|
+
if (file.filename === MANIFEST || name === CHANGELOG) {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
if (name !== PACKAGE_JSON) {
|
|
38
|
+
return `${file.filename} is not a release file`;
|
|
39
|
+
}
|
|
40
|
+
if (file.patch === void 0) {
|
|
41
|
+
return `${file.filename} has no patch to verify`;
|
|
42
|
+
}
|
|
43
|
+
return isVersionOnlyPatch(file.patch) ? void 0 : `${file.filename} changes more than its version`;
|
|
44
|
+
}
|
|
45
|
+
function classifyReleaseDiff(files) {
|
|
46
|
+
if (files === void 0) {
|
|
47
|
+
return { release: false, reason: "changed files unavailable" };
|
|
48
|
+
}
|
|
49
|
+
if (files.length === 0) {
|
|
50
|
+
return { release: false, reason: "no changed files" };
|
|
51
|
+
}
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
const problem = checkFile(file);
|
|
54
|
+
if (problem !== void 0) {
|
|
55
|
+
return { release: false, reason: problem };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { release: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
1
61
|
// src/bot-eligibility.ts
|
|
2
62
|
var DEPENDABOT_LOGIN = "dependabot[bot]";
|
|
3
63
|
var RELEASE_PLEASE_BRANCH_PREFIX = "release-please--";
|
|
4
|
-
var RELEASE_PLEASE_PENDING_LABEL = "autorelease: pending";
|
|
5
64
|
var DEPENDABOT_UPDATE_TYPES = [
|
|
6
65
|
"version-update:semver-patch",
|
|
7
66
|
"version-update:semver-minor",
|
|
@@ -72,8 +131,13 @@ function classifyBotPr(facts) {
|
|
|
72
131
|
if (facts.authorLogin === DEPENDABOT_LOGIN) {
|
|
73
132
|
return classifyDependabot(facts.commits);
|
|
74
133
|
}
|
|
75
|
-
if (facts.headRef.startsWith(RELEASE_PLEASE_BRANCH_PREFIX)
|
|
76
|
-
|
|
134
|
+
if (facts.headRef.startsWith(RELEASE_PLEASE_BRANCH_PREFIX)) {
|
|
135
|
+
const diff = classifyReleaseDiff(facts.changedFiles);
|
|
136
|
+
return diff.release ? result(true, "release-please release PR \u2014 eligible", "release-please") : result(
|
|
137
|
+
false,
|
|
138
|
+
`release-please branch, but not a pure release (${diff.reason}) \u2014 held for review`,
|
|
139
|
+
"release-please"
|
|
140
|
+
);
|
|
77
141
|
}
|
|
78
142
|
return result(false, "not a recognized bot PR");
|
|
79
143
|
}
|
|
@@ -90,6 +154,7 @@ function parseList(value) {
|
|
|
90
154
|
function parseReconcile(args) {
|
|
91
155
|
const values = {};
|
|
92
156
|
let armAutoMerge = false;
|
|
157
|
+
let autoUpdate = false;
|
|
93
158
|
let skipCopilotReview = false;
|
|
94
159
|
let dryRun = false;
|
|
95
160
|
let json = false;
|
|
@@ -100,6 +165,9 @@ function parseReconcile(args) {
|
|
|
100
165
|
case "--arm-auto-merge":
|
|
101
166
|
armAutoMerge = true;
|
|
102
167
|
break;
|
|
168
|
+
case "--auto-update":
|
|
169
|
+
autoUpdate = true;
|
|
170
|
+
break;
|
|
103
171
|
case "--skip-copilot-review":
|
|
104
172
|
skipCopilotReview = true;
|
|
105
173
|
break;
|
|
@@ -151,6 +219,7 @@ function parseReconcile(args) {
|
|
|
151
219
|
pr: Number(prArg),
|
|
152
220
|
policy: {
|
|
153
221
|
armAutoMerge,
|
|
222
|
+
autoUpdate,
|
|
154
223
|
skipCopilotReview,
|
|
155
224
|
...trustedAuthors === void 0 ? {} : { trustedAuthors },
|
|
156
225
|
...holdChecks === void 0 ? {} : { holdChecks },
|
|
@@ -181,7 +250,7 @@ function parseArgs(argv) {
|
|
|
181
250
|
// src/cli/output.ts
|
|
182
251
|
var SCHEMA_VERSION = 1;
|
|
183
252
|
function toReconcileJson(target, result2) {
|
|
184
|
-
const { plan, botEligibility, lineage, skippedAutoMerge } = result2;
|
|
253
|
+
const { plan, botEligibility, lineage, skippedAutoMerge, skippedUpdate } = result2;
|
|
185
254
|
return {
|
|
186
255
|
schemaVersion: SCHEMA_VERSION,
|
|
187
256
|
repo: `${target.owner}/${target.repo}`,
|
|
@@ -199,7 +268,9 @@ function toReconcileJson(target, result2) {
|
|
|
199
268
|
},
|
|
200
269
|
carryOver: lineage === void 0 ? null : { cleanAncestors: lineage.cleanAncestors, stoppedBecause: lineage.stoppedBecause },
|
|
201
270
|
// The CLI makes release actions unavailable only for a missing token.
|
|
202
|
-
autoMergeSkipped: skippedAutoMerge === void 0 ? null : { action: skippedAutoMerge, reason: "token-missing" }
|
|
271
|
+
autoMergeSkipped: skippedAutoMerge === void 0 ? null : { action: skippedAutoMerge, reason: "token-missing" },
|
|
272
|
+
update: plan.update,
|
|
273
|
+
updateSkipped: skippedUpdate === void 0 ? null : { action: skippedUpdate, reason: "token-missing" }
|
|
203
274
|
};
|
|
204
275
|
}
|
|
205
276
|
function formatSummary(target, result2) {
|
|
@@ -208,7 +279,9 @@ function formatSummary(target, result2) {
|
|
|
208
279
|
...plan.addLabels.map((label) => `+${label}`),
|
|
209
280
|
...plan.removeLabels.map((label) => `-${label}`),
|
|
210
281
|
...plan.autoMerge === "none" ? [] : [`auto-merge ${plan.autoMerge}`],
|
|
211
|
-
...result2.skippedAutoMerge === void 0 ? [] : [`auto-merge ${result2.skippedAutoMerge} skipped (no release token)`]
|
|
282
|
+
...result2.skippedAutoMerge === void 0 ? [] : [`auto-merge ${result2.skippedAutoMerge} skipped (no release token)`],
|
|
283
|
+
...plan.update === "none" ? [] : [`update ${plan.update}`],
|
|
284
|
+
...result2.skippedUpdate === void 0 ? [] : [`update ${result2.skippedUpdate} skipped (no release token)`]
|
|
212
285
|
];
|
|
213
286
|
const prefix = target.dryRun ? "[dry-run] " : "";
|
|
214
287
|
const body = changes.length === 0 ? "no changes" : changes.join(", ");
|
|
@@ -239,38 +312,11 @@ var REVIEW_STATES = [
|
|
|
239
312
|
"PENDING"
|
|
240
313
|
];
|
|
241
314
|
var PR_STATUSES = ["closed", "merged", "open"];
|
|
315
|
+
var BRANCH_UPDATERS = ["dependabot", "github"];
|
|
242
316
|
|
|
243
|
-
// src/github/
|
|
244
|
-
function toReviewState(state) {
|
|
245
|
-
return REVIEW_STATES.find((known) => known === state) ?? "DISMISSED";
|
|
246
|
-
}
|
|
247
|
-
function toActorType(type) {
|
|
248
|
-
return type === "User" ? "User" : "Bot";
|
|
249
|
-
}
|
|
250
|
-
function toReviewData(review) {
|
|
251
|
-
return {
|
|
252
|
-
id: review.id,
|
|
253
|
-
login: review.user?.login,
|
|
254
|
-
type: review.user === null ? "User" : toActorType(review.user.type),
|
|
255
|
-
commitSha: review.commit_id,
|
|
256
|
-
state: toReviewState(review.state),
|
|
257
|
-
body: review.body ?? "",
|
|
258
|
-
submittedAt: review.submitted_at ?? void 0
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// src/github/http-client.ts
|
|
317
|
+
// src/github/http-transport.ts
|
|
263
318
|
var PAGE_SIZE = 100;
|
|
264
|
-
|
|
265
|
-
enablePullRequestAutoMerge(input: { pullRequestId: $id, mergeMethod: SQUASH, expectedHeadOid: $head }) { clientMutationId }
|
|
266
|
-
}`;
|
|
267
|
-
var MERGE_PULL_REQUEST = `mutation($id: ID!, $head: GitObjectID!) {
|
|
268
|
-
mergePullRequest(input: { pullRequestId: $id, mergeMethod: SQUASH, expectedHeadOid: $head }) { clientMutationId }
|
|
269
|
-
}`;
|
|
270
|
-
var DISABLE_AUTO_MERGE = `mutation($id: ID!) {
|
|
271
|
-
disablePullRequestAutoMerge(input: { pullRequestId: $id }) { clientMutationId }
|
|
272
|
-
}`;
|
|
273
|
-
function createHttpClient(options) {
|
|
319
|
+
function createTransport(options) {
|
|
274
320
|
let apiUrl = options.apiUrl ?? "https://api.github.com";
|
|
275
321
|
while (apiUrl.endsWith("/")) {
|
|
276
322
|
apiUrl = apiUrl.slice(0, -1);
|
|
@@ -315,7 +361,79 @@ function createHttpClient(options) {
|
|
|
315
361
|
throw new GitHubApiError(200, `GraphQL error: ${messages}`);
|
|
316
362
|
}
|
|
317
363
|
}
|
|
364
|
+
return { repoPath, request, paginate, graphql };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/github/http-checks.ts
|
|
368
|
+
function createCheckMethods({ repoPath, request, paginate }) {
|
|
369
|
+
return {
|
|
370
|
+
async getRequiredStatusChecks(branch) {
|
|
371
|
+
const rules = await paginate(
|
|
372
|
+
`${repoPath}/rules/branches/${encodeURIComponent(branch)}`
|
|
373
|
+
);
|
|
374
|
+
const contexts = rules.filter((rule) => rule.type === "required_status_checks").flatMap((rule) => rule.parameters?.required_status_checks ?? []).map((check) => check.context);
|
|
375
|
+
return [...new Set(contexts)];
|
|
376
|
+
},
|
|
377
|
+
async listCheckRuns(sha) {
|
|
378
|
+
const runs = [];
|
|
379
|
+
for (let page = 1; ; page += 1) {
|
|
380
|
+
const batch = await request(
|
|
381
|
+
"GET",
|
|
382
|
+
`${repoPath}/commits/${sha}/check-runs?per_page=${PAGE_SIZE}&page=${page}`
|
|
383
|
+
);
|
|
384
|
+
runs.push(...batch.check_runs);
|
|
385
|
+
if (batch.check_runs.length < PAGE_SIZE) {
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return runs.map((run) => ({
|
|
390
|
+
name: run.name,
|
|
391
|
+
status: run.status,
|
|
392
|
+
conclusion: run.conclusion,
|
|
393
|
+
completedAt: run.completed_at
|
|
394
|
+
}));
|
|
395
|
+
},
|
|
396
|
+
async listCommitStatuses(sha) {
|
|
397
|
+
const combined = await request("GET", `${repoPath}/commits/${sha}/status`);
|
|
398
|
+
return combined.statuses.map((status) => ({ context: status.context, state: status.state }));
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/github/rest-payloads.ts
|
|
404
|
+
function toReviewState(state) {
|
|
405
|
+
return REVIEW_STATES.find((known) => known === state) ?? "DISMISSED";
|
|
406
|
+
}
|
|
407
|
+
function toActorType(type) {
|
|
408
|
+
return type === "User" ? "User" : "Bot";
|
|
409
|
+
}
|
|
410
|
+
function toReviewData(review) {
|
|
318
411
|
return {
|
|
412
|
+
id: review.id,
|
|
413
|
+
login: review.user?.login,
|
|
414
|
+
type: review.user === null ? "User" : toActorType(review.user.type),
|
|
415
|
+
commitSha: review.commit_id,
|
|
416
|
+
state: toReviewState(review.state),
|
|
417
|
+
body: review.body ?? "",
|
|
418
|
+
submittedAt: review.submitted_at ?? void 0
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/github/http-client.ts
|
|
423
|
+
var ENABLE_AUTO_MERGE = `mutation($id: ID!, $head: GitObjectID!) {
|
|
424
|
+
enablePullRequestAutoMerge(input: { pullRequestId: $id, mergeMethod: SQUASH, expectedHeadOid: $head }) { clientMutationId }
|
|
425
|
+
}`;
|
|
426
|
+
var MERGE_PULL_REQUEST = `mutation($id: ID!, $head: GitObjectID!) {
|
|
427
|
+
mergePullRequest(input: { pullRequestId: $id, mergeMethod: SQUASH, expectedHeadOid: $head }) { clientMutationId }
|
|
428
|
+
}`;
|
|
429
|
+
var DISABLE_AUTO_MERGE = `mutation($id: ID!) {
|
|
430
|
+
disablePullRequestAutoMerge(input: { pullRequestId: $id }) { clientMutationId }
|
|
431
|
+
}`;
|
|
432
|
+
function createHttpClient(options) {
|
|
433
|
+
const transport = createTransport(options);
|
|
434
|
+
const { repoPath, request, paginate, graphql } = transport;
|
|
435
|
+
return {
|
|
436
|
+
...createCheckMethods(transport),
|
|
319
437
|
async getPullRequest(pr) {
|
|
320
438
|
const pull = await request("GET", `${repoPath}/pulls/${pr}`);
|
|
321
439
|
return {
|
|
@@ -324,6 +442,7 @@ function createHttpClient(options) {
|
|
|
324
442
|
merged: pull.merged,
|
|
325
443
|
draft: pull.draft === true,
|
|
326
444
|
title: pull.title,
|
|
445
|
+
body: pull.body ?? "",
|
|
327
446
|
headSha: pull.head.sha,
|
|
328
447
|
labels: pull.labels.map((label) => label.name),
|
|
329
448
|
autoMergeEnabled: pull.auto_merge !== null && pull.auto_merge !== void 0,
|
|
@@ -335,16 +454,10 @@ function createHttpClient(options) {
|
|
|
335
454
|
// repo is treated as a fork, so it can never look like a trusted branch.
|
|
336
455
|
isCrossRepository: pull.head.repo?.id !== pull.base.repo.id,
|
|
337
456
|
mergeable: pull.mergeable ?? void 0,
|
|
338
|
-
mergeState: pull.mergeable_state
|
|
457
|
+
mergeState: pull.mergeable_state,
|
|
458
|
+
changedFileCount: pull.changed_files
|
|
339
459
|
};
|
|
340
460
|
},
|
|
341
|
-
async getRequiredStatusChecks(branch) {
|
|
342
|
-
const rules = await paginate(
|
|
343
|
-
`${repoPath}/rules/branches/${encodeURIComponent(branch)}`
|
|
344
|
-
);
|
|
345
|
-
const contexts = rules.filter((rule) => rule.type === "required_status_checks").flatMap((rule) => rule.parameters?.required_status_checks ?? []).map((check) => check.context);
|
|
346
|
-
return [...new Set(contexts)];
|
|
347
|
-
},
|
|
348
461
|
async getBranchHeadSha(branch) {
|
|
349
462
|
const result2 = await request(
|
|
350
463
|
"GET",
|
|
@@ -352,25 +465,6 @@ function createHttpClient(options) {
|
|
|
352
465
|
);
|
|
353
466
|
return result2.commit.sha;
|
|
354
467
|
},
|
|
355
|
-
async listCheckRuns(sha) {
|
|
356
|
-
const runs = [];
|
|
357
|
-
for (let page = 1; ; page += 1) {
|
|
358
|
-
const batch = await request(
|
|
359
|
-
"GET",
|
|
360
|
-
`${repoPath}/commits/${sha}/check-runs?per_page=${PAGE_SIZE}&page=${page}`
|
|
361
|
-
);
|
|
362
|
-
runs.push(...batch.check_runs);
|
|
363
|
-
if (batch.check_runs.length < PAGE_SIZE) {
|
|
364
|
-
break;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return runs.map((run) => ({
|
|
368
|
-
name: run.name,
|
|
369
|
-
status: run.status,
|
|
370
|
-
conclusion: run.conclusion,
|
|
371
|
-
completedAt: run.completed_at
|
|
372
|
-
}));
|
|
373
|
-
},
|
|
374
468
|
async getCommit(sha) {
|
|
375
469
|
const commit = await request("GET", `${repoPath}/git/commits/${sha}`);
|
|
376
470
|
return { treeSha: commit.tree.sha, parents: commit.parents.map((parent) => parent.sha) };
|
|
@@ -379,9 +473,13 @@ function createHttpClient(options) {
|
|
|
379
473
|
const result2 = await request("GET", `${repoPath}/compare/${base}...${head}?per_page=1`);
|
|
380
474
|
return { status: result2.status, mergeBaseSha: result2.merge_base_commit.sha };
|
|
381
475
|
},
|
|
382
|
-
async
|
|
383
|
-
const
|
|
384
|
-
return
|
|
476
|
+
async listPullRequestFiles(pr) {
|
|
477
|
+
const files = await paginate(`${repoPath}/pulls/${pr}/files`);
|
|
478
|
+
return files.map((file) => ({
|
|
479
|
+
filename: file.filename,
|
|
480
|
+
status: file.status,
|
|
481
|
+
patch: file.patch
|
|
482
|
+
}));
|
|
385
483
|
},
|
|
386
484
|
async listCommits(pr) {
|
|
387
485
|
const commits = await paginate(`${repoPath}/pulls/${pr}/commits`);
|
|
@@ -427,6 +525,11 @@ function createHttpClient(options) {
|
|
|
427
525
|
async mergePullRequest(pullRequestNodeId, expectedHeadOid) {
|
|
428
526
|
await graphql(MERGE_PULL_REQUEST, { id: pullRequestNodeId, head: expectedHeadOid });
|
|
429
527
|
},
|
|
528
|
+
async updateBranch(pr, expectedHeadSha) {
|
|
529
|
+
await request("PUT", `${repoPath}/pulls/${pr}/update-branch`, {
|
|
530
|
+
expected_head_sha: expectedHeadSha
|
|
531
|
+
});
|
|
532
|
+
},
|
|
430
533
|
async disableAutoMerge(pullRequestNodeId) {
|
|
431
534
|
await graphql(DISABLE_AUTO_MERGE, { id: pullRequestNodeId });
|
|
432
535
|
}
|
|
@@ -574,6 +677,7 @@ var LIFECYCLE_LABELS = [
|
|
|
574
677
|
"review requested"
|
|
575
678
|
];
|
|
576
679
|
var AUTO_MERGE_LABEL = "auto-merge enabled";
|
|
680
|
+
var UPDATE_REQUIRED_LABEL = "update required";
|
|
577
681
|
function lifecycleLabels(state) {
|
|
578
682
|
switch (state) {
|
|
579
683
|
case "approved":
|
|
@@ -596,10 +700,27 @@ function lifecycleLabels(state) {
|
|
|
596
700
|
return [];
|
|
597
701
|
}
|
|
598
702
|
}
|
|
703
|
+
function updateFor(updater, rebasePending) {
|
|
704
|
+
switch (updater) {
|
|
705
|
+
case "dependabot":
|
|
706
|
+
return rebasePending ? "none" : "dependabot-rebase";
|
|
707
|
+
case "github":
|
|
708
|
+
return "update-branch";
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function planUpdate(facts, policy, state, autoMerge) {
|
|
712
|
+
if (policy.autoUpdate !== true || state !== "approved" || autoMerge === "merge") {
|
|
713
|
+
return "none";
|
|
714
|
+
}
|
|
715
|
+
if (!facts.labels.includes(UPDATE_REQUIRED_LABEL)) {
|
|
716
|
+
return "none";
|
|
717
|
+
}
|
|
718
|
+
return updateFor(facts.updater, facts.rebasePending);
|
|
719
|
+
}
|
|
599
720
|
function planReconcile(facts, policy) {
|
|
600
721
|
const state = computeState(facts, policy);
|
|
601
722
|
if (state === "closed") {
|
|
602
|
-
return { state, addLabels: [], removeLabels: [], autoMerge: "none" };
|
|
723
|
+
return { state, addLabels: [], removeLabels: [], autoMerge: "none", update: "none" };
|
|
603
724
|
}
|
|
604
725
|
const arming = policy.armAutoMerge === true;
|
|
605
726
|
const shouldArm = arming && state === "approved";
|
|
@@ -624,20 +745,39 @@ function planReconcile(facts, policy) {
|
|
|
624
745
|
state,
|
|
625
746
|
addLabels: [...desired].filter((name) => !present.has(name)),
|
|
626
747
|
removeLabels: owned.filter((name) => present.has(name) && !desired.has(name)),
|
|
627
|
-
autoMerge
|
|
748
|
+
autoMerge,
|
|
749
|
+
update: planUpdate(facts, policy, state, autoMerge)
|
|
628
750
|
};
|
|
629
751
|
}
|
|
630
|
-
function
|
|
631
|
-
|
|
632
|
-
return plan;
|
|
633
|
-
}
|
|
752
|
+
function withoutReleaseActions(plan) {
|
|
753
|
+
const arming = plan.autoMerge === "arm" || plan.autoMerge === "merge";
|
|
634
754
|
return {
|
|
635
755
|
...plan,
|
|
636
|
-
addLabels: plan.addLabels.filter((name) => name !== AUTO_MERGE_LABEL),
|
|
637
|
-
autoMerge: "none"
|
|
756
|
+
addLabels: arming ? plan.addLabels.filter((name) => name !== AUTO_MERGE_LABEL) : plan.addLabels,
|
|
757
|
+
autoMerge: arming ? "none" : plan.autoMerge,
|
|
758
|
+
update: "none"
|
|
638
759
|
};
|
|
639
760
|
}
|
|
640
761
|
|
|
762
|
+
// src/github/dependabot-rebase.ts
|
|
763
|
+
var DEPENDABOT_REBASE_COMMAND = "@dependabot rebase";
|
|
764
|
+
var DEPENDABOT_REBASING_NOTICE = "Dependabot is rebasing this PR";
|
|
765
|
+
function rebaseRequestMarker(headSha) {
|
|
766
|
+
return `<!-- pr-lifecycle:dependabot-rebase head=${headSha} -->`;
|
|
767
|
+
}
|
|
768
|
+
function buildRebaseRequestBody(headSha) {
|
|
769
|
+
return `${DEPENDABOT_REBASE_COMMAND}
|
|
770
|
+
|
|
771
|
+
${rebaseRequestMarker(headSha)}`;
|
|
772
|
+
}
|
|
773
|
+
function isRebasePending(prBody, comments, headSha) {
|
|
774
|
+
if (prBody.includes(DEPENDABOT_REBASING_NOTICE)) {
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
const marker = rebaseRequestMarker(headSha);
|
|
778
|
+
return comments.some((comment) => comment.includes(marker));
|
|
779
|
+
}
|
|
780
|
+
|
|
641
781
|
// src/github/label-roster.ts
|
|
642
782
|
var OWNED_LABEL_DEFINITIONS = {
|
|
643
783
|
approved: {
|
|
@@ -734,6 +874,25 @@ async function executePlan(client, target, plan, release = client) {
|
|
|
734
874
|
if (plan.autoMerge === "merge") {
|
|
735
875
|
await release.mergePullRequest(target.nodeId, target.headSha);
|
|
736
876
|
}
|
|
877
|
+
switch (plan.update) {
|
|
878
|
+
case "update-branch":
|
|
879
|
+
await updateBranch(release, target);
|
|
880
|
+
break;
|
|
881
|
+
case "dependabot-rebase":
|
|
882
|
+
await release.createIssueComment(target.pr, buildRebaseRequestBody(target.headSha));
|
|
883
|
+
break;
|
|
884
|
+
case "none":
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
async function updateBranch(release, target) {
|
|
889
|
+
try {
|
|
890
|
+
await release.updateBranch(target.pr, target.headSha);
|
|
891
|
+
} catch (error) {
|
|
892
|
+
if (!isApiStatus(error, 422)) {
|
|
893
|
+
throw error;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
737
896
|
}
|
|
738
897
|
|
|
739
898
|
// src/lineage/git.ts
|
|
@@ -917,6 +1076,28 @@ async function gatherLineage(client, git, input) {
|
|
|
917
1076
|
}
|
|
918
1077
|
}
|
|
919
1078
|
|
|
1079
|
+
// src/github/bot-facts.ts
|
|
1080
|
+
async function listAllChangedFiles(client, pr, pull) {
|
|
1081
|
+
const files = await client.listPullRequestFiles(pr);
|
|
1082
|
+
return files.length === pull.changedFileCount ? files : void 0;
|
|
1083
|
+
}
|
|
1084
|
+
async function gatherBotEligibility(client, pr, pull) {
|
|
1085
|
+
const sameRepo = !pull.isCrossRepository;
|
|
1086
|
+
const isDependabot = pull.authorLogin === DEPENDABOT_LOGIN;
|
|
1087
|
+
const isReleaseBranch = !isDependabot && pull.headRef.startsWith(RELEASE_PLEASE_BRANCH_PREFIX);
|
|
1088
|
+
const [commits, changedFiles] = await Promise.all([
|
|
1089
|
+
sameRepo && isDependabot ? client.listCommits(pr) : [],
|
|
1090
|
+
sameRepo && isReleaseBranch ? listAllChangedFiles(client, pr, pull) : void 0
|
|
1091
|
+
]);
|
|
1092
|
+
return classifyBotPr({
|
|
1093
|
+
authorLogin: pull.authorLogin ?? "ghost",
|
|
1094
|
+
headRef: pull.headRef,
|
|
1095
|
+
isCrossRepository: pull.isCrossRepository,
|
|
1096
|
+
commits,
|
|
1097
|
+
changedFiles
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
920
1101
|
// src/ci.ts
|
|
921
1102
|
var DEFAULT_HOLD_CHECKS = ["pr-policy"];
|
|
922
1103
|
var DEFAULT_IGNORED_CHECKS = ["merge-safety"];
|
|
@@ -1010,16 +1191,6 @@ async function gatherCiFacts(client, pull, policy) {
|
|
|
1010
1191
|
}
|
|
1011
1192
|
|
|
1012
1193
|
// src/github/gather.ts
|
|
1013
|
-
async function gatherBotEligibility(client, pr, pull) {
|
|
1014
|
-
const needsCommits = pull.authorLogin === DEPENDABOT_LOGIN && !pull.isCrossRepository;
|
|
1015
|
-
return classifyBotPr({
|
|
1016
|
-
authorLogin: pull.authorLogin ?? "ghost",
|
|
1017
|
-
headRef: pull.headRef,
|
|
1018
|
-
isCrossRepository: pull.isCrossRepository,
|
|
1019
|
-
labels: pull.labels,
|
|
1020
|
-
commits: needsCommits ? await client.listCommits(pr) : []
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
1194
|
function isRepoPermission(value) {
|
|
1024
1195
|
return REPO_PERMISSIONS.some((permission) => permission === value);
|
|
1025
1196
|
}
|
|
@@ -1084,14 +1255,29 @@ async function gatherLineageFor(client, pull, reviews, options) {
|
|
|
1084
1255
|
return { cleanAncestors: [], stoppedBecause: `verification failed: ${message}` };
|
|
1085
1256
|
}
|
|
1086
1257
|
}
|
|
1258
|
+
async function gatherRebasePending(client, pr, pull, updater, policy) {
|
|
1259
|
+
if (updater !== "dependabot") {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
if (pull.body.includes(DEPENDABOT_REBASING_NOTICE)) {
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
const couldUpdate = policy.autoUpdate === true && pull.state === "open" && pull.labels.includes(UPDATE_REQUIRED_LABEL);
|
|
1266
|
+
if (!couldUpdate) {
|
|
1267
|
+
return false;
|
|
1268
|
+
}
|
|
1269
|
+
return isRebasePending(pull.body, await client.listIssueComments(pr), pull.headSha);
|
|
1270
|
+
}
|
|
1087
1271
|
var IMMEDIATE_MERGE_STATES = /* @__PURE__ */ new Set(["clean", "has_hooks", "unstable"]);
|
|
1088
1272
|
async function gatherFacts(client, pr, policy = {}, options = {}) {
|
|
1089
1273
|
const [pull, reviews] = await Promise.all([client.getPullRequest(pr), client.listReviews(pr)]);
|
|
1090
|
-
const
|
|
1274
|
+
const updater = pull.authorLogin === DEPENDABOT_LOGIN ? "dependabot" : "github";
|
|
1275
|
+
const [permissions, botEligibility, ci, lineage, rebasePending] = await Promise.all([
|
|
1091
1276
|
lookupPermissions(client, reviews),
|
|
1092
1277
|
gatherBotEligibility(client, pr, pull),
|
|
1093
1278
|
gatherCiFacts(client, pull, policy),
|
|
1094
|
-
gatherLineageFor(client, pull, reviews, options)
|
|
1279
|
+
gatherLineageFor(client, pull, reviews, options),
|
|
1280
|
+
gatherRebasePending(client, pr, pull, updater, policy)
|
|
1095
1281
|
]);
|
|
1096
1282
|
return {
|
|
1097
1283
|
nodeId: pull.nodeId,
|
|
@@ -1110,6 +1296,8 @@ async function gatherFacts(client, pr, policy = {}, options = {}) {
|
|
|
1110
1296
|
ciStatus: ci.ciStatus,
|
|
1111
1297
|
baseCiFailing: ci.baseCiFailing,
|
|
1112
1298
|
cleanAncestors: lineage?.cleanAncestors ?? [],
|
|
1299
|
+
updater,
|
|
1300
|
+
rebasePending,
|
|
1113
1301
|
reviews: reviews.map((review) => toReviewFact(review, permissions))
|
|
1114
1302
|
}
|
|
1115
1303
|
};
|
|
@@ -1117,21 +1305,24 @@ async function gatherFacts(client, pr, policy = {}, options = {}) {
|
|
|
1117
1305
|
|
|
1118
1306
|
// src/github/reconcile.ts
|
|
1119
1307
|
var REFUSE_RELEASE = {
|
|
1308
|
+
createIssueComment: () => Promise.reject(new Error("rebase requests need a release token")),
|
|
1120
1309
|
enableAutoMerge: () => Promise.reject(new Error("arming needs a release token")),
|
|
1121
|
-
mergePullRequest: () => Promise.reject(new Error("merging needs a release token"))
|
|
1310
|
+
mergePullRequest: () => Promise.reject(new Error("merging needs a release token")),
|
|
1311
|
+
updateBranch: () => Promise.reject(new Error("updating needs a release token"))
|
|
1122
1312
|
};
|
|
1123
1313
|
async function reconcilePullRequest(client, pr, policy, options = {}) {
|
|
1124
1314
|
const { facts, nodeId, botEligibility, lineage } = await gatherFacts(client, pr, policy, options);
|
|
1125
1315
|
const planned = planReconcile(facts, policy);
|
|
1126
1316
|
const unavailable = options.release === "unavailable";
|
|
1127
|
-
const plan = unavailable ?
|
|
1317
|
+
const plan = unavailable ? withoutReleaseActions(planned) : planned;
|
|
1128
1318
|
if (options.dryRun !== true) {
|
|
1129
1319
|
const release = options.release === "unavailable" ? REFUSE_RELEASE : options.release ?? client;
|
|
1130
1320
|
await executePlan(client, { pr, nodeId, headSha: facts.headSha }, plan, release);
|
|
1131
1321
|
}
|
|
1132
1322
|
const wanted = planned.autoMerge;
|
|
1133
1323
|
const skippedAutoMerge = unavailable && (wanted === "arm" || wanted === "merge") ? wanted : void 0;
|
|
1134
|
-
|
|
1324
|
+
const skippedUpdate = unavailable && planned.update !== "none" ? planned.update : void 0;
|
|
1325
|
+
return { plan, botEligibility, lineage, skippedAutoMerge, skippedUpdate };
|
|
1135
1326
|
}
|
|
1136
1327
|
|
|
1137
1328
|
// src/github/token-advisory.ts
|
|
@@ -1168,6 +1359,8 @@ reconcile options:
|
|
|
1168
1359
|
--repo <owner/repo> Repository (required)
|
|
1169
1360
|
--pr <number> Pull request number (required)
|
|
1170
1361
|
--arm-auto-merge Arm/disarm native auto-merge from the state
|
|
1362
|
+
--auto-update Update approved PRs merge-safety flags 'update required'
|
|
1363
|
+
(Dependabot PRs are asked to rebase, never updated)
|
|
1171
1364
|
--trusted-authors <a,b> Only these logins (with write access) may cast verdicts
|
|
1172
1365
|
--skip-copilot-review Don't wait for a Copilot review
|
|
1173
1366
|
--hold-checks <a,b> Required checks whose pending is a hold (default: pr-policy)
|
|
@@ -1179,8 +1372,8 @@ reconcile options:
|
|
|
1179
1372
|
Environment:
|
|
1180
1373
|
GITHUB_TOKEN Token for reads and writes (required)
|
|
1181
1374
|
PR_LIFECYCLE_TOKEN
|
|
1182
|
-
Real-actor token for arming and
|
|
1183
|
-
|
|
1375
|
+
Real-actor token for arming, merging, and updating;
|
|
1376
|
+
without it, labels are kept but those are skipped
|
|
1184
1377
|
GITHUB_API_URL API base URL (GitHub Enterprise Server)`;
|
|
1185
1378
|
async function runCli(argv, io, deps) {
|
|
1186
1379
|
const args = parseArgs(argv);
|
|
@@ -1216,8 +1409,8 @@ async function runCli(argv, io, deps) {
|
|
|
1216
1409
|
const result2 = await reconcilePullRequest(client, args.pr, args.policy, {
|
|
1217
1410
|
dryRun: args.dryRun,
|
|
1218
1411
|
lineage: { git: deps.git ?? createGitRunner(), token },
|
|
1219
|
-
// Only arming and
|
|
1220
|
-
// they are skipped, not done with GITHUB_TOKEN; see docs/cli.md.
|
|
1412
|
+
// Only arming, merging, and updating use the release token (never reads).
|
|
1413
|
+
// Without one they are skipped, not done with GITHUB_TOKEN; see docs/cli.md.
|
|
1221
1414
|
release: hasReleaseToken ? clientFor(releaseToken) : "unavailable"
|
|
1222
1415
|
});
|
|
1223
1416
|
if (result2.skippedAutoMerge !== void 0) {
|
|
@@ -1228,6 +1421,11 @@ async function runCli(argv, io, deps) {
|
|
|
1228
1421
|
await adviseMissingToken(client, args.pr, result2.skippedAutoMerge, io);
|
|
1229
1422
|
}
|
|
1230
1423
|
}
|
|
1424
|
+
if (result2.skippedUpdate !== void 0) {
|
|
1425
|
+
io.stderr(
|
|
1426
|
+
`warning: ${args.owner}/${args.repo}#${args.pr} needs an update, but ${result2.skippedUpdate} was skipped: PR_LIFECYCLE_TOKEN is not set`
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1231
1429
|
if (args.json) {
|
|
1232
1430
|
io.stdout(JSON.stringify(toReconcileJson(target, result2)));
|
|
1233
1431
|
} else {
|
|
@@ -1250,9 +1448,10 @@ async function adviseMissingToken(client, pr, skipped, io) {
|
|
|
1250
1448
|
}
|
|
1251
1449
|
|
|
1252
1450
|
export {
|
|
1451
|
+
isVersionOnlyPatch,
|
|
1452
|
+
classifyReleaseDiff,
|
|
1253
1453
|
DEPENDABOT_LOGIN,
|
|
1254
1454
|
RELEASE_PLEASE_BRANCH_PREFIX,
|
|
1255
|
-
RELEASE_PLEASE_PENDING_LABEL,
|
|
1256
1455
|
DEPENDABOT_UPDATE_TYPES,
|
|
1257
1456
|
parseDependabotUpdateType,
|
|
1258
1457
|
classifyBotPr,
|
|
@@ -1266,6 +1465,7 @@ export {
|
|
|
1266
1465
|
ACTOR_TYPES,
|
|
1267
1466
|
REVIEW_STATES,
|
|
1268
1467
|
PR_STATUSES,
|
|
1468
|
+
BRANCH_UPDATERS,
|
|
1269
1469
|
createHttpClient,
|
|
1270
1470
|
VERDICTS,
|
|
1271
1471
|
parseVerdict,
|
|
@@ -1276,8 +1476,14 @@ export {
|
|
|
1276
1476
|
computeState,
|
|
1277
1477
|
LIFECYCLE_LABELS,
|
|
1278
1478
|
AUTO_MERGE_LABEL,
|
|
1479
|
+
UPDATE_REQUIRED_LABEL,
|
|
1279
1480
|
planReconcile,
|
|
1280
|
-
|
|
1481
|
+
withoutReleaseActions,
|
|
1482
|
+
DEPENDABOT_REBASE_COMMAND,
|
|
1483
|
+
DEPENDABOT_REBASING_NOTICE,
|
|
1484
|
+
rebaseRequestMarker,
|
|
1485
|
+
buildRebaseRequestBody,
|
|
1486
|
+
isRebasePending,
|
|
1281
1487
|
OWNED_LABEL_DEFINITIONS,
|
|
1282
1488
|
executePlan,
|
|
1283
1489
|
createGitRunner,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is a release-please PR's diff a pure release: only the files release-please
|
|
3
|
+
* writes, with `package.json` changed in its `version` line and nowhere else?
|
|
4
|
+
* The branch name alone can't say (any write user can push a `release-please--`
|
|
5
|
+
* branch with arbitrary code), and release-please commits as whoever owns its
|
|
6
|
+
* token, so its commit authors can't either. See docs/bot-eligibility.md.
|
|
7
|
+
*
|
|
8
|
+
* FAIL-SAFE: a file outside the release set, a status other than added or
|
|
9
|
+
* modified, a missing patch, or any doubt about a `package.json` line means the
|
|
10
|
+
* diff is not a release.
|
|
11
|
+
*/
|
|
12
|
+
/** One changed file, as the PR files API reports it. */
|
|
13
|
+
interface ChangedFile {
|
|
14
|
+
/** Repository-relative path. */
|
|
15
|
+
filename: string;
|
|
16
|
+
/** `added`, `modified`, `removed`, `renamed`, `copied`, `changed`, or `unchanged`. */
|
|
17
|
+
status: string;
|
|
18
|
+
/** The unified-diff hunk text; undefined when GitHub omits it (e.g. a large diff). */
|
|
19
|
+
patch: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
type ReleaseDiffVerdict = {
|
|
22
|
+
release: true;
|
|
23
|
+
} | {
|
|
24
|
+
release: false;
|
|
25
|
+
reason: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Whether a `package.json` patch changes exactly one line, its `version`: one
|
|
29
|
+
* removed and one added line, both a bare `"version"` member. Context lines,
|
|
30
|
+
* hunk headers, and the no-newline marker are ignored.
|
|
31
|
+
*/
|
|
32
|
+
declare function isVersionOnlyPatch(patch: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The verdict over a PR's changed files. `undefined` means the list couldn't be
|
|
35
|
+
* read in full (truncated or not fetched), which is never a release.
|
|
36
|
+
*/
|
|
37
|
+
declare function classifyReleaseDiff(files: readonly ChangedFile[] | undefined): ReleaseDiffVerdict;
|
|
38
|
+
|
|
1
39
|
/**
|
|
2
40
|
* Bot-PR eligibility: is this a bot PR trusted enough to count as `approved`
|
|
3
41
|
* without a review (state priority 7 in docs/reconciler-design.md)? Ported from
|
|
4
|
-
* @rmartz/bot-automerge's classifier, with
|
|
5
|
-
* branch must live in the base repository
|
|
42
|
+
* @rmartz/bot-automerge's classifier, with deliberate tightenings: the head
|
|
43
|
+
* branch must live in the base repository, and a release-please PR is recognized
|
|
44
|
+
* by its branch and a release-only diff, never by a label. See
|
|
45
|
+
* docs/bot-eligibility.md.
|
|
6
46
|
*
|
|
7
47
|
* FAIL-SAFE: every uncertain or unrecognized case is not eligible.
|
|
8
48
|
*/
|
|
9
49
|
declare const DEPENDABOT_LOGIN = "dependabot[bot]";
|
|
10
50
|
declare const RELEASE_PLEASE_BRANCH_PREFIX = "release-please--";
|
|
11
|
-
declare const RELEASE_PLEASE_PENDING_LABEL = "autorelease: pending";
|
|
12
51
|
/** Dependabot's semver update types, lowest to highest risk. */
|
|
13
52
|
declare const DEPENDABOT_UPDATE_TYPES: readonly ["version-update:semver-patch", "version-update:semver-minor", "version-update:semver-major"];
|
|
14
53
|
type DependabotUpdateType = (typeof DEPENDABOT_UPDATE_TYPES)[number];
|
|
@@ -23,9 +62,13 @@ interface BotPrFacts {
|
|
|
23
62
|
headRef: string;
|
|
24
63
|
/** True when the head branch lives in a fork, not the base repository. */
|
|
25
64
|
isCrossRepository: boolean;
|
|
26
|
-
labels: readonly string[];
|
|
27
65
|
/** The PR's commits; only consulted for Dependabot PRs. */
|
|
28
66
|
commits: readonly BotPrCommit[];
|
|
67
|
+
/**
|
|
68
|
+
* The PR's changed files; only consulted for release-please PRs. Undefined when
|
|
69
|
+
* not fetched or not read in full, which is never eligible.
|
|
70
|
+
*/
|
|
71
|
+
changedFiles: readonly ChangedFile[] | undefined;
|
|
29
72
|
}
|
|
30
73
|
interface BotEligibility {
|
|
31
74
|
eligible: boolean;
|
|
@@ -73,6 +116,12 @@ type ActorType = (typeof ACTOR_TYPES)[number];
|
|
|
73
116
|
declare const REVIEW_STATES: readonly ["APPROVED", "CHANGES_REQUESTED", "COMMENTED", "DISMISSED", "PENDING"];
|
|
74
117
|
type ReviewState = (typeof REVIEW_STATES)[number];
|
|
75
118
|
declare const PR_STATUSES: readonly ["closed", "merged", "open"];
|
|
119
|
+
/**
|
|
120
|
+
* Who brings a PR's branch up to date with its base. Dependabot owns its branches
|
|
121
|
+
* and rebases them itself when asked; anything else uses GitHub's update-branch.
|
|
122
|
+
*/
|
|
123
|
+
declare const BRANCH_UPDATERS: readonly ["dependabot", "github"];
|
|
124
|
+
type BranchUpdater = (typeof BRANCH_UPDATERS)[number];
|
|
76
125
|
type PullRequestStatus = (typeof PR_STATUSES)[number];
|
|
77
126
|
interface ReviewAuthor {
|
|
78
127
|
login: string;
|
|
@@ -120,6 +169,15 @@ interface PullRequestFacts {
|
|
|
120
169
|
* docs/reconciler-design.md §Approval carry-over). Verified at the edge.
|
|
121
170
|
*/
|
|
122
171
|
cleanAncestors: readonly string[];
|
|
172
|
+
/** Who updates the branch (see `BranchUpdater`). */
|
|
173
|
+
updater: BranchUpdater;
|
|
174
|
+
/**
|
|
175
|
+
* For a Dependabot PR: a rebase is running (the PR body says so) or was already
|
|
176
|
+
* requested for this head, so asking again would only add noise. A request is
|
|
177
|
+
* made at most once per head, whether Dependabot then rebases or replies with an
|
|
178
|
+
* error. Always `false` for other PRs.
|
|
179
|
+
*/
|
|
180
|
+
rebasePending: boolean;
|
|
123
181
|
reviews: readonly ReviewFact[];
|
|
124
182
|
}
|
|
125
183
|
interface ReconcilePolicy {
|
|
@@ -131,6 +189,12 @@ interface ReconcilePolicy {
|
|
|
131
189
|
trustedAuthors?: readonly string[];
|
|
132
190
|
/** Arm/disarm native auto-merge from the lifecycle state. Off by default. */
|
|
133
191
|
armAutoMerge?: boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Bring an approved PR up to date when merge-safety labels it `update required`:
|
|
194
|
+
* `update-branch` for most PRs, a `@dependabot rebase` request for Dependabot's.
|
|
195
|
+
* Off by default.
|
|
196
|
+
*/
|
|
197
|
+
autoUpdate?: boolean;
|
|
134
198
|
/**
|
|
135
199
|
* Don't wait for a Copilot review before `review-requested`, for repos without
|
|
136
200
|
* Copilot code review (where the wait would never end). Off by default.
|
|
@@ -159,6 +223,8 @@ interface PullRequestData {
|
|
|
159
223
|
merged: boolean;
|
|
160
224
|
draft: boolean;
|
|
161
225
|
title: string;
|
|
226
|
+
/** The PR description (empty when none); Dependabot reports a running rebase here. */
|
|
227
|
+
body: string;
|
|
162
228
|
headSha: string;
|
|
163
229
|
labels: string[];
|
|
164
230
|
autoMergeEnabled: boolean;
|
|
@@ -175,6 +241,8 @@ interface PullRequestData {
|
|
|
175
241
|
mergeable: boolean | undefined;
|
|
176
242
|
/** GitHub's merge state (`clean`, `blocked`, `behind`, …); undefined while computing. */
|
|
177
243
|
mergeState: string | undefined;
|
|
244
|
+
/** How many files the PR changes (`changed_files`), to tell a full file list from a truncated one. */
|
|
245
|
+
changedFileCount: number;
|
|
178
246
|
}
|
|
179
247
|
/** A check-run on a commit (the latest run per name). */
|
|
180
248
|
interface CheckRunData {
|
|
@@ -231,6 +299,8 @@ interface GitHubClient {
|
|
|
231
299
|
getPullRequest(pr: number): Promise<PullRequestData>;
|
|
232
300
|
listReviews(pr: number): Promise<ReviewData[]>;
|
|
233
301
|
listCommits(pr: number): Promise<CommitData[]>;
|
|
302
|
+
/** The PR's changed files. GitHub lists at most 3,000, so compare with `changedFileCount`. */
|
|
303
|
+
listPullRequestFiles(pr: number): Promise<ChangedFile[]>;
|
|
234
304
|
/** The contexts the branch's rulesets require (empty when none are configured). */
|
|
235
305
|
getRequiredStatusChecks(branch: string): Promise<string[]>;
|
|
236
306
|
getBranchHeadSha(branch: string): Promise<string>;
|
|
@@ -257,13 +327,19 @@ interface GitHubClient {
|
|
|
257
327
|
/** Squash-merge now, bound to `expectedHeadOid` like `enableAutoMerge`. */
|
|
258
328
|
mergePullRequest(pullRequestNodeId: string, expectedHeadOid: string): Promise<void>;
|
|
259
329
|
disableAutoMerge(pullRequestNodeId: string): Promise<void>;
|
|
330
|
+
/**
|
|
331
|
+
* Merge the base into the PR branch, bound to `expectedHeadSha`. Throws a
|
|
332
|
+
* GitHubApiError with status 422 when the head moved or there is nothing to merge.
|
|
333
|
+
*/
|
|
334
|
+
updateBranch(pr: number, expectedHeadSha: string): Promise<void>;
|
|
260
335
|
}
|
|
261
336
|
/**
|
|
262
|
-
* The writes that must come from a real actor
|
|
263
|
-
* `
|
|
264
|
-
*
|
|
337
|
+
* The writes that must come from a real actor: a merge or branch update made with
|
|
338
|
+
* `GITHUB_TOKEN` triggers no workflows (so no release, and no CI on the new head),
|
|
339
|
+
* and Dependabot only takes commands from people. Kept to these calls so the
|
|
340
|
+
* release token is never used for anything else, reads included.
|
|
265
341
|
*/
|
|
266
|
-
type ReleaseActions = Pick<GitHubClient, 'enableAutoMerge' | 'mergePullRequest'>;
|
|
342
|
+
type ReleaseActions = Pick<GitHubClient, 'createIssueComment' | 'enableAutoMerge' | 'mergePullRequest' | 'updateBranch'>;
|
|
267
343
|
declare class GitHubApiError extends Error {
|
|
268
344
|
readonly status: number;
|
|
269
345
|
constructor(status: number, message: string);
|
|
@@ -271,10 +347,9 @@ declare class GitHubApiError extends Error {
|
|
|
271
347
|
declare function isApiStatus(error: unknown, status: number): boolean;
|
|
272
348
|
|
|
273
349
|
/**
|
|
274
|
-
* The real GitHubClient: REST
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* gather.ts / execute.ts. `fetch` is injectable so tests use a fake transport.
|
|
350
|
+
* The HTTP transport under the real GitHubClient: authenticated REST requests,
|
|
351
|
+
* pagination, and GraphQL, each throwing GitHubApiError on failure. Split out of
|
|
352
|
+
* http-client.ts so the client modules only map requests and responses.
|
|
278
353
|
*/
|
|
279
354
|
interface HttpClientOptions {
|
|
280
355
|
token: string;
|
|
@@ -284,6 +359,7 @@ interface HttpClientOptions {
|
|
|
284
359
|
apiUrl?: string;
|
|
285
360
|
fetch?: typeof fetch;
|
|
286
361
|
}
|
|
362
|
+
|
|
287
363
|
declare function createHttpClient(options: HttpClientOptions): GitHubClient;
|
|
288
364
|
|
|
289
365
|
/**
|
|
@@ -324,7 +400,7 @@ interface CliDeps {
|
|
|
324
400
|
/** Runs git for approval carry-over; tests inject one. Defaults to the real binary. */
|
|
325
401
|
git?: GitRunner;
|
|
326
402
|
}
|
|
327
|
-
declare const USAGE = "Usage: ai-pr-lifecycle <command> [options]\n\nCommands:\n reconcile Recompute a PR's lifecycle state and converge its labels\n help Show this message\n\nreconcile options:\n --repo <owner/repo> Repository (required)\n --pr <number> Pull request number (required)\n --arm-auto-merge Arm/disarm native auto-merge from the state\n --trusted-authors <a,b> Only these logins (with write access) may cast verdicts\n --skip-copilot-review Don't wait for a Copilot review\n --hold-checks <a,b> Required checks whose pending is a hold (default: pr-policy)\n --ignore-checks <a,b> Required checks CI never counts (default: merge-safety)\n --dry-run Compute the plan without writing anything\n --json Print one JSON object (schemaVersion 1) on stdout\n --no-token-advisory Don't comment on the PR when arming lacks a release token\n\nEnvironment:\n GITHUB_TOKEN Token for reads and writes (required)\n PR_LIFECYCLE_TOKEN\n Real-actor token for arming
|
|
403
|
+
declare const USAGE = "Usage: ai-pr-lifecycle <command> [options]\n\nCommands:\n reconcile Recompute a PR's lifecycle state and converge its labels\n help Show this message\n\nreconcile options:\n --repo <owner/repo> Repository (required)\n --pr <number> Pull request number (required)\n --arm-auto-merge Arm/disarm native auto-merge from the state\n --auto-update Update approved PRs merge-safety flags 'update required'\n (Dependabot PRs are asked to rebase, never updated)\n --trusted-authors <a,b> Only these logins (with write access) may cast verdicts\n --skip-copilot-review Don't wait for a Copilot review\n --hold-checks <a,b> Required checks whose pending is a hold (default: pr-policy)\n --ignore-checks <a,b> Required checks CI never counts (default: merge-safety)\n --dry-run Compute the plan without writing anything\n --json Print one JSON object (schemaVersion 1) on stdout\n --no-token-advisory Don't comment on the PR when arming lacks a release token\n\nEnvironment:\n GITHUB_TOKEN Token for reads and writes (required)\n PR_LIFECYCLE_TOKEN\n Real-actor token for arming, merging, and updating;\n without it, labels are kept but those are skipped\n GITHUB_API_URL API base URL (GitHub Enterprise Server)";
|
|
328
404
|
declare function runCli(argv: readonly string[], io: CliIo, deps: CliDeps): Promise<number>;
|
|
329
405
|
|
|
330
406
|
/**
|
|
@@ -369,24 +445,33 @@ declare function computeState(facts: PullRequestFacts, policy: ReconcilePolicy):
|
|
|
369
445
|
declare const LIFECYCLE_LABELS: readonly ["approved", "changes requested", "escalation needed", "fix required", "ci failing", "review requested"];
|
|
370
446
|
type LifecycleLabel = (typeof LIFECYCLE_LABELS)[number];
|
|
371
447
|
declare const AUTO_MERGE_LABEL = "auto-merge enabled";
|
|
448
|
+
/** merge-safety's label for a PR whose base moved in a way that matters. Read, never written. */
|
|
449
|
+
declare const UPDATE_REQUIRED_LABEL = "update required";
|
|
372
450
|
/**
|
|
373
451
|
* `merge` is arming's twin for a PR GitHub would already merge: auto-merge can't
|
|
374
452
|
* be armed then, so it is merged directly (see `immediatelyMergeable`).
|
|
375
453
|
*/
|
|
376
454
|
type AutoMergeAction = 'arm' | 'disarm' | 'merge' | 'none';
|
|
455
|
+
/**
|
|
456
|
+
* How an approved PR flagged `update required` is brought up to date. A Dependabot
|
|
457
|
+
* PR is never updated directly — a foreign commit on its branch permanently breaks
|
|
458
|
+
* its own rebasing — so it is only ever asked to rebase itself.
|
|
459
|
+
*/
|
|
460
|
+
type UpdateAction = 'dependabot-rebase' | 'none' | 'update-branch';
|
|
377
461
|
interface ReconcilePlan {
|
|
378
462
|
state: LifecycleState;
|
|
379
463
|
addLabels: string[];
|
|
380
464
|
removeLabels: string[];
|
|
381
465
|
autoMerge: AutoMergeAction;
|
|
466
|
+
update: UpdateAction;
|
|
382
467
|
}
|
|
383
468
|
declare function planReconcile(facts: PullRequestFacts, policy: ReconcilePolicy): ReconcilePlan;
|
|
384
469
|
/**
|
|
385
|
-
* The plan with arming and
|
|
386
|
-
* available to perform them (docs/cli.md §Release token). Disarming
|
|
387
|
-
* safety, and any token may do it. Pure, like the planner.
|
|
470
|
+
* The plan with arming, merging, and updating taken out, for when no real-actor
|
|
471
|
+
* token is available to perform them (docs/cli.md §Release token). Disarming
|
|
472
|
+
* stays: it is safety, and any token may do it. Pure, like the planner.
|
|
388
473
|
*/
|
|
389
|
-
declare function
|
|
474
|
+
declare function withoutReleaseActions(plan: ReconcilePlan): ReconcilePlan;
|
|
390
475
|
|
|
391
476
|
interface MergeStep {
|
|
392
477
|
/** The merge base of the two parents. */
|
|
@@ -469,9 +554,9 @@ interface ReconcileOptions extends GatherOptions {
|
|
|
469
554
|
/** Compute and return the plan without writing anything. */
|
|
470
555
|
dryRun?: boolean;
|
|
471
556
|
/**
|
|
472
|
-
* Who arms and
|
|
473
|
-
* actor. `'unavailable'` (no real-actor token configured) skips
|
|
474
|
-
*
|
|
557
|
+
* Who arms, merges, and updates. Defaults to `client`, right when its token is a
|
|
558
|
+
* real actor. `'unavailable'` (no real-actor token configured) skips them rather
|
|
559
|
+
* than doing them with a token whose pushes and merges trigger no workflows.
|
|
475
560
|
*/
|
|
476
561
|
release?: ReleaseActions | 'unavailable';
|
|
477
562
|
}
|
|
@@ -483,6 +568,8 @@ interface ReconcileResult {
|
|
|
483
568
|
lineage: Lineage | undefined;
|
|
484
569
|
/** The arm or merge the plan wanted but skipped because `release` was unavailable. */
|
|
485
570
|
skippedAutoMerge: 'arm' | 'merge' | undefined;
|
|
571
|
+
/** The branch update the plan wanted but skipped because `release` was unavailable. */
|
|
572
|
+
skippedUpdate: Exclude<UpdateAction, 'none'> | undefined;
|
|
486
573
|
}
|
|
487
574
|
/**
|
|
488
575
|
* One full reconcile pass for a PR: gather its facts, plan, and (unless dry-run)
|
|
@@ -530,16 +617,45 @@ interface ReconcileJson {
|
|
|
530
617
|
action: 'arm' | 'merge';
|
|
531
618
|
reason: 'token-missing';
|
|
532
619
|
} | null;
|
|
620
|
+
/** The branch update performed: `none`, `update-branch`, or `dependabot-rebase`. */
|
|
621
|
+
update: string;
|
|
622
|
+
/** A branch update the plan wanted but skipped; `null` when nothing was skipped. */
|
|
623
|
+
updateSkipped: {
|
|
624
|
+
action: 'dependabot-rebase' | 'update-branch';
|
|
625
|
+
reason: 'token-missing';
|
|
626
|
+
} | null;
|
|
533
627
|
}
|
|
534
628
|
declare function toReconcileJson(target: ReconcileTarget, result: ReconcileResult): ReconcileJson;
|
|
535
629
|
/** One human-readable line, e.g. `rmartz/x#7 → approved: +approved, auto-merge arm`. */
|
|
536
630
|
declare function formatSummary(target: ReconcileTarget, result: ReconcileResult): string;
|
|
537
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Asking Dependabot to rebase its own PR — the only way a Dependabot PR is ever
|
|
634
|
+
* brought up to date. The request is made at most once per head: after asking,
|
|
635
|
+
* the reconciler waits for Dependabot to either rebase (a new head, which may be
|
|
636
|
+
* asked again) or reply with an error (the head is unchanged, so it stays quiet
|
|
637
|
+
* and leaves the PR to a person). See docs/github-edge-layer.md §Auto-update.
|
|
638
|
+
*/
|
|
639
|
+
declare const DEPENDABOT_REBASE_COMMAND = "@dependabot rebase";
|
|
640
|
+
/** Dependabot puts this in the PR body while a rebase runs, and removes it after. */
|
|
641
|
+
declare const DEPENDABOT_REBASING_NOTICE = "Dependabot is rebasing this PR";
|
|
642
|
+
/** Hidden marker recording which head a request was made for. */
|
|
643
|
+
declare function rebaseRequestMarker(headSha: string): string;
|
|
644
|
+
/** The command on its own first line, as Dependabot parses it, then the marker. */
|
|
645
|
+
declare function buildRebaseRequestBody(headSha: string): string;
|
|
646
|
+
/**
|
|
647
|
+
* Whether asking again would only add noise: a rebase is already running, or one
|
|
648
|
+
* was already requested for this head. A forged marker can only suppress a
|
|
649
|
+
* request, never cause one, so the comment author doesn't matter.
|
|
650
|
+
*/
|
|
651
|
+
declare function isRebasePending(prBody: string, comments: readonly string[], headSha: string): boolean;
|
|
652
|
+
|
|
538
653
|
/**
|
|
539
654
|
* Executes a reconcile plan against GitHub. Writes happen in a fail-safe order —
|
|
540
|
-
* disarm, remove labels, add labels, arm — so a run that dies midway never
|
|
541
|
-
* auto-merge armed on a PR that is not approved
|
|
542
|
-
*
|
|
655
|
+
* disarm, remove labels, add labels, arm, update — so a run that dies midway never
|
|
656
|
+
* leaves auto-merge armed on a PR that is not approved, and an update (which moves
|
|
657
|
+
* the head) comes last. Arming, merging, and updating go through `release` (a
|
|
658
|
+
* real-actor token) and are bound to the planned head. See
|
|
543
659
|
* docs/github-edge-layer.md.
|
|
544
660
|
*/
|
|
545
661
|
interface PlanTarget {
|
|
@@ -593,4 +709,4 @@ declare function parseVerdict(review: ReviewFact): ParsedVerdict | undefined;
|
|
|
593
709
|
declare function isTrustedAuthor(author: ReviewAuthor, policy: ReconcilePolicy): boolean;
|
|
594
710
|
declare function currentVerdict(facts: PullRequestFacts, policy: ReconcilePolicy): Verdict | undefined;
|
|
595
711
|
|
|
596
|
-
export { ACTOR_TYPES, AUTO_MERGE_LABEL, type ActorType, type AutoMergeAction, type BotEligibility, type BotPrCommit, type BotPrFacts, type BotPrType, COPILOT_REVIEWER_LOGIN, type CliDeps, type CliIo, type CollaboratorPermission, type CommitData, DEPENDABOT_LOGIN, DEPENDABOT_UPDATE_TYPES, type DependabotUpdateType, type GatherOptions, type GatheredPullRequest, GitHubApiError, type GitHubClient, type GitResult, type GitRunner, type HttpClientOptions, LIFECYCLE_LABELS, LIFECYCLE_STATES, type LabelDefinition, type LifecycleLabel, type LifecycleState, type Lineage, type LineageInput, MAX_CHAIN_STEPS, type MergeStep, type MergeVerifier, OWNED_LABEL_DEFINITIONS, PR_STATUSES, type ParsedArgs, type ParsedVerdict, type PlanTarget, type PullRequestData, type PullRequestFacts, type PullRequestStatus, RELEASE_PLEASE_BRANCH_PREFIX,
|
|
712
|
+
export { ACTOR_TYPES, AUTO_MERGE_LABEL, type ActorType, type AutoMergeAction, BRANCH_UPDATERS, type BotEligibility, type BotPrCommit, type BotPrFacts, type BotPrType, type BranchUpdater, COPILOT_REVIEWER_LOGIN, type ChangedFile, type CliDeps, type CliIo, type CollaboratorPermission, type CommitData, DEPENDABOT_LOGIN, DEPENDABOT_REBASE_COMMAND, DEPENDABOT_REBASING_NOTICE, DEPENDABOT_UPDATE_TYPES, type DependabotUpdateType, type GatherOptions, type GatheredPullRequest, GitHubApiError, type GitHubClient, type GitResult, type GitRunner, type HttpClientOptions, LIFECYCLE_LABELS, LIFECYCLE_STATES, type LabelDefinition, type LifecycleLabel, type LifecycleState, type Lineage, type LineageInput, MAX_CHAIN_STEPS, type MergeStep, type MergeVerifier, OWNED_LABEL_DEFINITIONS, PR_STATUSES, type ParsedArgs, type ParsedVerdict, type PlanTarget, type PullRequestData, type PullRequestFacts, type PullRequestStatus, RELEASE_PLEASE_BRANCH_PREFIX, RELEASE_TOKEN_DOCS_URL, REPO_PERMISSIONS, REVIEW_STATES, type ReconcileArgs, type ReconcileJson, type ReconcileOptions, type ReconcilePlan, type ReconcilePolicy, type ReconcileResult, type ReconcileTarget, type ReleaseActions, type ReleaseDiffVerdict, type RepoPermission, type RepoSource, type ReviewAuthor, type ReviewData, type ReviewFact, type ReviewState, SCHEMA_VERSION, TOKEN_ADVISORY_MARKER, UPDATE_REQUIRED_LABEL, USAGE, type UpdateAction, VERDICTS, type Verdict, buildRebaseRequestBody, buildTokenAdvisoryBody, classifyBotPr, classifyReleaseDiff, computeState, createGitRunner, createHttpClient, createMergeVerifier, currentVerdict, executePlan, formatSummary, gatherFacts, gatherLineage, isApiStatus, isRebasePending, isTrustedAuthor, isVersionOnlyPatch, parseArgs, parseDependabotUpdateType, parseGitVersion, parseVerdict, planReconcile, postTokenAdvisory, rebaseRequestMarker, reconcilePullRequest, runCli, toReconcileJson, withoutReleaseActions };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ACTOR_TYPES,
|
|
3
3
|
AUTO_MERGE_LABEL,
|
|
4
|
+
BRANCH_UPDATERS,
|
|
4
5
|
COPILOT_REVIEWER_LOGIN,
|
|
5
6
|
DEPENDABOT_LOGIN,
|
|
7
|
+
DEPENDABOT_REBASE_COMMAND,
|
|
8
|
+
DEPENDABOT_REBASING_NOTICE,
|
|
6
9
|
DEPENDABOT_UPDATE_TYPES,
|
|
7
10
|
GitHubApiError,
|
|
8
11
|
LIFECYCLE_LABELS,
|
|
@@ -11,16 +14,18 @@ import {
|
|
|
11
14
|
OWNED_LABEL_DEFINITIONS,
|
|
12
15
|
PR_STATUSES,
|
|
13
16
|
RELEASE_PLEASE_BRANCH_PREFIX,
|
|
14
|
-
RELEASE_PLEASE_PENDING_LABEL,
|
|
15
17
|
RELEASE_TOKEN_DOCS_URL,
|
|
16
18
|
REPO_PERMISSIONS,
|
|
17
19
|
REVIEW_STATES,
|
|
18
20
|
SCHEMA_VERSION,
|
|
19
21
|
TOKEN_ADVISORY_MARKER,
|
|
22
|
+
UPDATE_REQUIRED_LABEL,
|
|
20
23
|
USAGE,
|
|
21
24
|
VERDICTS,
|
|
25
|
+
buildRebaseRequestBody,
|
|
22
26
|
buildTokenAdvisoryBody,
|
|
23
27
|
classifyBotPr,
|
|
28
|
+
classifyReleaseDiff,
|
|
24
29
|
computeState,
|
|
25
30
|
createGitRunner,
|
|
26
31
|
createHttpClient,
|
|
@@ -31,23 +36,29 @@ import {
|
|
|
31
36
|
gatherFacts,
|
|
32
37
|
gatherLineage,
|
|
33
38
|
isApiStatus,
|
|
39
|
+
isRebasePending,
|
|
34
40
|
isTrustedAuthor,
|
|
41
|
+
isVersionOnlyPatch,
|
|
35
42
|
parseArgs,
|
|
36
43
|
parseDependabotUpdateType,
|
|
37
44
|
parseGitVersion,
|
|
38
45
|
parseVerdict,
|
|
39
46
|
planReconcile,
|
|
40
47
|
postTokenAdvisory,
|
|
48
|
+
rebaseRequestMarker,
|
|
41
49
|
reconcilePullRequest,
|
|
42
50
|
runCli,
|
|
43
51
|
toReconcileJson,
|
|
44
|
-
|
|
45
|
-
} from "./chunk-
|
|
52
|
+
withoutReleaseActions
|
|
53
|
+
} from "./chunk-PJOTYSEK.js";
|
|
46
54
|
export {
|
|
47
55
|
ACTOR_TYPES,
|
|
48
56
|
AUTO_MERGE_LABEL,
|
|
57
|
+
BRANCH_UPDATERS,
|
|
49
58
|
COPILOT_REVIEWER_LOGIN,
|
|
50
59
|
DEPENDABOT_LOGIN,
|
|
60
|
+
DEPENDABOT_REBASE_COMMAND,
|
|
61
|
+
DEPENDABOT_REBASING_NOTICE,
|
|
51
62
|
DEPENDABOT_UPDATE_TYPES,
|
|
52
63
|
GitHubApiError,
|
|
53
64
|
LIFECYCLE_LABELS,
|
|
@@ -56,16 +67,18 @@ export {
|
|
|
56
67
|
OWNED_LABEL_DEFINITIONS,
|
|
57
68
|
PR_STATUSES,
|
|
58
69
|
RELEASE_PLEASE_BRANCH_PREFIX,
|
|
59
|
-
RELEASE_PLEASE_PENDING_LABEL,
|
|
60
70
|
RELEASE_TOKEN_DOCS_URL,
|
|
61
71
|
REPO_PERMISSIONS,
|
|
62
72
|
REVIEW_STATES,
|
|
63
73
|
SCHEMA_VERSION,
|
|
64
74
|
TOKEN_ADVISORY_MARKER,
|
|
75
|
+
UPDATE_REQUIRED_LABEL,
|
|
65
76
|
USAGE,
|
|
66
77
|
VERDICTS,
|
|
78
|
+
buildRebaseRequestBody,
|
|
67
79
|
buildTokenAdvisoryBody,
|
|
68
80
|
classifyBotPr,
|
|
81
|
+
classifyReleaseDiff,
|
|
69
82
|
computeState,
|
|
70
83
|
createGitRunner,
|
|
71
84
|
createHttpClient,
|
|
@@ -76,15 +89,18 @@ export {
|
|
|
76
89
|
gatherFacts,
|
|
77
90
|
gatherLineage,
|
|
78
91
|
isApiStatus,
|
|
92
|
+
isRebasePending,
|
|
79
93
|
isTrustedAuthor,
|
|
94
|
+
isVersionOnlyPatch,
|
|
80
95
|
parseArgs,
|
|
81
96
|
parseDependabotUpdateType,
|
|
82
97
|
parseGitVersion,
|
|
83
98
|
parseVerdict,
|
|
84
99
|
planReconcile,
|
|
85
100
|
postTokenAdvisory,
|
|
101
|
+
rebaseRequestMarker,
|
|
86
102
|
reconcilePullRequest,
|
|
87
103
|
runCli,
|
|
88
104
|
toReconcileJson,
|
|
89
|
-
|
|
105
|
+
withoutReleaseActions
|
|
90
106
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmartz/pr-lifecycle",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Event-driven PR lifecycle reconciler: recomputes a pull request's lifecycle labels from its current facts on every GitHub event and arms native auto-merge on approval.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|