Files
makelore/electron/opencode/paths.ts
inman f4113a872f
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
收口 Makelore 客户端变更
2026-08-12 22:43:09 +08:00

117 lines
3.7 KiB
TypeScript

import { existsSync, readFileSync, realpathSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
export interface RuntimePathInput {
isPackaged: boolean;
resourcesPath: string;
appPath: string;
platform: NodeJS.Platform;
arch?: NodeJS.Architecture;
}
export interface OpencodeRuntimePaths {
runtimeDir: string;
binPath: string;
packageJsonPath: string;
}
const OPENCODE_RUNTIME_PACKAGE = 'opencode-ai';
const nativePlatformNames: Partial<Record<NodeJS.Platform, string>> = {
darwin: 'darwin',
linux: 'linux',
win32: 'windows',
};
const nativeArchNames: Partial<Record<NodeJS.Architecture, string>> = {
x64: 'x64',
arm64: 'arm64',
arm: 'arm',
};
function getDevelopmentNativePackageCandidates(
platform: NodeJS.Platform,
arch: NodeJS.Architecture,
): string[] {
const platformName = nativePlatformNames[platform] ?? platform;
const archName = nativeArchNames[arch] ?? arch;
const base = `opencode-${platformName}-${archName}`;
// The baseline x64 package runs on CPUs with and without AVX2, so prefer
// it when available. This mirrors the packaged-runtime bundler while
// avoiding opencode-ai's postinstall-generated wrapper in pnpm dev trees.
return archName === 'x64' ? [`${base}-baseline`, base] : [base];
}
function getNativeOpencodeBinName(platform: NodeJS.Platform): string {
return platform === 'win32' ? 'opencode.exe' : 'opencode';
}
function resolveNativePackageRoot(runtimeDir: string, packageName: string): string | undefined {
try {
const resolvedRuntimeDir = realpathSync(runtimeDir);
const runtimeRequire = createRequire(join(resolvedRuntimeDir, 'package.json'));
return dirname(runtimeRequire.resolve(`${packageName}/package.json`));
} catch {
return undefined;
}
}
function resolveDevelopmentNativeBinPath(
input: RuntimePathInput,
runtimeDir: string,
): string | undefined {
const arch = input.arch ?? process.arch;
for (const packageName of getDevelopmentNativePackageCandidates(input.platform, arch)) {
const binaryName = getNativeOpencodeBinName(input.platform);
const hoistedBinPath = join(input.appPath, 'node_modules', packageName, 'bin', binaryName);
if (existsSync(hoistedBinPath)) return hoistedBinPath;
const nativePackageRoot = resolveNativePackageRoot(runtimeDir, packageName);
if (!nativePackageRoot) continue;
const virtualStoreBinPath = join(nativePackageRoot, 'bin', binaryName);
if (existsSync(virtualStoreBinPath)) return virtualStoreBinPath;
}
return undefined;
}
export function getOpencodeBinName(
platform: NodeJS.Platform = process.platform,
isPackaged = false,
): string {
if (isPackaged && platform === 'win32') {
return 'opencode.exe';
}
return 'opencode';
}
export function resolveOpencodeRuntimePaths(input: RuntimePathInput): OpencodeRuntimePaths {
const runtimeDir = input.isPackaged
? join(input.resourcesPath, OPENCODE_RUNTIME_PACKAGE)
: join(input.appPath, 'node_modules', OPENCODE_RUNTIME_PACKAGE);
const developmentNativeBinPath = input.isPackaged
? undefined
: resolveDevelopmentNativeBinPath(input, runtimeDir);
return {
runtimeDir,
binPath: developmentNativeBinPath ?? join(runtimeDir, 'bin', getOpencodeBinName(input.platform, input.isPackaged)),
packageJsonPath: join(runtimeDir, 'package.json'),
};
}
export function readOpencodeRuntimeVersion(paths: OpencodeRuntimePaths): string | undefined {
if (!existsSync(paths.packageJsonPath)) {
return undefined;
}
const packageJson = JSON.parse(readFileSync(paths.packageJsonPath, 'utf8')) as {
version?: unknown;
};
return typeof packageJson.version === 'string' ? packageJson.version : undefined;
}