electrobun 0.0.19-beta.7 → 0.0.19-beta.70
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/dist/api/browser/builtinrpcSchema.ts +19 -0
- package/dist/api/browser/index.ts +409 -0
- package/dist/api/browser/rpc/webview.ts +79 -0
- package/dist/api/browser/stylesAndElements.ts +3 -0
- package/dist/api/browser/webviewtag.ts +534 -0
- package/dist/api/bun/core/ApplicationMenu.ts +66 -0
- package/dist/api/bun/core/BrowserView.ts +349 -0
- package/dist/api/bun/core/BrowserWindow.ts +191 -0
- package/dist/api/bun/core/ContextMenu.ts +67 -0
- package/dist/api/bun/core/Paths.ts +5 -0
- package/dist/api/bun/core/Socket.ts +181 -0
- package/dist/api/bun/core/Tray.ts +107 -0
- package/dist/api/bun/core/Updater.ts +390 -0
- package/dist/api/bun/core/Utils.ts +48 -0
- package/dist/api/bun/events/ApplicationEvents.ts +14 -0
- package/dist/api/bun/events/event.ts +29 -0
- package/dist/api/bun/events/eventEmitter.ts +45 -0
- package/dist/api/bun/events/trayEvents.ts +9 -0
- package/dist/api/bun/events/webviewEvents.ts +16 -0
- package/dist/api/bun/events/windowEvents.ts +12 -0
- package/dist/api/bun/index.ts +45 -0
- package/dist/api/bun/proc/linux.md +43 -0
- package/dist/api/bun/proc/native.ts +1217 -0
- package/dist/main.js +12 -0
- package/package.json +13 -7
- package/src/cli/index.ts +582 -175
- 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()) {
|
|
@@ -56,48 +58,88 @@ const configPath = join(projectRoot, configName);
|
|
|
56
58
|
const indexOfElectrobun = process.argv.findIndex((arg) =>
|
|
57
59
|
arg.includes("electrobun")
|
|
58
60
|
);
|
|
59
|
-
const commandArg = process.argv[indexOfElectrobun + 1] || "
|
|
61
|
+
const commandArg = process.argv[indexOfElectrobun + 1] || "build";
|
|
60
62
|
|
|
61
63
|
const ELECTROBUN_DEP_PATH = join(projectRoot, "node_modules", "electrobun");
|
|
62
64
|
|
|
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,61 +154,153 @@ 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
|
|
133
175
|
if (response.body) {
|
|
134
176
|
const reader = response.body.getReader();
|
|
177
|
+
let totalBytes = 0;
|
|
135
178
|
while (true) {
|
|
136
179
|
const { done, value } = await reader.read();
|
|
137
180
|
if (done) break;
|
|
138
|
-
|
|
181
|
+
const buffer = Buffer.from(value);
|
|
182
|
+
fileStream.write(buffer);
|
|
183
|
+
totalBytes += buffer.length;
|
|
139
184
|
}
|
|
185
|
+
console.log(`Downloaded ${totalBytes} bytes for ${platformOS}-${platformArch}`);
|
|
140
186
|
}
|
|
141
|
-
fileStream.end();
|
|
142
187
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
188
|
+
// Ensure file is properly closed before proceeding
|
|
189
|
+
await new Promise((resolve, reject) => {
|
|
190
|
+
fileStream.end((err) => {
|
|
191
|
+
if (err) reject(err);
|
|
192
|
+
else resolve(null);
|
|
193
|
+
});
|
|
148
194
|
});
|
|
149
195
|
|
|
196
|
+
// Verify the downloaded file exists and has content
|
|
197
|
+
if (!existsSync(tempFile)) {
|
|
198
|
+
throw new Error(`Downloaded file not found: ${tempFile}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const fileSize = require('fs').statSync(tempFile).size;
|
|
202
|
+
if (fileSize === 0) {
|
|
203
|
+
throw new Error(`Downloaded file is empty: ${tempFile}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log(`Verified download: ${tempFile} (${fileSize} bytes)`);
|
|
207
|
+
|
|
208
|
+
// Extract to platform-specific dist directory
|
|
209
|
+
console.log(`Extracting core dependencies for ${platformOS}-${platformArch}...`);
|
|
210
|
+
const platformDistPath = join(ELECTROBUN_DEP_PATH, `dist-${platformOS}-${platformArch}`);
|
|
211
|
+
mkdirSync(platformDistPath, { recursive: true });
|
|
212
|
+
|
|
213
|
+
// Use Windows native tar.exe on Windows due to npm tar library issues
|
|
214
|
+
if (OS === 'win') {
|
|
215
|
+
console.log('Using Windows native tar.exe for reliable extraction...');
|
|
216
|
+
execSync(`tar -xf "${tempFile}" -C "${platformDistPath}"`, {
|
|
217
|
+
stdio: 'inherit',
|
|
218
|
+
cwd: platformDistPath
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
await tar.x({
|
|
222
|
+
file: tempFile,
|
|
223
|
+
cwd: platformDistPath,
|
|
224
|
+
preservePaths: false,
|
|
225
|
+
strip: 0,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// NOTE: We no longer copy main.js from platform-specific downloads
|
|
230
|
+
// Platform-specific downloads should only contain native binaries
|
|
231
|
+
// main.js and api/ should be shipped via npm in the shared dist/ folder
|
|
232
|
+
|
|
150
233
|
// Clean up temp file
|
|
151
234
|
unlinkSync(tempFile);
|
|
152
235
|
|
|
153
|
-
|
|
236
|
+
// Debug: List what was actually extracted
|
|
237
|
+
try {
|
|
238
|
+
const extractedFiles = readdirSync(platformDistPath);
|
|
239
|
+
console.log(`Extracted files to ${platformDistPath}:`, extractedFiles);
|
|
240
|
+
|
|
241
|
+
// Check if files are in subdirectories
|
|
242
|
+
for (const file of extractedFiles) {
|
|
243
|
+
const filePath = join(platformDistPath, file);
|
|
244
|
+
const stat = require('fs').statSync(filePath);
|
|
245
|
+
if (stat.isDirectory()) {
|
|
246
|
+
const subFiles = readdirSync(filePath);
|
|
247
|
+
console.log(` ${file}/: ${subFiles.join(', ')}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.error('Could not list extracted files:', e);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Verify extraction completed successfully - check platform-specific binaries only
|
|
255
|
+
const requiredBinaries = [
|
|
256
|
+
platformPaths.BUN_BINARY,
|
|
257
|
+
platformPaths.LAUNCHER_RELEASE,
|
|
258
|
+
platformOS === 'macos' ? platformPaths.NATIVE_WRAPPER_MACOS :
|
|
259
|
+
platformOS === 'win' ? platformPaths.NATIVE_WRAPPER_WIN :
|
|
260
|
+
platformPaths.NATIVE_WRAPPER_LINUX
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
const missingBinaries = requiredBinaries.filter(file => !existsSync(file));
|
|
264
|
+
if (missingBinaries.length > 0) {
|
|
265
|
+
console.error(`Missing binaries after extraction: ${missingBinaries.map(f => f.replace(ELECTROBUN_DEP_PATH, '.')).join(', ')}`);
|
|
266
|
+
console.error('This suggests the tarball structure is different than expected');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// For development: if main.js doesn't exist in shared dist/, copy from platform-specific download as fallback
|
|
270
|
+
const sharedDistPath = join(ELECTROBUN_DEP_PATH, 'dist');
|
|
271
|
+
const extractedMainJs = join(platformDistPath, 'main.js');
|
|
272
|
+
const sharedMainJs = join(sharedDistPath, 'main.js');
|
|
273
|
+
|
|
274
|
+
if (existsSync(extractedMainJs) && !existsSync(sharedMainJs)) {
|
|
275
|
+
console.log('Development fallback: copying main.js from platform-specific download to shared dist/');
|
|
276
|
+
mkdirSync(sharedDistPath, { recursive: true });
|
|
277
|
+
cpSync(extractedMainJs, sharedMainJs);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log(`Core dependencies for ${platformOS}-${platformArch} downloaded and cached successfully`);
|
|
154
281
|
|
|
155
|
-
} catch (error) {
|
|
156
|
-
console.error(
|
|
282
|
+
} catch (error: any) {
|
|
283
|
+
console.error(`Failed to download core dependencies for ${platformOS}-${platformArch}:`, error.message);
|
|
157
284
|
console.error('Please ensure you have an internet connection and the release exists.');
|
|
158
285
|
process.exit(1);
|
|
159
286
|
}
|
|
160
287
|
}
|
|
161
288
|
|
|
162
|
-
async function ensureCEFDependencies() {
|
|
289
|
+
async function ensureCEFDependencies(targetOS?: 'macos' | 'win' | 'linux', targetArch?: 'arm64' | 'x64') {
|
|
290
|
+
// Use provided target platform or default to host platform
|
|
291
|
+
const platformOS = targetOS || OS;
|
|
292
|
+
const platformArch = targetArch || ARCH;
|
|
293
|
+
|
|
294
|
+
// Get platform-specific paths
|
|
295
|
+
const platformPaths = getPlatformPaths(platformOS, platformArch);
|
|
296
|
+
|
|
163
297
|
// Check if CEF dependencies already exist
|
|
164
|
-
if (existsSync(
|
|
165
|
-
console.log(
|
|
298
|
+
if (existsSync(platformPaths.CEF_DIR)) {
|
|
299
|
+
console.log(`CEF dependencies found for ${platformOS}-${platformArch}, using cached version`);
|
|
166
300
|
return;
|
|
167
301
|
}
|
|
168
302
|
|
|
169
|
-
console.log(
|
|
303
|
+
console.log(`CEF dependencies not found for ${platformOS}-${platformArch}, downloading...`);
|
|
170
304
|
|
|
171
305
|
// Get the current Electrobun version from package.json
|
|
172
306
|
const packageJsonPath = join(ELECTROBUN_DEP_PATH, 'package.json');
|
|
@@ -181,8 +315,8 @@ async function ensureCEFDependencies() {
|
|
|
181
315
|
}
|
|
182
316
|
}
|
|
183
317
|
|
|
184
|
-
const platformName =
|
|
185
|
-
const archName =
|
|
318
|
+
const platformName = platformOS === 'macos' ? 'darwin' : platformOS === 'win' ? 'win' : 'linux';
|
|
319
|
+
const archName = platformArch;
|
|
186
320
|
const cefTarballUrl = `https://github.com/blackboardsh/electrobun/releases/download/${version}/electrobun-cef-${platformName}-${archName}.tar.gz`;
|
|
187
321
|
|
|
188
322
|
console.log(`Downloading CEF from: ${cefTarballUrl}`);
|
|
@@ -195,7 +329,7 @@ async function ensureCEFDependencies() {
|
|
|
195
329
|
}
|
|
196
330
|
|
|
197
331
|
// Create temp file
|
|
198
|
-
const tempFile = join(ELECTROBUN_DEP_PATH,
|
|
332
|
+
const tempFile = join(ELECTROBUN_DEP_PATH, `cef-${platformOS}-${platformArch}-temp.tar.gz`);
|
|
199
333
|
const fileStream = createWriteStream(tempFile);
|
|
200
334
|
|
|
201
335
|
// Write response to file
|
|
@@ -209,20 +343,49 @@ async function ensureCEFDependencies() {
|
|
|
209
343
|
}
|
|
210
344
|
fileStream.end();
|
|
211
345
|
|
|
212
|
-
// Extract to dist directory
|
|
213
|
-
console.log(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
346
|
+
// Extract to platform-specific dist directory
|
|
347
|
+
console.log(`Extracting CEF dependencies for ${platformOS}-${platformArch}...`);
|
|
348
|
+
const platformDistPath = join(ELECTROBUN_DEP_PATH, `dist-${platformOS}-${platformArch}`);
|
|
349
|
+
mkdirSync(platformDistPath, { recursive: true });
|
|
350
|
+
|
|
351
|
+
// Use Windows native tar.exe on Windows due to npm tar library issues
|
|
352
|
+
if (OS === 'win') {
|
|
353
|
+
console.log('Using Windows native tar.exe for reliable extraction...');
|
|
354
|
+
execSync(`tar -xf "${tempFile}" -C "${platformDistPath}"`, {
|
|
355
|
+
stdio: 'inherit',
|
|
356
|
+
cwd: platformDistPath
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
await tar.x({
|
|
360
|
+
file: tempFile,
|
|
361
|
+
cwd: platformDistPath,
|
|
362
|
+
preservePaths: false,
|
|
363
|
+
strip: 0,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
218
366
|
|
|
219
367
|
// Clean up temp file
|
|
220
368
|
unlinkSync(tempFile);
|
|
221
369
|
|
|
222
|
-
|
|
370
|
+
// Debug: List what was actually extracted for CEF
|
|
371
|
+
try {
|
|
372
|
+
const extractedFiles = readdirSync(platformDistPath);
|
|
373
|
+
console.log(`CEF extracted files to ${platformDistPath}:`, extractedFiles);
|
|
374
|
+
|
|
375
|
+
// Check if CEF directory was created
|
|
376
|
+
const cefDir = join(platformDistPath, 'cef');
|
|
377
|
+
if (existsSync(cefDir)) {
|
|
378
|
+
const cefFiles = readdirSync(cefDir);
|
|
379
|
+
console.log(`CEF directory contents: ${cefFiles.slice(0, 10).join(', ')}${cefFiles.length > 10 ? '...' : ''}`);
|
|
380
|
+
}
|
|
381
|
+
} catch (e) {
|
|
382
|
+
console.error('Could not list CEF extracted files:', e);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
console.log(`CEF dependencies for ${platformOS}-${platformArch} downloaded and cached successfully`);
|
|
223
386
|
|
|
224
|
-
} catch (error) {
|
|
225
|
-
console.error(
|
|
387
|
+
} catch (error: any) {
|
|
388
|
+
console.error(`Failed to download CEF dependencies for ${platformOS}-${platformArch}:`, error.message);
|
|
226
389
|
console.error('Please ensure you have an internet connection and the release exists.');
|
|
227
390
|
process.exit(1);
|
|
228
391
|
}
|
|
@@ -241,10 +404,6 @@ const commandDefaults = {
|
|
|
241
404
|
projectRoot,
|
|
242
405
|
config: "electrobun.config",
|
|
243
406
|
},
|
|
244
|
-
launcher: {
|
|
245
|
-
projectRoot,
|
|
246
|
-
config: "electrobun.config",
|
|
247
|
-
},
|
|
248
407
|
};
|
|
249
408
|
|
|
250
409
|
// todo (yoav): add types for config
|
|
@@ -257,6 +416,7 @@ const defaultConfig = {
|
|
|
257
416
|
build: {
|
|
258
417
|
buildFolder: "build",
|
|
259
418
|
artifactFolder: "artifacts",
|
|
419
|
+
targets: undefined, // Will default to current platform if not specified
|
|
260
420
|
mac: {
|
|
261
421
|
codesign: false,
|
|
262
422
|
notarize: false,
|
|
@@ -267,6 +427,10 @@ const defaultConfig = {
|
|
|
267
427
|
},
|
|
268
428
|
icons: "icon.iconset",
|
|
269
429
|
},
|
|
430
|
+
bun: {
|
|
431
|
+
entrypoint: "src/bun/index.ts",
|
|
432
|
+
external: [],
|
|
433
|
+
},
|
|
270
434
|
},
|
|
271
435
|
scripts: {
|
|
272
436
|
postBuild: "",
|
|
@@ -286,7 +450,10 @@ if (!command) {
|
|
|
286
450
|
const config = getConfig();
|
|
287
451
|
|
|
288
452
|
const envArg =
|
|
289
|
-
process.argv.find((arg) => arg.startsWith("env="))?.split("=")[1] || "";
|
|
453
|
+
process.argv.find((arg) => arg.startsWith("--env="))?.split("=")[1] || "";
|
|
454
|
+
|
|
455
|
+
const targetsArg =
|
|
456
|
+
process.argv.find((arg) => arg.startsWith("--targets="))?.split("=")[1] || "";
|
|
290
457
|
|
|
291
458
|
const validEnvironments = ["dev", "canary", "stable"];
|
|
292
459
|
|
|
@@ -294,8 +461,175 @@ const validEnvironments = ["dev", "canary", "stable"];
|
|
|
294
461
|
const buildEnvironment: "dev" | "canary" | "stable" =
|
|
295
462
|
validEnvironments.includes(envArg) ? envArg : "dev";
|
|
296
463
|
|
|
464
|
+
// Determine build targets
|
|
465
|
+
type BuildTarget = { os: 'macos' | 'win' | 'linux', arch: 'arm64' | 'x64' };
|
|
466
|
+
|
|
467
|
+
function parseBuildTargets(): BuildTarget[] {
|
|
468
|
+
// If explicit targets provided via CLI
|
|
469
|
+
if (targetsArg) {
|
|
470
|
+
if (targetsArg === 'current') {
|
|
471
|
+
return [{ os: OS, arch: ARCH }];
|
|
472
|
+
} else if (targetsArg === 'all') {
|
|
473
|
+
return parseConfigTargets();
|
|
474
|
+
} else {
|
|
475
|
+
// Parse comma-separated targets like "macos-arm64,win-x64"
|
|
476
|
+
return targetsArg.split(',').map(target => {
|
|
477
|
+
const [os, arch] = target.trim().split('-') as [string, string];
|
|
478
|
+
if (!['macos', 'win', 'linux'].includes(os) || !['arm64', 'x64'].includes(arch)) {
|
|
479
|
+
console.error(`Invalid target: ${target}. Format should be: os-arch (e.g., macos-arm64)`);
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
return { os, arch } as BuildTarget;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Default behavior: always build for current platform only
|
|
488
|
+
// This ensures predictable, fast builds unless explicitly requesting multi-platform
|
|
489
|
+
return [{ os: OS, arch: ARCH }];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function parseConfigTargets(): BuildTarget[] {
|
|
493
|
+
// If config has targets, use them
|
|
494
|
+
if (config.build.targets && config.build.targets.length > 0) {
|
|
495
|
+
return config.build.targets.map(target => {
|
|
496
|
+
if (target === 'current') {
|
|
497
|
+
return { os: OS, arch: ARCH };
|
|
498
|
+
}
|
|
499
|
+
const [os, arch] = target.split('-') as [string, string];
|
|
500
|
+
if (!['macos', 'win', 'linux'].includes(os) || !['arm64', 'x64'].includes(arch)) {
|
|
501
|
+
console.error(`Invalid target in config: ${target}. Format should be: os-arch (e.g., macos-arm64)`);
|
|
502
|
+
process.exit(1);
|
|
503
|
+
}
|
|
504
|
+
return { os, arch } as BuildTarget;
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// If no config targets and --targets=all, use all available platforms
|
|
509
|
+
if (targetsArg === 'all') {
|
|
510
|
+
console.log('No targets specified in config, using all available platforms');
|
|
511
|
+
return [
|
|
512
|
+
{ os: 'macos', arch: 'arm64' },
|
|
513
|
+
{ os: 'macos', arch: 'x64' },
|
|
514
|
+
{ os: 'win', arch: 'x64' },
|
|
515
|
+
{ os: 'linux', arch: 'x64' },
|
|
516
|
+
{ os: 'linux', arch: 'arm64' }
|
|
517
|
+
];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Default to current platform
|
|
521
|
+
return [{ os: OS, arch: ARCH }];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const buildTargets = parseBuildTargets();
|
|
525
|
+
|
|
526
|
+
// Show build targets to user
|
|
527
|
+
if (buildTargets.length === 1) {
|
|
528
|
+
console.log(`Building for ${buildTargets[0].os}-${buildTargets[0].arch} (${buildEnvironment})`);
|
|
529
|
+
} else {
|
|
530
|
+
const targetList = buildTargets.map(t => `${t.os}-${t.arch}`).join(', ');
|
|
531
|
+
console.log(`Building for multiple targets: ${targetList} (${buildEnvironment})`);
|
|
532
|
+
console.log(`Running ${buildTargets.length} parallel builds...`);
|
|
533
|
+
|
|
534
|
+
// Spawn parallel build processes
|
|
535
|
+
const buildPromises = buildTargets.map(async (target) => {
|
|
536
|
+
const targetString = `${target.os}-${target.arch}`;
|
|
537
|
+
const prefix = `[${targetString}]`;
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
// Try to find the electrobun binary in node_modules/.bin or use bunx
|
|
541
|
+
const electrobunBin = join(projectRoot, 'node_modules', '.bin', 'electrobun');
|
|
542
|
+
let command: string[];
|
|
543
|
+
|
|
544
|
+
if (existsSync(electrobunBin)) {
|
|
545
|
+
command = [electrobunBin, 'build', `--env=${buildEnvironment}`, `--targets=${targetString}`];
|
|
546
|
+
} else {
|
|
547
|
+
// Fallback to bunx which should resolve node_modules binaries
|
|
548
|
+
command = ['bunx', 'electrobun', 'build', `--env=${buildEnvironment}`, `--targets=${targetString}`];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
console.log(`${prefix} Running:`, command.join(' '));
|
|
552
|
+
|
|
553
|
+
const result = await Bun.spawn(command, {
|
|
554
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
555
|
+
env: process.env,
|
|
556
|
+
cwd: projectRoot // Ensure we're in the right directory
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Pipe output with prefix
|
|
560
|
+
if (result.stdout) {
|
|
561
|
+
const reader = result.stdout.getReader();
|
|
562
|
+
while (true) {
|
|
563
|
+
const { done, value } = await reader.read();
|
|
564
|
+
if (done) break;
|
|
565
|
+
const text = new TextDecoder().decode(value);
|
|
566
|
+
// Add prefix to each line
|
|
567
|
+
const prefixedText = text.split('\n').map(line =>
|
|
568
|
+
line ? `${prefix} ${line}` : line
|
|
569
|
+
).join('\n');
|
|
570
|
+
process.stdout.write(prefixedText);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (result.stderr) {
|
|
575
|
+
const reader = result.stderr.getReader();
|
|
576
|
+
while (true) {
|
|
577
|
+
const { done, value } = await reader.read();
|
|
578
|
+
if (done) break;
|
|
579
|
+
const text = new TextDecoder().decode(value);
|
|
580
|
+
const prefixedText = text.split('\n').map(line =>
|
|
581
|
+
line ? `${prefix} ${line}` : line
|
|
582
|
+
).join('\n');
|
|
583
|
+
process.stderr.write(prefixedText);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const exitCode = await result.exited;
|
|
588
|
+
return { target, exitCode, success: exitCode === 0 };
|
|
589
|
+
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.error(`${prefix} Failed to start build:`, error);
|
|
592
|
+
return { target, exitCode: 1, success: false, error };
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Wait for all builds to complete
|
|
597
|
+
const results = await Promise.allSettled(buildPromises);
|
|
598
|
+
|
|
599
|
+
// Report final results
|
|
600
|
+
console.log('\n=== Build Results ===');
|
|
601
|
+
let allSucceeded = true;
|
|
602
|
+
|
|
603
|
+
for (const result of results) {
|
|
604
|
+
if (result.status === 'fulfilled') {
|
|
605
|
+
const { target, success, exitCode } = result.value;
|
|
606
|
+
const status = success ? '✅ SUCCESS' : '❌ FAILED';
|
|
607
|
+
console.log(`${target.os}-${target.arch}: ${status} (exit code: ${exitCode})`);
|
|
608
|
+
if (!success) allSucceeded = false;
|
|
609
|
+
} else {
|
|
610
|
+
console.log(`Build rejected: ${result.reason}`);
|
|
611
|
+
allSucceeded = false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (!allSucceeded) {
|
|
616
|
+
console.log('\nSome builds failed. Check the output above for details.');
|
|
617
|
+
process.exit(1);
|
|
618
|
+
} else {
|
|
619
|
+
console.log('\nAll builds completed successfully! 🎉');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
process.exit(0);
|
|
623
|
+
}
|
|
624
|
+
|
|
297
625
|
// todo (yoav): dev builds should include the branch name, and/or allow configuration via external config
|
|
298
|
-
|
|
626
|
+
// For now, assume single target build (we'll refactor for multi-target later)
|
|
627
|
+
const currentTarget = buildTargets[0];
|
|
628
|
+
const buildSubFolder = `${buildEnvironment}-${currentTarget.os}-${currentTarget.arch}`;
|
|
629
|
+
|
|
630
|
+
// Use target OS/ARCH for build logic (instead of current machine's OS/ARCH)
|
|
631
|
+
const targetOS = currentTarget.os;
|
|
632
|
+
const targetARCH = currentTarget.arch;
|
|
299
633
|
|
|
300
634
|
const buildFolder = join(projectRoot, config.build.buildFolder, buildSubFolder);
|
|
301
635
|
|
|
@@ -370,7 +704,7 @@ const appFileName = (
|
|
|
370
704
|
)
|
|
371
705
|
.replace(/\s/g, "")
|
|
372
706
|
.replace(/\./g, "-");
|
|
373
|
-
const bundleFileName =
|
|
707
|
+
const bundleFileName = targetOS === 'macos' ? `${appFileName}.app` : appFileName;
|
|
374
708
|
|
|
375
709
|
// const logPath = `/Library/Logs/Electrobun/ExampleApp/dev/out.log`;
|
|
376
710
|
|
|
@@ -380,6 +714,12 @@ if (commandArg === "init") {
|
|
|
380
714
|
// todo (yoav): init a repo folder structure
|
|
381
715
|
console.log("initializing electrobun project");
|
|
382
716
|
} else if (commandArg === "build") {
|
|
717
|
+
// Ensure core binaries are available for the target platform before starting build
|
|
718
|
+
await ensureCoreDependencies(currentTarget.os, currentTarget.arch);
|
|
719
|
+
|
|
720
|
+
// Get platform-specific paths for the current target
|
|
721
|
+
const targetPaths = getPlatformPaths(currentTarget.os, currentTarget.arch);
|
|
722
|
+
|
|
383
723
|
// refresh build folder
|
|
384
724
|
if (existsSync(buildFolder)) {
|
|
385
725
|
rmdirSync(buildFolder, { recursive: true });
|
|
@@ -403,7 +743,7 @@ if (commandArg === "init") {
|
|
|
403
743
|
appBundleMacOSPath,
|
|
404
744
|
appBundleFolderResourcesPath,
|
|
405
745
|
appBundleFolderFrameworksPath,
|
|
406
|
-
} = createAppBundle(appFileName, buildFolder);
|
|
746
|
+
} = createAppBundle(appFileName, buildFolder, targetOS);
|
|
407
747
|
|
|
408
748
|
const appBundleAppCodePath = join(appBundleFolderResourcesPath, "app");
|
|
409
749
|
|
|
@@ -473,29 +813,27 @@ if (commandArg === "init") {
|
|
|
473
813
|
// mkdirSync(destLauncherFolder, {recursive: true});
|
|
474
814
|
// }
|
|
475
815
|
// cpSync(zigLauncherBinarySource, zigLauncherDestination, {recursive: true, dereference: true});
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
// console.info('creating folder: ', destFolder);
|
|
486
|
-
mkdirSync(destLauncherFolder, { recursive: true });
|
|
487
|
-
}
|
|
816
|
+
// Only copy launcher for non-dev builds
|
|
817
|
+
if (buildEnvironment !== "dev") {
|
|
818
|
+
const bunCliLauncherBinarySource = targetPaths.LAUNCHER_RELEASE;
|
|
819
|
+
const bunCliLauncherDestination = join(appBundleMacOSPath, "launcher") + binExt;
|
|
820
|
+
const destLauncherFolder = dirname(bunCliLauncherDestination);
|
|
821
|
+
if (!existsSync(destLauncherFolder)) {
|
|
822
|
+
// console.info('creating folder: ', destFolder);
|
|
823
|
+
mkdirSync(destLauncherFolder, { recursive: true });
|
|
824
|
+
}
|
|
488
825
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
826
|
+
cpSync(bunCliLauncherBinarySource, bunCliLauncherDestination, {
|
|
827
|
+
recursive: true,
|
|
828
|
+
dereference: true,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
493
831
|
|
|
494
|
-
cpSync(
|
|
832
|
+
cpSync(targetPaths.MAIN_JS, join(appBundleMacOSPath, 'main.js'));
|
|
495
833
|
|
|
496
834
|
// Bun runtime binary
|
|
497
835
|
// todo (yoav): this only works for the current architecture
|
|
498
|
-
const bunBinarySourcePath =
|
|
836
|
+
const bunBinarySourcePath = targetPaths.BUN_BINARY;
|
|
499
837
|
// Note: .bin/bun binary in node_modules is a symlink to the versioned one in another place
|
|
500
838
|
// in node_modules, so we have to dereference here to get the actual binary in the bundle.
|
|
501
839
|
const bunBinaryDestInBundlePath = join(appBundleMacOSPath, "bun") + binExt;
|
|
@@ -507,8 +845,8 @@ if (commandArg === "init") {
|
|
|
507
845
|
cpSync(bunBinarySourcePath, bunBinaryDestInBundlePath, { dereference: true });
|
|
508
846
|
|
|
509
847
|
// copy native wrapper dynamic library
|
|
510
|
-
if (
|
|
511
|
-
const nativeWrapperMacosSource =
|
|
848
|
+
if (targetOS === 'macos') {
|
|
849
|
+
const nativeWrapperMacosSource = targetPaths.NATIVE_WRAPPER_MACOS;
|
|
512
850
|
const nativeWrapperMacosDestination = join(
|
|
513
851
|
appBundleMacOSPath,
|
|
514
852
|
"libNativeWrapper.dylib"
|
|
@@ -516,8 +854,8 @@ if (commandArg === "init") {
|
|
|
516
854
|
cpSync(nativeWrapperMacosSource, nativeWrapperMacosDestination, {
|
|
517
855
|
dereference: true,
|
|
518
856
|
});
|
|
519
|
-
} else if (
|
|
520
|
-
const nativeWrapperMacosSource =
|
|
857
|
+
} else if (targetOS === 'win') {
|
|
858
|
+
const nativeWrapperMacosSource = targetPaths.NATIVE_WRAPPER_WIN;
|
|
521
859
|
const nativeWrapperMacosDestination = join(
|
|
522
860
|
appBundleMacOSPath,
|
|
523
861
|
"libNativeWrapper.dll"
|
|
@@ -526,7 +864,7 @@ if (commandArg === "init") {
|
|
|
526
864
|
dereference: true,
|
|
527
865
|
});
|
|
528
866
|
|
|
529
|
-
const webview2LibSource =
|
|
867
|
+
const webview2LibSource = targetPaths.WEBVIEW2LOADER_WIN;
|
|
530
868
|
const webview2LibDestination = join(
|
|
531
869
|
appBundleMacOSPath,
|
|
532
870
|
"WebView2Loader.dll"
|
|
@@ -534,30 +872,34 @@ if (commandArg === "init") {
|
|
|
534
872
|
// copy webview2 system webview library
|
|
535
873
|
cpSync(webview2LibSource, webview2LibDestination);
|
|
536
874
|
|
|
537
|
-
} else if (
|
|
538
|
-
|
|
875
|
+
} else if (targetOS === 'linux') {
|
|
876
|
+
// Choose the appropriate native wrapper based on bundleCEF setting
|
|
877
|
+
const useCEF = config.build.linux?.bundleCEF;
|
|
878
|
+
const nativeWrapperLinuxSource = useCEF ? targetPaths.NATIVE_WRAPPER_LINUX_CEF : targetPaths.NATIVE_WRAPPER_LINUX;
|
|
539
879
|
const nativeWrapperLinuxDestination = join(
|
|
540
880
|
appBundleMacOSPath,
|
|
541
881
|
"libNativeWrapper.so"
|
|
542
882
|
);
|
|
883
|
+
|
|
543
884
|
if (existsSync(nativeWrapperLinuxSource)) {
|
|
544
885
|
cpSync(nativeWrapperLinuxSource, nativeWrapperLinuxDestination, {
|
|
545
886
|
dereference: true,
|
|
546
887
|
});
|
|
888
|
+
console.log(`Using ${useCEF ? 'CEF' : 'GTK'} native wrapper for Linux`);
|
|
889
|
+
} else {
|
|
890
|
+
throw new Error(`Native wrapper not found: ${nativeWrapperLinuxSource}`);
|
|
547
891
|
}
|
|
548
892
|
}
|
|
549
893
|
|
|
550
|
-
// Ensure core binaries are available
|
|
551
|
-
await ensureCoreDependencies();
|
|
552
894
|
|
|
553
895
|
// Download CEF binaries if needed when bundleCEF is enabled
|
|
554
|
-
if ((
|
|
555
|
-
(
|
|
556
|
-
(
|
|
896
|
+
if ((targetOS === 'macos' && config.build.mac?.bundleCEF) ||
|
|
897
|
+
(targetOS === 'win' && config.build.win?.bundleCEF) ||
|
|
898
|
+
(targetOS === 'linux' && config.build.linux?.bundleCEF)) {
|
|
557
899
|
|
|
558
|
-
await ensureCEFDependencies();
|
|
559
|
-
if (
|
|
560
|
-
const cefFrameworkSource =
|
|
900
|
+
await ensureCEFDependencies(currentTarget.os, currentTarget.arch);
|
|
901
|
+
if (targetOS === 'macos') {
|
|
902
|
+
const cefFrameworkSource = targetPaths.CEF_FRAMEWORK_MACOS;
|
|
561
903
|
const cefFrameworkDestination = join(
|
|
562
904
|
appBundleFolderFrameworksPath,
|
|
563
905
|
"Chromium Embedded Framework.framework"
|
|
@@ -578,7 +920,7 @@ if (commandArg === "init") {
|
|
|
578
920
|
"bun Helper (Renderer)",
|
|
579
921
|
];
|
|
580
922
|
|
|
581
|
-
const helperSourcePath =
|
|
923
|
+
const helperSourcePath = targetPaths.CEF_HELPER_MACOS;
|
|
582
924
|
cefHelperNames.forEach((helperName) => {
|
|
583
925
|
const destinationPath = join(
|
|
584
926
|
appBundleFolderFrameworksPath,
|
|
@@ -598,10 +940,9 @@ if (commandArg === "init") {
|
|
|
598
940
|
dereference: true,
|
|
599
941
|
});
|
|
600
942
|
});
|
|
601
|
-
} else if (
|
|
602
|
-
// Copy CEF DLLs from dist/cef/ to the main executable directory
|
|
603
|
-
const
|
|
604
|
-
const cefSourcePath = join(electrobunDistPath, "cef");
|
|
943
|
+
} else if (targetOS === 'win') {
|
|
944
|
+
// Copy CEF DLLs from platform-specific dist/cef/ to the main executable directory
|
|
945
|
+
const cefSourcePath = targetPaths.CEF_DIR;
|
|
605
946
|
const cefDllFiles = [
|
|
606
947
|
'libcef.dll',
|
|
607
948
|
'chrome_elf.dll',
|
|
@@ -641,7 +982,7 @@ if (commandArg === "init") {
|
|
|
641
982
|
});
|
|
642
983
|
|
|
643
984
|
// Copy CEF resources to MacOS/cef/ subdirectory for other resources like locales
|
|
644
|
-
const cefResourcesSource =
|
|
985
|
+
const cefResourcesSource = targetPaths.CEF_DIR;
|
|
645
986
|
const cefResourcesDestination = join(appBundleMacOSPath, 'cef');
|
|
646
987
|
|
|
647
988
|
if (existsSync(cefResourcesSource)) {
|
|
@@ -660,7 +1001,7 @@ if (commandArg === "init") {
|
|
|
660
1001
|
"bun Helper (Renderer)",
|
|
661
1002
|
];
|
|
662
1003
|
|
|
663
|
-
const helperSourcePath =
|
|
1004
|
+
const helperSourcePath = targetPaths.CEF_HELPER_WIN;
|
|
664
1005
|
if (existsSync(helperSourcePath)) {
|
|
665
1006
|
cefHelperNames.forEach((helperName) => {
|
|
666
1007
|
const destinationPath = join(appBundleMacOSPath, `${helperName}.exe`);
|
|
@@ -670,10 +1011,9 @@ if (commandArg === "init") {
|
|
|
670
1011
|
} else {
|
|
671
1012
|
console.log(`WARNING: Missing CEF helper: ${helperSourcePath}`);
|
|
672
1013
|
}
|
|
673
|
-
} else if (
|
|
674
|
-
// Copy CEF shared libraries from dist/cef/ to the main executable directory
|
|
675
|
-
const
|
|
676
|
-
const cefSourcePath = join(electrobunDistPath, "cef");
|
|
1014
|
+
} else if (targetOS === 'linux') {
|
|
1015
|
+
// Copy CEF shared libraries from platform-specific dist/cef/ to the main executable directory
|
|
1016
|
+
const cefSourcePath = targetPaths.CEF_DIR;
|
|
677
1017
|
|
|
678
1018
|
if (existsSync(cefSourcePath)) {
|
|
679
1019
|
const cefSoFiles = [
|
|
@@ -759,12 +1099,12 @@ if (commandArg === "init") {
|
|
|
759
1099
|
"bun Helper (Renderer)",
|
|
760
1100
|
];
|
|
761
1101
|
|
|
762
|
-
const helperSourcePath =
|
|
1102
|
+
const helperSourcePath = targetPaths.CEF_HELPER_LINUX;
|
|
763
1103
|
if (existsSync(helperSourcePath)) {
|
|
764
1104
|
cefHelperNames.forEach((helperName) => {
|
|
765
1105
|
const destinationPath = join(appBundleMacOSPath, helperName);
|
|
766
1106
|
cpSync(helperSourcePath, destinationPath);
|
|
767
|
-
console.log(`Copied CEF helper: ${helperName}`);
|
|
1107
|
+
// console.log(`Copied CEF helper: ${helperName}`);
|
|
768
1108
|
});
|
|
769
1109
|
} else {
|
|
770
1110
|
console.log(`WARNING: Missing CEF helper: ${helperSourcePath}`);
|
|
@@ -775,7 +1115,7 @@ if (commandArg === "init") {
|
|
|
775
1115
|
|
|
776
1116
|
|
|
777
1117
|
// copy native bindings
|
|
778
|
-
const bsPatchSource =
|
|
1118
|
+
const bsPatchSource = targetPaths.BSPATCH;
|
|
779
1119
|
const bsPatchDestination = join(appBundleMacOSPath, "bspatch") + binExt;
|
|
780
1120
|
const bsPatchDestFolder = dirname(bsPatchDestination);
|
|
781
1121
|
if (!existsSync(bsPatchDestFolder)) {
|
|
@@ -871,12 +1211,21 @@ if (commandArg === "init") {
|
|
|
871
1211
|
|
|
872
1212
|
// Run postBuild script
|
|
873
1213
|
if (config.scripts.postBuild) {
|
|
874
|
-
|
|
875
|
-
|
|
1214
|
+
// Use host platform's bun binary for running scripts, not target platform's
|
|
1215
|
+
const hostPaths = getPlatformPaths(OS, ARCH);
|
|
1216
|
+
|
|
1217
|
+
Bun.spawnSync([hostPaths.BUN_BINARY, config.scripts.postBuild], {
|
|
876
1218
|
stdio: ["ignore", "inherit", "inherit"],
|
|
877
1219
|
env: {
|
|
878
1220
|
...process.env,
|
|
879
1221
|
ELECTROBUN_BUILD_ENV: buildEnvironment,
|
|
1222
|
+
ELECTROBUN_OS: targetOS, // Use target OS for environment variables
|
|
1223
|
+
ELECTROBUN_ARCH: targetARCH, // Use target ARCH for environment variables
|
|
1224
|
+
ELECTROBUN_BUILD_DIR: buildFolder,
|
|
1225
|
+
ELECTROBUN_APP_NAME: appFileName,
|
|
1226
|
+
ELECTROBUN_APP_VERSION: config.app.version,
|
|
1227
|
+
ELECTROBUN_APP_IDENTIFIER: config.app.identifier,
|
|
1228
|
+
ELECTROBUN_ARTIFACT_DIR: artifactFolder,
|
|
880
1229
|
},
|
|
881
1230
|
});
|
|
882
1231
|
}
|
|
@@ -920,8 +1269,9 @@ if (commandArg === "init") {
|
|
|
920
1269
|
);
|
|
921
1270
|
|
|
922
1271
|
// todo (yoav): add these to config
|
|
1272
|
+
// Only codesign/notarize when building macOS targets on macOS host
|
|
923
1273
|
const shouldCodesign =
|
|
924
|
-
buildEnvironment !== "dev" && config.build.mac.codesign;
|
|
1274
|
+
buildEnvironment !== "dev" && targetOS === 'macos' && OS === 'macos' && config.build.mac.codesign;
|
|
925
1275
|
const shouldNotarize = shouldCodesign && config.build.mac.notarize;
|
|
926
1276
|
|
|
927
1277
|
if (shouldCodesign) {
|
|
@@ -956,6 +1306,8 @@ if (commandArg === "init") {
|
|
|
956
1306
|
// 6.5. code sign and notarize the dmg
|
|
957
1307
|
// 7. copy artifacts to directory [self-extractor dmg, zstd app bundle, bsdiff patch, update.json]
|
|
958
1308
|
|
|
1309
|
+
// Add platform suffix for all artifacts
|
|
1310
|
+
const platformSuffix = `-${targetOS}-${targetARCH}`;
|
|
959
1311
|
const tarPath = `${appBundleFolderPath}.tar`;
|
|
960
1312
|
|
|
961
1313
|
// tar the signed and notarized app bundle
|
|
@@ -979,7 +1331,8 @@ if (commandArg === "init") {
|
|
|
979
1331
|
// than saving 1 more MB of space/bandwidth.
|
|
980
1332
|
|
|
981
1333
|
const compressedTarPath = `${tarPath}.zst`;
|
|
982
|
-
|
|
1334
|
+
const platformCompressedTarPath = compressedTarPath.replace('.tar.zst', `${platformSuffix}.tar.zst`);
|
|
1335
|
+
artifactsToUpload.push(platformCompressedTarPath);
|
|
983
1336
|
|
|
984
1337
|
// zstd compress tarball
|
|
985
1338
|
// todo (yoav): consider using c bindings for zstd for speed instead of wasm
|
|
@@ -988,23 +1341,28 @@ if (commandArg === "init") {
|
|
|
988
1341
|
await ZstdInit().then(async ({ ZstdSimple, ZstdStream }) => {
|
|
989
1342
|
// Note: Simple is much faster than stream, but stream is better for large files
|
|
990
1343
|
// todo (yoav): consider a file size cutoff to switch to stream instead of simple.
|
|
1344
|
+
const useStream = tarball.size > 100 * 1024 * 1024;
|
|
1345
|
+
|
|
991
1346
|
if (tarball.size > 0) {
|
|
992
|
-
|
|
1347
|
+
// Uint8 array filestream of the tar file
|
|
993
1348
|
|
|
994
1349
|
const data = new Uint8Array(tarBuffer);
|
|
1350
|
+
|
|
995
1351
|
const compressionLevel = 22;
|
|
996
1352
|
const compressedData = ZstdSimple.compress(data, compressionLevel);
|
|
997
1353
|
|
|
998
1354
|
console.log(
|
|
999
1355
|
"compressed",
|
|
1000
|
-
|
|
1356
|
+
data.length,
|
|
1001
1357
|
"bytes",
|
|
1002
1358
|
"from",
|
|
1003
|
-
|
|
1359
|
+
tarBuffer.byteLength,
|
|
1004
1360
|
"bytes"
|
|
1005
1361
|
);
|
|
1006
1362
|
|
|
1007
1363
|
await Bun.write(compressedTarPath, compressedData);
|
|
1364
|
+
// Copy to platform-specific filename for upload
|
|
1365
|
+
cpSync(compressedTarPath, platformCompressedTarPath);
|
|
1008
1366
|
}
|
|
1009
1367
|
});
|
|
1010
1368
|
|
|
@@ -1012,7 +1370,7 @@ if (commandArg === "init") {
|
|
|
1012
1370
|
// now and it needs the same name as the original app bundle.
|
|
1013
1371
|
rmdirSync(appBundleFolderPath, { recursive: true });
|
|
1014
1372
|
|
|
1015
|
-
const selfExtractingBundle = createAppBundle(appFileName, buildFolder);
|
|
1373
|
+
const selfExtractingBundle = createAppBundle(appFileName, buildFolder, targetOS);
|
|
1016
1374
|
const compressedTarballInExtractingBundlePath = join(
|
|
1017
1375
|
selfExtractingBundle.appBundleFolderResourcesPath,
|
|
1018
1376
|
`${hash}.tar.zst`
|
|
@@ -1021,7 +1379,7 @@ if (commandArg === "init") {
|
|
|
1021
1379
|
// copy the zstd tarball to the self-extracting app bundle
|
|
1022
1380
|
cpSync(compressedTarPath, compressedTarballInExtractingBundlePath);
|
|
1023
1381
|
|
|
1024
|
-
const selfExtractorBinSourcePath =
|
|
1382
|
+
const selfExtractorBinSourcePath = targetPaths.EXTRACTOR;
|
|
1025
1383
|
const selfExtractorBinDestinationPath = join(
|
|
1026
1384
|
selfExtractingBundle.appBundleMacOSPath,
|
|
1027
1385
|
"launcher"
|
|
@@ -1053,28 +1411,49 @@ if (commandArg === "init") {
|
|
|
1053
1411
|
console.log("skipping notarization");
|
|
1054
1412
|
}
|
|
1055
1413
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1414
|
+
// DMG creation for macOS only
|
|
1415
|
+
if (targetOS === 'macos') {
|
|
1416
|
+
console.log("creating dmg...");
|
|
1417
|
+
// make a dmg
|
|
1418
|
+
const dmgPath = join(buildFolder, `${appFileName}.dmg`);
|
|
1419
|
+
const platformDmgPath = join(buildFolder, `${appFileName}${platformSuffix}.dmg`);
|
|
1420
|
+
artifactsToUpload.push(platformDmgPath);
|
|
1421
|
+
// hdiutil create -volname "YourAppName" -srcfolder /path/to/YourApp.app -ov -format UDZO YourAppName.dmg
|
|
1422
|
+
// Note: use ULFO (lzfse) for better compatibility with large CEF frameworks and modern macOS
|
|
1423
|
+
execSync(
|
|
1424
|
+
`hdiutil create -volname "${appFileName}" -srcfolder ${escapePathForTerminal(
|
|
1425
|
+
appBundleFolderPath
|
|
1426
|
+
)} -ov -format ULFO ${escapePathForTerminal(dmgPath)}`
|
|
1427
|
+
);
|
|
1067
1428
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1429
|
+
if (shouldCodesign) {
|
|
1430
|
+
codesignAppBundle(dmgPath);
|
|
1431
|
+
} else {
|
|
1432
|
+
console.log("skipping codesign");
|
|
1433
|
+
}
|
|
1073
1434
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1435
|
+
if (shouldNotarize) {
|
|
1436
|
+
notarizeAndStaple(dmgPath);
|
|
1437
|
+
} else {
|
|
1438
|
+
console.log("skipping notarization");
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// Copy to platform-specific filename
|
|
1442
|
+
cpSync(dmgPath, platformDmgPath);
|
|
1076
1443
|
} else {
|
|
1077
|
-
|
|
1444
|
+
// For Windows and Linux, add the self-extracting bundle directly
|
|
1445
|
+
const platformBundlePath = join(buildFolder, `${appFileName}${platformSuffix}${targetOS === 'win' ? '.exe' : ''}`);
|
|
1446
|
+
// Copy the self-extracting bundle to platform-specific filename
|
|
1447
|
+
if (targetOS === 'win') {
|
|
1448
|
+
// On Windows, create a self-extracting exe
|
|
1449
|
+
// For now, just copy the bundle folder
|
|
1450
|
+
artifactsToUpload.push(compressedTarPath.replace('.tar.zst', `${platformSuffix}.tar.zst`));
|
|
1451
|
+
} else if (targetOS === 'linux') {
|
|
1452
|
+
// On Linux, create a tar.gz of the bundle
|
|
1453
|
+
const linuxTarPath = join(buildFolder, `${appFileName}${platformSuffix}.tar.gz`);
|
|
1454
|
+
execSync(`tar -czf ${escapePathForTerminal(linuxTarPath)} -C ${escapePathForTerminal(buildFolder)} ${escapePathForTerminal(basename(appBundleFolderPath))}`);
|
|
1455
|
+
artifactsToUpload.push(linuxTarPath);
|
|
1456
|
+
}
|
|
1078
1457
|
}
|
|
1079
1458
|
|
|
1080
1459
|
// refresh artifacts folder
|
|
@@ -1093,39 +1472,49 @@ if (commandArg === "init") {
|
|
|
1093
1472
|
// the download button or display on your marketing site or in the app.
|
|
1094
1473
|
version: config.app.version,
|
|
1095
1474
|
hash: hash.toString(),
|
|
1475
|
+
platform: OS,
|
|
1476
|
+
arch: ARCH,
|
|
1096
1477
|
// channel: buildEnvironment,
|
|
1097
1478
|
// bucketUrl: config.release.bucketUrl
|
|
1098
1479
|
});
|
|
1099
1480
|
|
|
1100
|
-
|
|
1481
|
+
// Platform-specific update.json
|
|
1482
|
+
const platformUpdateJsonName = `update${platformSuffix}.json`;
|
|
1483
|
+
await Bun.write(join(artifactFolder, platformUpdateJsonName), updateJsonContent);
|
|
1101
1484
|
|
|
1102
1485
|
// generate bsdiff
|
|
1103
1486
|
// https://storage.googleapis.com/eggbun-static/electrobun-playground/canary/ElectrobunPlayground-canary.app.tar.zst
|
|
1104
1487
|
console.log("bucketUrl: ", config.release.bucketUrl);
|
|
1105
1488
|
|
|
1106
1489
|
console.log("generating a patch from the previous version...");
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1490
|
+
|
|
1491
|
+
// Skip patch generation if bucketUrl is not configured
|
|
1492
|
+
if (!config.release.bucketUrl || config.release.bucketUrl.trim() === '') {
|
|
1493
|
+
console.log("No bucketUrl configured, skipping patch generation");
|
|
1494
|
+
console.log("To enable patch generation, configure bucketUrl in your electrobun.config");
|
|
1495
|
+
} else {
|
|
1496
|
+
const urlToPrevUpdateJson = join(
|
|
1497
|
+
config.release.bucketUrl,
|
|
1498
|
+
buildEnvironment,
|
|
1499
|
+
`update${platformSuffix}.json`
|
|
1500
|
+
);
|
|
1501
|
+
const cacheBuster = Math.random().toString(36).substring(7);
|
|
1502
|
+
const updateJsonResponse = await fetch(
|
|
1503
|
+
urlToPrevUpdateJson + `?${cacheBuster}`
|
|
1504
|
+
).catch((err) => {
|
|
1505
|
+
console.log("bucketURL not found: ", err);
|
|
1506
|
+
});
|
|
1118
1507
|
|
|
1119
1508
|
const urlToLatestTarball = join(
|
|
1120
1509
|
config.release.bucketUrl,
|
|
1121
1510
|
buildEnvironment,
|
|
1122
|
-
`${appFileName}.app.tar.zst`
|
|
1511
|
+
`${appFileName}.app${platformSuffix}.tar.zst`
|
|
1123
1512
|
);
|
|
1124
1513
|
|
|
1125
1514
|
|
|
1126
1515
|
// attempt to get the previous version to create a patch file
|
|
1127
|
-
if (updateJsonResponse.ok) {
|
|
1128
|
-
const prevUpdateJson = await updateJsonResponse
|
|
1516
|
+
if (updateJsonResponse && updateJsonResponse.ok) {
|
|
1517
|
+
const prevUpdateJson = await updateJsonResponse!.json();
|
|
1129
1518
|
|
|
1130
1519
|
const prevHash = prevUpdateJson.hash;
|
|
1131
1520
|
console.log("PREVIOUS HASH", prevHash);
|
|
@@ -1164,9 +1553,10 @@ if (commandArg === "init") {
|
|
|
1164
1553
|
console.log("diff previous and new tarballs...");
|
|
1165
1554
|
// Run it as a separate process to leverage multi-threadedness
|
|
1166
1555
|
// especially for creating multiple diffs in parallel
|
|
1167
|
-
const bsdiffpath =
|
|
1556
|
+
const bsdiffpath = targetPaths.BSDIFF;
|
|
1168
1557
|
const patchFilePath = join(buildFolder, `${prevHash}.patch`);
|
|
1169
|
-
|
|
1558
|
+
const platformPatchFilePath = join(buildFolder, `${prevHash}${platformSuffix}.patch`);
|
|
1559
|
+
artifactsToUpload.push(platformPatchFilePath);
|
|
1170
1560
|
const result = Bun.spawnSync(
|
|
1171
1561
|
[bsdiffpath, prevTarballPath, tarPath, patchFilePath, "--use-zstd"],
|
|
1172
1562
|
{ cwd: buildFolder }
|
|
@@ -1176,11 +1566,14 @@ if (commandArg === "init") {
|
|
|
1176
1566
|
result.stdout.toString(),
|
|
1177
1567
|
result.stderr.toString()
|
|
1178
1568
|
);
|
|
1569
|
+
// Copy to platform-specific filename
|
|
1570
|
+
cpSync(patchFilePath, platformPatchFilePath);
|
|
1179
1571
|
}
|
|
1180
1572
|
} else {
|
|
1181
1573
|
console.log("prevoius version not found at: ", urlToLatestTarball);
|
|
1182
1574
|
console.log("skipping diff generation");
|
|
1183
1575
|
}
|
|
1576
|
+
} // End of bucketUrl validation block
|
|
1184
1577
|
|
|
1185
1578
|
// compress all the upload files
|
|
1186
1579
|
console.log("copying artifacts...");
|
|
@@ -1209,10 +1602,7 @@ if (commandArg === "init") {
|
|
|
1209
1602
|
// todo (yoav): rename to start
|
|
1210
1603
|
|
|
1211
1604
|
// run the project in dev mode
|
|
1212
|
-
// this runs the
|
|
1213
|
-
// there is another copy of the cli in the app bundle that will execute the app
|
|
1214
|
-
// the two cli processes communicate via named pipes and together manage the dev
|
|
1215
|
-
// lifecycle and debug functionality
|
|
1605
|
+
// this runs the bundled bun binary with main.js directly
|
|
1216
1606
|
|
|
1217
1607
|
// Note: this cli will be a bun single-file-executable
|
|
1218
1608
|
// Note: we want to use the version of bun that's packaged with electrobun
|
|
@@ -1290,7 +1680,12 @@ function getConfig() {
|
|
|
1290
1680
|
if (existsSync(configPath)) {
|
|
1291
1681
|
const configFileContents = readFileSync(configPath, "utf8");
|
|
1292
1682
|
// Note: we want this to hard fail if there's a syntax error
|
|
1293
|
-
|
|
1683
|
+
try {
|
|
1684
|
+
loadedConfig = JSON.parse(configFileContents);
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
console.error("Failed to parse config file:", error);
|
|
1687
|
+
console.error("using default config instead");
|
|
1688
|
+
}
|
|
1294
1689
|
}
|
|
1295
1690
|
|
|
1296
1691
|
// todo (yoav): write a deep clone fn
|
|
@@ -1312,6 +1707,18 @@ function getConfig() {
|
|
|
1312
1707
|
...(loadedConfig?.build?.mac?.entitlements || {}),
|
|
1313
1708
|
},
|
|
1314
1709
|
},
|
|
1710
|
+
win: {
|
|
1711
|
+
...defaultConfig.build.win,
|
|
1712
|
+
...(loadedConfig?.build?.win || {}),
|
|
1713
|
+
},
|
|
1714
|
+
linux: {
|
|
1715
|
+
...defaultConfig.build.linux,
|
|
1716
|
+
...(loadedConfig?.build?.linux || {}),
|
|
1717
|
+
},
|
|
1718
|
+
bun: {
|
|
1719
|
+
...defaultConfig.build.bun,
|
|
1720
|
+
...(loadedConfig?.build?.bun || {}),
|
|
1721
|
+
}
|
|
1315
1722
|
},
|
|
1316
1723
|
scripts: {
|
|
1317
1724
|
...defaultConfig.scripts,
|
|
@@ -1473,8 +1880,8 @@ function notarizeAndStaple(appOrDmgPath: string) {
|
|
|
1473
1880
|
// 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
1881
|
// either way you can pass in the parent folder here for that flexibility.
|
|
1475
1882
|
// 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 (
|
|
1883
|
+
function createAppBundle(bundleName: string, parentFolder: string, targetOS: 'macos' | 'win' | 'linux') {
|
|
1884
|
+
if (targetOS === 'macos') {
|
|
1478
1885
|
// macOS bundle structure
|
|
1479
1886
|
const bundleFileName = `${bundleName}.app`;
|
|
1480
1887
|
const appBundleFolderPath = join(parentFolder, bundleFileName);
|
|
@@ -1501,7 +1908,7 @@ function createAppBundle(bundleName: string, parentFolder: string) {
|
|
|
1501
1908
|
appBundleFolderResourcesPath,
|
|
1502
1909
|
appBundleFolderFrameworksPath,
|
|
1503
1910
|
};
|
|
1504
|
-
} else if (
|
|
1911
|
+
} else if (targetOS === 'linux' || targetOS === 'win') {
|
|
1505
1912
|
// Linux/Windows simpler structure
|
|
1506
1913
|
const appBundleFolderPath = join(parentFolder, bundleName);
|
|
1507
1914
|
const appBundleFolderContentsPath = appBundleFolderPath; // No Contents folder needed
|
|
@@ -1522,6 +1929,6 @@ function createAppBundle(bundleName: string, parentFolder: string) {
|
|
|
1522
1929
|
appBundleFolderFrameworksPath,
|
|
1523
1930
|
};
|
|
1524
1931
|
} else {
|
|
1525
|
-
throw new Error(`Unsupported OS: ${
|
|
1932
|
+
throw new Error(`Unsupported OS: ${targetOS}`);
|
|
1526
1933
|
}
|
|
1527
1934
|
}
|