feat: 取消安装包美化

This commit is contained in:
ILoveBingLu
2026-04-02 00:13:06 +08:00
parent a77e88907b
commit 31c1446369
6 changed files with 3 additions and 316 deletions
+3 -12
View File
@@ -113,7 +113,7 @@ abc1234 release: v2.0.4
GitHub Actions 会自动:
1. 安装依赖
2. 重新编译原生模块
3. 构建美化安装包
3. 构建标准安装包
4. 创建 GitHub Release
5. 上传到 Cloudflare R2
6. 上传 `CipherTalk-2.0.4-Setup.exe`
@@ -168,7 +168,7 @@ npm run tuisong
### 完整构建(生产环境)
```bash
npm run build:pro
npm run build
```
包含:
@@ -176,17 +176,8 @@ npm run build:pro
- ✅ TypeScript 编译
- ✅ Vite 构建前端
- ✅ Electron 打包
- ✅ 生成美化安装包
- ✅ 更新 latest.yml
### 仅构建外壳(测试用)
```bash
node scripts/build-shell-only.js
```
用于快速测试安装程序界面。
---
## 🤖 GitHub Actions 自动化
@@ -195,7 +186,7 @@ node scripts/build-shell-only.js
1. 📦 安装依赖
2. 🔨 重新编译原生模块
3. 🏗️ 构建应用程序(`npm run build:pro`
3. 🏗️ 构建应用程序(`npm run build`
4. 📊 获取版本号(从 `package.json`
5. 🎉 创建 GitHub Release(标签:`v2.0.4`
6. ☁️ 上传到 Cloudflare R2(自动删除旧版本)
-156
View File
@@ -1,156 +0,0 @@
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
//配置区
const PROJECT_ROOT = path.join(__dirname, '..');
const RELEASE_DIR = path.join(PROJECT_ROOT, 'release');
const INSTALLER_PRJ_DIR = path.join(PROJECT_ROOT, 'MyCoolInstaller');
const EMBEDDED_NAME = 'EmbeddedInstaller.exe';
function log(msg) {
console.log(`\n\x1b[36m[Build-Full]\x1b[0m ${msg}`);
}
function error(msg) {
console.error(`\n\x1b[31m[Error]\x1b[0m ${msg}`);
process.exit(1);
}
try {
// 1. 构建核心 Electron 应用 (包含 NSIS 打包 + UPX 优化)
log('🚀 Step 1: 构建核心 Electron 应用...');
execSync('npm run build', { stdio: 'inherit', cwd: PROJECT_ROOT });
// 2. 找到生成的 NSIS 安装包 (必须匹配当前版本)
log('🔍 Step 2: 寻找生成的 NSIS 安装包...');
if (!fs.existsSync(RELEASE_DIR)) error('Release 目录不存在,构建可能失败');
// 读取项目版本
const pkgPath = path.join(PROJECT_ROOT, 'package.json');
const pkgVersion = require(pkgPath).version;
const expectedName = `CipherTalk-${pkgVersion}-Setup.exe`;
const nsisPath = path.join(RELEASE_DIR, expectedName);
if (!fs.existsSync(nsisPath)) {
// 尝试模糊搜索作为备选(有时候 electron-builder 不带版本号?)
error(`未找到目标版本安装包: ${expectedName}\n请检查 package.json 版本号是否与生成产物一致。`);
}
const version = pkgVersion;
log(`✅ 找到安装包: ${expectedName} (v${version})`);
// 3. 复制到 WPF 工程目录准备嵌入
log('🚚 Step 3: 注入到安装器工程...');
const targetPayloadPath = path.join(INSTALLER_PRJ_DIR, EMBEDDED_NAME);
fs.copyFileSync(nsisPath, targetPayloadPath);
// 4. 编译 WPF 外壳 (需要系统中装有 .NET SDK)
log('🔨 Step 4: 编译 WPF 高颜值外壳...');
// 动态同步版本号:将 package.json 的 version 同步到 CSPROJ
// .NET 版本号遵循 Major.Minor.Build.Revision (4位),所以补个 .0
const netVersion = version.split('.').length === 3 ? `${version}.0` : version;
const csprojPath = path.join(INSTALLER_PRJ_DIR, 'MyCoolInstaller.csproj');
let csprojContent = fs.readFileSync(csprojPath, 'utf8');
csprojContent = csprojContent.replace(/<AssemblyVersion>.*<\/AssemblyVersion>/g, `<AssemblyVersion>${netVersion}</AssemblyVersion>`);
csprojContent = csprojContent.replace(/<FileVersion>.*<\/FileVersion>/g, `<FileVersion>${netVersion}</FileVersion>`);
fs.writeFileSync(csprojPath, csprojContent);
log(`️ 已更新安装器元数据版本为: ${netVersion}`);
// 指向具体的 csproj,避免多项目时的歧义
// 不使用 -o 参数,规避 Solution 构建时的路径冲突
const publishCmd = `dotnet publish "${csprojPath}" -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true`;
try {
execSync(publishCmd, { stdio: 'inherit' });
} catch (e) {
error('WPF 编译失败。请确保安装了 .NET 8 SDK。');
}
// 5. 将最终产物移回 release 目录
log('🎁 Step 5: 输出最终产物...');
// 默认发布路径
const wpfOutput = path.join(INSTALLER_PRJ_DIR, 'bin', 'Release', 'net8.0-windows', 'win-x64', 'publish', 'MyCoolInstaller.exe');
if (!fs.existsSync(wpfOutput)) error(`WPF 产物未找到: ${wpfOutput}`);
// 记录原始大小用于日志
const originalSize = (fs.statSync(nsisPath).size / 1024 / 1024).toFixed(2);
// A. 备份原版 (改名为 Core-Setup)
const coreName = `CipherTalk-${version}-Core-Setup.exe`;
const corePath = path.join(RELEASE_DIR, coreName);
if (fs.existsSync(corePath)) fs.unlinkSync(corePath); // 覆盖旧备份
fs.renameSync(nsisPath, corePath);
log(`️ 原版安装包已重命名备份为: ${coreName}`);
// B. WPF 外壳上位 (使用标准 Setup 名字)
const finalName = `CipherTalk-${version}-Setup.exe`;
const finalPath = path.join(RELEASE_DIR, finalName);
// 复制前先检查占用
try {
if (fs.existsSync(finalPath)) fs.unlinkSync(finalPath);
fs.copyFileSync(wpfOutput, finalPath);
} catch (e) {
if (e.code === 'EBUSY') error(`目标文件被占用: ${finalPath}\n请关闭文件夹或程序后重试。`);
throw e;
}
// 清理临时文件
fs.unlinkSync(targetPayloadPath);
log(`🎉🎉🎉 全流程构建完成!`);
log(`📂 最终安装包: ${finalPath}`);
log(`📏 原始大小: ${originalSize} MB`);
const finalSize = fs.statSync(finalPath).size;
log(`📏 最终大小: ${(finalSize / 1024 / 1024).toFixed(2)} MB`);
// 6. 关键步骤:更新 latest.yml 以匹配新的安装包
// 否则自动更新会因为 SHA512 不匹配而失败
log('📝 Step 6: 修正 latest.yml 校验信息...');
const yamlPath = path.join(RELEASE_DIR, 'latest.yml');
// A. 必须删除 .blockmap 文件!
// 因为我们的 Setup.exe 已经被替换,原有的 blockmap 是针对旧 EXE 的。
// 如果不删,Updater 会尝试差分更新,导致校验失败。
const blockMapName = `${finalName}.blockmap`;
const blockMapPath = path.join(RELEASE_DIR, blockMapName);
if (fs.existsSync(blockMapPath)) {
fs.unlinkSync(blockMapPath);
log(`🗑️ 已删除无效的 BlockMap: ${blockMapName} (禁用差分更新)`);
}
if (fs.existsSync(yamlPath)) {
const crypto = require('crypto');
// 计算新的 SHA512 (Base64格式)
const buffer = fs.readFileSync(finalPath);
const hash = crypto.createHash('sha512').update(buffer).digest('base64');
let yamlContent = fs.readFileSync(yamlPath, 'utf8');
// 简单正则替换 (避免引入 yaml 库依赖)
// 1. 替换顶层 sha512
yamlContent = yamlContent.replace(/sha512: .+/g, `sha512: ${hash}`);
// 2. 替换顶层 size
yamlContent = yamlContent.replace(/size: \d+/g, `size: ${finalSize}`);
// 3. 确保 files 列表下的信息也更新 (如果有)
// 这比较复杂,通常 electron-updater 主要看顶层,或者 files 里的第一项
// 我们假设 electron-builder 生成的标准格式,暴力替换所有匹配的 checksum
// 但更安全的是只替换顶部的。标准 latest.yml 结构中 files 下也有 sha512。
// 重新写入
fs.writeFileSync(yamlPath, yamlContent);
log(`✅ latest.yml 已更新:\n SHA512: ${hash.substring(0, 20)}...\n Size: ${finalSize}`);
} else {
log('⚠️ 未找到 latest.yml,跳过元数据更新 (仅本地构建?)');
}
} catch (err) {
error(err.message);
}
-84
View File
@@ -1,84 +0,0 @@
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
//配置区
const PROJECT_ROOT = path.join(__dirname, '..');
const RELEASE_DIR = path.join(PROJECT_ROOT, 'release');
const INSTALLER_PRJ_DIR = path.join(PROJECT_ROOT, 'MyCoolInstaller');
const EMBEDDED_NAME = 'EmbeddedInstaller.exe';
function log(msg) { console.log(`\n\x1b[36m[Build-Shell]\x1b[0m ${msg}`); }
function error(msg) { console.error(`\n\x1b[31m[Error]\x1b[0m ${msg}`); process.exit(1); }
try {
// 0. 读取当前项目版本
const pkg = require(path.join(PROJECT_ROOT, 'package.json'));
const currentVersion = pkg.version;
log(`️ 当前项目版本: v${currentVersion}`);
// 1. 找到对应的 NSIS 安装包
log('🔍 Step 1: 寻找对应的 NSIS 安装包...');
if (!fs.existsSync(RELEASE_DIR)) error('Release 目录不存在');
// 精准匹配当前版本的安装包
const targetInstallerName = `CipherTalk-${currentVersion}-Setup.exe`;
const nsisPath = path.join(RELEASE_DIR, targetInstallerName);
if (!fs.existsSync(nsisPath)) {
error(`未找到对应版本的安装包: ${targetInstallerName}\n请先运行 npm run build 生成该版本的 Electron 安装包。`);
}
log(`✅ 找到安装包: ${targetInstallerName}`);
// 不需要正则匹配了,版本就是 currentVersion
const version = currentVersion;
// 2. 复制到 WPF 工程目录准备嵌入
log('🚚 Step 2: 注入到安装器工程...');
const targetPayloadPath = path.join(INSTALLER_PRJ_DIR, EMBEDDED_NAME);
fs.copyFileSync(nsisPath, targetPayloadPath);
// 3. 编译 WPF 外壳
log('🔨 Step 3: 快速编译 WPF 外壳...');
const csprojPath = path.join(INSTALLER_PRJ_DIR, 'MyCoolInstaller.csproj');
// 使用 PublishSingleFile 确保成单文件
const publishCmd = `dotnet publish "${csprojPath}" -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true`;
try {
execSync(publishCmd, { stdio: 'inherit' });
} catch (e) {
error('WPF 编译失败');
}
// 4. 将最终产物移回 release 目录
log('🎁 Step 4: 输出最终产物...');
const wpfOutput = path.join(INSTALLER_PRJ_DIR, 'bin', 'Release', 'net8.0-windows', 'win-x64', 'publish', 'MyCoolInstaller.exe');
if (!fs.existsSync(wpfOutput)) error(`WPF 产物未找到: ${wpfOutput}`);
// 使用 Shell-Setup 后缀区分全量构建
const finalName = `CipherTalk-${version}-Shell-Setup.exe`;
const finalPath = path.join(RELEASE_DIR, finalName);
try {
if (fs.existsSync(finalPath)) {
fs.unlinkSync(finalPath); // 尝试先删除旧文件
}
fs.copyFileSync(wpfOutput, finalPath);
} catch (e) {
if (e.code === 'EBUSY' || e.code === 'EPERM') {
error(`目标文件被占用: ${finalPath}\n请关闭正在运行的安装程序或文件夹,然后重试。`);
} else {
throw e;
}
}
// 清理临时文件
fs.unlinkSync(targetPayloadPath);
log(`🎉🎉🎉 外壳构建完成!`);
log(`📂 最终安装包: ${finalPath}`);
} catch (err) {
error(err.message);
}
-61
View File
@@ -1,61 +0,0 @@
const sharp = require('sharp');
const path = require('path');
const fs = require('fs');
const INPUT_LOGO = path.join(__dirname, '../public/xinnian.png');
// WPF Project Directory
const OUTPUT_DIR = path.join(__dirname, '../MyCoolInstaller');
const OUTPUT_BANNER = path.join(OUTPUT_DIR, 'left_banner.png');
async function generateWpfAssets() {
console.log('正在生成 WPF 安装器资源...');
// 生成左侧通栏图片: 240x520 (Window Height is 520)
try {
const width = 240;
const height = 520;
// 1. 创建背景 (新年淡红)
const banner = await sharp({
create: {
width: width,
height: height,
channels: 4,
background: '#FFF0F0'
}
});
// 2. 准备 Logo (大一点,放在上部)
const logoBuffer = await sharp(INPUT_LOGO)
.resize(160, 160, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
.toBuffer();
// 3. 准备底部装饰 (可选,这里简化,只放Logo)
// 也可以叠加一些红色圆或者图案来增加氛围,这里简单叠加一个半透明红色块到底部
const decorHeight = 100;
const decor = await sharp({
create: {
width: width,
height: decorHeight,
channels: 4,
background: { r: 230, g: 0, b: 18, alpha: 0.1 } // rgba(230, 0, 18, 0.1)
}
}).png().toBuffer();
// 合成
await banner
.composite([
{ input: logoBuffer, top: 60, left: 40 }, // Logo 居中 (240-160)/2 = 40
{ input: decor, top: height - decorHeight, left: 0 } //底部装饰
])
.png()
.toFile(OUTPUT_BANNER);
console.log('✅ WPF 侧边栏已生成:', OUTPUT_BANNER);
} catch (e) {
console.error('生成 WPF 资源失败:', e);
}
}
generateWpfAssets();