342 lines
7.9 KiB
JavaScript
342 lines
7.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Build Script for Psycho-Symbolic Reasoner
|
|
*
|
|
* This script orchestrates the complete build process:
|
|
* 1. Clean previous builds
|
|
* 2. Build WASM components
|
|
* 3. Build TypeScript
|
|
* 4. Copy assets
|
|
* 5. Validate build
|
|
*/
|
|
|
|
import { spawn } from 'child_process';
|
|
import { promises as fs } from 'fs';
|
|
import { join, resolve } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = resolve(__filename, '..');
|
|
const rootDir = resolve(__dirname, '..');
|
|
|
|
// Colors for console output
|
|
const colors = {
|
|
reset: '\x1b[0m',
|
|
bright: '\x1b[1m',
|
|
red: '\x1b[31m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
blue: '\x1b[34m',
|
|
magenta: '\x1b[35m',
|
|
cyan: '\x1b[36m',
|
|
};
|
|
|
|
function log(color, prefix, message) {
|
|
console.log(`${color}${prefix}${colors.reset} ${message}`);
|
|
}
|
|
|
|
function info(message) {
|
|
log(colors.blue, '[INFO]', message);
|
|
}
|
|
|
|
function success(message) {
|
|
log(colors.green, '[SUCCESS]', message);
|
|
}
|
|
|
|
function warn(message) {
|
|
log(colors.yellow, '[WARN]', message);
|
|
}
|
|
|
|
function error(message) {
|
|
log(colors.red, '[ERROR]', message);
|
|
}
|
|
|
|
async function runCommand(command, args = [], options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(command, args, {
|
|
stdio: 'inherit',
|
|
cwd: options.cwd || rootDir,
|
|
...options
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
if (code === 0) {
|
|
resolve(code);
|
|
} else {
|
|
reject(new Error(`Command failed: ${command} ${args.join(' ')}`));
|
|
}
|
|
});
|
|
|
|
child.on('error', reject);
|
|
});
|
|
}
|
|
|
|
async function checkCommand(command) {
|
|
try {
|
|
await runCommand('which', [command], { stdio: 'ignore' });
|
|
return true;
|
|
} catch {
|
|
try {
|
|
await runCommand('where', [command], { stdio: 'ignore' });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function ensureDir(dirPath) {
|
|
try {
|
|
await fs.mkdir(dirPath, { recursive: true });
|
|
} catch (error) {
|
|
if (error.code !== 'EEXIST') {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function clean() {
|
|
info('Cleaning previous builds...');
|
|
|
|
const dirsToClean = [
|
|
'dist',
|
|
'wasm/pkg',
|
|
'wasm/extractors',
|
|
'wasm/planner'
|
|
];
|
|
|
|
for (const dir of dirsToClean) {
|
|
try {
|
|
await fs.rm(resolve(rootDir, dir), { recursive: true, force: true });
|
|
info(`Cleaned ${dir}`);
|
|
} catch (error) {
|
|
warn(`Could not clean ${dir}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
success('Clean completed');
|
|
}
|
|
|
|
async function buildWasm() {
|
|
info('Building WASM components...');
|
|
|
|
// Check if wasm-pack is available
|
|
if (!(await checkCommand('wasm-pack'))) {
|
|
error('wasm-pack not found. Please install it with: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Check if Rust is available
|
|
if (!(await checkCommand('cargo'))) {
|
|
error('Rust/Cargo not found. Please install Rust from https://rustup.rs/');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Ensure wasm directories exist
|
|
await ensureDir(resolve(rootDir, 'wasm'));
|
|
|
|
const wasmComponents = [
|
|
{ name: 'graph_reasoner', outDir: '../wasm/pkg' },
|
|
{ name: 'extractors', outDir: '../wasm/extractors' },
|
|
{ name: 'planner', outDir: '../wasm/planner' }
|
|
];
|
|
|
|
for (const component of wasmComponents) {
|
|
info(`Building WASM component: ${component.name}`);
|
|
|
|
try {
|
|
await runCommand('wasm-pack', [
|
|
'build',
|
|
'--target', 'nodejs',
|
|
'--out-dir', component.outDir,
|
|
component.name
|
|
]);
|
|
success(`Built ${component.name}`);
|
|
} catch (error) {
|
|
error(`Failed to build ${component.name}: ${error.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
success('WASM build completed');
|
|
}
|
|
|
|
async function buildTypeScript() {
|
|
info('Building TypeScript...');
|
|
|
|
// Check if TypeScript is available
|
|
if (!(await checkCommand('tsc'))) {
|
|
error('TypeScript compiler not found. Installing...');
|
|
try {
|
|
await runCommand('npm', ['install', '-g', 'typescript']);
|
|
} catch (error) {
|
|
error('Failed to install TypeScript globally. Please run: npm install -g typescript');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
try {
|
|
await runCommand('npx', ['tsc']);
|
|
success('TypeScript build completed');
|
|
} catch (error) {
|
|
error(`TypeScript build failed: ${error.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
async function copyAssets() {
|
|
info('Copying assets...');
|
|
|
|
const assetsToCopy = [
|
|
{ src: 'wasm', dest: 'dist/wasm' },
|
|
{ src: 'examples', dest: 'dist/examples' },
|
|
{ src: 'docs', dest: 'dist/docs' }
|
|
];
|
|
|
|
for (const asset of assetsToCopy) {
|
|
const srcPath = resolve(rootDir, asset.src);
|
|
const destPath = resolve(rootDir, asset.dest);
|
|
|
|
try {
|
|
await fs.access(srcPath);
|
|
await ensureDir(destPath);
|
|
await fs.cp(srcPath, destPath, { recursive: true });
|
|
info(`Copied ${asset.src} to ${asset.dest}`);
|
|
} catch (error) {
|
|
warn(`Could not copy ${asset.src}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
success('Assets copied');
|
|
}
|
|
|
|
async function validateBuild() {
|
|
info('Validating build...');
|
|
|
|
const requiredFiles = [
|
|
'dist/index.js',
|
|
'dist/index.d.ts',
|
|
'dist/cli/index.js',
|
|
'dist/mcp/index.js'
|
|
];
|
|
|
|
let allValid = true;
|
|
|
|
for (const file of requiredFiles) {
|
|
try {
|
|
await fs.access(resolve(rootDir, file));
|
|
info(`✓ ${file}`);
|
|
} catch (error) {
|
|
error(`✗ Missing: ${file}`);
|
|
allValid = false;
|
|
}
|
|
}
|
|
|
|
// Check WASM files
|
|
const wasmFiles = [
|
|
'wasm/pkg/package.json',
|
|
'wasm/extractors/package.json',
|
|
'wasm/planner/package.json'
|
|
];
|
|
|
|
for (const file of wasmFiles) {
|
|
try {
|
|
await fs.access(resolve(rootDir, file));
|
|
info(`✓ ${file}`);
|
|
} catch (error) {
|
|
warn(`⚠ WASM file missing: ${file}`);
|
|
}
|
|
}
|
|
|
|
if (allValid) {
|
|
success('Build validation passed');
|
|
} else {
|
|
error('Build validation failed');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
async function generatePackageInfo() {
|
|
info('Generating package info...');
|
|
|
|
const packageJson = JSON.parse(
|
|
await fs.readFile(resolve(rootDir, 'package.json'), 'utf8')
|
|
);
|
|
|
|
const buildInfo = {
|
|
name: packageJson.name,
|
|
version: packageJson.version,
|
|
buildTime: new Date().toISOString(),
|
|
components: {
|
|
typescript: true,
|
|
wasm: {
|
|
graph_reasoner: true,
|
|
extractors: true,
|
|
planner: true
|
|
}
|
|
}
|
|
};
|
|
|
|
await fs.writeFile(
|
|
resolve(rootDir, 'dist/build-info.json'),
|
|
JSON.stringify(buildInfo, null, 2)
|
|
);
|
|
|
|
success('Package info generated');
|
|
}
|
|
|
|
async function main() {
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
log(colors.cyan, '[BUILD]', 'Starting build process...');
|
|
|
|
await clean();
|
|
await buildWasm();
|
|
await buildTypeScript();
|
|
await copyAssets();
|
|
await generatePackageInfo();
|
|
await validateBuild();
|
|
|
|
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
success(`Build completed successfully in ${duration}s`);
|
|
|
|
} catch (error) {
|
|
error(`Build failed: ${error.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Handle command line arguments
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.includes('--help') || args.includes('-h')) {
|
|
console.log(`
|
|
Usage: node scripts/build.js [options]
|
|
|
|
Options:
|
|
--help, -h Show this help message
|
|
--clean-only Only clean, don't build
|
|
--wasm-only Only build WASM components
|
|
--ts-only Only build TypeScript
|
|
--skip-validation Skip build validation
|
|
|
|
Examples:
|
|
node scripts/build.js # Full build
|
|
node scripts/build.js --clean-only # Clean only
|
|
node scripts/build.js --wasm-only # WASM only
|
|
node scripts/build.js --ts-only # TypeScript only
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (args.includes('--clean-only')) {
|
|
clean().then(() => success('Clean completed')).catch(console.error);
|
|
} else if (args.includes('--wasm-only')) {
|
|
buildWasm().then(() => success('WASM build completed')).catch(console.error);
|
|
} else if (args.includes('--ts-only')) {
|
|
buildTypeScript().then(() => success('TypeScript build completed')).catch(console.error);
|
|
} else {
|
|
main().catch(console.error);
|
|
} |