前端自动部署项目到服务器
三方依赖
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