yw-auto-deploy-tool
Version:
一个自动打包、上传并部署的脚本工具
142 lines (120 loc) • 4.65 kB
JavaScript
// lib/index.js
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const ClientSFtp = require('ssh2-sftp-client');
const { Client } = require('ssh2');
const ProgressBar = require('progress');
const sftp = new ClientSFtp();
function resolveConfig(configPath) {
if (!fs.existsSync(configPath)) {
throw new Error(`未找到配置文件: ${configPath}`);
}
return require(configPath);
}
async function zipDist(localDistDir, localZipPath) {
const zipPath = path.resolve(localZipPath);
if (fs.existsSync(zipPath)) {
fs.unlinkSync(zipPath);
}
const output = fs.createWriteStream(zipPath);
const archive = archiver('zip', { zlib: { level: 9 } });
archive.pipe(output);
archive.directory(localDistDir + '/', false);
await archive.finalize();
return new Promise((resolve, reject) => {
output.on('close', () => {
const sizeMB = (archive.pointer() / 1_000_000).toFixed(2);
console.log(`✅ dist.zip 打包完成,大小约 ${sizeMB} MB`);
resolve(zipPath);
});
archive.on('error', err => reject(err));
});
}
async function uploadToSFTP(zipPath, remoteZipPath, sshConfig) {
try {
console.log(sshConfig)
console.log(remoteZipPath, 'remoteZipPath')
console.log(zipPath, 'zipPath')
await sftp.connect(sshConfig);
console.log('📡 已连接 SFTP,开始上传...');
const fileSize = fs.statSync(zipPath).size;
const bar = new ProgressBar('📤 上传进度 [:bar] :percent :etas', {
complete: '=', incomplete: ' ', width: 40, total: fileSize,
});
let lastTransferred = 0;
await sftp.fastPut(zipPath, remoteZipPath, {
step: (transferred) => {
const delta = transferred - lastTransferred;
lastTransferred = transferred;
bar.tick(delta);
},
});
console.log('\n✅ 上传成功!');
} catch (err) {
throw new Error('上传失败: ' + err.message);
} finally {
await sftp.end();
}
}
function execCommandOnServer(sshConfig, command) {
return new Promise((resolve, reject) => {
const conn = new Client();
conn.on('ready', () => {
conn.exec(command, (err, stream) => {
if (err) return reject(err);
let stdout = '', stderr = '';
stream.on('close', (code) => {
conn.end();
if (code === 0) resolve({ stdout, stderr });
else reject(new Error(`命令失败: ${stderr}`));
});
stream.on('data', data => stdout += data.toString());
stream.stderr.on('data', data => stderr += data.toString());
});
}).on('error', reject).connect(sshConfig);
});
}
async function backupRemoteDir(sshConfig, remoteDir) {
const dateStr = new Date().toISOString().replace(/[:.]/g, '_');
const backupDir = `${remoteDir}_backup_${dateStr}`;
try {
await execCommandOnServer(sshConfig, `mv ${remoteDir} ${backupDir}`);
console.log(`✅ 已备份 ${remoteDir} -> ${backupDir}`);
} catch (err) {
console.warn('⚠️ 远程目录备份失败(可能原目录不存在):', err.message);
}
}
async function execUnzipOnServer(sshConfig, zipPath, targetDir) {
await backupRemoteDir(sshConfig, targetDir);
const cmd = `unzip -o ${zipPath} -d ${targetDir}`;
await execCommandOnServer(sshConfig, cmd);
console.log('✅ 解压完成');
}
function cleanLocalBuild(localDistDir, localZipPath) {
try {
if (fs.existsSync(localZipPath)) fs.unlinkSync(localZipPath);
if (fs.existsSync(localDistDir)) fs.rmSync(localDistDir, { recursive: true, force: true });
console.log('🧹 已清理本地构建资源');
} catch (err) {
console.warn('❌ 清理失败:', err.message);
}
}
async function deploy({ configPath }) {
const config = resolveConfig(configPath);
const { sshConfig, paths } = config;
const zipPath = await zipDist(paths.localDistDir, paths.localZipPath);
await uploadToSFTP(zipPath, paths.remoteZipPath, sshConfig);
await execUnzipOnServer(sshConfig, paths.remoteZipPath, paths.remoteUnzipDir);
cleanLocalBuild(paths.localDistDir, paths.localZipPath);
console.log('🚀 全部流程执行完毕!');
}
module.exports = {
deploy,
zipDist,
uploadToSFTP,
execUnzipOnServer,
cleanLocalBuild,
execCommandOnServer,
backupRemoteDir,
};