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>
This commit is contained in:
David Kaya
2026-03-26 00:37:24 +01:00
co-authored by Copilot
parent 66c129f8b9
commit e26e6abe7a
2 changed files with 5 additions and 41 deletions
+4 -22
View File
@@ -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<string>;
+1 -19
View File
@@ -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<string, string | Error>) {
}
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',