directory.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /**
  2. * Directory Management
  3. *
  4. * Manages the .codegraph/ directory structure for CodeGraph data.
  5. */
  6. import * as fs from 'fs';
  7. import * as path from 'path';
  8. /**
  9. * CodeGraph directory name
  10. */
  11. export const CODEGRAPH_DIR = '.codegraph';
  12. /**
  13. * Get the .codegraph directory path for a project
  14. */
  15. export function getCodeGraphDir(projectRoot: string): string {
  16. return path.join(projectRoot, CODEGRAPH_DIR);
  17. }
  18. /**
  19. * Check if a project has been initialized with CodeGraph
  20. * Requires both .codegraph/ directory AND codegraph.db to exist
  21. */
  22. export function isInitialized(projectRoot: string): boolean {
  23. const codegraphDir = getCodeGraphDir(projectRoot);
  24. if (!fs.existsSync(codegraphDir) || !fs.statSync(codegraphDir).isDirectory()) {
  25. return false;
  26. }
  27. // Must have codegraph.db, not just .codegraph folder
  28. const dbPath = path.join(codegraphDir, 'codegraph.db');
  29. return fs.existsSync(dbPath);
  30. }
  31. /**
  32. * Find the nearest parent directory containing .codegraph/
  33. *
  34. * Walks up from the given path to find a CodeGraph-initialized project,
  35. * similar to how git finds .git/ directories.
  36. *
  37. * @param startPath - Directory to start searching from
  38. * @returns The project root containing .codegraph/, or null if not found
  39. */
  40. export function findNearestCodeGraphRoot(startPath: string): string | null {
  41. let current = path.resolve(startPath);
  42. const root = path.parse(current).root;
  43. while (current !== root) {
  44. if (isInitialized(current)) {
  45. return current;
  46. }
  47. const parent = path.dirname(current);
  48. if (parent === current) break; // Reached filesystem root
  49. current = parent;
  50. }
  51. // Check root as well
  52. if (isInitialized(current)) {
  53. return current;
  54. }
  55. return null;
  56. }
  57. /**
  58. * Create the .codegraph directory structure
  59. * Note: Only throws if codegraph.db already exists, not just if .codegraph/ exists.
  60. */
  61. export function createDirectory(projectRoot: string): void {
  62. const codegraphDir = getCodeGraphDir(projectRoot);
  63. const dbPath = path.join(codegraphDir, 'codegraph.db');
  64. // Only throw if CodeGraph is actually initialized (db exists)
  65. // .codegraph/ folder alone is fine
  66. if (fs.existsSync(dbPath)) {
  67. throw new Error(`CodeGraph already initialized in ${projectRoot}`);
  68. }
  69. // Create main directory (if it doesn't exist)
  70. fs.mkdirSync(codegraphDir, { recursive: true });
  71. // Create .gitignore inside .codegraph (if it doesn't exist)
  72. const gitignorePath = path.join(codegraphDir, '.gitignore');
  73. if (!fs.existsSync(gitignorePath)) {
  74. const gitignoreContent = `# CodeGraph data files
  75. # These are local to each machine and should not be committed
  76. # Database
  77. *.db
  78. *.db-wal
  79. *.db-shm
  80. # Cache
  81. cache/
  82. # Logs
  83. *.log
  84. `;
  85. fs.writeFileSync(gitignorePath, gitignoreContent, 'utf-8');
  86. }
  87. }
  88. /**
  89. * Remove the .codegraph directory
  90. */
  91. export function removeDirectory(projectRoot: string): void {
  92. const codegraphDir = getCodeGraphDir(projectRoot);
  93. if (!fs.existsSync(codegraphDir)) {
  94. return;
  95. }
  96. // Recursively remove directory
  97. fs.rmSync(codegraphDir, { recursive: true, force: true });
  98. }
  99. /**
  100. * Get all files in the .codegraph directory
  101. */
  102. export function listDirectoryContents(projectRoot: string): string[] {
  103. const codegraphDir = getCodeGraphDir(projectRoot);
  104. if (!fs.existsSync(codegraphDir)) {
  105. return [];
  106. }
  107. const files: string[] = [];
  108. function walkDir(dir: string, prefix: string = ''): void {
  109. const entries = fs.readdirSync(dir, { withFileTypes: true });
  110. for (const entry of entries) {
  111. const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
  112. if (entry.isDirectory()) {
  113. walkDir(path.join(dir, entry.name), relativePath);
  114. } else {
  115. files.push(relativePath);
  116. }
  117. }
  118. }
  119. walkDir(codegraphDir);
  120. return files;
  121. }
  122. /**
  123. * Get the total size of the .codegraph directory in bytes
  124. */
  125. export function getDirectorySize(projectRoot: string): number {
  126. const codegraphDir = getCodeGraphDir(projectRoot);
  127. if (!fs.existsSync(codegraphDir)) {
  128. return 0;
  129. }
  130. let totalSize = 0;
  131. function walkDir(dir: string): void {
  132. const entries = fs.readdirSync(dir, { withFileTypes: true });
  133. for (const entry of entries) {
  134. const fullPath = path.join(dir, entry.name);
  135. if (entry.isDirectory()) {
  136. walkDir(fullPath);
  137. } else {
  138. const stats = fs.statSync(fullPath);
  139. totalSize += stats.size;
  140. }
  141. }
  142. }
  143. walkDir(codegraphDir);
  144. return totalSize;
  145. }
  146. /**
  147. * Ensure a subdirectory exists within .codegraph
  148. */
  149. export function ensureSubdirectory(projectRoot: string, subdirName: string): string {
  150. const subdirPath = path.join(getCodeGraphDir(projectRoot), subdirName);
  151. if (!fs.existsSync(subdirPath)) {
  152. fs.mkdirSync(subdirPath, { recursive: true });
  153. }
  154. return subdirPath;
  155. }
  156. /**
  157. * Check if the .codegraph directory has valid structure
  158. */
  159. export function validateDirectory(projectRoot: string): {
  160. valid: boolean;
  161. errors: string[];
  162. } {
  163. const errors: string[] = [];
  164. const codegraphDir = getCodeGraphDir(projectRoot);
  165. if (!fs.existsSync(codegraphDir)) {
  166. errors.push('CodeGraph directory does not exist');
  167. return { valid: false, errors };
  168. }
  169. if (!fs.statSync(codegraphDir).isDirectory()) {
  170. errors.push('.codegraph exists but is not a directory');
  171. return { valid: false, errors };
  172. }
  173. // Check for required files
  174. const gitignorePath = path.join(codegraphDir, '.gitignore');
  175. if (!fs.existsSync(gitignorePath)) {
  176. errors.push('.gitignore missing in .codegraph directory');
  177. }
  178. return {
  179. valid: errors.length === 0,
  180. errors,
  181. };
  182. }