From b19be802955f9bce2d26de7956dd82092354c649 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Tue, 17 Mar 2026 13:55:09 -0700 Subject: [PATCH 1/2] feat(hooks): pass Listr2 context --- packages/api/core/spec/fast/util/hook.spec.ts | 46 ++++++++++++++++++- packages/api/core/src/util/hook.ts | 4 +- .../api/core/src/util/plugin-interface.ts | 4 +- packages/utils/types/src/index.ts | 5 +- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/api/core/spec/fast/util/hook.spec.ts b/packages/api/core/spec/fast/util/hook.spec.ts index eb1b4ea512..01c5dd9fe3 100644 --- a/packages/api/core/spec/fast/util/hook.spec.ts +++ b/packages/api/core/spec/fast/util/hook.spec.ts @@ -1,7 +1,11 @@ import { ForgeHookFn, ResolvedForgeConfig } from '@electron-forge/shared-types'; import { describe, expect, it, vi } from 'vitest'; -import { runHook, runMutatingHook } from '../../../src/util/hook.js'; +import { + getHookListrTasks, + runHook, + runMutatingHook, +} from '../../../src/util/hook.js'; const fakeConfig = { pluginInterface: { @@ -35,6 +39,46 @@ describe('runHook', () => { await runHook({ hooks: { preMake: fn }, ...fakeConfig }, 'preMake'); expect(fn).toHaveBeenCalledOnce(); }); + + it('should pass null as the task parameter when running outside listr', async () => { + const fn = vi.fn().mockResolvedValue(undefined); + await runHook({ hooks: { preMake: fn }, ...fakeConfig }, 'preMake'); + expect(fn).toHaveBeenCalledWith(expect.anything(), null); + }); +}); + +describe('getHookListrTasks', () => { + // A minimal mock of childTrace that preserves the (childTrace, ctx, task) => ... calling convention + const fakeChildTrace: any = (_opts: any, fn: any) => { + return (...args: any[]) => fn(fakeChildTrace, ...args); + }; + + it('should pass the listr task to the hook function', async () => { + const fn = vi.fn().mockResolvedValue(undefined); + const config = { + ...fakeConfig, + hooks: { generateAssets: fn }, + pluginInterface: { + ...fakeConfig.pluginInterface, + getHookListrTasks: vi.fn().mockResolvedValue([]), + }, + } as unknown as ResolvedForgeConfig; + + const tasks = await getHookListrTasks( + fakeChildTrace, + config, + 'generateAssets', + 'darwin', + 'x64', + ); + + expect(tasks).toHaveLength(1); + + const fakeTask = { output: '' }; + await (tasks[0].task as any)({}, fakeTask); + + expect(fn).toHaveBeenCalledWith(config, 'darwin', 'x64', fakeTask); + }); }); describe('runMutatingHook', () => { diff --git a/packages/api/core/src/util/hook.ts b/packages/api/core/src/util/hook.ts index 7b80664ee5..2beec12887 100644 --- a/packages/api/core/src/util/hook.ts +++ b/packages/api/core/src/util/hook.ts @@ -26,6 +26,7 @@ export const runHook = async ( await (hooks[hookName] as ForgeSimpleHookFn)( forgeConfig, ...hookArgs, + null, ); } } @@ -54,10 +55,11 @@ export const getHookListrTasks = async < category: '@electron-forge/hooks', extraDetails: { hook: hookName }, }, - async () => { + async (_childTrace, _ctx, task) => { await (hooks[hookName] as ForgeSimpleHookFn)( forgeConfig, ...hookArgs, + task, ); }, ), diff --git a/packages/api/core/src/util/plugin-interface.ts b/packages/api/core/src/util/plugin-interface.ts index 69aa08e3da..b6a893e6f7 100644 --- a/packages/api/core/src/util/plugin-interface.ts +++ b/packages/api/core/src/util/plugin-interface.ts @@ -103,7 +103,7 @@ export default class PluginInterface implements IForgePluginInterface { if (hooks) { if (typeof hooks === 'function') hooks = [hooks]; for (const hook of hooks) { - await hook(this.config, ...hookArgs); + await hook(this.config, ...hookArgs, null); } } } @@ -142,7 +142,7 @@ export default class PluginInterface implements IForgePluginInterface { ...(hookArgs as any[]), ); } else { - await hook(this.config, ...hookArgs); + await hook(this.config, ...hookArgs, task); } }, ), diff --git a/packages/utils/types/src/index.ts b/packages/utils/types/src/index.ts index 43a68cff64..459829585e 100644 --- a/packages/utils/types/src/index.ts +++ b/packages/utils/types/src/index.ts @@ -85,7 +85,10 @@ export type ForgeHookName = keyof (ForgeSimpleHookSignatures & ForgeMutatingHookSignatures); export type ForgeSimpleHookFn = ( forgeConfig: ResolvedForgeConfig, - ...args: ForgeSimpleHookSignatures[Hook] + ...args: [ + ...ForgeSimpleHookSignatures[Hook], + task: ForgeListrTask | null, + ] ) => Promise; export type ForgeMutatingHookFn< Hook extends keyof ForgeMutatingHookSignatures, From 6ac943f8f0bc21ed8e2ed00ef57aa87572f97d24 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Tue, 17 Mar 2026 16:54:21 -0700 Subject: [PATCH 2/2] fix types --- packages/api/core/src/api/publish.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/core/src/api/publish.ts b/packages/api/core/src/api/publish.ts index e338220ab7..b344d60be1 100644 --- a/packages/api/core/src/api/publish.ts +++ b/packages/api/core/src/api/publish.ts @@ -114,7 +114,7 @@ export default autoTrace( return delayTraceTillSignal( childTrace, - task.newListr( + task.newListr( publishers.map((publisher) => ({ title: `${chalk.cyan(`[publisher-${publisher.name}]`)} Running the ${chalk.yellow('publish')} command`, task: childTrace>(