fix: resolve macos bun interop

Use Bun-safe Electron and child_process import patterns for macOS GitHub Actions tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-26 00:30:41 +01:00
co-authored by Copilot
parent 40ef1c07b5
commit d9f3f5302a
13 changed files with 104 additions and 58 deletions
+3 -1
View File
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
import { rm } from 'node:fs/promises';
import { basename, dirname } from 'node:path';
import { dialog, shell } from 'electron';
import electron from 'electron';
import type {
AgentActivityEvent,
@@ -107,6 +107,8 @@ import {
validateSessionToolingSelectionIds,
} from '@main/sessionToolingConfig';
const { dialog, shell } = electron;
type AppServiceEvents = {
'workspace-updated': [WorkspaceState];
'session-event': [SessionEventRecord];
+1 -3
View File
@@ -1,11 +1,9 @@
import * as childProcess from 'node:child_process';
import childProcess, { type ExecFileException } from 'node:child_process';
import { promisify } from 'node:util';
import type { ProjectGitChangeSummary, ProjectGitCommitSummary, ProjectGitContext } from '@shared/domain/project';
import { nowIso } from '@shared/utils/ids';
type ExecFileException = childProcess.ExecFileException;
const execFileAsync = promisify(childProcess.execFile);
const GIT_TIMEOUT_MS = 5_000;
+5 -2
View File
@@ -1,11 +1,14 @@
import { app, BrowserWindow } from 'electron';
import electron from 'electron';
import type { BrowserWindow as BrowserWindowType } from 'electron';
import { registerIpcHandlers } from '@main/ipc/registerIpcHandlers';
import { AryxAppService } from '@main/AryxAppService';
import { createMainWindow } from '@main/windows/createMainWindow';
import { applyTitleBarTheme } from '@main/windows/titleBarTheme';
let mainWindow: BrowserWindow | undefined;
const { app, BrowserWindow } = electron;
let mainWindow: BrowserWindowType | undefined;
let appService: AryxAppService | undefined;
async function bootstrap(): Promise<void> {
+4 -1
View File
@@ -1,4 +1,5 @@
import { BrowserWindow, ipcMain } from 'electron';
import electron from 'electron';
import type { BrowserWindow } from 'electron';
import { ipcChannels } from '@shared/contracts/channels';
import type {
@@ -27,6 +28,8 @@ import type { AppearanceTheme } from '@shared/domain/tooling';
import { AryxAppService } from '@main/AryxAppService';
import { applyTitleBarTheme } from '@main/windows/titleBarTheme';
const { ipcMain } = electron;
export function registerIpcHandlers(window: BrowserWindow, service: AryxAppService): void {
ipcMain.handle(ipcChannels.describeSidecarCapabilities, () => service.describeSidecarCapabilities());
ipcMain.handle(ipcChannels.refreshSidecarCapabilities, () => service.refreshSidecarCapabilities());
+3 -1
View File
@@ -1,6 +1,8 @@
import { app } from 'electron';
import electron from 'electron';
import { join } from 'node:path';
const { app } = electron;
export function getWorkspaceFilePath(): string {
return join(app.getPath('userData'), 'workspace.json');
}
+3 -1
View File
@@ -1,4 +1,4 @@
import { app } from 'electron';
import electron from 'electron';
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
import type {
@@ -23,6 +23,8 @@ import {
import { TurnCancelledError } from '@main/sidecar/turnCancelledError';
import { resolveSidecarProcess } from '@main/sidecar/sidecarRuntime';
const { app } = electron;
type PendingCommand =
| ({
processId: number;
+5 -2
View File
@@ -1,9 +1,12 @@
import { app, BrowserWindow, Menu, shell } from 'electron';
import electron from 'electron';
import type { BrowserWindow as BrowserWindowType } from 'electron';
import { join } from 'node:path';
import { resolveWindowIconPath } from '@main/windows/appIcon';
export function createMainWindow(): BrowserWindow {
const { app, BrowserWindow, Menu, shell } = electron;
export function createMainWindow(): BrowserWindowType {
Menu.setApplicationMenu(null);
const window = new BrowserWindow({
+4 -1
View File
@@ -1,7 +1,10 @@
import { nativeTheme, type BrowserWindow } from 'electron';
import electron from 'electron';
import type { BrowserWindow } from 'electron';
import type { AppearanceTheme } from '@shared/domain/tooling';
const { nativeTheme } = electron;
interface TitleBarColors {
backgroundColor: string;
overlay: { color: string; symbolColor: string };
+3 -1
View File
@@ -1,8 +1,10 @@
import { contextBridge, ipcRenderer } from 'electron';
import electron from 'electron';
import { ipcChannels } from '@shared/contracts/channels';
import type { ElectronApi } from '@shared/contracts/ipc';
const { contextBridge, ipcRenderer } = electron;
const api: ElectronApi = {
describeSidecarCapabilities: () => ipcRenderer.invoke(ipcChannels.describeSidecarCapabilities),
refreshSidecarCapabilities: () => ipcRenderer.invoke(ipcChannels.refreshSidecarCapabilities),
+20 -13
View File
@@ -8,19 +8,26 @@ import { createWorkspaceSeed, type WorkspaceState } from '@shared/domain/workspa
const TIMESTAMP = '2026-03-25T00:00:00.000Z';
mock.module('electron', () => ({
app: {
isPackaged: false,
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
},
dialog: {
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
},
shell: {
openPath: async () => '',
},
}));
mock.module('electron', () => {
const electronMock = {
app: {
isPackaged: false,
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
},
dialog: {
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
},
shell: {
openPath: async () => '',
},
};
return {
...electronMock,
default: electronMock,
};
});
mock.module('keytar', () => ({
default: {
@@ -2,19 +2,26 @@ import { describe, expect, mock, test } from 'bun:test';
import type { SidecarCapabilities } from '@shared/contracts/sidecar';
mock.module('electron', () => ({
app: {
isPackaged: false,
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
},
dialog: {
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
},
shell: {
openPath: async () => '',
},
}));
mock.module('electron', () => {
const electronMock = {
app: {
isPackaged: false,
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
},
dialog: {
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
},
shell: {
openPath: async () => '',
},
};
return {
...electronMock,
default: electronMock,
};
});
mock.module('keytar', () => ({
default: {
+20 -13
View File
@@ -9,19 +9,26 @@ import { createWorkspaceSeed, type WorkspaceState } from '@shared/domain/workspa
const TIMESTAMP = '2026-03-25T00:00:00.000Z';
const SCRATCHPAD_PATH = 'C:\\workspace\\personal\\repositories\\aryx\\scratchpad';
mock.module('electron', () => ({
app: {
isPackaged: false,
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
},
dialog: {
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
},
shell: {
openPath: async () => '',
},
}));
mock.module('electron', () => {
const electronMock = {
app: {
isPackaged: false,
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
},
dialog: {
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
},
shell: {
openPath: async () => '',
},
};
return {
...electronMock,
default: electronMock,
};
});
mock.module('keytar', () => ({
default: {
+13 -6
View File
@@ -42,12 +42,19 @@ class FakeChildProcess extends EventEmitter {
const spawnedProcesses: FakeChildProcess[] = [];
mock.module('electron', () => ({
app: {
isPackaged: false,
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
},
}));
mock.module('electron', () => {
const electronMock = {
app: {
isPackaged: false,
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
},
};
return {
...electronMock,
default: electronMock,
};
});
mock.module('node:child_process', () => ({
spawn: () => {