@rmartz/merge-safety 0.14.0 → 0.15.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/dist/bin/merge-safety.js +71 -3
- package/package.json +1 -1
package/dist/bin/merge-safety.js
CHANGED
|
@@ -53,16 +53,35 @@ var MAX_RETRIES = 2;
|
|
|
53
53
|
var BACKOFF_BASE_MS = 1e3;
|
|
54
54
|
var BACKOFF_CAP_MS = 8e3;
|
|
55
55
|
var realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
|
+
var GhTransientError = class extends Error {
|
|
57
|
+
constructor(stderr) {
|
|
58
|
+
super(`transient GitHub API failure: ${stderr}`);
|
|
59
|
+
this.stderr = stderr;
|
|
60
|
+
this.name = "GhTransientError";
|
|
61
|
+
}
|
|
62
|
+
stderr;
|
|
63
|
+
};
|
|
56
64
|
function isRateLimited(text) {
|
|
57
65
|
const t = (text ?? "").toLowerCase();
|
|
58
66
|
return t.includes("rate limit") || t.includes("rate-limit");
|
|
59
67
|
}
|
|
68
|
+
var TRANSIENT_PATTERNS = [
|
|
69
|
+
/\bHTTP 5\d\d\b/,
|
|
70
|
+
/bad gateway|service unavailable|gateway time-?out|internal server error/i,
|
|
71
|
+
/timed out|timeout/i,
|
|
72
|
+
/connection (?:reset|refused)/i,
|
|
73
|
+
/could not resolve host|no such host|tls handshake|unexpected eof/i
|
|
74
|
+
];
|
|
75
|
+
function isTransientGhFailure(stderr) {
|
|
76
|
+
return isRateLimited(stderr) || TRANSIENT_PATTERNS.some((p) => p.test(stderr));
|
|
77
|
+
}
|
|
60
78
|
async function runTransport({ argv, stdin }, cwd) {
|
|
61
79
|
const [command, ...args] = argv;
|
|
62
80
|
if (command === void 0) return { stdout: null, stderr: "empty argv" };
|
|
63
81
|
try {
|
|
64
82
|
const r = await boundedRun(command, args, { timeoutMs: GH_API_TIMEOUT_MS, cwd, input: stdin });
|
|
65
83
|
if (r.code === 0) return { stdout: r.stdout, stderr: "" };
|
|
84
|
+
if (r.timedOut) return { stdout: null, stderr: `gh timed out after ${GH_API_TIMEOUT_MS}ms` };
|
|
66
85
|
return { stdout: null, stderr: r.stderr || "" };
|
|
67
86
|
} catch (err) {
|
|
68
87
|
return { stdout: null, stderr: err instanceof Error ? err.message : String(err) };
|
|
@@ -70,17 +89,22 @@ async function runTransport({ argv, stdin }, cwd) {
|
|
|
70
89
|
}
|
|
71
90
|
async function ghCall(primary, fallback, opts = {}) {
|
|
72
91
|
const sleep = opts.sleep ?? realSleep;
|
|
92
|
+
let lastStderr = "";
|
|
73
93
|
for (const transport of [primary, fallback]) {
|
|
74
94
|
if (!transport) continue;
|
|
75
95
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
76
96
|
const { stdout, stderr } = await runTransport(transport, opts.cwd);
|
|
77
97
|
if (stdout !== null) return stdout;
|
|
98
|
+
lastStderr = stderr;
|
|
78
99
|
if (isRateLimited(stderr)) break;
|
|
79
100
|
if (attempt < MAX_RETRIES) {
|
|
80
101
|
await sleep(Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_CAP_MS));
|
|
81
102
|
}
|
|
82
103
|
}
|
|
83
104
|
}
|
|
105
|
+
if (opts.throwOnTransient && isTransientGhFailure(lastStderr)) {
|
|
106
|
+
throw new GhTransientError(lastStderr.trim());
|
|
107
|
+
}
|
|
84
108
|
return null;
|
|
85
109
|
}
|
|
86
110
|
function parseSlugFromRemoteUrl(url) {
|
|
@@ -225,6 +249,40 @@ async function postCheckRun(repo, headSha, output, conclusion, cwd) {
|
|
|
225
249
|
);
|
|
226
250
|
}
|
|
227
251
|
|
|
252
|
+
// src/lib/cancel-run.ts
|
|
253
|
+
var EXIT_TRANSIENT = 75;
|
|
254
|
+
var CANCEL_GRACE_MS = 12e4;
|
|
255
|
+
async function requestRunCancel(opts = {}) {
|
|
256
|
+
const { GITHUB_ACTIONS, GITHUB_REPOSITORY, GITHUB_RUN_ID } = opts.env ?? process.env;
|
|
257
|
+
if (GITHUB_ACTIONS !== "true" || !GITHUB_REPOSITORY || !GITHUB_RUN_ID) return false;
|
|
258
|
+
const out = await ghCall(
|
|
259
|
+
{
|
|
260
|
+
argv: [
|
|
261
|
+
"gh",
|
|
262
|
+
"api",
|
|
263
|
+
"-X",
|
|
264
|
+
"POST",
|
|
265
|
+
`repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/cancel`
|
|
266
|
+
]
|
|
267
|
+
},
|
|
268
|
+
null,
|
|
269
|
+
{ cwd: opts.cwd }
|
|
270
|
+
);
|
|
271
|
+
return out !== null;
|
|
272
|
+
}
|
|
273
|
+
async function abandonRunAsCancelled(err, opts = {}) {
|
|
274
|
+
console.warn(
|
|
275
|
+
`::warning title=merge-safety cancelled::${err.message} \u2014 cancelling this run; the next event re-evaluates.`
|
|
276
|
+
);
|
|
277
|
+
process.exitCode = EXIT_TRANSIENT;
|
|
278
|
+
if (!await requestRunCancel(opts)) {
|
|
279
|
+
console.error("could not request cancellation of this run; exiting as failed");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const wait = opts.wait ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
283
|
+
await wait(CANCEL_GRACE_MS);
|
|
284
|
+
}
|
|
285
|
+
|
|
228
286
|
// src/conventional-commits.ts
|
|
229
287
|
var BREAKING_SUBJECT_RE = /^[a-z]+(?:!(?:\([^)]*\))?|(?:\([^)]*\))?!):/;
|
|
230
288
|
var BREAKING_FOOTER_RE = /^BREAKING[ -]CHANGE:/m;
|
|
@@ -644,7 +702,7 @@ function parse(argv) {
|
|
|
644
702
|
return args;
|
|
645
703
|
}
|
|
646
704
|
async function ghJson(argv, cwd) {
|
|
647
|
-
const out = await ghCall({ argv }, null, { cwd });
|
|
705
|
+
const out = await ghCall({ argv }, null, { cwd, throwOnTransient: true });
|
|
648
706
|
if (out === null) return null;
|
|
649
707
|
try {
|
|
650
708
|
return JSON.parse(out);
|
|
@@ -835,8 +893,18 @@ async function main() {
|
|
|
835
893
|
console.error("error: could not resolve target repo (pass --repo <owner/repo>)");
|
|
836
894
|
process.exit(2);
|
|
837
895
|
}
|
|
838
|
-
|
|
839
|
-
|
|
896
|
+
try {
|
|
897
|
+
if (args.mode === "evaluate") await runEvaluate(repo, args.pr, args);
|
|
898
|
+
else await runInvalidate(repo, args);
|
|
899
|
+
} catch (err) {
|
|
900
|
+
if (!(err instanceof GhTransientError)) throw err;
|
|
901
|
+
if (args.json) {
|
|
902
|
+
console.error(`error: ${err.message}`);
|
|
903
|
+
process.exitCode = EXIT_TRANSIENT;
|
|
904
|
+
} else {
|
|
905
|
+
await abandonRunAsCancelled(err, { cwd: args.cwd });
|
|
906
|
+
}
|
|
907
|
+
}
|
|
840
908
|
}
|
|
841
909
|
function isDirectRun() {
|
|
842
910
|
const entry = process.argv[1];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmartz/merge-safety",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "The pre-auto-merge safety verdict for a PR (base-currency, breaking-change, and conflict facts), posted as the `merge-safety` check-run and distributed through the `rmartz/merge-safety-action` composite action kept current by Dependabot.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|