Files
cstb-next/scripts/generate-latest-json.js

319 lines
10 KiB
JavaScript
Raw Permalink Normal View History

2025-11-08 23:57:26 +08:00
#!/usr/bin/env node
/**
* 生成 Tauri 更新器的 latest.json 文件
*
* 使用方法:
* node scripts/generate-latest-json.js [options]
*
* 选项:
* --base-url <url> 更新文件的基 URL必需
* --version <version> 版本号可选默认从 tauri.conf.json 读取
* --notes <notes> 更新说明可选
* --target <target> 构建类型可选默认 release可选值debugreleasefast-release
* --rename 在生成 latest.json 之前将文件名中的 "CS工具箱" 改为 "CS_Toolbox"
2025-11-08 23:57:26 +08:00
*/
const fs = require('fs');
const path = require('path');
// 解析命令行参数
const args = process.argv.slice(2);
const getArg = (name, defaultValue = null) => {
const index = args.indexOf(name);
if (index !== -1 && args[index + 1]) {
return args[index + 1];
}
return defaultValue;
};
// 读取配置
const tauriConfigPath = path.join(__dirname, '../src-tauri/tauri.conf.json');
const tauriConfig = JSON.parse(fs.readFileSync(tauriConfigPath, 'utf-8'));
const version = getArg('--version', tauriConfig.version);
const baseUrl = getArg('--base-url');
const notes = getArg('--notes', '');
const target = getArg('--target', 'release');
const shouldRename = args.includes('--rename');
2025-11-08 23:57:26 +08:00
// 验证 target 参数
const validTargets = ['debug', 'release', 'fast-release'];
if (!validTargets.includes(target)) {
console.error(`错误: --target 必须是以下值之一: ${validTargets.join(', ')}`);
process.exit(1);
}
if (!baseUrl) {
console.error('错误: 必须提供 --base-url 参数');
console.error('示例: node scripts/generate-latest-json.js --base-url https://your-server.com/releases');
process.exit(1);
}
// 如果需要重命名,先执行重命名脚本
if (shouldRename) {
console.log('\n执行文件重命名...');
const { execSync } = require('child_process');
try {
execSync(`node scripts/rename-build-artifacts.js --target ${target}`, {
stdio: 'inherit',
cwd: path.join(__dirname, '..')
});
console.log('✓ 文件重命名完成\n');
} catch (err) {
console.error('✗ 文件重命名失败:', err.message);
process.exit(1);
}
}
2025-11-08 23:57:26 +08:00
// 确保 baseUrl 不以 / 结尾
const cleanBaseUrl = baseUrl.replace(/\/$/, '');
// 检测是否是 GitHub releases URL
const githubReleasesMatch = cleanBaseUrl.match(/^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/releases$/i);
let githubOwner = null;
let githubRepo = null;
if (githubReleasesMatch) {
githubOwner = githubReleasesMatch[1];
githubRepo = githubReleasesMatch[2];
console.log(`检测到 GitHub releases: ${githubOwner}/${githubRepo}`);
}
// 生成文件 URL
function generateFileUrl(filename) {
// 如果使用了 --rename文件名应该已经是 CS_Toolbox 了
// 但为了兼容,我们检查一下是否需要替换
let finalFilename = filename;
if (shouldRename && filename.includes('CS工具箱')) {
finalFilename = filename.replace(/CS工具箱/g, 'CS_Toolbox');
}
2025-11-08 23:57:26 +08:00
if (githubOwner && githubRepo) {
// GitHub releases 下载 URL 格式: https://github.com/{owner}/{repo}/releases/download/{tag}/{filename}
const tag = version.startsWith('v') ? version : `v${version}`;
return `https://github.com/${githubOwner}/${githubRepo}/releases/download/${tag}/${finalFilename}`;
2025-11-08 23:57:26 +08:00
}
// 普通 URL
return `${cleanBaseUrl}/${finalFilename}`;
2025-11-08 23:57:26 +08:00
}
// 根据 target 确定构建产物目录
const bundleDir = path.join(__dirname, '../src-tauri/target', target, 'bundle');
console.log(`使用构建类型: ${target}`);
console.log(`构建产物目录: ${bundleDir}`);
const platforms = {
'windows-x86_64': {
dir: path.join(bundleDir, 'nsis'),
extensions: ['.exe'],
pattern: /windows|win/i
},
'darwin-x86_64': {
dir: path.join(bundleDir, 'macos'),
extensions: ['.dmg', '.app.tar.gz'],
pattern: /darwin|macos|mac/i
},
'darwin-aarch64': {
dir: path.join(bundleDir, 'macos'),
extensions: ['.dmg', '.app.tar.gz'],
pattern: /darwin|macos|mac|aarch64|arm64/i
},
'linux-x86_64': {
dir: path.join(bundleDir, 'appimage'),
extensions: ['.AppImage', '.deb', '.rpm'],
pattern: /linux/i
}
};
// 递归查找文件
function findFilesRecursive(dir, version, extensions, maxDepth = 2, currentDepth = 0) {
if (!fs.existsSync(dir) || currentDepth > maxDepth) {
return null;
}
try {
const items = fs.readdirSync(dir);
const versionPattern = version.replace(/\./g, '\\.').replace(/-/g, '[-.]');
const regex = new RegExp(versionPattern, 'i');
// 先尝试精确匹配版本
for (const item of items) {
const itemPath = path.join(dir, item);
const stat = fs.statSync(itemPath);
if (stat.isFile()) {
if (!regex.test(item)) continue;
const ext = path.extname(item);
if (extensions.includes(ext)) {
const sigPath = itemPath + '.sig';
return {
file: item,
url: generateFileUrl(item),
signature: fs.existsSync(sigPath) ? fs.readFileSync(sigPath, 'utf-8').trim() : null
};
}
} else if (stat.isDirectory() && currentDepth < maxDepth) {
const result = findFilesRecursive(itemPath, version, extensions, maxDepth, currentDepth + 1);
if (result) {
return result;
}
}
}
// 如果精确匹配失败,尝试查找最新版本的文件(仅在同一目录下)
if (currentDepth === 0) {
const matchingFiles = [];
for (const item of items) {
const itemPath = path.join(dir, item);
const stat = fs.statSync(itemPath);
if (stat.isFile()) {
const ext = path.extname(item);
if (extensions.includes(ext)) {
matchingFiles.push({
file: item,
path: itemPath,
mtime: stat.mtime
});
}
}
}
if (matchingFiles.length > 0) {
// 按修改时间排序,选择最新的文件
matchingFiles.sort((a, b) => b.mtime - a.mtime);
const latest = matchingFiles[0];
const sigPath = latest.path + '.sig';
return {
file: latest.file,
url: generateFileUrl(latest.file),
signature: fs.existsSync(sigPath) ? fs.readFileSync(sigPath, 'utf-8').trim() : null
};
}
}
} catch (err) {
// 忽略错误,继续查找
}
return null;
}
// 查找文件(兼容旧接口)
function findFiles(dir, version, extensions) {
return findFilesRecursive(dir, version, extensions);
}
// 生成 platforms 对象
const platformsData = {};
for (const [platform, config] of Object.entries(platforms)) {
console.log(`\n查找平台 ${platform}:`);
console.log(` 目录: ${config.dir}`);
console.log(` 扩展名: ${config.extensions.join(', ')}`);
// 检查目录是否存在
if (!fs.existsSync(config.dir)) {
console.warn(` 警告: 目录不存在: ${config.dir}`);
continue;
}
const result = findFiles(config.dir, version, config.extensions);
if (result) {
// 如果使用了 --rename文件名应该已经是 CS_Toolbox 了
// 但如果仍然包含 CS工具箱说明重命名可能失败了尝试手动处理
let fileName = result.file;
if (shouldRename && fileName.includes('CS工具箱')) {
console.warn(` 警告: 文件 ${fileName} 仍包含中文,尝试查找重命名后的文件...`);
const renamedFile = fileName.replace(/CS工具箱/g, 'CS_Toolbox');
const renamedPath = path.join(config.dir, renamedFile);
if (fs.existsSync(renamedPath)) {
fileName = renamedFile;
result.file = renamedFile;
result.url = generateFileUrl(renamedFile);
// 检查重命名后的签名文件
const renamedSigPath = renamedPath + '.sig';
if (fs.existsSync(renamedSigPath)) {
result.signature = fs.readFileSync(renamedSigPath, 'utf-8').trim();
}
}
}
console.log(` ✓ 找到文件: ${fileName}`);
2025-11-08 23:57:26 +08:00
platformsData[platform] = {
url: result.url,
signature: result.signature
};
} else {
console.warn(` ✗ 未找到匹配版本 ${version} 的文件`);
// 列出目录中的所有文件以便调试
try {
const files = fs.readdirSync(config.dir);
const exeFiles = files.filter(f => {
const ext = path.extname(f);
return config.extensions.includes(ext);
});
if (exeFiles.length > 0) {
console.log(` 目录中的文件: ${exeFiles.join(', ')}`);
}
} catch (err) {
// 忽略错误
}
}
}
// 如果没有找到任何平台文件,尝试查找所有文件
if (Object.keys(platformsData).length === 0) {
console.warn('\n警告: 未找到任何构建产物,将生成空的 platforms 对象');
console.warn('请确保已经构建了应用: npm run build');
console.warn(`构建产物应该在: ${bundleDir}`);
}
// 生成 latest.json
const latestJson = {
version: version,
notes: notes || `版本 ${version} 更新`,
pub_date: new Date().toISOString(),
platforms: Object.keys(platformsData).length > 0 ? platformsData : undefined
};
// 如果只有一个平台,也可以使用简化的格式
if (Object.keys(platformsData).length === 1) {
const platform = Object.keys(platformsData)[0];
latestJson.download_url = platformsData[platform].url;
latestJson.signature = platformsData[platform].signature;
}
// 输出文件到对应的 bundle 目录
const outputDir = bundleDir;
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// 确保 nsis 子目录存在
const nsisOutputDir = path.join(outputDir, "nsis");
if (!fs.existsSync(nsisOutputDir)) {
fs.mkdirSync(nsisOutputDir, { recursive: true });
}
const outputPath = path.join(nsisOutputDir, 'latest.json');
fs.writeFileSync(outputPath, JSON.stringify(latestJson, null, 2), 'utf-8');
console.log('✓ 成功生成 latest.json');
console.log(` 构建类型: ${target}`);
console.log(` 文件位置: ${outputPath}`);
console.log(` 版本: ${version}`);
console.log(` 平台数量: ${Object.keys(platformsData).length}`);
if (Object.keys(platformsData).length > 0) {
console.log(` 平台: ${Object.keys(platformsData).join(', ')}`);
// 显示签名信息
for (const [platform, data] of Object.entries(platformsData)) {
if (data.signature) {
console.log(` ${platform}: 已找到签名文件`);
} else {
console.log(` ${platform}: 未找到签名文件`);
}
}
}
console.log('\n文件内容:');
console.log(JSON.stringify(latestJson, null, 2));