247 lines
9.7 KiB
TypeScript
247 lines
9.7 KiB
TypeScript
|
|
#!/usr/bin/env node
|
|||
|
|
|
|||
|
|
import { execSync, spawn } from 'child_process';
|
|||
|
|
import { platform } from 'os';
|
|||
|
|
import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|||
|
|
import { join } from 'path';
|
|||
|
|
import {
|
|||
|
|
APPLE_CERTIFICATE,
|
|||
|
|
APPLE_CERTIFICATE_PASSWORD,
|
|||
|
|
APPLE_ID,
|
|||
|
|
APPLE_PASSWORD,
|
|||
|
|
APPLE_SIGNING_IDENTITY,
|
|||
|
|
APPLE_TEAM_ID,
|
|||
|
|
TAURI_SIGNING_PRIVATE_KEY,
|
|||
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD,
|
|||
|
|
} from './utils/constant.ts';
|
|||
|
|
|
|||
|
|
// ==================== 解析命令行参数 ====================
|
|||
|
|
// 从命令行参数中获取 --features 的值
|
|||
|
|
const args: string[] = process.argv.slice(2);
|
|||
|
|
const featuresIndex: number = args.findIndex((arg: string) => arg === '--features');
|
|||
|
|
const buildFeature: string = featuresIndex !== -1 && args[featuresIndex + 1] ? (args[featuresIndex + 1] as string) : 'production';
|
|||
|
|
|
|||
|
|
// 根据 feature 设置对应的环境模式
|
|||
|
|
const modeMap: Record<string, string> = {
|
|||
|
|
production: 'production',
|
|||
|
|
development: 'development',
|
|||
|
|
prod_150_8080: 'prod_150_8080',
|
|||
|
|
};
|
|||
|
|
const buildMode: string = (buildFeature in modeMap ? modeMap[buildFeature as keyof typeof modeMap] : modeMap.production) || 'production';
|
|||
|
|
|
|||
|
|
console.log('\x1b[36m%s\x1b[0m', `构建配置: --features ${buildFeature}, --mode ${buildMode}`);
|
|||
|
|
|
|||
|
|
// ==================== updater签名环境变量配置 ====================
|
|||
|
|
// 私匙
|
|||
|
|
process.env.TAURI_SIGNING_PRIVATE_KEY = TAURI_SIGNING_PRIVATE_KEY;
|
|||
|
|
// 私匙对应的密码,没有就不配置
|
|||
|
|
process.env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD = TAURI_SIGNING_PRIVATE_KEY_PASSWORD;
|
|||
|
|
|
|||
|
|
// Mac 公证
|
|||
|
|
if (platform() === 'darwin') {
|
|||
|
|
/** 签名证书在钥匙串中的名称(签名标识) */
|
|||
|
|
process.env.APPLE_SIGNING_IDENTITY = APPLE_SIGNING_IDENTITY;
|
|||
|
|
/** 从钥匙串导出的 .p12 证书的 base64 字符串(适用于 CI 或没有本地证书时) */
|
|||
|
|
process.env.APPLE_CERTIFICATE = APPLE_CERTIFICATE;
|
|||
|
|
/** .p12 证书的密码。 */
|
|||
|
|
process.env.APPLE_CERTIFICATE_PASSWORD = APPLE_CERTIFICATE_PASSWORD;
|
|||
|
|
/** 你的 Apple 账号邮箱(用于公证) */
|
|||
|
|
process.env.APPLE_ID = APPLE_ID;
|
|||
|
|
/** Apple 账号的 App 专用密码(用于公证) */
|
|||
|
|
process.env.APPLE_PASSWORD = APPLE_PASSWORD;
|
|||
|
|
/** 你的 Apple 开发者团队 ID(用于公证) */
|
|||
|
|
process.env.APPLE_TEAM_ID = APPLE_TEAM_ID;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 运行版本更新脚本,同步 tauri.conf.json 和 package.json 的版本号
|
|||
|
|
console.log('\x1b[36m%s\x1b[0m', '正在同步版本号...');
|
|||
|
|
try {
|
|||
|
|
const updateVersionScriptPath = join('.', 'vite', 'scripts', 'update-version.ts');
|
|||
|
|
execSync(`node "${updateVersionScriptPath}"`, { stdio: 'inherit' });
|
|||
|
|
console.log('\x1b[32m%s\x1b[0m', '版本号同步完成');
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error('\x1b[31m%s\x1b[0m', '版本号同步失败:', error.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 读取更新后的 package.json 获取版本号
|
|||
|
|
interface PackageJson {
|
|||
|
|
version: string;
|
|||
|
|
[key: string]: any;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const packageJsonPath = join('package.json');
|
|||
|
|
let packageJson: PackageJson = {} as PackageJson;
|
|||
|
|
try {
|
|||
|
|
packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error('\x1b[31m%s\x1b[0m', '读取 package.json 失败:', error.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前版本号
|
|||
|
|
const newVersion: string = packageJson.version;
|
|||
|
|
|
|||
|
|
// 在构建前删除 src-tauri/target/release/bundle 目录内容
|
|||
|
|
const bundleDir = join('src-tauri', 'target', 'release', 'bundle');
|
|||
|
|
if (existsSync(bundleDir)) {
|
|||
|
|
console.log('\x1b[33m%s\x1b[0m', `正在删除 ${bundleDir} 目录内容...`);
|
|||
|
|
rmSync(bundleDir, { recursive: true, force: true });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 先执行 Vite 默认配置构建
|
|||
|
|
console.log('\x1b[36m%s\x1b[0m', '正在构建 index.html入口...');
|
|||
|
|
const viteBuild = spawn('vite', ['build', '--mode', buildMode], {
|
|||
|
|
stdio: 'inherit',
|
|||
|
|
shell: true,
|
|||
|
|
env: { ...process.env },
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
viteBuild.on('close', (code) => {
|
|||
|
|
if (code !== 0) {
|
|||
|
|
console.error('\x1b[31m%s\x1b[0m', `Vite 打包 index.html失败,退出码: ${code}`);
|
|||
|
|
process.exit(code);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('\x1b[36m%s\x1b[0m', '正在构建 其他.html入口...');
|
|||
|
|
const viteSimplifiedBuild = spawn('vite', ['build', '--config', 'vite.simplified.config.ts', '--mode', buildMode], {
|
|||
|
|
stdio: 'inherit',
|
|||
|
|
shell: true,
|
|||
|
|
env: { ...process.env },
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
viteSimplifiedBuild.on('close', (simplifiedCode) => {
|
|||
|
|
if (simplifiedCode !== 0) {
|
|||
|
|
console.error('\x1b[31m%s\x1b[0m', `Vite 打包 其他.html入口构建失败,退出码: ${simplifiedCode}`);
|
|||
|
|
process.exit(simplifiedCode);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('\x1b[36m%s\x1b[0m', 'Vite 构建完成,开始执行 Tauri 构建...');
|
|||
|
|
const tauriBuild = spawn('tauri', ['build', '--features', buildFeature], {
|
|||
|
|
stdio: 'inherit',
|
|||
|
|
shell: true,
|
|||
|
|
env: process.env, // 传递所有环境变量
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
tauriBuild.on('close', (tauriCode) => {
|
|||
|
|
if (tauriCode === 0) {
|
|||
|
|
// 写入 latest.json 到 bundle 目录
|
|||
|
|
const latestJsonPath = join(bundleDir, 'latest.json');
|
|||
|
|
// 为不同平台生成相应的 latest.json
|
|||
|
|
if (platform() === 'win32') {
|
|||
|
|
const latestJson = getLatestJson('windows', newVersion, 'nsis', 'exe');
|
|||
|
|
writeFileSync(latestJsonPath, JSON.stringify(latestJson, null, 2));
|
|||
|
|
console.log('\x1b[32m%s\x1b[0m', `已生成 latest.json 文件: ${latestJsonPath}`);
|
|||
|
|
} else if (platform() === 'darwin') {
|
|||
|
|
const latestJson = getLatestJson('macos', newVersion, 'macos', 'app.tar.gz');
|
|||
|
|
writeFileSync(latestJsonPath, JSON.stringify(latestJson, null, 2));
|
|||
|
|
console.log('\x1b[32m%s\x1b[0m', `已生成 latest.json 文件: ${latestJsonPath}`);
|
|||
|
|
} else if (platform() === 'linux') {
|
|||
|
|
// 为 AppImage 生成 latest.json
|
|||
|
|
const appImageLatest = getLatestJson('linux', newVersion, 'appimage', 'AppImage');
|
|||
|
|
writeFileSync(join(bundleDir, 'appimage', 'latest.json'), JSON.stringify(appImageLatest, null, 2));
|
|||
|
|
console.log('\x1b[32m%s\x1b[0m', `已生成 appimage-latest.json 文件`);
|
|||
|
|
|
|||
|
|
// 为 deb 生成 latest.json
|
|||
|
|
const debLatest = getLatestJson('linux', newVersion, 'deb', 'deb');
|
|||
|
|
writeFileSync(join(bundleDir, 'deb', 'latest.json'), JSON.stringify(debLatest, null, 2));
|
|||
|
|
console.log('\x1b[32m%s\x1b[0m', `已生成 deb-latest.json 文件`);
|
|||
|
|
|
|||
|
|
// 为 rpm 生成 latest.json
|
|||
|
|
const rpmLatest = getLatestJson('linux', newVersion, 'rpm', 'rpm');
|
|||
|
|
writeFileSync(join(bundleDir, 'rpm', 'latest.json'), JSON.stringify(rpmLatest, null, 2));
|
|||
|
|
console.log('\x1b[32m%s\x1b[0m', `已生成 rpm-latest.json 文件`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Tauri 构建成功后执行自动部署
|
|||
|
|
console.log('\x1b[36m%s\x1b[0m', '开始执行自动部署...');
|
|||
|
|
try {
|
|||
|
|
const autoDeployScriptPath = join('.', 'vite', 'scripts', 'auto-deply.ts');
|
|||
|
|
execSync(`npx tsx "${autoDeployScriptPath}"`, { stdio: 'inherit' });
|
|||
|
|
console.log('\x1b[32m%s\x1b[0m', '自动部署执行完成');
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error('\x1b[31m%s\x1b[0m', '自动部署执行失败:', error.message);
|
|||
|
|
// 注意:这里不退出进程,因为构建本身是成功的
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
console.error('\x1b[31m%s\x1b[0m', `Tauri 构建失败,退出码: ${tauriCode}`);
|
|||
|
|
process.exit(tauriCode);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 得到最新版本信息
|
|||
|
|
* @param ostype - 平台类型,如 'windows'、'macos'、'linux'
|
|||
|
|
* @param version - 新版本号
|
|||
|
|
* @param catalog - 包的目录名
|
|||
|
|
* @param extension - 包的扩展名
|
|||
|
|
*/
|
|||
|
|
interface LatestJson {
|
|||
|
|
version: string;
|
|||
|
|
pub_date: string;
|
|||
|
|
url: string;
|
|||
|
|
signature: string;
|
|||
|
|
notes: string;
|
|||
|
|
downloadLink?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取最新版本信息
|
|||
|
|
* @param ostype - 平台类型,如 'windows'、'macos'、'linux'
|
|||
|
|
* @param version - 新版本号
|
|||
|
|
* @param catalog - 包的目录名
|
|||
|
|
* @param extension - 包的扩展名
|
|||
|
|
*/
|
|||
|
|
function getLatestJson(ostype: string, version: string, catalog: string, extension: string): LatestJson {
|
|||
|
|
const pubDate = new Date().toISOString();
|
|||
|
|
const latestJson: LatestJson = {
|
|||
|
|
version,
|
|||
|
|
pub_date: pubDate,
|
|||
|
|
url: '',
|
|||
|
|
signature: '',
|
|||
|
|
notes: '1. 修复BUG \n 2. 优化部分功能',
|
|||
|
|
};
|
|||
|
|
const baseUrl = `https://file.qyzhjy.com/app/zpkt_desktop_app/${ostype}`;
|
|||
|
|
try {
|
|||
|
|
if (ostype === 'windows') {
|
|||
|
|
const nsisDir = join(bundleDir, catalog);
|
|||
|
|
const files = readdirSync(nsisDir);
|
|||
|
|
const exeFile = files.find((file) => file.endsWith('exe'));
|
|||
|
|
latestJson.downloadLink = `${baseUrl}/${catalog}/${exeFile}`;
|
|||
|
|
} else if (ostype === 'macos') {
|
|||
|
|
const dmgDir = join(bundleDir, 'dmg');
|
|||
|
|
const files = readdirSync(dmgDir);
|
|||
|
|
const dmgFile = files.find((file) => file.endsWith('dmg'));
|
|||
|
|
latestJson.downloadLink = `${baseUrl}/dmg/${dmgFile}`;
|
|||
|
|
} else if (ostype === 'linux') {
|
|||
|
|
const appImageDir = join(bundleDir, catalog);
|
|||
|
|
const files = readdirSync(appImageDir);
|
|||
|
|
const appImageFile = files.find((file) => file.endsWith(extension));
|
|||
|
|
latestJson.downloadLink = `${baseUrl}/${catalog}/${appImageFile}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查找 nsis 目录下的 exe 文件
|
|||
|
|
const nsisDir = join(bundleDir, catalog);
|
|||
|
|
if (existsSync(nsisDir)) {
|
|||
|
|
const files = readdirSync(nsisDir);
|
|||
|
|
const exeFile = files.find((file) => file.endsWith(extension));
|
|||
|
|
const sigFile = files.find((file) => file.endsWith(`${extension}.sig`));
|
|||
|
|
if (exeFile) {
|
|||
|
|
// 设置 Windows 平台的 URL
|
|||
|
|
latestJson.url = `${baseUrl}/${catalog}/${exeFile}`;
|
|||
|
|
// 如果存在对应的 .sig 文件,读取签名内容
|
|||
|
|
if (sigFile) {
|
|||
|
|
const sigFilePath = join(nsisDir, sigFile);
|
|||
|
|
latestJson.signature = readFileSync(sigFilePath, 'utf8').trim();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return latestJson;
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.warn('\x1b[33m%s\x1b[0m', '警告: 无法读取 Windows 安装包信息:', error.message);
|
|||
|
|
return latestJson;
|
|||
|
|
}
|
|||
|
|
}
|