Compare commits

...

2 Commits

  • fix(desktop-runtime): 兼容 updater 缺失并修复打包后 wcdb 加载路径
    - electron-updater 改为可选加载,缺失时自动禁用更新并记录原因
    - 打包环境优先注入 WECHAT_TOOL_WCDB_API_DLL_PATH 指向 resources/backend/native/wcdb_api.dll
    - 启动后端进程 cwd 调整为 exe 所在目录,避免运行目录不一致
    - wcdb_init 失败时附带 native log 摘要,提升故障排查信息
    - package.json 显式保留 updater 相关依赖,避免构建裁剪导致模块缺失
  • build(desktop-backend): 打包后端时注入版本信息并同步 native 资源
    - 从 desktop/package.json 读取版本号并生成 PyInstaller version-file
    - 打包后复制 native 目录到 resources/backend/native,避免 onefile 临时目录依赖
    - 将 pyproject.toml 复制到 backend 资源目录,补齐运行时项目标记
    - 更新 .gitignore,忽略 backend/native 与 backend/pyproject.toml 打包产物
5 changed files with 148 additions and 10 deletions
+2
View File
@@ -44,6 +44,8 @@ pnpm-lock.yaml
/desktop/resources/ui/*
!/desktop/resources/ui/.gitkeep
/desktop/resources/backend/*.exe
/desktop/resources/backend/native/*
/desktop/resources/backend/pyproject.toml
!/desktop/resources/backend/.gitkeep
/desktop/resources/icon.ico
+23 -1
View File
@@ -25,7 +25,29 @@
},
"files": [
"src/**/*",
"package.json"
"package.json",
{
"from": "node_modules",
"to": "node_modules",
"filter": [
"electron-updater/**/*",
"builder-util-runtime/**/*",
"debug/**/*",
"ms/**/*",
"sax/**/*",
"js-yaml/**/*",
"argparse/**/*",
"lazy-val/**/*",
"lodash.escaperegexp/**/*",
"lodash.isequal/**/*",
"tiny-typed-emitter/**/*",
"fs-extra/**/*",
"graceful-fs/**/*",
"jsonfile/**/*",
"universalify/**/*",
"semver/**/*"
]
}
],
"extraResources": [
{
+87 -1
View File
@@ -13,8 +13,63 @@ fs.mkdirSync(distDir, { recursive: true });
fs.mkdirSync(workDir, { recursive: true });
fs.mkdirSync(specDir, { recursive: true });
function parseVersionTuple(rawVersion) {
const nums = String(rawVersion || "")
.split(/[^\d]+/)
.map((x) => Number.parseInt(x, 10))
.filter((n) => Number.isInteger(n) && n >= 0);
while (nums.length < 4) nums.push(0);
return nums.slice(0, 4);
}
function buildVersionInfoText(versionTuple, versionDot) {
const [a, b, c, d] = versionTuple;
return `# UTF-8
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(${a}, ${b}, ${c}, ${d}),
prodvers=(${a}, ${b}, ${c}, ${d}),
mask=0x3f,
flags=0x0,
OS=0x4,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo([
StringTable(
'080404B0',
[StringStruct('CompanyName', 'LifeArchiveProject'),
StringStruct('FileDescription', 'WeFlow'),
StringStruct('FileVersion', '${versionDot}'),
StringStruct('InternalName', 'weflow'),
StringStruct('LegalCopyright', 'github.com/hicccc77/WeFlow'),
StringStruct('OriginalFilename', 'weflow.exe'),
StringStruct('ProductName', 'WeFlow'),
StringStruct('ProductVersion', '${versionDot}')])
]),
VarFileInfo([VarStruct('Translation', [2052, 1200])])
]
)
`;
}
const nativeDir = path.join(repoRoot, "src", "wechat_decrypt_tool", "native");
const addData = `${nativeDir};wechat_decrypt_tool/native`;
const projectToml = path.join(repoRoot, "pyproject.toml");
const desktopPackageJsonPath = path.join(repoRoot, "desktop", "package.json");
let desktopVersion = "1.3.0";
try {
const pkg = JSON.parse(fs.readFileSync(desktopPackageJsonPath, { encoding: "utf8" }));
const v = String(pkg?.version || "").trim();
if (v) desktopVersion = v;
} catch {}
const versionTuple = parseVersionTuple(desktopVersion);
const versionDot = versionTuple.join(".");
const versionFilePath = path.join(workDir, "weflow-version.txt");
fs.writeFileSync(versionFilePath, buildVersionInfoText(versionTuple, versionDot), { encoding: "utf8" });
const args = [
"run",
@@ -30,11 +85,42 @@ const args = [
workDir,
"--specpath",
specDir,
"--version-file",
versionFilePath,
"--add-data",
addData,
entry,
];
const r = spawnSync("uv", args, { cwd: repoRoot, stdio: "inherit" });
process.exit(r.status ?? 1);
if ((r.status ?? 1) !== 0) {
process.exit(r.status ?? 1);
}
// Keep a stable external native folder for packaged runtime to avoid relying on
// onefile temp extraction paths when wcdb_api.dll performs environment checks.
const packagedNativeDir = path.join(distDir, "native");
try {
fs.rmSync(packagedNativeDir, { recursive: true, force: true });
} catch {}
fs.mkdirSync(packagedNativeDir, { recursive: true });
for (const name of fs.readdirSync(nativeDir)) {
const src = path.join(nativeDir, name);
const dst = path.join(packagedNativeDir, name);
try {
if (fs.statSync(src).isFile()) {
fs.copyFileSync(src, dst);
}
} catch {}
}
// Provide the project marker next to packaged backend resources.
if (fs.existsSync(projectToml)) {
try {
fs.copyFileSync(projectToml, path.join(distDir, "pyproject.toml"));
} catch {}
}
process.exit(0);
+27 -2
View File
@@ -8,7 +8,13 @@ const {
dialog,
shell,
} = require("electron");
const { autoUpdater } = require("electron-updater");
let autoUpdater = null;
let autoUpdaterLoadError = null;
try {
({ autoUpdater } = require("electron-updater"));
} catch (err) {
autoUpdaterLoadError = err;
}
const { spawn, spawnSync } = require("child_process");
const fs = require("fs");
const http = require("http");
@@ -297,6 +303,12 @@ function isAutoUpdateEnabled() {
const forced = parseEnvBool(process.env.AUTO_UPDATE_ENABLED);
let enabled = forced != null ? forced : !!app.isPackaged;
if (enabled && !autoUpdater) {
enabled = false;
logMain(
`[main] auto-update disabled: electron-updater unavailable: ${autoUpdaterLoadError?.message || "unknown error"}`
);
}
// In packaged builds electron-updater reads update config from app-update.yml.
// If missing, treat auto-update as disabled to avoid noisy errors.
@@ -823,6 +835,10 @@ function getPackagedBackendPath() {
return path.join(process.resourcesPath, "backend", "wechat-backend.exe");
}
function getPackagedWcdbDllPath() {
return path.join(process.resourcesPath, "backend", "native", "wcdb_api.dll");
}
function startBackend() {
if (backendProc) return backendProc;
@@ -853,8 +869,17 @@ function startBackend() {
`Packaged backend not found: ${backendExe}. Build it into desktop/resources/backend/wechat-backend.exe`
);
}
const packagedWcdbDll = getPackagedWcdbDllPath();
if (fs.existsSync(packagedWcdbDll)) {
env.WECHAT_TOOL_WCDB_API_DLL_PATH = packagedWcdbDll;
logMain(`[main] using packaged wcdb_api.dll: ${packagedWcdbDll}`);
} else {
logMain(`[main] packaged wcdb_api.dll not found: ${packagedWcdbDll}`);
}
const backendCwd = path.dirname(backendExe);
backendProc = spawn(backendExe, [], {
cwd: env.WECHAT_TOOL_DATA_DIR,
cwd: backendCwd,
env,
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
+9 -6
View File
@@ -253,7 +253,9 @@ def _ensure_initialized() -> None:
return
rc = int(lib.wcdb_init())
if rc != 0:
raise WCDBRealtimeError(f"wcdb_init failed: {rc}")
logs = get_native_logs(require_initialized=False)
hint = f" logs={logs[:6]}" if logs else ""
raise WCDBRealtimeError(f"wcdb_init failed: {rc}.{hint}")
_initialized = True
@@ -315,11 +317,12 @@ def _call_out_error(fn, *args) -> None:
pass
def get_native_logs() -> list[str]:
try:
_ensure_initialized()
except Exception:
return []
def get_native_logs(*, require_initialized: bool = True) -> list[str]:
if require_initialized:
try:
_ensure_initialized()
except Exception:
return []
lib = _load_wcdb_lib()
out = ctypes.c_char_p()
rc = int(lib.wcdb_get_logs(ctypes.byref(out)))