前端自动部署项目到服务器

前端自动部署项目到服务器

三方依赖

bash 复制代码
npm install --save-dev ssh2-sftp-client ora chalk cli-progress

新建deploy.js与dist同级

完整代码

javascript 复制代码
import Client from 'ssh2-sftp-client'
import ora from 'ora'
import chalk from 'chalk'
import fs from 'node:fs'
import path from 'node:path'
import cliProgress from 'cli-progress'
import { fileURLToPath } from 'node:url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

// 服务器配置
const config = {
  host: '', // 例如 '123.123.123.123'
  port: '', // SSH端口,默认22
  username: '', // 例如 'root'
  password: '', // 为了安全,建议后期使用密钥或通过环境变量设置
  localPath: path.join(__dirname, 'dist'),
  remotePath: '' // 服务器上目标目录,例如 '/var/www/html/'
}

class Deployer {
  constructor() {
    this.sftp = new Client()
    this.totalFiles = 0
    this.uploadedFiles = 0
    this.totalSize = 0
    this.uploadedSize = 0

    // 创建进度条
    this.progressBar = new cliProgress.SingleBar({
      format: `上传进度 |${chalk.cyan('{bar}')}| {percentage}% | {value}/{total} 文件 | {speed}`,
      barCompleteChar: '\u2588',
      barIncompleteChar: '\u2591',
      hideCursor: true
    })
  }

  // 获取目录下所有文件
  async getAllFiles(dir) {
    let files = []
    const items = fs.readdirSync(dir)

    for (const item of items) {
      const fullPath = path.join(dir, item)
      const stat = fs.statSync(fullPath)

      if (stat.isDirectory()) {
        const subFiles = await this.getAllFiles(fullPath)
        files = files.concat(subFiles)
      } else {
        files.push({
          path: fullPath,
          size: stat.size,
          relativePath: path.relative(config.localPath, fullPath)
        })
      }
    }

    return files
  }

  // 计算总文件大小
  calculateTotalSize(files) {
    return files.reduce((total, file) => total + file.size, 0)
  }

  // 显示开始信息
  showStartInfo(files) {
    this.totalFiles = files.length
    this.totalSize = this.calculateTotalSize(files)

    console.log(chalk.blue('🚀 开始部署...'))
    console.log(chalk.gray(`📁 本地目录: ${config.localPath}`))
    console.log(chalk.gray(`🌐 远程目录: ${config.remotePath}`))
    console.log(chalk.gray(`📊 文件数量: ${this.totalFiles} 个文件`))
    console.log(chalk.gray(`💾 总大小: ${(this.totalSize / 1024 / 1024).toFixed(2)} MB\n`))

    this.progressBar.start(this.totalFiles, 0, {
      speed: '0 KB/s'
    })
  }

  // 上传单个文件
  async uploadFile(localFile, remoteFile) {
    const startTime = Date.now()

    try {
      await this.sftp.put(localFile, remoteFile)

      this.uploadedFiles++
      this.uploadedSize += fs.statSync(localFile).size

      // 计算上传速度
      const elapsedTime = (Date.now() - startTime) / 1000
      const speed = elapsedTime > 0 ? `${(fs.statSync(localFile).size / 1024 / elapsedTime).toFixed(1)} KB/s` : '计算中...'

      this.progressBar.update(this.uploadedFiles, {
        speed
      })
    } catch (error) {
      console.log(chalk.red(`\n❌ 上传失败: ${localFile}`))
      console.log(chalk.red(`错误信息: ${error.message}`))
      throw error
    }
  }

  // 确保远程目录存在
  async ensureRemotePath() {
    const spinner = ora('检查远程目录...').start()

    try {
      await this.sftp.mkdir(config.remotePath, true)
      spinner.succeed('远程目录检查完成')
    } catch (error) {
      spinner.fail('远程目录检查失败')
      throw error
    }
  }

  // 主部署方法
  async deploy() {
    const connectingSpinner = ora('连接服务器...').start()

    try {
      // 连接服务器
      await this.sftp.connect(config)
      connectingSpinner.succeed('服务器连接成功')

      // 检查远程目录
      await this.ensureRemotePath()

      // 获取所有文件
      const filesSpinner = ora('扫描本地文件...').start()
      const allFiles = await this.getAllFiles(config.localPath)
      filesSpinner.succeed(`找到 ${allFiles.length} 个文件`)

      // 显示开始信息
      this.showStartInfo(allFiles)

      // 上传所有文件
      for (const file of allFiles) {
        const remoteFilePath = path.join(config.remotePath, file.relativePath).replace(/\\/g, '/')
        await this.uploadFile(file.path, remoteFilePath)
      }

      // 完成
      this.progressBar.stop()

      const totalTime = (Date.now() - this.startTime) / 1000
      console.log(chalk.green(`\n✅ 部署完成!`))
      console.log(chalk.gray(`⏱️  总耗时: ${totalTime.toFixed(1)} 秒`))
      console.log(chalk.gray(`📤 上传文件: ${this.uploadedFiles}/${this.totalFiles}`))
      console.log(chalk.gray(`💾 数据量: ${(this.uploadedSize / 1024 / 1024).toFixed(2)} MB`))
    } catch (error) {
      console.log(chalk.red('\n❌ 部署失败:'), error.message)
    } finally {
      // 关闭连接
      await this.sftp.end()
    }
  }
}

// 启动部署
const deployer = new Deployer()
deployer.startTime = Date.now()
deployer.deploy()

脚本配置

在package.json的scripts中添加一行脚本

javascript 复制代码
"deploy": "vite build && node deploy.js"

部署命令

javascript 复制代码
npm run deploy
相关推荐
0思必得010 分钟前
[Web自动化] Selenium处理Cookie
前端·爬虫·python·selenium·自动化
serve the people14 分钟前
python环境搭建 (六) Makefile 简单使用方法
java·服务器·python
徐同保19 分钟前
react-markdown使用
前端·react.js·前端框架
深圳市恒讯科技20 分钟前
2026新加坡服务器配置全攻略:SSL证书、硬件防火墙与CDN加速
运维·服务器·ssl
2601_9498574326 分钟前
Flutter for OpenHarmony Web开发助手App实战:CSS参考
前端·css·flutter
无法长大26 分钟前
如何判断项目需不需要用、能不能用Tailwind CSS
前端·css·vue.js·elementui·vue3·tailwind css
橙露28 分钟前
移动端前端适配:Rem、VW/VH 与媒体查询的综合应用指南
前端·媒体
GGGG寄了44 分钟前
CSS——CSS引入方式+选择器类型
前端·css·html
墨染青竹梦悠然1 小时前
基于Django+vue的图书借阅管理系统
前端·vue.js·后端·python·django·毕业设计·毕设
松涛和鸣1 小时前
DAY69 Practical Guide to Linux Character Device Drivers
linux·服务器·arm开发·数据库·单片机·嵌入式硬件