mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
fix(setup): honor CODEX_HOME and redact TOML errors
This commit is contained in:
parent
8019281e74
commit
dfe2e48249
3 changed files with 91 additions and 8 deletions
|
|
@ -36,7 +36,7 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
|
|||
|
||||
`gitnexus setup` auto-detects your editors and writes the correct global MCP config. You only need to run it once. To configure only selected integrations, pass `--coding-agent`/`-c` with a comma-separated list or repeat the option, for example `gitnexus setup -c cursor,codex`.
|
||||
|
||||
For Claude Code, Cursor, and Codex, setup keeps an existing URL-based `gitnexus` MCP entry and its authentication settings; skills and hooks are still installed.
|
||||
For Claude Code, Cursor, and Codex, setup keeps an existing URL-based `gitnexus` MCP entry and its authentication settings. Skills are still installed; setup installs Claude Code and Codex hooks, while Cursor hooks require [manual installation](../gitnexus-cursor-integration/README.md#hook-install).
|
||||
|
||||
### Editor Support
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
ParseError,
|
||||
parse as parseJsonc,
|
||||
} from 'jsonc-parser';
|
||||
import { parse as parseToml } from 'smol-toml';
|
||||
import { parse as parseToml, TomlError } from 'smol-toml';
|
||||
import { packageVersion } from '../core/package-version.js';
|
||||
import { getGlobalDir } from '../storage/repo-manager.js';
|
||||
import {
|
||||
|
|
@ -1091,19 +1091,27 @@ async function codexHasHttpMcpEntry(configPath: string): Promise<boolean> {
|
|||
}
|
||||
|
||||
async function setupCodex(result: SetupResult): Promise<void> {
|
||||
const codexDir = path.join(os.homedir(), '.codex');
|
||||
if (!(await dirExists(codexDir))) {
|
||||
const configuredHome = process.env.CODEX_HOME;
|
||||
const codexDir = configuredHome
|
||||
? path.resolve(configuredHome)
|
||||
: path.join(os.homedir(), '.codex');
|
||||
if (!configuredHome && !(await dirExists(codexDir))) {
|
||||
result.skipped.push('Codex (not installed)');
|
||||
return;
|
||||
}
|
||||
|
||||
const configPath = path.join(codexDir, 'config.toml');
|
||||
try {
|
||||
if (await codexHasHttpMcpEntry(getEditorTargets().codex.configFile)) {
|
||||
if (await codexHasHttpMcpEntry(configPath)) {
|
||||
result.configured.push('Codex (existing HTTP MCP entry kept)');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
result.errors.push(`Codex: ${err instanceof Error ? err.message : String(err)}`);
|
||||
result.errors.push(
|
||||
err instanceof TomlError
|
||||
? `Codex: invalid config.toml (line ${err.line}, column ${err.column}); MCP entry unchanged`
|
||||
: `Codex: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1120,9 +1128,8 @@ async function setupCodex(result: SetupResult): Promise<void> {
|
|||
}
|
||||
|
||||
try {
|
||||
const configPath = getEditorTargets().codex.configFile;
|
||||
await upsertCodexConfigToml(configPath);
|
||||
result.configured.push('Codex (MCP added to ~/.codex/config.toml)');
|
||||
result.configured.push('Codex (MCP added to active config.toml)');
|
||||
} catch (err: any) {
|
||||
result.errors.push(`Codex: ${err.message}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ describe('setupCommand codex execution', () => {
|
|||
let tempHome: string;
|
||||
let originalHome: string | undefined;
|
||||
let originalUserProfile: string | undefined;
|
||||
let originalCodexHome: string | undefined;
|
||||
let platformDescriptor: PropertyDescriptor | undefined;
|
||||
|
||||
const setPlatform = (value: NodeJS.Platform) => {
|
||||
|
|
@ -42,6 +43,8 @@ describe('setupCommand codex execution', () => {
|
|||
|
||||
originalHome = process.env.HOME;
|
||||
originalUserProfile = process.env.USERPROFILE;
|
||||
originalCodexHome = process.env.CODEX_HOME;
|
||||
delete process.env.CODEX_HOME;
|
||||
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-codex-setup-'));
|
||||
process.env.HOME = tempHome;
|
||||
process.env.USERPROFILE = tempHome;
|
||||
|
|
@ -62,6 +65,8 @@ describe('setupCommand codex execution', () => {
|
|||
|
||||
process.env.HOME = originalHome;
|
||||
process.env.USERPROFILE = originalUserProfile;
|
||||
if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
|
||||
else process.env.CODEX_HOME = originalCodexHome;
|
||||
await fs.rm(tempHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
|
|
@ -126,6 +131,77 @@ describe('setupCommand codex execution', () => {
|
|||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('does not mistake an HTTP entry in the default home for the active CODEX_HOME entry', async () => {
|
||||
const defaultConfig = path.join(tempHome, '.codex', 'config.toml');
|
||||
const activeHome = path.join(tempHome, 'active-codex');
|
||||
await fs.writeFile(defaultConfig, '[mcp_servers.gitnexus]\nurl = "https://example.test/mcp"\n');
|
||||
await fs.mkdir(activeHome);
|
||||
process.env.CODEX_HOME = activeHome;
|
||||
|
||||
const { setupCommand } = await import('../../src/cli/setup.js');
|
||||
await setupCommand({ codingAgent: 'codex' });
|
||||
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
'codex',
|
||||
['mcp', 'add', 'gitnexus', '--', 'cmd', '/c', 'npx', '-y', NPX_REF, 'mcp'],
|
||||
{ shell: true, windowsHide: true },
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves an HTTP entry in the active CODEX_HOME even when the default home is empty', async () => {
|
||||
const activeHome = path.join(tempHome, 'active-codex');
|
||||
const activeConfig = path.join(activeHome, 'config.toml');
|
||||
const raw = '[mcp_servers.gitnexus]\nurl = "https://example.test/mcp"\n';
|
||||
await fs.mkdir(activeHome);
|
||||
await fs.writeFile(activeConfig, raw);
|
||||
process.env.CODEX_HOME = activeHome;
|
||||
|
||||
const { setupCommand } = await import('../../src/cli/setup.js');
|
||||
await setupCommand({ codingAgent: 'codex' });
|
||||
|
||||
expect(execFileMock).not.toHaveBeenCalled();
|
||||
expect(await fs.readFile(activeConfig, 'utf-8')).toBe(raw);
|
||||
});
|
||||
|
||||
it('writes the fallback entry to the active CODEX_HOME when the Codex CLI is unavailable', async () => {
|
||||
const activeHome = path.join(tempHome, 'active-codex');
|
||||
process.env.CODEX_HOME = activeHome;
|
||||
execFileMock.mockImplementationOnce((...args: unknown[]) => {
|
||||
const callback = args.at(-1);
|
||||
if (typeof callback === 'function') callback(new Error('codex unavailable'));
|
||||
});
|
||||
|
||||
const { setupCommand } = await import('../../src/cli/setup.js');
|
||||
await setupCommand({ codingAgent: 'codex' });
|
||||
|
||||
expect(await fs.readFile(path.join(activeHome, 'config.toml'), 'utf-8')).toContain(
|
||||
'[mcp_servers.gitnexus]',
|
||||
);
|
||||
await expect(fs.access(path.join(tempHome, '.codex', 'config.toml'))).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('does not print TOML source or nearby credentials when the active config is malformed', async () => {
|
||||
const activeHome = path.join(tempHome, 'active-codex');
|
||||
const configPath = path.join(activeHome, 'config.toml');
|
||||
const token = 'Bearer FAKE_PR3460_REVIEW_TOKEN';
|
||||
await fs.mkdir(activeHome);
|
||||
await fs.writeFile(
|
||||
configPath,
|
||||
`[mcp_servers.gitnexus]\nhttp_headers = { Authorization = "${token}" }\ninvalid =\n`,
|
||||
);
|
||||
process.env.CODEX_HOME = activeHome;
|
||||
|
||||
const { setupCommand } = await import('../../src/cli/setup.js');
|
||||
await setupCommand({ codingAgent: 'codex' });
|
||||
|
||||
const output = JSON.stringify(vi.mocked(console.log).mock.calls);
|
||||
expect(output).toMatch(/invalid config\.toml \(line 3, column \d+\)/);
|
||||
expect(output).not.toContain(token);
|
||||
expect(execFileMock).not.toHaveBeenCalled();
|
||||
expect(await fs.readFile(configPath, 'utf-8')).toContain(token);
|
||||
});
|
||||
|
||||
it('skips Codex setup entirely when ~/.codex is missing', async () => {
|
||||
await fs.rm(path.join(tempHome, '.codex'), { recursive: true, force: true });
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue