electrobun 2.0.1-beta.24 → 2.0.1-beta.28

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/README.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # Electrobun npm bootstrap
2
2
 
3
- The `electrobun` npm package is a small Hutch entry point for npm, pnpm, Yarn,
4
- and Bun users. It contains no Electrobun runtime or SDK. The `electrobun`
5
- command installs Hutch into `~/.hutch` (override with `HUTCH_HOME`) when
6
- necessary and delegates all commands to `hutch electrobun`.
3
+ The dependency-free `electrobun` npm package is a small Hutch entry point for
4
+ npm, pnpm, Yarn, and Bun users. It contains no Electrobun runtime, SDK, or
5
+ platform-specific npm dependency. On first use, the `electrobun` command reads
6
+ the `hutch-artifacts.json` index for its exact version, downloads the host's
7
+ matching Hutch archive from the indexed immutable Electrobun GitHub Release
8
+ URL, checks its byte length and SHA-256 digest against that index entry, caches
9
+ the extracted launcher and engine safely, and delegates all commands to `hutch
10
+ electrobun`. `electrobun init` additionally ensures a compatible global
11
+ Hutch launcher is present for the generated project's `hutch run ...` tasks;
12
+ init itself still runs through the exact private cache.
7
13
 
8
14
  Create a project from the current stable template catalog:
9
15
 
@@ -17,19 +23,26 @@ Use the beta catalog only when you ask for it:
17
23
  npx electrobun init --beta
18
24
  ```
19
25
 
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 `~/.hutch/releases/electrobun` store
24
- and projects the SDKs into the project's `.hutch/devkit` sysroot. Installing a
25
- beta bootstrap still uses stable templates unless `init` receives `--beta`.
26
+ The npm bootstrap supplies the exact Hutch and Electrobun defaults paired with
27
+ its own release version. Those defaults are not overrides: an exact
28
+ `// @hutch` pragma or `electrobun.version` in `hutch.config.ts` always wins.
29
+ Without an explicit Electrobun pin, the installed package version selects the
30
+ toolchain for npm-launched commands, so the single `electrobun` dependency and
31
+ its immutable GitHub Release assets ride the project lockfile together. Hutch
32
+ downloads the matching runtime and SDK into the shared
33
+ `~/.hutch/releases/electrobun` store and projects the SDKs into the project's
34
+ `.hutch/devkit` sysroot. Hutch's paired Cottontail is the build-time runtime;
35
+ the Cottontail bundled into an app is separately pinned by that resolved
36
+ Electrobun devkit. Installing a beta bootstrap still uses stable templates
37
+ unless `init` receives `--beta`.
26
38
 
27
39
  `DASH_RELEASE_OFFLINE=1` prevents this bootstrap from downloading a missing
28
- Hutch installation. An already installed Hutch is still invoked, but
40
+ Hutch archive. A previously verified cached copy can still run, but
29
41
  `electrobun init` requires network access for the current template catalog and
30
42
  selected template. Builds can run without network access only when every exact
31
43
  release and managed toolchain they require is already installed. The flag does
32
- not put npm or another project package manager into offline mode.
44
+ not put Hutch's built-in dependency resolver or an external project package
45
+ manager into offline mode.
33
46
 
34
47
  You can install Hutch directly from <https://hutch.blackboard.sh> and run the
35
48
  same commands without npm:
@@ -1,60 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
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");
4
+ // The electrobun front door: resolves the vendored (or installed) Hutch and
5
+ // forwards every argument to `hutch electrobun`, supplying this release's
6
+ // paired toolchain versions as defaults for unpinned projects.
9
7
 
10
- const installerBaseUrl = "https://hutch.blackboard.sh/hutch";
11
- const maxInstallerBytes = 1024 * 1024;
12
- const maxInstallerRedirects = 5;
13
-
14
- // Hutch owns a separate release-channel contract: its stable executable is
15
- // still published under Hutch's "production" channel.
16
- function normalizeHutchChannel(value) {
17
- if (value === "stable") return "production";
18
- if (value === "production" || value === "canary") return value;
19
- return null;
20
- }
21
-
22
- function hutchChannel(environment) {
23
- for (const key of ["ELECTROBUN_HUTCH_CHANNEL", "HUTCH_ACTIVE_CHANNEL"]) {
24
- const selected = normalizeHutchChannel(environment[key]);
25
- if (selected) return selected;
26
- }
27
- return "production";
28
- }
29
-
30
- function environmentFlagEnabled(environment, name) {
31
- const value = environment[name];
32
- return (
33
- value === "1" ||
34
- (typeof value === "string" &&
35
- ["true", "yes"].includes(value.toLowerCase()))
36
- );
37
- }
38
-
39
- function hutchBinaryPath(channel, environment, platform, userHome) {
40
- if (environment.ELECTROBUN_HUTCH_BINARY) {
41
- return environment.ELECTROBUN_HUTCH_BINARY;
42
- }
43
-
44
- const pathApi = platform === "win32" ? path.win32 : path.posix;
45
- // HUTCH_HOME is Hutch's own home; DASH_HOME stays honored as a deprecated
46
- // fallback for Dash Desktop and older setups.
47
- const hutchHome =
48
- environment.HUTCH_HOME ||
49
- environment.DASH_HOME ||
50
- pathApi.join(userHome, ".hutch");
51
- const command = channel === "canary" ? "hutch-canary" : "hutch";
52
- return pathApi.join(
53
- hutchHome,
54
- "bin",
55
- `${command}${platform === "win32" ? ".exe" : ""}`,
56
- );
57
- }
8
+ const {
9
+ resolveHutchBinary,
10
+ runHutch,
11
+ } = require("./resolve-hutch.cjs");
58
12
 
59
13
  function hutchArguments(args) {
60
14
  const forwarded = Array.from(args);
@@ -70,163 +24,15 @@ function hutchArguments(args) {
70
24
  : [...forwarded, "--channel=stable"];
71
25
  }
72
26
 
73
- function download(url, redirects = 0) {
74
- if (redirects > maxInstallerRedirects) {
75
- return Promise.reject(new Error("too many installer redirects"));
76
- }
77
-
78
- let target;
79
- try {
80
- target = new URL(url);
81
- } catch {
82
- return Promise.reject(new Error("invalid Hutch installer URL"));
83
- }
84
- if (target.protocol !== "https:") {
85
- return Promise.reject(
86
- new Error("the Hutch installer must be downloaded over HTTPS"),
87
- );
88
- }
89
-
90
- return new Promise((resolve, reject) => {
91
- const request = get(target, (response) => {
92
- if (
93
- response.statusCode >= 300 &&
94
- response.statusCode < 400 &&
95
- response.headers.location
96
- ) {
97
- response.resume();
98
- let redirected;
99
- try {
100
- redirected = new URL(response.headers.location, target);
101
- } catch {
102
- reject(new Error("invalid Hutch installer redirect URL"));
103
- return;
104
- }
105
- resolve(download(redirected.href, redirects + 1));
106
- return;
107
- }
108
-
109
- if (response.statusCode !== 200) {
110
- response.resume();
111
- reject(
112
- new Error(
113
- `installer download returned HTTP ${response.statusCode ?? "unknown"}`,
114
- ),
115
- );
116
- return;
117
- }
118
-
119
- const chunks = [];
120
- let size = 0;
121
- response.on("data", (chunk) => {
122
- size += chunk.length;
123
- if (size > maxInstallerBytes) {
124
- request.destroy(
125
- new Error("installer download exceeded the 1 MiB limit"),
126
- );
127
- return;
128
- }
129
- chunks.push(chunk);
130
- });
131
- response.on("end", () => resolve(Buffer.concat(chunks)));
132
- response.on("error", reject);
133
- });
134
- request.on("error", reject);
135
- });
136
- }
137
-
138
- function checkedSpawn(command, args, options) {
139
- const result = spawnSync(command, args, options);
140
- if (result.error) throw result.error;
141
- if (result.status !== 0) {
142
- throw new Error(
143
- `${command} exited with status ${result.status ?? "unknown"}`,
144
- );
145
- }
146
- }
147
-
148
- async function installHutch({ channel, environment, platform }) {
149
- const temporary = mkdtempSync(path.join(tmpdir(), "electrobun-hutch-"));
150
- try {
151
- if (platform === "win32") {
152
- const installer = path.join(temporary, "install.ps1");
153
- writeFileSync(
154
- installer,
155
- await download(`${installerBaseUrl}/install.ps1`),
156
- );
157
- checkedSpawn(
158
- "powershell.exe",
159
- [
160
- "-NoProfile",
161
- "-NonInteractive",
162
- "-ExecutionPolicy",
163
- "Bypass",
164
- "-File",
165
- installer,
166
- "-Channel",
167
- channel,
168
- ],
169
- { env: environment, stdio: "inherit" },
170
- );
171
- } else {
172
- const installer = path.join(temporary, "install.sh");
173
- writeFileSync(
174
- installer,
175
- await download(`${installerBaseUrl}/install.sh`),
176
- { mode: 0o700 },
177
- );
178
- checkedSpawn("sh", [installer, "--channel", channel], {
179
- env: environment,
180
- stdio: "inherit",
181
- });
182
- }
183
- } finally {
184
- rmSync(temporary, { force: true, recursive: true });
185
- }
186
- }
187
-
188
- function runHutch({ binary, args, environment }) {
189
- const result = spawnSync(binary, ["electrobun", ...args], {
190
- env: environment,
191
- stdio: "inherit",
192
- });
193
- if (result.error) throw result.error;
194
- if (result.status !== null) return result.status;
195
- if (result.signal === "SIGINT") return 130;
196
- if (result.signal === "SIGTERM") return 143;
197
- return 1;
198
- }
199
-
200
27
  async function main(options = {}) {
201
28
  const args = hutchArguments(options.args ?? process.argv.slice(2));
202
29
  const environment = options.environment ?? process.env;
203
- const platform = options.platform ?? process.platform;
204
- const userHome = options.userHome ?? homedir();
205
- const fileExists = options.existsSync ?? existsSync;
206
- const install = options.installHutch ?? installHutch;
207
30
  const run = options.runHutch ?? runHutch;
208
- const channel = hutchChannel(environment);
209
- const binary = hutchBinaryPath(channel, environment, platform, userHome);
210
-
211
- if (!fileExists(binary)) {
212
- if (environment.ELECTROBUN_HUTCH_BINARY) {
213
- throw new Error(`ELECTROBUN_HUTCH_BINARY does not exist: ${binary}`);
214
- }
215
- if (environmentFlagEnabled(environment, "DASH_RELEASE_OFFLINE")) {
216
- throw new Error(
217
- `Hutch is not installed at ${binary}; DASH_RELEASE_OFFLINE prevents downloading it`,
218
- );
219
- }
220
- console.error(
221
- `Electrobun requires Hutch; installing the latest ${channel} release...`,
222
- );
223
- await install({ channel, environment, platform });
224
- }
225
-
226
- if (!fileExists(binary)) {
227
- throw new Error(`Hutch was not installed at ${binary}`);
228
- }
229
- return run({ binary, args, environment });
31
+ const binary = await resolveHutchBinary({
32
+ ...options,
33
+ ensureGlobalHutch: args[0] === "init",
34
+ });
35
+ return run({ binary, args: ["electrobun", ...args], environment });
230
36
  }
231
37
 
232
38
  if (require.main === module) {
@@ -240,9 +46,4 @@ if (require.main === module) {
240
46
  });
241
47
  }
242
48
 
243
- module.exports = {
244
- hutchArguments,
245
- hutchBinaryPath,
246
- hutchChannel,
247
- main,
248
- };
49
+ module.exports = { hutchArguments, main };