// @vitest-environment node import { join } from 'node:path'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getOpencodeBinName, readOpencodeRuntimeVersion, resolveOpencodeRuntimePaths, } from '@electron/opencode/paths'; const fsMock = vi.hoisted(() => ({ packageJson: undefined as string | undefined, existingPaths: new Set(), })); vi.mock('node:fs', () => ({ existsSync: (path: string) => fsMock.packageJson !== undefined || fsMock.existingPaths.has(path), readFileSync: () => fsMock.packageJson, })); describe('opencode runtime paths', () => { beforeEach(() => { fsMock.packageJson = undefined; fsMock.existingPaths.clear(); }); it('resolves packaged darwin runtime paths from resourcesPath', () => { const resourcesPath = '/Applications/NianCode.app/Contents/Resources'; const paths = resolveOpencodeRuntimePaths({ isPackaged: true, resourcesPath, appPath: '/Applications/NianCode.app/Contents/Resources/app.asar', platform: 'darwin', }); const runtimeDir = join(resourcesPath, 'opencode-ai'); expect(paths).toEqual({ runtimeDir, binPath: join(runtimeDir, 'bin', 'opencode'), packageJsonPath: join(runtimeDir, 'package.json'), }); }); it('resolves dev linux runtime paths from appPath node_modules', () => { const appPath = '/home/me/niancode'; const paths = resolveOpencodeRuntimePaths({ isPackaged: false, resourcesPath: '/unused/resources', appPath, platform: 'linux', arch: 'x64', }); const runtimeDir = join(appPath, 'node_modules', 'opencode-ai'); expect(paths).toEqual({ runtimeDir, binPath: join(runtimeDir, 'bin', 'opencode'), packageJsonPath: join(runtimeDir, 'package.json'), }); }); it('uses the installed platform-native binary in development when pnpm skips opencode-ai postinstall', () => { const appPath = '/Users/me/NianCode'; const nativeBinPath = join(appPath, 'node_modules', 'opencode-darwin-arm64', 'bin', 'opencode'); fsMock.existingPaths.add(nativeBinPath); expect(resolveOpencodeRuntimePaths({ isPackaged: false, resourcesPath: '/unused/resources', appPath, platform: 'darwin', arch: 'arm64', })).toMatchObject({ runtimeDir: join(appPath, 'node_modules', 'opencode-ai'), binPath: nativeBinPath, packageJsonPath: join(appPath, 'node_modules', 'opencode-ai', 'package.json'), }); }); it('resolves packaged Windows runtime paths to the bundled native exe', () => { const resourcesPath = 'C:\\Program Files\\NianCode\\resources'; const paths = resolveOpencodeRuntimePaths({ isPackaged: true, resourcesPath, appPath: 'C:\\Program Files\\NianCode\\resources\\app.asar', platform: 'win32', }); const runtimeDir = join(resourcesPath, 'opencode-ai'); expect(paths).toEqual({ runtimeDir, binPath: join(runtimeDir, 'bin', 'opencode.exe'), packageJsonPath: join(runtimeDir, 'package.json'), }); }); it('uses the package wrapper path for development runtime paths', () => { expect(getOpencodeBinName('win32')).toBe('opencode'); expect(getOpencodeBinName('linux')).toBe('opencode'); expect(getOpencodeBinName('win32', true)).toBe('opencode.exe'); }); it('reads the opencode runtime package version when package.json exists', () => { fsMock.packageJson = JSON.stringify({ version: '1.2.3' }); expect(readOpencodeRuntimeVersion({ runtimeDir: '/runtime/opencode', binPath: '/runtime/opencode/bin/opencode', packageJsonPath: '/runtime/opencode/package.json', })).toBe('1.2.3'); }); it('returns undefined when the runtime package.json is missing', () => { expect(readOpencodeRuntimeVersion({ runtimeDir: '/runtime/opencode', binPath: '/runtime/opencode/bin/opencode', packageJsonPath: '/runtime/opencode/package.json', })).toBeUndefined(); }); });