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

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

三方依赖

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
相关推荐
啊吧怪不啊吧3 小时前
UU远程协助迎来升级!第一期更新实测
运维·服务器·远程工作
一 乐8 小时前
婚纱摄影网站|基于ssm + vue婚纱摄影网站系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端
C_心欲无痕9 小时前
ts - tsconfig.json配置讲解
linux·前端·ubuntu·typescript·json
清沫9 小时前
Claude Skills:Agent 能力扩展的新范式
前端·ai编程
yinuo9 小时前
前端跨页面通信终极指南:方案拆解、对比分析
前端
yinuo10 小时前
前端跨页面通讯终极指南⑨:IndexedDB 用法全解析
前端
cuijiecheng201810 小时前
Linux下Beyond Compare过期
linux·运维·服务器
xkxnq10 小时前
第二阶段:Vue 组件化开发(第 16天)
前端·javascript·vue.js
烛阴10 小时前
拒绝配置地狱!5 分钟搭建 Three.js + Parcel 完美开发环境
前端·webgl·three.js
Van_Moonlight10 小时前
RN for OpenHarmony 实战 TodoList 项目:空状态占位图
javascript·开源·harmonyos