Releases: baronunread/sproutboat-cli
Releases · baronunread/sproutboat-cli
Release list
v0.11.10
[0.11.10] - 2026-09-18
Changed
- Bumped
@sproutboat/toolchainto0.4.10, pinning Porffor to the
alpha-7tag. All local patches (render.js, uwebsockets.js) verified
against the real alpha-7 source — no anchor drift, kitchen-sink
conformance and the full test suite pass.
v0.11.9
[0.11.9] - 2026-09-17
Fixed
- Bumped
@sproutboat/toolchainto^0.4.9and@sproutboat/runtimeto
^0.9.6.0.11.6's toolchain pin (^0.4.5since0.4.2) predated a
native R2 size-gating feature that shipped in0.4.6/0.4.7with a
misplaced#endifin the generated C++ guard, breaking every R2-free
native build with a compiler error. The fix landed as0.4.8, but that
version got stuck permanently conflicted on npm after an interrupted
trusted-publish attempt, so0.4.9/0.9.6revert the feature outright
instead: R2 transfer support is native code again, always compiled in
with 404 stubs where unused, same as before it existed. It saved ~11KB
of__text(~1.3% of a typical binary) and didn't even change the
shipped file size on macOS, since__TEXTis page-aligned there — not
worth the guard-placement bug class it introduced.
v0.11.5
[0.11.5] - 2026-09-14
Fixed
- Bumped
@sproutboat/runtimeto^0.6.6.__sbEntrycalledhandlers.fetch()
(and thescheduled/queue/alarmtrigger paths) with no try/catch at all:
any synchronous throw, or a rejected async handler promise, propagated all
the way up and crashed the whole process: not just the request that hit
it, but every other in-flight and future request on that sprout, until
whatever supervises it restarted the binary (baronunread/sproutboat#179). A
throwing/rejectingfetch()now returns a 500 instead.scheduled()/
alarm()still reply204either way (no failure signal existed before
this either); a throwingqueue()falls through to the existing
default-ack pass, same as a handler that never callsack()/retry()on
every message. - Upgrading the CLI is the only way to pick this up. The runtime prelude is
embedded in the CLI binary when that binary is built, so a project's own
@sproutboat/runtimedependency has no effect on--standaloneor
--target hostbuilds.
v0.11.4
[0.11.4] - 2026-09-14
Fixed
- Bumped
@sproutboat/runtimeto^0.6.5, undoing a severe performance
regression thatv0.11.3shipped (baronunread/sproutboat#181).0.6.4's
encode fix was correct for the large inputs it targeted and badly wrong for
small ones: an app hashing ~150-byte values throughcrypto.subtle.digest
on every request lost 64% of its throughput (4,700 to 1,680 req/s),
tripled its p50 (4.6ms to 13.4ms) and grew 41% in RSS. The encoder now only
reaches for an array once 512 bytes have accumulated, so a small input runs
exactly likev0.11.2while a large one keeps the#180fix. - Upgrading the CLI is the only way to pick this up. The runtime prelude is
embedded in the CLI binary when that binary is built, so a project's own
@sproutboat/runtimedependency has no effect on--standaloneor
--target hostbuilds. Anyone onv0.11.3should move to this release;
v0.11.2is the last unaffected version before it.
Changed
- The
darwin-x64platform package is now cross-compiled on the arm64 macOS
runner instead of built natively onmacos-13. GitHub's only x64 macOS pool
routinely left that leg queued for 30+ minutes and blocked two releases,
includingv0.11.3, where the root package published while its own
optionalDependencyon@sproutboat/cli-darwin-x64was still waiting on it.
Nothing in the package needs x64 hardware to produce.scripts/build-cli.ts
gains--target <os>-<arch>for this.
v0.11.3
[0.11.3] - 2026-09-14
Fixed
- Bumped
@sproutboat/runtimeto^0.6.4, picking up the encode-side half of
the sameO(n^2)string-building bugv0.11.2fixed on the decode side.
__sbToBytessits behind everycrypto.subtlecall a handler makes:
digest,importKey,sign,verifyand the PBKDF2/scrypt path all funnel
their data, keys, salts and signatures through it, so hashing a request body
was quadratic in the body's size.__sbHexOrBytes, which decodes the
expectedside of an HMAC verify, was worse still; it rewrote its
accumulator's last character on every odd nibble, copying the whole string
twice per byte (baronunread/sproutboat#180).
Superseded by v0.11.4. Do not use this release. The runtime bump it carries regressed small-input encoding badly: an app hashing ~150-byte values through crypto.subtle on every request lost 64% of its throughput, tripled its p50 and grew 41% in RSS (baronunread/sproutboat#181). Upgrade to v0.11.4, or stay on v0.11.2.
v0.10.2
[0.10.2] - 2026-09-11
Fixed
- The one-time Zig download no longer depends on ziglang.org being reachable.
It now pulls from a random Zig community mirror, with ziglang.org kept only
as a last resort, and tries the mirrors in turn on failure. ziglang.org
rate-limits the multi-megabyte tarballs and had been timing out mid-build,
with no fallback (baronunread/sproutboat#166). The archive sha256 is still
enforced whatever the source.SPROUTBOAT_ZIG_URLoverrides the source;
SPROUTBOAT_ZIGstill points at a prebuilt binary. - A leftover
~/.cache/sproutboat/zig-<version>/directory from an earlier
cache layout is removed on the next build instead of sitting unused.
v0.10.1
[0.10.1] - 2026-09-11
Fixed
npm install sproutboat/bunx sproutboatwork again. 0.10.0's launcher
hard-required per-platform@sproutboat/cli-*packages that are not published
yet, so it always failed with "optional package … is missing". The launcher
now falls back to runningsrc/main.tswith Bun when no platform binary is
installed (the v0.9.0 model), andoptionalDependenciesare dropped until the
binaries actually ship. 0.10.0 is deprecated on npm.sproutboat --versionand the deploy banner report the real version instead
of a hard-coded0.9.0when run from the npm package rather than a
bun build --compilebinary.sproutboat build/deploylocateesbuildvia the installed dependency,
not justprocess.execPath's directory, so bundling works on the Bun
fallback path.- The launcher no longer hangs when it receives
SIGINT/SIGTERMwhile
forwarding a signal to a running platform binary.
v0.10.0
[0.10.0] - 2026-09-11
Added
- Per-platform native CLI:
npm install sproutboatpulls a prebuilt binary for
the host (@sproutboat/cli-{darwin,linux}-{arm64,x64}) behind a thin launcher
that preserves signals and embeds its own version, so the CLI runs without Bun
onPATH. - Porffor is fetched on the first build, not installed as a dependency: the
pinned commit is downloaded into~/.cache/sproutboat, SHA-256 verified, and
patched there. Installation clones nothing and runs no Git, Make or compiler. request.cf.clientIpin standalone builds, from a server-set
x-sb-remote-addr, withSB_TRUSTED_PROXIESforX-Forwarded-Forresolution
behind a reverse proxy (baronunread/sproutboat#163).env.<D1>.backup(name?)— an online, integrity-checked single-file snapshot
of a D1 database viaVACUUM INTO, on both the embedded and broker transports
(baronunread/sproutboat#164).- Rate Limiting binding:
ratelimiters: [{ binding, limit, period }]in
sproutboat.jsoncgivesenv.<NAME>.limit({ key }) -> { success }, a
fixed-window counter on both transports (baronunread/sproutboat#69). crypto.subtlesubset:digest(SHA-256/384/512) and HMAC
importKey/sign/verify, backed by reference SHA-2 as inline C so it
works on both transports (baronunread/sproutboat#133). No ECDSA or AES yet.crypto.scryptVerify(password, salt, expected, { N, r, p })— a verify-only
scrypt (RFC 7914) for migrating password hashes made by Node/Bunscrypt
(baronunread/sproutboat#153). Not a blessed KDF for new credentials.x-sb-cpu-ms: per-invocation CPU time, carried on the handler response.
Fixed
303(and every other status not in Porffor's table) no longer resets the
connection on standalone builds (baronunread/sproutboat#156).- Handler
console.log/console.errorreach stderr, unbuffered, in
standalone builds instead of vanishing (baronunread/sproutboat#165). sproutboat devno longer leaves the previous sprout running across a
rebuild, delivers no triggers to a candidate that failed to start, and cleans
up every failed setup path on rebuild and on shutdown.- The Zig toolchain cache is hardened against a partial or concurrent download.
Changed
- Porffor pin bumped alpha-4 → alpha-5 (
1f4ae4ae); the sameUWS_COMMIT,
so no uWebSockets re-vendor. - Config parsing, the artifact manifest, the binding broker, the wire assets and
the whole native-fetch runtime (prelude + transports) now come from published
@sproutboat/*packages; the CLI keeps thin re-export shims. The Porffor pin
and its source patches moved to@sproutboat/toolchain. - Broker cron / queue / alarm delivery is gated by a local signal, so only a
promoted candidate runs timers.
Performance
- Embedded transport caches prepared statements per database (FIFO, 32/db)
instead of recompiling the SQL on every binding op — the op boundary drops
from ~0.6 ms to tens of µs (baronunread/sproutboat#155). - The broker's due-queue and due-alarm polls are indexed.
Docs
docs/standalone.mddocuments the single-threaded execution model and the
SO_REUSEPORTmulti-process recipe for scaling past one core
(baronunread/sproutboat#154); the embedded transport also sets
PRAGMA busy_timeoutso shared-data-dir writers wait instead of failing.
Also: the client-IP behaviour and backing up D1.
v0.8.0
[0.8.0] — 2026-09-07
Added
sproutboat build --standalone: one executable that carries its own bindings.
SQLite is compiled into the sprout, so a ~2 MB file serves KV, D1, R2, queues,
Durable Objects, alarms, analytics and assets with nothing beside it on disk —
no Bun, no broker, no control plane. State lives in<name>.data/store.sqlite
plusd1/<binding>.sqlite, the same layoutsproutboat devwrites, which is
what lets one conformance suite hold both backends to the same behaviour.
Secrets come from the environment (then<data>/secrets.json) and a missing
one refuses the boot, listing every name at once, rather than throwing on the
first request that needs it. Cron ticks, queue batches and DO alarms run on
in-process timers; assets are baked in, capped at 8 MB.- Outbound TLS from a standalone binary.
fetch("https://…")verifies against
curl's Mozilla-derived root set via BearSSL, linked in beside SQLite
(1.86 → 2.02 MB).SB_CA_BUNDLEadds a private or corporate CA to that set;
it only ever adds trust, and nothing disables verification. - Durable Object alarms:
setAlarm/getAlarm/deleteAlarmand the
alarm()handler. At most one alarm is pending per object and a later
setAlarmreplaces it, matching Workers. Delivery claims before running, so
analarm()that schedules its own next run is not erased by the delivery
that invoked it. - Service bindings:
env.<BINDING>.fetch()reaches another deployment on the
same node through the edge on loopback. This is the CLI half; a binding that
resolves to nothing reports the target as undeployed rather than failing as a
502. - Binary values in the binding frame. An R2 object body now travels beside the
JSON rather than encoded inside it. SB_FETCH_MAX_BYTES(32 MiB default) caps an outbound response body. An
unbounded upstream could previously drive a sprout's memory to whatever it
chose to send.
Fixed
d1.execran only the first statement of a multi-statement script, so a
schema built in oneexec()call silently created only its first table.r2.get/head/put/listreturned flat fields where the shim reads
r.object, andae.queryomitted thecountits caller reads.- Trigger authentication accepted any caller when no token was configured.
The hole predates this release; a standalone binary listening on a public
interface is what made it reachable. - Binary R2 values were corrupted in transit —
0x08and0x0carrived as
bandf. - A retried binding call could apply a write twice. Every request now carries an
id and the broker replays the cached reply for a repeat of a mutating op
instead of performing it again.
Changed
- A dropped broker connection is retried four times with 0/5/25/100 ms backoff
instead of failing after one attempt, which covers a broker restart mid-call. - The bundled (Bun) standalone backend is gone. The embedded one passes the same
suite at 1.9 MB against 63 MB, and TLS removed its last real advantage. - A native-fetch binary cannot see
argv— Porffor's runtime init calls
porf_init(0, NULL)— so a standalone binary is configured throughPORT,
SB_DATA_DIR/SPROUTBOAT_DATAand the environment only, never flags.
Performance
- An 8 MB R2 put through the broker went from 255 MB to 149 MB peak RSS.
- The broker's frame reader no longer re-concatenates its buffer per chunk.
v0.6.0
Added
bindings.jsonnow carriesvars— the baked plain values a sprout was
built with — so the control plane can show what a version was compiled
against. They ride along for display only; the broker never serves them,
they are compiled into the sprout itself. A project whose only binding
config isvarsnow gets a sidecar written at all, where before it got
none.
Changed
- Tooling only, no change to how the CLI behaves: the tree is now formatted
with oxfmt 0.66.0 (the config landed in 0.5.0 but was never run over the
tree), markdown is excluded from formatting, a lefthook pre-commit hook
formats staged files and lints, the last 10 oxlint warnings are cleared,
and CI gates lint alongside typecheck and test. - README rewritten shorter: a logo lockup that survives both GitHub themes
(docs/logo-light.svg/docs/logo-dark.svgbehind a<picture>), the
everyday commands as a five-row table, and the full command inventory left
to the generatedSURFACE.mdinstead of duplicated by hand.