From 9778b6e56aea799a2cb5628b653eada0d978cf20 Mon Sep 17 00:00:00 2001 From: alexcastgonz <82875882+pcbeingused333@users.noreply.github.com> Date: Fri, 6 Nov 2026 21:47:11 -0500 Subject: [PATCH] fix(build): ship the arm64 and armv7l sharp binaries in the Linux builds (#1771) The Linux job runs on x64, so npm ci only installs @img/sharp-linux-x64 and @img/sharp-libvips-linux-x64. The arm64 and armv7l AppImages then carry x64 binaries and fail to load sharp on start. Install the arm packages at the versions sharp declares, as the Windows and macOS jobs already do. Fixes #1769 Co-authored-by: Claude Sonnet 5.5 --- .github/workflows/electron.yml | 1 + scripts/install-linux-sharp.cjs | 40 ++++++++++++++++++++ scripts/install-linux-sharp.test.ts | 58 +++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 scripts/install-linux-sharp.cjs create mode 100644 scripts/install-linux-sharp.test.ts diff --git a/.github/workflows/electron.yml b/.github/workflows/electron.yml index 243ae116f..6909a5c1a 100644 --- a/.github/workflows/electron.yml +++ b/.github/workflows/electron.yml @@ -301,6 +301,7 @@ jobs: npm install --force @rollup/rollup-linux-x64-gnu npm install --force @rollup/rollup-linux-arm64-gnu npm install --force @rollup/rollup-linux-arm-gnueabihf + node scripts/install-linux-sharp.cjs # Bundled plugins are counted as downloads in the plugin store, so a # build only fetches them when the pins change. diff --git a/scripts/install-linux-sharp.cjs b/scripts/install-linux-sharp.cjs new file mode 100644 index 000000000..9e32536f4 --- /dev/null +++ b/scripts/install-linux-sharp.cjs @@ -0,0 +1,40 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const { execFileSync } = require("node:child_process"); + +function findPackage(start) { + let directory = path.dirname(start); + while (directory !== path.dirname(directory)) { + const manifest = path.join(directory, "package.json"); + if (fs.existsSync(manifest)) return JSON.parse(fs.readFileSync(manifest)); + directory = path.dirname(directory); + } + throw new Error("Could not locate the installed sharp package manifest"); +} + +// The build runs on x64 but ships arm64 and armv7l too, so npm ci alone +// only installs the x64 binaries. +function linuxSharpPackages(sharpPackage) { + return ["x64", "arm64", "arm"].flatMap((arch) => + [`@img/sharp-linux-${arch}`, `@img/sharp-libvips-linux-${arch}`].map( + (name) => { + const version = sharpPackage.optionalDependencies?.[name]; + if (!version) { + throw new Error(`sharp does not declare ${name} as a dependency`); + } + return `${name}@${version}`; + }, + ), + ); +} + +module.exports = { linuxSharpPackages }; + +if (require.main === module) { + const sharpPackage = findPackage(require.resolve("sharp")); + execFileSync( + "npm", + ["install", "--force", "--no-save", ...linuxSharpPackages(sharpPackage)], + { stdio: "inherit" }, + ); +} diff --git a/scripts/install-linux-sharp.test.ts b/scripts/install-linux-sharp.test.ts new file mode 100644 index 000000000..4aafc8944 --- /dev/null +++ b/scripts/install-linux-sharp.test.ts @@ -0,0 +1,58 @@ +import fs from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const require = createRequire(import.meta.url); +const { linuxSharpPackages } = require("./install-linux-sharp.cjs"); + +const lock = JSON.parse( + fs.readFileSync(path.join(__dirname, "../package-lock.json"), "utf8"), +); +const sharpManifest = lock.packages["node_modules/sharp"]; + +describe("Linux sharp packaging", () => { + // The build runs on x64 but ships arm64 and armv7l too, so npm ci alone + // only installs the x64 binaries and the arm AppImages cannot load sharp. + it("pins the sharp and libvips binaries of every shipped Linux architecture", () => { + const packages = linuxSharpPackages({ + optionalDependencies: { + "@img/sharp-linux-x64": "1.0.0", + "@img/sharp-linux-arm64": "1.0.1", + "@img/sharp-linux-arm": "1.0.2", + "@img/sharp-libvips-linux-x64": "2.0.0", + "@img/sharp-libvips-linux-arm64": "2.0.1", + "@img/sharp-libvips-linux-arm": "2.0.2", + "@img/sharp-win32-x64": "9.9.9", + }, + }); + + expect([...packages].sort()).toEqual([ + "@img/sharp-libvips-linux-arm64@2.0.1", + "@img/sharp-libvips-linux-arm@2.0.2", + "@img/sharp-libvips-linux-x64@2.0.0", + "@img/sharp-linux-arm64@1.0.1", + "@img/sharp-linux-arm@1.0.2", + "@img/sharp-linux-x64@1.0.0", + ]); + }); + + it("only names packages the installed sharp really declares", () => { + const packages = linuxSharpPackages(sharpManifest); + expect(packages).toHaveLength(6); + for (const spec of packages) { + expect(spec).toMatch(/^@img\/sharp-[a-z0-9-]+@\d+\.\d+\.\d+$/); + } + }); + + it("fails loudly when sharp stops declaring one of them", () => { + expect(() => + linuxSharpPackages({ + optionalDependencies: { + "@img/sharp-linux-x64": "1.0.0", + "@img/sharp-libvips-linux-x64": "2.0.0", + }, + }), + ).toThrow(/sharp-linux-arm64/); + }); +});