electrobun 1.18.1 → 1.18.4-beta.10

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.
Files changed (64) hide show
  1. package/README.md +37 -14
  2. package/dash.config.ts +28 -0
  3. package/dist/api/browser/global.d.ts +6 -0
  4. package/dist/api/browser/index.ts +162 -44
  5. package/dist/api/{bun → config}/ElectrobunConfig.ts +109 -40
  6. package/dist/api/config/validate.test.ts +31 -0
  7. package/dist/api/config/validate.ts +19 -0
  8. package/dist/api/preload/.generated/compiled.ts +8 -0
  9. package/dist/api/{bun/preload → preload}/build.ts +6 -6
  10. package/dist/api/{bun/preload → preload}/encryption.ts +11 -5
  11. package/dist/api/{bun/preload → preload}/globals.d.ts +8 -0
  12. package/dist/api/{bun/preload → preload}/index.ts +18 -8
  13. package/dist/api/{bun/preload → preload}/webviewTag.ts +32 -3
  14. package/dist/api/{bun/preload → preload}/wgpuTag.ts +33 -2
  15. package/dist/api/{bun → sdks/bun}/__tests__/ffi-contract.test.ts +6 -9
  16. package/dist/api/{bun → sdks/bun}/core/BrowserView.ts +96 -55
  17. package/dist/api/{bun → sdks/bun}/core/BrowserWindow.ts +20 -30
  18. package/dist/api/{bun → sdks/bun}/core/BuildConfig.ts +32 -5
  19. package/dist/api/{bun → sdks/bun}/core/GpuWindow.ts +20 -7
  20. package/dist/api/sdks/bun/core/Socket.ts +22 -0
  21. package/dist/api/{bun → sdks/bun}/core/Tray.ts +33 -32
  22. package/dist/api/{bun → sdks/bun}/core/Updater.ts +10 -10
  23. package/dist/api/{bun → sdks/bun}/core/Utils.ts +3 -5
  24. package/dist/api/{bun → sdks/bun}/core/WGPUView.ts +43 -13
  25. package/dist/api/{bun → sdks/bun}/index.ts +39 -10
  26. package/dist/api/{bun → sdks/bun}/proc/native.ts +942 -746
  27. package/dist/api/{bun → sdks/bun}/webGPU.ts +1 -1
  28. package/dist/api/{bun → sdks/bun}/webgpuAdapter.ts +40 -33
  29. package/dist/api/shared/build-dependencies.test.ts +48 -0
  30. package/dist/api/shared/build-dependencies.ts +46 -0
  31. package/dist/api/shared/go-version.ts +3 -0
  32. package/dist/api/shared/odin-version.ts +6 -0
  33. package/dist/api/shared/rust-version.ts +3 -0
  34. package/dist/go-sdk/callbacks.go +260 -0
  35. package/dist/go-sdk/electrobun.go +2021 -0
  36. package/dist/main.js +35 -28
  37. package/dist/odin-sdk/electrobun/electrobun.odin +2337 -0
  38. package/dist/preload-full.js +948 -0
  39. package/dist/preload-sandboxed.js +111 -0
  40. package/dist/rust-sdk/electrobun.rs +2396 -0
  41. package/dist/zig-sdk/electrobun.zig +2005 -0
  42. package/package.json +5 -29
  43. package/src/cli/index.ts +868 -654
  44. package/bin/electrobun.cjs +0 -169
  45. package/dist/api/bun/core/Socket.ts +0 -205
  46. package/dist/api/bun/core/windowIds.ts +0 -5
  47. package/dist/api/bun/preload/.generated/compiled.ts +0 -8
  48. package/dist/api/shared/bun-version.ts +0 -3
  49. /package/dist/api/{bun/preload → preload}/dragRegions.ts +0 -0
  50. /package/dist/api/{bun/preload → preload}/events.ts +0 -0
  51. /package/dist/api/{bun/preload → preload}/index-sandboxed.ts +0 -0
  52. /package/dist/api/{bun/preload → preload}/internalRpc.ts +0 -0
  53. /package/dist/api/{bun/preload → preload}/overlaySync.ts +0 -0
  54. /package/dist/api/{bun → sdks/bun}/core/ApplicationMenu.ts +0 -0
  55. /package/dist/api/{bun → sdks/bun}/core/ContextMenu.ts +0 -0
  56. /package/dist/api/{bun → sdks/bun}/core/Paths.ts +0 -0
  57. /package/dist/api/{bun → sdks/bun}/core/menuRoles.ts +0 -0
  58. /package/dist/api/{bun → sdks/bun}/events/ApplicationEvents.ts +0 -0
  59. /package/dist/api/{bun → sdks/bun}/events/event.ts +0 -0
  60. /package/dist/api/{bun → sdks/bun}/events/eventEmitter.ts +0 -0
  61. /package/dist/api/{bun → sdks/bun}/events/trayEvents.ts +0 -0
  62. /package/dist/api/{bun → sdks/bun}/events/webviewEvents.ts +0 -0
  63. /package/dist/api/{bun → sdks/bun}/events/windowEvents.ts +0 -0
  64. /package/dist/api/{bun → sdks/bun}/proc/linux.md +0 -0
package/README.md CHANGED
@@ -14,9 +14,9 @@
14
14
  ## What is Electrobun?
15
15
 
16
16
  Electrobun aims to be a complete **solution-in-a-box** for building, updating, and shipping ultra fast, tiny, and cross-platform desktop applications written in Typescript.
17
- Under the hood it uses <a href="https://bun.sh">bun</a> to execute the main process and to bundle webview typescript, and has native bindings written in Objc, C++, and several core parts written in <a href="https://ziglang.org/">zig</a>.
17
+ Under the hood it uses Cottontail, Electrobun's JSC-based JavaScript runtime, to execute the main process and bundle webview TypeScript. Native bindings are written in ObjC and C++, with several core components written in <a href="https://ziglang.org/">Zig</a>.
18
18
 
19
- Visit <a href="https://docs.electrobunny.ai/electrobun/">https://docs.electrobunny.ai/electrobun/</a> to see api documentation, guides, and more.
19
+ Visit <a href="https://framework.blackboard.sh/electrobun/">https://framework.blackboard.sh/electrobun/</a> to see api documentation, guides, and more.
20
20
 
21
21
  You use it via npm.
22
22
 
@@ -25,7 +25,7 @@ Don't miss our:
25
25
  - zig optimized BSDIFF implementation that lets you ship tiny app updates as small as 4KB
26
26
  - `bundleCEF` flag to bundle and pin Chromium for those that want that tradeoff of consistency over file size
27
27
  - `bundleWGPU` that lets you use Bun Typescript -> WGPU to control a native GPU surface without a webview
28
- - Our Three.js and Babylon.js adapters that work right in Bun
28
+ - Our Three.js and Babylon.js adapters that work directly in the Cottontail main process
29
29
  - Our `<electrobun-webview>` and `<electrobun-wpgu>` html elements that let you composit proper OOPIFs and native GPU surfaces into your UIs
30
30
  - so much more.
31
31
 
@@ -33,7 +33,7 @@ Don't miss our:
33
33
 
34
34
  - Write typescript for the main process and webviews without having to think about it.
35
35
  - Isolation between main and webview processes with fast, typed, easy to implement RPC between them.
36
- - Small self-extracting app bundles ~14MB (when using system webview, most of this is the bun runtime)
36
+ - Small self-extracting app bundles ~14MB (when using the system webview, most of this is the Cottontail/JSC runtime)
37
37
  - Even smaller app updates as small as 4KB (using bsdiff it only downloads tiny patches between versions)
38
38
  - Provide everything you need in one tightly integrated workflow to start writing code in 5 minutes and distribute in 10.
39
39
 
@@ -63,9 +63,11 @@ Don't miss our:
63
63
  - [GOG Achievements GUI](https://github.com/timendum/gog-achievements-gui) - desktop app for managing GOG achievements
64
64
  - [groov](https://github.com/laurenzcodes/groov) - desktop audio deck monitor
65
65
  - [Guerilla Glass](https://github.com/okikeSolutions/guerillaglass) - open-source cross-platform creator studio for fast Record -> Edit -> Deliver workflows
66
+ - [Invoke](https://getinvoke.com) - macOS UI automation & shortcut platform
66
67
  - [Marginalia](https://github.com/lars-hoeijmans/Marginalia) - a simple note taking app
67
68
  - [MarkBun](https://github.com/xiaochong/markbun) - fast, beautiful, Typora-like markdown desktop editor
68
69
  - [md-browse](https://github.com/needle-tools/md-browse) - a markdown-first browser that converts web pages to clean markdown
70
+ - [moop](https://github.com/zrubinrattet/moop/) - desktop app for batch image optimization for the web
69
71
  - [Patchline](https://github.com/adwaithks/Patchline) - lightweight desktop Git client for reading patches and line diffs, then staging and committing changes
70
72
  - [peekachu](https://github.com/needle-tools/peekachu) - password manager for AIs; store secrets in your OS keychain and scrub output so AI assistants never see actual values
71
73
  - [PiBun](https://github.com/khairold/pibun) - desktop GUI for the Pi coding agent with chat, terminal, git integration, and plugin system
@@ -113,6 +115,18 @@ Building apps with Electrobun is as easy as updating your package.json dependenc
113
115
 
114
116
  ### Prerequisites
115
117
 
118
+ Install Dash globally before building Electrobun:
119
+
120
+ ```bash
121
+ curl -fsSL https://electrobun-artifacts.blackboard.sh/dash-cli/install.sh | sh
122
+ ```
123
+
124
+ On Windows PowerShell:
125
+
126
+ ```powershell
127
+ & ([scriptblock]::Create((irm https://electrobun-artifacts.blackboard.sh/dash-cli/install.ps1)))
128
+ ```
129
+
116
130
  **macOS:**
117
131
  - Xcode command line tools
118
132
  - cmake (install via homebrew: `brew install cmake`)
@@ -133,8 +147,8 @@ On Ubuntu/Debian based distros: `sudo apt install build-essential cmake pkg-conf
133
147
  ```bash
134
148
  git clone --recurse-submodules https://github.com/blackboardsh/electrobun.git
135
149
  cd electrobun/package
136
- bun install
137
- bun dev:clean
150
+ dash install
151
+ dash dev:clean
138
152
  ```
139
153
 
140
154
  ### Development Workflow
@@ -144,22 +158,31 @@ bun dev:clean
144
158
  cd electrobun/package
145
159
 
146
160
  # After making changes to source code
147
- bun dev
148
-
149
- # If you only changed kitchen sink code (not electrobun source)
150
- bun dev:rerun
161
+ dash dev
151
162
 
152
163
  # If you need a completely fresh start
153
- bun dev:clean
164
+ dash dev:clean
165
+ ```
166
+
167
+ With sibling `jsc`, `cottontail`, `dash-cloud`, and `electrobun` checkouts, use
168
+ one command to build only changed native layers and run Kitchen with the local
169
+ stack:
170
+
171
+ ```bash
172
+ dash dev --local
154
173
  ```
155
174
 
175
+ The first Dash is globally installed. Stack preparation explicitly selects the
176
+ completed local Dash engine and Cottontail build for the remainder of the
177
+ command.
178
+
156
179
  ### Additional Commands
157
180
 
158
181
  All commands are run from the `/package` directory:
159
182
 
160
- - `bun dev:canary` - Build and run kitchen sink in canary mode
161
- - `bun build:dev` - Build electrobun in development mode
162
- - `bun build:release` - Build electrobun in release mode
183
+ - `dash dev:canary` - Build and run kitchen sink in canary mode
184
+ - `dash build:dev` - Build Electrobun in development mode
185
+ - `dash build:release` - Build Electrobun in release mode
163
186
 
164
187
  ### Debugging
165
188
 
package/dash.config.ts ADDED
@@ -0,0 +1,28 @@
1
+ // @dash cli=0.2.0-canary.2 cottontail=0.1.1-canary.2
2
+ export default {
3
+ scripts: {
4
+ start: "dash src/sdks/bun/index.ts",
5
+ "check-zig-version": "vendors/zig/zig version",
6
+ "check-rust-version": "vendors/rust/bin/rustc --version",
7
+ "build:dev": "dash build.ts",
8
+ "build:local": "scripts/build-local.js",
9
+ "build:release": "dash build.ts --release",
10
+ dev: "scripts/dev.ts",
11
+ "dev:test": "scripts/dev.test.ts",
12
+ "dev:clean": "cd ../kitchen && rm -rf node_modules vendors/cef && cd ../package && dash dev",
13
+ "dev:canary": "dash install && dash build:release && cd ../kitchen && dash install && dash electrobun build --env=canary",
14
+ "dev:production": "dash install && dash build:release && cd ../kitchen && dash install && dash electrobun build --env=production",
15
+ "build:docs:release": "cd ../docs && dash run build",
16
+ "npm:publish": "dash build:release && npm publish",
17
+ "npm:publish:beta": "dash build:release && npm publish --tag beta",
18
+ typecheck: "./node_modules/.bin/tsc --noEmit && cd ../kitchen && ./node_modules/.bin/tsc --noEmit",
19
+ "push:beta": "dash typecheck && node scripts/push-version.js beta",
20
+ "push:patch": "dash typecheck && node scripts/push-version.js patch",
21
+ "push:minor": "dash typecheck && node scripts/push-version.js minor",
22
+ "push:major": "dash typecheck && node scripts/push-version.js major",
23
+ "push:stable": "dash typecheck && node scripts/push-version.js stable",
24
+ "build:push:artifacts": "node scripts/build-and-upload-artifacts.js",
25
+ "test:unit": "dash test src/shared src/sdks/bun src/config",
26
+ "bump-cef": "dash scripts/update-cef-version.ts",
27
+ },
28
+ };
@@ -7,6 +7,8 @@ interface ElectrobunEncryptResult {
7
7
  }
8
8
 
9
9
  interface ElectrobunBridge {
10
+ receiveMessageFromHost: (msg: unknown) => void;
11
+ receiveInternalMessageFromHost: (msg: unknown) => void;
10
12
  receiveMessageFromBun: (msg: unknown) => void;
11
13
  receiveInternalMessageFromBun: (msg: unknown) => void;
12
14
  }
@@ -20,10 +22,14 @@ declare global {
20
22
  __electrobunWebviewId: number;
21
23
  __electrobunWindowId: number;
22
24
  __electrobunRpcSocketPort: number;
25
+ __electrobunHostSocketPort?: number;
26
+ __electrobunPlaintextHostSocket?: boolean;
23
27
  __electrobun?: ElectrobunBridge;
28
+ __electrobunPendingHostMessages?: unknown[];
24
29
  __electrobun_encrypt: (msg: string) => Promise<ElectrobunEncryptResult>;
25
30
  __electrobun_decrypt: (encryptedData: string, iv: string, tag: string) => Promise<string>;
26
31
  __electrobunInternalBridge?: MessageHandler;
32
+ __electrobunHostBridge?: MessageHandler;
27
33
  __electrobunBunBridge?: MessageHandler;
28
34
  }
29
35
  }
@@ -15,13 +15,27 @@ import { type WgpuTagElement, type WgpuEventTypes } from "./wgputag";
15
15
  import "./global.d.ts";
16
16
 
17
17
  const WEBVIEW_ID = window.__electrobunWebviewId;
18
- const RPC_SOCKET_PORT = window.__electrobunRpcSocketPort;
18
+ const HOST_SOCKET_PORT =
19
+ window.__electrobunHostSocketPort ?? window.__electrobunRpcSocketPort;
19
20
 
20
21
  class Electroview<T extends RPCWithTransport> {
21
- bunSocket?: WebSocket;
22
+ hostSocket?: WebSocket;
23
+ hostSocketCanSend = false;
24
+ pendingHostSocketMessages: string[] = [];
25
+ flushingHostSocketMessages = false;
26
+ hostSocketSendQueue: string[] = [];
27
+ flushingHostSocketSendQueue = false;
22
28
  // user's custom rpc browser <-> bun
23
29
  rpc?: T;
24
30
  rpcHandler?: (msg: unknown) => void;
31
+ carrots = {
32
+ invoke: <R = unknown>(
33
+ carrotId: string,
34
+ method: string,
35
+ params?: unknown,
36
+ options?: { windowId?: string },
37
+ ) => this.invokeCarrot<R>(carrotId, method, params, options),
38
+ };
25
39
 
26
40
  constructor(config: { rpc: T }) {
27
41
  this.rpc = config.rpc;
@@ -29,49 +43,65 @@ class Electroview<T extends RPCWithTransport> {
29
43
  }
30
44
 
31
45
  init() {
32
- this.initSocketToBun();
46
+ this.initSocketToHost();
33
47
 
34
- // Set up handler for user RPC messages from bun
35
- // Note: receiveInternalMessageFromBun is set up by the preload script
36
- window.__electrobun!.receiveMessageFromBun =
37
- this.receiveMessageFromBun.bind(this);
48
+ // Set up handler for user RPC messages from the host runtime.
49
+ const hostMessageHandler = this.receiveMessageFromHost.bind(this);
50
+ window.__electrobun!.receiveMessageFromHost = hostMessageHandler;
51
+ window.__electrobun!.receiveMessageFromBun = hostMessageHandler;
38
52
 
39
53
  if (this.rpc) {
40
54
  this.rpc.setTransport(this.createTransport());
41
55
  }
56
+
57
+ const pendingMessages = window.__electrobunPendingHostMessages;
58
+ if (pendingMessages?.length) {
59
+ window.__electrobunPendingHostMessages = [];
60
+ for (const message of pendingMessages) {
61
+ hostMessageHandler(message);
62
+ }
63
+ }
42
64
  }
43
65
 
44
- initSocketToBun() {
66
+ initSocketToHost() {
45
67
  // Skip native socket when running in a remote browser (no port/webview ID)
46
- if (!RPC_SOCKET_PORT || !WEBVIEW_ID) {
68
+ if (!HOST_SOCKET_PORT || !WEBVIEW_ID) {
47
69
  return;
48
70
  }
49
71
 
50
- // Note: Using ws:// for localhost is intentional - all RPC messages are
72
+ // Note: Using ws:// for loopback is intentional - all RPC messages are
51
73
  // encrypted with per-webview AES-GCM keys, making TLS redundant
52
74
  const socket = new WebSocket(
53
- `ws://localhost:${RPC_SOCKET_PORT}/socket?webviewId=${WEBVIEW_ID}`,
75
+ `ws://127.0.0.1:${HOST_SOCKET_PORT}/socket?webviewId=${WEBVIEW_ID}`,
54
76
  );
55
77
 
56
- this.bunSocket = socket;
78
+ this.hostSocket = socket;
57
79
 
58
80
  socket.addEventListener("open", () => {
59
- // this.bunSocket?.send("Hello from webview " + WEBVIEW_ID);
81
+ this.hostSocketCanSend = true;
82
+ void this.flushPendingHostSocketMessages();
60
83
  });
61
84
 
62
85
  socket.addEventListener("message", async (event) => {
63
86
  const message = event.data;
64
87
  if (typeof message === "string") {
65
88
  try {
66
- const encryptedPacket = JSON.parse(message);
67
-
68
- const decrypted = await window.__electrobun_decrypt(
69
- encryptedPacket.encryptedData,
70
- encryptedPacket.iv,
71
- encryptedPacket.tag,
72
- );
73
-
74
- this.rpcHandler?.(JSON.parse(decrypted));
89
+ const packet = JSON.parse(message);
90
+ this.hostSocketCanSend = true;
91
+ if (
92
+ typeof packet?.encryptedData === "string" &&
93
+ typeof packet?.iv === "string" &&
94
+ typeof packet?.tag === "string"
95
+ ) {
96
+ const decrypted = await window.__electrobun_decrypt(
97
+ packet.encryptedData,
98
+ packet.iv,
99
+ packet.tag,
100
+ );
101
+ this.rpcHandler?.(JSON.parse(decrypted));
102
+ } else {
103
+ this.rpcHandler?.(packet);
104
+ }
75
105
  } catch (err) {
76
106
  console.error("Error parsing bun message:", err);
77
107
  }
@@ -83,10 +113,13 @@ class Electroview<T extends RPCWithTransport> {
83
113
  });
84
114
 
85
115
  socket.addEventListener("error", (event) => {
116
+ this.hostSocketCanSend = false;
86
117
  console.error("Socket error:", event);
87
118
  });
88
119
 
89
120
  socket.addEventListener("close", (_event) => {
121
+ this.hostSocketCanSend = false;
122
+ this.pendingHostSocketMessages = [];
90
123
  // console.log("Socket closed:", event);
91
124
  });
92
125
  }
@@ -97,9 +130,9 @@ class Electroview<T extends RPCWithTransport> {
97
130
  send(message: unknown) {
98
131
  try {
99
132
  const messageString = JSON.stringify(message);
100
- that.bunBridge(messageString);
133
+ that.sendMessageToHost(messageString);
101
134
  } catch (error) {
102
- console.error("bun: failed to serialize message to webview", error);
135
+ console.error("host: failed to serialize message to webview", error);
103
136
  }
104
137
  },
105
138
  registerHandler(handler: (msg: unknown) => void) {
@@ -108,36 +141,121 @@ class Electroview<T extends RPCWithTransport> {
108
141
  };
109
142
  }
110
143
 
111
- async bunBridge(msg: string) {
112
- if (this.bunSocket?.readyState === WebSocket.OPEN) {
113
- try {
114
- const { encryptedData, iv, tag } =
115
- await window.__electrobun_encrypt(msg);
116
-
117
- const encryptedPacket = {
118
- encryptedData: encryptedData,
119
- iv: iv,
120
- tag: tag,
121
- };
122
- const encryptedPacketString = JSON.stringify(encryptedPacket);
123
- this.bunSocket.send(encryptedPacketString);
124
- return;
125
- } catch (error) {
126
- console.error("Error sending message to bun via socket:", error);
127
- }
144
+ async sendMessageToHost(msg: string) {
145
+ if (this.canSendToHostSocket()) {
146
+ this.hostSocketSendQueue.push(msg);
147
+ void this.flushHostSocketSendQueue();
148
+ return;
149
+ }
150
+
151
+ if (this.hostSocket?.readyState === WebSocket.CONNECTING) {
152
+ this.pendingHostSocketMessages.push(msg);
153
+ return;
128
154
  }
129
155
 
130
156
  // if socket's are unavailable, fallback to postMessage
131
- window.__electrobunBunBridge?.postMessage(msg);
157
+ window.__electrobunHostBridge?.postMessage(msg);
158
+ }
159
+
160
+ canSendToHostSocket() {
161
+ return (
162
+ this.hostSocketCanSend &&
163
+ this.hostSocket?.readyState === WebSocket.OPEN
164
+ );
165
+ }
166
+
167
+ async sendMessageToHostSocket(msg: string) {
168
+ if (!this.canSendToHostSocket()) {
169
+ return false;
170
+ }
171
+
172
+ try {
173
+ if (window.__electrobunPlaintextHostSocket) {
174
+ this.hostSocket!.send(msg);
175
+ return true;
176
+ }
177
+ await new Promise((resolve) => setTimeout(resolve, 0));
178
+ const { encryptedData, iv, tag } =
179
+ await window.__electrobun_encrypt(msg);
180
+
181
+ const encryptedPacket = {
182
+ encryptedData: encryptedData,
183
+ iv: iv,
184
+ tag: tag,
185
+ };
186
+ const encryptedPacketString = JSON.stringify(encryptedPacket);
187
+ this.hostSocket!.send(encryptedPacketString);
188
+ return true;
189
+ } catch (error) {
190
+ console.error("Error sending message to host via socket:", error);
191
+ return false;
192
+ }
193
+ }
194
+
195
+ async flushHostSocketSendQueue() {
196
+ if (this.flushingHostSocketSendQueue) {
197
+ return;
198
+ }
199
+
200
+ this.flushingHostSocketSendQueue = true;
201
+ try {
202
+ while (
203
+ this.hostSocketSendQueue.length > 0 &&
204
+ this.canSendToHostSocket()
205
+ ) {
206
+ const message = this.hostSocketSendQueue[0]!;
207
+ this.hostSocketSendQueue.shift();
208
+ if (!(await this.sendMessageToHostSocket(message))) {
209
+ window.__electrobunHostBridge?.postMessage(message);
210
+ }
211
+ }
212
+ } finally {
213
+ this.flushingHostSocketSendQueue = false;
214
+ }
132
215
  }
133
216
 
134
- receiveMessageFromBun(msg: unknown) {
135
- // NOTE: in the webview messages are passed by executing ElectrobunView.receiveMessageFromBun(object)
217
+ async flushPendingHostSocketMessages() {
218
+ if (this.flushingHostSocketMessages) {
219
+ return;
220
+ }
221
+
222
+ this.flushingHostSocketMessages = true;
223
+ try {
224
+ while (this.pendingHostSocketMessages.length > 0) {
225
+ const message = this.pendingHostSocketMessages.shift()!;
226
+ this.hostSocketSendQueue.push(message);
227
+ }
228
+ await this.flushHostSocketSendQueue();
229
+ } finally {
230
+ this.flushingHostSocketMessages = false;
231
+ }
232
+ }
233
+
234
+ receiveMessageFromHost(msg: unknown) {
235
+ // NOTE: in the webview messages are passed by executing window.__electrobun.receiveMessageFromHost(object)
136
236
  // so they're already parsed into an object here
137
237
  if (this.rpcHandler) {
138
238
  this.rpcHandler(msg);
139
239
  }
140
240
  }
241
+
242
+ async invokeCarrot<R = unknown>(
243
+ carrotId: string,
244
+ method: string,
245
+ params?: unknown,
246
+ options?: { windowId?: string },
247
+ ): Promise<R> {
248
+ const requestProxy = (this.rpc as any)?.request;
249
+ if (!requestProxy || typeof requestProxy.invokeCarrot !== "function") {
250
+ throw new Error("Renderer carrot invocation is not available in this Electrobun host.");
251
+ }
252
+ return requestProxy.invokeCarrot({
253
+ carrotId,
254
+ method,
255
+ params,
256
+ windowId: options?.windowId,
257
+ }) as Promise<R>;
258
+ }
141
259
  static defineRPC<Schema extends ElectrobunRPCSchema>(
142
260
  config: ElectrobunRPCConfig<Schema, "webview">,
143
261
  ) {
@@ -3,15 +3,38 @@
3
3
  * Used in electrobun.config.ts files
4
4
  */
5
5
 
6
- /**
7
- * Bun.build() options that can be passed through to the bundler.
8
- * Excludes options that are controlled by Electrobun (entrypoints, outdir, target).
9
- * See https://bun.sh/docs/bundler for full documentation.
10
- */
11
- type BunBuildOptions = Omit<
12
- Parameters<typeof Bun.build>[0],
13
- "entrypoints" | "outdir" | "target"
14
- >;
6
+ type BundlerOptions = {
7
+ external?: string[];
8
+ define?: Record<string, string>;
9
+ loader?: Record<string, string>;
10
+ minify?: boolean;
11
+ sourcemap?: boolean | "inline" | "external" | "linked";
12
+ splitting?: boolean;
13
+ plugins?: unknown[];
14
+ [key: string]: unknown;
15
+ };
16
+
17
+ type CarrotFileActivatorConfig = {
18
+ baseName?: string;
19
+ nodeType?: "file" | "dir" | "any";
20
+ slate: {
21
+ type: string;
22
+ name?: string;
23
+ icon?: string;
24
+ config?: Record<string, unknown>;
25
+ };
26
+ };
27
+
28
+ type CarrotContributionsConfig = {
29
+ fileActivators?: CarrotFileActivatorConfig[];
30
+ };
31
+
32
+ type CarrotUIDefinition = {
33
+ name?: string;
34
+ entrypoint?: string;
35
+ path?: string;
36
+ [key: string]: unknown;
37
+ };
15
38
 
16
39
  export interface ElectrobunConfig {
17
40
  /**
@@ -113,22 +136,85 @@ export interface ElectrobunConfig {
113
136
  */
114
137
  build?: {
115
138
  /**
116
- * Bun process build configuration.
117
- * Accepts all Bun.build() options (plugins, sourcemap, minify, define, etc.)
118
- * in addition to the entrypoint. See https://bun.sh/docs/bundler
139
+ * Main process implementation to build and package.
140
+ * - "cottontail": bundle and run the Cottontail main process entrypoint
141
+ * - "zig": compile and run the Zig main process entrypoint
142
+ * - "rust": compile and run the Rust main process entrypoint
143
+ * - "go": compile and run the Go main process entrypoint
144
+ * - "odin": compile and run the Odin main process entrypoint
145
+ * @default "cottontail"
146
+ */
147
+ mainProcess?: "cottontail" | "zig" | "rust" | "go" | "odin";
148
+
149
+ /**
150
+ * Cottontail main process build configuration.
151
+ * Used when `build.mainProcess` is set to `"cottontail"`.
119
152
  */
120
- bun?: {
153
+ cottontail?: {
121
154
  /**
122
- * Entry point for the main Bun process
155
+ * Entry point for the main Cottontail process
123
156
  * @default "src/bun/index.ts"
124
157
  */
125
158
  entrypoint?: string;
126
- } & BunBuildOptions;
159
+ } & BundlerOptions;
160
+
161
+ /**
162
+ * Zig main process build configuration.
163
+ * Used when `build.mainProcess` is set to `"zig"`.
164
+ */
165
+ zig?: {
166
+ /**
167
+ * Entry point for the main Zig process
168
+ * @default "src/zig/main.zig"
169
+ */
170
+ entrypoint?: string;
171
+ };
172
+
173
+ /**
174
+ * Rust main process build configuration.
175
+ * Used when `build.mainProcess` is set to `"rust"`.
176
+ */
177
+ rust?: {
178
+ /**
179
+ * Entry point for the main Rust process
180
+ * @default "src/rust/main.rs"
181
+ */
182
+ entrypoint?: string;
183
+ };
184
+
185
+ /**
186
+ * Go main process build configuration.
187
+ * Used when `build.mainProcess` is set to `"go"`.
188
+ */
189
+ go?: {
190
+ /**
191
+ * Entry point for the main Go process
192
+ * @default "src/go/main.go"
193
+ */
194
+ entrypoint?: string;
195
+ };
196
+
197
+ /**
198
+ * Odin main process build configuration.
199
+ * Used when `build.mainProcess` is set to `"odin"`.
200
+ *
201
+ * Note: Odin support has platform caveats (x64-only on Windows, MSVC
202
+ * Build Tools required on Windows). See docs for details.
203
+ */
204
+ odin?: {
205
+ /**
206
+ * Entry point for the main Odin process. May be a .odin file or a
207
+ * package directory; a file entrypoint compiles its containing
208
+ * directory as the root package.
209
+ * @default "src/odin/main.odin"
210
+ */
211
+ entrypoint?: string;
212
+ };
127
213
 
128
214
  /**
129
215
  * Browser view build configurations.
130
- * Each view accepts all Bun.build() options (plugins, sourcemap, minify, define, etc.)
131
- * in addition to the entrypoint. See https://bun.sh/docs/bundler
216
+ * Each view accepts bundler options (plugins, sourcemap, minify, define, etc.)
217
+ * in addition to the entrypoint.
132
218
  */
133
219
  views?: {
134
220
  [viewName: string]: {
@@ -136,7 +222,7 @@ export interface ElectrobunConfig {
136
222
  * Entry point for this view's TypeScript code
137
223
  */
138
224
  entrypoint: string;
139
- } & BunBuildOptions;
225
+ } & BundlerOptions;
140
226
  };
141
227
 
142
228
  /**
@@ -199,24 +285,6 @@ export interface ElectrobunConfig {
199
285
  */
200
286
  wgpuVersion?: string;
201
287
 
202
- /**
203
- * Override the Bun runtime version.
204
- * Format: semver string (e.g., "1.4.2")
205
- *
206
- * This downloads the specified Bun version from GitHub releases and uses it
207
- * instead of the version bundled with this Electrobun release.
208
- * @default Uses the version bundled with this Electrobun release
209
- */
210
- bunVersion?: string;
211
-
212
- /**
213
- * Locales to include in the ICU data file (Linux/Windows only).
214
- * Set to '*' to include all locales, or specify a subset like ['en', 'de']
215
- * to reduce app size. Has no effect on macOS (uses system ICU).
216
- * @default '*'
217
- */
218
- locales?: string[] | "*";
219
-
220
288
  /**
221
289
  * Additional file or directory paths to watch for changes during `electrobun dev --watch`.
222
290
  * Paths are relative to the project root.
@@ -242,9 +310,10 @@ export interface ElectrobunConfig {
242
310
  description?: string;
243
311
  mode?: "window" | "background";
244
312
  carrotOnly?: boolean;
245
- permissions?: Record<string, unknown>;
246
313
  dependencies?: Record<string, string>;
247
- remoteUIs?: Record<string, { entrypoint: string; [key: string]: unknown }>;
314
+ remoteUIs?: Record<string, CarrotUIDefinition>;
315
+ slateUIs?: Record<string, CarrotUIDefinition>;
316
+ contributions?: CarrotContributionsConfig;
248
317
  };
249
318
 
250
319
  /**
@@ -425,7 +494,7 @@ export interface ElectrobunConfig {
425
494
 
426
495
  /**
427
496
  * Runtime behaviour configuration.
428
- * These values are copied into build.json and available to the Bun process at runtime.
497
+ * These values are copied into build.json and available to the main process at runtime.
429
498
  * You can add arbitrary keys here and access them via BuildConfig.
430
499
  */
431
500
  runtime?: {
@@ -478,7 +547,7 @@ export interface ElectrobunConfig {
478
547
  baseUrl?: string;
479
548
  /**
480
549
  * Generate delta patch files by diffing against the previous release.
481
- * Disable to skip patch generation for local canary/stable testing.
550
+ * Disable to skip patch generation for local canary/production testing.
482
551
  * @default true
483
552
  */
484
553
  generatePatch?: boolean;
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { assertNoLegacyBunMainProcessConfig } from "./validate";
3
+
4
+ describe("assertNoLegacyBunMainProcessConfig", () => {
5
+ it("accepts Cottontail and native main-process configurations", () => {
6
+ expect(() =>
7
+ assertNoLegacyBunMainProcessConfig({
8
+ build: {
9
+ mainProcess: "cottontail",
10
+ cottontail: { entrypoint: "src/bun/index.ts" },
11
+ },
12
+ }),
13
+ ).not.toThrow();
14
+ expect(() =>
15
+ assertNoLegacyBunMainProcessConfig({
16
+ build: { mainProcess: "zig" },
17
+ }),
18
+ ).not.toThrow();
19
+ });
20
+
21
+ it.each([
22
+ { mainProcess: "bun" },
23
+ { bun: { entrypoint: "src/bun/index.ts" } },
24
+ { bunVersion: "1.3.8" },
25
+ { bunnyBun: "bunny-bun-test" },
26
+ ])("rejects removed Bun configuration: %p", (build) => {
27
+ expect(() => assertNoLegacyBunMainProcessConfig({ build })).toThrow(
28
+ 'Use build.mainProcess = "cottontail"',
29
+ );
30
+ });
31
+ });