mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-22 00:31:17 +00:00
Merge branch 'main' into copilot/chore-remediate-gitnexus-web-devdep-advisories
This commit is contained in:
commit
fa13265a6b
7 changed files with 307 additions and 608 deletions
786
gitnexus/package-lock.json
generated
786
gitnexus/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -60,7 +60,7 @@
|
|||
"cli-progress": "^3.12.0",
|
||||
"commander": "^14.0.3",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2",
|
||||
"express": "^5.2.1",
|
||||
"express-rate-limit": "^8.4.1",
|
||||
"glob": "^13.0.6",
|
||||
"graphology": "^0.26.0",
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
"devDependencies": {
|
||||
"@types/cli-progress": "^3.11.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/uuid": "^11.0.0",
|
||||
|
|
|
|||
|
|
@ -61,11 +61,13 @@ function resolveGitnexusBin(): string | null {
|
|||
.filter(Boolean);
|
||||
|
||||
if (isWin) {
|
||||
// On Windows, `where` returns multiple entries (e.g. the POSIX shell
|
||||
// script AND the .cmd/.bat wrapper). Prefer the wrapper because
|
||||
// child_process.spawn() cannot execute a shell script directly.
|
||||
// On Windows, npm global installs can surface multiple launchers for the
|
||||
// same package (e.g. a POSIX shell shim plus .cmd/.bat wrappers). Claude
|
||||
// and the other MCP hosts need a directly spawnable command path, so only
|
||||
// accept the Windows wrapper. If it is missing, fall back to the slower
|
||||
// npx entry instead of persisting a non-spawnable shim path.
|
||||
const cmdLine = lines.find((l) => /\.(cmd|bat)$/i.test(l));
|
||||
return cmdLine || lines[0] || null;
|
||||
return cmdLine || null;
|
||||
}
|
||||
|
||||
return lines[0] || null;
|
||||
|
|
|
|||
|
|
@ -448,7 +448,14 @@ export const streamGraphNdjson = async (
|
|||
*/
|
||||
const mountSSEProgress = (app: express.Express, routePath: string, jm: JobManager) => {
|
||||
app.get(routePath, (req, res) => {
|
||||
const job = jm.getJob(req.params.jobId);
|
||||
let jobId: string;
|
||||
try {
|
||||
jobId = assertString(req.params.jobId, 'jobId');
|
||||
} catch (err: any) {
|
||||
res.status(err.status ?? 400).json({ error: err.message });
|
||||
return;
|
||||
}
|
||||
const job = jm.getJob(jobId);
|
||||
if (!job) {
|
||||
res.status(404).json({ error: 'Job not found' });
|
||||
return;
|
||||
|
|
@ -494,7 +501,7 @@ const mountSSEProgress = (app: express.Express, routePath: string, jm: JobManage
|
|||
try {
|
||||
eventId++;
|
||||
if (progress.phase === 'complete' || progress.phase === 'failed') {
|
||||
const eventJob = jm.getJob(req.params.jobId);
|
||||
const eventJob = jm.getJob(jobId);
|
||||
res.write(
|
||||
`id: ${eventId}\nevent: ${progress.phase}\ndata: ${JSON.stringify({
|
||||
repoName: eventJob?.repoName,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,13 @@ const execFileMock = vi.fn((...args: any[]) => {
|
|||
}
|
||||
});
|
||||
|
||||
const execFileSyncMock = vi.fn(() => {
|
||||
throw new Error('not found');
|
||||
});
|
||||
|
||||
vi.mock('child_process', () => ({
|
||||
execFile: execFileMock,
|
||||
execFileSync: execFileSyncMock,
|
||||
}));
|
||||
|
||||
describe('setupCommand codex execution', () => {
|
||||
|
|
@ -74,6 +79,21 @@ describe('setupCommand codex execution', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('uses Windows npx fallback arguments when where returns only a non-wrapper shim', async () => {
|
||||
execFileSyncMock.mockReturnValueOnce('C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\n');
|
||||
|
||||
const { setupCommand } = await import('../../src/cli/setup.js');
|
||||
|
||||
await setupCommand();
|
||||
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
'codex',
|
||||
['mcp', 'add', 'gitnexus', '--', 'cmd', '/c', 'npx', '-y', NPX_REF, 'mcp'],
|
||||
{ shell: true },
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('invokes codex mcp add without shell on non-Windows and does not write fallback config', async () => {
|
||||
setPlatform('darwin');
|
||||
|
||||
|
|
|
|||
|
|
@ -241,6 +241,50 @@ describe('setupOpenCode — JSONC preservation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('uses Windows npx fallback when where returns only a non-wrapper shim', async () => {
|
||||
setPlatform('win32');
|
||||
execFileSyncMock.mockReturnValueOnce('C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\n');
|
||||
|
||||
const jsonc = `{
|
||||
"model": "test",
|
||||
"mcp": {}
|
||||
}`;
|
||||
await fs.writeFile(opencodeJsonPath(), jsonc, 'utf-8');
|
||||
|
||||
const { setupCommand } = await import('../../src/cli/setup.js');
|
||||
await setupCommand();
|
||||
|
||||
const raw = await fs.readFile(opencodeJsonPath(), 'utf-8');
|
||||
const config = parseJsonc(raw);
|
||||
|
||||
expect(config.mcp.gitnexus).toEqual({
|
||||
type: 'local',
|
||||
command: ['cmd', '/c', 'npx', '-y', NPX_REF, 'mcp'],
|
||||
});
|
||||
});
|
||||
|
||||
it('uses Windows npx fallback when where returns only a .ps1 path', async () => {
|
||||
setPlatform('win32');
|
||||
execFileSyncMock.mockReturnValueOnce('C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.ps1\n');
|
||||
|
||||
const jsonc = `{
|
||||
"model": "test",
|
||||
"mcp": {}
|
||||
}`;
|
||||
await fs.writeFile(opencodeJsonPath(), jsonc, 'utf-8');
|
||||
|
||||
const { setupCommand } = await import('../../src/cli/setup.js');
|
||||
await setupCommand();
|
||||
|
||||
const raw = await fs.readFile(opencodeJsonPath(), 'utf-8');
|
||||
const config = parseJsonc(raw);
|
||||
|
||||
expect(config.mcp.gitnexus).toEqual({
|
||||
type: 'local',
|
||||
command: ['cmd', '/c', 'npx', '-y', NPX_REF, 'mcp'],
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves tab indentation in existing file', async () => {
|
||||
const tabbed = `{\n\t"model": "test"\n}`;
|
||||
await fs.writeFile(opencodeJsonPath(), tabbed, 'utf-8');
|
||||
|
|
@ -360,6 +404,22 @@ describe('setupCursor — JSONC preservation', () => {
|
|||
const raw = await fs.readFile(mcpPath(), 'utf-8');
|
||||
expect(raw).toBe(corrupt);
|
||||
});
|
||||
|
||||
it('uses Windows npx fallback when where returns only a non-wrapper shim', async () => {
|
||||
setPlatform('win32');
|
||||
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(mcpPath(), 'utf-8');
|
||||
const config = parseJsonc(raw);
|
||||
|
||||
expect(config.mcpServers.gitnexus).toEqual({
|
||||
command: 'cmd',
|
||||
args: ['/c', 'npx', '-y', NPX_REF, 'mcp'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupClaudeCode — JSONC preservation', () => {
|
||||
|
|
|
|||
|
|
@ -282,9 +282,9 @@ describe('setupClaudeCode', () => {
|
|||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('falls back to first line on Windows when no .cmd/.bat wrapper found', async () => {
|
||||
it('falls back to npx on Windows when no .cmd/.bat wrapper is found', async () => {
|
||||
setPlatform('win32');
|
||||
// Edge case: where returns only the POSIX script (no .cmd wrapper)
|
||||
// Edge case: where returns only a non-spawnable shim (no .cmd wrapper)
|
||||
execFileSyncMock.mockReturnValueOnce('C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus\n');
|
||||
|
||||
const { setupCommand } = await import('../../src/cli/setup.js');
|
||||
|
|
@ -294,8 +294,24 @@ describe('setupClaudeCode', () => {
|
|||
const config = JSON.parse(raw);
|
||||
|
||||
expect(config.mcpServers.gitnexus).toEqual({
|
||||
command: 'C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus',
|
||||
args: ['mcp'],
|
||||
command: 'cmd',
|
||||
args: ['/c', 'npx', '-y', NPX_REF, 'mcp'],
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to npx on Windows when where returns only a .ps1 path', async () => {
|
||||
setPlatform('win32');
|
||||
execFileSyncMock.mockReturnValueOnce('C:\\Users\\dev\\AppData\\Roaming\\npm\\gitnexus.ps1\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: 'cmd',
|
||||
args: ['/c', 'npx', '-y', NPX_REF, 'mcp'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue