UNPKG

sophia-code

Version:

Production-ready agentic CLI code editor with AI-powered coding assistance, planning, and multi-agent delegation. Enterprise-grade security and reliability.

270 lines (230 loc) • 7.5 kB
#!/usr/bin/env node /** * Pre-publish validation script for sophia-code NPM package * Ensures the package is ready for production deployment */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const chalk = require('chalk'); function log(message, color = 'white') { console.log(chalk[color](message)); } function checkFileExists(filePath, description) { if (fs.existsSync(filePath)) { log(`āœ… ${description}: ${filePath}`, 'green'); return true; } else { log(`āŒ ${description} missing: ${filePath}`, 'red'); return false; } } function validatePackageJson() { log('\nšŸ“¦ Validating package.json...', 'cyan'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); let valid = true; // Check version is 1.0.0+ if (!pkg.version || !pkg.version.startsWith('1.')) { log('āŒ Version should be 1.0.0 or higher for production', 'red'); valid = false; } else { log(`āœ… Version: ${pkg.version}`, 'green'); } // Check required fields const requiredFields = ['name', 'description', 'bin', 'engines', 'files']; for (const field of requiredFields) { if (!pkg[field]) { log(`āŒ Missing required field: ${field}`, 'red'); valid = false; } else { log(`āœ… ${field}: present`, 'green'); } } // Check that all dependencies are pinned (no ^ or ~) const deps = { ...pkg.dependencies, ...pkg.devDependencies }; const unpinned = Object.entries(deps).filter(([name, version]) => version.includes('^') || version.includes('~') ); if (unpinned.length > 0) { log('āŒ Unpinned dependencies found:', 'red'); unpinned.forEach(([name, version]) => { log(` - ${name}: ${version}`, 'red'); }); valid = false; } else { log('āœ… All dependencies pinned', 'green'); } return valid; } function validateFiles() { log('\nšŸ“ Validating required files...', 'cyan'); const requiredFiles = [ ['README.md', 'README'], ['PRODUCTION_READY.md', 'Production documentation'], ['CLAUDE.md', 'Claude Code documentation'], ['AGENTS.md', 'Agent system documentation'], ['package.json', 'Package configuration'], ['setup.sh', 'Setup script'], ['requirements.txt', 'Python requirements'], ['src/cli.py', 'Main CLI module'], ['src/groq_client.py', 'Groq client'], ['config/security.py', 'Security configuration'], ['scripts/postinstall.js', 'Post-install script'] ]; let allValid = true; for (const [file, desc] of requiredFiles) { if (!checkFileExists(file, desc)) { allValid = false; } } return allValid; } function validateDirectories() { log('\nšŸ“‚ Validating directory structure...', 'cyan'); const requiredDirs = [ ['src', 'Source code'], ['config', 'Configuration'], ['plugins', 'Plugin system'], ['tests', 'Test suite'], ['scripts', 'Scripts'], ['bin', 'Executables'], ['lib', 'Library'] ]; let allValid = true; for (const [dir, desc] of requiredDirs) { if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) { log(`āœ… ${desc}: ${dir}/`, 'green'); } else { log(`āŒ ${desc} directory missing: ${dir}/`, 'red'); allValid = false; } } return allValid; } function checkGitStatus() { log('\nšŸ”„ Checking git status...', 'cyan'); try { const status = execSync('git status --porcelain', { encoding: 'utf8' }); if (status.trim()) { log('āŒ Uncommitted changes found:', 'red'); log(status, 'yellow'); return false; } else { log('āœ… No uncommitted changes', 'green'); return true; } } catch (error) { log('āŒ Failed to check git status', 'red'); return false; } } function validateReadme() { log('\nšŸ“– Validating README.md...', 'cyan'); const readme = fs.readFileSync('README.md', 'utf8'); const requiredSections = [ 'Features', 'Quick Start', 'Installation', 'Production', 'Sophia' ]; let allValid = true; for (const section of requiredSections) { if (readme.toLowerCase().includes(section.toLowerCase())) { log(`āœ… Section found: ${section}`, 'green'); } else { log(`āŒ Section missing: ${section}`, 'red'); allValid = false; } } // Check if it mentions v1.0.0 or production if (readme.includes('1.0.0') || readme.toLowerCase().includes('production')) { log('āœ… Production version mentioned', 'green'); } else { log('āš ļø Consider mentioning production readiness', 'yellow'); } return allValid; } function runSecurityCheck() { log('\nšŸ”’ Running security checks...', 'cyan'); try { // Check for obvious secrets in package files const sensitiveFiles = ['package.json', 'scripts/postinstall.js']; const sensitivePatterns = [ /gsk_[a-zA-Z0-9_-]{32,}/g, // Groq API key /sk-[a-zA-Z0-9]{48,}/g, // OpenAI key /ghp_[a-zA-Z0-9]{36}/g, // GitHub token ]; let issuesFound = false; for (const file of sensitiveFiles) { if (fs.existsSync(file)) { const content = fs.readFileSync(file, 'utf8'); for (const pattern of sensitivePatterns) { if (pattern.test(content)) { log(`āŒ Potential secret found in ${file}`, 'red'); issuesFound = true; } } } } if (!issuesFound) { log('āœ… No secrets found in package files', 'green'); } return !issuesFound; } catch (error) { log('āš ļø Security check failed, but continuing', 'yellow'); return true; } } function main() { log('šŸš€ Sophia NPM Package Pre-Publish Validation', 'cyan'); log('=' .repeat(50), 'cyan'); const checks = [ ['Package.json', validatePackageJson], ['Required Files', validateFiles], ['Directory Structure', validateDirectories], ['Git Status', checkGitStatus], ['README', validateReadme], ['Security', runSecurityCheck] ]; let allPassed = true; const results = {}; for (const [checkName, checkFn] of checks) { try { const passed = checkFn(); results[checkName] = passed; allPassed = allPassed && passed; } catch (error) { log(`āŒ ${checkName} check failed: ${error.message}`, 'red'); results[checkName] = false; allPassed = false; } } // Summary log('\n' + '=' .repeat(50), 'cyan'); log('šŸ“Š NPM PACKAGE VALIDATION SUMMARY', 'cyan'); log('=' .repeat(50), 'cyan'); for (const [checkName, passed] of Object.entries(results)) { const status = passed ? 'āœ… PASS' : 'āŒ FAIL'; log(`${status.padEnd(10)} ${checkName}`); } if (allPassed) { log('\nšŸŽ‰ ALL CHECKS PASSED - Package ready for NPM publish!', 'green'); log('\nšŸ’” Next steps:', 'cyan'); log(' 1. npm publish --dry-run (test publish)', 'white'); log(' 2. npm publish (actual publish)', 'white'); log(' 3. Verify at: https://www.npmjs.com/package/sophia-code', 'white'); process.exit(0); } else { log('\nāš ļø SOME CHECKS FAILED - Please fix issues before publishing', 'red'); const failedChecks = Object.entries(results) .filter(([_, passed]) => !passed) .map(([name, _]) => name); log(` Failed: ${failedChecks.join(', ')}`, 'red'); process.exit(1); } } if (require.main === module) { main(); } module.exports = { validatePackageJson, validateFiles, checkGitStatus };