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.
This commit is contained in:
Mohamed Boudra
2026-09-08 11:07:13 +02:00
committed by GitHub
parent f809651876
commit 47171b419b
7 changed files with 54 additions and 5 deletions
+2 -1
View File
@@ -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) {
+10
View File
@@ -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);
}
+34
View File
@@ -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);
});
+2 -1
View File
@@ -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 <arm64.yml> <x64.yml> <out.yml>");
+2 -1
View File
@@ -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(
+2 -1
View File
@@ -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();
}
@@ -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();
}