Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a vendor directory option to use a custom vendor #526

Merged
merged 5 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function checkIfCommandExists(command: string): Promise<boolean> {
/**
* This package's main function, which creates a Squirrel.Windows executable
* installer and optionally code-signs the output.
*
*
* @param options Options for installer generation and signing
* @see {@link https://github.com/Squirrel/Squirrel.Windows | Squirrel.Windows}
*/
Expand Down Expand Up @@ -74,7 +74,7 @@ export async function createWindowsInstaller(options: SquirrelWindowsOptions): P
let { appDirectory, outputDirectory, loadingGif } = options;
outputDirectory = path.resolve(outputDirectory || 'installer');

const vendorPath = path.join(__dirname, '..', 'vendor');
const vendorPath = options.vendorDirectory || path.join(__dirname, '..', 'vendor');
const vendorUpdate = path.join(vendorPath, 'Squirrel.exe');
const appUpdate = path.join(appDirectory, 'Squirrel.exe');

Expand Down
18 changes: 11 additions & 7 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export interface SquirrelWindowsOptions {
* The folder path of your Electron app
*/
appDirectory: string;
/**
* The folder path of Squirrel windows
*/
vendorDirectory?: string;
/**
* The folder path to create the .exe installer in.
*
Expand All @@ -23,7 +27,7 @@ export interface SquirrelWindowsOptions {
nuspecTemplate?: string;
/**
* The local path to a `.gif` file to display during install.
*
*
* @defaultValue the bundled {@link https://github.com/electron/windows-installer/blob/main/resources/install-spinner.gif | install-spinner.gif}
*/
loadingGif?: string;
Expand Down Expand Up @@ -92,7 +96,7 @@ export interface SquirrelWindowsOptions {
name?: string;
/**
* The path to an Authenticode Code Signing Certificate.
*
*
* This is a legacy parameter provided for backwards compatibility.
* For more comprehensive support of various codesigning scenarios
* like EV certificates, see the
Expand All @@ -101,7 +105,7 @@ export interface SquirrelWindowsOptions {
certificateFile?: string;
/**
* The password to decrypt the certificate given in `certificateFile`
*
*
* This is a legacy parameter provided for backwards compatibility.
* For more comprehensive support of various codesigning scenarios
* like EV certificates, see the
Expand All @@ -113,7 +117,7 @@ export interface SquirrelWindowsOptions {
*
* Overrides {@link SquirrelWindowsOptions.certificateFile | certificateFile}
* and {@link SquirrelWindowsOptions.certificatePassword | certificatePassword}`.
*
*
* This is a legacy parameter provided for backwards compatibility.
* For more comprehensive support of various codesigning scenarios
* like EV certificates, see the
Expand Down Expand Up @@ -186,13 +190,13 @@ export interface SquirrelWindowsOptions {

/**
* Requires Node.js 18 or newer.
*
*
* Sign your app with `@electron/windows-sign`, allowing for full customization
* of the code-signing process - and supports more complicated scenarios like
* cloud-hosted EV certificates, custom sign pipelines, and per-file overrides.
* It also supports all existing "simple" codesigning scenarios, including
* just passing a certificate file and password.
*
* just passing a certificate file and password.
*
* @see {@link https://github.com/electron/windows-sign | @electron/windows-sign documentation}
* for all possible configuration options.
*/
Expand Down
173 changes: 89 additions & 84 deletions src/sign.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,89 @@
import type { createSeaSignTool as createSeaSignToolType } from '@electron/windows-sign';
import path from 'path';
import semver from 'semver';
import fs from 'fs-extra';

import { SquirrelWindowsOptions } from './options';

const VENDOR_PATH = path.join(__dirname, '..', 'vendor');
const ORIGINAL_SIGN_TOOL_PATH = path.join(VENDOR_PATH, 'signtool.exe');
const BACKUP_SIGN_TOOL_PATH = path.join(VENDOR_PATH, 'signtool-original.exe');
const SIGN_LOG_PATH = path.join(VENDOR_PATH, 'electron-windows-sign.log');

/**
* This method uses @electron/windows-sign to create a fake signtool.exe
* that can be called by Squirrel - but then just calls @electron/windows-sign
* to actually perform the signing.
*
* That's useful for users who need a high degree of customization of the signing
* process but still want to use @electron/windows-installer.
*/
export async function createSignTool(options: SquirrelWindowsOptions): Promise<void> {
if (!options.windowsSign) {
throw new Error('Signtool should only be created if windowsSign options are set');
}

const createSeaSignTool = await getCreateSeaSignTool();

await resetSignTool();
await fs.remove(SIGN_LOG_PATH);

// Make a backup of signtool.exe
await fs.copy(ORIGINAL_SIGN_TOOL_PATH, BACKUP_SIGN_TOOL_PATH, { overwrite: true });

// Create a new signtool.exe using @electron/windows-sign
await createSeaSignTool({
path: ORIGINAL_SIGN_TOOL_PATH,
windowsSign: options.windowsSign
});
}

/**
* Ensure that signtool.exe is actually the "real" signtool.exe, not our
* fake substitute.
*/
export async function resetSignTool() {
if (fs.existsSync(BACKUP_SIGN_TOOL_PATH)) {
// Reset the backup of signtool.exe
await fs.copy(BACKUP_SIGN_TOOL_PATH, ORIGINAL_SIGN_TOOL_PATH, { overwrite: true });
await fs.remove(BACKUP_SIGN_TOOL_PATH);
}
}

/**
* @electron/windows-installer only requires Node.js >= 8.0.0.
* @electron/windows-sign requires Node.js >= 16.0.0.
* @electron/windows-sign's "fake signtool.exe" feature requires
* Node.js >= 20.0.0, the first version to contain the "single
* executable" feature with proper support.
*
* Since this is overall a very niche feature and only benefits
* consumers with rather advanced codesigning needs, we did not
* want to make Node.js v18 a hard requirement for @electron/windows-installer.
*
* Instead, @electron/windows-sign is an optional dependency - and
* if it didn't install, we'll throw a useful error here.
*
* @returns
*/
async function getCreateSeaSignTool(): Promise<typeof createSeaSignToolType> {
try {
const { createSeaSignTool } = await import('@electron/windows-sign');
return createSeaSignTool;
} catch(error) {
let message = 'In order to use windowsSign options, @electron/windows-sign must be installed as a dependency.';

if (semver.lte(process.version, '20.0.0')) {
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.`;
} else {
message += ` ${error}`;
}

throw new Error(message);
}
}
import type { createSeaSignTool as createSeaSignToolType } from '@electron/windows-sign';
import path from 'path';
import semver from 'semver';
import fs from 'fs-extra';

import { SquirrelWindowsOptions } from './options';

let VENDOR_PATH: string;
let ORIGINAL_SIGN_TOOL_PATH: string;
let BACKUP_SIGN_TOOL_PATH: string;
let SIGN_LOG_PATH: string;

/**
* This method uses @electron/windows-sign to create a fake signtool.exe
* that can be called by Squirrel - but then just calls @electron/windows-sign
* to actually perform the signing.
*
* That's useful for users who need a high degree of customization of the signing
* process but still want to use @electron/windows-installer.
*/
export async function createSignTool(options: SquirrelWindowsOptions): Promise<void> {
if (!options.windowsSign) {
throw new Error('Signtool should only be created if windowsSign options are set');
}

VENDOR_PATH = options.vendorDirectory || path.join(__dirname, '..', 'vendor');
ORIGINAL_SIGN_TOOL_PATH = path.join(VENDOR_PATH, 'signtool.exe');
BACKUP_SIGN_TOOL_PATH = path.join(VENDOR_PATH, 'signtool-original.exe');
SIGN_LOG_PATH = path.join(VENDOR_PATH, 'electron-windows-sign.log');

const createSeaSignTool = await getCreateSeaSignTool();

await resetSignTool();
await fs.remove(SIGN_LOG_PATH);

// Make a backup of signtool.exe
await fs.copy(ORIGINAL_SIGN_TOOL_PATH, BACKUP_SIGN_TOOL_PATH, { overwrite: true });

// Create a new signtool.exe using @electron/windows-sign
await createSeaSignTool({
path: ORIGINAL_SIGN_TOOL_PATH,
windowsSign: options.windowsSign
});
}

/**
* Ensure that signtool.exe is actually the "real" signtool.exe, not our
* fake substitute.
*/
export async function resetSignTool() {
if (fs.existsSync(BACKUP_SIGN_TOOL_PATH)) {
// Reset the backup of signtool.exe
await fs.copy(BACKUP_SIGN_TOOL_PATH, ORIGINAL_SIGN_TOOL_PATH, { overwrite: true });
await fs.remove(BACKUP_SIGN_TOOL_PATH);
}
}

/**
* @electron/windows-installer only requires Node.js >= 8.0.0.
* @electron/windows-sign requires Node.js >= 16.0.0.
* @electron/windows-sign's "fake signtool.exe" feature requires
* Node.js >= 20.0.0, the first version to contain the "single
* executable" feature with proper support.
*
* Since this is overall a very niche feature and only benefits
* consumers with rather advanced codesigning needs, we did not
* want to make Node.js v18 a hard requirement for @electron/windows-installer.
*
* Instead, @electron/windows-sign is an optional dependency - and
* if it didn't install, we'll throw a useful error here.
*
* @returns
*/
async function getCreateSeaSignTool(): Promise<typeof createSeaSignToolType> {
try {
const { createSeaSignTool } = await import('@electron/windows-sign');
return createSeaSignTool;
} catch(error) {
let message = 'In order to use windowsSign options, @electron/windows-sign must be installed as a dependency.';

if (semver.lte(process.version, '20.0.0')) {
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.`;
} else {
message += ` ${error}`;
}

throw new Error(message);
}
}