vite-auto-deploy
Version:
用于配合 PHP 自动打包并上传打包后的文件到服务器指定目录的插件,此插件需要配合 php [项目地址](https://gitee.com/mxp131011/auto_deploy/)
420 lines (417 loc) • 16.3 kB
JavaScript
import { FormData, File } from 'formdata-node';
import path2 from 'path';
import ora from 'ora';
import chalk from 'chalk';
import os from 'os';
import fs from 'fs';
import crypto from 'crypto';
import machineId from 'node-machine-id';
import JSZip from 'jszip';
import fetch from 'node-fetch';
import https from 'https';
import zlib from 'zlib';
// src/auto-deploy/index.ts
function readFile(readPath) {
if (fs.existsSync(readPath)) {
return fs.readFileSync(readPath, "utf8");
} else {
return null;
}
}
function deleteFile(removePath) {
fs.existsSync(removePath) && fs.unlinkSync(removePath);
}
function writeFile(filePath, data) {
const directory = path2.dirname(filePath);
fs.mkdirSync(directory, { recursive: true });
fs.writeFileSync(filePath, data);
}
function getecretKeyPath(saveSecretKeyDirectory) {
const rootDirectory = saveSecretKeyDirectory || path2.join(os.homedir(), ".vite-auto-deploy");
const publicKeyPath = path2.join(rootDirectory, "ed25519_public.pem");
const privateKeyPath = path2.join(rootDirectory, "ed25519_private.pem");
const keyPairIdPath = path2.join(rootDirectory, ".keyPairId");
return { publicKeyPath, privateKeyPath, keyPairIdPath };
}
function createSecretKey(passphrase, pathInfo) {
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519", {
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem", passphrase, cipher: "aes-256-cbc" }
});
let keyPairId = "";
try {
keyPairId = machineId.machineIdSync();
} catch (error) {
}
keyPairId = keyPairId ? keyPairId : calculateSHA256(crypto.randomUUID());
keyPairId = calculateSHA256(calculateSHA256(publicKey).substring(8, 16) + keyPairId + calculateSHA256(privateKey).substring(8, 16));
writeFile(pathInfo.publicKeyPath, publicKey);
writeFile(pathInfo.privateKeyPath, privateKey);
writeFile(pathInfo.keyPairIdPath, keyPairId);
return { publicKey, privateKey, keyPairId };
}
function getSecretKey(passphrase, pathInfo, resetKey, debug) {
resetKey && deleteFile(pathInfo.publicKeyPath);
resetKey && deleteFile(pathInfo.privateKeyPath);
resetKey && deleteFile(pathInfo.keyPairIdPath);
let keys = {
privateKey: readFile(pathInfo.privateKeyPath) || "",
publicKey: readFile(pathInfo.publicKeyPath) || "",
keyPairId: readFile(pathInfo.keyPairIdPath) || ""
};
if (!keys.privateKey || !keys.publicKey || !keys.keyPairId) {
keys = createSecretKey(passphrase, pathInfo);
debug && console.log("\u65E5\u5FD7: \u751F\u6210\u6216\u91CD\u7F6E\u5BC6\u5319\u6210\u529F");
} else {
debug && console.log("\u65E5\u5FD7: \u83B7\u53D6\u5BC6\u5319\u6210\u529F");
}
return { publicKey: keys.publicKey, privateKey: keys.privateKey, keyPairId: keys.keyPairId };
}
function signEd25519(originalText, privateKey, passphrase) {
const encryptedData = crypto.sign(null, Buffer.from(originalText), { key: privateKey, passphrase });
const signature = encryptedData.toString("base64");
return signature;
}
function calculateSHA256(fileContent) {
const hash = crypto.createHash("sha256");
hash.update(fileContent);
const sha256Hash = hash.digest("hex");
return sha256Hash;
}
async function createZip(outDir, zipName, saveLocalZip) {
try {
const catalog = path2.resolve(outDir);
const storagePath = path2.join(catalog, zipName);
deleteFile(storagePath);
const buf = await zipDir(catalog);
if (saveLocalZip) {
deleteFile(storagePath);
fs.writeFileSync(storagePath, buf);
}
return Promise.resolve(buf);
} catch (error) {
return Promise.reject({ msg: error.message });
}
}
function zipDir(catalog) {
const jszip = new JSZip();
readDir(jszip, catalog);
return jszip.generateAsync({
type: "nodebuffer",
// 压缩类型
compression: "DEFLATE",
// 压缩算法
compressionOptions: {
level: 6
// 压缩级别
}
});
}
function readDir(jszip, catalog) {
const files = fs.readdirSync(catalog);
files.forEach((fileName) => {
const fillPath = path2.join(catalog, "./", fileName);
const fileState = fs.statSync(fillPath);
if (fileState.isDirectory()) {
const dirZip = jszip.folder(fileName);
dirZip && readDir(dirZip, fillPath);
} else {
jszip.file(fileName, fs.readFileSync(fillPath));
}
});
}
function viteAutoDeployPlugin(config) {
let viteConfig = void 0;
return {
name: "vite-auto-deploy",
apply: "build",
// 在什么模式下执行(构建还是开发)
enforce: "post",
// 插件执行顺序
/* 在解析 Vite 配置后的回调函数 */
configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
},
/* 在构建完成后的回调 */
async closeBundle() {
try {
await viteAutoDeply(config, viteConfig);
} catch (error) {
}
}
};
}
async function viteAutoDeply(config, viteConfig) {
try {
const newConfig = {
uploadUrl: "",
projectName: "",
zipName: "viteAutoDeploy.zip",
saveLocalZip: false,
passphrase: "vite-auto-deploy",
autoDeploy: true,
root: void 0,
outDir: void 0,
headers: {},
resetKey: false,
debug: false,
...config || {}
};
const autoDeploy = newConfig.autoDeploy ?? true;
if (autoDeploy) {
const spinner = ora({ spinner: "dots" });
const uploadUrl = newConfig.uploadUrl || false;
const projectName = newConfig.projectName || false;
console.log("");
spinner.start(chalk.blue("\u5F00\u59CB\u81EA\u52A8\u90E8\u7F72..."));
if (!uploadUrl) {
spinner.fail(chalk.red("\u81EA\u52A8\u90E8\u7F72\u5931\u8D25,\u8BF7\u914D\u7F6EuploadUrl"));
} else if (!projectName) {
spinner.fail(chalk.red("\u81EA\u52A8\u90E8\u7F72\u5931\u8D25,\u8BF7\u914D\u7F6EprojectName"));
} else {
newConfig.root = newConfig.root || viteConfig?.root || process.cwd();
let outDir = newConfig.outDir || viteConfig?.build?.outDir || "dist";
outDir = path2.isAbsolute(outDir) ? outDir : path2.join(newConfig.root, outDir);
outDir = path2.resolve(outDir);
newConfig.outDir = outDir;
await autoDeply(newConfig, newConfig.outDir, spinner);
typeof newConfig.success === "function" && newConfig.success(newConfig, viteConfig);
}
console.log("");
}
return Promise.resolve(true);
} catch (error) {
return Promise.reject(false);
}
}
async function autoDeply(config, outDir, spinner) {
const uploadUrl = config.uploadUrl || "";
const projectName = config.projectName || "";
const passphrase = config.passphrase || "vite-auto-deploy";
const keyPathInfo = getecretKeyPath(config.saveSecretKeyDirectory);
const { publicKey, privateKey, keyPairId } = getSecretKey(passphrase, keyPathInfo, config.resetKey || false, config.debug || false);
config.debug && console.log("\u65E5\u5FD7: \u516C\u5319=======" + publicKey);
config.debug && console.log("\u65E5\u5FD7: \u79C1\u5319=======" + privateKey);
config.debug && console.log("\u65E5\u5FD7: \u5BC6\u5319\u5BF9ID==== " + keyPairId);
try {
const zipName = `${config.zipName || "vite-auto-deploy"}.zip`;
const zipBuffer = await createZip(outDir, zipName, config.saveLocalZip);
const fileSHA256 = calculateSHA256(zipBuffer);
const timestamp = (/* @__PURE__ */ new Date()).getTime();
const postData = { projectName, keyPairId, sign: "", timestamp };
const srd = Math.floor(timestamp % 1e4 * 333.3).toString();
const signStr = `${calculateSHA256(srd)}${fileSHA256}${keyPairId}${calculateSHA256(projectName.toString())}`;
postData.sign = signEd25519(signStr, privateKey, passphrase);
await uploading(uploadUrl, zipBuffer, zipName, postData, config.headers || {}, config.rejectUnauthorized || false, config.debug || false);
spinner.succeed(chalk.bold.green(`\u81EA\u52A8\u90E8\u7F72\u5B8C\u6210!`));
return Promise.resolve(true);
} catch (err) {
config.debug && console.log("\u65E5\u5FD7: \u9519\u8BEF\u4FE1\u606F2:", err);
if (err.code === 427) {
spinner.fail(chalk.yellow("\u81EA\u52A8\u90E8\u7F72\u5931\u8D25,\u8BF7\u628A\u4EE5\u4E0B\u516C\u5319\u548C\u5BC6\u5319\u5BF9ID\u6DFB\u52A0\u5230\u670D\u52A1\u7AEF\uFF1A"));
spinner.info("\u516C\u5319\uFF1A");
spinner.warn(chalk.green(publicKey));
spinner.info("\u5BC6\u5319\u5BF9ID\uFF1A");
spinner.warn(`${chalk.green(keyPairId)}`);
console.log("");
} else {
spinner.fail(chalk.bold.red(err.message || err.msg || "\u81EA\u52A8\u90E8\u7F72\u5931\u8D25"));
console.log("");
}
spinner.fail(chalk.yellow("\u6E29\u99A8\u63D0\u793A\uFF1A\u5EFA\u8BAE\u670D\u52A1\u5668\u8FD4\u56DE427\u8868\u793A\u516C\u5319\u4E0D\u53EF\u4FE1(\u5373\u6CA1\u6709\u628A\u516C\u5319\u548C\u5BC6\u5319\u5BF9ID\u6DFB\u52A0\u5230\u670D\u52A1\u5668\u4E0A)"));
return Promise.reject(false);
}
}
async function uploading(urlStr, buffer, zipName, postData, headers, rejectUnauthorized, debug) {
try {
const formData = new FormData();
const file = new File([buffer], zipName, { type: "application/zip" });
formData.append("file", file, zipName);
for (const key in postData) {
formData.append(key, postData[key]);
}
debug && console.log("\u65E5\u5FD7: \u6E90\u4EE3\u7801\u4E0A\u4F20url:", urlStr);
debug && console.log("\u65E5\u5FD7: \u6E90\u4EE3\u7801\u4E0A\u4F20body:", postData);
debug && console.log("\u65E5\u5FD7: \u6E90\u4EE3\u7801\u4E0A\u4F20headers:", headers);
const res = await fetch(urlStr, {
method: "POST",
body: formData,
headers,
agent: rejectUnauthorized ? new https.Agent({ rejectUnauthorized: false }) : void 0
});
const result = await res.json();
debug && console.log("\u65E5\u5FD7: \u8FD4\u56DE\u7ED3\u679C", result);
if (result && typeof result === "object" && !Array.isArray(result) && "code" in result && "msg" in result) {
if (result.code === 200) {
return Promise.resolve("\u90E8\u7F72\u6210\u529F");
} else {
return Promise.reject({
msg: typeof result.msg === "string" ? result.msg : "\u90E8\u7F72\u5931\u8D25",
code: typeof result.code === "number" ? result.code : -102
});
}
} else {
return Promise.reject({ msg: "\u7ED3\u679C\u672A\u77E5\uFF0C\u670D\u52A1\u5668\u8FD4\u56DE\u5185\u5BB9\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684json,-BD003" });
}
} catch (error) {
debug && console.log("\u65E5\u5FD7: \u9519\u8BEF\u4FE1\u606F1: ", error);
return Promise.reject({ msg: "\u4E0A\u4F20\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5url\u662F\u5426\u6B63\u786E\u6216\u670D\u52A1\u5668\u4EE3\u7801\u662F\u5426\u6709\u8BEF,-BD002" });
}
}
function isFunction(arg) {
return typeof arg === "function";
}
function isRegExp(arg) {
return Object.prototype.toString.call(arg) === "[object RegExp]";
}
function readAllFile(root, reg) {
let resultArr = [];
if (fs.existsSync(root)) {
const state = fs.lstatSync(root);
if (state.isDirectory()) {
const files = fs.readdirSync(root);
files.forEach((file) => {
const t = readAllFile(path2.join(root, "/", file));
resultArr = resultArr.concat(t);
});
} else {
{
resultArr.push(root);
}
}
}
return resultArr;
}
function viteCompressionPlugin(config = {}) {
let viteConfig = null;
return {
name: "vite-mxp-compression",
apply: "build",
enforce: "post",
configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
},
closeBundle() {
viteCompression(config, viteConfig);
}
};
}
function viteCompression(config, viteConfig) {
const newConfig = {
verbose: true,
threshold: 1024,
filter: void 0,
disable: false,
algorithm: "gzip",
ext: ".gz",
compressionOptions: void 0,
deleteOriginFile: false,
...config || {}
};
const spinner = ora({ spinner: "dots2" });
try {
const {
disable = false,
filter = /\.(js|mjs|json|css|html)$/i,
verbose = true,
algorithm = "gzip",
threshold = 1025,
compressionOptions = {},
deleteOriginFile = false,
success
} = newConfig;
if (!disable) {
newConfig.root = newConfig.root || viteConfig?.root || process.cwd();
let outDir = newConfig.outDir || viteConfig?.build?.outDir || "dist";
outDir = path2.isAbsolute(outDir) ? outDir : path2.join(newConfig.root, outDir);
outDir = path2.resolve(outDir);
newConfig.outDir = outDir;
const ext = newConfig.ext ? newConfig.ext : algorithm === "gzip" ? ".gz" : ".br";
let files = readAllFile(outDir) || [];
if (!files.length) {
return;
}
console.log("");
spinner.start(chalk.blue("\u6B63\u5728\u538B\u7F29\u6587\u4EF6..."));
files = filterFiles(files, filter);
const compressOptions = getCompressionOptions(algorithm, compressionOptions);
const compressInfoMap = /* @__PURE__ */ new Map();
files.forEach((filePath) => {
const { size: oldSize } = fs.statSync(filePath);
if (oldSize >= threshold) {
let content = fs.readFileSync(filePath);
deleteOriginFile && deleteFile(filePath);
try {
content = Buffer.from(compress(content, algorithm, compressOptions));
} catch (error) {
spinner.fail(`vite-mxp-compression: ${chalk.bold.green(`\u538B\u7F29\u5931\u8D25\uFF1A${filePath}`)}`);
}
const size = content.byteLength;
const fullPath = getOutputFileName(filePath, ext);
compressInfoMap.set(filePath, { size: size / 1024, oldSize: oldSize / 1024, fullPath });
fs.writeFileSync(fullPath, content);
}
});
spinner.succeed(chalk.bold.green(`\u538B\u7F29\u5B8C\u6210, \u538B\u7F29\u7B97\u6CD5\uFF1A${algorithm}${verbose ? ", \u5DF2\u538B\u7F29\u6587\u4EF6\uFF1A" : ""}`));
verbose && handleOutputLogger(compressInfoMap, algorithm, outDir);
typeof success === "function" && success(newConfig, viteConfig);
}
} catch (error) {
console.log(`vite-mxp-compression: ${chalk.bold.red(error.message)}`);
}
}
function filterFiles(files, filter) {
const isRe = isRegExp(filter);
const isFn = isFunction(filter);
if (isRe) {
return files.filter((file) => filter.test(file));
} else if (isFn) {
return files.filter((file) => filter(file));
}
return files;
}
function getCompressionOptions(algorithm, compressionOptions = {}) {
const defaultOptions = {
gzip: { level: zlib.constants.Z_BEST_COMPRESSION },
deflate: { level: zlib.constants.Z_BEST_COMPRESSION },
deflateRaw: { level: zlib.constants.Z_BEST_COMPRESSION },
brotliCompress: {
params: { [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY, [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT }
}
};
return {
...defaultOptions[algorithm],
...compressionOptions
};
}
function compress(content, algorithm, options = {}) {
switch (algorithm) {
case "brotliCompress":
return zlib.brotliCompressSync(content, options);
case "deflate":
return zlib.deflateSync(content, options);
case "deflateRaw":
return zlib.deflateRawSync(content, options);
default:
return zlib.gzipSync(content, options);
}
}
function getOutputFileName(filepath, ext) {
const compressExt = ext.startsWith(".") ? ext : `.${ext}`;
return `${filepath}${compressExt}`;
}
function handleOutputLogger(compressInfoMap, algorithm, outDir) {
const keyLengths = Array.from(compressInfoMap.keys(), (name) => name.length);
const maxKeyLength = Math.max(...keyLengths);
console.log("");
compressInfoMap.forEach((value, name) => {
const { size, oldSize, fullPath } = value;
const rName = fullPath.replace(outDir, "");
const sizeStr = `\u539F\u59CB\u5927\u5C0F\uFF1A${oldSize.toFixed(2)}kb / ${algorithm}\u538B\u7F29\u540E: ${size.toFixed(2)}kb`;
const spacings = " ".repeat(2 + maxKeyLength - name.length);
const basename = path2.basename(outDir);
console.log(`${chalk.dim(basename)}${chalk.blueBright(rName)}${spacings} ${chalk.dim(sizeStr)}`);
});
console.log(" ");
}
export { autoDeply, viteAutoDeployPlugin, viteAutoDeply, viteCompressionPlugin };