feat(FN-1070): restore desktop Electron scaffolding
- Reintroduce the @fusion/desktop workspace package with Electron main, preload, and renderer entrypoints - Add desktop build/test tooling (TypeScript, Vitest, electron-builder) and update workspace build dependency settings for Electron - Wire secure IPC bridge APIs for app version/quit flows with exported preload typings - Add README usage docs and unit tests covering main-process window/tray behavior and preload API wiring
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
|
"electron",
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"koffi",
|
"koffi",
|
||||||
"protobufjs"
|
"protobufjs"
|
||||||
|
|||||||
32
packages/desktop/README.md
Normal file
32
packages/desktop/README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# @fusion/desktop
|
||||||
|
|
||||||
|
Electron desktop shell for Fusion.
|
||||||
|
|
||||||
|
This package provides a native Electron wrapper around the existing Fusion dashboard web UI. The desktop shell currently connects to a running dashboard server and displays it inside a desktop window with tray integration.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Start the Fusion dashboard server first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fn dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, in another terminal, start the desktop app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @fusion/desktop dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
- `pnpm --filter @fusion/desktop dev` — run the Electron main process in development
|
||||||
|
- `pnpm --filter @fusion/desktop build` — compile TypeScript sources
|
||||||
|
- `pnpm --filter @fusion/desktop test` — run Vitest suite
|
||||||
|
- `pnpm --filter @fusion/desktop typecheck` — run TypeScript checks without emitting files
|
||||||
|
- `pnpm --filter @fusion/desktop pack` — build distributable package via electron-builder
|
||||||
|
- `pnpm --filter @fusion/desktop dist` — build distribution artifacts without publishing
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- `FUSION_DASHBOARD_URL` — override the default dashboard URL used by the desktop shell (`http://localhost:4040`)
|
||||||
50
packages/desktop/package.json
Normal file
50
packages/desktop/package.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "@fusion/desktop",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.5.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx src/main.ts",
|
||||||
|
"build": "tsc",
|
||||||
|
"test": "vitest run",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"pack": "electron-builder",
|
||||||
|
"dist": "electron-builder --publish never"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "com.fusion.desktop",
|
||||||
|
"productName": "Fusion",
|
||||||
|
"directories": {
|
||||||
|
"output": "dist-electron"
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.developer-tools",
|
||||||
|
"target": [
|
||||||
|
"dmg"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
"nsis"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
"AppImage"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@vitest/coverage-v8": "^3.1.0",
|
||||||
|
"electron": "^35.0.0",
|
||||||
|
"electron-builder": "^26.0.0",
|
||||||
|
"tsx": "^4.19.0",
|
||||||
|
"typescript": "^5.7.0",
|
||||||
|
"vitest": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
170
packages/desktop/src/__tests__/main.test.ts
Normal file
170
packages/desktop/src/__tests__/main.test.ts
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const mocks = vi.hoisted(() => {
|
||||||
|
const browserWindowInstance = {
|
||||||
|
loadURL: vi.fn(),
|
||||||
|
on: vi.fn(),
|
||||||
|
show: vi.fn(),
|
||||||
|
focus: vi.fn(),
|
||||||
|
hide: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const BrowserWindow = vi.fn(() => browserWindowInstance) as unknown as {
|
||||||
|
(...args: unknown[]): typeof browserWindowInstance;
|
||||||
|
getAllWindows: () => unknown[];
|
||||||
|
};
|
||||||
|
BrowserWindow.getAllWindows = vi.fn(() => []);
|
||||||
|
|
||||||
|
const app = {
|
||||||
|
whenReady: vi.fn(() => Promise.resolve()),
|
||||||
|
getVersion: vi.fn(() => "0.1.0"),
|
||||||
|
quit: vi.fn(),
|
||||||
|
on: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ipcMain = {
|
||||||
|
handle: vi.fn(),
|
||||||
|
on: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const trayInstance = {
|
||||||
|
setToolTip: vi.fn(),
|
||||||
|
setContextMenu: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const Tray = vi.fn(() => trayInstance);
|
||||||
|
const Menu = { buildFromTemplate: vi.fn(() => ({ id: "mock-menu" })) };
|
||||||
|
const nativeImage = { createEmpty: vi.fn(() => ({ id: "mock-image" })) };
|
||||||
|
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
BrowserWindow,
|
||||||
|
ipcMain,
|
||||||
|
trayInstance,
|
||||||
|
Tray,
|
||||||
|
Menu,
|
||||||
|
nativeImage,
|
||||||
|
browserWindowInstance,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("electron", () => ({
|
||||||
|
app: mocks.app,
|
||||||
|
BrowserWindow: mocks.BrowserWindow,
|
||||||
|
ipcMain: mocks.ipcMain,
|
||||||
|
Tray: mocks.Tray,
|
||||||
|
Menu: mocks.Menu,
|
||||||
|
nativeImage: mocks.nativeImage,
|
||||||
|
}));
|
||||||
|
|
||||||
|
async function importMainModule() {
|
||||||
|
return import("../main.ts");
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("main process", () => {
|
||||||
|
const originalDashboardUrl = process.env.FUSION_DASHBOARD_URL;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.resetModules();
|
||||||
|
if (originalDashboardUrl === undefined) {
|
||||||
|
delete process.env.FUSION_DASHBOARD_URL;
|
||||||
|
} else {
|
||||||
|
process.env.FUSION_DASHBOARD_URL = originalDashboardUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("DASHBOARD_URL defaults to localhost:4040", async () => {
|
||||||
|
delete process.env.FUSION_DASHBOARD_URL;
|
||||||
|
|
||||||
|
const { DASHBOARD_URL } = await importMainModule();
|
||||||
|
|
||||||
|
expect(DASHBOARD_URL).toBe("http://localhost:4040");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("DASHBOARD_URL uses env override", async () => {
|
||||||
|
process.env.FUSION_DASHBOARD_URL = "http://localhost:5050";
|
||||||
|
|
||||||
|
const { DASHBOARD_URL } = await importMainModule();
|
||||||
|
|
||||||
|
expect(DASHBOARD_URL).toBe("http://localhost:5050");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("createMainWindow creates BrowserWindow with secure preferences", async () => {
|
||||||
|
const { createMainWindow } = await importMainModule();
|
||||||
|
|
||||||
|
createMainWindow();
|
||||||
|
|
||||||
|
expect(mocks.BrowserWindow).toHaveBeenCalledTimes(1);
|
||||||
|
const [options] = mocks.BrowserWindow.mock.calls[0] as [
|
||||||
|
{
|
||||||
|
webPreferences: {
|
||||||
|
contextIsolation: boolean;
|
||||||
|
nodeIntegration: boolean;
|
||||||
|
preload: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(options.webPreferences.contextIsolation).toBe(true);
|
||||||
|
expect(options.webPreferences.nodeIntegration).toBe(false);
|
||||||
|
expect(options.webPreferences.preload).toContain("preload.ts");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("createMainWindow loads the dashboard URL", async () => {
|
||||||
|
const { createMainWindow, DASHBOARD_URL } = await importMainModule();
|
||||||
|
|
||||||
|
createMainWindow();
|
||||||
|
|
||||||
|
expect(mocks.browserWindowInstance.loadURL).toHaveBeenCalledWith(DASHBOARD_URL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("registerIpcHandlers registers app:get-version via handle", async () => {
|
||||||
|
const { registerIpcHandlers } = await importMainModule();
|
||||||
|
|
||||||
|
registerIpcHandlers();
|
||||||
|
|
||||||
|
expect(mocks.ipcMain.handle).toHaveBeenCalledWith(
|
||||||
|
"app:get-version",
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("registerIpcHandlers registers app:quit via on", async () => {
|
||||||
|
const { registerIpcHandlers } = await importMainModule();
|
||||||
|
|
||||||
|
registerIpcHandlers();
|
||||||
|
|
||||||
|
expect(mocks.ipcMain.on).toHaveBeenCalledWith("app:quit", expect.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("importing main does not auto-start", async () => {
|
||||||
|
await importMainModule();
|
||||||
|
|
||||||
|
expect(mocks.app.whenReady).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("setupTray creates tray menu and hides window on close", async () => {
|
||||||
|
const { setupTray } = await importMainModule();
|
||||||
|
|
||||||
|
setupTray(mocks.browserWindowInstance as never);
|
||||||
|
|
||||||
|
expect(mocks.Tray).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mocks.nativeImage.createEmpty).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mocks.trayInstance.setToolTip).toHaveBeenCalledWith("Fusion");
|
||||||
|
expect(mocks.Menu.buildFromTemplate).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
const closeCall = mocks.browserWindowInstance.on.mock.calls.find(
|
||||||
|
(call) => call[0] === "close",
|
||||||
|
);
|
||||||
|
expect(closeCall).toBeDefined();
|
||||||
|
|
||||||
|
const closeHandler = closeCall?.[1] as (event: { preventDefault: () => void }) => void;
|
||||||
|
const event = { preventDefault: vi.fn() };
|
||||||
|
|
||||||
|
closeHandler(event);
|
||||||
|
|
||||||
|
expect(event.preventDefault).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mocks.browserWindowInstance.hide).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
94
packages/desktop/src/__tests__/preload.test.ts
Normal file
94
packages/desktop/src/__tests__/preload.test.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const mocks = vi.hoisted(() => {
|
||||||
|
const contextBridge = {
|
||||||
|
exposeInMainWorld: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ipcRenderer = {
|
||||||
|
invoke: vi.fn(),
|
||||||
|
send: vi.fn(),
|
||||||
|
on: vi.fn(),
|
||||||
|
removeListener: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return { contextBridge, ipcRenderer };
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("electron", () => ({
|
||||||
|
contextBridge: mocks.contextBridge,
|
||||||
|
ipcRenderer: mocks.ipcRenderer,
|
||||||
|
}));
|
||||||
|
|
||||||
|
async function importPreloadModule() {
|
||||||
|
await import("../preload.ts");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExposedApi() {
|
||||||
|
const call = mocks.contextBridge.exposeInMainWorld.mock.calls[0] as
|
||||||
|
| [string, {
|
||||||
|
getAppVersion: () => Promise<string>;
|
||||||
|
quit: () => void;
|
||||||
|
onDashboardReady: (callback: () => void) => () => void;
|
||||||
|
}]
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
return call?.[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("preload", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("contextBridge.exposeInMainWorld called with fusionDesktop", async () => {
|
||||||
|
await importPreloadModule();
|
||||||
|
|
||||||
|
expect(mocks.contextBridge.exposeInMainWorld).toHaveBeenCalledWith(
|
||||||
|
"fusionDesktop",
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getAppVersion calls ipcRenderer.invoke", async () => {
|
||||||
|
mocks.ipcRenderer.invoke.mockResolvedValue("0.1.0");
|
||||||
|
await importPreloadModule();
|
||||||
|
|
||||||
|
const api = getExposedApi();
|
||||||
|
const version = await api?.getAppVersion();
|
||||||
|
|
||||||
|
expect(version).toBe("0.1.0");
|
||||||
|
expect(mocks.ipcRenderer.invoke).toHaveBeenCalledWith("app:get-version");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("quit calls ipcRenderer.send", async () => {
|
||||||
|
await importPreloadModule();
|
||||||
|
|
||||||
|
const api = getExposedApi();
|
||||||
|
api?.quit();
|
||||||
|
|
||||||
|
expect(mocks.ipcRenderer.send).toHaveBeenCalledWith("app:quit");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("onDashboardReady returns unsubscribe function", async () => {
|
||||||
|
await importPreloadModule();
|
||||||
|
|
||||||
|
const api = getExposedApi();
|
||||||
|
const callback = vi.fn();
|
||||||
|
const unsubscribe = api?.onDashboardReady(callback);
|
||||||
|
|
||||||
|
expect(mocks.ipcRenderer.on).toHaveBeenCalledWith(
|
||||||
|
"dashboard:ready",
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
expect(typeof unsubscribe).toBe("function");
|
||||||
|
|
||||||
|
unsubscribe?.();
|
||||||
|
|
||||||
|
expect(mocks.ipcRenderer.removeListener).toHaveBeenCalledWith(
|
||||||
|
"dashboard:ready",
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
9
packages/desktop/src/index.ts
Normal file
9
packages/desktop/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export {
|
||||||
|
createMainWindow,
|
||||||
|
setupTray,
|
||||||
|
registerIpcHandlers,
|
||||||
|
DASHBOARD_URL,
|
||||||
|
run,
|
||||||
|
} from "./main.js";
|
||||||
|
|
||||||
|
export type { FusionDesktopAPI } from "./preload.js";
|
||||||
85
packages/desktop/src/main.ts
Normal file
85
packages/desktop/src/main.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { join, resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { app, BrowserWindow, Menu, Tray, ipcMain, nativeImage } from "electron";
|
||||||
|
|
||||||
|
export const DASHBOARD_URL = process.env.FUSION_DASHBOARD_URL || "http://localhost:4040";
|
||||||
|
|
||||||
|
export function createMainWindow(): BrowserWindow {
|
||||||
|
const mainWindow = new BrowserWindow({
|
||||||
|
width: 1280,
|
||||||
|
height: 900,
|
||||||
|
title: "Fusion",
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(import.meta.dirname, "preload.ts"),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
void mainWindow.loadURL(DASHBOARD_URL);
|
||||||
|
return mainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupTray(mainWindow: BrowserWindow): Tray {
|
||||||
|
const tray = new Tray(nativeImage.createEmpty());
|
||||||
|
tray.setToolTip("Fusion");
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: "Show Fusion",
|
||||||
|
click: () => {
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Quit",
|
||||||
|
click: () => {
|
||||||
|
app.quit();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
tray.setContextMenu(menu);
|
||||||
|
|
||||||
|
mainWindow.on("close", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
mainWindow.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
return tray;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerIpcHandlers(): void {
|
||||||
|
ipcMain.handle("app:get-version", () => app.getVersion());
|
||||||
|
ipcMain.on("app:quit", () => app.quit());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function run(): void {
|
||||||
|
let tray: Tray | undefined;
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
const mainWindow = createMainWindow();
|
||||||
|
tray = setupTray(mainWindow);
|
||||||
|
registerIpcHandlers();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("window-all-closed", () => {
|
||||||
|
if (process.platform !== "darwin") {
|
||||||
|
// Keep app alive in tray on non-macOS platforms.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("activate", () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
const mainWindow = createMainWindow();
|
||||||
|
tray = tray ?? setupTray(mainWindow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const modulePath = fileURLToPath(import.meta.url);
|
||||||
|
if (process.argv[1] && resolve(process.argv[1]) === modulePath) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
11
packages/desktop/src/preload.d.ts
vendored
Normal file
11
packages/desktop/src/preload.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export interface FusionDesktopAPI {
|
||||||
|
getAppVersion(): Promise<string>;
|
||||||
|
quit(): void;
|
||||||
|
onDashboardReady(callback: () => void): () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
fusionDesktop: FusionDesktopAPI;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
packages/desktop/src/preload.ts
Normal file
25
packages/desktop/src/preload.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { contextBridge, ipcRenderer } from "electron";
|
||||||
|
|
||||||
|
export interface FusionDesktopAPI {
|
||||||
|
getAppVersion(): Promise<string>;
|
||||||
|
quit(): void;
|
||||||
|
onDashboardReady(callback: () => void): () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const api: FusionDesktopAPI = {
|
||||||
|
getAppVersion(): Promise<string> {
|
||||||
|
return ipcRenderer.invoke("app:get-version");
|
||||||
|
},
|
||||||
|
quit(): void {
|
||||||
|
ipcRenderer.send("app:quit");
|
||||||
|
},
|
||||||
|
onDashboardReady(callback: () => void): () => void {
|
||||||
|
const listener = () => callback();
|
||||||
|
ipcRenderer.on("dashboard:ready", listener);
|
||||||
|
return () => {
|
||||||
|
ipcRenderer.removeListener("dashboard:ready", listener);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld("fusionDesktop", api);
|
||||||
3
packages/desktop/src/renderer.ts
Normal file
3
packages/desktop/src/renderer.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file is a placeholder. The Electron shell currently connects to the
|
||||||
|
// Fusion dashboard server via URL. Future tasks will embed the dashboard's
|
||||||
|
// built client assets here.
|
||||||
10
packages/desktop/tsconfig.json
Normal file
10
packages/desktop/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["src/**/*.test.ts", "src/**/__tests__/**/*"]
|
||||||
|
}
|
||||||
13
packages/desktop/vitest.config.ts
Normal file
13
packages/desktop/vitest.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
const maxWorkers = Number.parseInt(process.env.VITEST_MAX_WORKERS ?? "16", 10);
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
include: ["src/**/*.test.ts"],
|
||||||
|
maxWorkers,
|
||||||
|
fileParallelism: true,
|
||||||
|
pool: "threads",
|
||||||
|
passWithNoTests: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
1769
pnpm-lock.yaml
generated
1769
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user