From 31c14463694e4cbd5836e5c82ee2184c362d84bb Mon Sep 17 00:00:00 2001 From: ILoveBingLu Date: Thu, 2 Apr 2026 00:13:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=96=E6=B6=88=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E5=8C=85=E7=BE=8E=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - package.json | 2 - scripts/README.md | 15 +--- scripts/build-full.js | 156 --------------------------------- scripts/build-shell-only.js | 84 ------------------ scripts/generate-wpf-assets.js | 61 ------------- 6 files changed, 3 insertions(+), 316 deletions(-) delete mode 100644 scripts/build-full.js delete mode 100644 scripts/build-shell-only.js delete mode 100644 scripts/generate-wpf-assets.js diff --git a/.gitignore b/.gitignore index ad3fb3e..ccaf71e 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ Docs WeFolw upx native-dlls -MyCoolInstaller resources/whisper xkey skills diff --git a/package.json b/package.json index faec754..47a57c0 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,8 @@ "scripts": { "dev": "vite", "prebuild": "node scripts/update-readme-version.js", - "build:full": "node scripts/build-full.js", "build": "tsc && vite build && electron-builder && node scripts/add-size-to-yml.js", "build:mcp": "tsc && vite build", - "build:pro": "node scripts/build-full.js", "build:force-update-manifest": "node scripts/generate-force-update-manifest.js", "mcp": "node scripts/mcp-runner.js", "mcp:probe": "node scripts/mcp-probe.js", diff --git a/scripts/README.md b/scripts/README.md index 2981c19..75a8258 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -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(自动删除旧版本) diff --git a/scripts/build-full.js b/scripts/build-full.js deleted file mode 100644 index 7c78475..0000000 --- a/scripts/build-full.js +++ /dev/null @@ -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>/g, `${netVersion}`); - csprojContent = csprojContent.replace(/.*<\/FileVersion>/g, `${netVersion}`); - 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); -} diff --git a/scripts/build-shell-only.js b/scripts/build-shell-only.js deleted file mode 100644 index 6887f30..0000000 --- a/scripts/build-shell-only.js +++ /dev/null @@ -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); -} diff --git a/scripts/generate-wpf-assets.js b/scripts/generate-wpf-assets.js deleted file mode 100644 index 2945b59..0000000 --- a/scripts/generate-wpf-assets.js +++ /dev/null @@ -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();