Files
tauri-meeting/vite/scripts/build.ts

247 lines
9.7 KiB
TypeScript
Raw Permalink Normal View History

#!/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;
}
}