Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion scripts/actionlint.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readFileSync, readdirSync } from "node:fs";
import { createRequire } from "node:module";
import { join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { resolveActionlintDownloadAttempts } from "./lib/actionlint-download-attempts.mjs";

const require = createRequire(import.meta.url);
const { actionlint } = require("github-actionlint");
Expand All @@ -16,7 +17,7 @@ if (files.length === 0) {
process.exit(1);
}

const maxAttempts = Math.max(1, Number.parseInt(process.env.ACTIONLINT_DOWNLOAD_ATTEMPTS ?? "4", 10) || 4);
const maxAttempts = resolveActionlintDownloadAttempts(process.env.ACTIONLINT_DOWNLOAD_ATTEMPTS);
const retryDelaysMs = [1000, 3000, 7000];
const retryableSetupError = /Download failed: (?:408|425|429|5\d\d)\b|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|socket hang up/i;

Expand Down
1 change: 1 addition & 0 deletions scripts/lib/actionlint-download-attempts.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function resolveActionlintDownloadAttempts(raw: string | undefined): number;
15 changes: 15 additions & 0 deletions scripts/lib/actionlint-download-attempts.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Resolve the actionlint download-retry attempt count from a raw env value (ACTIONLINT_DOWNLOAD_ATTEMPTS).
*
* Uses an explicit finiteness check rather than a `Number.parseInt(...) || 4` fallback (#7773): `0` is falsy
* in JS, so `Number.parseInt("0", 10) || 4` wrongly yields 4, silently ignoring an operator's explicit `"0"`.
* A missing/blank/non-integer value defaults to 4; any parsed integer is honored and floored to a minimum of
* 1. Mirrors the validation shape in migrate-selfhost-sqlite-to-postgres.ts (`Number.isFinite`, not `||`).
*
* @param {string | undefined} raw
* @returns {number} attempt count, always >= 1
*/
export function resolveActionlintDownloadAttempts(raw) {
const parsed = Number.parseInt(raw ?? "4", 10);
return Math.max(1, Number.isFinite(parsed) ? parsed : 4);
}
26 changes: 26 additions & 0 deletions test/unit/actionlint-download-attempts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from "vitest";

import { resolveActionlintDownloadAttempts } from "../../scripts/lib/actionlint-download-attempts.mjs";

// #7773: an explicit ACTIONLINT_DOWNLOAD_ATTEMPTS="0" must be respected (floored to 1), not silently turned
// into the default of 4 by a `parseInt(...) || 4` fallback (0 is falsy).
describe("resolveActionlintDownloadAttempts (#7773)", () => {
it("respects an explicit 0, flooring it to 1 (not the default 4)", () => {
expect(resolveActionlintDownloadAttempts("0")).toBe(1);
});

it("defaults to 4 when unset, blank, or non-integer", () => {
expect(resolveActionlintDownloadAttempts(undefined)).toBe(4);
expect(resolveActionlintDownloadAttempts("")).toBe(4);
expect(resolveActionlintDownloadAttempts("abc")).toBe(4);
});

it("honors an explicit positive integer", () => {
expect(resolveActionlintDownloadAttempts("1")).toBe(1);
expect(resolveActionlintDownloadAttempts("7")).toBe(7);
});

it("floors a negative value to 1", () => {
expect(resolveActionlintDownloadAttempts("-3")).toBe(1);
});
});