File size: 5,090 Bytes
529090e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { config } from 'dotenv';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { readFileSync, existsSync } from 'fs';
import neo4j from 'neo4j-driver';

const __dirname = fileURLToPath(new URL('.', import.meta.url));
config({ path: resolve(__dirname, '../../.env') });

const NEO4J_URI = process.env.NEO4J_URI || 'bolt://localhost:7687';
const NEO4J_USERNAME = process.env.NEO4J_USERNAME || 'neo4j';
const NEO4J_PASSWORD = process.env.NEO4J_PASSWORD || 'password';

async function mapLibraries() {
    console.log('🗺️  The Cartographer: Mapping external dependencies...');
    const driver = neo4j.driver(NEO4J_URI, neo4j.auth.basic(NEO4J_USERNAME, NEO4J_PASSWORD));
    const session = driver.session();

    try {
        // 1. Fetch all TypeScript files
        console.log('📥 Fetching TypeScript nodes...');
        const result = await session.run(`
            MATCH (f:File) 
            WHERE f.language = 'TypeScript' OR f.extension = '.ts' OR f.extension = '.tsx'
            RETURN f.path as absolutePath, f.id as id
        `);

        const files = result.records.map(r => ({
            absolutePath: r.get('absolutePath'),
            id: r.get('id')
        }));

        console.log(`Found ${files.length} TS files. Scanning for libraries...`);

        let librariesMapped = 0;
        const libraryUsage: Record<string, number> = {};

        for (const file of files) {
            try {
                if (!existsSync(file.absolutePath)) {
                    continue;
                }

                const content = readFileSync(file.absolutePath, 'utf-8');
                // Regex to capture import sources
                const importRegex = /import\s+(?:[\s\S]*?from\s+)?['"](.*?)['"]/g;
                // Also capture dynamic imports and requires if possible, but stick to static imports for now as per weave_graph logic
                
                let match;
                while ((match = importRegex.exec(content)) !== null) {
                    const importPath = match[1];
                    
                    // Filter for libraries: NOT starting with . or / or absolute windows paths (C:\...)
                    // And usually libraries don't start with @widget-tdc (monorepo packages) unless we want to map those as libs too?
                    // The instruction says: "Hvis stien IKKE starter med . eller /, er det et Library"
                    
                    if (importPath.startsWith('.') || importPath.startsWith('/') || importPath.match(/^[a-zA-Z]:/)) {
                        continue; 
                    }

                    // Clean library name (e.g., 'neo4j-driver/lib/types' -> 'neo4j-driver')
                    // Handle scoped packages like '@scope/pkg/sub' -> '@scope/pkg'
                    let libName = importPath;
                    if (libName.startsWith('@')) {
                        const parts = libName.split('/');
                        if (parts.length >= 2) {
                            libName = `${parts[0]}/${parts[1]}`;
                        }
                    } else {
                        const parts = libName.split('/');
                        if (parts.length >= 1) {
                            libName = parts[0];
                        }
                    }

                    // Skip internal monorepo packages if desired, but "Supply Chain" usually implies everything external to the *file*.
                    // However, to distinguish 3rd party from internal packages, we might want to tag them differently.
                    // For now, treat everything that looks like a package as a Library.

                    // Neo4j Action
                    await session.run(`
                        MERGE (lib:Library {name: $libName})
                        WITH lib
                        MATCH (f:File {id: $fileId})
                        MERGE (f)-[r:USES]->(lib)
                    `, { libName, fileId: file.id });

                    librariesMapped++;
                    libraryUsage[libName] = (libraryUsage[libName] || 0) + 1;
                    
                    if (librariesMapped % 50 === 0) process.stdout.write('.');
                }
            } catch (err) {
                // console.error(`Error parsing ${file.absolutePath}:`, err);
            }
        }

        console.log(`\n✅ Mapping complete. Mapped ${librariesMapped} library usages.`);

        // Run The Bloat Report Query
        const bloatResult = await session.run(`
            MATCH (l:Library)<-[r:USES]-(f:File) 
            RETURN l.name as name, count(r) as UsageCount 
            ORDER BY UsageCount DESC
            LIMIT 10
        `);

        console.log('\n📊 Top 10 Libraries:');
        bloatResult.records.forEach(r => {
            console.log(`- ${r.get('name')}: ${r.get('UsageCount')}`);
        });

    } catch (error) {
        console.error('❌ Cartographer failed:', error);
    } finally {
        await session.close();
        await driver.close();
    }
}

mapLibraries();