trilium/scripts/update-flake-electron.spec.ts
Elian Doran ae34d8ad0d
build(nix): pin Electron 44, which drops armv7l and bundled ANGLE
The Update Nix flake workflow has been failing since the bump to Electron
44 landed on main, for two independent reasons.

parseShasums treated every entry of ELECTRON_PLATFORM_TAGS as mandatory,
and Electron 44 ships no linux-armv7l assets at all — 43.4.1 lists seven
armv7l entries in SHASUMS256.txt, 44.0.0 lists none:

  Error: SHASUMS256.txt for Electron 44.0.0 is missing:
  electron-v44.0.0-linux-armv7l.zip

Drop armv7l-linux from the table, and x86_64-darwin with it: the nixpkgs
rev in flake.lock (6d12004) lists only x86_64-linux, armv7l-linux,
aarch64-linux and aarch64-darwin in generic.nix's `tags`, so that hash
could never have been read. Keep the hard throw on a missing asset —
shrinking the platform set is a decision for a human, not something to
paper over by silently pinning fewer systems.

Refreshing the hashes then got the job as far as the verification step,
which failed on its own:

  patchelf: missing filename

The nixpkgs Linux builder rewrites the rpath of Electron's ANGLE
libraries with an unguarded `patchelf ... lib*GL*`. Electron 43.4.1
shipped libEGL.so and libGLESv2.so; 44.0.0 links ANGLE into the main
binary and ships neither, leaving only libffmpeg.so, libvk_swiftshader.so
and libvulkan.so.1, so the glob expands to nothing. There is nothing
upstream to pull — nixpkgs master packages Electron 41-43 only and still
has the unguarded glob — so override that one command to tolerate an
empty match, guarded by a throwIf that fails loudly if the upstream text
changes rather than silently no-opping. Linux only, because the Darwin
branch of the builder defines no postFixup.

`nix build --no-link '.#electron' '.#electron.headers'` now succeeds and
the result reports v44.0.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 00:05:17 +03:00

119 lines
4.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
parseShasums,
readPinnedElectronVersion,
readWantedElectronVersion,
rewriteFlake
} from "./update-flake-electron";
/** Trimmed to the shape the script anchors on, indentation included. */
const FLAKE = `{
outputs =
{ self }:
let
pinnedElectronVersion = "43.2.0";
pinnedElectronHashes = {
x86_64-linux = "old-x64";
aarch64-linux = "old-arm64";
aarch64-darwin = "old-darwin-arm64";
headers = "old-headers";
};
nodejs = pkgs.nodejs_24;
in
{ };
}
`;
const HASHES = {
"x86_64-linux": "x64",
"aarch64-linux": "arm64",
"aarch64-darwin": "darwin-arm64",
"headers": "headers"
};
const desktopPackageJson = (electron: string) => JSON.stringify({ devDependencies: { electron } });
describe("readWantedElectronVersion", () => {
it("reads an exact pin", () => {
expect(readWantedElectronVersion(desktopPackageJson("43.3.0"))).toBe("43.3.0");
});
it("rejects ranges and missing entries, which cannot be pinned to one build", () => {
expect(() => readWantedElectronVersion(desktopPackageJson("^43.3.0"))).toThrow(/exact/);
expect(() => readWantedElectronVersion(desktopPackageJson("latest"))).toThrow(/exact/);
expect(() => readWantedElectronVersion("{}")).toThrow(/devDependencies/);
});
});
describe("readPinnedElectronVersion", () => {
it("reads the flake's pin, and fails loudly when the binding is gone", () => {
expect(readPinnedElectronVersion(FLAKE)).toBe("43.2.0");
expect(() => readPinnedElectronVersion("{ }")).toThrow(/pinnedElectronVersion/);
});
});
describe("parseShasums", () => {
const shasums = [
`${"a".repeat(64)} *chromedriver-v43.3.0-linux-x64.zip`,
`${"1".repeat(64)} *electron-v43.3.0-linux-x64.zip`,
`${"2".repeat(64)} *electron-v43.3.0-linux-armv7l.zip`,
`${"3".repeat(64)} *electron-v43.3.0-linux-arm64.zip`,
`${"4".repeat(64)} *electron-v43.3.0-darwin-x64.zip`,
`${"5".repeat(64)} *electron-v43.3.0-darwin-arm64.zip`,
`${"b".repeat(64)} *electron-v43.3.0-linux-x64-symbols.zip`,
`${"c".repeat(64)} *electron-v43.3.0-mas-x64.zip`,
""
].join("\n");
it("picks the platform zips it pins and ignores the rest of the release", () => {
expect(parseShasums(shasums, "43.3.0")).toEqual({
"x86_64-linux": "1".repeat(64),
"aarch64-linux": "3".repeat(64),
"aarch64-darwin": "5".repeat(64)
});
});
it("names the assets it could not find rather than emitting a partial set", () => {
expect(() => parseShasums(shasums, "43.4.0")).toThrow(/electron-v43\.4\.0-linux-x64\.zip/);
// Only the one that is genuinely absent — the two it did resolve stay unmentioned.
const withoutArm = shasums.replace(/^.*darwin-arm64\.zip$/m, "");
const onlyDarwinArm = /^(?!.*linux-x64).*darwin-arm64\.zip/s;
expect(() => parseShasums(withoutArm, "43.3.0")).toThrow(onlyDarwinArm);
});
});
describe("rewriteFlake", () => {
const rewritten = rewriteFlake(FLAKE, "43.3.0", HASHES);
it("replaces both bindings, keeping indentation and the surrounding expression", () => {
expect(rewritten).toBe(`{
outputs =
{ self }:
let
pinnedElectronVersion = "43.3.0";
pinnedElectronHashes = {
x86_64-linux = "x64";
aarch64-linux = "arm64";
aarch64-darwin = "darwin-arm64";
headers = "headers";
};
nodejs = pkgs.nodejs_24;
in
{ };
}
`);
});
it("is stable when re-run against its own output", () => {
expect(rewriteFlake(rewritten, "43.3.0", HASHES)).toBe(rewritten);
});
it("refuses to touch a flake it no longer recognises", () => {
const versionOnly = `pinnedElectronVersion = "43.2.0";`;
expect(() => rewriteFlake(versionOnly, "43.3.0", HASHES)).toThrow(/pinnedElectronHashes/);
expect(() => rewriteFlake("{ }", "43.3.0", HASHES)).toThrow(/pinnedElectronVersion/);
});
});