130 lines
5.3 KiB
JavaScript
130 lines
5.3 KiB
JavaScript
// This is free software for the public good of a permacomputer hosted at
|
|
// permacomputer.com, an always-on computer by the people, for the people.
|
|
// One which is durable, easy to repair, & distributed like tap water
|
|
// for machine learning intelligence.
|
|
//
|
|
// The permacomputer is community-owned infrastructure optimized around
|
|
// four values:
|
|
//
|
|
// TRUTH First principles, math & science, open source code freely distributed
|
|
// FREEDOM Voluntary partnerships, freedom from tyranny & corporate control
|
|
// HARMONY Minimal waste, self-renewing systems with diverse thriving connections
|
|
// LOVE Be yourself without hurting others, cooperation through natural law
|
|
//
|
|
// This software contributes to that vision by making machine learning
|
|
// accessible to everyone through a free, open, embeddable chat interface.
|
|
// Code is seeds to sprout on any abandoned technology.
|
|
|
|
import { promises as fs } from 'fs';
|
|
import path from 'path';
|
|
|
|
const srcDir = './src';
|
|
|
|
async function validateExports() {
|
|
const files = await fs.readdir(srcDir);
|
|
const jsFiles = files.filter(f => f.endsWith('.js'));
|
|
|
|
// Also check languages subdirectory
|
|
try {
|
|
const langFiles = await fs.readdir(path.join(srcDir, 'languages'));
|
|
langFiles.filter(f => f.endsWith('.js')).forEach(f => {
|
|
jsFiles.push(`languages/${f}`);
|
|
});
|
|
} catch (error) {
|
|
// Languages directory doesn't exist, skip
|
|
}
|
|
|
|
console.log('🔍 Validating exports/imports...\n');
|
|
|
|
const exportMap = new Map();
|
|
const importMap = new Map();
|
|
|
|
// Extract exports and imports
|
|
for (const file of jsFiles) {
|
|
const content = await fs.readFile(path.join(srcDir, file), 'utf-8');
|
|
|
|
// Extract exports (including generator functions and destructured exports)
|
|
const exports = [];
|
|
|
|
// Match named exports: export function/const/let/var/class
|
|
const namedExportMatches = content.match(/export\s+(?:async\s+)?(?:function\*?|const|let|var|class)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g);
|
|
if (namedExportMatches) {
|
|
namedExportMatches.forEach(match => {
|
|
const parts = match.split(/\s+/);
|
|
exports.push(parts[parts.length - 1]);
|
|
});
|
|
}
|
|
|
|
// Match destructured exports: export { name1, name2 }
|
|
const destructuredExportMatches = content.match(/export\s*{\s*([^}]+)\s*}/g);
|
|
if (destructuredExportMatches) {
|
|
destructuredExportMatches.forEach(match => {
|
|
const namesMatch = match.match(/export\s*{\s*([^}]+)\s*}/);
|
|
if (namesMatch) {
|
|
const names = namesMatch[1].split(',').map(name => name.trim().split(/\s+as\s+/)[0].trim());
|
|
exports.push(...names);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (exports.length > 0) {
|
|
exportMap.set(file, exports);
|
|
}
|
|
|
|
// Extract imports (handle multi-line imports)
|
|
const normalizedContent = content.replace(/\n\s*/g, ' ');
|
|
const importMatches = normalizedContent.match(/import\s+{[^}]+}\s+from\s+["']\.\/[^"']+["']/g);
|
|
if (importMatches) {
|
|
importMatches.forEach(match => {
|
|
const parts = match.match(/import\s+{([^}]+)}\s+from\s+["']\.\/([^"']+)["']/);
|
|
if (parts) {
|
|
const imports = parts[1].split(',').map(s => s.trim()).filter(s => s.length > 0);
|
|
const fromFile = parts[2];
|
|
if (!importMap.has(file)) importMap.set(file, []);
|
|
importMap.get(file).push({ imports, fromFile });
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Validate
|
|
let errors = 0;
|
|
for (const [file, importData] of importMap) {
|
|
for (const { imports, fromFile } of importData) {
|
|
let targetFile = fromFile.endsWith('.js') ? fromFile : fromFile + '.js';
|
|
|
|
// Handle subdirectory imports like languages/index.js
|
|
if (fromFile.includes('/')) {
|
|
const parts = fromFile.split('/');
|
|
if (parts.length === 2 && !parts[1].includes('.')) {
|
|
targetFile = `${parts[0]}/index.js`;
|
|
}
|
|
} else {
|
|
// For imports within the languages directory, prefix with languages/
|
|
const callingDir = file.includes('/') ? file.split('/')[0] : '';
|
|
if (callingDir === 'languages' && !targetFile.includes('/')) {
|
|
targetFile = `languages/${targetFile}`;
|
|
}
|
|
}
|
|
|
|
const exports = exportMap.get(targetFile) || [];
|
|
|
|
for (const importName of imports) {
|
|
if (!exports.includes(importName)) {
|
|
console.log(`❌ ${file}: imports '${importName}' from ${fromFile}, but it's not exported`);
|
|
console.log(` Available exports: ${exports.join(', ') || 'none'}`);
|
|
console.log(` Looking in file: ${targetFile}`);
|
|
errors++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (errors === 0) {
|
|
console.log('✅ All imports match their exports!');
|
|
} else {
|
|
console.log(`\n💥 Found ${errors} import/export mismatches`);
|
|
}
|
|
}
|
|
|
|
validateExports().catch(console.error);
|