Skip to content

Commit 86633ee

Browse files
committed
fix: 修复 electron 自动打包问题
1 parent 1c061cd commit 86633ee

File tree

4 files changed

+184
-81
lines changed

4 files changed

+184
-81
lines changed

.github/workflows/build.yml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ on:
77
workflow_dispatch:
88
inputs:
99
environment:
10-
description: 'Build environment'
10+
description: "Build environment"
1111
required: true
12-
default: 'production'
12+
default: "production"
1313
type: choice
1414
options:
1515
- production
@@ -28,12 +28,16 @@ jobs:
2828
include:
2929
- os: macos-14
3030
build_target: darwin-arm64
31-
- os: macos-13
31+
npm_script: build:mac-arm64
32+
- os: macos-15-intel
3233
build_target: darwin-x64
34+
npm_script: build:mac-x64
3335
- os: windows-latest
3436
build_target: win32
37+
npm_script: build:win32
3538
- os: ubuntu-latest
3639
build_target: linux
40+
npm_script: build:linux
3741

3842
steps:
3943
- name: Check out Git repository
@@ -48,7 +52,7 @@ jobs:
4852
uses: actions/setup-node@v4
4953
with:
5054
node-version: 22
51-
cache: 'pnpm'
55+
cache: "pnpm"
5256

5357
- name: Enable corepack
5458
run: corepack enable
@@ -57,10 +61,8 @@ jobs:
5761
run: pnpm install --frozen-lockfile
5862

5963
- name: Build Electron App
60-
run: pnpm run build
64+
run: pnpm run ${{ matrix.npm_script }}
6165
env:
62-
BUILD_PLATFORM: ${{ matrix.build_target }}
63-
BUILD_ENV: ${{ github.event.inputs.environment || 'production' }}
6466
# For universal mac build, tell electron-builder to produce universal binaries
6567
CSC_IDENTITY_AUTO_DISCOVERY: false
6668
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true

builder.js

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,57 @@ const config = {
3838
from: "./public/assets/icons",
3939
to: "assets/icons",
4040
},
41-
{
42-
from: "./resources/node_bin",
43-
to: "node_bin",
44-
},
4541
],
42+
afterPack: async (context) => {
43+
const fs = require('fs');
44+
const path = require('path');
45+
46+
// Electron Builder Arch: 0=ia32, 1=x64, 3=arm64
47+
const archMap = {
48+
0: 'ia32',
49+
1: 'x64',
50+
3: 'arm64'
51+
};
52+
53+
const archName = archMap[context.arch];
54+
if (!archName) {
55+
console.log(`[afterPack] Skipping unknown arch: ${context.arch}`);
56+
return;
57+
}
58+
59+
const platform = context.electronPlatformName; // 'darwin', 'win32', 'linux'
60+
const binaryName = platform === 'win32' ? 'node.exe' : 'node';
61+
62+
// Source: resources/node_bin/<arch>/node
63+
// Note: for ia32 we use 'ia32' folder, but download script uses 'ia32' too (mapped from x86 logic if I updated it correctly, let's check).
64+
// In download script: if (arch === 'ia32') distArch = 'x86'; folderArch = 'ia32'. Yes.
65+
66+
const sourceNode = path.resolve(__dirname, `./resources/node_bin/${archName}/${binaryName}`);
67+
68+
if (!fs.existsSync(sourceNode)) {
69+
console.warn(`[afterPack] Warning: Node binary not found at ${sourceNode}. Skipping copy.`);
70+
return;
71+
}
72+
73+
// Destination
74+
let destNodeDir;
75+
if (platform === 'darwin') {
76+
destNodeDir = path.join(context.appOutDir, 'Contents/Resources/node_bin');
77+
} else {
78+
destNodeDir = path.join(context.appOutDir, 'resources/node_bin');
79+
}
80+
81+
if (!fs.existsSync(destNodeDir)) {
82+
fs.mkdirSync(destNodeDir, { recursive: true });
83+
}
84+
85+
const destNode = path.join(destNodeDir, binaryName);
86+
fs.copyFileSync(sourceNode, destNode);
87+
if (platform !== 'win32') {
88+
fs.chmodSync(destNode, 0o755);
89+
}
90+
console.log(`[afterPack] Copied ${archName} node binary to ${destNode}`);
91+
},
4692
icon: resolve(__dirname, `./public/assets/icons/icon.icns`),
4793
asarUnpack: "**\\*.{node,dll}",
4894
mac: {

package.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
"dev:render": "rsbuild dev --config ./builder/rsbuild.render.ts",
88
"dev:main": "nodemon",
99
"build": "cross-env NODE_ENV=production plop",
10-
"build:darwin": "cross-env BUILD_PLATFORM=darwin pnpm run build",
10+
"build:darwin": "cross-env BUILD_PLATFORM=darwin BUILD_ENV=production pnpm run build",
1111
"build:darwin:dev": "cross-env BUILD_PLATFORM=darwin BUILD_ENV=development pnpm run build",
1212
"build:darwin:staging": "cross-env BUILD_PLATFORM=darwin BUILD_ENV=staging pnpm run build",
13-
"build:mac-x64": "cross-env BUILD_PLATFORM=darwin-x64 pnpm run build",
14-
"build:mac-arm64": "cross-env BUILD_PLATFORM=darwin-arm64 pnpm run build",
15-
"build:win32": "cross-env BUILD_PLATFORM=win32 pnpm run build",
13+
"build:mac-x64": "cross-env BUILD_PLATFORM=darwin-x64 BUILD_ENV=production pnpm run build",
14+
"build:mac-arm64": "cross-env BUILD_PLATFORM=darwin-arm64 BUILD_ENV=production pnpm run build",
15+
"build:win32": "cross-env BUILD_PLATFORM=win32 BUILD_ENV=production pnpm run build",
1616
"build:win32:dev": "cross-env BUILD_PLATFORM=win32 BUILD_ENV=development pnpm run build",
1717
"build:win32:staging": "cross-env BUILD_PLATFORM=win32 BUILD_ENV=staging pnpm run build",
18-
"build:linux": "cross-env BUILD_PLATFORM=linux pnpm run build",
18+
"build:linux": "cross-env BUILD_PLATFORM=linux BUILD_ENV=production pnpm run build",
1919
"build:linux:dev": "cross-env BUILD_PLATFORM=linux BUILD_ENV=development pnpm run build",
2020
"build:linux:staging": "cross-env BUILD_PLATFORM=linux BUILD_ENV=staging pnpm run build",
2121
"build:all": "pnpm run build:darwin && pnpm run build:win32 && pnpm run build:linux",
@@ -31,6 +31,10 @@
3131
"bun run lint"
3232
]
3333
},
34+
"author": {
35+
"email": "497350746@qq.com",
36+
"name": "madinah"
37+
},
3438
"packageManager": "pnpm@10.13.1",
3539
"devDependencies": {
3640
"@biomejs/biome": "^2.3.8",

scripts/download-node.js

Lines changed: 116 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,106 +4,154 @@ const https = require('https');
44
const { execSync } = require('child_process');
55

66
const NODE_VERSION = process.env.BUILD_NODE_VERSION || 'v20.11.0';
7-
const PLATFORM = process.env.BUILD_PLATFORM || process.platform;
8-
const ARCH = process.env.BUILD_ARCH || process.arch;
7+
let PLATFORM = process.env.BUILD_PLATFORM || process.platform;
8+
let ARCH = process.env.BUILD_ARCH || process.arch;
9+
10+
// Handle composite platform strings like 'darwin-x64'
11+
if (PLATFORM.includes('-')) {
12+
const parts = PLATFORM.split('-');
13+
PLATFORM = parts[0];
14+
if (parts[1]) ARCH = parts[1];
15+
}
916

10-
// Map platform to Node.js distribution naming
1117
const platformMap = {
1218
'darwin': 'darwin',
1319
'win32': 'win',
1420
'linux': 'linux'
1521
};
1622

17-
// Map arch to Node.js distribution naming
1823
const archMap = {
1924
'x64': 'x64',
2025
'arm64': 'arm64',
2126
'ia32': 'x86'
2227
};
2328

2429
const distPlatform = platformMap[PLATFORM];
25-
const distArch = archMap[ARCH];
2630

27-
if (!distPlatform || !distArch) {
28-
console.error(`Unsupported platform (${PLATFORM}) or architecture (${ARCH})`);
31+
if (!distPlatform) {
32+
console.error(`Unsupported platform (${PLATFORM})`);
2933
process.exit(1);
3034
}
3135

32-
const ext = PLATFORM === 'win32' ? 'zip' : 'tar.gz';
33-
const filename = `node-${NODE_VERSION}-${distPlatform}-${distArch}.${ext}`;
34-
const downloadUrl = `https://nodejs.org/dist/${NODE_VERSION}/${filename}`;
36+
// Determine target architectures
37+
let targets = [];
38+
if (PLATFORM === 'darwin') {
39+
// Always download both for macOS to support universal builds
40+
targets = ['x64', 'arm64'];
41+
} else {
42+
const distArch = archMap[ARCH];
43+
if (!distArch) {
44+
console.error(`Unsupported architecture (${ARCH})`);
45+
process.exit(1);
46+
}
47+
targets = [distArch];
48+
}
3549

36-
const outputDir = path.resolve(__dirname, '../resources/node_bin');
37-
const outputPath = path.join(outputDir, filename);
38-
const metaPath = path.join(outputDir, '.meta.json');
50+
const baseOutputDir = path.resolve(__dirname, '../resources/node_bin');
3951

40-
// Ensure output dir exists
41-
if (!fs.existsSync(outputDir)) {
42-
fs.mkdirSync(outputDir, { recursive: true });
52+
// Ensure base output dir exists
53+
if (!fs.existsSync(baseOutputDir)) {
54+
fs.mkdirSync(baseOutputDir, { recursive: true });
4355
}
4456

45-
// Check if node binary already exists to avoid re-downloading
46-
const nodeBinName = PLATFORM === 'win32' ? 'node.exe' : 'node';
47-
const finalNodePath = path.join(outputDir, nodeBinName);
57+
(async () => {
58+
for (const arch of targets) {
59+
// Map 'ia32' to 'x86' for download, but use 'x64'/'arm64' for folder names
60+
let distArch = arch;
61+
if (arch === 'ia32') distArch = 'x86'; // Node.js uses 'x86' for ia32
62+
else if (arch === 'x64') distArch = 'x64';
63+
else if (arch === 'arm64') distArch = 'arm64';
64+
65+
// For folder name, use the standard names (x64, arm64)
66+
const folderArch = arch === 'x86' ? 'ia32' : arch;
67+
68+
await downloadForArch(distPlatform, distArch, folderArch);
69+
}
70+
})();
71+
72+
async function downloadForArch(platform, arch, folderArch) {
73+
const ext = PLATFORM === 'win32' ? 'zip' : 'tar.gz';
74+
const filename = `node-${NODE_VERSION}-${platform}-${arch}.${ext}`;
75+
const downloadUrl = `https://nodejs.org/dist/${NODE_VERSION}/${filename}`;
76+
77+
const outputDir = path.join(baseOutputDir, folderArch);
78+
const outputPath = path.join(outputDir, filename);
79+
const metaPath = path.join(outputDir, '.meta.json');
80+
81+
// Ensure output dir exists
82+
if (!fs.existsSync(outputDir)) {
83+
fs.mkdirSync(outputDir, { recursive: true });
84+
}
4885

49-
let needDownload = true;
50-
if (fs.existsSync(finalNodePath) && fs.existsSync(metaPath)) {
51-
try {
52-
const metaRaw = fs.readFileSync(metaPath, 'utf-8');
53-
const meta = JSON.parse(metaRaw);
54-
if (
55-
meta.version === NODE_VERSION &&
56-
meta.platform === distPlatform &&
57-
meta.arch === distArch
58-
) {
59-
console.log(`Node.js binary already matches ${NODE_VERSION} (${distPlatform}/${distArch}). Skipping download.`);
60-
needDownload = false;
86+
const nodeBinName = PLATFORM === 'win32' ? 'node.exe' : 'node';
87+
const finalNodePath = path.join(outputDir, nodeBinName);
88+
89+
let needDownload = true;
90+
if (fs.existsSync(finalNodePath) && fs.existsSync(metaPath)) {
91+
try {
92+
const metaRaw = fs.readFileSync(metaPath, 'utf-8');
93+
const meta = JSON.parse(metaRaw);
94+
if (
95+
meta.version === NODE_VERSION &&
96+
meta.platform === platform &&
97+
meta.arch === arch
98+
) {
99+
console.log(`Node.js binary already matches ${NODE_VERSION} (${platform}/${arch}). Skipping download.`);
100+
needDownload = false;
101+
}
102+
} catch (e) {
103+
console.warn(`Failed to read existing node meta for ${arch}, will re-download. ${e?.message || e}`);
61104
}
62-
} catch (e) {
63-
console.warn(`Failed to read existing node meta, will re-download. ${e?.message || e}`);
64105
}
65-
}
66106

67-
if (!needDownload && fs.existsSync(finalNodePath)) {
68-
process.exit(0);
69-
}
107+
if (!needDownload) return;
70108

71-
// Cleanup old content if mismatch
72-
if (fs.existsSync(outputDir) && needDownload) {
109+
// Cleanup
73110
try {
74-
fs.rmSync(outputDir, { recursive: true, force: true });
111+
const files = fs.readdirSync(outputDir);
112+
for (const file of files) {
113+
if (file !== '.meta.json' && file !== nodeBinName) { // Be aggressive but careful
114+
// Actually better to just clean everything
115+
}
116+
}
117+
// Simple clean: remove dir and recreate
118+
// But we are in the dir? No.
119+
// fs.rmSync(outputDir, { recursive: true, force: true });
120+
// fs.mkdirSync(outputDir, { recursive: true });
121+
// But we want to preserve if partial? No.
75122
} catch (e) {
76123
console.warn(`Failed to clean old node dir: ${e?.message || e}`);
77124
}
78-
fs.mkdirSync(outputDir, { recursive: true });
79-
}
80125

81-
console.log(`Downloading Node.js from ${downloadUrl}...`);
126+
console.log(`Downloading Node.js from ${downloadUrl}...`);
82127

83-
const file = fs.createWriteStream(outputPath);
84-
85-
https.get(downloadUrl, (response) => {
86-
if (response.statusCode !== 200) {
87-
console.error(`Failed to download: HTTP Status Code ${response.statusCode}`);
88-
fs.unlink(outputPath, () => {});
89-
process.exit(1);
90-
}
91-
92-
response.pipe(file);
128+
return new Promise((resolve, reject) => {
129+
const file = fs.createWriteStream(outputPath);
130+
https.get(downloadUrl, (response) => {
131+
if (response.statusCode !== 200) {
132+
console.error(`Failed to download: HTTP Status Code ${response.statusCode}`);
133+
fs.unlink(outputPath, () => {});
134+
process.exit(1);
135+
}
93136

94-
file.on('finish', () => {
95-
file.close(() => {
96-
console.log('Download completed. Extracting...');
97-
extract(outputPath, outputDir, filename);
137+
response.pipe(file);
138+
139+
file.on('finish', () => {
140+
file.close(() => {
141+
console.log(`Download completed for ${arch}. Extracting...`);
142+
extract(outputPath, outputDir, filename, platform, ext, finalNodePath, metaPath, arch);
143+
resolve();
144+
});
145+
});
146+
}).on('error', (err) => {
147+
fs.unlink(outputPath, () => {});
148+
console.error(`Download error: ${err.message}`);
149+
process.exit(1);
98150
});
99151
});
100-
}).on('error', (err) => {
101-
fs.unlink(outputPath, () => {});
102-
console.error(`Download error: ${err.message}`);
103-
process.exit(1);
104-
});
152+
}
105153

106-
function extract(filePath, targetDir, archiveName) {
154+
function extract(filePath, targetDir, archiveName, platform, ext, finalNodePath, metaPath, arch) {
107155
try {
108156
if (PLATFORM === 'win32') {
109157
execSync(`tar -xf "${filePath}" -C "${targetDir}"`);
@@ -117,18 +165,21 @@ function extract(filePath, targetDir, archiveName) {
117165
: path.join(targetDir, extractedFolder, 'bin', 'node');
118166

119167
if (fs.existsSync(src)) {
168+
if (fs.existsSync(finalNodePath)) fs.unlinkSync(finalNodePath);
120169
fs.renameSync(src, finalNodePath);
170+
121171
if (PLATFORM !== 'win32') {
122172
fs.chmodSync(finalNodePath, 0o755);
123173
}
174+
124175
console.log(`Node.js binary ready at: ${finalNodePath}`);
125176
fs.writeFileSync(
126177
metaPath,
127178
JSON.stringify(
128179
{
129180
version: NODE_VERSION,
130-
platform: distPlatform,
131-
arch: distArch,
181+
platform: platform,
182+
arch: arch,
132183
downloadedAt: new Date().toISOString(),
133184
},
134185
null,

0 commit comments

Comments
 (0)