mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-05 04:20:34 +08:00
chore: 构建
This commit is contained in:
@@ -75,8 +75,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.2",
|
"@electron-toolkit/preload": "^3.0.2",
|
||||||
"@electron-toolkit/utils": "^4.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
|
"@koromix/koffi-darwin-x64": "3.1.0",
|
||||||
"@koromix/koffi-win32-x64": "3.1.0",
|
"@koromix/koffi-win32-x64": "3.1.0",
|
||||||
"@napi-rs/system-ocr": "1.2.0",
|
"@napi-rs/system-ocr": "1.2.0",
|
||||||
|
"@napi-rs/system-ocr-darwin-x64": "1.2.0",
|
||||||
"@napi-rs/system-ocr-win32-x64-msvc": "1.2.0",
|
"@napi-rs/system-ocr-win32-x64-msvc": "1.2.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.23",
|
"@radix-ui/react-alert-dialog": "^1.1.23",
|
||||||
"@radix-ui/react-checkbox": "^1.3.11",
|
"@radix-ui/react-checkbox": "^1.3.11",
|
||||||
@@ -108,7 +110,9 @@
|
|||||||
"parse5": "^8.0.0",
|
"parse5": "^8.0.0",
|
||||||
"pinyin-pro": "^3.26.0",
|
"pinyin-pro": "^3.26.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
|
"sherpa-onnx-darwin-x64": "1.13.3",
|
||||||
"sherpa-onnx-node": "1.13.3",
|
"sherpa-onnx-node": "1.13.3",
|
||||||
|
"sherpa-onnx-win-x64": "1.13.4",
|
||||||
"silk-wasm": "^3.7.1",
|
"silk-wasm": "^3.7.1",
|
||||||
"tailwind-merge": "^3.6.0",
|
"tailwind-merge": "^3.6.0",
|
||||||
"unzipper": "^0.12.0",
|
"unzipper": "^0.12.0",
|
||||||
|
|||||||
Generated
+38
-31
@@ -11,8 +11,10 @@ specifiers:
|
|||||||
'@electron-toolkit/preload': ^3.0.2
|
'@electron-toolkit/preload': ^3.0.2
|
||||||
'@electron-toolkit/tsconfig': ^2.0.0
|
'@electron-toolkit/tsconfig': ^2.0.0
|
||||||
'@electron-toolkit/utils': ^4.0.0
|
'@electron-toolkit/utils': ^4.0.0
|
||||||
|
'@koromix/koffi-darwin-x64': 3.1.0
|
||||||
'@koromix/koffi-win32-x64': 3.1.0
|
'@koromix/koffi-win32-x64': 3.1.0
|
||||||
'@napi-rs/system-ocr': 1.2.0
|
'@napi-rs/system-ocr': 1.2.0
|
||||||
|
'@napi-rs/system-ocr-darwin-x64': 1.2.0
|
||||||
'@napi-rs/system-ocr-win32-x64-msvc': 1.2.0
|
'@napi-rs/system-ocr-win32-x64-msvc': 1.2.0
|
||||||
'@playwright/test': ^1.62.1
|
'@playwright/test': ^1.62.1
|
||||||
'@radix-ui/react-alert-dialog': ^1.1.23
|
'@radix-ui/react-alert-dialog': ^1.1.23
|
||||||
@@ -72,7 +74,9 @@ specifiers:
|
|||||||
react: ^19.2.1
|
react: ^19.2.1
|
||||||
react-dom: ^19.2.1
|
react-dom: ^19.2.1
|
||||||
sass: ^1.102.0
|
sass: ^1.102.0
|
||||||
|
sherpa-onnx-darwin-x64: 1.13.3
|
||||||
sherpa-onnx-node: 1.13.3
|
sherpa-onnx-node: 1.13.3
|
||||||
|
sherpa-onnx-win-x64: 1.13.4
|
||||||
silk-wasm: ^3.7.1
|
silk-wasm: ^3.7.1
|
||||||
tailwind-merge: ^3.6.0
|
tailwind-merge: ^3.6.0
|
||||||
tailwindcss: 3.4.17
|
tailwindcss: 3.4.17
|
||||||
@@ -87,8 +91,10 @@ specifiers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@electron-toolkit/preload': 3.0.2_electron@43.1.0
|
'@electron-toolkit/preload': 3.0.2_electron@43.1.0
|
||||||
'@electron-toolkit/utils': 4.0.0_electron@43.1.0
|
'@electron-toolkit/utils': 4.0.0_electron@43.1.0
|
||||||
|
'@koromix/koffi-darwin-x64': 3.1.0
|
||||||
'@koromix/koffi-win32-x64': 3.1.0
|
'@koromix/koffi-win32-x64': 3.1.0
|
||||||
'@napi-rs/system-ocr': 1.2.0
|
'@napi-rs/system-ocr': 1.2.0
|
||||||
|
'@napi-rs/system-ocr-darwin-x64': 1.2.0
|
||||||
'@napi-rs/system-ocr-win32-x64-msvc': 1.2.0
|
'@napi-rs/system-ocr-win32-x64-msvc': 1.2.0
|
||||||
'@radix-ui/react-alert-dialog': 1.1.23_eijghdl4n2x4hz6j4cg7ctgbuu
|
'@radix-ui/react-alert-dialog': 1.1.23_eijghdl4n2x4hz6j4cg7ctgbuu
|
||||||
'@radix-ui/react-checkbox': 1.3.11_eijghdl4n2x4hz6j4cg7ctgbuu
|
'@radix-ui/react-checkbox': 1.3.11_eijghdl4n2x4hz6j4cg7ctgbuu
|
||||||
@@ -120,7 +126,9 @@ dependencies:
|
|||||||
parse5: 8.0.1
|
parse5: 8.0.1
|
||||||
pinyin-pro: 3.29.3
|
pinyin-pro: 3.29.3
|
||||||
qrcode: 1.5.4
|
qrcode: 1.5.4
|
||||||
|
sherpa-onnx-darwin-x64: 1.13.3
|
||||||
sherpa-onnx-node: 1.13.3
|
sherpa-onnx-node: 1.13.3
|
||||||
|
sherpa-onnx-win-x64: 1.13.4
|
||||||
silk-wasm: 3.7.1
|
silk-wasm: 3.7.1
|
||||||
tailwind-merge: 3.6.0
|
tailwind-merge: 3.6.0
|
||||||
unzipper: 0.12.5
|
unzipper: 0.12.5
|
||||||
@@ -1585,7 +1593,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
|
||||||
|
|
||||||
/@koromix/koffi-freebsd-arm64/3.1.0:
|
/@koromix/koffi-freebsd-arm64/3.1.0:
|
||||||
resolution: {integrity: sha512-vazoPYIhOAlXZksVIqDRMIID4VeUZKx8F3dR90hOobT2ATyOkqNS5dv5UCV7Q7DSq22lQTrdbvENBAhROzCp0w==}
|
resolution: {integrity: sha512-vazoPYIhOAlXZksVIqDRMIID4VeUZKx8F3dR90hOobT2ATyOkqNS5dv5UCV7Q7DSq22lQTrdbvENBAhROzCp0w==}
|
||||||
@@ -1689,6 +1696,36 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@napi-rs/system-ocr-darwin-arm64/1.2.0:
|
||||||
|
resolution: {integrity: sha512-cK8dcDBEl3P4A04xmFJSHEJQxfDytaAIFyDCLqavTp92FVU5plESttWzZsqtTkS81/kzKiBfHyPQffSIndfWbQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@napi-rs/system-ocr-darwin-x64/1.2.0:
|
||||||
|
resolution: {integrity: sha512-u3TBvBGrhmT5Os6AfaxbUEg6VHe8lvrFJNPgThJgshJHyRXUx/wCfTyOroJ22KdVCP5AE4GpwS5tFHMb6p6iaQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@napi-rs/system-ocr-win32-arm64-msvc/1.2.0:
|
||||||
|
resolution: {integrity: sha512-7ej8uMvmXomw3NXo5gZ5p2Nl6UKsHI+VRU3ELv0mhcxR0sJ6wFifYTu5bJrM1TGcz1/RsaX+TjWMmsDq8vriKQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@napi-rs/system-ocr-win32-x64-msvc/1.2.0:
|
||||||
|
resolution: {integrity: sha512-oOoCj3FPWDVctTxx98vMBiMI6m51U+w7SMmMefvmtpcpLelzZ/zYTqdwtWZFAjShaHO+RdaKkcpeVcQuBQiVbA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@napi-rs/system-ocr/1.2.0:
|
/@napi-rs/system-ocr/1.2.0:
|
||||||
resolution: {integrity: sha512-r0f2xNH6U+sth44qF+lUP+2WuHSGUBAry5KSCNuaLDGRbgslFqeROr/qJJ/fb6AjBp3Ov+CJP5MdrOWoaoM3cw==}
|
resolution: {integrity: sha512-r0f2xNH6U+sth44qF+lUP+2WuHSGUBAry5KSCNuaLDGRbgslFqeROr/qJJ/fb6AjBp3Ov+CJP5MdrOWoaoM3cw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
@@ -1699,34 +1736,6 @@ packages:
|
|||||||
'@napi-rs/system-ocr-win32-x64-msvc': 1.2.0
|
'@napi-rs/system-ocr-win32-x64-msvc': 1.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@napi-rs/system-ocr-darwin-arm64/1.2.0:
|
|
||||||
resolution: {integrity: sha512-cK8dcDBEl3P4A04xmFJSHEJQxfDytaAIFyDCLqavTp92FVU5plESttWzZsqtTkS81/kzKiBfHyPQffSIndfWbQ==}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
|
||||||
engines: {node: '>= 10'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@napi-rs/system-ocr-darwin-x64/1.2.0:
|
|
||||||
resolution: {integrity: sha512-u3TBvBGrhmT5Os6AfaxbUEg6VHe8lvrFJNPgThJgshJHyRXUx/wCfTyOroJ22KdVCP5AE4GpwS5tFHMb6p6iaQ==}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
|
||||||
engines: {node: '>= 10'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@napi-rs/system-ocr-win32-arm64-msvc/1.2.0:
|
|
||||||
resolution: {integrity: sha512-7ej8uMvmXomw3NXo5gZ5p2Nl6UKsHI+VRU3ELv0mhcxR0sJ6wFifYTu5bJrM1TGcz1/RsaX+TjWMmsDq8vriKQ==}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [win32]
|
|
||||||
engines: {node: '>= 10'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@napi-rs/system-ocr-win32-x64-msvc/1.2.0:
|
|
||||||
resolution: {integrity: sha512-oOoCj3FPWDVctTxx98vMBiMI6m51U+w7SMmMefvmtpcpLelzZ/zYTqdwtWZFAjShaHO+RdaKkcpeVcQuBQiVbA==}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [win32]
|
|
||||||
engines: {node: '>= 10'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@nodelib/fs.scandir/2.1.5:
|
/@nodelib/fs.scandir/2.1.5:
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -7589,7 +7598,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
|
||||||
|
|
||||||
/sherpa-onnx-linux-arm64/1.13.4:
|
/sherpa-onnx-linux-arm64/1.13.4:
|
||||||
resolution: {integrity: sha512-RMjMRqT82BgTXypNNGmLe6ZFYhc3WEvnAGl3DdkK7qB/kuXwkL3iHhV31wAecbnWPsnEpUoD+8cFovWSBzsCuw==}
|
resolution: {integrity: sha512-RMjMRqT82BgTXypNNGmLe6ZFYhc3WEvnAGl3DdkK7qB/kuXwkL3iHhV31wAecbnWPsnEpUoD+8cFovWSBzsCuw==}
|
||||||
@@ -7628,7 +7636,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
|
||||||
|
|
||||||
/side-channel-list/1.0.0:
|
/side-channel-list/1.0.0:
|
||||||
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
||||||
|
|||||||
+134
-18
@@ -125,13 +125,62 @@ function validateSystemOcrRuntime(runtimeResources, platform, arch) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* koffi 运行期按 `${process.platform}-${process.arch}` 拼出原生包目录名
|
||||||
|
* (node_modules/koffi/src/koffi/index.cjs:153/175),找不到就直接抛
|
||||||
|
* "Cannot find the native Koffi module; did you bundle it correctly?"。
|
||||||
|
* pnpm 7 不支持 supportedArchitectures,会静默跳过外平台可选依赖,所以每个目标平台的
|
||||||
|
* koffi 原生包都必须在 package.json 里显式声明;这里再兜一层,缺了就让构建失败,
|
||||||
|
* 而不是发出一个装得上、却打不开 WCDB 的包。
|
||||||
|
*/
|
||||||
|
function koffiNativeTarget(platform, arch) {
|
||||||
|
if (platform === 'win32') {
|
||||||
|
return arch === 'x64'
|
||||||
|
? { label: 'Windows', segments: ['@koromix', 'koffi-win32-x64', 'win32_x64', 'koffi.node'] }
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
if (platform === 'darwin' && (arch === 'x64' || arch === 'arm64')) {
|
||||||
|
return {
|
||||||
|
label: 'macOS',
|
||||||
|
segments: ['@koromix', `koffi-darwin-${arch}`, `darwin_${arch}`, 'koffi.node']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateKoffiRuntime(runtimeResources, platform, arch) {
|
||||||
|
const target = koffiNativeTarget(platform, arch)
|
||||||
|
if (!target) return
|
||||||
|
const nativePath = path.join(
|
||||||
|
runtimeResources,
|
||||||
|
'app.asar.unpacked',
|
||||||
|
'node_modules',
|
||||||
|
...target.segments
|
||||||
|
)
|
||||||
|
if (!existsSync(nativePath)) {
|
||||||
|
throw new Error(`Missing ${target.label} Koffi native module: ${nativePath}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeBuilderArch(arch) {
|
function normalizeBuilderArch(arch) {
|
||||||
if (typeof arch === 'string') return arch
|
if (typeof arch === 'string') return arch
|
||||||
return { 0: 'ia32', 1: 'x64', 2: 'armv7l', 3: 'arm64', 4: 'universal' }[arch] || String(arch)
|
return { 0: 'ia32', 1: 'x64', 2: 'armv7l', 3: 'arm64', 4: 'universal' }[arch] || String(arch)
|
||||||
}
|
}
|
||||||
|
|
||||||
function runCodesign(args) {
|
function runCodesign(args) {
|
||||||
execFileSync('/usr/bin/codesign', args, { stdio: 'ignore' })
|
try {
|
||||||
|
execFileSync('/usr/bin/codesign', args, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe']
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
const stderr =
|
||||||
|
error && typeof error === 'object' && 'stderr' in error ? String(error.stderr) : ''
|
||||||
|
if (stderr.trim() && error instanceof Error) {
|
||||||
|
error.message += `\n${stderr.trim()}`
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMacosCodeValid(targetPath, run = runCodesign) {
|
function isMacosCodeValid(targetPath, run = runCodesign) {
|
||||||
@@ -174,8 +223,89 @@ function signMacosHelpers(runtimeResources, run = runCodesign) {
|
|||||||
return helperPaths
|
return helperPaths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* codesign 只把这些位置当作「嵌套代码」并要求它们先各自签好,才肯签外层 app。
|
||||||
|
* 只遍历这一组根目录,而不是整个 bundle:Contents/Resources 下的
|
||||||
|
* app.asar.unpacked 里成千上万个原生文件不属于嵌套代码,逐个签既慢又无意义。
|
||||||
|
*/
|
||||||
|
const MACOS_CODE_LOCATIONS = [
|
||||||
|
'Frameworks',
|
||||||
|
'MacOS',
|
||||||
|
'PlugIns',
|
||||||
|
'XPCServices',
|
||||||
|
'Helpers',
|
||||||
|
'Library/LoginItems'
|
||||||
|
]
|
||||||
|
|
||||||
|
function collectNestedMacosCode(dir, depth, targets) {
|
||||||
|
let entries
|
||||||
|
try {
|
||||||
|
entries = readdirSync(dir, { withFileTypes: true })
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryPath = path.join(dir, entry.name)
|
||||||
|
// framework 里的 Mantle -> Versions/Current/Mantle 这类符号链接指向真实文件,
|
||||||
|
// 真实文件会在更深的层级被走到;这里跳过以免重复签名。
|
||||||
|
if (entry.isSymbolicLink()) continue
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
if (/\.(app|framework|xpc)$/.test(entry.name)) {
|
||||||
|
targets.push({ path: entryPath, depth, bundle: true })
|
||||||
|
}
|
||||||
|
collectNestedMacosCode(entryPath, depth + 1, targets)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!entry.isFile()) continue
|
||||||
|
if (readBinaryArchitectures(entryPath).length === 0) continue
|
||||||
|
targets.push({ path: entryPath, depth, bundle: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回嵌套代码的签名顺序:深度大的先签(framework 内部的 dylib、无扩展名的
|
||||||
|
* crashpad handler 先于 framework 本身,helper 的可执行文件先于 helper app),
|
||||||
|
* 同深度时文件先于 bundle。
|
||||||
|
*/
|
||||||
|
function findNestedMacosCodePaths(appBundlePath) {
|
||||||
|
const targets = []
|
||||||
|
for (const location of MACOS_CODE_LOCATIONS) {
|
||||||
|
const root = path.join(appBundlePath, 'Contents', ...location.split('/'))
|
||||||
|
if (existsSync(root)) collectNestedMacosCode(root, 1, targets)
|
||||||
|
}
|
||||||
|
return targets
|
||||||
|
.map((target, index) => ({ ...target, index }))
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.depth !== b.depth) return b.depth - a.depth
|
||||||
|
if (a.bundle !== b.bundle) return a.bundle ? 1 : -1
|
||||||
|
return a.index - b.index
|
||||||
|
})
|
||||||
|
.map((target) => target.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Electron 43.1.0 的 darwin-x64 官方 zip(sha256 与上游 SHASUMS256.txt 一致)
|
||||||
|
* 里所有嵌套 Mach-O 都是未签名状态,darwin-arm64 那份则是 linker-signed。
|
||||||
|
* codesign 签外层 bundle 时要求子组件已签,否则直接报
|
||||||
|
* "code object is not signed at all" + "In subcomponent: ...",
|
||||||
|
* 所以 x64 出包时只签外层必然失败,必须先由内向外补签一遍。
|
||||||
|
*
|
||||||
|
* 这里不采用 `--deep`(Apple 已标记 deprecated):它会把外层的签名选项套用到
|
||||||
|
* 所有子组件上,将来接上 Developer ID + entitlements 时会把 app 的 entitlements
|
||||||
|
* 一并套到 helper 上,属于已知的坑。
|
||||||
|
*/
|
||||||
function signMacosAppBundle(appBundlePath, run = runCodesign) {
|
function signMacosAppBundle(appBundlePath, run = runCodesign) {
|
||||||
if (isMacosCodeValid(appBundlePath, run)) return appBundlePath
|
if (isMacosCodeValid(appBundlePath, run)) return appBundlePath
|
||||||
|
for (const nestedPath of findNestedMacosCodePaths(appBundlePath)) {
|
||||||
|
try {
|
||||||
|
run(['--force', '--sign', '-', nestedPath])
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
'macOS nested code signing failed: ' + path.relative(appBundlePath, nestedPath),
|
||||||
|
{ cause: error }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
run(['--force', '--sign', '-', appBundlePath])
|
run(['--force', '--sign', '-', appBundlePath])
|
||||||
try {
|
try {
|
||||||
run(['--verify', '--strict', appBundlePath])
|
run(['--verify', '--strict', appBundlePath])
|
||||||
@@ -401,6 +531,7 @@ exports.default = async function afterPack(context) {
|
|||||||
)
|
)
|
||||||
validateSherpaRuntime(runtimeResources, context.electronPlatformName, arch)
|
validateSherpaRuntime(runtimeResources, context.electronPlatformName, arch)
|
||||||
validateSystemOcrRuntime(runtimeResources, context.electronPlatformName, arch)
|
validateSystemOcrRuntime(runtimeResources, context.electronPlatformName, arch)
|
||||||
|
validateKoffiRuntime(runtimeResources, context.electronPlatformName, arch)
|
||||||
pruneIntelMacKeyTool(runtimeResources, context.electronPlatformName, arch)
|
pruneIntelMacKeyTool(runtimeResources, context.electronPlatformName, arch)
|
||||||
pruneForeignArchConnectors(runtimeResources, context.electronPlatformName, arch)
|
pruneForeignArchConnectors(runtimeResources, context.electronPlatformName, arch)
|
||||||
pruneForeignArchNativeRuntimes(runtimeResources, context.electronPlatformName, arch)
|
pruneForeignArchNativeRuntimes(runtimeResources, context.electronPlatformName, arch)
|
||||||
@@ -413,23 +544,6 @@ exports.default = async function afterPack(context) {
|
|||||||
const productName = context.packager.appInfo.productFilename
|
const productName = context.packager.appInfo.productFilename
|
||||||
signMacosAppBundle(path.join(context.appOutDir, productName + '.app'))
|
signMacosAppBundle(path.join(context.appOutDir, productName + '.app'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.electronPlatformName === 'win32') {
|
|
||||||
const koffiNative = path.join(
|
|
||||||
context.appOutDir,
|
|
||||||
'resources',
|
|
||||||
'app.asar.unpacked',
|
|
||||||
'node_modules',
|
|
||||||
'@koromix',
|
|
||||||
'koffi-win32-x64',
|
|
||||||
'win32_x64',
|
|
||||||
'koffi.node'
|
|
||||||
)
|
|
||||||
if (!existsSync(koffiNative)) {
|
|
||||||
throw new Error(`Missing Windows Koffi native module: ${koffiNative}`)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getRuntimeResources = getRuntimeResources
|
exports.getRuntimeResources = getRuntimeResources
|
||||||
@@ -439,6 +553,7 @@ exports.validateFfmpegRuntime = validateFfmpegRuntime
|
|||||||
exports.validateSilkWasmRuntime = validateSilkWasmRuntime
|
exports.validateSilkWasmRuntime = validateSilkWasmRuntime
|
||||||
exports.validateSherpaRuntime = validateSherpaRuntime
|
exports.validateSherpaRuntime = validateSherpaRuntime
|
||||||
exports.validateSystemOcrRuntime = validateSystemOcrRuntime
|
exports.validateSystemOcrRuntime = validateSystemOcrRuntime
|
||||||
|
exports.validateKoffiRuntime = validateKoffiRuntime
|
||||||
exports.pruneIntelMacKeyTool = pruneIntelMacKeyTool
|
exports.pruneIntelMacKeyTool = pruneIntelMacKeyTool
|
||||||
exports.pruneForeignArchConnectors = pruneForeignArchConnectors
|
exports.pruneForeignArchConnectors = pruneForeignArchConnectors
|
||||||
exports.pruneForeignArchNativeRuntimes = pruneForeignArchNativeRuntimes
|
exports.pruneForeignArchNativeRuntimes = pruneForeignArchNativeRuntimes
|
||||||
@@ -447,6 +562,7 @@ exports.findMacosHelperPaths = findMacosHelperPaths
|
|||||||
exports.isMacosCodeValid = isMacosCodeValid
|
exports.isMacosCodeValid = isMacosCodeValid
|
||||||
exports.signMacosHelpers = signMacosHelpers
|
exports.signMacosHelpers = signMacosHelpers
|
||||||
exports.signMacosAppBundle = signMacosAppBundle
|
exports.signMacosAppBundle = signMacosAppBundle
|
||||||
|
exports.findNestedMacosCodePaths = findNestedMacosCodePaths
|
||||||
exports.sendRuntimeLocations = sendRuntimeLocations
|
exports.sendRuntimeLocations = sendRuntimeLocations
|
||||||
exports.findSendRuntime = findSendRuntime
|
exports.findSendRuntime = findSendRuntime
|
||||||
exports.enforceSendRuntimeBoundary = enforceSendRuntimeBoundary
|
exports.enforceSendRuntimeBoundary = enforceSendRuntimeBoundary
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import { createRequire } from 'module'
|
||||||
|
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'fs'
|
||||||
|
import { tmpdir } from 'os'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { afterAll, describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
const nodeRequire = createRequire(import.meta.url)
|
||||||
|
const { findNestedMacosCodePaths, signMacosAppBundle } = nodeRequire(
|
||||||
|
'../../scripts/after-pack.cjs'
|
||||||
|
) as {
|
||||||
|
findNestedMacosCodePaths: (appBundlePath: string) => string[]
|
||||||
|
signMacosAppBundle: (appBundlePath: string, run?: (args: string[]) => void) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'wxe-after-pack-sign-'))
|
||||||
|
|
||||||
|
/** 最小可用的 64 位 thin Mach-O 头,足以让 readBinaryArchitectures 判出 x64。 */
|
||||||
|
function macho(): Buffer {
|
||||||
|
const buffer = Buffer.alloc(32)
|
||||||
|
buffer.writeUInt32LE(0xfeedfacf, 0)
|
||||||
|
buffer.writeUInt32LE(0x01000007, 4)
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
function file(...segments: string[]): string {
|
||||||
|
const target = join(root, ...segments)
|
||||||
|
mkdirSync(join(target, '..'), { recursive: true })
|
||||||
|
writeFileSync(target, macho())
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 复刻 Electron 43.1.0 darwin-x64 的未签名 bundle 形状。 */
|
||||||
|
function fixtureApp(): string {
|
||||||
|
const app = join(root, 'TraceMemo.app')
|
||||||
|
file('TraceMemo.app', 'Contents', 'MacOS', 'TraceMemo')
|
||||||
|
file('TraceMemo.app', 'Contents', 'Frameworks', 'Mantle.framework', 'Versions', 'A', 'Mantle')
|
||||||
|
file(
|
||||||
|
'TraceMemo.app',
|
||||||
|
'Contents',
|
||||||
|
'Frameworks',
|
||||||
|
'Electron Framework.framework',
|
||||||
|
'Versions',
|
||||||
|
'A',
|
||||||
|
'Electron Framework'
|
||||||
|
)
|
||||||
|
file(
|
||||||
|
'TraceMemo.app',
|
||||||
|
'Contents',
|
||||||
|
'Frameworks',
|
||||||
|
'Electron Framework.framework',
|
||||||
|
'Versions',
|
||||||
|
'A',
|
||||||
|
'Libraries',
|
||||||
|
'libffmpeg.dylib'
|
||||||
|
)
|
||||||
|
file(
|
||||||
|
'TraceMemo.app',
|
||||||
|
'Contents',
|
||||||
|
'Frameworks',
|
||||||
|
'Electron Framework.framework',
|
||||||
|
'Versions',
|
||||||
|
'A',
|
||||||
|
'Helpers',
|
||||||
|
'chrome_crashpad_handler'
|
||||||
|
)
|
||||||
|
file('TraceMemo.app', 'Contents', 'Frameworks', 'Helper.app', 'Contents', 'MacOS', 'Helper')
|
||||||
|
// framework 内指向 Versions/A 的符号链接:真实文件在更深层级被走到,这里要跳过。
|
||||||
|
symlinkSync(
|
||||||
|
'A',
|
||||||
|
join(
|
||||||
|
root,
|
||||||
|
'TraceMemo.app',
|
||||||
|
'Contents',
|
||||||
|
'Frameworks',
|
||||||
|
'Mantle.framework',
|
||||||
|
'Versions',
|
||||||
|
'Current'
|
||||||
|
),
|
||||||
|
'dir'
|
||||||
|
)
|
||||||
|
// Contents/Resources 下的原生文件不是「嵌套代码」,不参与签名。
|
||||||
|
file(
|
||||||
|
'TraceMemo.app',
|
||||||
|
'Contents',
|
||||||
|
'Resources',
|
||||||
|
'app.asar.unpacked',
|
||||||
|
'node_modules',
|
||||||
|
'sherpa-onnx-darwin-x64',
|
||||||
|
'sherpa-onnx.node'
|
||||||
|
)
|
||||||
|
// 非原生文件必须被忽略。
|
||||||
|
writeFileSync(join(root, 'TraceMemo.app', 'Contents', 'Frameworks', 'README.md'), 'not a binary')
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = fixtureApp()
|
||||||
|
const relative = (target: string): string => target.slice(app.length + 1)
|
||||||
|
|
||||||
|
describe('macOS nested code signing order', () => {
|
||||||
|
afterAll(() => rmSync(root, { recursive: true, force: true }))
|
||||||
|
|
||||||
|
it('signs nested code inside-out and ignores resources and symlinks', () => {
|
||||||
|
const paths = findNestedMacosCodePaths(app).map(relative)
|
||||||
|
|
||||||
|
expect(paths).toContain(join('Contents', 'MacOS', 'TraceMemo'))
|
||||||
|
expect(paths).not.toContain(app)
|
||||||
|
expect(paths.some((entry) => entry.includes('Resources'))).toBe(false)
|
||||||
|
expect(paths.some((entry) => entry.endsWith('.md'))).toBe(false)
|
||||||
|
expect(paths.some((entry) => entry.includes('Versions/Current'))).toBe(false)
|
||||||
|
|
||||||
|
const index = (needle: string): number => paths.indexOf(needle)
|
||||||
|
const handler =
|
||||||
|
'Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler'
|
||||||
|
const frameworkBinary =
|
||||||
|
'Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework'
|
||||||
|
const framework = 'Contents/Frameworks/Electron Framework.framework'
|
||||||
|
const helperBinary = 'Contents/Frameworks/Helper.app/Contents/MacOS/Helper'
|
||||||
|
const helperApp = 'Contents/Frameworks/Helper.app'
|
||||||
|
|
||||||
|
expect(index(handler)).toBeGreaterThanOrEqual(0)
|
||||||
|
// 内层可执行文件先于其所属 bundle,helper 的可执行文件先于 helper app。
|
||||||
|
expect(index(handler)).toBeLessThan(index(framework))
|
||||||
|
expect(index(frameworkBinary)).toBeLessThan(index(framework))
|
||||||
|
expect(index(helperBinary)).toBeLessThan(index(helperApp))
|
||||||
|
// 最深的目标排在最前。
|
||||||
|
expect(paths[0]).toBe(handler)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('re-signs the app bundle after every nested target', () => {
|
||||||
|
const calls: string[][] = []
|
||||||
|
let verifyCalls = 0
|
||||||
|
const run = (args: string[]): void => {
|
||||||
|
if (args[0] === '--verify') {
|
||||||
|
verifyCalls += 1
|
||||||
|
// 未签名来源包:外层首次校验必然失败,补签之后才允许通过。
|
||||||
|
if (verifyCalls === 1) throw new Error('code object is not signed at all')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
calls.push(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
signMacosAppBundle(app, run)
|
||||||
|
|
||||||
|
const signed = calls.map((args) => args[args.length - 1])
|
||||||
|
expect(signed[signed.length - 1]).toBe(app)
|
||||||
|
expect(signed.slice(0, -1)).toEqual(findNestedMacosCodePaths(app))
|
||||||
|
expect(verifyCalls).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('leaves an already valid bundle untouched', () => {
|
||||||
|
const calls: string[][] = []
|
||||||
|
const run = (args: string[]): void => {
|
||||||
|
calls.push(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
signMacosAppBundle(app, run)
|
||||||
|
|
||||||
|
// 只有首次 --verify,没有任何 --sign。
|
||||||
|
expect(calls).toEqual([['--verify', '--strict', app]])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -22,18 +22,26 @@ const { hasWindowsSherpaRuntime } = nodeRequire('../../scripts/prepare-win-runti
|
|||||||
const {
|
const {
|
||||||
validateAsarRuntimeDependencies,
|
validateAsarRuntimeDependencies,
|
||||||
validateFfmpegRuntime,
|
validateFfmpegRuntime,
|
||||||
|
validateKoffiRuntime,
|
||||||
validateReaderSkillRuntime,
|
validateReaderSkillRuntime,
|
||||||
validateSherpaRuntime,
|
validateSherpaRuntime,
|
||||||
validateSilkWasmRuntime,
|
validateSilkWasmRuntime,
|
||||||
|
validateSystemOcrRuntime,
|
||||||
findMacosHelperPaths,
|
findMacosHelperPaths,
|
||||||
signMacosHelpers,
|
signMacosHelpers,
|
||||||
signMacosAppBundle
|
signMacosAppBundle
|
||||||
} = nodeRequire('../../scripts/after-pack.cjs') as {
|
} = nodeRequire('../../scripts/after-pack.cjs') as {
|
||||||
validateAsarRuntimeDependencies: (runtimeResources: string) => void
|
validateAsarRuntimeDependencies: (runtimeResources: string) => void
|
||||||
validateFfmpegRuntime: (runtimeResources: string, platform?: NodeJS.Platform) => void
|
validateFfmpegRuntime: (runtimeResources: string, platform?: NodeJS.Platform) => void
|
||||||
|
validateKoffiRuntime: (runtimeResources: string, platform: NodeJS.Platform, arch: string) => void
|
||||||
validateReaderSkillRuntime: (runtimeResources: string) => string
|
validateReaderSkillRuntime: (runtimeResources: string) => string
|
||||||
validateSherpaRuntime: (runtimeResources: string, platform: NodeJS.Platform, arch: string) => void
|
validateSherpaRuntime: (runtimeResources: string, platform: NodeJS.Platform, arch: string) => void
|
||||||
validateSilkWasmRuntime: (runtimeResources: string) => void
|
validateSilkWasmRuntime: (runtimeResources: string) => void
|
||||||
|
validateSystemOcrRuntime: (
|
||||||
|
runtimeResources: string,
|
||||||
|
platform: NodeJS.Platform,
|
||||||
|
arch: string
|
||||||
|
) => void
|
||||||
findMacosHelperPaths: (runtimeResources: string) => string[]
|
findMacosHelperPaths: (runtimeResources: string) => string[]
|
||||||
signMacosHelpers: (runtimeResources: string, run?: CodesignRunner) => string[]
|
signMacosHelpers: (runtimeResources: string, run?: CodesignRunner) => string[]
|
||||||
signMacosAppBundle: (appBundlePath: string, run?: CodesignRunner) => string
|
signMacosAppBundle: (appBundlePath: string, run?: CodesignRunner) => string
|
||||||
@@ -265,6 +273,91 @@ describe('production runtime packaging', () => {
|
|||||||
expect(config).toContain('node_modules/sherpa-onnx-*/**')
|
expect(config).toContain('node_modules/sherpa-onnx-*/**')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('requires the matching System OCR native runtime', () => {
|
||||||
|
const resources = join(root, 'system-ocr-resources')
|
||||||
|
const modules = join(resources, 'app.asar.unpacked', 'node_modules', '@napi-rs')
|
||||||
|
const base = join(modules, 'system-ocr')
|
||||||
|
|
||||||
|
expect(() => validateSystemOcrRuntime(resources, 'darwin', 'arm64')).toThrow(
|
||||||
|
/Missing unpacked System OCR runtime:.*system-ocr/
|
||||||
|
)
|
||||||
|
|
||||||
|
mkdirSync(base, { recursive: true })
|
||||||
|
writeFileSync(join(base, 'package.json'), '{}')
|
||||||
|
writeFileSync(join(base, 'index.js'), 'module.exports = {}')
|
||||||
|
|
||||||
|
const mac = join(modules, 'system-ocr-darwin-arm64')
|
||||||
|
mkdirSync(mac, { recursive: true })
|
||||||
|
writeFileSync(join(mac, 'package.json'), '{}')
|
||||||
|
writeFileSync(join(mac, 'system-ocr.darwin-arm64.node'), 'fixture')
|
||||||
|
expect(() => validateSystemOcrRuntime(resources, 'darwin', 'arm64')).not.toThrow()
|
||||||
|
|
||||||
|
// Windows 的原生包名带 -msvc 后缀,查找规则必须跟着改。
|
||||||
|
expect(() => validateSystemOcrRuntime(resources, 'win32', 'x64')).toThrow(/win32-x64-msvc/)
|
||||||
|
const windows = join(modules, 'system-ocr-win32-x64-msvc')
|
||||||
|
mkdirSync(windows, { recursive: true })
|
||||||
|
writeFileSync(join(windows, 'package.json'), '{}')
|
||||||
|
writeFileSync(join(windows, 'system-ocr.win32-x64-msvc.node'), 'fixture')
|
||||||
|
expect(() => validateSystemOcrRuntime(resources, 'win32', 'x64')).not.toThrow()
|
||||||
|
|
||||||
|
// Linux 不是 supported target,不应做硬校验。
|
||||||
|
expect(() => validateSystemOcrRuntime(resources, 'linux', 'x64')).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('requires the koffi native module for the packaged platform', () => {
|
||||||
|
const resources = join(root, 'koffi-resources')
|
||||||
|
const modules = join(resources, 'app.asar.unpacked', 'node_modules', '@koromix')
|
||||||
|
|
||||||
|
expect(() => validateKoffiRuntime(resources, 'darwin', 'x64')).toThrow(
|
||||||
|
/Missing macOS Koffi native module:.*koffi-darwin-x64/
|
||||||
|
)
|
||||||
|
expect(() => validateKoffiRuntime(resources, 'darwin', 'arm64')).toThrow(
|
||||||
|
/koffi-darwin-arm64[/\\]darwin_arm64[/\\]koffi\.node/
|
||||||
|
)
|
||||||
|
expect(() => validateKoffiRuntime(resources, 'win32', 'x64')).toThrow(
|
||||||
|
/Missing Windows Koffi native module:.*koffi-win32-x64/
|
||||||
|
)
|
||||||
|
|
||||||
|
// koffi 运行期按 `${platform}-${arch}` 拼目录名,darwin 用 darwin_<arch>,
|
||||||
|
// win32 用 win32_x64(见 node_modules/koffi/src/koffi/index.cjs)。
|
||||||
|
for (const segments of [
|
||||||
|
['koffi-darwin-x64', 'darwin_x64'],
|
||||||
|
['koffi-darwin-arm64', 'darwin_arm64'],
|
||||||
|
['koffi-win32-x64', 'win32_x64']
|
||||||
|
]) {
|
||||||
|
const nativeDirectory = join(modules, ...segments)
|
||||||
|
mkdirSync(nativeDirectory, { recursive: true })
|
||||||
|
writeFileSync(join(nativeDirectory, 'koffi.node'), 'fixture')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => validateKoffiRuntime(resources, 'darwin', 'x64')).not.toThrow()
|
||||||
|
expect(() => validateKoffiRuntime(resources, 'darwin', 'arm64')).not.toThrow()
|
||||||
|
expect(() => validateKoffiRuntime(resources, 'win32', 'x64')).not.toThrow()
|
||||||
|
|
||||||
|
// 没有对应原生包的组合应静默跳过,而不是误报。
|
||||||
|
expect(() => validateKoffiRuntime(resources, 'linux', 'x64')).not.toThrow()
|
||||||
|
expect(() => validateKoffiRuntime(resources, 'win32', 'arm64')).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('declares the cross-arch native runtimes pnpm 7 would otherwise skip', () => {
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
readFileSync(resolve(__dirname, '../../package.json'), 'utf8')
|
||||||
|
) as { dependencies: Record<string, string> }
|
||||||
|
|
||||||
|
// pnpm 7.33.7 不支持 supportedArchitectures,非宿主平台的可选依赖会被静默跳过,
|
||||||
|
// 而这些原生包必须在 dependencies 里显式声明,否则打包阶段才在 afterPack 报缺。
|
||||||
|
for (const name of [
|
||||||
|
'sherpa-onnx-darwin-x64',
|
||||||
|
'sherpa-onnx-win-x64',
|
||||||
|
'@napi-rs/system-ocr-darwin-x64',
|
||||||
|
'@napi-rs/system-ocr-win32-x64-msvc',
|
||||||
|
'@koromix/koffi-darwin-x64',
|
||||||
|
'@koromix/koffi-win32-x64'
|
||||||
|
]) {
|
||||||
|
expect(packageJson.dependencies).toHaveProperty(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('finds only the macOS helpers that exist in packaged resources', () => {
|
it('finds only the macOS helpers that exist in packaged resources', () => {
|
||||||
const resources = join(root, 'helper-detect-resources', 'resources')
|
const resources = join(root, 'helper-detect-resources', 'resources')
|
||||||
mkdirSync(resources, { recursive: true })
|
mkdirSync(resources, { recursive: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user