sproutboat 0.10.2 → 0.11.0
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/CHANGELOG.md +76 -1
- package/README.md +7 -0
- package/SURFACE.md +6 -4
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/bin/sproutboat.cjs +17 -19
- package/package.json +11 -4
- package/scripts/build-cli.ts +113 -40
- package/src/bearssl.ts +203 -50
- package/src/build.ts +25 -4
- package/src/compile.ts +31 -11
- package/src/main.ts +40 -0
- package/src/sqlite.ts +133 -25
- package/src/surface.ts +19 -2
- package/src/toolchain.ts +314 -61
- package/types/sproutboat.d.ts +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,79 @@ maintained going forward by the `release` skill.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.11.0] - 2026-09-13
|
|
14
|
+
### Added
|
|
15
|
+
- `sproutboat toolchain doctor [--json]`: reports the whole toolchain's state
|
|
16
|
+
at a glance — Zig, Porffor, SQLite, BearSSL, and uWebSockets
|
|
17
|
+
versions/paths/presence, plus C compiler/archiver/SDK prerequisites.
|
|
18
|
+
- Self-contained platform packages (baronunread/sproutboat#134, #166):
|
|
19
|
+
`@sproutboat/cli-<os>-<arch>` now ships a vendored, sha256-verified Zig
|
|
20
|
+
archive and the patched Porffor source alongside the native binary. A fresh
|
|
21
|
+
install's first build needs no network for either.
|
|
22
|
+
- `optionalDependencies` restored on the root package: `npm install
|
|
23
|
+
sproutboat` resolves the real native binary for your platform again.
|
|
24
|
+
- `scripts/build-cli.ts --all`: cross-compiles all four platform binaries
|
|
25
|
+
from one machine, for a maintainer building a full release without
|
|
26
|
+
waiting on CI.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Host builds (`build --target host`, `dev`, `build --standalone`) no longer
|
|
30
|
+
require a system C compiler. They prefer one when present; when none is
|
|
31
|
+
found, they fall back to the vendored Zig cross-compiler targeting the
|
|
32
|
+
host itself — covering uWebSockets/SQLite/BearSSL's own object compiles
|
|
33
|
+
and Porffor's final link alike.
|
|
34
|
+
- Zig is validated right after download (confirmed to actually link the
|
|
35
|
+
linux-x86_64-musl target) instead of failing unexplained mid-build.
|
|
36
|
+
- Native dependency caches (Zig, SQLite, BearSSL, uWebSockets) are locked
|
|
37
|
+
during publication and rebuild, and keyed by compiler identity — a
|
|
38
|
+
concurrent build or a stale/corrupted cache entry can no longer be
|
|
39
|
+
silently reused.
|
|
40
|
+
- Non-ASCII Latin-1-range strings emitted as raw bytes under
|
|
41
|
+
`charset=utf-8` (baronunread/sproutboat#172), fixed upstream and pulled in
|
|
42
|
+
via the `@sproutboat/runtime`/`@sproutboat/toolchain` bump.
|
|
43
|
+
- Binary static assets and proxied bodies corrupted by the #172 fix itself
|
|
44
|
+
(baronunread/sproutboat#176), fixed upstream and pulled in via the same
|
|
45
|
+
bump.
|
|
46
|
+
- A capability check rejected any code declaring a local `process()` member,
|
|
47
|
+
blocking `zod` (baronunread/sproutboat#132), fixed upstream and pulled in
|
|
48
|
+
via the same bump.
|
|
49
|
+
|
|
50
|
+
### Removed
|
|
51
|
+
- The Bun-fallback launcher: `bin/sproutboat.cjs` no longer runs
|
|
52
|
+
`src/main.ts` under `bun` when no platform binary is installed — the
|
|
53
|
+
platform binaries are guaranteed present now that they're published and
|
|
54
|
+
self-contained.
|
|
55
|
+
|
|
56
|
+
### Docs
|
|
57
|
+
- `docs/standalone.md`: documents the `toISOString()`-after-async-resume
|
|
58
|
+
livelock (baronunread/sproutboat#168) and its `Date.now()` workaround;
|
|
59
|
+
corrects an earlier note that wrongly called it `Date`-specific.
|
|
60
|
+
- Release provenance notices published alongside toolchain diagnostics.
|
|
61
|
+
|
|
62
|
+
## [0.10.3] - 2026-09-11
|
|
63
|
+
### Added
|
|
64
|
+
- Rate-limiter result carries `resetAt`: `env.<NAME>.limit({ key })` now returns
|
|
65
|
+
`{ success, resetAt }`, where `resetAt` is epoch ms for when the fixed window
|
|
66
|
+
rolls, so a `429` can send an accurate `Retry-After`
|
|
67
|
+
(baronunread/sproutboat#69). Additive; callers reading only `success` are
|
|
68
|
+
unaffected.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- `@sproutboat/runtime` and `@sproutboat/wire` bumped to `^0.5.0`; they carry
|
|
72
|
+
the `resetAt` value the rate-limiter type now declares.
|
|
73
|
+
|
|
74
|
+
### Docs
|
|
75
|
+
- `docs/standalone.md`: dropped the stale "outbound `fetch()` speaks `http://`
|
|
76
|
+
only" bullet. Standalone binaries link BearSSL and verify HTTPS against the
|
|
77
|
+
Mozilla root set (see "TLS, in both directions"). Added a
|
|
78
|
+
dev/standalone/deployed parity note: every binding op, rate limiters and
|
|
79
|
+
`request.cf.clientIp` with `SB_TRUSTED_PROXIES` included, behaves the same
|
|
80
|
+
under `sproutboat dev` as in a standalone binary and a deployed sprout.
|
|
81
|
+
- `capability-http-sync-v0.md`: documented async handlers. `fetch` may return a
|
|
82
|
+
promise it creates (not a `.then()`-chained one, which hangs); async responses
|
|
83
|
+
omit `x-sb-cpu-ms`; `crypto.subtle` digest/sign/verify are async in signature
|
|
84
|
+
only over synchronous inline C, so per-request hashing costs no I/O.
|
|
85
|
+
|
|
13
86
|
## [0.10.2] - 2026-09-11
|
|
14
87
|
### Fixed
|
|
15
88
|
- The one-time Zig download no longer depends on ziglang.org being reachable.
|
|
@@ -370,7 +443,9 @@ its own package.
|
|
|
370
443
|
- Renamed the package to `sproutboat` (was `@sproutboat/cli`); dropped the
|
|
371
444
|
`sprout` bin alias in favour of a user-defined shell alias.
|
|
372
445
|
|
|
373
|
-
[Unreleased]: https://github.com/baronunread/sproutboat-cli/compare/v0.
|
|
446
|
+
[Unreleased]: https://github.com/baronunread/sproutboat-cli/compare/v0.11.0...HEAD
|
|
447
|
+
[0.11.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.10.3...v0.11.0
|
|
448
|
+
[0.10.3]: https://github.com/baronunread/sproutboat-cli/compare/v0.10.2...v0.10.3
|
|
374
449
|
[0.10.2]: https://github.com/baronunread/sproutboat-cli/compare/v0.10.1...v0.10.2
|
|
375
450
|
[0.10.1]: https://github.com/baronunread/sproutboat-cli/compare/v0.10.0...v0.10.1
|
|
376
451
|
[0.10.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.9.0...v0.10.0
|
package/README.md
CHANGED
|
@@ -81,6 +81,13 @@ Run `sproutboat` with no arguments for the grouped list.
|
|
|
81
81
|
[`SURFACE.md`](SURFACE.md) is the generated inventory: every command, every
|
|
82
82
|
argument, every env var, kept honest by a drift test.
|
|
83
83
|
|
|
84
|
+
### Toolchain diagnostics
|
|
85
|
+
|
|
86
|
+
`sproutboat toolchain doctor` reports the selected Porffor, Zig, SQLite, and
|
|
87
|
+
BearSSL inputs, cache locations, active overrides, and host compiler
|
|
88
|
+
prerequisites. It never downloads a toolchain or starts a compiler. Use
|
|
89
|
+
`sproutboat toolchain doctor --json` for CI or support tooling.
|
|
90
|
+
|
|
84
91
|
### KV data and waitlist export
|
|
85
92
|
|
|
86
93
|
Manage one namespace by its account-level name:
|
package/SURFACE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Generated by `src/surface.test.ts` from `src/surface.ts` + the pinned
|
|
4
4
|
> toolchain constants. Do not edit by hand — run `UPDATE_SURFACE=1 bun test`.
|
|
5
5
|
|
|
6
|
-
**Package:** `sproutboat` 0.
|
|
6
|
+
**Package:** `sproutboat` 0.11.0 · runs on Bun (use `bunx`, not `npx`)
|
|
7
7
|
|
|
8
8
|
## Commands
|
|
9
9
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
| `types` | `[project-dir]` | Write sproutboat-env.d.ts from the config, so an editor knows what `env` holds. `dev` and `deploy` refresh it when the config is newer. |
|
|
15
15
|
| `dev` | `[project-dir] [--port <n>] [--no-watch]` | Run the project on this machine against a real broker, rebuilding on save. |
|
|
16
16
|
| `build` | `[project-dir] [--target host] [--standalone]` | Cross-compile the native-fetch sprout (Porffor + Zig). `--target host` builds for this machine instead, to run locally — not deployable. `--standalone` emits one executable carrying its own bindings, with SQLite compiled in (~2 MB) and no broker process. |
|
|
17
|
+
| `toolchain` | `doctor [--json]` | Report the selected compiler inputs, cache locations, overrides, and host prerequisites without downloading or compiling anything. |
|
|
17
18
|
| `deploy` | `[project-dir] [--dry-run] [--artifact <dir>] [--no-wait] [--no-provision]` | Build (unless --artifact), auto-provision id-less storage bindings and pin their ids into sproutboat.jsonc, print the report, upload, wait until the URL serves. The control plane skips an upload that matches the live artifact byte-for-byte. --dry-run stops before upload; --no-wait skips the health check; --no-provision leaves id-less bindings as ephemeral deploy-scoped stores. |
|
|
18
19
|
| `versions` | `<list | view <version-id>> [project-dir]` | List the project's deployed versions, or show one version's artifact and bindings. |
|
|
19
20
|
| `rollback` | `<version-id> [project-dir]` | Re-activate a previous version. |
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
| `whoami` | — | Show the active endpoint and the account the stored token belongs to. |
|
|
31
32
|
|
|
32
33
|
```
|
|
33
|
-
usage: sproutboat <init [name] | check [project-dir] | types [project-dir] | dev [project-dir] [--port <n>] [--no-watch] | build [project-dir] [--target host] [--standalone] | deploy [project-dir] [--dry-run] [--artifact <dir>] [--no-wait] [--no-provision] | versions <list | view <version-id>> [project-dir] | rollback <version-id> [project-dir] | tail [project-dir] [--sprout] | kv <list | create <name> | info <name> | rename <name> <new> | delete <name>> | <key | bulk | export> ... | d1 <list | create <name> | info <name> | rename <name> <new> | delete <name>> | r2 <list | create <name> | info <name> | rename <name> <new> | delete <name>> | queues <list | create <name> | info <name> | rename <name> <new> | delete <name>> | domains <list | add <host> | verify <host> | delete <host>> [project-dir] | secrets <list | put <NAME> [--value <value>] | delete <NAME>> [project-dir] | delete [project-dir] [--name <project>] --yes | login [--api-url <url>] [--token <token>] | logout [--api-url <url>] | whoami>
|
|
34
|
+
usage: sproutboat <init [name] | check [project-dir] | types [project-dir] | dev [project-dir] [--port <n>] [--no-watch] | build [project-dir] [--target host] [--standalone] | toolchain doctor [--json] | deploy [project-dir] [--dry-run] [--artifact <dir>] [--no-wait] [--no-provision] | versions <list | view <version-id>> [project-dir] | rollback <version-id> [project-dir] | tail [project-dir] [--sprout] | kv <list | create <name> | info <name> | rename <name> <new> | delete <name>> | <key | bulk | export> ... | d1 <list | create <name> | info <name> | rename <name> <new> | delete <name>> | r2 <list | create <name> | info <name> | rename <name> <new> | delete <name>> | queues <list | create <name> | info <name> | rename <name> <new> | delete <name>> | domains <list | add <host> | verify <host> | delete <host>> [project-dir] | secrets <list | put <NAME> [--value <value>] | delete <NAME>> [project-dir] | delete [project-dir] [--name <project>] --yes | login [--api-url <url>] [--token <token>] | logout [--api-url <url>] | whoami>
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
## Environment variables
|
|
@@ -42,8 +43,9 @@ usage: sproutboat <init [name] | check [project-dir] | types [project-dir] | dev
|
|
|
42
43
|
| `SPROUTBOAT_ZIG` | Path to a Zig binary to use instead of downloading the pinned one. |
|
|
43
44
|
| `SPROUTBOAT_ZIG_URL` | URL to download the pinned Zig tarball from, instead of a Zig community mirror. The sha256 is still enforced. |
|
|
44
45
|
| `SPROUTBOAT_UWS_TARBALL` | Path to a uWebSockets source tarball to seed the Porffor cache with, instead of the one vendored in the package. Both targets are seeded from it. |
|
|
45
|
-
| `CC` | C compiler
|
|
46
|
-
| `
|
|
46
|
+
| `CC` | C compiler for a host build (default `cc` if present, else the vendored Zig cross-compiler targeting this host). |
|
|
47
|
+
| `CXX` | C++ compiler for a host build's native-fetch link step (default `c++` if present, else the vendored Zig cross-compiler targeting this host). |
|
|
48
|
+
| `AR` | Archiver used to assemble uSockets.a for a host build (default `ar` if present, else Zig's). |
|
|
47
49
|
| `SPROUTBOAT_COMPILE_TIMEOUT_MS` | Porffor compile timeout in ms (default 600000). |
|
|
48
50
|
| `SPROUTBOAT_TOOLCHAIN_CACHE` | Managed Porffor and compiler cache root (default ~/.cache/sproutboat). |
|
|
49
51
|
| `SPROUTBOAT_PORFFOR_DIR` | Contributor override for an existing Porffor source checkout. Required files are validated before use. |
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Third-party notices and provenance
|
|
2
|
+
|
|
3
|
+
This release contains or acquires the components below. Pins and checksums are
|
|
4
|
+
recorded so a released executable and every first-build download can be traced
|
|
5
|
+
to its upstream source.
|
|
6
|
+
|
|
7
|
+
| Component | Version or revision | Distribution role | License | Source |
|
|
8
|
+
| --- | --- | --- | --- | --- |
|
|
9
|
+
| Porffor | `a415d194e74948f0ac32b9d608153ea8720c71fd` | Acquired on first application build | MIT | https://github.com/CanadaHonk/porffor |
|
|
10
|
+
| Zig | `0.16.0` | Acquired cross-compiler | MIT | https://ziglang.org |
|
|
11
|
+
| uWebSockets and uSockets | `360c276d609d59af56ae6932adb95154ace9f15f` | Embedded source and static archive | Apache-2.0 | https://github.com/uNetworking/uWebSockets |
|
|
12
|
+
| esbuild | `0.28.2` | Embedded platform executable | MIT | https://esbuild.github.io |
|
|
13
|
+
| SQLite | `3.50.4` | Acquired for standalone builds | Public domain | https://sqlite.org |
|
|
14
|
+
| BearSSL | `0.6` | Acquired for standalone builds | MIT | https://bearssl.org |
|
|
15
|
+
|
|
16
|
+
## Integrity pins
|
|
17
|
+
|
|
18
|
+
| Component | SHA-256 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| Porffor source archive | `677779c8be49efc28686378f665cd6fc4a8a2dd81e7b15a89298103ded960829` |
|
|
21
|
+
| Embedded uWebSockets archive | `e83736f3f8cf9d56a1ebe6ea61625a7af12386763374d47c14cff472ada7484a` |
|
|
22
|
+
| SQLite amalgamation archive | `1d3049dd0f830a025a53105fc79fd2ab9431aea99e137809d064d8ee8356b032` |
|
|
23
|
+
| BearSSL source archive | `6705bba1714961b41a728dfc5debbe348d2966c117649392f8c8139efc83ff14` |
|
|
24
|
+
| Mozilla CA bundle | `f66dff1bdf8f96060b8177976f8b7d9254bc89bc4db933d769f7384d28480bc9` |
|
|
25
|
+
|
|
26
|
+
The complete Apache-2.0 notice for uWebSockets and uSockets is retained in the
|
|
27
|
+
embedded source archive. Porffor and BearSSL retain their MIT notices in the
|
|
28
|
+
source archives acquired at the revisions above. esbuild's MIT notice is
|
|
29
|
+
available from its upstream distribution.
|
package/bin/sproutboat.cjs
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// npm
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// npm resolves exactly one @sproutboat/cli-<os>-<arch> optionalDependency for
|
|
3
|
+
// this host at install time (#134); this launcher just execs that native
|
|
4
|
+
// single-file binary. No Bun, no fallback: a platform with no published
|
|
5
|
+
// binary means npm skipped that optional dependency, and require.resolve
|
|
6
|
+
// below fails with a clear "package not found" rather than a silent one.
|
|
6
7
|
const { spawn } = require("node:child_process");
|
|
7
|
-
const { join } = require("node:path");
|
|
8
8
|
|
|
9
9
|
const platform = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : null;
|
|
10
10
|
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : null;
|
|
11
11
|
|
|
12
|
+
if (!platform || !arch) {
|
|
13
|
+
console.error(`sproutboat: unsupported host ${process.platform}/${process.arch}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
let command;
|
|
13
|
-
let args;
|
|
14
|
-
let viaBun = false;
|
|
15
18
|
try {
|
|
16
|
-
if (!platform || !arch) throw new Error(`unsupported host ${process.platform}/${process.arch}`);
|
|
17
19
|
command = require.resolve(`@sproutboat/cli-${platform}-${arch}/bin/sproutboat`);
|
|
18
|
-
args = process.argv.slice(2);
|
|
19
20
|
} catch {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
console.error(
|
|
22
|
+
`sproutboat: no native build installed for ${platform}-${arch}. ` +
|
|
23
|
+
`Reinstall sproutboat, or install @sproutboat/cli-${platform}-${arch} directly.`,
|
|
24
|
+
);
|
|
25
|
+
process.exit(1);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
const child = spawn(command,
|
|
28
|
+
const child = spawn(command, process.argv.slice(2), { stdio: "inherit", windowsHide: true });
|
|
27
29
|
|
|
28
30
|
child.once("error", (error) => {
|
|
29
|
-
|
|
30
|
-
console.error("sproutboat: this build needs Bun on PATH. Install it: https://bun.sh");
|
|
31
|
-
} else {
|
|
32
|
-
console.error(`sproutboat: could not start: ${error.message}`);
|
|
33
|
-
}
|
|
31
|
+
console.error(`sproutboat: could not start: ${error.message}`);
|
|
34
32
|
process.exit(1);
|
|
35
33
|
});
|
|
36
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sproutboat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Wrangler-shaped CLI for Sproutboat. Deploys workers to any control plane via --api-url / SPROUTBOAT_API_URL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"CHANGELOG.md",
|
|
26
26
|
"SURFACE.md",
|
|
27
|
+
"THIRD_PARTY_NOTICES.md",
|
|
27
28
|
"src",
|
|
28
29
|
"bin/sproutboat.cjs",
|
|
29
30
|
"scripts",
|
|
@@ -63,9 +64,9 @@
|
|
|
63
64
|
"@sproutboat/artifact": "^0.2.0",
|
|
64
65
|
"@sproutboat/assets": "^0.2.0",
|
|
65
66
|
"@sproutboat/config": "^0.3.0",
|
|
66
|
-
"@sproutboat/runtime": "^0.
|
|
67
|
-
"@sproutboat/toolchain": "^0.
|
|
68
|
-
"@sproutboat/wire": "^0.
|
|
67
|
+
"@sproutboat/runtime": "^0.6.1",
|
|
68
|
+
"@sproutboat/toolchain": "^0.4.0",
|
|
69
|
+
"@sproutboat/wire": "^0.5.0",
|
|
69
70
|
"esbuild": "^0.28.2"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
@@ -76,6 +77,12 @@
|
|
|
76
77
|
"oxlint": "1.81.0",
|
|
77
78
|
"typescript": "7.0.2"
|
|
78
79
|
},
|
|
80
|
+
"optionalDependencies": {
|
|
81
|
+
"@sproutboat/cli-darwin-arm64": "0.11.0",
|
|
82
|
+
"@sproutboat/cli-darwin-x64": "0.11.0",
|
|
83
|
+
"@sproutboat/cli-linux-arm64": "0.11.0",
|
|
84
|
+
"@sproutboat/cli-linux-x64": "0.11.0"
|
|
85
|
+
},
|
|
79
86
|
"engines": {
|
|
80
87
|
"node": ">=18"
|
|
81
88
|
}
|
package/scripts/build-cli.ts
CHANGED
|
@@ -1,44 +1,117 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Build platform package executable(s). npm's tiny root launcher resolves one
|
|
3
|
+
* of these at install time.
|
|
4
|
+
*
|
|
5
|
+
* Default (no args): build the host's own target only — what CI's per-platform
|
|
6
|
+
* matrix runners want, one native build each, no cross-compilation involved.
|
|
7
|
+
*
|
|
8
|
+
* `--all`: cross-compile all four targets from this one machine (`bun build
|
|
9
|
+
* --compile` can target any `bun-<os>-<arch>` regardless of host — verified
|
|
10
|
+
* against real Linux/macOS output, not just docs). For a maintainer building
|
|
11
|
+
* every platform package locally without waiting on CI.
|
|
12
|
+
*/
|
|
2
13
|
import { chmod, copyFile, mkdir } from "node:fs/promises";
|
|
3
14
|
import { resolve } from "node:path";
|
|
15
|
+
import { ensureZigArchive, type ZigPlatform } from "../src/toolchain";
|
|
4
16
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
throw new Error(`
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
17
|
+
type Target = { platform: "darwin" | "linux"; arch: "arm64" | "x64"; bunTarget: string; zigPlatform: ZigPlatform };
|
|
18
|
+
|
|
19
|
+
const TARGETS: Target[] = [
|
|
20
|
+
{ platform: "darwin", arch: "arm64", bunTarget: "bun-darwin-arm64", zigPlatform: "aarch64-macos" },
|
|
21
|
+
{ platform: "darwin", arch: "x64", bunTarget: "bun-darwin-x64", zigPlatform: "x86_64-macos" },
|
|
22
|
+
{ platform: "linux", arch: "arm64", bunTarget: "bun-linux-arm64", zigPlatform: "aarch64-linux" },
|
|
23
|
+
{ platform: "linux", arch: "x64", bunTarget: "bun-linux-x64", zigPlatform: "x86_64-linux" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function hostTarget(): Target {
|
|
27
|
+
const platform = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : null;
|
|
28
|
+
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : null;
|
|
29
|
+
if (!platform || !arch) throw new Error(`unsupported release host ${process.platform}/${process.arch}`);
|
|
30
|
+
const match = TARGETS.find((t) => t.platform === platform && t.arch === arch);
|
|
31
|
+
if (!match) throw new Error(`no target entry for ${platform}/${arch}`);
|
|
32
|
+
return match;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const root = resolve(import.meta.dir, "..");
|
|
36
|
+
|
|
37
|
+
/** esbuild ships one binary per platform (`@esbuild/<os>-<arch>`); a cross
|
|
38
|
+
* target's copy is never installed locally (npm/bun refuse it — the package
|
|
39
|
+
* declares `os`/`cpu` and gets skipped for any other host), so fetch that
|
|
40
|
+
* platform's tarball from the registry instead, same shape as ensureZig's
|
|
41
|
+
* prebuilt-archive fetches. */
|
|
42
|
+
async function esbuildBinaryFor(target: Target): Promise<string> {
|
|
43
|
+
const host = hostTarget();
|
|
44
|
+
if (target.platform === host.platform && target.arch === host.arch) {
|
|
45
|
+
try {
|
|
46
|
+
return Bun.resolveSync(`@esbuild/${target.platform}-${target.arch}/bin/esbuild`, import.meta.dir);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error("host esbuild binary is missing; run bun install first");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// SAFETY: package.json is this repo's own manifest, with a required esbuild dependency entry.
|
|
52
|
+
const pkg = (await Bun.file(resolve(root, "package.json")).json()) as { dependencies: { esbuild: string } };
|
|
53
|
+
const version = pkg.dependencies.esbuild.replace(/^[\^~]/, "");
|
|
54
|
+
const name = `${target.platform}-${target.arch}`;
|
|
55
|
+
const cacheDir = resolve(
|
|
56
|
+
process.env.SPROUTBOAT_TOOLCHAIN_CACHE ?? `${process.env.HOME}/.cache/sproutboat`,
|
|
57
|
+
`esbuild-${version}-${name}`,
|
|
58
|
+
);
|
|
59
|
+
const cached = resolve(cacheDir, "esbuild");
|
|
60
|
+
if (await Bun.file(cached).exists()) return cached;
|
|
61
|
+
const res = await fetch(`https://registry.npmjs.org/@esbuild/${name}/-/${name}-${version}.tgz`);
|
|
62
|
+
if (!res.ok) throw new Error(`could not fetch @esbuild/${name}@${version}: HTTP ${res.status}`);
|
|
63
|
+
const tarPath = resolve(cacheDir, "esbuild.tgz");
|
|
64
|
+
await mkdir(cacheDir, { recursive: true });
|
|
65
|
+
await Bun.write(tarPath, await res.arrayBuffer());
|
|
66
|
+
const untar = Bun.spawn(["tar", "-xzf", tarPath, "-C", cacheDir, "--strip-components=2", "package/bin/esbuild"], {
|
|
67
|
+
stdout: "pipe",
|
|
68
|
+
stderr: "pipe",
|
|
69
|
+
});
|
|
70
|
+
const [code, err] = await Promise.all([untar.exited, new Response(untar.stderr).text()]);
|
|
71
|
+
if (code !== 0) throw new Error(`could not extract @esbuild/${name}: ${err.trim()}`);
|
|
72
|
+
await chmod(cached, 0o755);
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function buildOne(target: Target): Promise<void> {
|
|
77
|
+
const packageDir = resolve(root, "platform-packages", `${target.platform}-${target.arch}`);
|
|
78
|
+
const out = resolve(packageDir, "bin", "sproutboat");
|
|
79
|
+
await mkdir(resolve(out, ".."), { recursive: true });
|
|
80
|
+
// SAFETY: package.json is this release's manifest and npm requires its version.
|
|
81
|
+
const version = ((await Bun.file(resolve(root, "package.json")).json()) as { version: string }).version;
|
|
82
|
+
// SAFETY: every checked-in platform package manifest owns a required string version.
|
|
83
|
+
const platformVersion = ((await Bun.file(resolve(packageDir, "package.json")).json()) as { version: string }).version;
|
|
84
|
+
if (platformVersion !== version)
|
|
85
|
+
throw new Error(`platform package version ${platformVersion} does not match root ${version}`);
|
|
86
|
+
console.log(`building ${target.platform}-${target.arch}...`);
|
|
87
|
+
const child = Bun.spawn(
|
|
88
|
+
[
|
|
89
|
+
process.execPath,
|
|
90
|
+
"build",
|
|
91
|
+
"--compile",
|
|
92
|
+
`--target=${target.bunTarget}`,
|
|
93
|
+
`--define:process.env.SPROUTBOAT_CLI_VERSION=${JSON.stringify(version)}`,
|
|
94
|
+
"--outfile",
|
|
95
|
+
out,
|
|
96
|
+
"src/main.ts",
|
|
97
|
+
],
|
|
98
|
+
{ cwd: root, stdout: "inherit", stderr: "inherit" },
|
|
99
|
+
);
|
|
100
|
+
if ((await child.exited) !== 0) throw new Error(`compile failed for ${target.platform}-${target.arch}`);
|
|
101
|
+
const esbuild = await esbuildBinaryFor(target);
|
|
102
|
+
const packagedEsbuild = resolve(packageDir, "bin", "esbuild");
|
|
103
|
+
await copyFile(esbuild, packagedEsbuild);
|
|
104
|
+
// The compressed archive (~50 MB), not the ~400 MB extracted install: Zig's
|
|
105
|
+
// lib/ carries libc/libc++ sources for every target it can cross-compile to,
|
|
106
|
+
// which only matters once, on extract. `ensureZig` (src/toolchain.ts) looks
|
|
107
|
+
// for this file next to the running executable before ever hitting the
|
|
108
|
+
// network, and extracts it into the ordinary toolchain cache on first use --
|
|
109
|
+
// same shape as the vendored uWebSockets archive.
|
|
110
|
+
await copyFile(await ensureZigArchive(target.zigPlatform), resolve(packageDir, "bin", "zig.tar.xz"));
|
|
111
|
+
await copyFile(resolve(root, "THIRD_PARTY_NOTICES.md"), resolve(packageDir, "THIRD_PARTY_NOTICES.md"));
|
|
112
|
+
await chmod(packagedEsbuild, 0o755);
|
|
113
|
+
await chmod(out, 0o755);
|
|
41
114
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
115
|
+
|
|
116
|
+
const all = process.argv.includes("--all");
|
|
117
|
+
for (const target of all ? TARGETS : [hostTarget()]) await buildOne(target);
|