electrobun 0.0.19-beta.6 → 0.0.19-beta.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BUILD.md +90 -0
- package/bin/electrobun.cjs +165 -0
- package/package.json +13 -7
- package/src/cli/index.ts +544 -142
- package/bin/electrobun +0 -0
package/src/cli/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
mkdirSync,
|
|
8
8
|
createWriteStream,
|
|
9
9
|
unlinkSync,
|
|
10
|
+
readdirSync,
|
|
10
11
|
} from "fs";
|
|
11
12
|
import { execSync } from "child_process";
|
|
12
13
|
import tar from "tar";
|
|
@@ -18,7 +19,8 @@ const MAX_CHUNK_SIZE = 1024 * 2;
|
|
|
18
19
|
|
|
19
20
|
// TODO: dedup with built.ts
|
|
20
21
|
const OS: 'win' | 'linux' | 'macos' = getPlatform();
|
|
21
|
-
|
|
22
|
+
// Always use x64 for Windows since we only build x64 Windows binaries
|
|
23
|
+
const ARCH: 'arm64' | 'x64' = OS === 'win' ? 'x64' : getArch();
|
|
22
24
|
|
|
23
25
|
function getPlatform() {
|
|
24
26
|
switch (platform()) {
|
|
@@ -63,41 +65,81 @@ const ELECTROBUN_DEP_PATH = join(projectRoot, "node_modules", "electrobun");
|
|
|
63
65
|
// When debugging electrobun with the example app use the builds (dev or release) right from the source folder
|
|
64
66
|
// For developers using electrobun cli via npm use the release versions in /dist
|
|
65
67
|
// This lets us not have to commit src build folders to git and provide pre-built binaries
|
|
66
|
-
const PATHS = {
|
|
67
|
-
BUN_BINARY: join(ELECTROBUN_DEP_PATH, "dist", "bun") + binExt,
|
|
68
|
-
LAUNCHER_DEV: join(ELECTROBUN_DEP_PATH, "dist", "electrobun") + binExt,
|
|
69
|
-
LAUNCHER_RELEASE: join(ELECTROBUN_DEP_PATH, "dist", "launcher") + binExt,
|
|
70
|
-
MAIN_JS: join(ELECTROBUN_DEP_PATH, "dist", "main.js"),
|
|
71
|
-
NATIVE_WRAPPER_MACOS: join(
|
|
72
|
-
ELECTROBUN_DEP_PATH,
|
|
73
|
-
"dist",
|
|
74
|
-
"libNativeWrapper.dylib"
|
|
75
|
-
),
|
|
76
|
-
NATIVE_WRAPPER_WIN: join(ELECTROBUN_DEP_PATH, "dist", "libNativeWrapper.dll"),
|
|
77
|
-
NATIVE_WRAPPER_LINUX: join(ELECTROBUN_DEP_PATH, "dist", "libNativeWrapper.so"),
|
|
78
|
-
WEBVIEW2LOADER_WIN: join(ELECTROBUN_DEP_PATH, "dist", "WebView2Loader.dll"),
|
|
79
|
-
BSPATCH: join(ELECTROBUN_DEP_PATH, "dist", "bspatch") + binExt,
|
|
80
|
-
EXTRACTOR: join(ELECTROBUN_DEP_PATH, "dist", "extractor") + binExt,
|
|
81
|
-
BSDIFF: join(ELECTROBUN_DEP_PATH, "dist", "bsdiff") + binExt,
|
|
82
|
-
CEF_FRAMEWORK_MACOS: join(
|
|
83
|
-
ELECTROBUN_DEP_PATH,
|
|
84
|
-
"dist",
|
|
85
|
-
"cef",
|
|
86
|
-
"Chromium Embedded Framework.framework"
|
|
87
|
-
),
|
|
88
|
-
CEF_HELPER_MACOS: join(ELECTROBUN_DEP_PATH, "dist", "cef", "process_helper"),
|
|
89
|
-
CEF_HELPER_WIN: join(ELECTROBUN_DEP_PATH, "dist", "cef", "process_helper.exe"),
|
|
90
|
-
CEF_HELPER_LINUX: join(ELECTROBUN_DEP_PATH, "dist", "cef", "process_helper"),
|
|
91
|
-
CEF_DIR: join(ELECTROBUN_DEP_PATH, "dist", "cef"),
|
|
92
|
-
};
|
|
93
68
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
69
|
+
// Function to get platform-specific paths
|
|
70
|
+
function getPlatformPaths(targetOS: 'macos' | 'win' | 'linux', targetArch: 'arm64' | 'x64') {
|
|
71
|
+
const binExt = targetOS === 'win' ? '.exe' : '';
|
|
72
|
+
const platformDistDir = join(ELECTROBUN_DEP_PATH, `dist-${targetOS}-${targetArch}`);
|
|
73
|
+
const sharedDistDir = join(ELECTROBUN_DEP_PATH, "dist");
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
// Platform-specific binaries (from dist-OS-ARCH/)
|
|
77
|
+
BUN_BINARY: join(platformDistDir, "bun") + binExt,
|
|
78
|
+
LAUNCHER_DEV: join(platformDistDir, "electrobun") + binExt,
|
|
79
|
+
LAUNCHER_RELEASE: join(platformDistDir, "launcher") + binExt,
|
|
80
|
+
NATIVE_WRAPPER_MACOS: join(platformDistDir, "libNativeWrapper.dylib"),
|
|
81
|
+
NATIVE_WRAPPER_WIN: join(platformDistDir, "libNativeWrapper.dll"),
|
|
82
|
+
NATIVE_WRAPPER_LINUX: join(platformDistDir, "libNativeWrapper.so"),
|
|
83
|
+
NATIVE_WRAPPER_LINUX_CEF: join(platformDistDir, "libNativeWrapper_cef.so"),
|
|
84
|
+
WEBVIEW2LOADER_WIN: join(platformDistDir, "WebView2Loader.dll"),
|
|
85
|
+
BSPATCH: join(platformDistDir, "bspatch") + binExt,
|
|
86
|
+
EXTRACTOR: join(platformDistDir, "extractor") + binExt,
|
|
87
|
+
BSDIFF: join(platformDistDir, "bsdiff") + binExt,
|
|
88
|
+
CEF_FRAMEWORK_MACOS: join(platformDistDir, "cef", "Chromium Embedded Framework.framework"),
|
|
89
|
+
CEF_HELPER_MACOS: join(platformDistDir, "cef", "process_helper"),
|
|
90
|
+
CEF_HELPER_WIN: join(platformDistDir, "cef", "process_helper.exe"),
|
|
91
|
+
CEF_HELPER_LINUX: join(platformDistDir, "cef", "process_helper"),
|
|
92
|
+
CEF_DIR: join(platformDistDir, "cef"),
|
|
93
|
+
|
|
94
|
+
// Shared platform-independent files (from dist/)
|
|
95
|
+
// These work with existing package.json and development workflow
|
|
96
|
+
MAIN_JS: join(sharedDistDir, "main.js"),
|
|
97
|
+
API_DIR: join(sharedDistDir, "api"),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Default PATHS for host platform (backward compatibility)
|
|
102
|
+
const PATHS = getPlatformPaths(OS, ARCH);
|
|
103
|
+
|
|
104
|
+
async function ensureCoreDependencies(targetOS?: 'macos' | 'win' | 'linux', targetArch?: 'arm64' | 'x64') {
|
|
105
|
+
// Use provided target platform or default to host platform
|
|
106
|
+
const platformOS = targetOS || OS;
|
|
107
|
+
const platformArch = targetArch || ARCH;
|
|
108
|
+
|
|
109
|
+
// Get platform-specific paths
|
|
110
|
+
const platformPaths = getPlatformPaths(platformOS, platformArch);
|
|
111
|
+
|
|
112
|
+
// Check platform-specific binaries
|
|
113
|
+
const requiredBinaries = [
|
|
114
|
+
platformPaths.BUN_BINARY,
|
|
115
|
+
platformPaths.LAUNCHER_RELEASE,
|
|
116
|
+
// Platform-specific native wrapper
|
|
117
|
+
platformOS === 'macos' ? platformPaths.NATIVE_WRAPPER_MACOS :
|
|
118
|
+
platformOS === 'win' ? platformPaths.NATIVE_WRAPPER_WIN :
|
|
119
|
+
platformPaths.NATIVE_WRAPPER_LINUX
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
// Check shared files (main.js should be in shared dist/)
|
|
123
|
+
const requiredSharedFiles = [
|
|
124
|
+
platformPaths.MAIN_JS
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const missingBinaries = requiredBinaries.filter(file => !existsSync(file));
|
|
128
|
+
const missingSharedFiles = requiredSharedFiles.filter(file => !existsSync(file));
|
|
129
|
+
|
|
130
|
+
// If only shared files are missing, that's expected in production (they come via npm)
|
|
131
|
+
if (missingBinaries.length === 0 && missingSharedFiles.length > 0) {
|
|
132
|
+
console.log(`Shared files missing (expected in production): ${missingSharedFiles.map(f => f.replace(ELECTROBUN_DEP_PATH, '.')).join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Only download if platform-specific binaries are missing
|
|
136
|
+
if (missingBinaries.length === 0) {
|
|
97
137
|
return;
|
|
98
138
|
}
|
|
99
139
|
|
|
100
|
-
|
|
140
|
+
// Show which binaries are missing
|
|
141
|
+
console.log(`Core dependencies not found for ${platformOS}-${platformArch}. Missing files:`, missingBinaries.map(f => f.replace(ELECTROBUN_DEP_PATH, '.')).join(', '));
|
|
142
|
+
console.log(`Downloading core binaries for ${platformOS}-${platformArch}...`);
|
|
101
143
|
|
|
102
144
|
// Get the current Electrobun version from package.json
|
|
103
145
|
const packageJsonPath = join(ELECTROBUN_DEP_PATH, 'package.json');
|
|
@@ -112,21 +154,21 @@ async function ensureCoreDependencies() {
|
|
|
112
154
|
}
|
|
113
155
|
}
|
|
114
156
|
|
|
115
|
-
const platformName =
|
|
116
|
-
const archName =
|
|
117
|
-
const
|
|
157
|
+
const platformName = platformOS === 'macos' ? 'darwin' : platformOS === 'win' ? 'win' : 'linux';
|
|
158
|
+
const archName = platformArch;
|
|
159
|
+
const coreTarballUrl = `https://github.com/blackboardsh/electrobun/releases/download/${version}/electrobun-core-${platformName}-${archName}.tar.gz`;
|
|
118
160
|
|
|
119
|
-
console.log(`Downloading core binaries from: ${
|
|
161
|
+
console.log(`Downloading core binaries from: ${coreTarballUrl}`);
|
|
120
162
|
|
|
121
163
|
try {
|
|
122
|
-
// Download
|
|
123
|
-
const response = await fetch(
|
|
164
|
+
// Download core binaries tarball
|
|
165
|
+
const response = await fetch(coreTarballUrl);
|
|
124
166
|
if (!response.ok) {
|
|
125
167
|
throw new Error(`Failed to download binaries: ${response.status} ${response.statusText}`);
|
|
126
168
|
}
|
|
127
169
|
|
|
128
170
|
// Create temp file
|
|
129
|
-
const tempFile = join(ELECTROBUN_DEP_PATH,
|
|
171
|
+
const tempFile = join(ELECTROBUN_DEP_PATH, `core-${platformOS}-${platformArch}-temp.tar.gz`);
|
|
130
172
|
const fileStream = createWriteStream(tempFile);
|
|
131
173
|
|
|
132
174
|
// Write response to file
|
|
@@ -140,33 +182,102 @@ async function ensureCoreDependencies() {
|
|
|
140
182
|
}
|
|
141
183
|
fileStream.end();
|
|
142
184
|
|
|
143
|
-
// Extract to dist directory
|
|
144
|
-
console.log(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
185
|
+
// Extract to platform-specific dist directory
|
|
186
|
+
console.log(`Extracting core dependencies for ${platformOS}-${platformArch}...`);
|
|
187
|
+
const platformDistPath = join(ELECTROBUN_DEP_PATH, `dist-${platformOS}-${platformArch}`);
|
|
188
|
+
mkdirSync(platformDistPath, { recursive: true });
|
|
189
|
+
|
|
190
|
+
// Use Windows native tar.exe on Windows due to npm tar library issues
|
|
191
|
+
if (OS === 'win') {
|
|
192
|
+
console.log('Using Windows native tar.exe for reliable extraction...');
|
|
193
|
+
execSync(`tar -xf "${tempFile}" -C "${platformDistPath}"`, {
|
|
194
|
+
stdio: 'inherit',
|
|
195
|
+
cwd: platformDistPath
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
await tar.x({
|
|
199
|
+
file: tempFile,
|
|
200
|
+
cwd: platformDistPath,
|
|
201
|
+
preservePaths: false,
|
|
202
|
+
strip: 0,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// NOTE: We no longer copy main.js from platform-specific downloads
|
|
207
|
+
// Platform-specific downloads should only contain native binaries
|
|
208
|
+
// main.js and api/ should be shipped via npm in the shared dist/ folder
|
|
149
209
|
|
|
150
210
|
// Clean up temp file
|
|
151
211
|
unlinkSync(tempFile);
|
|
152
212
|
|
|
153
|
-
|
|
213
|
+
// Debug: List what was actually extracted
|
|
214
|
+
try {
|
|
215
|
+
const extractedFiles = readdirSync(platformDistPath);
|
|
216
|
+
console.log(`Extracted files to ${platformDistPath}:`, extractedFiles);
|
|
217
|
+
|
|
218
|
+
// Check if files are in subdirectories
|
|
219
|
+
for (const file of extractedFiles) {
|
|
220
|
+
const filePath = join(platformDistPath, file);
|
|
221
|
+
const stat = require('fs').statSync(filePath);
|
|
222
|
+
if (stat.isDirectory()) {
|
|
223
|
+
const subFiles = readdirSync(filePath);
|
|
224
|
+
console.log(` ${file}/: ${subFiles.join(', ')}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
console.error('Could not list extracted files:', e);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Verify extraction completed successfully - check platform-specific binaries only
|
|
232
|
+
const requiredBinaries = [
|
|
233
|
+
platformPaths.BUN_BINARY,
|
|
234
|
+
platformPaths.LAUNCHER_RELEASE,
|
|
235
|
+
platformOS === 'macos' ? platformPaths.NATIVE_WRAPPER_MACOS :
|
|
236
|
+
platformOS === 'win' ? platformPaths.NATIVE_WRAPPER_WIN :
|
|
237
|
+
platformPaths.NATIVE_WRAPPER_LINUX
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
const missingBinaries = requiredBinaries.filter(file => !existsSync(file));
|
|
241
|
+
if (missingBinaries.length > 0) {
|
|
242
|
+
console.error(`Missing binaries after extraction: ${missingBinaries.map(f => f.replace(ELECTROBUN_DEP_PATH, '.')).join(', ')}`);
|
|
243
|
+
console.error('This suggests the tarball structure is different than expected');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// For development: if main.js doesn't exist in shared dist/, copy from platform-specific download as fallback
|
|
247
|
+
const sharedDistPath = join(ELECTROBUN_DEP_PATH, 'dist');
|
|
248
|
+
const extractedMainJs = join(platformDistPath, 'main.js');
|
|
249
|
+
const sharedMainJs = join(sharedDistPath, 'main.js');
|
|
154
250
|
|
|
155
|
-
|
|
156
|
-
|
|
251
|
+
if (existsSync(extractedMainJs) && !existsSync(sharedMainJs)) {
|
|
252
|
+
console.log('Development fallback: copying main.js from platform-specific download to shared dist/');
|
|
253
|
+
mkdirSync(sharedDistPath, { recursive: true });
|
|
254
|
+
cpSync(extractedMainJs, sharedMainJs);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log(`Core dependencies for ${platformOS}-${platformArch} downloaded and cached successfully`);
|
|
258
|
+
|
|
259
|
+
} catch (error: any) {
|
|
260
|
+
console.error(`Failed to download core dependencies for ${platformOS}-${platformArch}:`, error.message);
|
|
157
261
|
console.error('Please ensure you have an internet connection and the release exists.');
|
|
158
262
|
process.exit(1);
|
|
159
263
|
}
|
|
160
264
|
}
|
|
161
265
|
|
|
162
|
-
async function ensureCEFDependencies() {
|
|
266
|
+
async function ensureCEFDependencies(targetOS?: 'macos' | 'win' | 'linux', targetArch?: 'arm64' | 'x64') {
|
|
267
|
+
// Use provided target platform or default to host platform
|
|
268
|
+
const platformOS = targetOS || OS;
|
|
269
|
+
const platformArch = targetArch || ARCH;
|
|
270
|
+
|
|
271
|
+
// Get platform-specific paths
|
|
272
|
+
const platformPaths = getPlatformPaths(platformOS, platformArch);
|
|
273
|
+
|
|
163
274
|
// Check if CEF dependencies already exist
|
|
164
|
-
if (existsSync(
|
|
165
|
-
console.log(
|
|
275
|
+
if (existsSync(platformPaths.CEF_DIR)) {
|
|
276
|
+
console.log(`CEF dependencies found for ${platformOS}-${platformArch}, using cached version`);
|
|
166
277
|
return;
|
|
167
278
|
}
|
|
168
279
|
|
|
169
|
-
console.log(
|
|
280
|
+
console.log(`CEF dependencies not found for ${platformOS}-${platformArch}, downloading...`);
|
|
170
281
|
|
|
171
282
|
// Get the current Electrobun version from package.json
|
|
172
283
|
const packageJsonPath = join(ELECTROBUN_DEP_PATH, 'package.json');
|
|
@@ -181,8 +292,8 @@ async function ensureCEFDependencies() {
|
|
|
181
292
|
}
|
|
182
293
|
}
|
|
183
294
|
|
|
184
|
-
const platformName =
|
|
185
|
-
const archName =
|
|
295
|
+
const platformName = platformOS === 'macos' ? 'darwin' : platformOS === 'win' ? 'win' : 'linux';
|
|
296
|
+
const archName = platformArch;
|
|
186
297
|
const cefTarballUrl = `https://github.com/blackboardsh/electrobun/releases/download/${version}/electrobun-cef-${platformName}-${archName}.tar.gz`;
|
|
187
298
|
|
|
188
299
|
console.log(`Downloading CEF from: ${cefTarballUrl}`);
|
|
@@ -195,7 +306,7 @@ async function ensureCEFDependencies() {
|
|
|
195
306
|
}
|
|
196
307
|
|
|
197
308
|
// Create temp file
|
|
198
|
-
const tempFile = join(ELECTROBUN_DEP_PATH,
|
|
309
|
+
const tempFile = join(ELECTROBUN_DEP_PATH, `cef-${platformOS}-${platformArch}-temp.tar.gz`);
|
|
199
310
|
const fileStream = createWriteStream(tempFile);
|
|
200
311
|
|
|
201
312
|
// Write response to file
|
|
@@ -209,20 +320,49 @@ async function ensureCEFDependencies() {
|
|
|
209
320
|
}
|
|
210
321
|
fileStream.end();
|
|
211
322
|
|
|
212
|
-
// Extract to dist directory
|
|
213
|
-
console.log(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
323
|
+
// Extract to platform-specific dist directory
|
|
324
|
+
console.log(`Extracting CEF dependencies for ${platformOS}-${platformArch}...`);
|
|
325
|
+
const platformDistPath = join(ELECTROBUN_DEP_PATH, `dist-${platformOS}-${platformArch}`);
|
|
326
|
+
mkdirSync(platformDistPath, { recursive: true });
|
|
327
|
+
|
|
328
|
+
// Use Windows native tar.exe on Windows due to npm tar library issues
|
|
329
|
+
if (OS === 'win') {
|
|
330
|
+
console.log('Using Windows native tar.exe for reliable extraction...');
|
|
331
|
+
execSync(`tar -xf "${tempFile}" -C "${platformDistPath}"`, {
|
|
332
|
+
stdio: 'inherit',
|
|
333
|
+
cwd: platformDistPath
|
|
334
|
+
});
|
|
335
|
+
} else {
|
|
336
|
+
await tar.x({
|
|
337
|
+
file: tempFile,
|
|
338
|
+
cwd: platformDistPath,
|
|
339
|
+
preservePaths: false,
|
|
340
|
+
strip: 0,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
218
343
|
|
|
219
344
|
// Clean up temp file
|
|
220
345
|
unlinkSync(tempFile);
|
|
221
346
|
|
|
222
|
-
|
|
347
|
+
// Debug: List what was actually extracted for CEF
|
|
348
|
+
try {
|
|
349
|
+
const extractedFiles = readdirSync(platformDistPath);
|
|
350
|
+
console.log(`CEF extracted files to ${platformDistPath}:`, extractedFiles);
|
|
351
|
+
|
|
352
|
+
// Check if CEF directory was created
|
|
353
|
+
const cefDir = join(platformDistPath, 'cef');
|
|
354
|
+
if (existsSync(cefDir)) {
|
|
355
|
+
const cefFiles = readdirSync(cefDir);
|
|
356
|
+
console.log(`CEF directory contents: ${cefFiles.slice(0, 10).join(', ')}${cefFiles.length > 10 ? '...' : ''}`);
|
|
357
|
+
}
|
|
358
|
+
} catch (e) {
|
|
359
|
+
console.error('Could not list CEF extracted files:', e);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
console.log(`CEF dependencies for ${platformOS}-${platformArch} downloaded and cached successfully`);
|
|
223
363
|
|
|
224
|
-
} catch (error) {
|
|
225
|
-
console.error(
|
|
364
|
+
} catch (error: any) {
|
|
365
|
+
console.error(`Failed to download CEF dependencies for ${platformOS}-${platformArch}:`, error.message);
|
|
226
366
|
console.error('Please ensure you have an internet connection and the release exists.');
|
|
227
367
|
process.exit(1);
|
|
228
368
|
}
|
|
@@ -257,6 +397,7 @@ const defaultConfig = {
|
|
|
257
397
|
build: {
|
|
258
398
|
buildFolder: "build",
|
|
259
399
|
artifactFolder: "artifacts",
|
|
400
|
+
targets: undefined, // Will default to current platform if not specified
|
|
260
401
|
mac: {
|
|
261
402
|
codesign: false,
|
|
262
403
|
notarize: false,
|
|
@@ -267,6 +408,10 @@ const defaultConfig = {
|
|
|
267
408
|
},
|
|
268
409
|
icons: "icon.iconset",
|
|
269
410
|
},
|
|
411
|
+
bun: {
|
|
412
|
+
entrypoint: "src/bun/index.ts",
|
|
413
|
+
external: [],
|
|
414
|
+
},
|
|
270
415
|
},
|
|
271
416
|
scripts: {
|
|
272
417
|
postBuild: "",
|
|
@@ -286,7 +431,10 @@ if (!command) {
|
|
|
286
431
|
const config = getConfig();
|
|
287
432
|
|
|
288
433
|
const envArg =
|
|
289
|
-
process.argv.find((arg) => arg.startsWith("env="))?.split("=")[1] || "";
|
|
434
|
+
process.argv.find((arg) => arg.startsWith("--env="))?.split("=")[1] || "";
|
|
435
|
+
|
|
436
|
+
const targetsArg =
|
|
437
|
+
process.argv.find((arg) => arg.startsWith("--targets="))?.split("=")[1] || "";
|
|
290
438
|
|
|
291
439
|
const validEnvironments = ["dev", "canary", "stable"];
|
|
292
440
|
|
|
@@ -294,8 +442,175 @@ const validEnvironments = ["dev", "canary", "stable"];
|
|
|
294
442
|
const buildEnvironment: "dev" | "canary" | "stable" =
|
|
295
443
|
validEnvironments.includes(envArg) ? envArg : "dev";
|
|
296
444
|
|
|
445
|
+
// Determine build targets
|
|
446
|
+
type BuildTarget = { os: 'macos' | 'win' | 'linux', arch: 'arm64' | 'x64' };
|
|
447
|
+
|
|
448
|
+
function parseBuildTargets(): BuildTarget[] {
|
|
449
|
+
// If explicit targets provided via CLI
|
|
450
|
+
if (targetsArg) {
|
|
451
|
+
if (targetsArg === 'current') {
|
|
452
|
+
return [{ os: OS, arch: ARCH }];
|
|
453
|
+
} else if (targetsArg === 'all') {
|
|
454
|
+
return parseConfigTargets();
|
|
455
|
+
} else {
|
|
456
|
+
// Parse comma-separated targets like "macos-arm64,win-x64"
|
|
457
|
+
return targetsArg.split(',').map(target => {
|
|
458
|
+
const [os, arch] = target.trim().split('-') as [string, string];
|
|
459
|
+
if (!['macos', 'win', 'linux'].includes(os) || !['arm64', 'x64'].includes(arch)) {
|
|
460
|
+
console.error(`Invalid target: ${target}. Format should be: os-arch (e.g., macos-arm64)`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
return { os, arch } as BuildTarget;
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Default behavior: always build for current platform only
|
|
469
|
+
// This ensures predictable, fast builds unless explicitly requesting multi-platform
|
|
470
|
+
return [{ os: OS, arch: ARCH }];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function parseConfigTargets(): BuildTarget[] {
|
|
474
|
+
// If config has targets, use them
|
|
475
|
+
if (config.build.targets && config.build.targets.length > 0) {
|
|
476
|
+
return config.build.targets.map(target => {
|
|
477
|
+
if (target === 'current') {
|
|
478
|
+
return { os: OS, arch: ARCH };
|
|
479
|
+
}
|
|
480
|
+
const [os, arch] = target.split('-') as [string, string];
|
|
481
|
+
if (!['macos', 'win', 'linux'].includes(os) || !['arm64', 'x64'].includes(arch)) {
|
|
482
|
+
console.error(`Invalid target in config: ${target}. Format should be: os-arch (e.g., macos-arm64)`);
|
|
483
|
+
process.exit(1);
|
|
484
|
+
}
|
|
485
|
+
return { os, arch } as BuildTarget;
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// If no config targets and --targets=all, use all available platforms
|
|
490
|
+
if (targetsArg === 'all') {
|
|
491
|
+
console.log('No targets specified in config, using all available platforms');
|
|
492
|
+
return [
|
|
493
|
+
{ os: 'macos', arch: 'arm64' },
|
|
494
|
+
{ os: 'macos', arch: 'x64' },
|
|
495
|
+
{ os: 'win', arch: 'x64' },
|
|
496
|
+
{ os: 'linux', arch: 'x64' },
|
|
497
|
+
{ os: 'linux', arch: 'arm64' }
|
|
498
|
+
];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Default to current platform
|
|
502
|
+
return [{ os: OS, arch: ARCH }];
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const buildTargets = parseBuildTargets();
|
|
506
|
+
|
|
507
|
+
// Show build targets to user
|
|
508
|
+
if (buildTargets.length === 1) {
|
|
509
|
+
console.log(`Building for ${buildTargets[0].os}-${buildTargets[0].arch} (${buildEnvironment})`);
|
|
510
|
+
} else {
|
|
511
|
+
const targetList = buildTargets.map(t => `${t.os}-${t.arch}`).join(', ');
|
|
512
|
+
console.log(`Building for multiple targets: ${targetList} (${buildEnvironment})`);
|
|
513
|
+
console.log(`Running ${buildTargets.length} parallel builds...`);
|
|
514
|
+
|
|
515
|
+
// Spawn parallel build processes
|
|
516
|
+
const buildPromises = buildTargets.map(async (target) => {
|
|
517
|
+
const targetString = `${target.os}-${target.arch}`;
|
|
518
|
+
const prefix = `[${targetString}]`;
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
// Try to find the electrobun binary in node_modules/.bin or use bunx
|
|
522
|
+
const electrobunBin = join(projectRoot, 'node_modules', '.bin', 'electrobun');
|
|
523
|
+
let command: string[];
|
|
524
|
+
|
|
525
|
+
if (existsSync(electrobunBin)) {
|
|
526
|
+
command = [electrobunBin, 'build', `--env=${buildEnvironment}`, `--targets=${targetString}`];
|
|
527
|
+
} else {
|
|
528
|
+
// Fallback to bunx which should resolve node_modules binaries
|
|
529
|
+
command = ['bunx', 'electrobun', 'build', `--env=${buildEnvironment}`, `--targets=${targetString}`];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
console.log(`${prefix} Running:`, command.join(' '));
|
|
533
|
+
|
|
534
|
+
const result = await Bun.spawn(command, {
|
|
535
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
536
|
+
env: process.env,
|
|
537
|
+
cwd: projectRoot // Ensure we're in the right directory
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// Pipe output with prefix
|
|
541
|
+
if (result.stdout) {
|
|
542
|
+
const reader = result.stdout.getReader();
|
|
543
|
+
while (true) {
|
|
544
|
+
const { done, value } = await reader.read();
|
|
545
|
+
if (done) break;
|
|
546
|
+
const text = new TextDecoder().decode(value);
|
|
547
|
+
// Add prefix to each line
|
|
548
|
+
const prefixedText = text.split('\n').map(line =>
|
|
549
|
+
line ? `${prefix} ${line}` : line
|
|
550
|
+
).join('\n');
|
|
551
|
+
process.stdout.write(prefixedText);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (result.stderr) {
|
|
556
|
+
const reader = result.stderr.getReader();
|
|
557
|
+
while (true) {
|
|
558
|
+
const { done, value } = await reader.read();
|
|
559
|
+
if (done) break;
|
|
560
|
+
const text = new TextDecoder().decode(value);
|
|
561
|
+
const prefixedText = text.split('\n').map(line =>
|
|
562
|
+
line ? `${prefix} ${line}` : line
|
|
563
|
+
).join('\n');
|
|
564
|
+
process.stderr.write(prefixedText);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const exitCode = await result.exited;
|
|
569
|
+
return { target, exitCode, success: exitCode === 0 };
|
|
570
|
+
|
|
571
|
+
} catch (error) {
|
|
572
|
+
console.error(`${prefix} Failed to start build:`, error);
|
|
573
|
+
return { target, exitCode: 1, success: false, error };
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Wait for all builds to complete
|
|
578
|
+
const results = await Promise.allSettled(buildPromises);
|
|
579
|
+
|
|
580
|
+
// Report final results
|
|
581
|
+
console.log('\n=== Build Results ===');
|
|
582
|
+
let allSucceeded = true;
|
|
583
|
+
|
|
584
|
+
for (const result of results) {
|
|
585
|
+
if (result.status === 'fulfilled') {
|
|
586
|
+
const { target, success, exitCode } = result.value;
|
|
587
|
+
const status = success ? '✅ SUCCESS' : '❌ FAILED';
|
|
588
|
+
console.log(`${target.os}-${target.arch}: ${status} (exit code: ${exitCode})`);
|
|
589
|
+
if (!success) allSucceeded = false;
|
|
590
|
+
} else {
|
|
591
|
+
console.log(`Build rejected: ${result.reason}`);
|
|
592
|
+
allSucceeded = false;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (!allSucceeded) {
|
|
597
|
+
console.log('\nSome builds failed. Check the output above for details.');
|
|
598
|
+
process.exit(1);
|
|
599
|
+
} else {
|
|
600
|
+
console.log('\nAll builds completed successfully! 🎉');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
process.exit(0);
|
|
604
|
+
}
|
|
605
|
+
|
|
297
606
|
// todo (yoav): dev builds should include the branch name, and/or allow configuration via external config
|
|
298
|
-
|
|
607
|
+
// For now, assume single target build (we'll refactor for multi-target later)
|
|
608
|
+
const currentTarget = buildTargets[0];
|
|
609
|
+
const buildSubFolder = `${buildEnvironment}-${currentTarget.os}-${currentTarget.arch}`;
|
|
610
|
+
|
|
611
|
+
// Use target OS/ARCH for build logic (instead of current machine's OS/ARCH)
|
|
612
|
+
const targetOS = currentTarget.os;
|
|
613
|
+
const targetARCH = currentTarget.arch;
|
|
299
614
|
|
|
300
615
|
const buildFolder = join(projectRoot, config.build.buildFolder, buildSubFolder);
|
|
301
616
|
|
|
@@ -370,7 +685,7 @@ const appFileName = (
|
|
|
370
685
|
)
|
|
371
686
|
.replace(/\s/g, "")
|
|
372
687
|
.replace(/\./g, "-");
|
|
373
|
-
const bundleFileName =
|
|
688
|
+
const bundleFileName = targetOS === 'macos' ? `${appFileName}.app` : appFileName;
|
|
374
689
|
|
|
375
690
|
// const logPath = `/Library/Logs/Electrobun/ExampleApp/dev/out.log`;
|
|
376
691
|
|
|
@@ -380,6 +695,12 @@ if (commandArg === "init") {
|
|
|
380
695
|
// todo (yoav): init a repo folder structure
|
|
381
696
|
console.log("initializing electrobun project");
|
|
382
697
|
} else if (commandArg === "build") {
|
|
698
|
+
// Ensure core binaries are available for the target platform before starting build
|
|
699
|
+
await ensureCoreDependencies(currentTarget.os, currentTarget.arch);
|
|
700
|
+
|
|
701
|
+
// Get platform-specific paths for the current target
|
|
702
|
+
const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);
|
|
703
|
+
|
|
383
704
|
// refresh build folder
|
|
384
705
|
if (existsSync(buildFolder)) {
|
|
385
706
|
rmdirSync(buildFolder, { recursive: true });
|
|
@@ -403,7 +724,7 @@ if (commandArg === "init") {
|
|
|
403
724
|
appBundleMacOSPath,
|
|
404
725
|
appBundleFolderResourcesPath,
|
|
405
726
|
appBundleFolderFrameworksPath,
|
|
406
|
-
} = createAppBundle(appFileName, buildFolder);
|
|
727
|
+
} = createAppBundle(appFileName, buildFolder, targetOS);
|
|
407
728
|
|
|
408
729
|
const appBundleAppCodePath = join(appBundleFolderResourcesPath, "app");
|
|
409
730
|
|
|
@@ -473,12 +794,28 @@ if (commandArg === "init") {
|
|
|
473
794
|
// mkdirSync(destLauncherFolder, {recursive: true});
|
|
474
795
|
// }
|
|
475
796
|
// cpSync(zigLauncherBinarySource, zigLauncherDestination, {recursive: true, dereference: true});
|
|
797
|
+
// For dev builds, use the actual CLI binary that's currently running
|
|
798
|
+
// It could be in .cache (npm install) or bin (local dev)
|
|
799
|
+
let devLauncherPath = targetPaths.LAUNCHER_DEV;
|
|
800
|
+
if (buildEnvironment === "dev" && !existsSync(devLauncherPath)) {
|
|
801
|
+
// Check .cache location (npm installed)
|
|
802
|
+
const cachePath = join(ELECTROBUN_DEP_PATH, ".cache", "electrobun") + binExt;
|
|
803
|
+
if (existsSync(cachePath)) {
|
|
804
|
+
devLauncherPath = cachePath;
|
|
805
|
+
} else {
|
|
806
|
+
// Check bin location (local dev)
|
|
807
|
+
const binPath = join(ELECTROBUN_DEP_PATH, "bin", "electrobun") + binExt;
|
|
808
|
+
if (existsSync(binPath)) {
|
|
809
|
+
devLauncherPath = binPath;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
476
814
|
const bunCliLauncherBinarySource =
|
|
477
815
|
buildEnvironment === "dev"
|
|
478
|
-
?
|
|
479
|
-
PATHS.LAUNCHER_DEV
|
|
816
|
+
? devLauncherPath
|
|
480
817
|
: // Note: for release use the zig launcher optimized for smol size
|
|
481
|
-
|
|
818
|
+
targetPaths.LAUNCHER_RELEASE;
|
|
482
819
|
const bunCliLauncherDestination = join(appBundleMacOSPath, "launcher") + binExt;
|
|
483
820
|
const destLauncherFolder = dirname(bunCliLauncherDestination);
|
|
484
821
|
if (!existsSync(destLauncherFolder)) {
|
|
@@ -491,11 +828,11 @@ if (commandArg === "init") {
|
|
|
491
828
|
dereference: true,
|
|
492
829
|
});
|
|
493
830
|
|
|
494
|
-
cpSync(
|
|
831
|
+
cpSync(targetPaths.MAIN_JS, join(appBundleMacOSPath, 'main.js'));
|
|
495
832
|
|
|
496
833
|
// Bun runtime binary
|
|
497
834
|
// todo (yoav): this only works for the current architecture
|
|
498
|
-
const bunBinarySourcePath =
|
|
835
|
+
const bunBinarySourcePath = targetPaths.BUN_BINARY;
|
|
499
836
|
// Note: .bin/bun binary in node_modules is a symlink to the versioned one in another place
|
|
500
837
|
// in node_modules, so we have to dereference here to get the actual binary in the bundle.
|
|
501
838
|
const bunBinaryDestInBundlePath = join(appBundleMacOSPath, "bun") + binExt;
|
|
@@ -507,8 +844,8 @@ if (commandArg === "init") {
|
|
|
507
844
|
cpSync(bunBinarySourcePath, bunBinaryDestInBundlePath, { dereference: true });
|
|
508
845
|
|
|
509
846
|
// copy native wrapper dynamic library
|
|
510
|
-
if (
|
|
511
|
-
const nativeWrapperMacosSource =
|
|
847
|
+
if (targetOS === 'macos') {
|
|
848
|
+
const nativeWrapperMacosSource = targetPaths.NATIVE_WRAPPER_MACOS;
|
|
512
849
|
const nativeWrapperMacosDestination = join(
|
|
513
850
|
appBundleMacOSPath,
|
|
514
851
|
"libNativeWrapper.dylib"
|
|
@@ -516,8 +853,8 @@ if (commandArg === "init") {
|
|
|
516
853
|
cpSync(nativeWrapperMacosSource, nativeWrapperMacosDestination, {
|
|
517
854
|
dereference: true,
|
|
518
855
|
});
|
|
519
|
-
} else if (
|
|
520
|
-
const nativeWrapperMacosSource =
|
|
856
|
+
} else if (targetOS === 'win') {
|
|
857
|
+
const nativeWrapperMacosSource = targetPaths.NATIVE_WRAPPER_WIN;
|
|
521
858
|
const nativeWrapperMacosDestination = join(
|
|
522
859
|
appBundleMacOSPath,
|
|
523
860
|
"libNativeWrapper.dll"
|
|
@@ -526,7 +863,7 @@ if (commandArg === "init") {
|
|
|
526
863
|
dereference: true,
|
|
527
864
|
});
|
|
528
865
|
|
|
529
|
-
const webview2LibSource =
|
|
866
|
+
const webview2LibSource = targetPaths.WEBVIEW2LOADER_WIN;
|
|
530
867
|
const webview2LibDestination = join(
|
|
531
868
|
appBundleMacOSPath,
|
|
532
869
|
"WebView2Loader.dll"
|
|
@@ -534,30 +871,34 @@ if (commandArg === "init") {
|
|
|
534
871
|
// copy webview2 system webview library
|
|
535
872
|
cpSync(webview2LibSource, webview2LibDestination);
|
|
536
873
|
|
|
537
|
-
} else if (
|
|
538
|
-
|
|
874
|
+
} else if (targetOS === 'linux') {
|
|
875
|
+
// Choose the appropriate native wrapper based on bundleCEF setting
|
|
876
|
+
const useCEF = config.build.linux?.bundleCEF;
|
|
877
|
+
const nativeWrapperLinuxSource = useCEF ? targetPaths.NATIVE_WRAPPER_LINUX_CEF : targetPaths.NATIVE_WRAPPER_LINUX;
|
|
539
878
|
const nativeWrapperLinuxDestination = join(
|
|
540
879
|
appBundleMacOSPath,
|
|
541
880
|
"libNativeWrapper.so"
|
|
542
881
|
);
|
|
882
|
+
|
|
543
883
|
if (existsSync(nativeWrapperLinuxSource)) {
|
|
544
884
|
cpSync(nativeWrapperLinuxSource, nativeWrapperLinuxDestination, {
|
|
545
885
|
dereference: true,
|
|
546
886
|
});
|
|
887
|
+
console.log(`Using ${useCEF ? 'CEF' : 'GTK'} native wrapper for Linux`);
|
|
888
|
+
} else {
|
|
889
|
+
throw new Error(`Native wrapper not found: ${nativeWrapperLinuxSource}`);
|
|
547
890
|
}
|
|
548
891
|
}
|
|
549
892
|
|
|
550
|
-
// Ensure core binaries are available
|
|
551
|
-
await ensureCoreDependencies();
|
|
552
893
|
|
|
553
894
|
// Download CEF binaries if needed when bundleCEF is enabled
|
|
554
|
-
if ((
|
|
555
|
-
(
|
|
556
|
-
(
|
|
895
|
+
if ((targetOS === 'macos' && config.build.mac?.bundleCEF) ||
|
|
896
|
+
(targetOS === 'win' && config.build.win?.bundleCEF) ||
|
|
897
|
+
(targetOS === 'linux' && config.build.linux?.bundleCEF)) {
|
|
557
898
|
|
|
558
|
-
await ensureCEFDependencies();
|
|
559
|
-
if (
|
|
560
|
-
const cefFrameworkSource =
|
|
899
|
+
await ensureCEFDependencies(currentTarget.os, currentTarget.arch);
|
|
900
|
+
if (targetOS === 'macos') {
|
|
901
|
+
const cefFrameworkSource = targetPaths.CEF_FRAMEWORK_MACOS;
|
|
561
902
|
const cefFrameworkDestination = join(
|
|
562
903
|
appBundleFolderFrameworksPath,
|
|
563
904
|
"Chromium Embedded Framework.framework"
|
|
@@ -578,7 +919,7 @@ if (commandArg === "init") {
|
|
|
578
919
|
"bun Helper (Renderer)",
|
|
579
920
|
];
|
|
580
921
|
|
|
581
|
-
const helperSourcePath =
|
|
922
|
+
const helperSourcePath = targetPaths.CEF_HELPER_MACOS;
|
|
582
923
|
cefHelperNames.forEach((helperName) => {
|
|
583
924
|
const destinationPath = join(
|
|
584
925
|
appBundleFolderFrameworksPath,
|
|
@@ -598,10 +939,9 @@ if (commandArg === "init") {
|
|
|
598
939
|
dereference: true,
|
|
599
940
|
});
|
|
600
941
|
});
|
|
601
|
-
} else if (
|
|
602
|
-
// Copy CEF DLLs from dist/cef/ to the main executable directory
|
|
603
|
-
const
|
|
604
|
-
const cefSourcePath = join(electrobunDistPath, "cef");
|
|
942
|
+
} else if (targetOS === 'win') {
|
|
943
|
+
// Copy CEF DLLs from platform-specific dist/cef/ to the main executable directory
|
|
944
|
+
const cefSourcePath = targetPaths.CEF_DIR;
|
|
605
945
|
const cefDllFiles = [
|
|
606
946
|
'libcef.dll',
|
|
607
947
|
'chrome_elf.dll',
|
|
@@ -641,7 +981,7 @@ if (commandArg === "init") {
|
|
|
641
981
|
});
|
|
642
982
|
|
|
643
983
|
// Copy CEF resources to MacOS/cef/ subdirectory for other resources like locales
|
|
644
|
-
const cefResourcesSource =
|
|
984
|
+
const cefResourcesSource = targetPaths.CEF_DIR;
|
|
645
985
|
const cefResourcesDestination = join(appBundleMacOSPath, 'cef');
|
|
646
986
|
|
|
647
987
|
if (existsSync(cefResourcesSource)) {
|
|
@@ -660,7 +1000,7 @@ if (commandArg === "init") {
|
|
|
660
1000
|
"bun Helper (Renderer)",
|
|
661
1001
|
];
|
|
662
1002
|
|
|
663
|
-
const helperSourcePath =
|
|
1003
|
+
const helperSourcePath = targetPaths.CEF_HELPER_WIN;
|
|
664
1004
|
if (existsSync(helperSourcePath)) {
|
|
665
1005
|
cefHelperNames.forEach((helperName) => {
|
|
666
1006
|
const destinationPath = join(appBundleMacOSPath, `${helperName}.exe`);
|
|
@@ -670,10 +1010,9 @@ if (commandArg === "init") {
|
|
|
670
1010
|
} else {
|
|
671
1011
|
console.log(`WARNING: Missing CEF helper: ${helperSourcePath}`);
|
|
672
1012
|
}
|
|
673
|
-
} else if (
|
|
674
|
-
// Copy CEF shared libraries from dist/cef/ to the main executable directory
|
|
675
|
-
const
|
|
676
|
-
const cefSourcePath = join(electrobunDistPath, "cef");
|
|
1013
|
+
} else if (targetOS === 'linux') {
|
|
1014
|
+
// Copy CEF shared libraries from platform-specific dist/cef/ to the main executable directory
|
|
1015
|
+
const cefSourcePath = targetPaths.CEF_DIR;
|
|
677
1016
|
|
|
678
1017
|
if (existsSync(cefSourcePath)) {
|
|
679
1018
|
const cefSoFiles = [
|
|
@@ -759,12 +1098,12 @@ if (commandArg === "init") {
|
|
|
759
1098
|
"bun Helper (Renderer)",
|
|
760
1099
|
];
|
|
761
1100
|
|
|
762
|
-
const helperSourcePath =
|
|
1101
|
+
const helperSourcePath = targetPaths.CEF_HELPER_LINUX;
|
|
763
1102
|
if (existsSync(helperSourcePath)) {
|
|
764
1103
|
cefHelperNames.forEach((helperName) => {
|
|
765
1104
|
const destinationPath = join(appBundleMacOSPath, helperName);
|
|
766
1105
|
cpSync(helperSourcePath, destinationPath);
|
|
767
|
-
console.log(`Copied CEF helper: ${helperName}`);
|
|
1106
|
+
// console.log(`Copied CEF helper: ${helperName}`);
|
|
768
1107
|
});
|
|
769
1108
|
} else {
|
|
770
1109
|
console.log(`WARNING: Missing CEF helper: ${helperSourcePath}`);
|
|
@@ -775,7 +1114,7 @@ if (commandArg === "init") {
|
|
|
775
1114
|
|
|
776
1115
|
|
|
777
1116
|
// copy native bindings
|
|
778
|
-
const bsPatchSource =
|
|
1117
|
+
const bsPatchSource = targetPaths.BSPATCH;
|
|
779
1118
|
const bsPatchDestination = join(appBundleMacOSPath, "bspatch") + binExt;
|
|
780
1119
|
const bsPatchDestFolder = dirname(bsPatchDestination);
|
|
781
1120
|
if (!existsSync(bsPatchDestFolder)) {
|
|
@@ -871,12 +1210,21 @@ if (commandArg === "init") {
|
|
|
871
1210
|
|
|
872
1211
|
// Run postBuild script
|
|
873
1212
|
if (config.scripts.postBuild) {
|
|
874
|
-
|
|
875
|
-
|
|
1213
|
+
// Use host platform's bun binary for running scripts, not target platform's
|
|
1214
|
+
const hostPaths = getPlatformPaths(OS, ARCH);
|
|
1215
|
+
|
|
1216
|
+
Bun.spawnSync([hostPaths.BUN_BINARY, config.scripts.postBuild], {
|
|
876
1217
|
stdio: ["ignore", "inherit", "inherit"],
|
|
877
1218
|
env: {
|
|
878
1219
|
...process.env,
|
|
879
1220
|
ELECTROBUN_BUILD_ENV: buildEnvironment,
|
|
1221
|
+
ELECTROBUN_OS: targetOS, // Use target OS for environment variables
|
|
1222
|
+
ELECTROBUN_ARCH: targetARCH, // Use target ARCH for environment variables
|
|
1223
|
+
ELECTROBUN_BUILD_DIR: buildFolder,
|
|
1224
|
+
ELECTROBUN_APP_NAME: appFileName,
|
|
1225
|
+
ELECTROBUN_APP_VERSION: config.app.version,
|
|
1226
|
+
ELECTROBUN_APP_IDENTIFIER: config.app.identifier,
|
|
1227
|
+
ELECTROBUN_ARTIFACT_DIR: artifactFolder,
|
|
880
1228
|
},
|
|
881
1229
|
});
|
|
882
1230
|
}
|
|
@@ -920,8 +1268,9 @@ if (commandArg === "init") {
|
|
|
920
1268
|
);
|
|
921
1269
|
|
|
922
1270
|
// todo (yoav): add these to config
|
|
1271
|
+
// Only codesign/notarize when building macOS targets on macOS host
|
|
923
1272
|
const shouldCodesign =
|
|
924
|
-
buildEnvironment !== "dev" && config.build.mac.codesign;
|
|
1273
|
+
buildEnvironment !== "dev" && targetOS === 'macos' && OS === 'macos' && config.build.mac.codesign;
|
|
925
1274
|
const shouldNotarize = shouldCodesign && config.build.mac.notarize;
|
|
926
1275
|
|
|
927
1276
|
if (shouldCodesign) {
|
|
@@ -956,6 +1305,8 @@ if (commandArg === "init") {
|
|
|
956
1305
|
// 6.5. code sign and notarize the dmg
|
|
957
1306
|
// 7. copy artifacts to directory [self-extractor dmg, zstd app bundle, bsdiff patch, update.json]
|
|
958
1307
|
|
|
1308
|
+
// Add platform suffix for all artifacts
|
|
1309
|
+
const platformSuffix = `-${targetOS}-${targetARCH}`;
|
|
959
1310
|
const tarPath = `${appBundleFolderPath}.tar`;
|
|
960
1311
|
|
|
961
1312
|
// tar the signed and notarized app bundle
|
|
@@ -979,7 +1330,8 @@ if (commandArg === "init") {
|
|
|
979
1330
|
// than saving 1 more MB of space/bandwidth.
|
|
980
1331
|
|
|
981
1332
|
const compressedTarPath = `${tarPath}.zst`;
|
|
982
|
-
|
|
1333
|
+
const platformCompressedTarPath = compressedTarPath.replace('.tar.zst', `${platformSuffix}.tar.zst`);
|
|
1334
|
+
artifactsToUpload.push(platformCompressedTarPath);
|
|
983
1335
|
|
|
984
1336
|
// zstd compress tarball
|
|
985
1337
|
// todo (yoav): consider using c bindings for zstd for speed instead of wasm
|
|
@@ -988,23 +1340,28 @@ if (commandArg === "init") {
|
|
|
988
1340
|
await ZstdInit().then(async ({ ZstdSimple, ZstdStream }) => {
|
|
989
1341
|
// Note: Simple is much faster than stream, but stream is better for large files
|
|
990
1342
|
// todo (yoav): consider a file size cutoff to switch to stream instead of simple.
|
|
1343
|
+
const useStream = tarball.size > 100 * 1024 * 1024;
|
|
1344
|
+
|
|
991
1345
|
if (tarball.size > 0) {
|
|
992
|
-
|
|
1346
|
+
// Uint8 array filestream of the tar file
|
|
993
1347
|
|
|
994
1348
|
const data = new Uint8Array(tarBuffer);
|
|
1349
|
+
|
|
995
1350
|
const compressionLevel = 22;
|
|
996
1351
|
const compressedData = ZstdSimple.compress(data, compressionLevel);
|
|
997
1352
|
|
|
998
1353
|
console.log(
|
|
999
1354
|
"compressed",
|
|
1000
|
-
|
|
1355
|
+
data.length,
|
|
1001
1356
|
"bytes",
|
|
1002
1357
|
"from",
|
|
1003
|
-
|
|
1358
|
+
tarBuffer.byteLength,
|
|
1004
1359
|
"bytes"
|
|
1005
1360
|
);
|
|
1006
1361
|
|
|
1007
1362
|
await Bun.write(compressedTarPath, compressedData);
|
|
1363
|
+
// Copy to platform-specific filename for upload
|
|
1364
|
+
cpSync(compressedTarPath, platformCompressedTarPath);
|
|
1008
1365
|
}
|
|
1009
1366
|
});
|
|
1010
1367
|
|
|
@@ -1012,7 +1369,7 @@ if (commandArg === "init") {
|
|
|
1012
1369
|
// now and it needs the same name as the original app bundle.
|
|
1013
1370
|
rmdirSync(appBundleFolderPath, { recursive: true });
|
|
1014
1371
|
|
|
1015
|
-
const selfExtractingBundle = createAppBundle(appFileName, buildFolder);
|
|
1372
|
+
const selfExtractingBundle = createAppBundle(appFileName, buildFolder, targetOS);
|
|
1016
1373
|
const compressedTarballInExtractingBundlePath = join(
|
|
1017
1374
|
selfExtractingBundle.appBundleFolderResourcesPath,
|
|
1018
1375
|
`${hash}.tar.zst`
|
|
@@ -1021,7 +1378,7 @@ if (commandArg === "init") {
|
|
|
1021
1378
|
// copy the zstd tarball to the self-extracting app bundle
|
|
1022
1379
|
cpSync(compressedTarPath, compressedTarballInExtractingBundlePath);
|
|
1023
1380
|
|
|
1024
|
-
const selfExtractorBinSourcePath =
|
|
1381
|
+
const selfExtractorBinSourcePath = targetPaths.EXTRACTOR;
|
|
1025
1382
|
const selfExtractorBinDestinationPath = join(
|
|
1026
1383
|
selfExtractingBundle.appBundleMacOSPath,
|
|
1027
1384
|
"launcher"
|
|
@@ -1053,28 +1410,49 @@ if (commandArg === "init") {
|
|
|
1053
1410
|
console.log("skipping notarization");
|
|
1054
1411
|
}
|
|
1055
1412
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1413
|
+
// DMG creation for macOS only
|
|
1414
|
+
if (targetOS === 'macos') {
|
|
1415
|
+
console.log("creating dmg...");
|
|
1416
|
+
// make a dmg
|
|
1417
|
+
const dmgPath = join(buildFolder, `${appFileName}.dmg`);
|
|
1418
|
+
const platformDmgPath = join(buildFolder, `${appFileName}${platformSuffix}.dmg`);
|
|
1419
|
+
artifactsToUpload.push(platformDmgPath);
|
|
1420
|
+
// hdiutil create -volname "YourAppName" -srcfolder /path/to/YourApp.app -ov -format UDZO YourAppName.dmg
|
|
1421
|
+
// Note: use UDBZ for better compression vs. UDZO
|
|
1422
|
+
execSync(
|
|
1423
|
+
`hdiutil create -volname "${appFileName}" -srcfolder ${escapePathForTerminal(
|
|
1424
|
+
appBundleFolderPath
|
|
1425
|
+
)} -ov -format UDBZ ${escapePathForTerminal(dmgPath)}`
|
|
1426
|
+
);
|
|
1067
1427
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1428
|
+
if (shouldCodesign) {
|
|
1429
|
+
codesignAppBundle(dmgPath);
|
|
1430
|
+
} else {
|
|
1431
|
+
console.log("skipping codesign");
|
|
1432
|
+
}
|
|
1073
1433
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1434
|
+
if (shouldNotarize) {
|
|
1435
|
+
notarizeAndStaple(dmgPath);
|
|
1436
|
+
} else {
|
|
1437
|
+
console.log("skipping notarization");
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Copy to platform-specific filename
|
|
1441
|
+
cpSync(dmgPath, platformDmgPath);
|
|
1076
1442
|
} else {
|
|
1077
|
-
|
|
1443
|
+
// For Windows and Linux, add the self-extracting bundle directly
|
|
1444
|
+
const platformBundlePath = join(buildFolder, `${appFileName}${platformSuffix}${targetOS === 'win' ? '.exe' : ''}`);
|
|
1445
|
+
// Copy the self-extracting bundle to platform-specific filename
|
|
1446
|
+
if (targetOS === 'win') {
|
|
1447
|
+
// On Windows, create a self-extracting exe
|
|
1448
|
+
// For now, just copy the bundle folder
|
|
1449
|
+
artifactsToUpload.push(compressedTarPath.replace('.tar.zst', `${platformSuffix}.tar.zst`));
|
|
1450
|
+
} else if (targetOS === 'linux') {
|
|
1451
|
+
// On Linux, create a tar.gz of the bundle
|
|
1452
|
+
const linuxTarPath = join(buildFolder, `${appFileName}${platformSuffix}.tar.gz`);
|
|
1453
|
+
execSync(`tar -czf ${escapePathForTerminal(linuxTarPath)} -C ${escapePathForTerminal(buildFolder)} ${escapePathForTerminal(basename(appBundleFolderPath))}`);
|
|
1454
|
+
artifactsToUpload.push(linuxTarPath);
|
|
1455
|
+
}
|
|
1078
1456
|
}
|
|
1079
1457
|
|
|
1080
1458
|
// refresh artifacts folder
|
|
@@ -1093,11 +1471,15 @@ if (commandArg === "init") {
|
|
|
1093
1471
|
// the download button or display on your marketing site or in the app.
|
|
1094
1472
|
version: config.app.version,
|
|
1095
1473
|
hash: hash.toString(),
|
|
1474
|
+
platform: OS,
|
|
1475
|
+
arch: ARCH,
|
|
1096
1476
|
// channel: buildEnvironment,
|
|
1097
1477
|
// bucketUrl: config.release.bucketUrl
|
|
1098
1478
|
});
|
|
1099
1479
|
|
|
1100
|
-
|
|
1480
|
+
// Platform-specific update.json
|
|
1481
|
+
const platformUpdateJsonName = `update${platformSuffix}.json`;
|
|
1482
|
+
await Bun.write(join(artifactFolder, platformUpdateJsonName), updateJsonContent);
|
|
1101
1483
|
|
|
1102
1484
|
// generate bsdiff
|
|
1103
1485
|
// https://storage.googleapis.com/eggbun-static/electrobun-playground/canary/ElectrobunPlayground-canary.app.tar.zst
|
|
@@ -1107,7 +1489,7 @@ if (commandArg === "init") {
|
|
|
1107
1489
|
const urlToPrevUpdateJson = join(
|
|
1108
1490
|
config.release.bucketUrl,
|
|
1109
1491
|
buildEnvironment,
|
|
1110
|
-
`update.json`
|
|
1492
|
+
`update${platformSuffix}.json`
|
|
1111
1493
|
);
|
|
1112
1494
|
const cacheBuster = Math.random().toString(36).substring(7);
|
|
1113
1495
|
const updateJsonResponse = await fetch(
|
|
@@ -1119,13 +1501,13 @@ if (commandArg === "init") {
|
|
|
1119
1501
|
const urlToLatestTarball = join(
|
|
1120
1502
|
config.release.bucketUrl,
|
|
1121
1503
|
buildEnvironment,
|
|
1122
|
-
`${appFileName}.app.tar.zst`
|
|
1504
|
+
`${appFileName}.app${platformSuffix}.tar.zst`
|
|
1123
1505
|
);
|
|
1124
1506
|
|
|
1125
1507
|
|
|
1126
1508
|
// attempt to get the previous version to create a patch file
|
|
1127
|
-
if (updateJsonResponse.ok) {
|
|
1128
|
-
const prevUpdateJson = await updateJsonResponse
|
|
1509
|
+
if (updateJsonResponse && updateJsonResponse.ok) {
|
|
1510
|
+
const prevUpdateJson = await updateJsonResponse!.json();
|
|
1129
1511
|
|
|
1130
1512
|
const prevHash = prevUpdateJson.hash;
|
|
1131
1513
|
console.log("PREVIOUS HASH", prevHash);
|
|
@@ -1164,9 +1546,10 @@ if (commandArg === "init") {
|
|
|
1164
1546
|
console.log("diff previous and new tarballs...");
|
|
1165
1547
|
// Run it as a separate process to leverage multi-threadedness
|
|
1166
1548
|
// especially for creating multiple diffs in parallel
|
|
1167
|
-
const bsdiffpath =
|
|
1549
|
+
const bsdiffpath = targetPaths.BSDIFF;
|
|
1168
1550
|
const patchFilePath = join(buildFolder, `${prevHash}.patch`);
|
|
1169
|
-
|
|
1551
|
+
const platformPatchFilePath = join(buildFolder, `${prevHash}${platformSuffix}.patch`);
|
|
1552
|
+
artifactsToUpload.push(platformPatchFilePath);
|
|
1170
1553
|
const result = Bun.spawnSync(
|
|
1171
1554
|
[bsdiffpath, prevTarballPath, tarPath, patchFilePath, "--use-zstd"],
|
|
1172
1555
|
{ cwd: buildFolder }
|
|
@@ -1176,6 +1559,8 @@ if (commandArg === "init") {
|
|
|
1176
1559
|
result.stdout.toString(),
|
|
1177
1560
|
result.stderr.toString()
|
|
1178
1561
|
);
|
|
1562
|
+
// Copy to platform-specific filename
|
|
1563
|
+
cpSync(patchFilePath, platformPatchFilePath);
|
|
1179
1564
|
}
|
|
1180
1565
|
} else {
|
|
1181
1566
|
console.log("prevoius version not found at: ", urlToLatestTarball);
|
|
@@ -1290,7 +1675,12 @@ function getConfig() {
|
|
|
1290
1675
|
if (existsSync(configPath)) {
|
|
1291
1676
|
const configFileContents = readFileSync(configPath, "utf8");
|
|
1292
1677
|
// Note: we want this to hard fail if there's a syntax error
|
|
1293
|
-
|
|
1678
|
+
try {
|
|
1679
|
+
loadedConfig = JSON.parse(configFileContents);
|
|
1680
|
+
} catch (error) {
|
|
1681
|
+
console.error("Failed to parse config file:", error);
|
|
1682
|
+
console.error("using default config instead");
|
|
1683
|
+
}
|
|
1294
1684
|
}
|
|
1295
1685
|
|
|
1296
1686
|
// todo (yoav): write a deep clone fn
|
|
@@ -1312,6 +1702,18 @@ function getConfig() {
|
|
|
1312
1702
|
...(loadedConfig?.build?.mac?.entitlements || {}),
|
|
1313
1703
|
},
|
|
1314
1704
|
},
|
|
1705
|
+
win: {
|
|
1706
|
+
...defaultConfig.build.win,
|
|
1707
|
+
...(loadedConfig?.build?.win || {}),
|
|
1708
|
+
},
|
|
1709
|
+
linux: {
|
|
1710
|
+
...defaultConfig.build.linux,
|
|
1711
|
+
...(loadedConfig?.build?.linux || {}),
|
|
1712
|
+
},
|
|
1713
|
+
bun: {
|
|
1714
|
+
...defaultConfig.build.bun,
|
|
1715
|
+
...(loadedConfig?.build?.bun || {}),
|
|
1716
|
+
}
|
|
1315
1717
|
},
|
|
1316
1718
|
scripts: {
|
|
1317
1719
|
...defaultConfig.scripts,
|
|
@@ -1473,8 +1875,8 @@ function notarizeAndStaple(appOrDmgPath: string) {
|
|
|
1473
1875
|
// have the same name but different subfolders in our build directory. or I guess delete the first one after tar/compression and then create the other one.
|
|
1474
1876
|
// either way you can pass in the parent folder here for that flexibility.
|
|
1475
1877
|
// for intel/arm builds on mac we'll probably have separate subfolders as well and build them in parallel.
|
|
1476
|
-
function createAppBundle(bundleName: string, parentFolder: string) {
|
|
1477
|
-
if (
|
|
1878
|
+
function createAppBundle(bundleName: string, parentFolder: string, targetOS: 'macos' | 'win' | 'linux') {
|
|
1879
|
+
if (targetOS === 'macos') {
|
|
1478
1880
|
// macOS bundle structure
|
|
1479
1881
|
const bundleFileName = `${bundleName}.app`;
|
|
1480
1882
|
const appBundleFolderPath = join(parentFolder, bundleFileName);
|
|
@@ -1501,7 +1903,7 @@ function createAppBundle(bundleName: string, parentFolder: string) {
|
|
|
1501
1903
|
appBundleFolderResourcesPath,
|
|
1502
1904
|
appBundleFolderFrameworksPath,
|
|
1503
1905
|
};
|
|
1504
|
-
} else if (
|
|
1906
|
+
} else if (targetOS === 'linux' || targetOS === 'win') {
|
|
1505
1907
|
// Linux/Windows simpler structure
|
|
1506
1908
|
const appBundleFolderPath = join(parentFolder, bundleName);
|
|
1507
1909
|
const appBundleFolderContentsPath = appBundleFolderPath; // No Contents folder needed
|
|
@@ -1522,6 +1924,6 @@ function createAppBundle(bundleName: string, parentFolder: string) {
|
|
|
1522
1924
|
appBundleFolderFrameworksPath,
|
|
1523
1925
|
};
|
|
1524
1926
|
} else {
|
|
1525
|
-
throw new Error(`Unsupported OS: ${
|
|
1927
|
+
throw new Error(`Unsupported OS: ${targetOS}`);
|
|
1526
1928
|
}
|
|
1527
1929
|
}
|