From cb05148231e41fb72e85fcc648955eee74db3048 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Thu, 26 Mar 2026 00:37:24 +0100 Subject: [PATCH] fix: require child_process in gitService Use createRequire for node:child_process so Bun on macOS can load gitService without ESM export-shape issues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/main/git/gitService.ts | 26 ++++---------------------- tests/main/gitService.test.ts | 20 +------------------- 2 files changed, 5 insertions(+), 41 deletions(-) diff --git a/src/main/git/gitService.ts b/src/main/git/gitService.ts index 3d16105..95f86d2 100644 --- a/src/main/git/gitService.ts +++ b/src/main/git/gitService.ts @@ -1,4 +1,4 @@ -import * as childProcessModule from 'node:child_process'; +import { createRequire } from 'node:module'; import { promisify } from 'node:util'; import type { ProjectGitChangeSummary, ProjectGitCommitSummary, ProjectGitContext } from '@shared/domain/project'; @@ -6,27 +6,9 @@ import { nowIso } from '@shared/utils/ids'; type ExecFileException = import('node:child_process').ExecFileException; -type ChildProcessModuleLike = { - readonly execFile?: typeof childProcessModule.execFile; - readonly default?: { - readonly execFile?: typeof childProcessModule.execFile; - }; -}; - -export function resolveExecFileForInterop( - module: ChildProcessModuleLike, -): typeof childProcessModule.execFile { - const execFile = module.execFile ?? module.default?.execFile; - if (typeof execFile !== 'function') { - throw new Error('node:child_process execFile is unavailable.'); - } - - return execFile; -} - -const execFileAsync = promisify( - resolveExecFileForInterop(childProcessModule as ChildProcessModuleLike), -); +const require = createRequire(import.meta.url); +const { execFile } = require('node:child_process') as typeof import('node:child_process'); +const execFileAsync = promisify(execFile); const GIT_TIMEOUT_MS = 5_000; type GitCommandRunner = (projectPath: string, args: string[]) => Promise; diff --git a/tests/main/gitService.test.ts b/tests/main/gitService.test.ts index b27dae0..320cedf 100644 --- a/tests/main/gitService.test.ts +++ b/tests/main/gitService.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'bun:test'; -import { GitService, resolveExecFileForInterop } from '@main/git/gitService'; +import { GitService } from '@main/git/gitService'; function createGitError(message: string, options?: { code?: number | string; stderr?: string }) { return Object.assign(new Error(message), options); @@ -24,24 +24,6 @@ function createService(responses: Record) { } describe('GitService', () => { - test('resolves execFile from the named child_process export when available', () => { - const namedExecFile = (() => undefined) as unknown as typeof import('node:child_process').execFile; - - expect(resolveExecFileForInterop({ execFile: namedExecFile })).toBe(namedExecFile); - }); - - test('falls back to default.execFile for Bun child_process interop', () => { - const defaultExecFile = (() => undefined) as unknown as typeof import('node:child_process').execFile; - - expect(resolveExecFileForInterop({ default: { execFile: defaultExecFile } })).toBe(defaultExecFile); - }); - - test('throws when execFile is unavailable in both child_process export shapes', () => { - expect(() => resolveExecFileForInterop({})).toThrow( - 'node:child_process execFile is unavailable.', - ); - }); - test('describes a git repository with branch, dirty state, change counts, and head commit summary', async () => { const service = createService({ 'rev-parse --show-toplevel': 'C:\\workspace\\repo\n',