From 99fa06e12b9885e58c54d145bcfe999cc41d5b06 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sat, 22 Nov 2025 17:31:21 +0100 Subject: [PATCH] fix(companion): resolve fixtures path correctly for both dev and built modes --- apps/companion/main/di-container.ts | 33 ++-- .../companion/di-container.test.ts | 144 ++++++++++++------ 2 files changed, 116 insertions(+), 61 deletions(-) diff --git a/apps/companion/main/di-container.ts b/apps/companion/main/di-container.ts index 91d0697b7..866493941 100644 --- a/apps/companion/main/di-container.ts +++ b/apps/companion/main/di-container.ts @@ -20,29 +20,30 @@ import type { IFixtureServerService } from '@/packages/infrastructure/adapters/a /** * Resolve the fixtures path relative to the monorepo root. - * In Electron, app.getAppPath() returns the path to the app's main directory - * (e.g., apps/companion/dist/main in development, or the asar in production). - * We navigate up to find the monorepo root and then join with the resources path. + * Uses __dirname to determine the navigation depth based on whether we're + * running from source (dev mode) or built output (dist mode). * - * Path breakdown from apps/companion/dist/main: - * - Level 1: apps/companion/dist (../) - * - Level 2: apps/companion (../../) - * - Level 3: apps (../../../) - * - Level 4: gridpilot (monorepo root) (../../../../) + * Path breakdown: + * - Dev mode: apps/companion/main → 3 levels to root + * - Built mode: apps/companion/dist/main → 4 levels to root * * @param configuredPath - The path configured (may be relative or absolute) - * @param appPath - The app path from electron (app.getAppPath()) + * @param dirname - The directory name (__dirname) of the calling module * @returns Resolved absolute path */ -export function resolveFixturesPath(configuredPath: string, appPath: string): string { +export function resolveFixturesPath(configuredPath: string, dirname: string): string { if (path.isAbsolute(configuredPath)) { return configuredPath; } - const projectRoot = path.resolve(appPath, '../../../../'); - const resolvedPath = path.join(projectRoot, configuredPath); + // Determine navigation depth based on whether we're in dist/ or source + // Dev mode: apps/companion/main → 3 levels to root + // Built mode: apps/companion/dist/main → 4 levels to root + const isBuiltMode = dirname.includes(`${path.sep}dist${path.sep}`); + const levelsUp = isBuiltMode ? '../../../../' : '../../../'; + const projectRoot = path.resolve(dirname, levelsUp); - return resolvedPath; + return path.join(projectRoot, configuredPath); } export interface BrowserConnectionResult { @@ -196,10 +197,10 @@ export class DIContainer { /** * Resolve the fixtures path relative to the monorepo root. - * Uses the exported resolveFixturesPath function with app.getAppPath(). + * Uses the exported resolveFixturesPath function with __dirname. */ private resolveFixturesPathInternal(configuredPath: string): string { - return resolveFixturesPath(configuredPath, app.getAppPath()); + return resolveFixturesPath(configuredPath, __dirname); } /** @@ -226,7 +227,7 @@ export class DIContainer { this.logger.debug('Fixture server path resolution', { configuredPath: config.fixtureServer.fixturesPath, - appPath: app.getAppPath(), + dirname: __dirname, resolvedPath: fixturesPath }); diff --git a/tests/integration/companion/di-container.test.ts b/tests/integration/companion/di-container.test.ts index 9a548b53f..8097be97e 100644 --- a/tests/integration/companion/di-container.test.ts +++ b/tests/integration/companion/di-container.test.ts @@ -4,63 +4,117 @@ import { resolveFixturesPath } from '@/apps/companion/main/di-container'; describe('DIContainer path resolution', () => { describe('resolveFixturesPath', () => { - it('should resolve fixtures path to monorepo root when app path is apps/companion/dist/main', () => { - // Given: The app path as it would be in Electron runtime (apps/companion/dist/main) - const mockAppPath = '/Users/test/Projects/gridpilot/apps/companion/dist/main'; - const relativePath = './resources/iracing-hosted-sessions'; + describe('built mode (with /dist/ in path)', () => { + it('should resolve fixtures path to monorepo root when dirname is apps/companion/dist/main', () => { + // Given: The dirname as it would be in built Electron runtime (apps/companion/dist/main) + const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/dist/main'; + const relativePath = './resources/iracing-hosted-sessions'; - // When: Resolving the fixtures path - const resolved = resolveFixturesPath(relativePath, mockAppPath); + // When: Resolving the fixtures path + const resolved = resolveFixturesPath(relativePath, mockDirname); - // Then: Should resolve to monorepo root (4 levels up from apps/companion/dist/main) - // Level 0: apps/companion/dist/main (app.getAppPath()) - // Level 1: apps/companion/dist (../) - // Level 2: apps/companion (../../) - // Level 3: apps (../../../) - // Level 4: gridpilot (monorepo root) (../../../../) ← CORRECT - const expectedPath = '/Users/test/Projects/gridpilot/resources/iracing-hosted-sessions'; - expect(resolved).toBe(expectedPath); + // Then: Should resolve to monorepo root (4 levels up from apps/companion/dist/main) + // Level 0: apps/companion/dist/main (__dirname) + // Level 1: apps/companion/dist (../) + // Level 2: apps/companion (../../) + // Level 3: apps (../../../) + // Level 4: gridpilot (monorepo root) (../../../../) ← CORRECT + const expectedPath = '/Users/test/Projects/gridpilot/resources/iracing-hosted-sessions'; + expect(resolved).toBe(expectedPath); + }); + + it('should navigate exactly 4 levels up in built mode', () => { + // Given: A path with /dist/ that demonstrates the 4-level navigation + const mockDirname = '/level4/level3/dist/level1'; + const relativePath = './target'; + + // When: Resolving the fixtures path + const resolved = resolveFixturesPath(relativePath, mockDirname); + + // Then: Should resolve to the path 4 levels up (root /) + expect(resolved).toBe('/target'); + }); + + it('should work with different relative path formats in built mode', () => { + // Given: Various relative path formats + const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/dist/main'; + + // When/Then: Different relative formats should all work + expect(resolveFixturesPath('resources/fixtures', mockDirname)) + .toBe('/Users/test/Projects/gridpilot/resources/fixtures'); + + expect(resolveFixturesPath('./resources/fixtures', mockDirname)) + .toBe('/Users/test/Projects/gridpilot/resources/fixtures'); + }); }); - it('should return absolute paths unchanged', () => { - // Given: An absolute path - const absolutePath = '/some/absolute/path/to/fixtures'; - const mockAppPath = '/Users/test/Projects/gridpilot/apps/companion/dist/main'; + describe('dev mode (without /dist/ in path)', () => { + it('should resolve fixtures path to monorepo root when dirname is apps/companion/main', () => { + // Given: The dirname as it would be in dev mode (apps/companion/main) + const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/main'; + const relativePath = './resources/iracing-hosted-sessions'; - // When: Resolving an absolute path - const resolved = resolveFixturesPath(absolutePath, mockAppPath); + // When: Resolving the fixtures path + const resolved = resolveFixturesPath(relativePath, mockDirname); - // Then: Should return the absolute path unchanged - expect(resolved).toBe(absolutePath); + // Then: Should resolve to monorepo root (3 levels up from apps/companion/main) + // Level 0: apps/companion/main (__dirname) + // Level 1: apps/companion (../) + // Level 2: apps (../../) + // Level 3: gridpilot (monorepo root) (../../../) ← CORRECT + const expectedPath = '/Users/test/Projects/gridpilot/resources/iracing-hosted-sessions'; + expect(resolved).toBe(expectedPath); + }); + + it('should navigate exactly 3 levels up in dev mode', () => { + // Given: A path without /dist/ that demonstrates the 3-level navigation + const mockDirname = '/level3/level2/level1'; + const relativePath = './target'; + + // When: Resolving the fixtures path + const resolved = resolveFixturesPath(relativePath, mockDirname); + + // Then: Should resolve to the path 3 levels up (root /) + expect(resolved).toBe('/target'); + }); + + it('should work with different relative path formats in dev mode', () => { + // Given: Various relative path formats + const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/main'; + + // When/Then: Different relative formats should all work + expect(resolveFixturesPath('resources/fixtures', mockDirname)) + .toBe('/Users/test/Projects/gridpilot/resources/fixtures'); + + expect(resolveFixturesPath('./resources/fixtures', mockDirname)) + .toBe('/Users/test/Projects/gridpilot/resources/fixtures'); + }); }); - it('should navigate exactly 4 levels up from app path', () => { - // Given: A path that demonstrates the 4-level navigation - const mockAppPath = '/level4/level3/level2/level1'; - const relativePath = './target'; + describe('absolute paths', () => { + it('should return absolute paths unchanged in built mode', () => { + // Given: An absolute path + const absolutePath = '/some/absolute/path/to/fixtures'; + const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/dist/main'; - // When: Resolving the fixtures path - const resolved = resolveFixturesPath(relativePath, mockAppPath); + // When: Resolving an absolute path + const resolved = resolveFixturesPath(absolutePath, mockDirname); - // Then: Should resolve to the path 4 levels up (root /) - // Level 0: /level4/level3/level2/level1 (appPath) - // Level 1: /level4/level3/level2 (../) - // Level 2: /level4/level3 (../../) - // Level 3: /level4 (../../../) - // Level 4: / (../../../../) ← monorepo root equivalent - expect(resolved).toBe('/target'); - }); + // Then: Should return the absolute path unchanged + expect(resolved).toBe(absolutePath); + }); - it('should work with different relative path formats', () => { - // Given: Various relative path formats - const mockAppPath = '/Users/test/Projects/gridpilot/apps/companion/dist/main'; + it('should return absolute paths unchanged in dev mode', () => { + // Given: An absolute path + const absolutePath = '/some/absolute/path/to/fixtures'; + const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/main'; - // When/Then: Different relative formats should all work - expect(resolveFixturesPath('resources/fixtures', mockAppPath)) - .toBe('/Users/test/Projects/gridpilot/resources/fixtures'); - - expect(resolveFixturesPath('./resources/fixtures', mockAppPath)) - .toBe('/Users/test/Projects/gridpilot/resources/fixtures'); + // When: Resolving an absolute path + const resolved = resolveFixturesPath(absolutePath, mockDirname); + + // Then: Should return the absolute path unchanged + expect(resolved).toBe(absolutePath); + }); }); }); }); \ No newline at end of file