Files
codegraph/src/directory.ts
T
Colby McHenry d0ee6f7fc4 Enhances code extraction and project indexing
Adds support for Dart and Liquid languages with tree-sitter parsing.
Improves accuracy of code symbol extraction for existing languages.
Indexes project files to enhance code navigation features.
Migrates build system to facilitate code contributions.
Removes git hook functionality.
Integrates Sentry for error tracking and reporting.
Enhances project initialization and configuration loading.
2026-02-09 22:18:59 -06:00

224 lines
5.5 KiB
TypeScript

/**
* Directory Management
*
* Manages the .codegraph/ directory structure for CodeGraph data.
*/
import * as fs from 'fs';
import * as path from 'path';
/**
* CodeGraph directory name
*/
export const CODEGRAPH_DIR = '.codegraph';
/**
* Get the .codegraph directory path for a project
*/
export function getCodeGraphDir(projectRoot: string): string {
return path.join(projectRoot, CODEGRAPH_DIR);
}
/**
* Check if a project has been initialized with CodeGraph
* Requires both .codegraph/ directory AND codegraph.db to exist
*/
export function isInitialized(projectRoot: string): boolean {
const codegraphDir = getCodeGraphDir(projectRoot);
if (!fs.existsSync(codegraphDir) || !fs.statSync(codegraphDir).isDirectory()) {
return false;
}
// Must have codegraph.db, not just .codegraph folder
const dbPath = path.join(codegraphDir, 'codegraph.db');
return fs.existsSync(dbPath);
}
/**
* Find the nearest parent directory containing .codegraph/
*
* Walks up from the given path to find a CodeGraph-initialized project,
* similar to how git finds .git/ directories.
*
* @param startPath - Directory to start searching from
* @returns The project root containing .codegraph/, or null if not found
*/
export function findNearestCodeGraphRoot(startPath: string): string | null {
let current = path.resolve(startPath);
const root = path.parse(current).root;
while (current !== root) {
if (isInitialized(current)) {
return current;
}
const parent = path.dirname(current);
if (parent === current) break; // Reached filesystem root
current = parent;
}
// Check root as well
if (isInitialized(current)) {
return current;
}
return null;
}
/**
* Create the .codegraph directory structure
* Note: Only throws if codegraph.db already exists, not just if .codegraph/ exists.
*/
export function createDirectory(projectRoot: string): void {
const codegraphDir = getCodeGraphDir(projectRoot);
const dbPath = path.join(codegraphDir, 'codegraph.db');
// Only throw if CodeGraph is actually initialized (db exists)
// .codegraph/ folder alone is fine
if (fs.existsSync(dbPath)) {
throw new Error(`CodeGraph already initialized in ${projectRoot}`);
}
// Create main directory (if it doesn't exist)
fs.mkdirSync(codegraphDir, { recursive: true });
// Create .gitignore inside .codegraph (if it doesn't exist)
const gitignorePath = path.join(codegraphDir, '.gitignore');
if (!fs.existsSync(gitignorePath)) {
const gitignoreContent = `# CodeGraph data files
# These are local to each machine and should not be committed
# Database
*.db
*.db-wal
*.db-shm
# Cache
cache/
# Logs
*.log
`;
fs.writeFileSync(gitignorePath, gitignoreContent, 'utf-8');
}
}
/**
* Remove the .codegraph directory
*/
export function removeDirectory(projectRoot: string): void {
const codegraphDir = getCodeGraphDir(projectRoot);
if (!fs.existsSync(codegraphDir)) {
return;
}
// Recursively remove directory
fs.rmSync(codegraphDir, { recursive: true, force: true });
}
/**
* Get all files in the .codegraph directory
*/
export function listDirectoryContents(projectRoot: string): string[] {
const codegraphDir = getCodeGraphDir(projectRoot);
if (!fs.existsSync(codegraphDir)) {
return [];
}
const files: string[] = [];
function walkDir(dir: string, prefix: string = ''): void {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
walkDir(path.join(dir, entry.name), relativePath);
} else {
files.push(relativePath);
}
}
}
walkDir(codegraphDir);
return files;
}
/**
* Get the total size of the .codegraph directory in bytes
*/
export function getDirectorySize(projectRoot: string): number {
const codegraphDir = getCodeGraphDir(projectRoot);
if (!fs.existsSync(codegraphDir)) {
return 0;
}
let totalSize = 0;
function walkDir(dir: string): void {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
walkDir(fullPath);
} else {
const stats = fs.statSync(fullPath);
totalSize += stats.size;
}
}
}
walkDir(codegraphDir);
return totalSize;
}
/**
* Ensure a subdirectory exists within .codegraph
*/
export function ensureSubdirectory(projectRoot: string, subdirName: string): string {
const subdirPath = path.join(getCodeGraphDir(projectRoot), subdirName);
if (!fs.existsSync(subdirPath)) {
fs.mkdirSync(subdirPath, { recursive: true });
}
return subdirPath;
}
/**
* Check if the .codegraph directory has valid structure
*/
export function validateDirectory(projectRoot: string): {
valid: boolean;
errors: string[];
} {
const errors: string[] = [];
const codegraphDir = getCodeGraphDir(projectRoot);
if (!fs.existsSync(codegraphDir)) {
errors.push('CodeGraph directory does not exist');
return { valid: false, errors };
}
if (!fs.statSync(codegraphDir).isDirectory()) {
errors.push('.codegraph exists but is not a directory');
return { valid: false, errors };
}
// Check for required files
const gitignorePath = path.join(codegraphDir, '.gitignore');
if (!fs.existsSync(gitignorePath)) {
errors.push('.gitignore missing in .codegraph directory');
}
return {
valid: errors.length === 0,
errors,
};
}