UNPKG

dreamhost-deployer

Version:

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

311 lines (259 loc) 10.2 kB
/** * DreamHost Deployer * Version 0.6.2 * * Deploy command implementation with enhanced UI */ const path = require('path'); const inquirer = require('inquirer'); const ui = require('../utils/ui'); // Import modular components const configManager = require('../utils/config-manager'); const deployment = require('../utils/deployment'); const buildIntegration = require('../utils/build-integration'); const { checkAndSetupServerIfNeeded } = require('../utils/server-check'); // Default config path const DEFAULT_CONFIG_PATH = path.join(process.cwd(), 'deploy.config.json'); /** * Main deployment function * @param {Object} options Deployment options */ async function deploy(options = {}) { ui.sectionHeader('DREAMHOST DEPLOYMENT'); const configPath = options.configPath || DEFAULT_CONFIG_PATH; try { // Load or create configuration const loadSpinner = ui.spinner('Loading configuration...'); loadSpinner.start(); let config = configManager.loadConfig(configPath); await new Promise(resolve => setTimeout(resolve, 800)); loadSpinner.stop(); if (!config) { console.log(ui.warning(`No configuration found at ${configPath}`)); // Check if we should create a new config const { createNew } = await inquirer.prompt([ { type: 'confirm', name: 'createNew', message: 'Would you like to create a new configuration?', default: true } ]); if (createNew) { // Check if this is potentially a Vite project const detectSpinner = ui.spinner('Detecting project type...'); detectSpinner.start(); const isVite = configManager.detectViteProject(); await new Promise(resolve => setTimeout(resolve, 1000)); detectSpinner.stop(); if (isVite) { console.log(ui.info('Detected a Vite project! Using Vite-specific defaults...')); console.log(ui.projectTypeBadge('vite')); } const configSpinner = ui.spinner('Creating configuration...'); configSpinner.start(); config = await configManager.createConfig(configPath, isVite); await new Promise(resolve => setTimeout(resolve, 1000)); configSpinner.stop(); } else { console.log(ui.error('Deployment cancelled: No configuration available')); return; } } // Validate configuration const validateSpinner = ui.spinner('Validating configuration...'); validateSpinner.start(); const validationErrors = configManager.validateConfig(config); await new Promise(resolve => setTimeout(resolve, 800)); validateSpinner.stop(); if (validationErrors.length > 0) { console.log(ui.error('Configuration validation failed:')); validationErrors.forEach(error => console.log(ui.warning(` • ${error}`))); // Ask if they want to continue anyway const { continueAnyway } = await inquirer.prompt([ { type: 'confirm', name: 'continueAnyway', message: 'Would you like to continue anyway?', default: false } ]); if (!continueAnyway) { console.log(ui.error('Deployment cancelled')); return; } } // Check server environment if needed const serverSpinner = ui.spinner('Checking server environment...'); serverSpinner.start(); await new Promise(resolve => setTimeout(resolve, 1000)); serverSpinner.stop(); await checkAndSetupServerIfNeeded(config); // Run build process if enabled if (config.buildIntegration) { try { console.log(ui.info('Running build process...')); const buildSpinner = ui.spinner(`Running: ${config.buildCommand}`); buildSpinner.start(); const buildResult = await buildIntegration.runBuild(config); buildSpinner.stop(); if (buildResult.success) { console.log(ui.success(`Build completed successfully`)); // Update localPath to use build output directory if it's not already set to it if (!config.localPath.includes(config.buildOutputDir)) { config.localPath = buildResult.outputPath; console.log(ui.info(`Updated local path to build output: ${config.localPath}`)); } } } catch (error) { console.error(ui.error(`Build failed: ${error.message}`)); const { continueDeployment } = await inquirer.prompt([ { type: 'confirm', name: 'continueDeployment', message: 'Would you like to continue with deployment anyway?', default: false } ]); if (!continueDeployment) { console.log(ui.error('Deployment cancelled')); return; } } } // Display deployment summary ui.sectionHeader('DEPLOYMENT SUMMARY'); // Create a table for deployment details const table = ui.createTable(['Setting', 'Value']); table.push( ['Host', config.host], ['Username', config.username], ['Remote Path', config.remotePath], ['Local Path', config.localPath], ['Authentication', config.privateKeyPath ? `SSH Key (${config.privateKeyPath})` : 'Password'], ['Web Server', config.webServer || 'Apache'] ); console.log(table.toString()); // Check if this is a dry run if (options.dryRun) { console.log(ui.info('PERFORMING DRY RUN - no actual deployment will occur')); } // Confirm deployment if (!options.skipConfirmation) { const { confirmDeploy } = await inquirer.prompt([ { type: 'confirm', name: 'confirmDeploy', message: options.dryRun ? 'Start dry run?' : 'Start deployment?', default: true } ]); if (!confirmDeploy) { console.log(ui.warning('Deployment cancelled by user')); return; } } // Perform the deployment console.log(ui.info(options.dryRun ? 'Starting dry run...' : 'Starting deployment...')); const deployResult = await deployment.deploy(config, { dryRun: options.dryRun, rollbackEnabled: options.rollbackEnabled !== false }); if (deployResult.success) { if (!options.dryRun) { console.log(ui.success('Deployment completed successfully!')); // Suggest optimizations based on project type const projectInfo = buildIntegration.detectProjectType(); if (projectInfo.type !== 'unknown') { console.log('\n' + ui.collapsibleSection(`${projectInfo.details} Optimization Tips`, buildIntegration.suggestOptimizations(projectInfo.type).join('\n'))); } } else { console.log(ui.success('Dry run completed - deployment looks good!')); } } else { console.error(ui.error(`Deployment failed: ${deployResult.error}`)); // Offer rollback if available if (deployResult.backupPath) { const { performRollback } = await inquirer.prompt([ { type: 'confirm', name: 'performRollback', message: 'Would you like to roll back to the previous version?', default: true } ]); if (performRollback) { const rollbackSpinner = ui.spinner('Rolling back to previous version...'); rollbackSpinner.start(); const rollbackResult = await deployment.rollback(config, deployResult.backupPath); rollbackSpinner.stop(); if (rollbackResult) { console.log(ui.success('Rollback completed successfully')); } else { console.error(ui.error('Rollback failed')); } } } } } catch (error) { console.error(ui.error(`Deployment process error: ${error.message}`)); } } /** * Run only the build process (without deployment) */ async function runBuildOnly() { ui.sectionHeader('BUILD PROCESS'); const configPath = DEFAULT_CONFIG_PATH; try { // Load configuration const loadSpinner = ui.spinner('Loading configuration...'); loadSpinner.start(); let config = configManager.loadConfig(configPath); await new Promise(resolve => setTimeout(resolve, 800)); loadSpinner.stop(); if (!config) { console.log(ui.warning(`No configuration found at ${configPath}`)); // Detect project type automatically const detectSpinner = ui.spinner('Detecting project type...'); detectSpinner.start(); const projectInfo = buildIntegration.detectProjectType(); await new Promise(resolve => setTimeout(resolve, 1200)); detectSpinner.stop(); if (projectInfo.type !== 'unknown' && projectInfo.buildCommand) { console.log(ui.info(`${projectInfo.details}`)); console.log(ui.projectTypeBadge(projectInfo.type)); console.log(ui.info(`Using detected build command: ${projectInfo.buildCommand}`)); config = { buildIntegration: true, buildCommand: projectInfo.buildCommand, buildOutputDir: projectInfo.outputDir }; } else { throw new Error('Could not determine build settings. Please create a configuration file.'); } } // Run build process if (!config.buildIntegration || !config.buildCommand) { throw new Error('Build integration not enabled in configuration'); } const buildSpinner = ui.spinner(`Running: ${config.buildCommand}`); buildSpinner.start(); const buildResult = await buildIntegration.runBuild(config); buildSpinner.stop(); if (buildResult.success) { console.log(ui.success(`Build completed successfully!`)); // Show output path console.log(ui.info(`Output directory: ${buildResult.outputPath}`)); return buildResult; } } catch (error) { console.error(ui.error(`Build failed: ${error.message}`)); throw error; } } module.exports = { deploy, runBuildOnly };