mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-12 08:41:34 +00:00
feat(cli): refactor indexCommand for improved error handling and add unit tests
This commit is contained in:
parent
c1996f45d8
commit
680cefd00c
2 changed files with 159 additions and 27 deletions
|
|
@ -9,17 +9,14 @@
|
|||
* shared team index).
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
import { execFileSync } from 'child_process';
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import {
|
||||
getStoragePaths,
|
||||
loadMeta,
|
||||
registerRepo,
|
||||
addToGitignore,
|
||||
getGlobalRegistryPath,
|
||||
} from '../storage/repo-manager.js';
|
||||
import { getGitRoot, isGitRepo } from '../storage/git.js';
|
||||
} from "../storage/repo-manager.js";
|
||||
import { getGitRoot, isGitRepo } from "../storage/git.js";
|
||||
|
||||
export interface IndexOptions {
|
||||
force?: boolean;
|
||||
|
|
@ -29,9 +26,11 @@ export const indexCommand = async (
|
|||
inputPathParts?: string[],
|
||||
options?: IndexOptions,
|
||||
) => {
|
||||
console.log('\n GitNexus Index\n');
|
||||
console.log("\n GitNexus Index\n");
|
||||
|
||||
const inputPath = inputPathParts?.length ? inputPathParts.join(' ') : undefined;
|
||||
const inputPath = inputPathParts?.length
|
||||
? inputPathParts.join(" ")
|
||||
: undefined;
|
||||
|
||||
let repoPath: string;
|
||||
if (inputPath) {
|
||||
|
|
@ -39,21 +38,28 @@ export const indexCommand = async (
|
|||
} else {
|
||||
const gitRoot = getGitRoot(process.cwd());
|
||||
if (!gitRoot) {
|
||||
console.log(' Not inside a git repository, try to run git init\n');
|
||||
console.log(" Not inside a git repository, try to run git init\n");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
repoPath = gitRoot;
|
||||
}
|
||||
|
||||
const { storagePath, lbugPath, metaPath } = getStoragePaths(repoPath);
|
||||
if (!isGitRepo(repoPath)) {
|
||||
console.log(` Not a git repository: ${repoPath}`);
|
||||
console.log(" Initialize one with `git init` or choose a valid repo path.\n");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const { storagePath, lbugPath } = getStoragePaths(repoPath);
|
||||
|
||||
// ── Verify .gitnexus/ exists ──────────────────────────────────────
|
||||
try {
|
||||
await fs.access(storagePath);
|
||||
} catch {
|
||||
console.log(` No .gitnexus/ folder found at: ${storagePath}`);
|
||||
console.log(' Run `gitnexus analyze` to build the index first.\n');
|
||||
console.log(" Run `gitnexus analyze` to build the index first.\n");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
|
@ -63,7 +69,7 @@ export const indexCommand = async (
|
|||
await fs.access(lbugPath);
|
||||
} catch {
|
||||
console.log(` .gitnexus/ folder exists but contains no LadybugDB index.`);
|
||||
console.log(' Run `gitnexus analyze` to build the index.\n');
|
||||
console.log(" Run `gitnexus analyze` to build the index.\n");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
|
@ -74,8 +80,8 @@ export const indexCommand = async (
|
|||
if (!meta) {
|
||||
if (!options?.force) {
|
||||
console.log(` .gitnexus/ exists but meta.json is missing.`);
|
||||
console.log(' Use --force to register anyway (stats will be empty),');
|
||||
console.log(' or run `gitnexus analyze` to rebuild properly.\n');
|
||||
console.log(" Use --force to register anyway (stats will be empty),");
|
||||
console.log(" or run `gitnexus analyze` to rebuild properly.\n");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
|
@ -83,14 +89,13 @@ export const indexCommand = async (
|
|||
// --force: build a minimal meta so the repo can be registered
|
||||
meta = {
|
||||
repoPath,
|
||||
lastCommit: '',
|
||||
lastCommit: "",
|
||||
indexedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Register in global registry ───────────────────────────────────
|
||||
await registerRepo(repoPath, meta);
|
||||
await addToGitignore(repoPath);
|
||||
|
||||
const projectName = path.basename(repoPath);
|
||||
const { stats } = meta;
|
||||
|
|
@ -98,19 +103,17 @@ export const indexCommand = async (
|
|||
console.log(` Repository registered: ${projectName}`);
|
||||
if (stats) {
|
||||
const parts: string[] = [];
|
||||
if (stats.nodes != null) parts.push(`${stats.nodes.toLocaleString()} nodes`);
|
||||
if (stats.edges != null) parts.push(`${stats.edges.toLocaleString()} edges`);
|
||||
if (stats.nodes != null) {
|
||||
parts.push(`${stats.nodes.toLocaleString()} nodes`);
|
||||
}
|
||||
if (stats.edges != null) {
|
||||
parts.push(`${stats.edges.toLocaleString()} edges`);
|
||||
}
|
||||
if (stats.communities != null) parts.push(`${stats.communities} clusters`);
|
||||
if (stats.processes != null) parts.push(`${stats.processes} flows`);
|
||||
if (parts.length) console.log(` ${parts.join(' | ')}`);
|
||||
if (parts.length) console.log(` ${parts.join(" | ")}`);
|
||||
}
|
||||
console.log(` ${repoPath}`);
|
||||
|
||||
try {
|
||||
await fs.access(getGlobalRegistryPath());
|
||||
} catch {
|
||||
console.log('\n Tip: Run `gitnexus setup` to configure MCP for your editor.');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log("");
|
||||
};
|
||||
|
|
|
|||
129
gitnexus/test/unit/index-repo-command.test.ts
Normal file
129
gitnexus/test/unit/index-repo-command.test.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import path from 'node:path';
|
||||
|
||||
const mockAccess = vi.fn();
|
||||
const mockGetStoragePaths = vi.fn();
|
||||
const mockLoadMeta = vi.fn();
|
||||
const mockRegisterRepo = vi.fn();
|
||||
const mockGetGitRoot = vi.fn();
|
||||
const mockIsGitRepo = vi.fn();
|
||||
|
||||
vi.mock('fs/promises', () => ({
|
||||
default: {
|
||||
access: mockAccess,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../src/storage/repo-manager.js', () => ({
|
||||
getStoragePaths: mockGetStoragePaths,
|
||||
loadMeta: mockLoadMeta,
|
||||
registerRepo: mockRegisterRepo,
|
||||
}));
|
||||
|
||||
vi.mock('../../src/storage/git.js', () => ({
|
||||
getGitRoot: mockGetGitRoot,
|
||||
isGitRepo: mockIsGitRepo,
|
||||
}));
|
||||
|
||||
describe('indexCommand', () => {
|
||||
const resolvedRepo = path.resolve('/repo');
|
||||
const resolvedOutside = path.resolve('/outside/path');
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
process.exitCode = undefined;
|
||||
|
||||
mockGetStoragePaths.mockImplementation((repoPath: string) => ({
|
||||
storagePath: `${repoPath}/.gitnexus`,
|
||||
lbugPath: `${repoPath}/.gitnexus/lbug`,
|
||||
metaPath: `${repoPath}/.gitnexus/meta.json`,
|
||||
}));
|
||||
mockLoadMeta.mockResolvedValue({
|
||||
repoPath: resolvedRepo,
|
||||
lastCommit: 'abc123',
|
||||
indexedAt: '2026-03-20T00:00:00.000Z',
|
||||
stats: { nodes: 10, edges: 20 },
|
||||
});
|
||||
mockAccess.mockResolvedValue(undefined);
|
||||
mockGetGitRoot.mockReturnValue(resolvedRepo);
|
||||
mockIsGitRepo.mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('fails when target path is not a git repository', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
mockIsGitRepo.mockReturnValue(false);
|
||||
|
||||
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
||||
await indexCommand(['/outside/path']);
|
||||
|
||||
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
||||
expect(process.exitCode).toBe(1);
|
||||
expect(logSpy).toHaveBeenCalledWith(` Not a git repository: ${resolvedOutside}`);
|
||||
});
|
||||
|
||||
it('fails when .gitnexus folder does not exist', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
mockAccess.mockRejectedValueOnce(new Error('missing .gitnexus'));
|
||||
|
||||
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
||||
await indexCommand(['/repo']);
|
||||
|
||||
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
||||
expect(process.exitCode).toBe(1);
|
||||
expect(logSpy).toHaveBeenCalledWith(` No .gitnexus/ folder found at: ${resolvedRepo}/.gitnexus`);
|
||||
});
|
||||
|
||||
it('fails when lbug database does not exist', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
mockAccess
|
||||
.mockResolvedValueOnce(undefined)
|
||||
.mockRejectedValueOnce(new Error('missing lbug'));
|
||||
|
||||
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
||||
await indexCommand(['/repo']);
|
||||
|
||||
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
||||
expect(process.exitCode).toBe(1);
|
||||
expect(logSpy).toHaveBeenCalledWith(' .gitnexus/ folder exists but contains no LadybugDB index.');
|
||||
});
|
||||
|
||||
it('fails when meta.json is missing and --force is not set', async () => {
|
||||
mockLoadMeta.mockResolvedValue(null);
|
||||
|
||||
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
||||
await indexCommand(['/repo']);
|
||||
|
||||
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
||||
expect(process.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it('registers with minimal metadata when meta is missing and --force is set', async () => {
|
||||
mockLoadMeta.mockResolvedValue(null);
|
||||
|
||||
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
||||
await indexCommand(['/repo'], { force: true });
|
||||
|
||||
expect(mockRegisterRepo).toHaveBeenCalledTimes(1);
|
||||
expect(mockRegisterRepo).toHaveBeenCalledWith(
|
||||
resolvedRepo,
|
||||
expect.objectContaining({
|
||||
repoPath: resolvedRepo,
|
||||
lastCommit: '',
|
||||
}),
|
||||
);
|
||||
expect(process.exitCode).toBeUndefined();
|
||||
});
|
||||
|
||||
it('registers successfully with existing metadata', async () => {
|
||||
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
||||
await indexCommand(['/repo']);
|
||||
|
||||
expect(mockRegisterRepo).toHaveBeenCalledTimes(1);
|
||||
expect(mockRegisterRepo).toHaveBeenCalledWith(
|
||||
resolvedRepo,
|
||||
expect.objectContaining({ repoPath: resolvedRepo }),
|
||||
);
|
||||
expect(process.exitCode).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue