UNPKG

dreamhost-deployer

Version:

A stylish, interactive CLI tool for deploying websites to DreamHost shared hosting with automated build integration

331 lines (308 loc) 13 kB
const { exec } = require('child_process'); const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); const { promisify } = require('util'); const execAsync = promisify(exec); /** * Build integration module for DreamHost Deployer * Handles running build processes for various project types */ // Main build function async function runBuild(config) { if (!config.buildIntegration || !config.buildCommand) { throw new Error('Build integration is not enabled or build command is not specified'); } console.log(chalk.blue(`🔨 Running build command: ${config.buildCommand}`)); try { // Execute the build command const { stdout, stderr } = await execAsync(config.buildCommand, { cwd: process.cwd(), shell: true, env: { ...process.env, FORCE_COLOR: 'true' } // Preserve color output }); if (stdout) { console.log(chalk.gray('Build output:')); console.log(stdout); } if (stderr && stderr.trim().length > 0) { console.log(chalk.yellow('Build warnings/errors:')); console.log(stderr); } // Verify that build output directory exists const buildOutputPath = path.resolve(process.cwd(), config.buildOutputDir); if (!fs.existsSync(buildOutputPath)) { // Create the directory instead of throwing an error console.log(chalk.yellow(`⚠️ Build output directory not found: ${buildOutputPath}, creating it...`)); try { fs.mkdirSync(buildOutputPath, { recursive: true }); console.log(chalk.green(`✅ Created build output directory: ${buildOutputPath}`)); } catch (err) { throw new Error(`Failed to create build output directory: ${err.message}`); } } return { success: true, outputPath: buildOutputPath }; } catch (error) { console.error(chalk.red(`❌ Build failed: ${error.message}`)); // Provide helpful errors based on common framework issues const errorOutput = error.message + (error.stderr || ''); if (errorOutput.includes('vite')) { if (errorOutput.includes('not found') || errorOutput.includes('command not found')) { console.log(chalk.yellow('\nTroubleshooting Vite build issues:')); console.log('- Make sure Vite is installed: npm install vite --save-dev'); console.log('- Check your package.json for a build script: "build": "vite build"'); } else if (errorOutput.includes('Cannot find module')) { console.log(chalk.yellow('\nTroubleshooting Vite module issues:')); console.log('- Try running: npm install'); console.log('- Check for missing dependencies in your project'); } } else if (errorOutput.includes('react-scripts')) { console.log(chalk.yellow('\nTroubleshooting Create React App build issues:')); console.log('- Make sure react-scripts is installed: npm install react-scripts --save-dev'); console.log('- Check your package.json for a build script: "build": "react-scripts build"'); } else if (errorOutput.includes('next')) { console.log(chalk.yellow('\nTroubleshooting Next.js build issues:')); console.log('- Make sure Next.js is installed: npm install next --save-dev'); console.log('- Check your package.json for a build script: "build": "next build"'); } throw error; } } // Detect project type for better build integration function detectProjectType() { try { // Check for Python project markers first const hasPipfile = fs.existsSync(path.join(process.cwd(), 'Pipfile')); const hasRequirements = fs.existsSync(path.join(process.cwd(), 'requirements.txt')); const hasSetupPy = fs.existsSync(path.join(process.cwd(), 'setup.py')); const hasPyprojectToml = fs.existsSync(path.join(process.cwd(), 'pyproject.toml')); const hasManagePy = fs.existsSync(path.join(process.cwd(), 'manage.py')); const hasAppPy = fs.existsSync(path.join(process.cwd(), 'app.py')) || fs.existsSync(path.join(process.cwd(), 'wsgi.py')) || fs.existsSync(path.join(process.cwd(), 'application.py')); // Python project detection if (hasPipfile || hasRequirements || hasSetupPy || hasPyprojectToml || hasManagePy || hasAppPy) { // Determine Python project type if (hasManagePy) { return { type: 'django', details: 'Django project detected', buildCommand: 'python manage.py collectstatic --noinput', outputDir: 'staticfiles', exclude: ['__pycache__', '*.pyc', '.env*', '.venv', 'venv', 'env', '*.sqlite3', '.pytest_cache', '.coverage', 'htmlcov'] }; } else if (hasAppPy || fs.existsSync(path.join(process.cwd(), 'wsgi.py'))) { return { type: 'flask', details: 'Flask project detected', buildCommand: '[ -d "static" ] && echo "Static files ready" || mkdir -p static', outputDir: 'static', exclude: ['__pycache__', '*.pyc', '.env*', '.venv', 'venv', 'env', '*.sqlite3', '.pytest_cache', '.coverage', 'htmlcov'] }; } else if (hasPyprojectToml) { // Check for FastAPI try { const pyprojectContent = fs.readFileSync(path.join(process.cwd(), 'pyproject.toml'), 'utf8'); if (pyprojectContent.includes('fastapi')) { return { type: 'fastapi', details: 'FastAPI project detected', buildCommand: '[ -d "static" ] && echo "Static files ready" || mkdir -p static', outputDir: 'static', exclude: ['__pycache__', '*.pyc', '.env*', '.venv', 'venv', 'env', '*.sqlite3', '.pytest_cache', '.coverage', 'htmlcov'] }; } } catch (err) { // Continue with generic Python detection } } // Generic Python project return { type: 'python', details: 'Python project detected', buildCommand: hasRequirements ? 'pip install -r requirements.txt' : (hasPipfile ? 'pipenv install --deploy' : null), outputDir: fs.existsSync(path.join(process.cwd(), 'dist')) ? 'dist' : (fs.existsSync(path.join(process.cwd(), 'build')) ? 'build' : (fs.existsSync(path.join(process.cwd(), 'static')) ? 'static' : '.')), exclude: ['__pycache__', '*.pyc', '.env*', '.venv', 'venv', 'env', '*.sqlite3', '.pytest_cache', '.coverage', 'htmlcov'] }; } // Check for package.json (Node.js projects) const packageJsonPath = path.join(process.cwd(), 'package.json'); if (!fs.existsSync(packageJsonPath)) { return { type: 'unknown', details: 'No package.json or Python project markers found' }; } const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Check for various frameworks if (deps.vite || fs.existsSync(path.join(process.cwd(), 'vite.config.js')) || fs.existsSync(path.join(process.cwd(), 'vite.config.ts'))) { return { type: 'vite', details: 'Vite project detected', buildCommand: 'npm run build', outputDir: 'dist', exclude: ['node_modules', '.git', 'src', 'public', '*.config.js', '*.config.ts', '.env*'] }; } else if (deps['react-scripts']) { return { type: 'cra', details: 'Create React App project detected', buildCommand: 'npm run build', outputDir: 'build', exclude: ['node_modules', '.git', 'src', 'public', '.env*'] }; } else if (deps.next) { return { type: 'nextjs', details: 'Next.js project detected', buildCommand: 'npm run build', outputDir: 'out', // Assumes export was run exclude: ['node_modules', '.git', 'src', 'pages', 'public', '.next', '.env*'] }; } else if (deps.gatsby) { return { type: 'gatsby', details: 'Gatsby project detected', buildCommand: 'npm run build', outputDir: 'public', exclude: ['node_modules', '.git', 'src', '.cache', '.env*'] }; } else if (deps.nuxt || deps['@nuxt/core']) { return { type: 'nuxt', details: 'Nuxt.js project detected', buildCommand: 'npm run build', outputDir: 'dist', exclude: ['node_modules', '.git', 'assets', 'components', 'layouts', 'pages', 'plugins', 'static', '.nuxt', '.env*'] }; } else if (deps.vue || deps['@vue/cli-service']) { return { type: 'vue-cli', details: 'Vue CLI project detected', buildCommand: 'npm run build', outputDir: 'dist', exclude: ['node_modules', '.git', 'src', 'public', 'vue.config.js', '.env*'] }; } else if (deps.svelte || deps['svelte-kit']) { return { type: 'svelte', details: 'Svelte/SvelteKit project detected', buildCommand: 'npm run build', outputDir: 'build', exclude: ['node_modules', '.git', 'src', 'static', 'svelte.config.js', '.env*'] }; } else if (deps.angular || deps['@angular/core']) { return { type: 'angular', details: 'Angular project detected', buildCommand: 'npm run build', outputDir: 'dist', exclude: ['node_modules', '.git', 'src', 'e2e', 'angular.json', '.env*'] }; } // Generic Node.js project return { type: 'generic', details: 'Generic Node.js project detected', buildCommand: packageJson.scripts?.build ? 'npm run build' : null, outputDir: fs.existsSync(path.join(process.cwd(), 'dist')) ? 'dist' : (fs.existsSync(path.join(process.cwd(), 'build')) ? 'build' : null), exclude: ['node_modules', '.git', 'src', '.env*'] }; } catch (error) { console.error(chalk.red(`Error detecting project type: ${error.message}`)); return { type: 'unknown', details: `Error: ${error.message}`, error }; } } // Suggest framework-specific optimizations function suggestOptimizations(projectType) { const suggestions = { // Python project types python: [ '- Use a requirements.txt file to specify dependencies', '- Consider using virtualenv or pipenv for dependency isolation', '- Store configuration in environment variables', '- Use a .env file (gitignored) for local development' ], django: [ '- Configure STATIC_ROOT and STATIC_URL in settings.py', '- Set DEBUG=False in production', '- Use Django\'s whitenoise for static file serving', '- Configure allowed hosts in settings.py' ], flask: [ '- Use Flask\'s static folder for static assets', '- Configure app.config from environment variables', '- Use Gunicorn or uWSGI for production serving', '- Set up proper error handling routes' ], fastapi: [ '- Organize static files in the /static directory', '- Use environment variables for configuration', '- Configure CORS settings for API access', '- Add proper error handling middleware' ], // JavaScript framework types vite: [ '- Use import.meta.env for environment variables', '- Enable build optimizations in vite.config.js', '- Consider using /assets/ for static files', '- Add base: "/" to vite.config.js for proper path resolution' ], cra: [ '- Use process.env.PUBLIC_URL for asset paths', '- Add "homepage": "." to package.json for relative paths', '- Create a .env.production file for production environment variables' ], nextjs: [ '- Use next export for static site generation', '- Configure basePath in next.config.js', '- Use next/image for optimized images' ], gatsby: [ '- Use gatsby-plugin-sitemap for SEO', '- Configure pathPrefix in gatsby-config.js', '- Use gatsby-image for optimized images' ], nuxt: [ '- Set target: "static" in nuxt.config.js', '- Configure generate.dir for custom output directory', '- Use nuxt/image for optimized images' ], 'vue-cli': [ '- Set publicPath: "./" in vue.config.js for relative paths', '- Enable modern build mode for better performance', '- Use vue/cli-plugin-pwa for offline support' ], svelte: [ '- Configure paths.base in svelte.config.js', '- Use "adapter-static" for static site generation', '- Consider URL rewriting for SPA mode' ], angular: [ '- Set "baseHref": "/" in angular.json', '- Enable production mode for optimized builds', '- Use Angular Universal for SSR if needed' ] }; return suggestions[projectType] || [ '- Use relative paths for assets', '- Create a .htaccess file for Apache URL rewriting', '- Set correct base URL in your HTML' ]; } module.exports = { runBuild, detectProjectType, suggestOptimizations };