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

319 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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可选值debug、release、fast-release
* --rename 在生成 latest.json 之前,将文件名中的 "CS工具箱" 改为 "CS_Toolbox"
*/
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');
// 验证 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);
}
}
// 确保 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');
}
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}`;
}
// 普通 URL
return `${cleanBaseUrl}/${finalFilename}`;
}
// 根据 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}`);
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));