From bc1d57bdd63252cad276bbee252d65d6bb40ce18 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 May 2026 10:34:31 +0000 Subject: [PATCH 1/5] Initial plan From c93a2abe13cc5e15210f062484ab732ff159ba66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 May 2026 10:37:11 +0000 Subject: [PATCH 2/5] fix(setup): prefer .cmd wrapper from Windows `where` output On Windows, `where gitnexus` returns multiple entries including the POSIX shell script and the .cmd wrapper. The code previously took the first line (shell script), which cannot be spawned directly by Node.js child_process on Windows. Now we prefer the .cmd entry when available. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e6b54037-87fb-4195-b157-4cfcafce5f5d Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- gitnexus/package-lock.json | 3 --- gitnexus/src/cli/setup.ts | 21 +++++++++++++++------ gitnexus/test/unit/setup.test.ts | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/gitnexus/package-lock.json b/gitnexus/package-lock.json index 61ddca3057..475e9053bf 100644 --- a/gitnexus/package-lock.json +++ b/gitnexus/package-lock.json @@ -1228,9 +1228,6 @@ "win32" ] }, - "node_modules/@ladybugdb/core/node_modules/@ladybugdb/core-darwin-x64": { - "optional": true - }, "node_modules/@ladybugdb/core/node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", diff --git a/gitnexus/src/cli/setup.ts b/gitnexus/src/cli/setup.ts index 9a2be45057..6c83d8af3a 100644 --- a/gitnexus/src/cli/setup.ts +++ b/gitnexus/src/cli/setup.ts @@ -32,15 +32,24 @@ interface SetupResult { */ function resolveGitnexusBin(): string | null { try { - const cmd = process.platform === 'win32' ? 'where' : 'which'; - const resolved = execFileSync(cmd, ['gitnexus'], { + const isWin = process.platform === 'win32'; + const cmd = isWin ? 'where' : 'which'; + const output = execFileSync(cmd, ['gitnexus'], { encoding: 'utf-8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], - }) - .split('\n')[0] - .trim(); - return resolved || null; + }); + const lines = output.split('\n').map((l) => l.trim()).filter(Boolean); + + if (isWin) { + // On Windows, `where` returns multiple entries (e.g. the POSIX shell + // script AND the .cmd wrapper). Prefer the .cmd wrapper because + // child_process.spawn() cannot execute a shell script directly. + const cmdLine = lines.find((l) => l.toLowerCase().endsWith('.cmd')); + return cmdLine || lines[0] || null; + } + + return lines[0] || null; } catch { return null; } diff --git a/gitnexus/test/unit/setup.test.ts b/gitnexus/test/unit/setup.test.ts index 4544ddb025..e7d52a1a78 100644 --- a/gitnexus/test/unit/setup.test.ts +++ b/gitnexus/test/unit/setup.test.ts @@ -185,4 +185,23 @@ describe('setupClaudeCode', () => { args: ['-y', 'gitnexus@latest', 'mcp'], }); }); + + it('picks .cmd wrapper from Windows where output (multiple lines)', async () => { + setPlatform('win32'); + // `where gitnexus` on Windows returns the POSIX script first, then .cmd + execFileSyncMock.mockReturnValueOnce( + 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\nC:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.cmd\n', + ); + + const { setupCommand } = await import('../../src/cli/setup.js'); + await setupCommand(); + + const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8'); + const config = JSON.parse(raw); + + expect(config.mcpServers.gitnexus).toEqual({ + command: 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.cmd', + args: ['mcp'], + }); + }); }); From 81595d0b19553482438d74c9dd3bd6f4c46196f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 May 2026 10:39:24 +0000 Subject: [PATCH 3/5] fix(setup): also handle .bat wrappers and add fallback test Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e6b54037-87fb-4195-b157-4cfcafce5f5d Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- gitnexus/src/cli/setup.ts | 4 ++-- gitnexus/test/unit/setup.test.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/gitnexus/src/cli/setup.ts b/gitnexus/src/cli/setup.ts index 6c83d8af3a..f9dfcc97cb 100644 --- a/gitnexus/src/cli/setup.ts +++ b/gitnexus/src/cli/setup.ts @@ -43,9 +43,9 @@ function resolveGitnexusBin(): string | null { if (isWin) { // On Windows, `where` returns multiple entries (e.g. the POSIX shell - // script AND the .cmd wrapper). Prefer the .cmd wrapper because + // script AND the .cmd/.bat wrapper). Prefer the wrapper because // child_process.spawn() cannot execute a shell script directly. - const cmdLine = lines.find((l) => l.toLowerCase().endsWith('.cmd')); + const cmdLine = lines.find((l) => /\.(cmd|bat)$/i.test(l)); return cmdLine || lines[0] || null; } diff --git a/gitnexus/test/unit/setup.test.ts b/gitnexus/test/unit/setup.test.ts index e7d52a1a78..e35d521f81 100644 --- a/gitnexus/test/unit/setup.test.ts +++ b/gitnexus/test/unit/setup.test.ts @@ -204,4 +204,23 @@ describe('setupClaudeCode', () => { args: ['mcp'], }); }); + + it('falls back to first line on Windows when no .cmd/.bat wrapper found', async () => { + setPlatform('win32'); + // Edge case: where returns only the POSIX script (no .cmd wrapper) + execFileSyncMock.mockReturnValueOnce( + 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\n', + ); + + const { setupCommand } = await import('../../src/cli/setup.js'); + await setupCommand(); + + const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8'); + const config = JSON.parse(raw); + + expect(config.mcpServers.gitnexus).toEqual({ + command: 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus', + args: ['mcp'], + }); + }); }); From 86c2b1e368eb2f1a402a2d8d73e5b55a824f1e33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 May 2026 12:54:00 +0000 Subject: [PATCH 4/5] revert package-lock.json and add CRLF/.bat/.CMD test variants - Revert package-lock.json to match base (no dependency changes needed) - Add CRLF line ending test (Windows `where` produces \r\n) - Add .bat wrapper test - Add uppercase .CMD extension test (case-insensitive regex) Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7ed71368-b3e8-44de-9f13-85af4effaf25 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- gitnexus/package-lock.json | 3 ++ gitnexus/test/unit/setup.test.ts | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/gitnexus/package-lock.json b/gitnexus/package-lock.json index 475e9053bf..61ddca3057 100644 --- a/gitnexus/package-lock.json +++ b/gitnexus/package-lock.json @@ -1228,6 +1228,9 @@ "win32" ] }, + "node_modules/@ladybugdb/core/node_modules/@ladybugdb/core-darwin-x64": { + "optional": true + }, "node_modules/@ladybugdb/core/node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", diff --git a/gitnexus/test/unit/setup.test.ts b/gitnexus/test/unit/setup.test.ts index e35d521f81..e436ca8700 100644 --- a/gitnexus/test/unit/setup.test.ts +++ b/gitnexus/test/unit/setup.test.ts @@ -205,6 +205,61 @@ describe('setupClaudeCode', () => { }); }); + it('handles CRLF line endings from Windows where output', async () => { + setPlatform('win32'); + // Windows `where` produces CRLF line endings + execFileSyncMock.mockReturnValueOnce( + 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\r\nC:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.cmd\r\n', + ); + + const { setupCommand } = await import('../../src/cli/setup.js'); + await setupCommand(); + + const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8'); + const config = JSON.parse(raw); + + expect(config.mcpServers.gitnexus).toEqual({ + command: 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.cmd', + args: ['mcp'], + }); + }); + + it('picks .bat wrapper when .cmd is not present', async () => { + setPlatform('win32'); + execFileSyncMock.mockReturnValueOnce( + 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\nC:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.bat\n', + ); + + const { setupCommand } = await import('../../src/cli/setup.js'); + await setupCommand(); + + const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8'); + const config = JSON.parse(raw); + + expect(config.mcpServers.gitnexus).toEqual({ + command: 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.bat', + args: ['mcp'], + }); + }); + + it('handles uppercase .CMD extension (case-insensitive match)', async () => { + setPlatform('win32'); + execFileSyncMock.mockReturnValueOnce( + 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\nC:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.CMD\n', + ); + + const { setupCommand } = await import('../../src/cli/setup.js'); + await setupCommand(); + + const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8'); + const config = JSON.parse(raw); + + expect(config.mcpServers.gitnexus).toEqual({ + command: 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.CMD', + args: ['mcp'], + }); + }); + it('falls back to first line on Windows when no .cmd/.bat wrapper found', async () => { setPlatform('win32'); // Edge case: where returns only the POSIX script (no .cmd wrapper) From da61050b9b4cba9e84725efbd551ed59effc6183 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Sun, 3 May 2026 14:08:51 +0100 Subject: [PATCH 5/5] chore: format code --- gitnexus/src/cli/setup.ts | 5 ++++- gitnexus/test/unit/setup.test.ts | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gitnexus/src/cli/setup.ts b/gitnexus/src/cli/setup.ts index f9dfcc97cb..b301d7f38f 100644 --- a/gitnexus/src/cli/setup.ts +++ b/gitnexus/src/cli/setup.ts @@ -39,7 +39,10 @@ function resolveGitnexusBin(): string | null { timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], }); - const lines = output.split('\n').map((l) => l.trim()).filter(Boolean); + const lines = output + .split('\n') + .map((l) => l.trim()) + .filter(Boolean); if (isWin) { // On Windows, `where` returns multiple entries (e.g. the POSIX shell diff --git a/gitnexus/test/unit/setup.test.ts b/gitnexus/test/unit/setup.test.ts index e436ca8700..6cab314679 100644 --- a/gitnexus/test/unit/setup.test.ts +++ b/gitnexus/test/unit/setup.test.ts @@ -263,9 +263,7 @@ describe('setupClaudeCode', () => { it('falls back to first line on Windows when no .cmd/.bat wrapper found', async () => { setPlatform('win32'); // Edge case: where returns only the POSIX script (no .cmd wrapper) - execFileSyncMock.mockReturnValueOnce( - 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\n', - ); + execFileSyncMock.mockReturnValueOnce('C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\n'); const { setupCommand } = await import('../../src/cli/setup.js'); await setupCommand();