mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-21 22:32:22 +03:00
v3.8.49 shipped with ZERO release assets. v3.8.48 had 16. Every gate was green.
Measured from run 30503231362, not inferred:
Build Electron (windows) ....... success
Build Electron (macos-intel) ... success
Build Electron (macos-arm64) ... success
Build Electron (linux) ......... failure
Create Release ................. skipped
Publish to npm ................. skipped
The linux leg died 8 min into "Creating an optimized production build" with
"The runner has received a shutdown signal" and no exit code, on runner
`GitHub Actions 1000378558` — github-hosted, so this was the VM being
reclaimed, not a Node heap error. Reproduced on a 32 GB machine from the exact
tag commit: the same build succeeds and peaks past 18 GB.
Three independent defects compounded, one fix each:
1. Turbopack allocates natively (Rust, off the V8 heap), so the existing
--max_old_space_size=6144 does not bound it. The project already documents
the webpack fallback as the escape hatch for RAM-constrained machines
(docs/reference/ENVIRONMENT.md, #6409), and nightly-compat already applies
it for the same reason on Node 26 (#8090). The linux leg now selects it;
Windows/macOS keep Turbopack since they build fine and it is faster.
2. `release` gated on `needs: [validate, build]` with no `if:`, so ONE failing
leg skipped it and discarded the three artifacts that DID build — 1.7 GB,
still retained — plus the source archives, which depend on no build at all.
Fail-closed made a partial failure look total. It is now fail-partial:
attach what exists, still requiring `validate` to have passed.
3. Nothing asserted the release HAS assets, so 16 binaries and 0 binaries were
indistinguishable to CI. New `verify-desktop-assets` job asserts one asset
per platform and fails loudly.
The check is a separate job on purpose: failing inside `release` would cascade
into `publish-npm` (`needs: [validate, release]`) and block the npm channel
over a desktop-only gap. Now the assets attach, npm still publishes, and an
incomplete desktop channel is a red job instead of silence.
TDD: 3 of the 4 new assertions fail on the tip of release/v3.8.50 and pass
with this change. The 4th asserts a negative (publish-npm must not gain a
dependency on the asset check) and guards against future regression rather
than reproducing the bug.
node --import tsx/esm --test tests/unit/electron-release-desktop-channel-8949.test.ts
# 4 pass, 0 fail (base: 1 pass, 3 fail)
check:workflows --ratchet → 189 findings, baseline 190, no regression
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
139 lines
5.7 KiB
TypeScript
139 lines
5.7 KiB
TypeScript
/**
|
|
* Regression guard for the v3.8.49 desktop channel, which shipped with ZERO assets
|
|
* while every gate stayed green.
|
|
*
|
|
* What actually happened, measured from the run (30503231362) and not inferred:
|
|
*
|
|
* Build Electron (windows) ....... success
|
|
* Build Electron (macos-intel) ... success
|
|
* Build Electron (macos-arm64) ... success
|
|
* Build Electron (linux) ......... failure — "The runner has received a shutdown
|
|
* signal", 8 min into "Creating an optimized
|
|
* production build", on runner `GitHub Actions
|
|
* 1000378558` (github-hosted)
|
|
* Create Release ................. skipped
|
|
* Publish to npm ................. skipped
|
|
*
|
|
* Three independent defects compounded:
|
|
*
|
|
* 1. Turbopack's production build allocates natively (Rust, off the V8 heap), so
|
|
* `--max_old_space_size` does not bound it. On this module graph it outgrows what
|
|
* the hosted runner can give and the VM is reclaimed mid-compile — the same wall
|
|
* nightly-compat hit on Node 26 (#8090) and the same documented remedy applies:
|
|
* the webpack fallback (OMNIROUTE_USE_TURBOPACK=0, docs/reference/ENVIRONMENT.md,
|
|
* #6409). The identical build passes on a 32 GB machine, peaking past 14 GB.
|
|
*
|
|
* 2. `release` gated on `needs: [validate, build]` with no `if:`, so ONE failing
|
|
* matrix leg skipped it and discarded the three artifacts that DID build (1.7 GB,
|
|
* retained for 90 days and recoverable by hand) plus the source archives, which
|
|
* depend on no build at all. Fail-closed made a partial failure look total.
|
|
*
|
|
* 3. Nothing anywhere asserted the release HAS assets. A release with 16 binaries and
|
|
* a release with 0 were indistinguishable to CI.
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const workflowPath = path.join(repoRoot, ".github/workflows/electron-release.yml");
|
|
|
|
function readWorkflow(): string {
|
|
return fs.readFileSync(workflowPath, "utf-8");
|
|
}
|
|
|
|
/** Job keys live at 2-space indentation under `jobs:`; the next such key ends the block. */
|
|
function extractJobBlock(yaml: string, jobName: string): string {
|
|
const lines = yaml.split("\n");
|
|
const startIdx = lines.findIndex((l) => new RegExp(`^ ${jobName}:\\s*$`).test(l));
|
|
assert.ok(startIdx !== -1, `electron-release.yml must define the ${jobName} job`);
|
|
const block: string[] = [];
|
|
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
if (/^ \S/.test(lines[i])) break;
|
|
block.push(lines[i]);
|
|
}
|
|
return block.join("\n");
|
|
}
|
|
|
|
test("the linux leg builds with the webpack fallback, not Turbopack", () => {
|
|
const buildBlock = extractJobBlock(readWorkflow(), "build");
|
|
|
|
assert.match(buildBlock, /npm run build/, "the build job must still run the production build");
|
|
assert.match(
|
|
buildBlock,
|
|
/OMNIROUTE_USE_TURBOPACK:/,
|
|
"the build job must pin the bundler explicitly — leaving it to the default is what " +
|
|
"let the linux leg die on the hosted runner and take the whole desktop channel down"
|
|
);
|
|
// The value is per-platform: linux gets webpack ('0'), the platforms that already
|
|
// build fine keep Turbopack. A blanket '0' would slow Windows/macOS for no reason.
|
|
assert.match(
|
|
buildBlock,
|
|
/matrix\.platform == 'linux'[^\n]*'0'/,
|
|
"linux specifically must select the webpack fallback ('0')"
|
|
);
|
|
});
|
|
|
|
test("one failing platform does not discard the platforms that built", () => {
|
|
const releaseBlock = extractJobBlock(readWorkflow(), "release");
|
|
|
|
assert.match(
|
|
releaseBlock,
|
|
/if:\s*\$\{\{\s*!cancelled\(\)/,
|
|
"the release job must run even when a build leg failed — a bare `needs: [.., build]` " +
|
|
"skips it on the first failure and throws away every artifact that succeeded"
|
|
);
|
|
assert.match(
|
|
releaseBlock,
|
|
/needs\.validate\.result == 'success'/,
|
|
"…but it must still require `validate` to have passed, so a bad version never releases"
|
|
);
|
|
assert.match(
|
|
releaseBlock,
|
|
/fail_on_unmatched_files:\s*false/,
|
|
"attaching a partial set requires tolerating globs that match nothing"
|
|
);
|
|
});
|
|
|
|
test("a release with no binaries fails a job instead of passing unnoticed", () => {
|
|
const wf = readWorkflow();
|
|
const verifyBlock = extractJobBlock(wf, "verify-desktop-assets");
|
|
|
|
for (const pattern of ["exe", "dmg", "AppImage", "deb"]) {
|
|
assert.ok(
|
|
verifyBlock.includes(pattern),
|
|
`the asset check must assert a ${pattern} installer is present`
|
|
);
|
|
}
|
|
assert.match(verifyBlock, /exit 1/, "a missing platform must fail the job, not just warn");
|
|
});
|
|
|
|
test("the asset check is a separate job, so it cannot block the npm channel", () => {
|
|
const wf = readWorkflow();
|
|
|
|
// publish-npm gates on `release`. If the assert lived inside `release`, a desktop-only
|
|
// gap would also stop the npm publish — the primary distribution channel.
|
|
const publishBlock = extractJobBlock(wf, "publish-npm");
|
|
assert.match(
|
|
publishBlock,
|
|
/needs:\s*\[validate, release\]/,
|
|
"publish-npm is expected to gate on release; if that changes, revisit this reasoning"
|
|
);
|
|
assert.doesNotMatch(
|
|
publishBlock,
|
|
/verify-desktop-assets/,
|
|
"publish-npm must NOT depend on the desktop asset check — npm must publish even when " +
|
|
"a desktop platform is missing"
|
|
);
|
|
|
|
const releaseBlock = extractJobBlock(wf, "release");
|
|
assert.doesNotMatch(
|
|
releaseBlock,
|
|
/exit 1/,
|
|
"the release job must not fail on missing assets — that would cascade into publish-npm; " +
|
|
"the dedicated verify-desktop-assets job carries that failure instead"
|
|
);
|
|
});
|