Skip to content

Commit 7aa8376

Browse files
committed
🧑‍💻 Supports code signing for Electron.app in node_modules in the macOS development environment
1 parent 809060f commit 7aa8376

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"gen:types": "tsc -d",
2222
"start": "cross-env NODE_ENV=development electron ./electron/main.js",
2323
"install:electron": "cross-env ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ node node_modules/electron/install.js",
24+
"sign:dev": "node scripts/signDevElectron.js",
2425
"dist": "cross-env ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --config electron-builder.yml --publish=never",
2526
"dist-arm64": "cross-env ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --arm64 --config electron-builder-arm64.yml --publish=never",
2627
"dist-darwin": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --mac --config electron-builder-darwin.yml --publish=never",

app/scripts/signDevElectron.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* macOS 开发环境:为 node_modules 中的 Electron.app 代码签名。
3+
*
4+
* 背景:Electron 42+ 在 macOS 上使用 UNNotification,要求应用具备有效代码签名;
5+
* 刚安装的 Electron.app 仅有 linker-signed 占位签名,开发时通知会静默失败。
6+
*
7+
* 用法(在 app 目录下):
8+
* pnpm run install:electron # 安装 / 更新 Electron 二进制
9+
* pnpm run sign:dev # macOS 开发者手动签名,供本地通知等功能使用
10+
*
11+
* 非 macOS 平台直接跳过。无可用证书时仅打印提示并以退出码 0 结束。
12+
* 可通过环境变量 SIYUAN_DEV_SIGN_IDENTITY 或 CSC_NAME 指定证书名称。
13+
*/
14+
const {execFileSync} = require("child_process");
15+
const fs = require("fs");
16+
const path = require("path");
17+
18+
const PREFERRED_IDENTITY_PATTERNS = [
19+
/^Apple Development:/,
20+
/^Developer ID Application:/,
21+
/^Mac Developer:/,
22+
];
23+
24+
function resolveElectronApp() {
25+
let electronPkg;
26+
try {
27+
electronPkg = require.resolve("electron/package.json");
28+
} catch {
29+
return null;
30+
}
31+
const appPath = path.join(path.dirname(electronPkg), "dist", "Electron.app");
32+
return fs.existsSync(appPath) ? appPath : null;
33+
}
34+
35+
function listCodeSignIdentities() {
36+
let output;
37+
try {
38+
output = execFileSync("security", ["find-identity", "-v", "-p", "codesigning"], {
39+
encoding: "utf8",
40+
});
41+
} catch {
42+
return [];
43+
}
44+
const identities = [];
45+
for (const line of output.split("\n")) {
46+
// 1) ABCD1234 "Apple Development: name (TEAMID)"
47+
const match = line.match(/^\s*\d+\)\s+[0-9A-F]+\s+"(.+)"\s*$/);
48+
if (match) {
49+
identities.push(match[1]);
50+
}
51+
}
52+
return identities;
53+
}
54+
55+
function pickIdentity(identities) {
56+
const envIdentity = process.env.SIYUAN_DEV_SIGN_IDENTITY || process.env.CSC_NAME;
57+
if (envIdentity) {
58+
if (identities.includes(envIdentity)) {
59+
return envIdentity;
60+
}
61+
console.warn(`sign:dev: identity not found: ${envIdentity}, auto-selecting`);
62+
}
63+
for (const pattern of PREFERRED_IDENTITY_PATTERNS) {
64+
const found = identities.find((name) => pattern.test(name));
65+
if (found) {
66+
return found;
67+
}
68+
}
69+
return identities[0] || null;
70+
}
71+
72+
function getCodesignInfo(appPath) {
73+
try {
74+
const result = require("child_process").spawnSync("codesign", ["-dvv", appPath], {encoding: "utf8"});
75+
return (result.stdout || "") + (result.stderr || "");
76+
} catch {
77+
return "";
78+
}
79+
}
80+
81+
function isAlreadySigned(appPath) {
82+
const output = getCodesignInfo(appPath);
83+
return !/linker-signed/.test(output) && /Authority=/.test(output);
84+
}
85+
86+
function main() {
87+
if (process.platform !== "darwin") {
88+
return;
89+
}
90+
91+
const appPath = resolveElectronApp();
92+
if (!appPath) {
93+
console.warn("sign:dev: Electron.app not found, run install:electron first");
94+
return;
95+
}
96+
97+
const identities = listCodeSignIdentities();
98+
const identity = pickIdentity(identities);
99+
if (!identity) {
100+
console.warn("sign:dev: no codesigning identity (macOS notifications may not work)");
101+
console.warn("sign:dev: sign in with Apple ID in Xcode, or set SIYUAN_DEV_SIGN_IDENTITY");
102+
return;
103+
}
104+
105+
if (isAlreadySigned(appPath)) {
106+
console.log(`sign:dev: already signed (${identity})`);
107+
return;
108+
}
109+
110+
console.log(`sign:dev: signing with ${identity}`);
111+
execFileSync("codesign", ["--force", "--deep", "--sign", identity, appPath], {stdio: "inherit"});
112+
console.log("sign:dev: done");
113+
}
114+
115+
main();

0 commit comments

Comments
 (0)