UNPKG

hexo-deployer-upyun-pro

Version:

Hexo deployer plugin for UPYUN, with auto remote cleaning feature.

150 lines (145 loc) 5.24 kB
const glob = require('glob'); const path = require('path'); const fs = require('fs'); const md5 = require('md5'); const ora = require('ora'); // 执行动画 const upyun = require('upyun'); class filesUpload { constructor(config, ctx) { const { serviceName, operatorName, operatorPassword, path, autoclean } = config; if (!(serviceName && operatorName && operatorPassword)) { throw(new Error('serviceName、operatorName、operatorPassword为必填项')); } this.storageInfo = { serviceName, operatorName, operatorPassword, path: path ? path.substring(0, 1) === '/' ? path : `/${path}` : '/', autoclean: typeof autoclean === 'boolean' ? autoclean : false // 默认关闭自动清理 }; this.ctx = ctx; this.errorFile = []; // 上传失败的文件 this.init(); } init() { const { serviceName, operatorName, operatorPassword } = this.storageInfo; const service = new upyun.Service(serviceName, operatorName, operatorPassword); this.client = new upyun.Client(service); } async startUpload() { const { config: { public_dir } } = this.ctx; const filePath = path.join(public_dir, '/**/*'); const allFiles = glob.sync(filePath, { nodir: true // 只匹配文件,不匹配文件夹。防止文件夹名称中有dot符号被匹配出来 }); // 如果开启自动清理,先删除云端多余的文件 if (this.storageInfo.autoclean) { await this.cleanRemoteFiles(allFiles); } await this.upload(allFiles); } readFilePromise(filepath) { return new Promise((resolve, reject) => { fs.readFile(filepath, (err, data) => { if (err) reject(err); resolve(data); }); }); } async upload(fileList) { for (let index = 0; index < fileList.length; index++) { const localFilePath = fileList[index]; const remotePath = (this.storageInfo.path.endsWith('/') ? this.storageInfo.path.slice(0, -1) : this.storageInfo.path) + '/' + fileList[index].split('/').slice(1).join('/'); // 文件远程保存路径 const spinner = ora().start(); spinner.text = `开始上传文件:${localFilePath}`; try { const localFile = await this.readFilePromise(localFilePath); // 本地文件md5值 const localFileMd5 = md5(localFile); const remoteFileMd5 = (await this.client.headFile(remotePath))['Content-Md5']; // 获取云存储上文件的md5值 if (localFileMd5 === remoteFileMd5) { spinner.succeed(localFilePath + ' 本地文件与云端文件md5值相同,不上传'); } else { // 上传 try { const result = await this.client.putFile(remotePath, localFile); if (result) { spinner.succeed(`${localFilePath}上传成功`); } else { throw new Error(`${localFilePath}上传失败`); } } catch (e) { spinner.fail(e); this.errorFile.push({ filePath: localFilePath, error: e }); } } } catch (e) { this.ctx.log.error(e, '本地文件读取失败'); } } this.errorFile.forEach(ele => { this.ctx.log.info('文件:', ele.filePath, ' 上传失败,原因:', ele.error); }); } async getRemoteFiles() { const remoteFiles = []; const listFiles = async (prefix = '') => { try { const { files } = await this.client.listDir(prefix); for (const file of files) { if (file.type === 'N') { // 假设 'N' 代表文件 const filePath = prefix + file.name; remoteFiles.push(filePath); } else if (file.type === 'F') { // 假设 'F' 代表文件夹 await listFiles(prefix + file.name + '/'); } } } catch (e) { console.error('获取文件列表时出错:', e); } }; await listFiles(this.storageInfo.path); return remoteFiles; } getFilesToDelete(localFiles, remoteFiles) { const localFilePaths = localFiles.map(file => { return (this.storageInfo.path.endsWith('/') ? this.storageInfo.path.slice(0, -1) : this.storageInfo.path) + '/' + file.split('/').slice(1).join('/'); }); return remoteFiles.filter(remoteFile => !localFilePaths.includes(remoteFile)); } async cleanRemoteFiles(localFiles) { const remoteFiles = await this.getRemoteFiles(); const filesToDelete = this.getFilesToDelete(localFiles, remoteFiles); for (const file of filesToDelete) { const spinner = ora().start(); spinner.text = `开始删除文件:${file}`; try { await this.client.deleteFile(file); spinner.succeed(`${file} 删除成功`); } catch (e) { spinner.fail(`删除 ${file} 失败: ${e}`); this.errorFile.push({ filePath: file, error: e }); } } } } module.exports = async function (args, cb) { try { // 连接upyun const upyunInstance = new filesUpload(args, this); // ftp上传实例 await upyunInstance.startUpload(); return cb(); } catch (error) { this.log.error('发生错误:', error); return; } }