From 785cbed44411176a84c03015011dce07a28f6fc2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 00:55:15 +0000 Subject: [PATCH] feat: error for win32/ia32 and linux/armv7l downloads of Electron >= 44 Electron 44 dropped support for 32-bit Windows (win32/ia32) and armv7l Linux. The first releases without these artifacts are v44.0.0-alpha.4 and v45.0.0-nightly.20260714, so requesting them for newer versions previously failed with an unhelpful HTTP 404 from GitHub releases. Throw a descriptive error before attempting the download instead, scoped to official downloads only - custom mirrors may legitimately host their own 32-bit builds. Electron <= 43 (supported until January 2027) and cached artifacts are unaffected. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KEEZg64JKFrSDAc7u4V4Na --- src/index.ts | 17 +++++++++++ src/types.ts | 8 ++--- src/utils.ts | 34 +++++++++++++++++++++ test/index.spec.ts | 66 +++++++++++++++++++++++++++++++++++++++++ test/utils.spec.ts | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 93c77407..dc4e87ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ import { getHostArch, getNodeArch, ensureIsTruthyString, + isOfficialDropped32BitDownload, isOfficialLinuxIA32Download, mkdtemp, doesCallerOwnTemporaryOutput, @@ -202,6 +203,22 @@ export async function downloadArtifact( console.warn('For more info: https://electronjs.org/blog/linux-32bit-support'); } + if ( + !details.isGeneric && + isOfficialDropped32BitDownload( + details.platform, + details.arch, + details.version, + details.mirrorOptions, + ) + ) { + throw new Error( + `Electron ${details.version} does not have a published ${details.platform}/${details.arch} artifact. ` + + 'Electron 44 and newer no longer publish win32/ia32 or linux/armv7l builds; ' + + 'the last release line with 32-bit support is Electron 43, which is supported until January 2027.', + ); + } + return await withTempDirectoryIn( details.tempDirectory, async (tempFolder) => { diff --git a/src/types.ts b/src/types.ts index 5a97b977..4f5e239b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -193,9 +193,9 @@ export type ElectronPlatformArtifactDetails = { platform: string; /** * The target artifact architecture. These are Node-style architecture names, for example: - * * `ia32` + * * `ia32` (Electron 43 and earlier) * * `x64` - * * `armv7l` + * * `armv7l` (Electron 43 and earlier) * * @see Node.js {@link https://nodejs.org/api/process.html#processarch | process.arch} docs */ @@ -252,9 +252,9 @@ export type ElectronPlatformArtifactDetailsWithDefaults = Omit< platform?: string; /** * The target artifact architecture. These are Node-style architecture names, for example: - * * `ia32` + * * `ia32` (Electron 43 and earlier) * * `x64` - * * `armv7l` + * * `armv7l` (Electron 43 and earlier) * * @see Node.js {@link https://nodejs.org/api/process.html#processarch | process.arch} docs */ diff --git a/src/utils.ts b/src/utils.ts index cf655300..302931ef 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,7 @@ import childProcess from 'node:child_process'; import fs from 'graceful-fs'; import os from 'node:os'; import path from 'node:path'; +import semver from 'semver'; import { ElectronDownloadCacheMode, @@ -116,6 +117,39 @@ export function isOfficialLinuxIA32Download( ); } +// Electron 44 dropped support for 32-bit Windows (win32/ia32) and for +// armv7l Linux. The last official releases to ship these artifacts are +// v44.0.0-alpha.3 on the release channel and v45.0.0-nightly.20260713 on +// the nightly channel. +const FIRST_RELEASE_WITHOUT_32_BIT_SUPPORT = '44.0.0-alpha.4'; +const FIRST_NIGHTLY_WITHOUT_32_BIT_SUPPORT = '45.0.0-nightly.20260714'; + +export function isOfficialDropped32BitDownload( + platform: string, + arch: string, + version: string, + mirrorOptions?: object, +): boolean { + // Custom mirrors may legitimately host their own builds for these platforms + if (typeof mirrorOptions !== 'undefined') { + return false; + } + + if (!((platform === 'win32' && arch === 'ia32') || (platform === 'linux' && arch === 'armv7l'))) { + return false; + } + + const parsedVersion = semver.parse(version); + if (parsedVersion === null) { + return false; + } + + const firstVersionWithoutArtifacts = parsedVersion.prerelease.includes('nightly') + ? FIRST_NIGHTLY_WITHOUT_32_BIT_SUPPORT + : FIRST_RELEASE_WITHOUT_32_BIT_SUPPORT; + return semver.gte(parsedVersion, firstVersionWithoutArtifacts); +} + /** * Find the value of a environment variable which may or may not have the * prefix, in a case-insensitive manner. diff --git a/test/index.spec.ts b/test/index.spec.ts index cc215ce4..9cbeb972 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -271,6 +271,72 @@ describe('Public API', () => { expect(await util.promisify(fs.readFile)(driverPath2, 'utf8')).toEqual('cached content'); }); + describe('dropped 32-bit support', () => { + it('should throw for official win32/ia32 downloads of Electron >= 44.0.0-alpha.4', async () => { + await expect( + downloadArtifact({ + cacheRoot, + downloader, + artifactName: 'electron', + version: '44.0.0', + platform: 'win32', + arch: 'ia32', + }), + ).rejects.toThrow('Electron v44.0.0 does not have a published win32/ia32 artifact.'); + }); + + it('should throw for official linux/armv7l downloads of Electron >= 44.0.0-alpha.4', async () => { + await expect( + downloadArtifact({ + cacheRoot, + downloader, + artifactName: 'electron', + version: '44.0.0-alpha.4', + platform: 'linux', + arch: 'armv7l', + }), + ).rejects.toThrow( + 'Electron v44.0.0-alpha.4 does not have a published linux/armv7l artifact.', + ); + }); + + it('should not throw for Electron 43 and earlier', async () => { + const zipPath = await downloadArtifact({ + cacheRoot, + downloader, + artifactName: 'electron', + version: '2.0.9', + platform: 'linux', + arch: 'armv7l', + }); + expect(fs.existsSync(zipPath)).toEqual(true); + expect(path.basename(zipPath)).toEqual('electron-v2.0.9-linux-armv7l.zip'); + }); + + it('should not throw when a custom mirror is used', async () => { + const zipPath = await downloadArtifact({ + cacheRoot, + unsafelyDisableChecksums: true, + artifactName: 'electron', + version: '44.0.0', + platform: 'win32', + arch: 'ia32', + mirrorOptions: { + mirror: 'https://mymirror.example.com/', + }, + downloader: { + async download(url: string, targetPath: string): Promise { + expect(url).toEqual( + 'https://mymirror.example.com/v44.0.0/electron-v44.0.0-win32-ia32.zip', + ); + await util.promisify(fs.writeFile)(targetPath, 'faked from mirror'); + }, + }, + }); + expect(await util.promisify(fs.readFile)(zipPath, 'utf8')).toEqual('faked from mirror'); + }); + }); + describe('tempDirectory', () => { let customTemp: string; diff --git a/test/utils.spec.ts b/test/utils.spec.ts index 4ef83291..078f8f8f 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -8,6 +8,7 @@ import { withTempDirectory, getHostArch, ensureIsTruthyString, + isOfficialDropped32BitDownload, isOfficialLinuxIA32Download, getEnv, setEnv, @@ -155,6 +156,78 @@ describe('utils', () => { }); }); + describe('isOfficialDropped32BitDownload()', () => { + it('should be true for win32/ia32 from v44.0.0-alpha.4 onward', () => { + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v44.0.0-alpha.4')).toEqual(true); + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v44.0.0-beta.1')).toEqual(true); + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v44.0.0')).toEqual(true); + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v45.1.2')).toEqual(true); + }); + + it('should be true for linux/armv7l from v44.0.0-alpha.4 onward', () => { + expect(isOfficialDropped32BitDownload('linux', 'armv7l', 'v44.0.0-alpha.4')).toEqual(true); + expect(isOfficialDropped32BitDownload('linux', 'armv7l', 'v44.0.0')).toEqual(true); + expect(isOfficialDropped32BitDownload('linux', 'armv7l', 'v45.1.2')).toEqual(true); + }); + + it('should be false for the Electron 44 prereleases that still shipped 32-bit builds', () => { + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v44.0.0-alpha.1')).toEqual(false); + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v44.0.0-alpha.3')).toEqual(false); + expect(isOfficialDropped32BitDownload('linux', 'armv7l', 'v44.0.0-alpha.3')).toEqual(false); + }); + + it('should be false for Electron 43 and earlier', () => { + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v43.0.0')).toEqual(false); + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v43.5.1')).toEqual(false); + expect(isOfficialDropped32BitDownload('linux', 'armv7l', 'v43.5.1')).toEqual(false); + expect(isOfficialDropped32BitDownload('linux', 'armv7l', 'v2.0.9')).toEqual(false); + }); + + it('should be true for nightlies from v45.0.0-nightly.20260714 onward', () => { + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v45.0.0-nightly.20260714')).toEqual( + true, + ); + expect(isOfficialDropped32BitDownload('linux', 'armv7l', 'v45.0.0-nightly.20260714')).toEqual( + true, + ); + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v46.0.0-nightly.20270101')).toEqual( + true, + ); + }); + + it('should be false for nightlies that still shipped 32-bit builds', () => { + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v44.0.0-nightly.20260501')).toEqual( + false, + ); + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'v45.0.0-nightly.20260713')).toEqual( + false, + ); + expect(isOfficialDropped32BitDownload('linux', 'armv7l', 'v45.0.0-nightly.20260713')).toEqual( + false, + ); + }); + + it('should be false if mirrorOptions specified', () => { + expect( + isOfficialDropped32BitDownload('win32', 'ia32', 'v44.0.0', { mirror: 'mymirror' }), + ).toEqual(false); + expect( + isOfficialDropped32BitDownload('linux', 'armv7l', 'v44.0.0', { mirror: 'mymirror' }), + ).toEqual(false); + }); + + it('should be false for platform/arch combinations that were never dropped', () => { + expect(isOfficialDropped32BitDownload('linux', 'ia32', 'v44.0.0')).toEqual(false); + expect(isOfficialDropped32BitDownload('win32', 'armv7l', 'v44.0.0')).toEqual(false); + expect(isOfficialDropped32BitDownload('win32', 'x64', 'v44.0.0')).toEqual(false); + expect(isOfficialDropped32BitDownload('darwin', 'arm64', 'v44.0.0')).toEqual(false); + }); + + it('should be false for unparseable versions', () => { + expect(isOfficialDropped32BitDownload('win32', 'ia32', 'vnot-a-version')).toEqual(false); + }); + }); + describe('getEnv()', () => { const [prefix, envName] = ['TeSt_EnV_vAr_', 'eNv_Key']; const prefixEnvName = `${prefix}${envName}`;