import { exec } from 'child_process' import { Client as SSHClient } from 'ssh2' import ora from 'ora' import SftpClient from 'ssh2-sftp-client' import chalk from 'chalk' import fs from 'fs' import { loadEnv } from 'vite' // 创建分隔线 const createSeparator = (text) => { const width = 60; const padding = Math.floor((width - text.length - 2) / 2); const separator = '='.repeat(padding) + ' ' + text + ' ' + '='.repeat(padding); return chalk.blue('\n' + separator + '\n'); }; const spinner = ora('正在构建项目...').start() spinner.stopAndPersist({ text: ' __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ \n' + ' | |\n' + ' | *** 运行发布流程 *** |\n' + ' | 【pigx管理系统】 |\n' + ' | __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ |\n' }) spinner.start() let speed = 20 let time = 20 const count = 400 const getProgress = () => { const done = '█' const undone = '░' let progress = Math.floor((speed / count) * 50) if (progress > 50) { progress = 50 } const progressBar = done.repeat(progress) + undone.repeat(50 - progress) return progressBar } const interval = setInterval(() => { spinner.text = `正在构建项目 ${getProgress()} 进度${((speed / count) * 100).toFixed(1)}% 耗时${(time / 10).toFixed(1)}秒 ` speed++ time++ if (speed > count) { speed = 400 } }, 100) const env = loadEnv('', process.cwd()) const command = (list, config, host) => { return new Promise((resolveCommand, rejectCommand) => { const ssh = new SSHClient(); const runCommand = (index) => { if (index >= list.length) { ssh.end(); console.log(chalk.green('✓'), chalk.blue(`端口 ${host} 的远程命令执行完毕`)); resolveCommand(); return; } const cmd = list[index]; console.log(chalk.blue('→'), chalk.gray(`执行命令: ${cmd}`)); ssh.exec(cmd, (err, stream) => { if (err) { console.error(chalk.red('✗'), chalk.red(`命令执行失败: ${err}`)); ssh.end(); rejectCommand(err); return; } let hasError = false; stream.on('close', (code, signal) => { if (hasError && !cmd.includes('docker build')) { console.error(chalk.red('✗'), chalk.red(`命令执行失败,退出码: ${code}`)); ssh.end(); rejectCommand(new Error(`命令 "${cmd}" 失败,退出码: ${code}`)); return; } console.log(chalk.green('✓'), chalk.gray(`命令执行完成: ${cmd}`)); runCommand(index + 1); }).on('data', (data) => { console.log(chalk.gray(data.toString())); // 实时输出命令结果 }).stderr.on('data', (data) => { hasError = true; console.error(chalk.yellow('!'), chalk.yellow(data.toString())); // 实时输出错误信息 }); }); }; ssh.on('ready', () => { console.log(chalk.green('✓'), chalk.blue('SSH 连接成功')); runCommand(0); }).connect(config); // Handle SSH connection errors ssh.on('error', (err) => { console.error(chalk.red('✗'), chalk.red('SSH 连接错误:'), err); rejectCommand(err); }); }); }; const updateFiles = async () => { spinner.text = '正在链接服务器...'; const config = { host: env.VITE_HOST, port: env.VITE_POST, username: env.VITE_USERNAME, password: env.VITE_PASSWORD }; const sftp = new SftpClient(); if (!fs.existsSync('dist')) { console.error(chalk.red('✗'), chalk.red('构建目录不存在')); spinner.fail('构建目录不存在'); spinner.stopAndPersist({ text: '' }); // 停止并清理 spinner return; } try { await sftp.connect(config); spinner.succeed(chalk.green('服务器连接成功')); console.log(chalk.blue('→'), chalk.gray('开始上传文件...')); await sftp.uploadDir('dist', '/data/seo-vue/dist'); console.log(chalk.green('✓'), chalk.blue('6888端口文件上传完成')); sftp.end(); console.log(createSeparator('开始部署 6888 端口')); await command([ "cd /data/seo-vue && pwd", "cd /data/seo-vue && ls -la", "cd /data/seo-vue && docker rm -f seo-vue", "cd /data/seo-vue && docker rmi seo-vue", "cd /data/seo-vue && docker build -t seo-vue .", "cd /data/seo-vue && docker run -d --name seo-vue -p 6888:80 seo-vue" ], config, "6888"); } catch (err) { console.error(chalk.red('✗'), chalk.red('部署或文件上传失败:'), err); spinner.fail(err); spinner.stopAndPersist({ text: '' }); // 停止并清理 spinner sftp.end(); throw err; // 向上抛出错误,让main函数捕获并处理退出 } }; // 项目构建 console.log(createSeparator('开始构建项目')); // 使用 Promise 包装构建过程 const buildWithProgress = () => { return new Promise((resolve, reject) => { // 重置进度条 speed = 20; time = 20; spinner.start(); // 确保每次构建前启动 spinner const buildProcess = exec('npm run build'); let buildOutput = ''; buildProcess.stdout.on('data', (data) => { buildOutput += data; // 根据输出更新进度 if (data.includes('compiled successfully')) { speed = count; } }); buildProcess.stderr.on('data', (data) => { buildOutput += data; }); buildProcess.on('close', async (code) => { if (code === 0) { spinner.succeed(chalk.green(`构建成功`)); resolve(buildOutput); } else { spinner.fail(chalk.red(`构建失败`)); reject(new Error(buildOutput)); } }); }); }; // 主构建流程 const main = async () => { spinner.start(); // 在主流程开始时启动 spinner,用于总体的进度指示 try { console.log(createSeparator('开始构建 6888 端口')); // 构建 6888 端口版本 await buildWithProgress(); // 开始部署 await updateFiles(); // 所有任务完成后,打印最终访问路径并停止 spinner clearInterval(interval); // 在所有任务完成后停止进度条动画 console.log(createSeparator('部署完成')); console.log(chalk.green('🚀'), chalk.cyan(`6888 端口访问地址: http://${env.VITE_HOST}:6888`)); spinner.stopAndPersist({ text: '' }); // 停止并清理 spinner,返回控制台 process.exit(0); // 正常退出,返回控制台 } catch (error) { console.error(chalk.red('✗'), chalk.red('发布流程出错:'), error); spinner.fail(error); spinner.stopAndPersist({ text: '' }); // 停止并清理 spinner,返回控制台 process.exit(1); // 异常退出 }}; // 启动主流程 main();