Reference implementation for the Phoenix Architecture. Work in progress. aicoding.leaflet.pub/
ai coding crazy
at main 114 lines 3.4 kB view raw
1/** 2 * Dependency Extractor — parses TypeScript source to find imports and side channels. 3 */ 4 5export interface ExtractedDep { 6 kind: 'import'; 7 source: string; // the import specifier (package name or path) 8 is_relative: boolean; 9 source_line: number; 10} 11 12export interface ExtractedSideChannel { 13 kind: 'database' | 'queue' | 'cache' | 'config' | 'external_api' | 'file'; 14 identifier: string; // the detected identifier (env var name, URL, etc.) 15 source_line: number; 16} 17 18export interface DependencyGraph { 19 file_path: string; 20 imports: ExtractedDep[]; 21 side_channels: ExtractedSideChannel[]; 22} 23 24/** 25 * Extract dependencies from TypeScript source code. 26 * Uses regex-based parsing (no AST in v1). 27 */ 28export function extractDependencies(source: string, filePath: string): DependencyGraph { 29 const lines = source.split('\n'); 30 const imports: ExtractedDep[] = []; 31 const sideChannels: ExtractedSideChannel[] = []; 32 33 for (let i = 0; i < lines.length; i++) { 34 const line = lines[i]; 35 const lineNum = i + 1; 36 37 // Match: import ... from 'specifier' 38 // Match: import 'specifier' 39 // Match: require('specifier') 40 const importMatch = line.match(/(?:import\s+.*?from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/); 41 if (importMatch) { 42 const source = importMatch[1] || importMatch[2] || importMatch[3]; 43 imports.push({ 44 kind: 'import', 45 source, 46 is_relative: source.startsWith('.') || source.startsWith('/'), 47 source_line: lineNum, 48 }); 49 } 50 51 // Side channel detection patterns 52 // process.env.XXX 53 const envMatch = line.match(/process\.env\.(\w+)/); 54 if (envMatch) { 55 sideChannels.push({ 56 kind: 'config', 57 identifier: envMatch[1], 58 source_line: lineNum, 59 }); 60 } 61 62 // process.env['XXX'] 63 const envBracketMatch = line.match(/process\.env\[['"](\w+)['"]\]/); 64 if (envBracketMatch) { 65 sideChannels.push({ 66 kind: 'config', 67 identifier: envBracketMatch[1], 68 source_line: lineNum, 69 }); 70 } 71 72 // fetch('url') or new URL('...') 73 const fetchMatch = line.match(/(?:fetch|new\s+URL)\s*\(\s*['"]([^'"]+)['"]/); 74 if (fetchMatch) { 75 sideChannels.push({ 76 kind: 'external_api', 77 identifier: fetchMatch[1], 78 source_line: lineNum, 79 }); 80 } 81 82 // Database patterns: createConnection, createPool, new Pool, PrismaClient, etc. 83 const dbMatch = line.match(/(?:createConnection|createPool|new\s+Pool|new\s+PrismaClient|mongoose\.connect)\s*\(/); 84 if (dbMatch) { 85 sideChannels.push({ 86 kind: 'database', 87 identifier: 'database_connection', 88 source_line: lineNum, 89 }); 90 } 91 92 // fs.readFile, fs.writeFile, etc. 93 const fsMatch = line.match(/fs\.(readFile|writeFile|readdir|mkdir|unlink|stat|access)/); 94 if (fsMatch) { 95 sideChannels.push({ 96 kind: 'file', 97 identifier: `fs.${fsMatch[1]}`, 98 source_line: lineNum, 99 }); 100 } 101 102 // Redis / cache patterns 103 const cacheMatch = line.match(/(?:new\s+Redis|createClient|redis\.connect)/); 104 if (cacheMatch) { 105 sideChannels.push({ 106 kind: 'cache', 107 identifier: 'redis_connection', 108 source_line: lineNum, 109 }); 110 } 111 } 112 113 return { file_path: filePath, imports, side_channels: sideChannels }; 114}