mirror of
https://mirror.skon.top/github.com/ILoveBingLu/CipherTalk
synced 2026-05-01 14:28:44 +08:00
libwcdb_api.dylib 编译时硬链接 @rpath/WCDB.framework/Versions/<ver>/WCDB,
但 native 产物把 WCDB 主二进制扁平化为 libWCDB.dylib 放在 resources/macos/,
extraResources 复制后落在 Contents/Resources/resources/macos/。dyld 在运行时
按 framework 路径找不到主二进制,导致 mac 装包后 GUI 解密入口报:
WCDB 初始化异常: Failed to load shared library
Library not loaded: @rpath/WCDB.framework/Versions/2.1.15/WCDB
Reason: tried: '<App>/Contents/Frameworks/WCDB.framework/Versions/2.1.15/WCDB' (no such file)
App 实际无法工作。
新增 scripts/setup-macos-wcdb-framework.js,由 clean-locales.js 在 afterPack
钩子里调用,打包后自动:
1. 从 libWCDB.dylib 的 install_name 解析期望的 framework 版本号(解析失败回退 "A")
2. 在 Contents/Frameworks/WCDB.framework/Versions/<ver>/WCDB 处放置同一个二进制
3. 创建 Versions/Current 与顶层 WCDB 软链
4. 对 framework 内主二进制和 framework 目录做 ad-hoc 重签名
不删除原 libWCDB.dylib,保留作为后向兼容来源。
MACOS_PORT_GUIDE.md 第 10 节补充该机制说明。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
128 lines
4.9 KiB
JavaScript
128 lines
4.9 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
const { Arch } = require('electron-builder');
|
||
const { setupMacosWcdbFramework } = require('./setup-macos-wcdb-framework');
|
||
|
||
const IMAGE_NATIVE_PREFIX = 'ciphertalk-image-native-';
|
||
const IMAGE_NATIVE_SUFFIX = '.node';
|
||
|
||
function resolveNativePlatform(electronPlatformName) {
|
||
if (electronPlatformName === 'darwin') return 'macos';
|
||
if (electronPlatformName === 'win32') return 'win32';
|
||
if (electronPlatformName === 'linux') return 'linux';
|
||
return electronPlatformName;
|
||
}
|
||
|
||
function resolveNativeArch(arch) {
|
||
if (typeof arch === 'string') return arch;
|
||
if (typeof arch === 'number' && Arch[arch]) return Arch[arch];
|
||
return process.arch;
|
||
}
|
||
|
||
function uniqueExistingDirs(candidates) {
|
||
return Array.from(new Set(candidates)).filter((targetPath) => fs.existsSync(targetPath));
|
||
}
|
||
|
||
function rewriteNativeManifest(manifestPath, targetKey) {
|
||
if (!fs.existsSync(manifestPath)) return;
|
||
|
||
try {
|
||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||
const nextActiveBinaries = {};
|
||
if (manifest.activeBinaries && manifest.activeBinaries[targetKey]) {
|
||
nextActiveBinaries[targetKey] = manifest.activeBinaries[targetKey];
|
||
}
|
||
manifest.activeBinaries = nextActiveBinaries;
|
||
manifest.platforms = Object.keys(nextActiveBinaries);
|
||
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||
console.log(`已收敛 image native manifest 到当前平台: ${targetKey}`);
|
||
} catch (error) {
|
||
console.warn(`收敛 image native manifest 失败: ${manifestPath}`, error);
|
||
}
|
||
}
|
||
|
||
function pruneImageNativeAddons(context) {
|
||
const platformDir = resolveNativePlatform(context.electronPlatformName);
|
||
const archDir = resolveNativeArch(context.arch);
|
||
const targetFileName = `${IMAGE_NATIVE_PREFIX}${platformDir}-${archDir}${IMAGE_NATIVE_SUFFIX}`;
|
||
const targetKey = `${platformDir}-${archDir}`;
|
||
const productName = context.packager?.appInfo?.productFilename || 'CipherTalk';
|
||
const resourceRoots = uniqueExistingDirs([
|
||
path.join(context.appOutDir, 'resources'),
|
||
path.join(context.appOutDir, 'Contents', 'Resources'),
|
||
path.join(context.appOutDir, `${productName}.app`, 'Contents', 'Resources')
|
||
]);
|
||
|
||
for (const resourceRoot of resourceRoots) {
|
||
for (const nativeDir of [
|
||
path.join(resourceRoot, 'resources', 'wedecrypt'),
|
||
path.join(resourceRoot, 'wedecrypt')
|
||
]) {
|
||
if (!fs.existsSync(nativeDir)) continue;
|
||
|
||
const nativeFiles = fs.readdirSync(nativeDir)
|
||
.filter((file) => file.startsWith(IMAGE_NATIVE_PREFIX) && file.endsWith(IMAGE_NATIVE_SUFFIX));
|
||
if (nativeFiles.length === 0) continue;
|
||
|
||
if (!nativeFiles.includes(targetFileName)) {
|
||
console.warn(`未找到当前平台 image native addon,跳过裁剪: ${targetFileName}`);
|
||
continue;
|
||
}
|
||
|
||
let deletedCount = 0;
|
||
for (const file of nativeFiles) {
|
||
if (file === targetFileName) continue;
|
||
fs.rmSync(path.join(nativeDir, file), { force: true });
|
||
deletedCount++;
|
||
}
|
||
|
||
rewriteNativeManifest(path.join(nativeDir, 'manifest.json'), targetKey);
|
||
console.log(`已裁剪 image native addon,仅保留 ${targetFileName},删除 ${deletedCount} 个无关文件。`);
|
||
}
|
||
}
|
||
}
|
||
|
||
exports.default = async function (context) {
|
||
// context.appOutDir 是打包后的临时解压目录
|
||
const localesDir = path.join(context.appOutDir, 'locales');
|
||
|
||
if (fs.existsSync(localesDir)) {
|
||
console.log('正在清理多余的 Chromium 语言包...');
|
||
const files = fs.readdirSync(localesDir);
|
||
|
||
// 只保留中文(简体/繁体)和英文
|
||
const whitelist = [
|
||
'zh-CN.pak',
|
||
'en-US.pak'
|
||
];
|
||
|
||
let deletedCount = 0;
|
||
for (const file of files) {
|
||
if (file.endsWith('.pak') && !whitelist.includes(file)) {
|
||
fs.unlinkSync(path.join(localesDir, file));
|
||
deletedCount++;
|
||
}
|
||
}
|
||
console.log(`已删除 ${deletedCount} 个无关语言包,仅保留中英文。`);
|
||
}
|
||
|
||
pruneImageNativeAddons(context);
|
||
|
||
if (context.electronPlatformName === 'darwin') {
|
||
const productName = context.packager?.appInfo?.productFilename || 'CipherTalk';
|
||
const launcherCandidates = [
|
||
path.join(context.appOutDir, 'ciphertalk-mcp'),
|
||
path.join(context.appOutDir, `${productName}.app`, 'Contents', 'MacOS', 'ciphertalk-mcp')
|
||
];
|
||
|
||
for (const launcherPath of launcherCandidates) {
|
||
if (!fs.existsSync(launcherPath)) continue;
|
||
fs.chmodSync(launcherPath, 0o755);
|
||
console.log(`已确保 macOS MCP 启动器可执行: ${launcherPath}`);
|
||
break;
|
||
}
|
||
|
||
setupMacosWcdbFramework(context);
|
||
}
|
||
};
|