diff --git a/packages/maker/squirrel/spec/MakerSquirrel.spec.ts b/packages/maker/squirrel/spec/MakerSquirrel.spec.ts new file mode 100644 index 0000000000..4b98f38d9d --- /dev/null +++ b/packages/maker/squirrel/spec/MakerSquirrel.spec.ts @@ -0,0 +1,59 @@ +import os from 'node:os'; +import path from 'node:path'; + +import { createWindowsInstaller } from 'electron-winstaller'; +import fs from 'fs-extra'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { MakerSquirrel } from '../src/MakerSquirrel'; + +vi.mock(import('electron-winstaller'), async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + createWindowsInstaller: vi.fn().mockResolvedValue(undefined), + }; +}); + +describe('MakerSquirrel', () => { + let dir: string; + let makeDir: string; + + const makeWithVersion = async (version: string) => { + const maker = new MakerSquirrel({}, []); + return maker.make({ + dir, + makeDir, + targetArch: process.arch, + appName: 'My Test App', + forgeConfig: { packagerConfig: {} }, + packageJSON: { version }, + } as Parameters[0]); + }; + + beforeEach(async () => { + vi.mocked(createWindowsInstaller).mockClear(); + dir = path.resolve(os.tmpdir(), `forge-squirrel-test-${Date.now()}`); + makeDir = path.resolve(os.tmpdir(), `forge-squirrel-make-${Date.now()}`); + await fs.ensureDir(dir); + await fs.writeJson(path.resolve(dir, 'package.json'), { + name: 'my-app', + version: '1.0.1-0', + }); + }); + + it('normalizes semver prerelease versions to a 4-part version for Squirrel', async () => { + await makeWithVersion('1.0.1-0'); + + expect(createWindowsInstaller).toHaveBeenCalledTimes(1); + const config = vi.mocked(createWindowsInstaller).mock.calls[0][0]; + expect(config.version).toEqual('1.0.1.0'); + }); + + it('keeps plain 3-part versions working and appends the revision digit', async () => { + await makeWithVersion('1.2.3'); + + const config = vi.mocked(createWindowsInstaller).mock.calls[0][0]; + expect(config.version).toEqual('1.2.3.0'); + }); +}); diff --git a/packages/maker/squirrel/src/MakerSquirrel.ts b/packages/maker/squirrel/src/MakerSquirrel.ts index cdb9017b14..bcf92f1c88 100644 --- a/packages/maker/squirrel/src/MakerSquirrel.ts +++ b/packages/maker/squirrel/src/MakerSquirrel.ts @@ -45,6 +45,10 @@ export default class MakerSquirrel extends MakerBase { await fs.copy(dir, tmpFolder); try { + // Squirrel.Windows (NuGet) only accepts 4-part version numbers, so a + // semver prerelease like 1.0.1-0 must be normalized before it reaches + // the installer. + const version = this.normalizeWindowsVersion(packageJSON.version); const winstallerConfig: ElectronWinstallerOptions = { name: typeof packageJSON.name === 'string' @@ -55,13 +59,14 @@ export default class MakerSquirrel extends MakerBase { exe: `${forgeConfig.packagerConfig.executableName || appName}.exe`, setupExe: `${appName}-${packageJSON.version} Setup.exe`, ...this.config, + version, appDirectory: tmpFolder, outputDirectory: outPath, }; await createWindowsInstaller(winstallerConfig); - const nupkgVersion = convertVersion(packageJSON.version); + const nupkgVersion = convertVersion(version); const artifacts = [ path.resolve(outPath, 'RELEASES'),