electrobun 2.0.1-beta.6 → 2.0.1

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