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

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

三方依赖

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
相关推荐
非凡ghost2 小时前
猫眼浏览器(Chrome内核增强版浏览器)官方便携版
前端·网络·chrome·windows·软件需求
Never_Satisfied2 小时前
在JavaScript / HTML / Node.js中,post方式的Content-Type属性的text的三种编码
javascript·node.js·html
思考的笛卡尔2 小时前
Go语言实战:高并发服务器设计与实现
服务器·开发语言·golang
C2X2 小时前
Vue3.0 学习总结
前端
汤姆Tom2 小时前
CSS 新特性与未来趋势
前端·css·面试
尘世中一位迷途小书童2 小时前
🚀 pnpm + Monorepo 实战指南:现代前端项目管理的最佳实践
前端·架构
Never_Satisfied3 小时前
在JavaScript / HTML中,Chrome报错Refused to execute inline script
javascript·chrome·html
杨超越luckly3 小时前
HTML应用指南:利用GET请求获取全国中国建设银行网点位置信息
前端·arcgis·html·数据可视化·门店数据
王翼鹏3 小时前
html 全角空格和半角空格
前端·html