electrobun 1.18.4-beta.5 → 2.0.1-beta.2

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -167
  3. package/bin/electrobun.cjs +225 -153
  4. package/package.json +18 -49
  5. package/bun.lock +0 -119
  6. package/dist/api/browser/builtinrpcSchema.ts +0 -19
  7. package/dist/api/browser/global.d.ts +0 -36
  8. package/dist/api/browser/index.ts +0 -234
  9. package/dist/api/browser/webviewtag.ts +0 -88
  10. package/dist/api/browser/wgputag.ts +0 -48
  11. package/dist/api/bun/ElectrobunConfig.ts +0 -530
  12. package/dist/api/bun/__tests__/ffi-contract.test.ts +0 -105
  13. package/dist/api/bun/core/ApplicationMenu.ts +0 -70
  14. package/dist/api/bun/core/BrowserView.ts +0 -419
  15. package/dist/api/bun/core/BrowserWindow.ts +0 -400
  16. package/dist/api/bun/core/BuildConfig.ts +0 -71
  17. package/dist/api/bun/core/ContextMenu.ts +0 -75
  18. package/dist/api/bun/core/GpuWindow.ts +0 -289
  19. package/dist/api/bun/core/Paths.ts +0 -5
  20. package/dist/api/bun/core/Socket.ts +0 -22
  21. package/dist/api/bun/core/Tray.ts +0 -197
  22. package/dist/api/bun/core/Updater.ts +0 -1162
  23. package/dist/api/bun/core/Utils.ts +0 -487
  24. package/dist/api/bun/core/WGPUView.ts +0 -167
  25. package/dist/api/bun/core/menuRoles.ts +0 -181
  26. package/dist/api/bun/events/ApplicationEvents.ts +0 -22
  27. package/dist/api/bun/events/event.ts +0 -29
  28. package/dist/api/bun/events/eventEmitter.ts +0 -45
  29. package/dist/api/bun/events/trayEvents.ts +0 -11
  30. package/dist/api/bun/events/webviewEvents.ts +0 -39
  31. package/dist/api/bun/events/windowEvents.ts +0 -23
  32. package/dist/api/bun/index.ts +0 -298
  33. package/dist/api/bun/preload/.generated/compiled.ts +0 -8
  34. package/dist/api/bun/preload/build.ts +0 -65
  35. package/dist/api/bun/preload/dragRegions.ts +0 -41
  36. package/dist/api/bun/preload/encryption.ts +0 -86
  37. package/dist/api/bun/preload/events.ts +0 -171
  38. package/dist/api/bun/preload/globals.d.ts +0 -45
  39. package/dist/api/bun/preload/index-sandboxed.ts +0 -28
  40. package/dist/api/bun/preload/index.ts +0 -77
  41. package/dist/api/bun/preload/internalRpc.ts +0 -80
  42. package/dist/api/bun/preload/overlaySync.ts +0 -107
  43. package/dist/api/bun/preload/webviewTag.ts +0 -451
  44. package/dist/api/bun/preload/wgpuTag.ts +0 -246
  45. package/dist/api/bun/proc/linux.md +0 -43
  46. package/dist/api/bun/proc/native.ts +0 -3385
  47. package/dist/api/bun/webGPU.ts +0 -346
  48. package/dist/api/bun/webgpuAdapter.ts +0 -3011
  49. package/dist/api/shared/bun-version.ts +0 -3
  50. package/dist/api/shared/cef-version.ts +0 -5
  51. package/dist/api/shared/electrobun-version.ts +0 -2
  52. package/dist/api/shared/naming.test.ts +0 -327
  53. package/dist/api/shared/naming.ts +0 -188
  54. package/dist/api/shared/platform.ts +0 -48
  55. package/dist/api/shared/rpc.ts +0 -541
  56. package/dist/main.js +0 -168
  57. package/dist/preload-full.js +0 -913
  58. package/dist/preload-sandboxed.js +0 -111
  59. package/dist/zig-sdk/electrobun.zig +0 -1993
  60. package/src/cli/bun.lockb +0 -0
  61. package/src/cli/index.ts +0 -5689
  62. package/src/cli/package-lock.json +0 -81
  63. package/src/cli/package.json +0 -11
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Blackboard Technologies inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,178 +1,37 @@
1
- <p align="center">
2
- <a href="https://electrobun.dev"><img src="https://github.com/blackboardsh/electrobun/assets/75102186/8799b522-0507-45e9-86e3-c3cfded1aa7c" alt="Logo" height=170></a>
3
- </p>
1
+ # Electrobun npm bootstrap
4
2
 
5
- <h1 align="center">Electrobun</h1>
3
+ The `electrobun` npm package is a small compatibility entry point for npm,
4
+ pnpm, Yarn, and Bun users. It contains no Electrobun runtime or SDK. The
5
+ `electrobun` command installs Hutch into `~/.dash` when necessary and delegates
6
+ all commands to `hutch electrobun`.
6
7
 
7
- <div align="center">
8
- Get started with a template <br />
9
- <code><strong>npx electrobun init</strong></code>
10
- </div>
8
+ Create a project from the current stable template catalog:
11
9
 
12
-
13
-
14
- ## What is Electrobun?
15
-
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>.
18
-
19
- Visit <a href="https://docs.electrobunny.ai/electrobun/">https://docs.electrobunny.ai/electrobun/</a> to see api documentation, guides, and more.
20
-
21
- You use it via npm.
22
-
23
- Don't miss our:
24
- - self-extracting bundles that use ZSTD compression for more compact distributables as small as 16MB
25
- - zig optimized BSDIFF implementation that lets you ship tiny app updates as small as 4KB
26
- - `bundleCEF` flag to bundle and pin Chromium for those that want that tradeoff of consistency over file size
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
29
- - Our `<electrobun-webview>` and `<electrobun-wpgu>` html elements that let you composit proper OOPIFs and native GPU surfaces into your UIs
30
- - so much more.
31
-
32
- **Project Goals**
33
-
34
- - Write typescript for the main process and webviews without having to think about it.
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)
37
- - Even smaller app updates as small as 4KB (using bsdiff it only downloads tiny patches between versions)
38
- - Provide everything you need in one tightly integrated workflow to start writing code in 5 minutes and distribute in 10.
39
-
40
- ## Apps Built with Electrobun
41
- - [24agents](https://github.com/jhsu/24agents) - Hyperprompter
42
- - [act-track-ai](https://github.com/IrdanGu/act-track-ai) - personal desktop productivity tracker
43
- - [Agents Council](https://github.com/MrLesk/agents-council) - agent-to-agent MCP communication tool for feedback requests
44
- - [ai-wrapped](https://github.com/gulivan/ai-wrapped) - Wrapped-style desktop dashboard for your AI coding agent activity
45
- - [Audio TTS](https://github.com/blackboardsh/audio-tts) - desktop text-to-speech app using Qwen3-TTS for voice design, cloning, and generation
46
- - [aueio-player-desktop](https://github.com/tuomashatakka/aueio-player-desktop) - beautiful, minimal cross-platform audio player
47
- - [bestdiff](https://github.com/tesmond/bestdiff) - a git diff checker with curved connectors
48
- - [BuddyWriter](https://github.com/OxFrancesco/BuddyWriter) - BuddyWriter desktop and mobile apps
49
- - [burns](https://github.com/l3wi/burns) - a Smithers manager
50
- - [cbx-tool](https://github.com/jebin2/cbx-tool) - desktop app for reading and editing comic book archives (.cbz/.cbr)
51
- - [Co(lab)](https://blackboard.sh/colab/) - a hybrid web browser + code editor for deep work
52
- - [codlogs](https://github.com/tobitege/codlogs) - search and export local Codex sessions via CLI or desktop app
53
- - [Codex Agents Composer](https://github.com/MrLesk/codex-agents-composer) - desktop app for managing your Codex agents and their skills
54
- - [codex-devtools](https://github.com/gulivan/codex-devtools) - desktop inspector for Codex session data; browse conversations, search messages, and analyze agent activity
55
- - [Deskdown](https://github.com/guarana-studio/deskdown) - transform any web address into a desktop app in under 20 seconds
56
- - [Dictate](https://github.com/siddhantparadox/dictate) - Windows dictation app with local and BYOK cloud transcription
57
- - [dev-3.0](https://github.com/h0x91b/dev-3.0) - helps you not get lost while managing multiple AI agents across projects
58
- - [DOOM](https://github.com/blackboardsh/electrobun-doom) - DOOM implemented in 2 ways: bun -> (c doom -> bundled wgpu) and (full ts port bun -> bundled wgpu)
59
- - [dotlock](https://github.com/tsconfigdotjson/dotlock) - macOS desktop app for managing `.env` files across your projects
60
- - [electrobun-pdf](https://github.com/GijungKim/electrobun-pdf) - local-first PDF & DOCX editor for opening, annotating, and exporting documents without leaving your machine
61
- - [electrobun-rms](https://github.com/khanhthanhdev/electrobun-rms) - fast Electrobun desktop app template with React, Tailwind CSS, and Vite
62
- - [golb](https://github.com/chrisdadev13/golb) - desktop AI coding workspace built with React, Vite, and Tailwind
63
- - [GOG Achievements GUI](https://github.com/timendum/gog-achievements-gui) - desktop app for managing GOG achievements
64
- - [groov](https://github.com/laurenzcodes/groov) - desktop audio deck monitor
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
67
- - [Marginalia](https://github.com/lars-hoeijmans/Marginalia) - a simple note taking app
68
- - [MarkBun](https://github.com/xiaochong/markbun) - fast, beautiful, Typora-like markdown desktop editor
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
71
- - [Patchline](https://github.com/adwaithks/Patchline) - lightweight desktop Git client for reading patches and line diffs, then staging and committing changes
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
73
- - [PiBun](https://github.com/khairold/pibun) - desktop GUI for the Pi coding agent with chat, terminal, git integration, and plugin system
74
- - [PLEXI](https://github.com/ianjamesburke/PLEXI) - a multi-dimensional terminal multiplexer for the agentic era
75
- - [Prometheus](https://github.com/opensourcectl/prometheus) - desktop utility toolbox for file cleanup, document manipulation, and image processing
76
- - [Quiver](https://ataraxy-labs.github.io/quiver/) - desktop app for GitHub PR reviews, merge conflict resolution, and AI commit messages
77
- - [remotecode.io](https://github.com/samuelfaj/remotecode.io) - continue local AI coding sessions (Claude Code or Codex) from your mobile device
78
- - [sirene](https://github.com/KevinBonnoron/sirene) - self-hosted multi-backend text-to-speech platform with voice cloning
79
- - [StoryForge](https://github.com/vrrdnt/StoryForge) - desktop app for Vintage Story players to switch between game versions, modpacks, servers, and accounts
80
- - [Tensamin Client](https://github.com/Tensamin/Client) - web, desktop, and mobile app for accessing Tensamin
81
- - [tokenpass-desktop](https://github.com/b-open-io/tokenpass-desktop) - desktop app that runs the Sigma Identity stack locally for Bitcoin-backed authentication
82
- - [typsmthng-desktop](https://github.com/aaditagrawal/typsmthng-desktop) - experimental desktop typing application
83
- - [VibesOS](https://github.com/popmechanic/VibesOS) - A GUI for Claude Code that makes it easy to vibe code simple, un-hackable apps
84
- - [VoiceVault](https://github.com/PJH720/VoiceVault) - AI-powered voice recorder with transcription, summarization, and RAG search
85
- - [warren](https://github.com/Loa212/warren) - open-source, peer-to-peer terminal mesh for accessing your machines from any device without SSH keys or config files
86
- - [whatsapp-reminder](https://github.com/FatahChan/whatsapp-reminder) - managed scheduled WhatsApp messages
87
-
88
- ### Video Demos
89
-
90
- [![Audio TTS Demo](https://img.youtube.com/vi/Z4dNK1d6l6E/maxresdefault.jpg)](https://www.youtube.com/watch?v=Z4dNK1d6l6E)
91
-
92
- [![Co(lab) Demo](https://img.youtube.com/vi/WWTCqGmE86w/maxresdefault.jpg)](https://www.youtube.com/watch?v=WWTCqGmE86w)
93
-
94
- [![DOOM Demo](https://github.com/user-attachments/assets/6cc5f04a-6d97-4010-b65f-3f282d32590c)](https://x.com/YoavCodes/status/2028499038148903239?s=20)
95
-
96
- ## Star History
97
-
98
- [![Star History Chart](https://api.star-history.com/svg?repos=blackboardsh/electrobun&type=date&legend=top-left&cache=3)](https://www.star-history.com/#blackboardsh/electrobun&type=date&legend=top-left)
99
-
100
- ## Contributing
101
- Electrobun is one piece of a vision I'm building. I'm optimizing for focus and execution. Issues and PRs can be used to share ideas, but there should be no expectation that I will review, respond to, or merge them.
102
-
103
- Ways to get involved:
104
-
105
- - Read the [Contribution guidelines](./CONTRIBUTING.md)
106
- - Follow us on X for updates <a href="https://twitter.com/BlackboardTech">@BlackboardTech</a> and <a href="https://twitter.com/YoavCodes">@YoavCodes</a> or on bluesky <a href="https://bsky.app/profile/yoav.codes">@yoav.codes</a>
107
- - Join the conversation on <a href="https://discord.gg/ueKE4tjaCE">Discord</a>
108
- - Create and participate in Github issues and discussions
109
- - Let me know what you're building with Electrobun
110
-
111
- ## Development Setup
112
- Building apps with Electrobun is as easy as updating your package.json dependencies with `npm add electrobun` or try one of our templates via `npx electrobun init`.
113
-
114
- **This section is for building Electrobun from source locally in order to contribute fixes to it.**
115
-
116
- ### Prerequisites
117
-
118
- **macOS:**
119
- - Xcode command line tools
120
- - cmake (install via homebrew: `brew install cmake`)
121
-
122
- **Windows:**
123
- - Visual Studio Build Tools or Visual Studio with C++ development tools
124
- - cmake
125
-
126
- **Linux:**
127
- - build-essential package
128
- - cmake
129
- - webkit2gtk and GTK development packages
130
-
131
- On Ubuntu/Debian based distros: `sudo apt install build-essential cmake pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev`
132
-
133
- ### First-time Setup
134
-
135
- ```bash
136
- git clone --recurse-submodules https://github.com/blackboardsh/electrobun.git
137
- cd electrobun/package
138
- bun install
139
- bun dev:clean
10
+ ```sh
11
+ npx electrobun init
140
12
  ```
141
13
 
142
- ### Development Workflow
143
-
144
- ```bash
145
- # All commands are run from the /package directory
146
- cd electrobun/package
147
-
148
- # After making changes to source code
149
- bun dev
150
-
151
- # If you only changed kitchen sink code (not electrobun source)
152
- bun dev:rerun
14
+ Use the beta catalog only when you ask for it:
153
15
 
154
- # If you need a completely fresh start
155
- bun dev:clean
16
+ ```sh
17
+ npx electrobun init --beta
156
18
  ```
157
19
 
158
- ### Additional Commands
20
+ The npm bootstrap ships under the same release version as Electrobun, but it
21
+ does not contain or select the runtime by itself. An application's exact
22
+ Electrobun version is selected in `hutch.config.ts`; Hutch downloads the
23
+ matching runtime and SDK into the shared `~/.dash` store and projects the SDKs
24
+ into the project's `.hutch/devkit` sysroot. Installing a beta bootstrap still
25
+ uses stable templates unless `init` receives `--beta`.
159
26
 
160
- All commands are run from the `/package` directory:
27
+ `DASH_RELEASE_OFFLINE=1` prevents this bootstrap from downloading a missing
28
+ Hutch installation. An already installed Hutch is still invoked, and the flag
29
+ continues to prevent network access for Dash-managed releases and artifacts.
30
+ It does not put npm or another project package manager into offline mode.
161
31
 
162
- - `bun dev:canary` - Build and run kitchen sink in canary mode
163
- - `bun build:dev` - Build electrobun in development mode
164
- - `bun build:release` - Build electrobun in release mode
32
+ You can install Hutch directly from <https://hutch.blackboard.sh> and run the
33
+ same commands without npm:
165
34
 
166
- ### Debugging
167
-
168
- **macOS:** Use `lldb <path-to-bundle>/Contents/MacOS/launcher` and then `run` to debug release builds
169
-
170
- ## Platform Support
171
-
172
- | OS | Status |
173
- |---|---|
174
- | macOS 14+ | Official |
175
- | Windows 11+ | Official |
176
- | Ubuntu 22.04+ | Official |
177
- | Other Linux distros (gtk3, webkit2gtk-4.1) | Community |
178
- | Raspberry Pi | Unofficial fork: [kortexa-ai/electrobun (linux-wpe)](https://github.com/kortexa-ai/electrobun/tree/kortexa/linux-wpe) — follow the author [@francip](https://x.com/francip/status/2050149256053539059?s=20) |
35
+ ```sh
36
+ hutch electrobun init
37
+ ```
@@ -1,169 +1,241 @@
1
1
  #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { spawnSync } = require("node:child_process");
5
+ const { existsSync, mkdtempSync, rmSync, writeFileSync } = require("node:fs");
6
+ const { get } = require("node:https");
7
+ const { homedir, tmpdir } = require("node:os");
8
+ const path = require("node:path");
9
+
10
+ const installerBaseUrl = "https://hutch.blackboard.sh/hutch";
11
+ const maxInstallerBytes = 1024 * 1024;
12
+ const maxInstallerRedirects = 5;
13
+
14
+ function normalizeChannel(value) {
15
+ if (value === "stable") return "production";
16
+ if (value === "production" || value === "canary") return value;
17
+ return null;
18
+ }
19
+
20
+ function hutchChannel(environment) {
21
+ for (const key of ["ELECTROBUN_HUTCH_CHANNEL", "HUTCH_ACTIVE_CHANNEL"]) {
22
+ const selected = normalizeChannel(environment[key]);
23
+ if (selected) return selected;
24
+ }
25
+ return "production";
26
+ }
2
27
 
3
- const { execSync, spawn } = require('child_process');
4
- const { existsSync, mkdirSync, unlinkSync, chmodSync, copyFileSync, createWriteStream } = require('fs');
5
- const { join, dirname } = require('path');
6
- const https = require('https');
7
- const ProxyAgent = require('proxy-agent').ProxyAgent;
8
-
9
- // Create an HTTPS agent that respects environment proxy settings
10
- const agent = new ProxyAgent();
11
-
12
- // Detect platform and architecture
13
- function getPlatform() {
14
- switch (process.platform) {
15
- case 'win32': return 'win';
16
- case 'darwin': return 'darwin';
17
- case 'linux': return 'linux';
18
- default: throw new Error(`Unsupported platform: ${process.platform}`);
19
- }
28
+ function environmentFlagEnabled(environment, name) {
29
+ const value = environment[name];
30
+ return (
31
+ value === "1" ||
32
+ (typeof value === "string" &&
33
+ ["true", "yes"].includes(value.toLowerCase()))
34
+ );
20
35
  }
21
36
 
22
- function getArch() {
23
- switch (process.arch) {
24
- case 'arm64': return 'arm64';
25
- case 'x64': return 'x64';
26
- default: throw new Error(`Unsupported architecture: ${process.arch}`);
27
- }
37
+ function hutchBinaryPath(channel, environment, platform, userHome) {
38
+ if (environment.ELECTROBUN_HUTCH_BINARY) {
39
+ return environment.ELECTROBUN_HUTCH_BINARY;
40
+ }
41
+
42
+ const dashHome = environment.DASH_HOME || path.join(userHome, ".dash");
43
+ const command = channel === "canary" ? "hutch-canary" : "hutch";
44
+ const pathApi = platform === "win32" ? path.win32 : path.posix;
45
+ return pathApi.join(
46
+ dashHome,
47
+ "bin",
48
+ `${command}${platform === "win32" ? ".exe" : ""}`,
49
+ );
28
50
  }
29
51
 
30
- const platform = getPlatform();
31
- // Always use x64 for Windows since we only build x64 Windows binaries
32
- const arch = platform === 'win' ? 'x64' : getArch();
33
- const binExt = platform === 'win' ? '.exe' : '';
52
+ function hutchArguments(args) {
53
+ const forwarded = Array.from(args);
54
+ if (forwarded[0] !== "init") return forwarded;
55
+
56
+ const explicitTemplateChannel = forwarded.some(
57
+ (argument) =>
58
+ argument === "--beta" ||
59
+ argument.startsWith("--channel="),
60
+ );
61
+ return explicitTemplateChannel
62
+ ? forwarded
63
+ : [...forwarded, "--channel=stable"];
64
+ }
34
65
 
35
- function getTarCommand() {
36
- if (platform !== 'win') {
37
- return 'tar';
38
- }
66
+ function download(url, redirects = 0) {
67
+ if (redirects > maxInstallerRedirects) {
68
+ return Promise.reject(new Error("too many installer redirects"));
69
+ }
70
+
71
+ let target;
72
+ try {
73
+ target = new URL(url);
74
+ } catch {
75
+ return Promise.reject(new Error("invalid Hutch installer URL"));
76
+ }
77
+ if (target.protocol !== "https:") {
78
+ return Promise.reject(
79
+ new Error("the Hutch installer must be downloaded over HTTPS"),
80
+ );
81
+ }
82
+
83
+ return new Promise((resolve, reject) => {
84
+ const request = get(target, (response) => {
85
+ if (
86
+ response.statusCode >= 300 &&
87
+ response.statusCode < 400 &&
88
+ response.headers.location
89
+ ) {
90
+ response.resume();
91
+ let redirected;
92
+ try {
93
+ redirected = new URL(response.headers.location, target);
94
+ } catch {
95
+ reject(new Error("invalid Hutch installer redirect URL"));
96
+ return;
97
+ }
98
+ resolve(download(redirected.href, redirects + 1));
99
+ return;
100
+ }
101
+
102
+ if (response.statusCode !== 200) {
103
+ response.resume();
104
+ reject(
105
+ new Error(
106
+ `installer download returned HTTP ${response.statusCode ?? "unknown"}`,
107
+ ),
108
+ );
109
+ return;
110
+ }
111
+
112
+ const chunks = [];
113
+ let size = 0;
114
+ response.on("data", (chunk) => {
115
+ size += chunk.length;
116
+ if (size > maxInstallerBytes) {
117
+ request.destroy(
118
+ new Error("installer download exceeded the 1 MiB limit"),
119
+ );
120
+ return;
121
+ }
122
+ chunks.push(chunk);
123
+ });
124
+ response.on("end", () => resolve(Buffer.concat(chunks)));
125
+ response.on("error", reject);
126
+ });
127
+ request.on("error", reject);
128
+ });
129
+ }
130
+
131
+ function checkedSpawn(command, args, options) {
132
+ const result = spawnSync(command, args, options);
133
+ if (result.error) throw result.error;
134
+ if (result.status !== 0) {
135
+ throw new Error(
136
+ `${command} exited with status ${result.status ?? "unknown"}`,
137
+ );
138
+ }
139
+ }
39
140
 
40
- // Git Bash tar can treat C:\... as a remote path. Force the built-in Windows tar.
41
- const systemTar = join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe');
42
- return existsSync(systemTar) ? `"${systemTar}"` : 'tar';
141
+ async function installHutch({ channel, environment, platform }) {
142
+ const temporary = mkdtempSync(path.join(tmpdir(), "electrobun-hutch-"));
143
+ try {
144
+ if (platform === "win32") {
145
+ const installer = path.join(temporary, "install.ps1");
146
+ writeFileSync(
147
+ installer,
148
+ await download(`${installerBaseUrl}/install.ps1`),
149
+ );
150
+ checkedSpawn(
151
+ "powershell.exe",
152
+ [
153
+ "-NoProfile",
154
+ "-NonInteractive",
155
+ "-ExecutionPolicy",
156
+ "Bypass",
157
+ "-File",
158
+ installer,
159
+ "-Channel",
160
+ channel,
161
+ ],
162
+ { env: environment, stdio: "inherit" },
163
+ );
164
+ } else {
165
+ const installer = path.join(temporary, "install.sh");
166
+ writeFileSync(
167
+ installer,
168
+ await download(`${installerBaseUrl}/install.sh`),
169
+ { mode: 0o700 },
170
+ );
171
+ checkedSpawn("sh", [installer, "--channel", channel], {
172
+ env: environment,
173
+ stdio: "inherit",
174
+ });
175
+ }
176
+ } finally {
177
+ rmSync(temporary, { force: true, recursive: true });
178
+ }
43
179
  }
44
180
 
45
- // Paths
46
- const electrobunDir = join(__dirname, '..');
47
- const cacheDir = join(electrobunDir, '.cache');
48
- const cliBinary = join(cacheDir, `electrobun${binExt}`);
49
-
50
- async function downloadFile(url, filePath) {
51
- return new Promise((resolve, reject) => {
52
- mkdirSync(dirname(filePath), { recursive: true });
53
- const file = createWriteStream(filePath);
54
-
55
- https.get(url, {agent}, (response) => {
56
- if (response.statusCode === 302 || response.statusCode === 301) {
57
- // Follow redirect
58
- return downloadFile(response.headers.location, filePath).then(resolve).catch(reject);
59
- }
60
-
61
- if (response.statusCode !== 200) {
62
- reject(new Error(`Download failed: ${response.statusCode}`));
63
- return;
64
- }
65
-
66
- response.pipe(file);
67
-
68
- file.on('finish', () => {
69
- file.close();
70
- resolve();
71
- });
72
-
73
- file.on('error', reject);
74
- }).on('error', reject);
75
- });
181
+ function runHutch({ binary, args, environment }) {
182
+ const result = spawnSync(binary, ["electrobun", ...args], {
183
+ env: environment,
184
+ stdio: "inherit",
185
+ });
186
+ if (result.error) throw result.error;
187
+ if (result.status !== null) return result.status;
188
+ if (result.signal === "SIGINT") return 130;
189
+ if (result.signal === "SIGTERM") return 143;
190
+ return 1;
76
191
  }
77
192
 
78
- async function ensureCliBinary() {
79
- // Check if CLI binary exists in bin location (where npm expects it)
80
- const binLocation = join(electrobunDir, 'bin', 'electrobun' + binExt);
81
- if (existsSync(binLocation)) {
82
- return binLocation;
83
- }
84
-
85
- // Check if core dependencies already exist in cache
86
- if (existsSync(cliBinary)) {
87
- // Copy to bin location if it exists in cache but not in bin
88
- mkdirSync(dirname(binLocation), { recursive: true });
89
- copyFileSync(cliBinary, binLocation);
90
- if (platform !== 'win') {
91
- chmodSync(binLocation, '755');
92
- }
93
- return binLocation;
94
- }
95
-
96
- console.log('Downloading electrobun CLI for your platform...');
97
-
98
- // Get the package version to download the matching release
99
- const packageJson = require(join(electrobunDir, 'package.json'));
100
- const version = packageJson.version;
101
- const tag = `v${version}`;
102
-
103
- const tarballUrl = `https://github.com/blackboardsh/electrobun/releases/download/${tag}/electrobun-cli-${platform}-${arch}.tar.gz`;
104
- const tarballPath = join(cacheDir, `electrobun-${platform}-${arch}.tar.gz`);
105
-
106
- try {
107
- // Download tarball
108
- await downloadFile(tarballUrl, tarballPath);
109
-
110
- // Extract using system tar (available on macOS, Linux, and Windows 10+)
111
- execSync(`${getTarCommand()} -xzf "${tarballPath}"`, { cwd: cacheDir, stdio: 'pipe' });
112
-
113
- // Clean up tarball
114
- unlinkSync(tarballPath);
115
-
116
- // Check if CLI binary was extracted
117
- if (!existsSync(cliBinary)) {
118
- throw new Error(`CLI binary not found at ${cliBinary} after extraction`);
119
- }
120
-
121
- // Make executable on Unix systems
122
- if (platform !== 'win') {
123
- chmodSync(cliBinary, '755');
124
- }
125
-
126
- // Copy CLI to bin location so npm scripts can find it
127
- mkdirSync(dirname(binLocation), { recursive: true });
128
- copyFileSync(cliBinary, binLocation);
129
-
130
- // Make the bin location executable too
131
- if (platform !== 'win') {
132
- chmodSync(binLocation, '755');
133
- }
134
-
135
- console.log('electrobun CLI downloaded successfully!');
136
- return binLocation;
137
-
138
- } catch (error) {
139
- throw new Error(`Failed to download electrobun CLI: ${error.message}`);
140
- }
193
+ async function main(options = {}) {
194
+ const args = hutchArguments(options.args ?? process.argv.slice(2));
195
+ const environment = options.environment ?? process.env;
196
+ const platform = options.platform ?? process.platform;
197
+ const userHome = options.userHome ?? homedir();
198
+ const fileExists = options.existsSync ?? existsSync;
199
+ const install = options.installHutch ?? installHutch;
200
+ const run = options.runHutch ?? runHutch;
201
+ const channel = hutchChannel(environment);
202
+ const binary = hutchBinaryPath(channel, environment, platform, userHome);
203
+
204
+ if (!fileExists(binary)) {
205
+ if (environment.ELECTROBUN_HUTCH_BINARY) {
206
+ throw new Error(`ELECTROBUN_HUTCH_BINARY does not exist: ${binary}`);
207
+ }
208
+ if (environmentFlagEnabled(environment, "DASH_RELEASE_OFFLINE")) {
209
+ throw new Error(
210
+ `Hutch is not installed at ${binary}; DASH_RELEASE_OFFLINE prevents downloading it`,
211
+ );
212
+ }
213
+ console.error(
214
+ `Electrobun requires Hutch; installing the latest ${channel} release...`,
215
+ );
216
+ await install({ channel, environment, platform });
217
+ }
218
+
219
+ if (!fileExists(binary)) {
220
+ throw new Error(`Hutch was not installed at ${binary}`);
221
+ }
222
+ return run({ binary, args, environment });
141
223
  }
142
224
 
143
- async function main() {
144
- try {
145
- const args = process.argv.slice(2);
146
- const cliPath = await ensureCliBinary();
147
-
148
- // Replace this process with the actual CLI
149
- const child = spawn(cliPath, args, {
150
- stdio: 'inherit',
151
- cwd: process.cwd()
152
- });
153
-
154
- child.on('exit', (code) => {
155
- process.exit(code || 0);
156
- });
157
-
158
- child.on('error', (error) => {
159
- console.error('Failed to start electrobun CLI:', error.message);
160
- process.exit(1);
161
- });
162
-
163
- } catch (error) {
164
- console.error('Error:', error.message);
165
- process.exit(1);
166
- }
225
+ if (require.main === module) {
226
+ main()
227
+ .then((status) => {
228
+ process.exitCode = status;
229
+ })
230
+ .catch((error) => {
231
+ console.error(`electrobun: ${error.message}`);
232
+ process.exitCode = 1;
233
+ });
167
234
  }
168
235
 
169
- main();
236
+ module.exports = {
237
+ hutchArguments,
238
+ hutchBinaryPath,
239
+ hutchChannel,
240
+ main,
241
+ };