dreamhost-deployer
Version:
A stylish, interactive CLI tool for deploying websites to DreamHost shared hosting with automated build integration
529 lines (468 loc) • 17.3 kB
JavaScript
#!/usr/bin/env node
/**
* DreamHost Deployer
* Version 0.6.2
*
* Command-line interface with a stylish interactive UI
*
* Features:
* - Stylish interactive terminal interface
* - Simple deployment to DreamHost via SSH/SCP
* - Server environment checks
* - Build integration for modern frameworks
* - Dry run mode to preview deployments
* - Automatic rollback for failed deployments
* - Progress bars for large deployments
*/
const path = require('path');
const { program } = require('commander');
const chalk = require('chalk');
const fs = require('fs');
const pkg = require('../package.json');
// Custom version display function
function displayVersion() {
// Simple ASCII art using border characters
const border = '━'.repeat(40);
const emptyLine = '┃' + ' '.repeat(38) + '┃';
// Create version box with manual ASCII art
console.log(chalk.cyan('┏' + border + '┓'));
console.log(chalk.cyan('┃' + ' '.repeat(38) + '┃'));
console.log(chalk.cyan('┃') + chalk.bold.cyan(` DreamHost Deployer v${pkg.version}`) + ' '.repeat(15 - pkg.version.length) + chalk.cyan('┃'));
console.log(chalk.cyan('┃' + ' '.repeat(38) + '┃'));
console.log(chalk.cyan('┗' + border + '┛'));
console.log();
// ASCII art of bear character holding sign
console.log(' \\');
console.log(' \\');
console.log(' \\');
console.log(' ' + chalk.green('ʕ•ᴥ•ʔ') + ' ' + chalk.bold.blue('~ DreamHost Deployer ~'));
console.log(' ' + chalk.blue('|') + ' ' + chalk.yellow('\\o/'));
console.log(' ' + chalk.blue('|') + ' ' + chalk.yellow('|'));
console.log(' ' + chalk.blue('/') + ' ' + chalk.blue('\\') + ' ' + chalk.yellow('/') + ' ' + chalk.yellow('\\'));
console.log();
// Additional info
console.log(chalk.blue('✧ A stylish CLI tool for deploying websites to DreamHost ✧'));
console.log(chalk.underline.blue('https://github.com/jakerains/dreamhost-deployer'));
process.exit(0);
}
// Check if version flag is passed
if (process.argv.includes('-v') || process.argv.includes('--version') || process.argv.includes('version')) {
displayVersion();
}
// Import remaining modules after version check
const deploy = require('../deploy');
const setupSsh = require('../setup-ssh');
const setupNode = require('../src/commands/setup-node');
const fixSshKey = require('../fix-ssh-key');
const { checkServerEnvironment } = require('../src/utils/server-check');
const buildIntegration = require('../src/utils/build-integration');
const ui = require('../src/utils/ui');
const inquirer = require('inquirer');
// Set up the CLI program
program
.name('dreamhost-deployer')
.description('Deploy websites to DreamHost servers via SSH')
.version(pkg.version, '-v, --version', 'Display fancy version information');
// Add dedicated version command that uses our fancy version display
program
.command('version')
.description('Display fancy version information')
.action(displayVersion);
// Initialize command - sets up project for deployment
program
.command('init')
.description('Set up project for DreamHost deployment (SSH, config, server environment)')
.action(() => {
const initCommand = require('../src/commands/init-command');
initCommand.run();
});
// Main menu command (default when no arguments provided)
program
.command('menu', { isDefault: true })
.description('Show interactive menu of available commands')
.action(async () => {
ui.showAppHeader();
// Create emoji-filled, categorized menu
const choices = [
{
name: 'DEPLOYMENT',
type: 'separator'
},
{
name: '🚀 Deploy website to DreamHost',
value: 'deploy',
description: 'Deploy your website files to DreamHost server'
},
{
name: '🔍 Dry run (preview deployment)',
value: 'dry-run',
description: 'Preview what files would be deployed without making changes'
},
{
name: '🔨 Run build process only',
value: 'build',
description: 'Build your project without deploying'
},
{
name: 'CONFIGURATION',
type: 'separator'
},
{
name: '🚀 Initialize project',
value: 'init',
description: 'Set up config, SSH, and server for DreamHost deployment'
},
{
name: '📋 Project-specific settings',
value: 'project-settings',
description: 'Configure build settings for various frameworks'
},
{
name: 'SERVER SETUP',
type: 'separator'
},
{
name: '🔑 Setup SSH key authentication',
value: 'setup-ssh',
description: 'Generate and upload SSH keys to DreamHost'
},
{
name: '🔧 Fix SSH key permissions',
value: 'fix-ssh-key',
description: 'Fix common SSH key permission issues'
},
{
name: '🔍 Check server environment',
value: 'check-server',
description: 'Check Node.js and NVM on your DreamHost server'
},
{
name: '📦 Setup Node.js on server',
value: 'setup-node',
description: 'Install Node.js and NVM on your DreamHost server'
},
{
name: 'INFORMATION',
type: 'separator'
},
{
name: '❓ About DreamHost Deployer',
value: 'about',
description: 'Show information about this tool'
},
{
name: '📑 Show documentation',
value: 'docs',
description: 'Display helpful documentation and links'
},
{
name: '❌ Exit',
value: 'exit',
description: 'Exit the program'
}
];
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'What would you like to do?',
loop: false,
pageSize: choices.length,
choices: choices.map(choice => {
if (choice.type === 'separator') {
return new inquirer.Separator(`\n${choice.name}\n`);
}
return {
name: `${choice.name.padEnd(35)} ${choice.description}`,
value: choice.value
};
})
}
]);
// Add a short delay for visual effect
const actionSpinner = ui.spinner('Loading action...');
actionSpinner.start();
await new Promise(resolve => setTimeout(resolve, 800));
actionSpinner.stop();
switch (action) {
case 'deploy':
deploy.deploy({ dryRun: false, rollbackEnabled: true });
break;
case 'dry-run':
deploy.deploy({ dryRun: true, rollbackEnabled: false });
break;
case 'build':
try {
const buildSpinner = ui.spinner('Preparing build process...');
buildSpinner.start();
await new Promise(resolve => setTimeout(resolve, 1000));
buildSpinner.stop();
console.log(ui.info('Running build process...'));
await deploy.runBuildOnly();
} catch (error) {
console.error(ui.error(`Build failed: ${error.message}`));
}
break;
case 'init':
const initCommand = require('../src/commands/init-command');
await initCommand.run();
break;
case 'setup-ssh':
setupSsh.run();
break;
case 'check-server':
await checkServerCmd();
break;
case 'setup-node':
setupNode.run();
break;
case 'fix-ssh-key':
fixSshKey.run();
break;
case 'about':
showAbout();
break;
case 'docs':
showDocs();
break;
case 'project-settings':
await showProjectSettings();
break;
case 'exit':
const exitSpinner = ui.spinner('Exiting...');
exitSpinner.start();
await new Promise(resolve => setTimeout(resolve, 1000));
exitSpinner.stop();
console.log(ui.success('Thanks for using DreamHost Deployer! 👋'));
process.exit(0);
break;
}
});
// Deploy command
program
.command('deploy')
.description('Deploy your website to DreamHost')
.option('-c, --config <path>', 'Path to config file')
.option('-d, --dry-run', 'Perform a dry run (preview deployment)')
.option('--no-rollback', 'Disable automatic rollback on failure')
.action((options) => {
deploy.deploy({
configPath: options.config,
dryRun: options.dryRun || false,
rollbackEnabled: options.rollback !== false
});
});
// Build only command
program
.command('build')
.description('Run the build process without deploying')
.action(async () => {
try {
console.log(ui.info('Running build process...'));
await deploy.runBuildOnly();
} catch (error) {
console.error(ui.error(`Build failed: ${error.message}`));
}
});
// Setup SSH command
program
.command('setup-ssh')
.description('Setup SSH key authentication for DreamHost')
.action(() => {
setupSsh.run();
});
// Fix SSH key command
program
.command('fix-ssh-key')
.description('Fix SSH key permissions')
.action(() => {
fixSshKey.run();
});
// Server check command
program
.command('check-server')
.description('Check Node.js environment on DreamHost server')
.action(async () => {
await checkServerCmd();
});
// Setup Node command
program
.command('setup-node')
.description('Setup Node.js on DreamHost server')
.action(() => {
setupNode.run();
});
// Project settings command
program
.command('project-settings')
.description('Configure project-specific settings')
.action(async () => {
await showProjectSettings();
});
// Helper function for check server command
async function checkServerCmd() {
ui.sectionHeader('SERVER ENVIRONMENT CHECK');
// Load config to get server details
const configPath = path.join(process.cwd(), 'deploy.config.json');
if (!fs.existsSync(configPath)) {
console.log(ui.warning('No configuration file found. Please create one first.'));
return;
}
try {
const checkSpinner = ui.spinner('Checking server environment...');
checkSpinner.start();
await new Promise(resolve => setTimeout(resolve, 1000));
checkSpinner.stop();
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
await checkServerEnvironment(config);
} catch (error) {
console.error(ui.error(`Error checking server: ${error.message}`));
}
}
// Helper function to show project settings menu
async function showProjectSettings() {
ui.sectionHeader('PROJECT SETTINGS');
try {
const detectSpinner = ui.spinner('Detecting project type...');
detectSpinner.start();
await new Promise(resolve => setTimeout(resolve, 1500));
detectSpinner.stop();
const projectInfo = buildIntegration.detectProjectType();
if (projectInfo.type !== 'unknown') {
console.log('\n' + ui.info(`${projectInfo.details}`));
console.log(ui.projectTypeBadge(projectInfo.type));
// Create a table for settings
const table = ui.createTable(['Setting', 'Value']);
table.push(
['Build Command', projectInfo.buildCommand || 'N/A'],
['Output Directory', projectInfo.outputDir || 'N/A']
);
console.log('\n' + table.toString());
// Show exclusions
if (projectInfo.exclude && projectInfo.exclude.length > 0) {
console.log('\n' + ui.collapsibleSection('Excluded Files/Directories',
projectInfo.exclude.map(item => `• ${item}`).join('\n')));
}
// Show optimization tips
const suggestions = buildIntegration.suggestOptimizations(projectInfo.type);
console.log('\n' + ui.collapsibleSection('Optimization Tips',
suggestions.join('\n')));
const { applySettings } = await inquirer.prompt([
{
type: 'confirm',
name: 'applySettings',
message: 'Would you like to apply these settings to your configuration?',
default: true
}
]);
if (applySettings) {
const configSpinner = ui.spinner('Applying configuration settings...');
configSpinner.start();
await new Promise(resolve => setTimeout(resolve, 1000));
configSpinner.stop();
// Load or create config
const configManager = require('../src/utils/config-manager');
const configPath = path.join(process.cwd(), 'deploy.config.json');
let config = configManager.loadConfig(configPath);
if (!config) {
console.log(ui.info('Creating new configuration file...'));
config = await configManager.createConfig(configPath, projectInfo.type === 'vite');
}
// Update with detected settings
config.buildIntegration = true;
config.buildCommand = projectInfo.buildCommand;
config.buildOutputDir = projectInfo.outputDir;
config.exclude = projectInfo.exclude;
// Save updated config
configManager.saveConfig(config, configPath);
console.log(ui.success('Configuration updated with project-specific settings'));
}
} else {
console.log(ui.warning('Could not automatically detect project type.'));
console.log(ui.info('Please configure your build settings manually.'));
// Prompt for manual configuration
const { setupManually } = await inquirer.prompt([
{
type: 'confirm',
name: 'setupManually',
message: 'Would you like to set up build integration manually?',
default: true
}
]);
if (setupManually) {
const configManager = require('../src/utils/config-manager');
const configPath = path.join(process.cwd(), 'deploy.config.json');
await configManager.createConfig(configPath, false);
}
}
} catch (error) {
console.error(ui.error(`Error: ${error.message}`));
}
}
// Helper function to show about information
function showAbout() {
ui.sectionHeader('ABOUT DREAMHOST DEPLOYER');
console.log(ui.info(`Version: ${pkg.version}`));
console.log(ui.info('A stylish CLI tool for deploying websites to DreamHost servers via SSH'));
console.log(ui.link('GitHub Repository', 'https://github.com/jakerains/dreamhost-deployer'));
console.log(ui.info('Author: jakerains'));
console.log(ui.info('License: MIT'));
// Create feature table
const featureTable = ui.createTable(['Feature', 'Description']);
featureTable.push(
['Easy Deployment', 'Deploy to DreamHost with a single command'],
['SSH Key Management', 'Set up and manage SSH keys for secure deployments'],
['Build Integration', 'Automatic build process for modern frameworks'],
['Dry Run Mode', 'Preview deployments without making changes'],
['Automatic Rollback', 'Revert to previous version if deployment fails'],
['Progress Tracking', 'Visual progress bars for large deployments'],
['Server Configuration', 'Set up Node.js and other requirements on your server'],
['Framework Detection', 'Automatically detect and optimize for your project type']
);
console.log('\n' + featureTable.toString());
// Show supported frameworks
console.log('\n' + ui.collapsibleSection('Supported Frameworks', [
ui.projectTypeBadge('vite') + ' Vite (React, Vue, Svelte, etc.)',
ui.projectTypeBadge('react') + ' Create React App',
ui.projectTypeBadge('nextjs') + ' Next.js',
ui.projectTypeBadge('gatsby') + ' Gatsby',
ui.projectTypeBadge('nuxt') + ' Nuxt.js',
ui.projectTypeBadge('vue-cli') + ' Vue CLI',
ui.projectTypeBadge('svelte') + ' SvelteKit',
ui.projectTypeBadge('angular') + ' Angular'
].join('\n')));
}
// Helper function to show documentation
function showDocs() {
ui.sectionHeader('DOCUMENTATION');
// Quick start guide
console.log(ui.collapsibleSection('Quick Start', `
1. Initialize project (sets up config and tests connection):
${ui.codeBlock('dreamhost-deployer init')}
2. If SSH setup wasn't completed during init:
${ui.codeBlock('dreamhost-deployer setup-ssh')}
3. Deploy your website:
${ui.codeBlock('dreamhost-deployer deploy')}
`));
// Common commands
console.log(ui.collapsibleSection('Common Commands', `
• Initialize project:
${ui.codeBlock('dreamhost-deployer init')}
• Deploy website:
${ui.codeBlock('dreamhost-deployer deploy')}
• Preview deployment (dry run):
${ui.codeBlock('dreamhost-deployer deploy --dry-run')}
• Build without deploying:
${ui.codeBlock('dreamhost-deployer build')}
• Configure project settings:
${ui.codeBlock('dreamhost-deployer project-settings')}
`));
// Helpful links
console.log(ui.collapsibleSection('Helpful Links', [
ui.link('DreamHost SSH Guide', 'https://help.dreamhost.com/hc/en-us/articles/216041267-SSH-overview'),
ui.link('DreamHost Node.js Guide', 'https://help.dreamhost.com/hc/en-us/articles/360029083351-Installing-a-custom-version-of-NVM-and-Node-js'),
ui.link('Deployer Documentation', 'https://github.com/jakerains/dreamhost-deployer/blob/main/README.md')
].join('\n')));
}
// Parse command line arguments
program.parse(process.argv);