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

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

三方依赖

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
相关推荐
刘逸潇200519 分钟前
CSS基础语法
前端·css
奋斗的蛋黄1 小时前
网络卡顿运维排查方案:从客户端到服务器的全链路处理
运维·服务器·网络
吃饺子不吃馅1 小时前
[开源] 从零到一打造在线 PPT 编辑器:React + Zustand + Zundo
前端·svg·图形学
wanhengidc2 小时前
云手机搬砖 尤弥尔传奇自动化操作
运维·服务器·arm开发·安全·智能手机·自动化
图图图图爱睡觉2 小时前
主机跟虚拟机ip一直Ping不通,并且虚拟机使用ifconfig命令时,ens33没有ipv4地址,只有ipv6地址
服务器·网络·tcp/ip
小马哥编程2 小时前
【软考架构】案例分析-Web应用设计(应用服务器概念)
前端·架构
鱼与宇2 小时前
苍穹外卖-VUE
前端·javascript·vue.js
啃火龙果的兔子2 小时前
前端直接渲染Markdown
前端
deephub2 小时前
FastMCP 入门:用 Python 快速搭建 MCP 服务器接入 LLM
服务器·人工智能·python·大语言模型·mcp
z-robot2 小时前
Nginx 配置代理
前端