aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
162 lines (134 loc) • 5.01 kB
JavaScript
;
/**
* Dependency Graph Validator — .aios-core/package.json completeness
* Story INS-4.12 (AC1, AC4, AC6)
*
* Scans all .js files in .aios-core/development/scripts/ for require() calls,
* then verifies each non-builtin, non-relative package is declared in
* .aios-core/package.json dependencies.
*
* Exit codes: 0 = PASS, 1 = FAIL (missing deps found)
* Usage: node scripts/validate-aios-core-deps.js
*/
const fs = require('fs');
const path = require('path');
const Module = require('module');
const PROJECT_ROOT = path.join(__dirname, '..');
const AIOS_CORE_DIR = path.join(PROJECT_ROOT, '.aios-core');
const SCRIPTS_DIR = path.join(AIOS_CORE_DIR, 'development', 'scripts');
const PACKAGE_JSON = path.join(AIOS_CORE_DIR, 'package.json');
// Node.js builtin modules
const BUILTINS = new Set(Module.builtinModules.concat(
Module.builtinModules.map(m => `node:${m}`),
));
// Optional dev-time packages that should be wrapped in try-catch, not declared as deps
const ALLOWLIST = new Set([
'eslint',
'prettier',
'jscodeshift',
'@babel/parser',
'@babel/traverse',
'@babel/generator',
'@babel/types',
'@jest/globals',
'jest',
]);
/**
* Extract require() calls from a JS file using regex.
* Handles: require('pkg'), require("pkg"), require(`pkg`)
* Skips: require('./relative'), require('fs'), dynamic require(variable)
*/
function extractRequires(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const requires = new Set();
// Match require('...') and require("...")
const pattern = /require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
let match;
while ((match = pattern.exec(content)) !== null) {
const dep = match[1];
// Skip relative paths
if (dep.startsWith('.') || dep.startsWith('/')) continue;
// Skip template literal expressions (e.g., require('${dep.name}') in code generators)
if (dep.includes('${') || dep.includes('$')) continue;
// Skip builtins
if (BUILTINS.has(dep)) continue;
// Extract package name (handle scoped packages like @babel/parser)
const parts = dep.split('/');
const pkgName = dep.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
requires.add(pkgName);
}
return requires;
}
/**
* Recursively find all .js files in a directory
*/
function findJsFiles(dir) {
const files = [];
if (!fs.existsSync(dir)) return files;
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory() && entry.name !== 'node_modules') {
files.push(...findJsFiles(fullPath));
} else if (entry.isFile() && entry.name.endsWith('.js')) {
files.push(fullPath);
}
}
return files;
}
function main() {
console.log('--- .aios-core Dependency Validation (INS-4.12) ---\n');
// Read .aios-core/package.json
if (!fs.existsSync(PACKAGE_JSON)) {
console.error('FAIL: .aios-core/package.json not found');
process.exit(1);
}
const pkg = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf8'));
const declared = new Set(Object.keys(pkg.dependencies || {}));
// Find all JS scripts
const scripts = findJsFiles(SCRIPTS_DIR);
if (scripts.length === 0) {
console.log('WARN: No .js files found in .aios-core/development/scripts/');
process.exit(0);
}
console.log(`Scanning ${scripts.length} scripts in .aios-core/development/scripts/\n`);
const errors = [];
const allRequires = new Map(); // pkg -> [files that use it]
for (const script of scripts) {
const requires = extractRequires(script);
const relPath = path.relative(PROJECT_ROOT, script);
for (const pkg of requires) {
if (!allRequires.has(pkg)) allRequires.set(pkg, []);
allRequires.get(pkg).push(relPath);
if (!declared.has(pkg) && !ALLOWLIST.has(pkg)) {
errors.push({ script: relPath, package: pkg });
}
}
}
// Report
console.log(`Found ${allRequires.size} unique external packages across ${scripts.length} scripts`);
console.log(`Declared in .aios-core/package.json: ${declared.size}`);
console.log(`Allowlisted (optional/dev-time): ${ALLOWLIST.size}\n`);
if (errors.length > 0) {
console.error(`FAIL: ${errors.length} undeclared dependencies found:\n`);
// Group by package
const byPkg = new Map();
for (const err of errors) {
if (!byPkg.has(err.package)) byPkg.set(err.package, []);
byPkg.get(err.package).push(err.script);
}
for (const [pkg, files] of byPkg) {
console.error(` Missing: "${pkg}"`);
for (const f of files) {
console.error(` Used in: ${f}`);
}
}
console.error('\nFix: Add missing packages to .aios-core/package.json dependencies');
console.error('Or add to ALLOWLIST if they are optional dev-time tools (wrap in try-catch)\n');
process.exit(1);
}
console.log('PASS: All script dependencies are declared in .aios-core/package.json\n');
process.exit(0);
}
main();