feat: implement caching for version and file hash to optimize prebuild process
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import AdmZip from "adm-zip";
|
import AdmZip from "adm-zip";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
|
import { createHash } from "crypto";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import fsp from "fs/promises";
|
import fsp from "fs/promises";
|
||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
@@ -10,9 +11,21 @@ import { extract } from "tar";
|
|||||||
import zlib from "zlib";
|
import zlib from "zlib";
|
||||||
import { log_debug, log_error, log_info, log_success } from "./utils.mjs";
|
import { log_debug, log_error, log_info, log_success } from "./utils.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prebuild script with optimization features:
|
||||||
|
* 1. Skip downloading mihomo core if it already exists (unless --force is used)
|
||||||
|
* 2. Cache version information for 1 hour to avoid repeated version checks
|
||||||
|
* 3. Use file hash to detect changes and skip unnecessary chmod/copy operations
|
||||||
|
* 4. Use --force or -f flag to force re-download and update all resources
|
||||||
|
*
|
||||||
|
* This optimization significantly reduces build time for local development
|
||||||
|
*/
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const TEMP_DIR = path.join(cwd, "node_modules/.verge");
|
const TEMP_DIR = path.join(cwd, "node_modules/.verge");
|
||||||
const FORCE = process.argv.includes("--force") || process.argv.includes("-f");
|
const FORCE = process.argv.includes("--force") || process.argv.includes("-f");
|
||||||
|
const VERSION_CACHE_FILE = path.join(TEMP_DIR, ".version_cache.json");
|
||||||
|
const HASH_CACHE_FILE = path.join(TEMP_DIR, ".hash_cache.json");
|
||||||
|
|
||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
"x86_64-pc-windows-msvc": "win32",
|
"x86_64-pc-windows-msvc": "win32",
|
||||||
@@ -55,6 +68,113 @@ const SIDECAR_HOST = target
|
|||||||
.toString()
|
.toString()
|
||||||
.match(/(?<=host: ).+(?=\s*)/g)[0];
|
.match(/(?<=host: ).+(?=\s*)/g)[0];
|
||||||
|
|
||||||
|
/* ======= Version Cache Functions ======= */
|
||||||
|
async function loadVersionCache() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(VERSION_CACHE_FILE)) {
|
||||||
|
const data = await fsp.readFile(VERSION_CACHE_FILE, "utf-8");
|
||||||
|
return JSON.parse(data);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log_debug("Failed to load version cache:", err.message);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveVersionCache(cache) {
|
||||||
|
try {
|
||||||
|
await fsp.mkdir(TEMP_DIR, { recursive: true });
|
||||||
|
await fsp.writeFile(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
|
||||||
|
log_debug("Version cache saved");
|
||||||
|
} catch (err) {
|
||||||
|
log_debug("Failed to save version cache:", err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCachedVersion(key) {
|
||||||
|
const cache = await loadVersionCache();
|
||||||
|
const cached = cache[key];
|
||||||
|
if (cached && Date.now() - cached.timestamp < 3600000) {
|
||||||
|
// 1小时内有效
|
||||||
|
log_info(`Using cached version for ${key}: ${cached.version}`);
|
||||||
|
return cached.version;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setCachedVersion(key, version) {
|
||||||
|
const cache = await loadVersionCache();
|
||||||
|
cache[key] = {
|
||||||
|
version,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
await saveVersionCache(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======= File Hash Functions ======= */
|
||||||
|
async function calculateFileHash(filePath) {
|
||||||
|
try {
|
||||||
|
const fileBuffer = await fsp.readFile(filePath);
|
||||||
|
const hashSum = createHash("sha256");
|
||||||
|
hashSum.update(fileBuffer);
|
||||||
|
return hashSum.digest("hex");
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadHashCache() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(HASH_CACHE_FILE)) {
|
||||||
|
const data = await fsp.readFile(HASH_CACHE_FILE, "utf-8");
|
||||||
|
return JSON.parse(data);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log_debug("Failed to load hash cache:", err.message);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveHashCache(cache) {
|
||||||
|
try {
|
||||||
|
await fsp.mkdir(TEMP_DIR, { recursive: true });
|
||||||
|
await fsp.writeFile(HASH_CACHE_FILE, JSON.stringify(cache, null, 2));
|
||||||
|
log_debug("Hash cache saved");
|
||||||
|
} catch (err) {
|
||||||
|
log_debug("Failed to save hash cache:", err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hasFileChanged(filePath, targetPath) {
|
||||||
|
if (FORCE) return true;
|
||||||
|
if (!fs.existsSync(targetPath)) return true;
|
||||||
|
|
||||||
|
const hashCache = await loadHashCache();
|
||||||
|
const sourceHash = await calculateFileHash(filePath);
|
||||||
|
const targetHash = await calculateFileHash(targetPath);
|
||||||
|
|
||||||
|
if (!sourceHash || !targetHash) return true;
|
||||||
|
|
||||||
|
const cacheKey = targetPath;
|
||||||
|
const cachedHash = hashCache[cacheKey];
|
||||||
|
|
||||||
|
if (cachedHash === sourceHash && sourceHash === targetHash) {
|
||||||
|
// 文件未变化,不输出日志
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateHashCache(targetPath) {
|
||||||
|
const hashCache = await loadHashCache();
|
||||||
|
const hash = await calculateFileHash(targetPath);
|
||||||
|
if (hash) {
|
||||||
|
hashCache[targetPath] = hash;
|
||||||
|
await saveHashCache(hashCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ======= clash meta alpha======= */
|
/* ======= clash meta alpha======= */
|
||||||
const META_ALPHA_VERSION_URL =
|
const META_ALPHA_VERSION_URL =
|
||||||
"https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt";
|
"https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt";
|
||||||
@@ -77,6 +197,15 @@ const META_ALPHA_MAP = {
|
|||||||
|
|
||||||
// Fetch the latest alpha release version from the version.txt file
|
// Fetch the latest alpha release version from the version.txt file
|
||||||
async function getLatestAlphaVersion() {
|
async function getLatestAlphaVersion() {
|
||||||
|
// 如果不强制更新,先尝试从缓存获取
|
||||||
|
if (!FORCE) {
|
||||||
|
const cached = await getCachedVersion("META_ALPHA_VERSION");
|
||||||
|
if (cached) {
|
||||||
|
META_ALPHA_VERSION = cached;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options = {};
|
const options = {};
|
||||||
|
|
||||||
const httpProxy =
|
const httpProxy =
|
||||||
@@ -96,6 +225,9 @@ async function getLatestAlphaVersion() {
|
|||||||
let v = await response.text();
|
let v = await response.text();
|
||||||
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
|
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
|
||||||
log_info(`Latest alpha version: ${META_ALPHA_VERSION}`);
|
log_info(`Latest alpha version: ${META_ALPHA_VERSION}`);
|
||||||
|
|
||||||
|
// 保存到缓存
|
||||||
|
await setCachedVersion("META_ALPHA_VERSION", META_ALPHA_VERSION);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log_error("Error fetching latest alpha version:", error.message);
|
log_error("Error fetching latest alpha version:", error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -124,6 +256,15 @@ const META_MAP = {
|
|||||||
|
|
||||||
// Fetch the latest release version from the version.txt file
|
// Fetch the latest release version from the version.txt file
|
||||||
async function getLatestReleaseVersion() {
|
async function getLatestReleaseVersion() {
|
||||||
|
// 如果不强制更新,先尝试从缓存获取
|
||||||
|
if (!FORCE) {
|
||||||
|
const cached = await getCachedVersion("META_VERSION");
|
||||||
|
if (cached) {
|
||||||
|
META_VERSION = cached;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options = {};
|
const options = {};
|
||||||
|
|
||||||
const httpProxy =
|
const httpProxy =
|
||||||
@@ -143,6 +284,9 @@ async function getLatestReleaseVersion() {
|
|||||||
let v = await response.text();
|
let v = await response.text();
|
||||||
META_VERSION = v.trim(); // Trim to remove extra whitespaces
|
META_VERSION = v.trim(); // Trim to remove extra whitespaces
|
||||||
log_info(`Latest release version: ${META_VERSION}`);
|
log_info(`Latest release version: ${META_VERSION}`);
|
||||||
|
|
||||||
|
// 保存到缓存
|
||||||
|
await setCachedVersion("META_VERSION", META_VERSION);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log_error("Error fetching latest release version:", error.message);
|
log_error("Error fetching latest release version:", error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -210,7 +354,12 @@ async function resolveSidecar(binInfo) {
|
|||||||
const sidecarPath = path.join(sidecarDir, targetFile);
|
const sidecarPath = path.join(sidecarDir, targetFile);
|
||||||
|
|
||||||
await fsp.mkdir(sidecarDir, { recursive: true });
|
await fsp.mkdir(sidecarDir, { recursive: true });
|
||||||
if (!FORCE && fs.existsSync(sidecarPath)) return;
|
|
||||||
|
// 检查文件是否已存在,如果存在则跳过重复下载
|
||||||
|
if (!FORCE && fs.existsSync(sidecarPath)) {
|
||||||
|
log_success(`"${name}" already exists, skipping download to save time`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const tempDir = path.join(TEMP_DIR, name);
|
const tempDir = path.join(TEMP_DIR, name);
|
||||||
const tempZip = path.join(tempDir, zipFile);
|
const tempZip = path.join(tempDir, zipFile);
|
||||||
@@ -300,22 +449,32 @@ async function resolveResource(binInfo) {
|
|||||||
const resDir = path.join(cwd, "src-tauri/resources");
|
const resDir = path.join(cwd, "src-tauri/resources");
|
||||||
const targetPath = path.join(resDir, file);
|
const targetPath = path.join(resDir, file);
|
||||||
|
|
||||||
if (!FORCE && fs.existsSync(targetPath)) return;
|
if (!FORCE && fs.existsSync(targetPath) && !downloadURL && !localPath) {
|
||||||
|
log_success(`"${file}" already exists, skipping`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (downloadURL) {
|
if (downloadURL) {
|
||||||
|
if (!FORCE && fs.existsSync(targetPath)) {
|
||||||
|
log_success(`"${file}" already exists, skipping download`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await fsp.mkdir(resDir, { recursive: true });
|
await fsp.mkdir(resDir, { recursive: true });
|
||||||
await downloadFile(downloadURL, targetPath);
|
await downloadFile(downloadURL, targetPath);
|
||||||
|
await updateHashCache(targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localPath) {
|
if (localPath) {
|
||||||
await fs.copyFile(localPath, targetPath, (err) => {
|
// 检查文件哈希是否变化
|
||||||
if (err) {
|
if (!(await hasFileChanged(localPath, targetPath))) {
|
||||||
console.error("Error copying file:", err);
|
// 文件未变化,静默跳过
|
||||||
} else {
|
return;
|
||||||
console.log("File was copied successfully");
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
log_debug(`copy file finished: "${localPath}"`);
|
await fsp.mkdir(resDir, { recursive: true });
|
||||||
|
await fsp.copyFile(localPath, targetPath);
|
||||||
|
await updateHashCache(targetPath);
|
||||||
|
log_success(`Copied file: ${file}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_success(`${file} finished`);
|
log_success(`${file} finished`);
|
||||||
@@ -387,16 +546,36 @@ const resolveServicePermission = async () => {
|
|||||||
"clash-verge-service-uninstall*",
|
"clash-verge-service-uninstall*",
|
||||||
];
|
];
|
||||||
const resDir = path.join(cwd, "src-tauri/resources");
|
const resDir = path.join(cwd, "src-tauri/resources");
|
||||||
|
const hashCache = await loadHashCache();
|
||||||
|
let hasChanges = false;
|
||||||
|
|
||||||
for (let f of serviceExecutables) {
|
for (let f of serviceExecutables) {
|
||||||
// 使用glob模块来处理通配符
|
// 使用glob模块来处理通配符
|
||||||
const files = glob.sync(path.join(resDir, f));
|
const files = glob.sync(path.join(resDir, f));
|
||||||
for (let filePath of files) {
|
for (let filePath of files) {
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
|
const currentHash = await calculateFileHash(filePath);
|
||||||
|
const cacheKey = `${filePath}_chmod`;
|
||||||
|
|
||||||
|
// 检查文件哈希是否变化
|
||||||
|
if (!FORCE && hashCache[cacheKey] === currentHash) {
|
||||||
|
// 权限未变化,静默跳过
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
execSync(`chmod 755 ${filePath}`);
|
execSync(`chmod 755 ${filePath}`);
|
||||||
log_success(`chmod finished: "${filePath}"`);
|
log_success(`chmod finished: "${filePath}"`);
|
||||||
|
|
||||||
|
// 更新哈希缓存
|
||||||
|
hashCache[cacheKey] = currentHash;
|
||||||
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
await saveHashCache(hashCache);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 在 resolveResource 函数后添加新函数
|
// 在 resolveResource 函数后添加新函数
|
||||||
@@ -411,16 +590,23 @@ async function resolveLocales() {
|
|||||||
// 读取所有语言文件
|
// 读取所有语言文件
|
||||||
const files = await fsp.readdir(srcLocalesDir);
|
const files = await fsp.readdir(srcLocalesDir);
|
||||||
|
|
||||||
// 复制每个文件
|
// 复制每个文件,只有当哈希变化时才复制
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const srcPath = path.join(srcLocalesDir, file);
|
const srcPath = path.join(srcLocalesDir, file);
|
||||||
const targetPath = path.join(targetLocalesDir, file);
|
const targetPath = path.join(targetLocalesDir, file);
|
||||||
|
|
||||||
|
// 检查文件是否需要更新
|
||||||
|
if (!(await hasFileChanged(srcPath, targetPath))) {
|
||||||
|
// 文件未变化,静默跳过
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
await fsp.copyFile(srcPath, targetPath);
|
await fsp.copyFile(srcPath, targetPath);
|
||||||
|
await updateHashCache(targetPath);
|
||||||
log_success(`Copied locale file: ${file}`);
|
log_success(`Copied locale file: ${file}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_success("All locale files copied successfully");
|
log_success("All locale files processed successfully");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log_error("Error copying locale files:", err.message);
|
log_error("Error copying locale files:", err.message);
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
Reference in New Issue
Block a user