Skip to content
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
85 changes: 30 additions & 55 deletions src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,43 @@
import { promisify } from 'node:util';
import {
FinalizePackageTargetsHookFunction,
HookFunction,
HookFunctionErrorCallback,
} from './types.js';
import { FinalizePackageTargetsHookFunction, HookFunction } from './types.js';

export async function promisifyHooks(
hooks: HookFunction[] | FinalizePackageTargetsHookFunction[] | undefined,
args?: unknown[],
) {
if (!hooks || !Array.isArray(hooks)) {
return Promise.resolve();
export async function runHooks(
hooks: HookFunction[] | undefined,
opts: Parameters<HookFunction>[0],
): Promise<void>;
export async function runHooks(
hooks: FinalizePackageTargetsHookFunction[] | undefined,
opts: Parameters<FinalizePackageTargetsHookFunction>[0],
): Promise<void>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function runHooks<T extends (...args: any[]) => any>(
hooks: T[] | undefined,
opts: Parameters<T>[0],
): Promise<void> {
if (hooks === undefined || !Array.isArray(hooks)) {
return;
}

await Promise.all(
hooks.map((hookFn) => promisify(hookFn).apply(promisifyHooks, args)),
);
await Promise.all(hooks.map((hook) => hook(opts)));
}

/**
* By default, the functions are called in parallel (via
* [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)).
* If you need the functions called serially, you can use the `serialHooks` utility function:
*
* ```javascript
* import { packager, serialHooks } from '@electron/packager'
*
* packager({
* // ...
* afterCopy: [serialHooks([
* (buildPath, electronVersion, platform, arch, callback) => {
* setTimeout(() => {
* console.log('first function')
* callback()
* }, 1000)
* },
* (buildPath, electronVersion, platform, arch, callback) => {
* console.log('second function')
* callback()
* }
* ])],
* // ...
* })
* ```
* If you need the functions called serially, you can use the `serialHooks` utility function.
*/
export function serialHooks(hooks: Parameters<typeof promisifyHooks>[0] = []) {
return async function runSerialHook(
...serialHookParams: Parameters<
HookFunction | FinalizePackageTargetsHookFunction
>
) {
const args = Array.prototype.slice.call(
serialHookParams,
0,
-1,
) as Parameters<HookFunction>;
const [done] = Array.prototype.slice.call(serialHookParams, -1) as [
HookFunctionErrorCallback,
];

export function serialHooks(
hooks: HookFunction[],
): (opts: Parameters<HookFunction>[0]) => Promise<void>;
export function serialHooks(
hooks: FinalizePackageTargetsHookFunction[],
): (opts: Parameters<FinalizePackageTargetsHookFunction>[0]) => Promise<void>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function serialHooks<T extends (...args: any[]) => any>(
hooks: T[] = [],
) {
return async function (opts: Parameters<T>[0]): Promise<void> {
for (const hook of hooks) {
await (hook as HookFunction).apply(runSerialHook, args);
await hook(opts);
}

return done();
};
}
21 changes: 11 additions & 10 deletions src/packager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { createDownloadCombos, downloadElectronZip } from './download.js';
import fs from 'graceful-fs';
import { promisifiedGracefulFs } from './util.js';
import { getMetadataFromPackageJSON } from './infer.js';
import { promisifyHooks } from './hooks.js';
import { runHooks } from './hooks.js';
import path from 'node:path';
import {
createPlatformArchPairs,
Expand All @@ -20,7 +20,7 @@ import {
} from './targets.js';
import { extractElectronZip } from './unzip.js';
import { packageUniversalMac } from './universal.js';
import {
import type {
ProcessedOptionsWithSinglePlatformArch,
DownloadOptions,
OfficialArch,
Expand Down Expand Up @@ -155,12 +155,12 @@ export class Packager {
) {
debug(`Extracting ${zipPath} to ${buildDir}`);
await extractElectronZip(zipPath, buildDir);
await promisifyHooks(this.opts.afterExtract, [
buildDir,
comboOpts.electronVersion,
comboOpts.platform,
comboOpts.arch,
]);
await runHooks(this.opts.afterExtract, {
buildPath: buildDir,
electronVersion: comboOpts.electronVersion,
platform: comboOpts.platform,
arch: comboOpts.arch,
});
}

async buildDir(
Expand Down Expand Up @@ -363,14 +363,15 @@ export async function packager(opts: Options): Promise<string[]> {
debug(`Application name: ${processedOpts.name}`);
debug(`Target Electron version: ${processedOpts.electronVersion}`);

await promisifyHooks(processedOpts.afterFinalizePackageTargets, [
await runHooks(
processedOpts.afterFinalizePackageTargets,
createPlatformArchPairs(processedOpts, platforms, archs).map(
([platform, arch]) => ({
platform,
arch,
}),
),
]);
);
const appPaths = await packageAllSpecifiedCombos(
processedOpts,
archs,
Expand Down
43 changes: 28 additions & 15 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import {
warning,
} from './common.js';
import { userPathFilter } from './copy-filter.js';
import { promisifyHooks } from './hooks.js';
import { runHooks } from './hooks.js';
import crypto from 'node:crypto';
import { ProcessedOptionsWithSinglePlatformArch } from './types.js';
import type { ProcessedOptionsWithSinglePlatformArch } from './types.js';

export class App {
asarIntegrity:
Expand Down Expand Up @@ -101,11 +101,18 @@ export class App {
}

get commonHookArgs() {
return [this.opts.electronVersion, this.opts.platform, this.opts.arch];
return {
electronVersion: this.opts.electronVersion,
platform: this.opts.platform,
arch: this.opts.arch,
};
}

get hookArgsWithOriginalResourcesAppDir() {
return [this.originalResourcesAppDir, ...this.commonHookArgs];
return {
buildPath: this.originalResourcesAppDir,
...this.commonHookArgs,
};
}

async relativeRename(basePath: string, oldName: string, newName: string) {
Expand Down Expand Up @@ -175,7 +182,7 @@ export class App {
await this.buildApp();
}

await promisifyHooks(
await runHooks(
this.opts.afterInitialize,
this.hookArgsWithOriginalResourcesAppDir,
);
Expand All @@ -188,7 +195,7 @@ export class App {
}

async copyTemplate() {
await promisifyHooks(
await runHooks(
this.opts.beforeCopy,
this.hookArgsWithOriginalResourcesAppDir,
);
Expand All @@ -201,12 +208,12 @@ export class App {
? this.opts.derefSymlinks
: true,
});
await promisifyHooks(
await runHooks(
this.opts.afterCopy,
this.hookArgsWithOriginalResourcesAppDir,
);
if (this.opts.prune) {
await promisifyHooks(
await runHooks(
this.opts.afterPrune,
this.hookArgsWithOriginalResourcesAppDir,
);
Expand Down Expand Up @@ -328,7 +335,7 @@ export class App {

debug(`Running asar with the options ${JSON.stringify(this.asarOptions)}`);

await promisifyHooks(
await runHooks(
this.opts.beforeAsar,
this.hookArgsWithOriginalResourcesAppDir,
);
Expand All @@ -348,7 +355,7 @@ export class App {
force: true,
});

await promisifyHooks(
await runHooks(
this.opts.afterAsar,
this.hookArgsWithOriginalResourcesAppDir,
);
Expand All @@ -371,9 +378,12 @@ export class App {

const extraResources = ensureArray(this.opts.extraResource);

const hookArgs = [this.stagingPath, ...this.commonHookArgs];
const hookArgs = {
buildPath: this.stagingPath,
...this.commonHookArgs,
};

await promisifyHooks(this.opts.beforeCopyExtraResources, hookArgs);
await runHooks(this.opts.beforeCopyExtraResources, hookArgs);

await Promise.all(
extraResources.map((resource) =>
Expand All @@ -388,7 +398,7 @@ export class App {
),
);

await promisifyHooks(this.opts.afterCopyExtraResources, hookArgs);
await runHooks(this.opts.afterCopyExtraResources, hookArgs);
}

async move() {
Expand Down Expand Up @@ -420,9 +430,12 @@ export class App {
}

if (this.opts.afterComplete) {
const hookArgs = [finalPath, ...this.commonHookArgs];
const hookArgs = {
buildPath: finalPath,
...this.commonHookArgs,
};

await promisifyHooks(this.opts.afterComplete, hookArgs);
await runHooks(this.opts.afterComplete, hookArgs);
}

return finalPath;
Expand Down
18 changes: 7 additions & 11 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ export type SupportedPlatform = OfficialPlatform | 'all';

export type IgnoreFunction = (path: string) => boolean;

export type HookFunctionErrorCallback = (err?: Error | null) => void;

/**
* A function that is called on the completion of a packaging stage.
*
Expand Down Expand Up @@ -103,13 +101,12 @@ export type HookFunctionErrorCallback = (err?: Error | null) => void;
* For real-world examples of `HookFunction`s, see the [list of related
* plugins](https://github.com/electron/packager#plugins).
*/
export type HookFunction = (
buildPath: string,
electronVersion: string,
platform: OfficialPlatform,
arch: OfficialArch,
callback: HookFunctionErrorCallback,
) => void;
export type HookFunction = (options: {
buildPath: string;
electronVersion: string;
platform: OfficialPlatform;
arch: OfficialArch;
}) => void | Promise<void>;

export type TargetDefinition = {
arch: OfficialArch;
Expand All @@ -118,8 +115,7 @@ export type TargetDefinition = {

export type FinalizePackageTargetsHookFunction = (
targets: TargetDefinition[],
callback: HookFunctionErrorCallback,
) => void;
) => void | Promise<void>;

/** See the documentation for [`@electron/osx-sign`](https://npm.im/@electron/osx-sign#opts) for details.
* @interface
Expand Down
36 changes: 22 additions & 14 deletions test/hooks.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { promisifyHooks, serialHooks } from '../src/hooks.js';
import { runHooks, serialHooks } from '../src/hooks.js';
import { describe, it, expect } from 'vitest';

describe('promisifyHooks', () => {
it('should call hooks in parallel', async () => {
let output = '0';
const timeoutFunc = (number: number, msTimeout: number) => {
return (done: () => void) => {
setTimeout(() => {
output += ` ${number}`;
done();
}, msTimeout);
const makeHook = (number: number, msTimeout: number) => {
return async () => {
await new Promise<void>((resolve) => {
setTimeout(() => {
output += ` ${number}`;
resolve();
}, msTimeout);
});
};
};
const testHooks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((number) =>
timeoutFunc(number, number % 2 === 0 ? 100 : 0),
makeHook(number, number % 2 === 0 ? 100 : 0),
);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
await promisifyHooks(testHooks as any);
await runHooks(testHooks as any, {} as any);
// all numbers should be printed in the output string
expect(output).toHaveLength(22);
expect(output).not.toBe('0 1 2 3 4 5 6 7 8 9 10');
Expand All @@ -26,21 +28,27 @@ describe('promisifyHooks', () => {
describe('serialHooks', () => {
it('should call hooks in order', async () => {
let output = '0';
const timeoutFunc = (number: number, msTimeout: number) => {
return () =>
new Promise<void>((resolve) => {
const makeHook = (number: number, msTimeout: number) => {
return async () => {
await new Promise<void>((resolve) => {
setTimeout(() => {
output += ` ${number}`;
resolve();
}, msTimeout);
});
};
};
const testHooks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((number) =>
timeoutFunc(number, number % 2 === 0 ? 100 : 0),
makeHook(number, number % 2 === 0 ? 100 : 0),
);

const runSerialHook = serialHooks(testHooks);
await runSerialHook('', '', 'darwin', 'arm64', () => {});
await runSerialHook({
buildPath: '',
electronVersion: '',
platform: 'darwin',
arch: 'arm64',
});
expect(output).toBe('0 1 2 3 4 5 6 7 8 9 10');
});
});
Expand Down
Loading
Loading