fix(setup): share active Codex config path with uninstall

This commit is contained in:
azizur100389 2026-10-04 21:41:57 +01:00
parent dfe2e48249
commit 44a2905330
3 changed files with 35 additions and 8 deletions

View file

@ -56,7 +56,7 @@ export interface McpJsoncTarget {
export interface CodexMcpTarget {
id: 'codex';
label: string;
/** Absolute path to ~/.codex/config.toml. */
/** Absolute path to CODEX_HOME/config.toml (default ~/.codex/config.toml). */
configFile: string;
/** The TOML table header (without brackets) setup writes / uninstall strips. */
tomlSection: string;
@ -100,6 +100,9 @@ export interface EditorTargets {
* `process.env.HOME` before invoking sees the right locations.
*/
export function getEditorTargets(home: string = os.homedir()): EditorTargets {
const codexHome = process.env.CODEX_HOME
? path.resolve(process.env.CODEX_HOME)
: path.join(home, '.codex');
const mcpJsonc: McpJsoncTarget[] = [
{
id: 'cursor',
@ -166,7 +169,7 @@ export function getEditorTargets(home: string = os.homedir()): EditorTargets {
const codex: CodexMcpTarget = {
id: 'codex',
label: 'Codex',
configFile: path.join(home, '.codex', 'config.toml'),
configFile: path.join(codexHome, 'config.toml'),
tomlSection: 'mcp_servers.gitnexus',
};

View file

@ -1091,16 +1091,13 @@ async function codexHasHttpMcpEntry(configPath: string): Promise<boolean> {
}
async function setupCodex(result: SetupResult): Promise<void> {
const configuredHome = process.env.CODEX_HOME;
const codexDir = configuredHome
? path.resolve(configuredHome)
: path.join(os.homedir(), '.codex');
if (!configuredHome && !(await dirExists(codexDir))) {
const configPath = getEditorTargets().codex.configFile;
const codexDir = path.dirname(configPath);
if (!process.env.CODEX_HOME && !(await dirExists(codexDir))) {
result.skipped.push('Codex (not installed)');
return;
}
const configPath = path.join(codexDir, 'config.toml');
try {
if (await codexHasHttpMcpEntry(configPath)) {
result.configured.push('Codex (existing HTTP MCP entry kept)');

View file

@ -23,6 +23,7 @@ describe('uninstallCommand', () => {
let tempHome: string;
let originalHome: string | undefined;
let originalUserProfile: string | undefined;
let originalCodexHome: string | undefined;
let originalSkillsRoot: string | undefined;
let originalExitCode: typeof process.exitCode;
let skillsRoot: string;
@ -35,6 +36,8 @@ describe('uninstallCommand', () => {
originalHome = process.env.HOME;
originalUserProfile = process.env.USERPROFILE;
originalCodexHome = process.env.CODEX_HOME;
delete process.env.CODEX_HOME;
originalSkillsRoot = process.env.GITNEXUS_TEST_SKILLS_ROOT;
originalExitCode = process.exitCode;
@ -57,6 +60,8 @@ describe('uninstallCommand', () => {
vi.restoreAllMocks();
process.env.HOME = originalHome;
process.env.USERPROFILE = originalUserProfile;
if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
else process.env.CODEX_HOME = originalCodexHome;
if (originalSkillsRoot === undefined) delete process.env.GITNEXUS_TEST_SKILLS_ROOT;
else process.env.GITNEXUS_TEST_SKILLS_ROOT = originalSkillsRoot;
// The command sets process.exitCode=1 on partial failure; restore it so a
@ -203,6 +208,28 @@ describe('uninstallCommand', () => {
expect(result).toContain('command = "other"');
});
it('dry-runs and removes only the active CODEX_HOME MCP entry', async () => {
const activeHome = path.join(tempHome, 'active-codex');
const activeConfig = path.join(activeHome, 'config.toml');
const defaultConfig = path.join(tempHome, '.codex', 'config.toml');
const activeRaw = '[mcp_servers.gitnexus]\ncommand = "active"\n';
const defaultRaw = '[mcp_servers.gitnexus]\ncommand = "default"\n';
await fs.mkdir(activeHome);
await fs.mkdir(path.dirname(defaultConfig));
await fs.writeFile(activeConfig, activeRaw);
await fs.writeFile(defaultConfig, defaultRaw);
process.env.CODEX_HOME = activeHome;
const uninstallCommand = await importUninstall();
await uninstallCommand();
expect(vi.mocked(console.log).mock.calls.flat().join('\n')).toContain(activeConfig);
expect(await fs.readFile(activeConfig, 'utf-8')).toBe(activeRaw);
await uninstallCommand({ force: true });
expect(await fs.readFile(activeConfig, 'utf-8')).not.toContain('[mcp_servers.gitnexus]');
expect(await fs.readFile(defaultConfig, 'utf-8')).toBe(defaultRaw);
});
it('leaves a corrupt JSON config untouched', async () => {
const claudeJson = path.join(tempHome, '.claude.json');
const corrupt = '{ not valid json !!!';