From 47171b419b8c1c4eadbc590403dc55d14d99395f Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 8 Sep 2026 11:07:13 +0200 Subject: [PATCH] fix(release): run release scripts as main on Windows runners (#4474) The ESM main-module guard compared import.meta.url with "file://" + process.argv[1], which never matches on Windows (file://D:\a\... vs file:///D:/a/...). On the Windows desktop release job, scripts/github-release.mjs therefore printed nothing and gh release upload received an empty release lookup ("release not found"), leaving v0.8.0-beta.1 without a Windows build. Compare resolved paths instead, shared by every release script that runs as a CLI. --- scripts/github-release.mjs | 3 +- scripts/is-main-module.mjs | 10 ++++++ scripts/is-main-module.test.mjs | 34 +++++++++++++++++++ scripts/merge-mac-manifest.mjs | 3 +- scripts/stamp-rollout.mjs | 3 +- scripts/sync-fdroid-changelogs.mjs | 3 +- scripts/sync-release-notes-from-changelog.mjs | 3 +- 7 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 scripts/is-main-module.mjs create mode 100644 scripts/is-main-module.test.mjs diff --git a/scripts/github-release.mjs b/scripts/github-release.mjs index cb166ef189..5c7b7f5988 100644 --- a/scripts/github-release.mjs +++ b/scripts/github-release.mjs @@ -1,4 +1,5 @@ import { execFileSync as nodeExecFileSync } from "node:child_process"; +import { isMainModule } from "./is-main-module.mjs"; function parseJson(output) { return JSON.parse(output); @@ -67,7 +68,7 @@ function parseArgs(argv) { return { cleanupDuplicates, repo, tag }; } -if (import.meta.url === `file://${process.argv[1]}`) { +if (isMainModule(import.meta.url)) { const { cleanupDuplicates, repo, tag } = parseArgs(process.argv.slice(2)); let release = null; for (let attempt = 0; attempt < 5 && !release; attempt += 1) { diff --git a/scripts/is-main-module.mjs b/scripts/is-main-module.mjs new file mode 100644 index 0000000000..f71c95f80d --- /dev/null +++ b/scripts/is-main-module.mjs @@ -0,0 +1,10 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// `import.meta.url === \`file://${process.argv[1]}\`` is false on Windows, where argv[1] is +// `D:\a\...` and the module URL is `file:///D:/a/...`, so a script guarded that way silently +// does nothing on a Windows runner. Compare resolved filesystem paths instead. +export function isMainModule(importMetaUrl, argvPath = process.argv[1]) { + if (!argvPath) return false; + return fileURLToPath(importMetaUrl) === path.resolve(argvPath); +} diff --git a/scripts/is-main-module.test.mjs b/scripts/is-main-module.test.mjs new file mode 100644 index 0000000000..b4d84882ed --- /dev/null +++ b/scripts/is-main-module.test.mjs @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import path from "node:path"; +import { test } from "node:test"; +import { pathToFileURL } from "node:url"; +import { isMainModule } from "./is-main-module.mjs"; + +test("matches the executed script on this platform", () => { + const scriptPath = path.resolve("scripts", "github-release.mjs"); + assert.equal(isMainModule(pathToFileURL(scriptPath).href, scriptPath), true); +}); + +test("does not match when another module is the entry point", () => { + const scriptPath = path.resolve("scripts", "github-release.mjs"); + const otherPath = path.resolve("scripts", "stamp-rollout.mjs"); + assert.equal(isMainModule(pathToFileURL(scriptPath).href, otherPath), false); +}); + +test("returns false without an argv entry", () => { + assert.equal( + isMainModule(pathToFileURL(path.resolve("scripts", "x.mjs")).href, undefined), + false, + ); +}); + +test("the naive string comparison that broke Windows is not used", () => { + const scriptPath = path.resolve("scripts", "github-release.mjs"); + const url = pathToFileURL(scriptPath).href; + // On Windows this naive form is `file://D:\\...` and never equals the module URL. + assert.notEqual( + `file://${"D:\\a\\paseo\\scripts\\github-release.mjs"}`, + "file:///D:/a/paseo/scripts/github-release.mjs", + ); + assert.equal(isMainModule(url, scriptPath), true); +}); diff --git a/scripts/merge-mac-manifest.mjs b/scripts/merge-mac-manifest.mjs index 78a5553b56..51fcc56df5 100644 --- a/scripts/merge-mac-manifest.mjs +++ b/scripts/merge-mac-manifest.mjs @@ -1,5 +1,6 @@ import fs from "node:fs"; import { dump, load } from "js-yaml"; +import { isMainModule } from "./is-main-module.mjs"; // electron-updater compares this with os.release(), which reports the Darwin // kernel version. Darwin 22 is macOS 13 Ventura. @@ -19,7 +20,7 @@ export function mergeMacManifest(arm64Path, x64Path, outputPath) { return output; } -if (import.meta.url === `file://${process.argv[1]}`) { +if (isMainModule(import.meta.url)) { const [, , arm64Path, x64Path, outputPath] = process.argv; if (!arm64Path || !x64Path || !outputPath) { throw new Error("Usage: node scripts/merge-mac-manifest.mjs "); diff --git a/scripts/stamp-rollout.mjs b/scripts/stamp-rollout.mjs index 2455d6b42d..a655b12e20 100644 --- a/scripts/stamp-rollout.mjs +++ b/scripts/stamp-rollout.mjs @@ -1,5 +1,6 @@ import fs from "node:fs"; import { dump, load } from "js-yaml"; +import { isMainModule } from "./is-main-module.mjs"; export function stampRollout({ releaseDate, rolloutHours }, paths) { if (releaseDate === undefined && rolloutHours === undefined) { @@ -35,7 +36,7 @@ function parseArgs(argv) { return { opts, paths }; } -if (import.meta.url === `file://${process.argv[1]}`) { +if (isMainModule(import.meta.url)) { const { opts, paths } = parseArgs(process.argv.slice(2)); if (paths.length === 0 || (opts.releaseDate === undefined && opts.rolloutHours === undefined)) { throw new Error( diff --git a/scripts/sync-fdroid-changelogs.mjs b/scripts/sync-fdroid-changelogs.mjs index 11690f39ef..8c69107640 100644 --- a/scripts/sync-fdroid-changelogs.mjs +++ b/scripts/sync-fdroid-changelogs.mjs @@ -21,6 +21,7 @@ import { fileURLToPath } from "node:url"; // require it, so the ABI table has exactly one definition across all three callers. import { getFdroidVersionCodes as defaultGetFdroidVersionCodes } from "../packages/app/native-release-version.js"; import { parseChangelogBody, parseChangelogEntries } from "./changelog-utils.mjs"; +import { isMainModule } from "./is-main-module.mjs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const rootDir = path.resolve(__dirname, ".."); @@ -263,6 +264,6 @@ export function syncFdroidChangelogs(argv = process.argv.slice(2), deps = {}) { return { contents, version, versionCodes, written }; } -if (import.meta.url === `file://${process.argv[1]}`) { +if (isMainModule(import.meta.url)) { syncFdroidChangelogs(); } diff --git a/scripts/sync-release-notes-from-changelog.mjs b/scripts/sync-release-notes-from-changelog.mjs index fb1018c181..95c9baec4c 100644 --- a/scripts/sync-release-notes-from-changelog.mjs +++ b/scripts/sync-release-notes-from-changelog.mjs @@ -9,6 +9,7 @@ import { normalizeReleaseTag, parseReleaseVersion, } from "./release-version-utils.mjs"; +import { isMainModule } from "./is-main-module.mjs"; function usageAndExit(code = 1) { const usage = ` @@ -201,6 +202,6 @@ export function syncReleaseNotes(argv = process.argv.slice(2), deps = {}) { } } -if (import.meta.url === `file://${process.argv[1]}`) { +if (isMainModule(import.meta.url)) { syncReleaseNotes(); }