Reference implementation for the Phoenix Architecture. Work in progress.
aicoding.leaflet.pub/
ai
coding
crazy
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}