feat: 重构更新流程 — 无更新不抛异常、用户决定下载、更新详情展示(md文件中的)
【核心重构】
- getLatestVersion() 不再 throw Error,无更新时返回 { version: APP_VERSION }
- checkForUpdates() 先自行预检 → 无更新直发 IPC,有更新才交 electron-updater
- 移除 NO_UPDATE_MESSAGE 常量及所有字符串匹配拦截代码
- HeiXiuProvider 增加 500ms TTL 缓存,避免预检+electron-updater 重复请求 API
【用户体验】
- 检查到新版本后不再自动下载,展示更新内容供用户决定
- 新增"下载更新"按钮(与"安装更新"分离为两步操作)
- 设置页新增"查看详情"弹窗,Markdown 格式化渲染更新日志
- available / downloading / downloaded 三个状态 UI 独立展示
【Bug 修复】
- 测试服务器版本排序:字符串序 → 语义版本序(_version_key),0.0.10 正确 > 0.0.9
- 版本比较:!= → <(_version_key),防止高版本误判为有更新
- get_best_release fallback 同样改为语义版本排序
- Windows cmd set 命令尾部空格 → Invalid URL(.trim() 修复)
- electron-updater 'error' 事件中拦截 NO_UPDATE_MESSAGE → UPDATE_NOT_AVAILABLE
【文档更新】
- CHANGELOG.md、PROJECT.md、UpdateA.md 同步更新架构图和流程说明
This commit is contained in:
92
electron/main/load-env.ts
Normal file
92
electron/main/load-env.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
// 主进程 .env 加载器:.env.{mode} → .env.local → OS 环境变量(后者覆盖前者)
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
// ESM 中 __dirname 不可用,手动计算
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
/**
|
||||
* 解析单个 .env 文件,将 key=value 写入 process.env。
|
||||
* 已存在的 process.env 值不会被覆盖(OS 环境变量优先)。
|
||||
* 返回成功加载的变量数。
|
||||
*/
|
||||
function parseEnvFile(filePath: string, fileName: string): number {
|
||||
try {
|
||||
const content = readFileSync(filePath, 'utf-8');
|
||||
let count = 0;
|
||||
|
||||
for (const line of content.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// 跳过空行和注释
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
|
||||
// 解析 key=value
|
||||
const eqIdx = trimmed.indexOf('=');
|
||||
if (eqIdx === -1) continue;
|
||||
|
||||
const key = trimmed.slice(0, eqIdx).trim();
|
||||
let value = trimmed.slice(eqIdx + 1).trim();
|
||||
|
||||
// 去除引号(支持 "value" 和 'value')
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
// 不覆盖已在 OS 环境变量中设置的值
|
||||
if (key && !process.env[key]) {
|
||||
process.env[key] = value;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
console.log(`[load-env] 从 ${fileName} 加载了 ${count} 个环境变量`);
|
||||
}
|
||||
return count;
|
||||
} catch (err) {
|
||||
// 文件不存在 — 静默跳过(打包后 .env.* 不在 ASAR 中,这是正常情况)
|
||||
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
return 0;
|
||||
}
|
||||
// 其他错误(权限、编码等)— 警告但不中断
|
||||
console.warn(`[load-env] 读取 ${fileName} 失败:`, (err as Error).message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载环境变量文件到 process.env。
|
||||
* 开发模式加载 .env.development,生产模式加载 .env.production,
|
||||
* 之后再加载 .env.local 作为覆盖层。
|
||||
*/
|
||||
export function loadEnvFile(): void {
|
||||
// 项目根目录
|
||||
// APP_ROOT 在 main.ts 中设置(dist-electron/.. = 项目根)
|
||||
// fallback:dist-electron/main/load-env.js → __dirname/../.. = 项目根
|
||||
const projectRoot = process.env.APP_ROOT || join(__dirname, '..', '..');
|
||||
|
||||
// 确定当前模式
|
||||
const mode =
|
||||
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
|
||||
? 'development'
|
||||
: 'production';
|
||||
|
||||
console.log(
|
||||
`[load-env] 模式: ${mode}, projectRoot: ${projectRoot}`,
|
||||
);
|
||||
|
||||
// ① 基础环境文件(按模式选择)
|
||||
const baseFile = `.env.${mode}`;
|
||||
parseEnvFile(join(projectRoot, baseFile), baseFile);
|
||||
|
||||
// ② 本地覆盖文件(不进 git,用于打包后连测试服务器等特殊场景)
|
||||
const localFile = '.env.local';
|
||||
parseEnvFile(join(projectRoot, localFile), localFile);
|
||||
}
|
||||
Reference in New Issue
Block a user