mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
* fix(cli): fixed an issue where the codex cli failed to run successfully when using hapi codex in powershell * fix(cli): Fixes the issue of Windows Codex npm shim bypassing the launcher --------- Co-authored-by: xhd902 <xuhang@infypower.cn>
185 lines
5.9 KiB
TypeScript
185 lines
5.9 KiB
TypeScript
import { win32 } from 'node:path';
|
|
import { beforeAll, afterAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const { execFileSyncMock, existsSyncMock, homedirMock } = vi.hoisted(() => ({
|
|
execFileSyncMock: vi.fn(),
|
|
existsSyncMock: vi.fn(),
|
|
homedirMock: vi.fn(() => 'home\junes')
|
|
}));
|
|
|
|
vi.mock('node:child_process', async () => {
|
|
const actual = await vi.importActual<typeof import('node:child_process')>('node:child_process');
|
|
return {
|
|
...actual,
|
|
execFileSync: execFileSyncMock
|
|
};
|
|
});
|
|
|
|
vi.mock('node:fs', async () => {
|
|
const actual = await vi.importActual<typeof import('node:fs')>('node:fs');
|
|
return {
|
|
...actual,
|
|
existsSync: existsSyncMock
|
|
};
|
|
});
|
|
|
|
vi.mock('node:os', async () => {
|
|
const actual = await vi.importActual<typeof import('node:os')>('node:os');
|
|
return {
|
|
...actual,
|
|
homedir: homedirMock
|
|
};
|
|
});
|
|
|
|
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
|
const homeDir = win32.join('home', 'junes');
|
|
const nodeRoot = win32.join('toolchains', 'nodejs');
|
|
|
|
function codexShimPath(): string {
|
|
return win32.join(nodeRoot, 'codex.cmd');
|
|
}
|
|
|
|
function nativeCodexPath(): string {
|
|
return win32.join(
|
|
nodeRoot,
|
|
'node_modules',
|
|
'@openai',
|
|
'codex',
|
|
'node_modules',
|
|
'@openai',
|
|
'codex-win32-x64',
|
|
'vendor',
|
|
'x86_64-pc-windows-msvc',
|
|
'bin',
|
|
'codex.exe'
|
|
);
|
|
}
|
|
|
|
function codexScriptPath(): string {
|
|
return win32.join(nodeRoot, 'node_modules', '@openai', 'codex', 'bin', 'codex.js');
|
|
}
|
|
|
|
function userCodexExePath(): string {
|
|
return win32.join(homeDir, '.local', 'bin', 'codex.exe');
|
|
}
|
|
|
|
function setPlatform(value: string) {
|
|
Object.defineProperty(process, 'platform', {
|
|
value,
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
describe('resolveCodexCommand', () => {
|
|
beforeAll(() => {
|
|
if (!originalPlatformDescriptor?.configurable) {
|
|
throw new Error('process.platform is not configurable in this runtime');
|
|
}
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.resetModules();
|
|
homedirMock.mockReturnValue(homeDir);
|
|
execFileSyncMock.mockImplementation(() => {
|
|
throw new Error('not found');
|
|
});
|
|
existsSyncMock.mockReturnValue(false);
|
|
});
|
|
|
|
afterAll(() => {
|
|
if (originalPlatformDescriptor) {
|
|
Object.defineProperty(process, 'platform', originalPlatformDescriptor);
|
|
}
|
|
});
|
|
|
|
it('resolves a Windows npm codex.cmd shim through the Codex launcher', async () => {
|
|
setPlatform('win32');
|
|
const shim = codexShimPath();
|
|
const laterExe = userCodexExePath();
|
|
const executable = nativeCodexPath();
|
|
const script = codexScriptPath();
|
|
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
|
|
if (command === 'where.exe' && args[0] === 'codex') {
|
|
return `${shim}\r\n${laterExe}\r\n`;
|
|
}
|
|
throw new Error('not found');
|
|
});
|
|
existsSyncMock.mockImplementation((candidate: string) =>
|
|
candidate === shim || candidate === laterExe || candidate === executable || candidate === script
|
|
);
|
|
const { resolveCodexCommand } = await import('./codexExecutable');
|
|
|
|
expect(resolveCodexCommand()).toEqual({
|
|
command: 'node',
|
|
args: [script]
|
|
});
|
|
});
|
|
|
|
it('continues to the next Windows PATH candidate when a shim has no launcher script', async () => {
|
|
setPlatform('win32');
|
|
const shim = codexShimPath();
|
|
const executable = userCodexExePath();
|
|
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
|
|
if (command === 'where.exe' && args[0] === 'codex') {
|
|
return `${shim}\r\n${executable}\r\n`;
|
|
}
|
|
throw new Error('not found');
|
|
});
|
|
existsSyncMock.mockImplementation((candidate: string) => candidate === shim || candidate === executable);
|
|
const { resolveCodexCommand } = await import('./codexExecutable');
|
|
|
|
expect(resolveCodexCommand()).toEqual({
|
|
command: executable,
|
|
args: []
|
|
});
|
|
});
|
|
|
|
it('keeps a Windows codex.exe found first on PATH', async () => {
|
|
setPlatform('win32');
|
|
const executable = userCodexExePath();
|
|
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
|
|
if (command === 'where.exe' && args[0] === 'codex') {
|
|
return `${executable}\r\n`;
|
|
}
|
|
throw new Error('not found');
|
|
});
|
|
existsSyncMock.mockImplementation((candidate: string) => candidate === executable);
|
|
const { resolveCodexCommand } = await import('./codexExecutable');
|
|
|
|
expect(resolveCodexCommand()).toEqual({
|
|
command: executable,
|
|
args: []
|
|
});
|
|
});
|
|
|
|
it('falls back to node plus codex.js when a Windows shim has no native exe', async () => {
|
|
setPlatform('win32');
|
|
const shim = codexShimPath();
|
|
const script = codexScriptPath();
|
|
execFileSyncMock.mockImplementation((command: string, args: string[]) => {
|
|
if (command === 'where.exe' && args[0] === 'codex') {
|
|
return `${shim}\r\n`;
|
|
}
|
|
throw new Error('not found');
|
|
});
|
|
existsSyncMock.mockImplementation((candidate: string) => candidate === shim || candidate === script);
|
|
const { resolveCodexCommand } = await import('./codexExecutable');
|
|
|
|
expect(resolveCodexCommand()).toEqual({
|
|
command: 'node',
|
|
args: [script]
|
|
});
|
|
});
|
|
|
|
it('uses the plain codex command outside Windows', async () => {
|
|
setPlatform('linux');
|
|
const { resolveCodexCommand } = await import('./codexExecutable');
|
|
|
|
expect(resolveCodexCommand()).toEqual({
|
|
command: 'codex',
|
|
args: []
|
|
});
|
|
});
|
|
});
|