- 添加项目图标文件(app-icon.png、各平台图标) - 配置开发环境文件(.env、.nvmrc、.npmrc) - 添加静态资源文件(背景图片、字体、音频) - 初始化Tauri后端结构(build.rs、main.rs、模块文件) - 配置前端项目结构(TypeScript、Vue组件、样式) - 添加Node.js API服务基础结构 - 配置构建和开发工具(vite、prettier、gitignore)
247 lines
9.7 KiB
JavaScript
247 lines
9.7 KiB
JavaScript
#!/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;
|
||
}
|
||
}
|