|
| 1 | +import type { createSeaSignTool as createSeaSignToolType } from '@electron/windows-sign'; |
| 2 | +import path from 'path'; |
| 3 | +import semver from 'semver'; |
| 4 | +import fs from 'fs-extra'; |
| 5 | + |
| 6 | +import { SquirrelWindowsOptions } from './options'; |
| 7 | + |
| 8 | +const VENDOR_PATH = path.join(__dirname, '..', 'vendor'); |
| 9 | +const ORIGINAL_SIGN_TOOL_PATH = path.join(VENDOR_PATH, 'signtool.exe'); |
| 10 | +const BACKUP_SIGN_TOOL_PATH = path.join(VENDOR_PATH, 'signtool-original.exe'); |
| 11 | +const SIGN_LOG_PATH = path.join(VENDOR_PATH, 'electron-windows-sign.log'); |
| 12 | + |
| 13 | +/** |
| 14 | + * This method uses @electron/windows-sign to create a fake signtool.exe |
| 15 | + * that can be called by Squirrel - but then just calls @electron/windows-sign |
| 16 | + * to actually perform the signing. |
| 17 | + * |
| 18 | + * That's useful for users who need a high degree of customization of the signing |
| 19 | + * process but still want to use @electron/windows-installer. |
| 20 | + */ |
| 21 | +export async function createSignTool(options: SquirrelWindowsOptions): Promise<void> { |
| 22 | + if (!options.windowsSign) { |
| 23 | + throw new Error('Signtool should only be created if windowsSign options are set'); |
| 24 | + } |
| 25 | + |
| 26 | + const createSeaSignTool = await getCreateSeaSignTool(); |
| 27 | + |
| 28 | + await resetSignTool(); |
| 29 | + await fs.remove(SIGN_LOG_PATH); |
| 30 | + |
| 31 | + // Make a backup of signtool.exe |
| 32 | + await fs.copy(ORIGINAL_SIGN_TOOL_PATH, BACKUP_SIGN_TOOL_PATH, { overwrite: true }); |
| 33 | + |
| 34 | + // Create a new signtool.exe using @electron/windows-sign |
| 35 | + await createSeaSignTool({ |
| 36 | + path: ORIGINAL_SIGN_TOOL_PATH, |
| 37 | + windowsSign: options.windowsSign |
| 38 | + }); |
| 39 | +} |
| 40 | + |
| 41 | +/** |
| 42 | + * Ensure that signtool.exe is actually the "real" signtool.exe, not our |
| 43 | + * fake substitute. |
| 44 | + */ |
| 45 | +export async function resetSignTool() { |
| 46 | + if (fs.existsSync(BACKUP_SIGN_TOOL_PATH)) { |
| 47 | + // Reset the backup of signtool.exe |
| 48 | + await fs.copy(BACKUP_SIGN_TOOL_PATH, ORIGINAL_SIGN_TOOL_PATH, { overwrite: true }); |
| 49 | + await fs.remove(BACKUP_SIGN_TOOL_PATH); |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +/** |
| 54 | + * @electron/windows-installer only requires Node.js >= 8.0.0. |
| 55 | + * @electron/windows-sign requires Node.js >= 16.0.0. |
| 56 | + * @electron/windows-sign's "fake signtool.exe" feature requires |
| 57 | + * Node.js >= 20.0.0, the first version to contain the "single |
| 58 | + * executable" feature with proper support. |
| 59 | + * |
| 60 | + * Since this is overall a very niche feature and only benefits |
| 61 | + * consumers with rather advanced codesigning needs, we did not |
| 62 | + * want to make Node.js v18 a hard requirement for @electron/windows-installer. |
| 63 | + * |
| 64 | + * Instead, @electron/windows-sign is an optional dependency - and |
| 65 | + * if it didn't install, we'll throw a useful error here. |
| 66 | + * |
| 67 | + * @returns |
| 68 | + */ |
| 69 | +async function getCreateSeaSignTool(): Promise<typeof createSeaSignToolType> { |
| 70 | + try { |
| 71 | + const { createSeaSignTool } = await import('@electron/windows-sign'); |
| 72 | + return createSeaSignTool; |
| 73 | + } catch(error) { |
| 74 | + let message = 'In order to use windowsSign options, @electron/windows-sign must be installed as a dependency.'; |
| 75 | + |
| 76 | + if (semver.lte(process.version, '20.0.0')) { |
| 77 | + message += ` You are currently using Node.js ${process.version}. Please upgrade to Node.js 19 or later and reinstall all dependencies to ensure that @electron/windows-sign is available.`; |
| 78 | + } else { |
| 79 | + message += ` ${error}`; |
| 80 | + } |
| 81 | + |
| 82 | + throw new Error(message); |
| 83 | + } |
| 84 | +} |
0 commit comments