Skip to main content
Glama
multi-manager-packager.js20.7 kB
#!/usr/bin/env node /** * CastPlan Ultimate Automation - Multi-Manager Packaging System * Universal packaging for npm, yarn, pnpm, pip, uv, uvx ecosystems */ import { execSync, spawn } from 'child_process'; import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, rmSync } from 'fs'; import { join, resolve } from 'path'; import { performance } from 'perf_hooks'; import BUILD_CONFIG from '../build.config.js'; // ANSI colors for terminal 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' }; class MultiManagerPackager { constructor() { this.projectRoot = process.cwd(); this.packageJson = JSON.parse(readFileSync('./package.json', 'utf8')); this.distPath = BUILD_CONFIG.paths.output; this.outputPath = './packages'; this.results = { startTime: performance.now(), packages: {}, errors: [], warnings: [] }; // Ensure output directory exists if (!existsSync(this.outputPath)) { mkdirSync(this.outputPath, { recursive: true }); } } log(message, level = 'info') { const timestamp = new Date().toISOString(); const colorMap = { info: colors.cyan, success: colors.green, warning: colors.yellow, error: colors.red, phase: colors.magenta }; const coloredMessage = `${colorMap[level]}[${level.toUpperCase()}]${colors.reset} ${message}`; console.log(coloredMessage); } // Detect available package managers detectPackageManagers() { const managers = {}; // Node.js package managers ['npm', 'yarn', 'pnpm'].forEach(manager => { try { execSync(`${manager} --version`, { stdio: 'ignore' }); managers[manager] = true; this.log(`✓ ${manager} detected`, 'success'); } catch (error) { managers[manager] = false; this.log(`✗ ${manager} not available`, 'warning'); } }); // Python package managers ['pip', 'uv', 'uvx', 'python'].forEach(manager => { try { execSync(`${manager} --version`, { stdio: 'ignore' }); managers[manager] = true; this.log(`✓ ${manager} detected`, 'success'); } catch (error) { managers[manager] = false; if (manager === 'python') { this.log(`✗ Python not available - Python packaging disabled`, 'warning'); } } }); return managers; } // Create NPM package async createNpmPackage() { this.log('📦 Creating NPM package...', 'phase'); try { // Verify dist exists if (!existsSync(this.distPath)) { throw new Error('Dist directory not found. Run build first.'); } // Create tarball const packageName = execSync('npm pack', { encoding: 'utf8' }).trim(); const packagePath = join(this.outputPath, packageName); // Move to output directory if (existsSync(packageName)) { copyFileSync(packageName, packagePath); rmSync(packageName); } this.results.packages.npm = { name: packageName, path: packagePath, size: this.getFileSize(packagePath), manager: 'npm', registry: 'https://registry.npmjs.org/', installCommand: `npm install -g ${this.packageJson.name}` }; this.log(`✅ NPM package created: ${packageName}`, 'success'); } catch (error) { this.results.errors.push(`NPM packaging failed: ${error.message}`); this.log(`❌ NPM packaging failed: ${error.message}`, 'error'); throw error; } } // Create Yarn package (uses npm pack but optimized for yarn) async createYarnPackage() { this.log('🧶 Creating Yarn package...', 'phase'); try { // Create yarn-specific package.json with yarn optimizations const yarnPackageJson = { ...this.packageJson, packageManager: `yarn@${this.getYarnVersion()}`, engines: { ...this.packageJson.engines, yarn: '>=1.22.0' } }; // Backup original package.json const originalPackageJson = readFileSync('./package.json', 'utf8'); // Write yarn-optimized package.json writeFileSync('./package.json', JSON.stringify(yarnPackageJson, null, 2)); // Create package const packageName = execSync('yarn pack --filename yarn-package.tgz', { encoding: 'utf8' }).trim(); const packagePath = join(this.outputPath, 'yarn-package.tgz'); // Move to output directory if (existsSync('yarn-package.tgz')) { copyFileSync('yarn-package.tgz', packagePath); rmSync('yarn-package.tgz'); } // Restore original package.json writeFileSync('./package.json', originalPackageJson); this.results.packages.yarn = { name: 'yarn-package.tgz', path: packagePath, size: this.getFileSize(packagePath), manager: 'yarn', registry: 'https://registry.yarnpkg.com/', installCommand: `yarn global add ${this.packageJson.name}` }; this.log(`✅ Yarn package created: yarn-package.tgz`, 'success'); } catch (error) { this.results.errors.push(`Yarn packaging failed: ${error.message}`); this.log(`❌ Yarn packaging failed: ${error.message}`, 'error'); } } // Create PNPM package async createPnpmPackage() { this.log('📦 Creating PNPM package...', 'phase'); try { // PNPM uses standard npm pack but with pnpm-specific optimizations const packageName = execSync('pnpm pack', { encoding: 'utf8' }).trim(); const packagePath = join(this.outputPath, `pnpm-${packageName}`); // Move to output directory if (existsSync(packageName)) { copyFileSync(packageName, packagePath); rmSync(packageName); } this.results.packages.pnpm = { name: `pnpm-${packageName}`, path: packagePath, size: this.getFileSize(packagePath), manager: 'pnpm', registry: 'https://registry.npmjs.org/', installCommand: `pnpm add -g ${this.packageJson.name}` }; this.log(`✅ PNPM package created: pnpm-${packageName}`, 'success'); } catch (error) { this.results.errors.push(`PNPM packaging failed: ${error.message}`); this.log(`❌ PNPM packaging failed: ${error.message}`, 'error'); } } // Create Python package for uv/uvx compatibility async createPythonPackage() { this.log('🐍 Creating Python package...', 'phase'); try { const pythonBridgePath = './python-bridge'; if (!existsSync(pythonBridgePath)) { this.log('Creating Python bridge structure...', 'info'); this.createPythonBridge(); } // Build Python package const originalCwd = process.cwd(); process.chdir(pythonBridgePath); try { // Clean previous builds if (existsSync('./dist')) { rmSync('./dist', { recursive: true }); } // Build package execSync('python -m build', { stdio: 'inherit' }); // Copy to output directory const distFiles = require('fs').readdirSync('./dist'); for (const file of distFiles) { const sourcePath = join('./dist', file); const targetPath = join(originalCwd, this.outputPath, `python-${file}`); copyFileSync(sourcePath, targetPath); } this.results.packages.python = { wheel: distFiles.find(f => f.endsWith('.whl')), tarball: distFiles.find(f => f.endsWith('.tar.gz')), manager: 'pip/uv/uvx', registry: 'https://pypi.org/', installCommands: { pip: `pip install ${this.packageJson.name}`, uv: `uv add ${this.packageJson.name}`, uvx: `uvx install ${this.packageJson.name}` } }; this.log(`✅ Python package created`, 'success'); } finally { process.chdir(originalCwd); } } catch (error) { this.results.errors.push(`Python packaging failed: ${error.message}`); this.log(`❌ Python packaging failed: ${error.message}`, 'error'); } } // Create Docker images async createDockerPackages() { this.log('🐳 Creating Docker packages...', 'phase'); try { // Single architecture build const imageName = `castplan/ultimate-automation:${this.packageJson.version}`; execSync(`docker build -t ${imageName} .`, { stdio: 'inherit' }); // Save as tar const dockerTarPath = join(this.outputPath, `docker-${this.packageJson.version}.tar`); execSync(`docker save -o ${dockerTarPath} ${imageName}`, { stdio: 'inherit' }); this.results.packages.docker = { image: imageName, tarball: dockerTarPath, size: this.getFileSize(dockerTarPath), manager: 'docker', registry: 'ghcr.io', installCommand: `docker pull ${imageName}` }; // Multi-architecture build (if buildx available) try { execSync('docker buildx version', { stdio: 'ignore' }); const multiArchImage = `${imageName}-multiarch`; execSync(`docker buildx build --platform linux/amd64,linux/arm64 -t ${multiArchImage} --load .`, { stdio: 'inherit' }); this.results.packages.dockerMultiArch = { image: multiArchImage, platforms: ['linux/amd64', 'linux/arm64'], manager: 'docker', registry: 'ghcr.io' }; this.log(`✅ Multi-architecture Docker image created`, 'success'); } catch (buildxError) { this.log('Docker buildx not available, skipping multi-arch build', 'warning'); } this.log(`✅ Docker package created: ${imageName}`, 'success'); } catch (error) { this.results.errors.push(`Docker packaging failed: ${error.message}`); this.log(`❌ Docker packaging failed: ${error.message}`, 'error'); } } // Create platform-specific installers async createPlatformInstallers() { this.log('💻 Creating platform-specific installers...', 'phase'); const platforms = ['win32', 'darwin', 'linux']; for (const platform of platforms) { try { await this.createPlatformInstaller(platform); } catch (error) { this.results.errors.push(`${platform} installer failed: ${error.message}`); this.log(`❌ ${platform} installer failed: ${error.message}`, 'error'); } } } // Create platform-specific installer async createPlatformInstaller(platform) { const installerPath = join(this.outputPath, `installer-${platform}`); mkdirSync(installerPath, { recursive: true }); // Create install script const installScript = this.generateInstallScript(platform); const scriptExt = platform === 'win32' ? '.bat' : '.sh'; const scriptPath = join(installerPath, `install${scriptExt}`); writeFileSync(scriptPath, installScript); // Make executable on Unix systems if (platform !== 'win32') { execSync(`chmod +x ${scriptPath}`); } // Copy necessary files const filesToCopy = [ this.results.packages.npm?.path, './README.md', './LICENSE', './DEPLOYMENT.md' ].filter(Boolean); for (const file of filesToCopy) { if (existsSync(file)) { const fileName = require('path').basename(file); copyFileSync(file, join(installerPath, fileName)); } } this.results.packages[`installer-${platform}`] = { path: installerPath, script: scriptPath, platform, installCommand: platform === 'win32' ? 'install.bat' : './install.sh' }; this.log(`✅ ${platform} installer created`, 'success'); } // Generate platform-specific install script generateInstallScript(platform) { const packageName = this.packageJson.name; const isWindows = platform === 'win32'; if (isWindows) { return `@echo off echo Installing CastPlan Ultimate Automation MCP Server... echo. REM Check if Node.js is installed node --version >nul 2>&1 if errorlevel 1 ( echo Error: Node.js is required but not installed. echo Please install Node.js from https://nodejs.org/ exit /b 1 ) REM Check if npm is available npm --version >nul 2>&1 if errorlevel 1 ( echo Error: npm is required but not available. exit /b 1 ) echo Installing package globally... npm install -g ${packageName} if errorlevel 1 ( echo. echo Installation failed. Trying alternative methods... REM Try local installation if exist "${packageName}*.tgz" ( echo Installing from local package... npm install -g ${packageName}*.tgz ) else ( echo No local package found. Please check your internet connection. exit /b 1 ) ) echo. echo Installation completed successfully! echo Run 'castplan-ultimate --help' to get started. pause`; } else { return `#!/bin/bash set -e echo "Installing CastPlan Ultimate Automation MCP Server..." echo # Check if Node.js is installed if ! command -v node &> /dev/null; then echo "Error: Node.js is required but not installed." echo "Please install Node.js from https://nodejs.org/" exit 1 fi # Check if npm is available if ! command -v npm &> /dev/null; then echo "Error: npm is required but not available." exit 1 fi echo "Installing package globally..." # Try global installation if npm install -g ${packageName}; then echo "Installation completed successfully!" elif ls ${packageName}*.tgz 1> /dev/null 2>&1; then echo "Global installation failed. Trying local package..." npm install -g ./${packageName}*.tgz echo "Installation completed successfully!" else echo "Installation failed. Please check your internet connection." exit 1 fi echo echo "Run 'castplan-ultimate --help' to get started."`; } } // Create Python bridge structure createPythonBridge() { const pythonBridgePath = './python-bridge'; mkdirSync(pythonBridgePath, { recursive: true }); mkdirSync(join(pythonBridgePath, 'src', 'castplan_ultimate_automation'), { recursive: true }); // Create __init__.py writeFileSync( join(pythonBridgePath, 'src', 'castplan_ultimate_automation', '__init__.py'), `"""CastPlan Ultimate Automation MCP Server - Python Bridge""" __version__ = "${this.packageJson.version}" def main(): """Main entry point for uvx/uv execution""" import subprocess import sys # Execute the Node.js application try: subprocess.run([ "node", "-e", "import('${this.packageJson.name}').then(m => m.main())" ], check=True) except subprocess.CalledProcessError as e: sys.exit(e.returncode) except FileNotFoundError: print("Error: Node.js is required but not found in PATH") print("Please install Node.js from https://nodejs.org/") sys.exit(1) if __name__ == "__main__": main() ` ); // Update pyproject.toml with current version const pyprojectPath = join(pythonBridgePath, 'pyproject.toml'); if (existsSync(pyprojectPath)) { let pyprojectContent = readFileSync(pyprojectPath, 'utf8'); pyprojectContent = pyprojectContent.replace( /version = "[^"]*"/, `version = "${this.packageJson.version}"` ); writeFileSync(pyprojectPath, pyprojectContent); } } // Utility methods getFileSize(filePath) { try { const stats = require('fs').statSync(filePath); return this.formatBytes(stats.size); } catch (error) { return 'Unknown'; } } formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } getYarnVersion() { try { return execSync('yarn --version', { encoding: 'utf8' }).trim(); } catch (error) { return '1.22.0'; } } // Generate packaging report generateReport() { const totalDuration = performance.now() - this.results.startTime; const report = { timestamp: new Date().toISOString(), duration: Math.round(totalDuration), success: this.results.errors.length === 0, packages: this.results.packages, errors: this.results.errors, warnings: this.results.warnings, summary: { totalPackages: Object.keys(this.results.packages).length, packageManagers: Object.keys(this.results.packages), totalErrors: this.results.errors.length, totalWarnings: this.results.warnings.length } }; // Write report writeFileSync(join(this.outputPath, 'packaging-report.json'), JSON.stringify(report, null, 2)); return report; } // Display results displayResults() { this.log('\n📦 Multi-Manager Packaging Results', 'phase'); this.log('=' .repeat(50), 'phase'); const packageCount = Object.keys(this.results.packages).length; this.log(`📊 Total Packages Created: ${packageCount}`, 'info'); if (packageCount > 0) { this.log('\n📋 Package Details:', 'info'); for (const [manager, details] of Object.entries(this.results.packages)) { if (details.name) { this.log(` ${manager.toUpperCase()}: ${details.name} (${details.size || 'N/A'})`, 'success'); } else { this.log(` ${manager.toUpperCase()}: Created`, 'success'); } } } if (this.results.errors.length > 0) { this.log('\n❌ Errors:', 'error'); this.results.errors.forEach(error => this.log(` ${error}`, 'error')); } if (this.results.warnings.length > 0) { this.log('\n⚠️ Warnings:', 'warning'); this.results.warnings.forEach(warning => this.log(` ${warning}`, 'warning')); } this.log(`\n📁 All packages saved to: ${this.outputPath}`, 'info'); this.log(`📄 Packaging report: ${join(this.outputPath, 'packaging-report.json')}`, 'info'); } // Main packaging method async package() { try { this.log('🚀 Starting multi-manager packaging...', 'phase'); this.log(`📦 Package: ${this.packageJson.name}@${this.packageJson.version}`, 'info'); // Detect available package managers const availableManagers = this.detectPackageManagers(); // Create packages based on available managers const packagingTasks = []; if (availableManagers.npm) { packagingTasks.push(this.createNpmPackage()); } if (availableManagers.yarn) { packagingTasks.push(this.createYarnPackage()); } if (availableManagers.pnpm) { packagingTasks.push(this.createPnpmPackage()); } if (availableManagers.python) { packagingTasks.push(this.createPythonPackage()); } // Docker packaging (if Docker is available) try { execSync('docker --version', { stdio: 'ignore' }); packagingTasks.push(this.createDockerPackages()); } catch (error) { this.log('Docker not available, skipping Docker packaging', 'warning'); } // Platform installers packagingTasks.push(this.createPlatformInstallers()); // Execute all packaging tasks await Promise.allSettled(packagingTasks); // Generate and display report const report = this.generateReport(); this.displayResults(); if (this.results.errors.length === 0) { this.log('\n🎉 Multi-manager packaging completed successfully!', 'success'); } else { this.log('\n⚠️ Packaging completed with errors. See report for details.', 'warning'); } return report; } catch (error) { this.results.errors.push(error.message); this.log(`💥 Packaging failed: ${error.message}`, 'error'); this.generateReport(); process.exit(1); } } } // CLI execution if (import.meta.url === `file://${process.argv[1]}`) { const packager = new MultiManagerPackager(); packager.package().catch(error => { console.error('Multi-manager packaging failed:', error); process.exit(1); }); } export default MultiManagerPackager;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ghostseller/CastPlan_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server