mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
fix(cli): Fix Codex CLI execution issue in PowerShell with Hapi Codex (#763)
* 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>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
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: []
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user