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

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

三方依赖

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
相关推荐
架构技术专栏4 小时前
Agent 工具调用:从工具声明到执行反馈
前端·人工智能·后端
别动我齐刘海4 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
志尊宝5 小时前
Vue3 零基础每日笔记(071):暗黑模式与主题切换——CSS 变量 + Element Plus 暗黑方案
前端·css·笔记·vue·html·前端开发·软件开发
刘发财6 小时前
前端如何生成10000页PDF文件?
前端·javascript·开源
A接拉起0076 小时前
2026 年 JavaScript 开发者需要了解的知识
前端·javascript·后端
_馒头6 小时前
pixiJs游戏开发实践 - 跳棋小游戏
javascript·vue.js
用户11481867894846 小时前
面试官:大屏项目里十几个模块同时渲染上万条数据,怎么做到不卡?
前端·性能优化
前端市界6 小时前
Pro React Admin: 统一工程底座,承载多种应用...
前端·github
葡萄城技术团队7 小时前
dsh-spreadjs-editor 当前支持什么:安装前需要了解的几件事
前端
三*一7 小时前
优化修整算法
前端·javascript·算法