sproutboat 0.8.0 → 0.10.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 ADDED
@@ -0,0 +1,365 @@
1
+ # Changelog
2
+
3
+ All notable changes to `sproutboat` (the CLI) are documented here. Format
4
+ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning
5
+ follows [SemVer](https://semver.org/), pre-1.0 so a minor bump can still carry
6
+ a breaking change.
7
+
8
+ Reconstructed from git history on 2026-09-03 for everything through v0.4.11;
9
+ maintained going forward by the `release` skill.
10
+
11
+ ## [Unreleased]
12
+
13
+ ## [0.10.0] - 2026-09-11
14
+ ### Added
15
+ - Per-platform native CLI: `npm install sproutboat` pulls a prebuilt binary for
16
+ the host (`@sproutboat/cli-{darwin,linux}-{arm64,x64}`) behind a thin launcher
17
+ that preserves signals and embeds its own version, so the CLI runs without Bun
18
+ on `PATH`.
19
+ - Porffor is fetched on the first build, not installed as a dependency: the
20
+ pinned commit is downloaded into `~/.cache/sproutboat`, SHA-256 verified, and
21
+ patched there. Installation clones nothing and runs no Git, Make or compiler.
22
+ - `request.cf.clientIp` in standalone builds, from a server-set
23
+ `x-sb-remote-addr`, with `SB_TRUSTED_PROXIES` for `X-Forwarded-For` resolution
24
+ behind a reverse proxy (baronunread/sproutboat#163).
25
+ - `env.<D1>.backup(name?)` — an online, integrity-checked single-file snapshot
26
+ of a D1 database via `VACUUM INTO`, on both the embedded and broker transports
27
+ (baronunread/sproutboat#164).
28
+ - Rate Limiting binding: `ratelimiters: [{ binding, limit, period }]` in
29
+ `sproutboat.jsonc` gives `env.<NAME>.limit({ key }) -> { success }`, a
30
+ fixed-window counter on both transports (baronunread/sproutboat#69).
31
+ - `crypto.subtle` subset: `digest` (SHA-256/384/512) and HMAC
32
+ `importKey` / `sign` / `verify`, backed by reference SHA-2 as inline C so it
33
+ works on both transports (baronunread/sproutboat#133). No ECDSA or AES yet.
34
+ - `crypto.scryptVerify(password, salt, expected, { N, r, p })` — a verify-only
35
+ scrypt (RFC 7914) for migrating password hashes made by Node/Bun `scrypt`
36
+ (baronunread/sproutboat#153). Not a blessed KDF for new credentials.
37
+ - `x-sb-cpu-ms`: per-invocation CPU time, carried on the handler response.
38
+
39
+ ### Fixed
40
+ - `303` (and every other status not in Porffor's table) no longer resets the
41
+ connection on standalone builds (baronunread/sproutboat#156).
42
+ - Handler `console.log` / `console.error` reach stderr, unbuffered, in
43
+ standalone builds instead of vanishing (baronunread/sproutboat#165).
44
+ - `sproutboat dev` no longer leaves the previous sprout running across a
45
+ rebuild, delivers no triggers to a candidate that failed to start, and cleans
46
+ up every failed setup path on rebuild and on shutdown.
47
+ - The Zig toolchain cache is hardened against a partial or concurrent download.
48
+
49
+ ### Changed
50
+ - Porffor pin bumped alpha-4 → **alpha-5** (`1f4ae4ae`); the same `UWS_COMMIT`,
51
+ so no uWebSockets re-vendor.
52
+ - Config parsing, the artifact manifest, the binding broker, the wire assets and
53
+ the whole native-fetch runtime (prelude + transports) now come from published
54
+ `@sproutboat/*` packages; the CLI keeps thin re-export shims. The Porffor pin
55
+ and its source patches moved to `@sproutboat/toolchain`.
56
+ - Broker cron / queue / alarm delivery is gated by a local signal, so only a
57
+ promoted candidate runs timers.
58
+
59
+ ### Performance
60
+ - Embedded transport caches prepared statements per database (FIFO, 32/db)
61
+ instead of recompiling the SQL on every binding op — the op boundary drops
62
+ from ~0.6 ms to tens of µs (baronunread/sproutboat#155).
63
+ - The broker's due-queue and due-alarm polls are indexed.
64
+
65
+ ### Docs
66
+ - `docs/standalone.md` documents the single-threaded execution model and the
67
+ `SO_REUSEPORT` multi-process recipe for scaling past one core
68
+ (baronunread/sproutboat#154); the embedded transport also sets
69
+ `PRAGMA busy_timeout` so shared-data-dir writers wait instead of failing.
70
+ Also: the client-IP behaviour and backing up D1.
71
+
72
+ ## [0.9.0] - 2026-09-07
73
+ ### Added
74
+ - KV content management: `kv key get`, `put`, `delete`, and `list`; bounded
75
+ `kv bulk get`, `put`, and `delete`; and cursor-paginated `kv export` files
76
+ that can be restored with `kv bulk put`.
77
+ - CLI integration coverage for pagination, bounded batches, text and file
78
+ output, overwrite refusal, and cleanup after failed exports.
79
+ - `sproutboat-env.d.ts` generation from project bindings.
80
+ - Focused runnable examples for every supported binding.
81
+
82
+ ### Fixed
83
+ - Static assets keep arbitrary binary bytes when they travel through the broker.
84
+ - KV exports use a permission-restricted temporary file and move into place only
85
+ after a complete export. Failed exports leave neither partial output nor a
86
+ temporary file behind.
87
+ - First-host builds no longer require Git or Make.
88
+
89
+ ### Changed
90
+ - Development binaries compile with Porffor `-O0` for faster iteration.
91
+ - The installation docs state that the CLI requires Bun and should be invoked
92
+ with `bunx` rather than `npx`.
93
+
94
+ ## [0.8.0] — 2026-09-07
95
+ ### Added
96
+ - `sproutboat build --standalone`: one executable that carries its own bindings.
97
+ SQLite is compiled into the sprout, so a ~2 MB file serves KV, D1, R2, queues,
98
+ Durable Objects, alarms, analytics and assets with nothing beside it on disk —
99
+ no Bun, no broker, no control plane. State lives in `<name>.data/store.sqlite`
100
+ plus `d1/<binding>.sqlite`, the same layout `sproutboat dev` writes, which is
101
+ what lets one conformance suite hold both backends to the same behaviour.
102
+ Secrets come from the environment (then `<data>/secrets.json`) and a missing
103
+ one refuses the boot, listing every name at once, rather than throwing on the
104
+ first request that needs it. Cron ticks, queue batches and DO alarms run on
105
+ in-process timers; assets are baked in, capped at 8 MB.
106
+ - Outbound TLS from a standalone binary. `fetch("https://…")` verifies against
107
+ curl's Mozilla-derived root set via BearSSL, linked in beside SQLite
108
+ (1.86 → 2.02 MB). `SB_CA_BUNDLE` adds a private or corporate CA to that set;
109
+ it only ever adds trust, and nothing disables verification.
110
+ - Durable Object alarms: `setAlarm` / `getAlarm` / `deleteAlarm` and the
111
+ `alarm()` handler. At most one alarm is pending per object and a later
112
+ `setAlarm` replaces it, matching Workers. Delivery claims before running, so
113
+ an `alarm()` that schedules its own next run is not erased by the delivery
114
+ that invoked it.
115
+ - Service bindings: `env.<BINDING>.fetch()` reaches another deployment on the
116
+ same node through the edge on loopback. This is the CLI half; a binding that
117
+ resolves to nothing reports the target as undeployed rather than failing as a
118
+ 502.
119
+ - Binary values in the binding frame. An R2 object body now travels beside the
120
+ JSON rather than encoded inside it.
121
+ - `SB_FETCH_MAX_BYTES` (32 MiB default) caps an outbound response body. An
122
+ unbounded upstream could previously drive a sprout's memory to whatever it
123
+ chose to send.
124
+
125
+ ### Fixed
126
+ - `d1.exec` ran only the first statement of a multi-statement script, so a
127
+ schema built in one `exec()` call silently created only its first table.
128
+ - `r2.get` / `head` / `put` / `list` returned flat fields where the shim reads
129
+ `r.object`, and `ae.query` omitted the `count` its caller reads.
130
+ - Trigger authentication accepted *any* caller when no token was configured.
131
+ The hole predates this release; a standalone binary listening on a public
132
+ interface is what made it reachable.
133
+ - Binary R2 values were corrupted in transit — `0x08` and `0x0c` arrived as
134
+ `b` and `f`.
135
+ - A retried binding call could apply a write twice. Every request now carries an
136
+ id and the broker replays the cached reply for a repeat of a mutating op
137
+ instead of performing it again.
138
+
139
+ ### Changed
140
+ - A dropped broker connection is retried four times with 0/5/25/100 ms backoff
141
+ instead of failing after one attempt, which covers a broker restart mid-call.
142
+ - The bundled (Bun) standalone backend is gone. The embedded one passes the same
143
+ suite at 1.9 MB against 63 MB, and TLS removed its last real advantage.
144
+ - A native-fetch binary cannot see `argv` — Porffor's runtime init calls
145
+ `porf_init(0, NULL)` — so a standalone binary is configured through `PORT`,
146
+ `SB_DATA_DIR` / `SPROUTBOAT_DATA` and the environment only, never flags.
147
+
148
+ ### Performance
149
+ - An 8 MB R2 put through the broker went from 255 MB to 149 MB peak RSS.
150
+ - The broker's frame reader no longer re-concatenates its buffer per chunk.
151
+
152
+ ## [0.7.0] — 2026-09-06
153
+ ### Added
154
+ - `compatibility_date` now reaches the artifact instead of being validated and
155
+ dropped. It is recorded in `manifest.json` as `compatibilityDate` and baked
156
+ into the binary as `__sbCompat`, so a future runtime change can be gated on
157
+ `__sbCompat >= "YYYY-MM-DD"` and old binaries keep the semantics they were
158
+ compiled with. The manifest field is optional and `schemaVersion` stays at 2,
159
+ so artifacts built before this release remain deployable and rollback keeps
160
+ working.
161
+ - A version-skew warning. Control planes advertise `x-sproutboat-control` and
162
+ `x-sproutboat-min-cli` on every `/api/` response; when this CLI is below the
163
+ advertised minimum it says so once per run, instead of leaving the user with
164
+ an unexplained 400. A control plane that predates the handshake sends no
165
+ headers and nothing changes.
166
+ - `CONTRACTS.md`, generated from source in the same style as `SURFACE.md`: the
167
+ broker ops, storage tables, manifest fields and config keys that a release may
168
+ not break, plus golden fixtures for the manifest and the storage format so a
169
+ regression fails a test rather than a user's deployment.
170
+
171
+ ## [0.6.1] — 2026-09-06
172
+ ### Fixed
173
+ - `sproutboat queues` help (and the generated `SURFACE.md`) claimed queue
174
+ consumers "are not implemented yet". They are implemented, and they run on
175
+ the deployed path as well as under `dev`: the supervisor passes
176
+ `--sprout-url` to the broker, which delivers messages in batches, retries a
177
+ failed or explicitly-retried message after 5s, and stops delivering it after
178
+ 5 attempts. The summary now describes what actually happens.
179
+
180
+ ### Changed
181
+ - Tooling only, no change to how the CLI behaves: oxfmt is scoped to the JS
182
+ family with the pre-commit hook's glob matched to it, TypeScript moves
183
+ 5.9 → 7.0, and the GitHub Actions group is bumped.
184
+
185
+ ## [0.6.0] — 2026-09-05
186
+ ### Added
187
+ - `bindings.json` now carries `vars` — the baked plain values a sprout was
188
+ built with — so the control plane can show what a version was compiled
189
+ against. They ride along for display only; the broker never serves them,
190
+ they are compiled into the sprout itself. A project whose only binding
191
+ config is `vars` now gets a sidecar written at all, where before it got
192
+ none.
193
+
194
+ ### Changed
195
+ - Tooling only, no change to how the CLI behaves: the tree is now formatted
196
+ with oxfmt 0.66.0 (the config landed in 0.5.0 but was never run over the
197
+ tree), markdown is excluded from formatting, a lefthook pre-commit hook
198
+ formats staged files and lints, the last 10 oxlint warnings are cleared,
199
+ and CI gates lint alongside typecheck and test.
200
+ - README rewritten shorter: a logo lockup that survives both GitHub themes
201
+ (`docs/logo-light.svg` / `docs/logo-dark.svg` behind a `<picture>`), the
202
+ everyday commands as a five-row table, and the full command inventory left
203
+ to the generated `SURFACE.md` instead of duplicated by hand.
204
+
205
+ ## [0.5.0] — 2026-09-03
206
+ ### Added
207
+ - `dev [--port <n>] [--no-watch]` — run the project on this machine against a
208
+ real broker (KV/D1/secrets/etc. all work), rebuilding on save.
209
+ - `build --target host` — compile for the machine doing the build instead of
210
+ cross-compiling for a box; what `dev` uses, and runnable standalone.
211
+ - Handlers may now `import` — relative modules across the project, and npm
212
+ packages from the project's own `node_modules`. The entry point is bundled
213
+ before it reaches Porffor; the capability checks run against that bundled
214
+ output, so a dependency can't reach `process`/`Bun`/`node:*` any more than
215
+ hand-written code can.
216
+ - `sproutboat init` scaffolds a `.gitignore` (`.sproutboat/`, `.dev.vars`,
217
+ `node_modules/`) alongside the project files, unless one already exists.
218
+
219
+ ### Fixed
220
+ - Async `fetch` handlers hung indefinitely — `__sbEntry` chained the #28
221
+ CPU-time tag onto the handler's own promise, and Porffor's native-fetch
222
+ server only resolves a promise a handler returns directly, never one
223
+ derived from `.then()`.
224
+ - `new Proxy(...)` compiles under Porffor alpha-4 and then silently ignores
225
+ every trap — a trapped property just reads back `undefined`. `check` now
226
+ rejects it before that reaches a deploy as an unexplained 502.
227
+ - `sproutboat init` crashed with a raw `EEXIST` stack trace, and could leave
228
+ a half-scaffolded project, if `src/index.js` already existed but
229
+ `sproutboat.jsonc` didn't. Both targets are checked before either is
230
+ written.
231
+ - Re-running `sproutboat build` (or `dev`'s rebuild-on-save) could fail to
232
+ link: the artifact directory is content-addressed, so an unchanged rebuild
233
+ targeted the previous binary, which was `chmod 0555` and possibly still
234
+ running.
235
+ - The broker's local dev state directory was never created before opening
236
+ its SQLite file, so a first `sproutboat dev` run failed outright.
237
+
238
+ ### Changed
239
+ - Lint: adopted the anti-slop Oxlint plugin and migrated the tree onto it —
240
+ no more bare `unknown`/`Record<string, unknown>` at binding boundaries,
241
+ every non-const type assertion carries a `SAFETY:` comment.
242
+
243
+ ## [0.4.11] — 2026-09-02
244
+ ### Changed
245
+ - README rewritten for current commands and config; points at the docs site.
246
+
247
+ ## [0.4.10] — 2026-09-02
248
+ ### Changed
249
+ - `domains`: prints the A record to add, and any DNS reachability warning.
250
+
251
+ ## [0.4.9] — 2026-09-02
252
+ ### Changed
253
+ - `deploy` (#80): dropped the client-side dedup check in favour of trusting
254
+ the server's own no-op response.
255
+
256
+ ## [0.4.8] — 2026-09-02
257
+ ### Fixed
258
+ - `deploy` (#80): no longer skipped the upload when only assets or bindings
259
+ had changed but the sprout binary hadn't.
260
+
261
+ ## [0.4.7] — 2026-09-02
262
+ ### Added
263
+ - `deploy` auto-provisions an id-less storage binding (wrangler-style):
264
+ creates the account-level resource, writes its id back into
265
+ `sproutboat.jsonc`.
266
+
267
+ ## [0.4.6] — 2026-09-02
268
+ ### Added
269
+ - `sproutboat resource` — manage account-level storage resources directly.
270
+ - `sproutboat.jsonc` storage bindings accept `{ binding, id }`, not just a
271
+ bare name.
272
+ ### Changed
273
+ - Broker keys KV/R2/queue/D1 stores by resource id when one is bound, so the
274
+ data survives a redeploy and can be shared across projects.
275
+
276
+ ## [0.4.5] — 2026-09-02
277
+ ### Changed
278
+ - `deploy` dropped the "✓ serving" line — silence now means the health check
279
+ passed.
280
+
281
+ ## [0.4.4] — 2026-09-02
282
+ ### Added
283
+ - `--version`, a once-a-day update-available notice, a richer deploy echo.
284
+ ### Changed
285
+ - Misuse now exits `2` (getopt convention) instead of `1`.
286
+
287
+ ## [0.4.3] — 2026-09-02
288
+ ### Changed
289
+ - `--help` output: grouped, emoji-labelled, aligned — was one wall-of-text
290
+ usage line.
291
+
292
+ ## [0.4.2] — 2026-09-02
293
+ ### Added
294
+ - `tail --sprout` streams the running sprout's and broker's stdout/stderr.
295
+ ### Changed
296
+ - `deploy` waits for the health check and prints every binding in the
297
+ report; `delete` takes flexible args plus `?confirm`; the banner reads the
298
+ real installed version.
299
+
300
+ ## [0.4.0] — 2026-09-01
301
+ ### Added
302
+ - `sproutboat domains` and `sproutboat secrets` commands.
303
+ - `deploy` uploads the `bindings.json` / `assets.json` sidecars alongside
304
+ the sprout binary.
305
+ - The worker self-reports per-invocation CPU time (`x-sb-cpu-ms`).
306
+ - Deploy surfaces a Porffor pin drift warning when the live version was
307
+ built against a different compiler pin than the one about to deploy.
308
+ ### Changed
309
+ - Renamed "worker" to "sprout" throughout the CLI, broker, and examples.
310
+
311
+ ## [0.3.0] — 2026-09-01
312
+ ### Added
313
+ - Published to npm via Trusted Publishing (OIDC) — no token secret in CI.
314
+ ### Changed
315
+ - `src/wrap.ts` extracted with `runtime/*` subpath exports, so the monorepo
316
+ can consume the binding/manifest contracts as a dependency instead of a
317
+ hand-vendored copy.
318
+ - CSPRNG-backed `crypto.getRandomValues`; the deploy binary is stripped.
319
+ - One long-lived broker connection per worker instead of reconnecting on
320
+ every binding call; `env.<SECRET>` memoized; `assets.get` made synchronous
321
+ with the broker service; WAL + `synchronous=NORMAL` and parameterised
322
+ `LIMIT` back-ported from the monorepo's broker.
323
+
324
+ ## [0.2.1] — 2026-08-31
325
+ ### Changed
326
+ - Ships a prebuilt uWebSockets archive, so the first build needs neither
327
+ `git` nor `make` on `PATH`.
328
+
329
+ ## [0.2.0] — 2026-08-31
330
+ Initial release, extracted from the `sproutboat` monorepo (`apps/cli`) as
331
+ its own package.
332
+ ### Added
333
+ - Cross-compiles a handler to a static `linux-x86_64` binary with Porffor +
334
+ Zig — no Docker.
335
+ - Static assets binding, with the `examples/kitchen-sink` Astro app as a
336
+ worked example.
337
+ - `SURFACE.md`, generated and drift-checked against the actual command/env
338
+ surface.
339
+ ### Changed
340
+ - Patches Porffor at build time rather than via a `postinstall` hook.
341
+ - Renamed the package to `sproutboat` (was `@sproutboat/cli`); dropped the
342
+ `sprout` bin alias in favour of a user-defined shell alias.
343
+
344
+ [Unreleased]: https://github.com/baronunread/sproutboat-cli/compare/v0.10.0...HEAD
345
+ [0.10.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.9.0...v0.10.0
346
+ [0.9.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.8.0...v0.9.0
347
+ [0.8.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.7.0...v0.8.0
348
+ [0.7.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.6.1...v0.7.0
349
+ [0.6.1]: https://github.com/baronunread/sproutboat-cli/compare/v0.6.0...v0.6.1
350
+ [0.6.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.5.0...v0.6.0
351
+ [0.5.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.11...v0.5.0
352
+ [0.4.11]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.10...v0.4.11
353
+ [0.4.10]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.9...v0.4.10
354
+ [0.4.9]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.8...v0.4.9
355
+ [0.4.8]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.7...v0.4.8
356
+ [0.4.7]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.6...v0.4.7
357
+ [0.4.6]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.5...v0.4.6
358
+ [0.4.5]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.4...v0.4.5
359
+ [0.4.4]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.3...v0.4.4
360
+ [0.4.3]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.2...v0.4.3
361
+ [0.4.2]: https://github.com/baronunread/sproutboat-cli/compare/v0.4.0...v0.4.2
362
+ [0.4.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.3.0...v0.4.0
363
+ [0.3.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.2.1...v0.3.0
364
+ [0.2.1]: https://github.com/baronunread/sproutboat-cli/compare/v0.2.0...v0.2.1
365
+ [0.2.0]: https://github.com/baronunread/sproutboat-cli/releases/tag/v0.2.0
package/README.md CHANGED
@@ -8,6 +8,11 @@ https://sproutboat.com
8
8
  The CLI for [Sproutboat](https://github.com/baronunread/sproutboat). Compiles a
9
9
  `fetch` handler to a native binary and ships it to any Sproutboat control plane.
10
10
 
11
+ ## Issues
12
+
13
+ Tracked centrally in [baronunread/sproutboat](https://github.com/baronunread/sproutboat/issues)
14
+ (label `area:cli`). Please file there.
15
+
11
16
  ## Overview
12
17
 
13
18
  Wrangler-shaped, MIT licensed. `build` and `deploy` cross-compile your handler
@@ -19,10 +24,23 @@ for agents: [sproutboat.com/llms.txt](https://sproutboat.com/llms.txt)).
19
24
 
20
25
  ## Using
21
26
 
27
+ The npm package installs a small Node launcher and the matching native
28
+ platform package, so `npm install -g sproutboat` and `npm exec sproutboat` work
29
+ without Bun on `darwin` and `linux` for `arm64` and `x64`. The self-hosted
30
+ runtime exports remain in the root package for Bun-based platform integrations.
31
+ Nothing is needed on the machine that *runs* a sprout: that gets a static
32
+ binary.
33
+
34
+ The first build downloads the pinned Porffor source into
35
+ `~/.cache/sproutboat`, verifies its SHA-256, applies Sproutboat's compiler
36
+ patches there, and publishes the cache entry atomically. Installation itself
37
+ does not clone Porffor or invoke Git, Make, or a compiler. Warm-cache builds can
38
+ run offline.
39
+
22
40
  ```sh
23
- bunx sproutboat init hello
41
+ npm exec sproutboat init hello
24
42
  cd hello
25
- bunx sproutboat dev # runs it right here, no control plane needed
43
+ npm exec sproutboat dev # runs it right here, no control plane needed
26
44
  ```
27
45
 
28
46
  Happy with it? Ship it:
@@ -32,10 +50,10 @@ bunx sproutboat login --api-url https://control.example.com # one browser appr
32
50
  bunx sproutboat deploy
33
51
  ```
34
52
 
35
- Or install it once and drop the `bunx`:
53
+ Or install it once and drop the `npm exec`:
36
54
 
37
55
  ```sh
38
- bun add -g sproutboat # then: sproutboat deploy, sproutboat tail, ...
56
+ npm install -g sproutboat # then: sproutboat deploy, sproutboat tail, ...
39
57
  ```
40
58
 
41
59
  `login` is one-time. It writes a long-lived token to
@@ -63,6 +81,32 @@ Run `sproutboat` with no arguments for the grouped list.
63
81
  [`SURFACE.md`](SURFACE.md) is the generated inventory: every command, every
64
82
  argument, every env var, kept honest by a drift test.
65
83
 
84
+ ### KV data and waitlist export
85
+
86
+ Manage one namespace by its account-level name:
87
+
88
+ ```sh
89
+ sproutboat kv key list registrations --prefix email:
90
+ sproutboat kv key get registrations email:person@example.com --text
91
+ sproutboat kv key put registrations email:person@example.com joined
92
+ sproutboat kv key delete registrations email:person@example.com --yes
93
+ sproutboat kv export registrations --prefix email: --output registrations.json
94
+ ```
95
+
96
+ Bulk put, and therefore exported dumps, use the version 1 text-value format:
97
+
98
+ ```json
99
+ [
100
+ { "key": "email:person@example.com", "value": "joined" }
101
+ ]
102
+ ```
103
+
104
+ `kv bulk get` and `kv bulk delete` accept a JSON array of key strings. `kv bulk
105
+ put` accepts the entry array above. The CLI sends large inputs in bounded
106
+ batches. Export pages through the namespace and writes incrementally to a
107
+ permission-restricted temporary file, then renames it into place only after a
108
+ complete export. Existing output files require `--force`.
109
+
66
110
  ## Config
67
111
 
68
112
  `sproutboat.jsonc`: the entry point plus Cloudflare-shaped `env.*` bindings.
@@ -81,6 +125,7 @@ argument, every env var, kept honest by a drift test.
81
125
  "queues": ["JOBS"],
82
126
  "analytics_engine_datasets": ["METRICS"], // bare name only, no id
83
127
  "durable_objects": { "COUNTER": "Counter" },
128
+ "ratelimiters": [{ "binding": "API", "limit": 100, "period": 60 }], // env.API.limit({ key })
84
129
  "outbound": ["api.example.com"],
85
130
  "triggers": { "crons": ["*/5 * * * *"] },
86
131
  "assets": { "directory": "public", "binding": "ASSETS", "run_sprout_first": ["/api/*"] }
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.8.0 · runs on Bun (use `bunx`, not `npx`)
6
+ **Package:** `sproutboat` 0.9.0 · runs on Bun (use `bunx`, not `npx`)
7
7
 
8
8
  ## Commands
9
9
 
@@ -11,13 +11,14 @@
11
11
  | --- | --- | --- |
12
12
  | `init` | `[name]` | Scaffold sproutboat.jsonc + src/index.js in ./<name>. |
13
13
  | `check` | `[project-dir]` | Validate the config and entry point without building. |
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. |
14
15
  | `dev` | `[project-dir] [--port <n>] [--no-watch]` | Run the project on this machine against a real broker, rebuilding on save. |
15
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. |
16
17
  | `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. |
17
18
  | `versions` | `<list | view <version-id>> [project-dir]` | List the project's deployed versions, or show one version's artifact and bindings. |
18
19
  | `rollback` | `<version-id> [project-dir]` | Re-activate a previous version. |
19
20
  | `tail` | `[project-dir] [--sprout]` | Print recent request logs; --sprout prints the running sprout + broker stdout/stderr instead. |
20
- | `kv` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>>` | KV namespaces. `create` prints the id to bind from sproutboat.jsonc. |
21
+ | `kv` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>> | <key | bulk | export> ...` | KV namespaces. `create` prints the id to bind from sproutboat.jsonc; key and bulk operations manage contents, and export writes a restorable JSON dump. |
21
22
  | `d1` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>>` | D1 databases. `create` prints the id to bind from sproutboat.jsonc. |
22
23
  | `r2` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>>` | R2 buckets. `create` prints the id to bind from sproutboat.jsonc. |
23
24
  | `queues` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>>` | Queues. `create` prints the id to bind from sproutboat.jsonc; consumers deliver in batches with retries, and stop after 5 attempts. |
@@ -29,7 +30,7 @@
29
30
  | `whoami` | — | Show the active endpoint and the account the stored token belongs to. |
30
31
 
31
32
  ```
32
- usage: sproutboat <init [name] | check [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>> | 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>
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>
33
34
  ```
34
35
 
35
36
  ## Environment variables
@@ -39,8 +40,14 @@ usage: sproutboat <init [name] | check [project-dir] | dev [project-dir] [--port
39
40
  | `SPROUTBOAT_API_URL` | Control-plane URL. Overrides the saved active endpoint. |
40
41
  | `SPROUTBOAT_TOKEN` | API token. Overrides the saved credential for the endpoint. |
41
42
  | `SPROUTBOAT_ZIG` | Path to a Zig binary to use instead of downloading the pinned one. |
42
- | `SPROUTBOAT_UWS_TARBALL` | Path to a prebuilt uWebSockets (x86_64-linux-musl) tarball to seed the Porffor cache with, instead of downloading it (removes the first-build git + make need). |
43
+ | `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. |
44
+ | `CC` | C compiler used to build uSockets for a host build (default `cc`). A host build needs one regardless: it is what Porffor compiles its own generated C with. |
45
+ | `AR` | Archiver used to assemble uSockets.a for a host build (default `ar`). |
43
46
  | `SPROUTBOAT_COMPILE_TIMEOUT_MS` | Porffor compile timeout in ms (default 600000). |
47
+ | `SPROUTBOAT_TOOLCHAIN_CACHE` | Managed Porffor and compiler cache root (default ~/.cache/sproutboat). |
48
+ | `SPROUTBOAT_PORFFOR_DIR` | Contributor override for an existing Porffor source checkout. Required files are validated before use. |
49
+ | `SPROUTBOAT_BUILD_UWS_FROM_SOURCE` | Set to 1 to explicitly allow the contributor-only Git and Make uWebSockets fallback. |
50
+ | `SPROUTBOAT_CLI_VERSION` | CLI version embedded by the release executable build. Not normally set by users. |
44
51
  | `SPROUTBOAT_VARS_JSON` | JSON object of baked `vars` (UPPER_SNAKE -> string), read by the wrapper when generating the sprout module. |
45
52
  | `SPROUTBOAT_BINDINGS_JSON` | The artifact's bindings.json, read by the wrapper to emit the `__sbInstallBindings` line. |
46
53
  | `SPROUTBOAT_CONFIG_DIR` | Directory for credentials.json (default ~/.config/sproutboat). |
@@ -64,7 +71,7 @@ usage: sproutboat <init [name] | check [project-dir] | dev [project-dir] [--port
64
71
  | | |
65
72
  | --- | --- |
66
73
  | Zig | `0.16.0` (`zig cc -target x86_64-linux-musl`, static) |
67
- | Provenance stamp | `zig-musl/0.16.0+porffor/a415d19+uws/360c276d` |
74
+ | Provenance stamp | `zig-musl/0.16.0+porffor/1f4ae4a+uws/360c276d` |
68
75
  | Artifact schema | `2` |
69
76
  | Runtime | `native-fetch` |
70
77
  | Capability profile | `http-sync-v0` |
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ // npm only selects the packaged executable. The CLI itself always runs in Bun.
3
+ const { spawn } = require("node:child_process");
4
+ const { resolve } = require("node:path");
5
+
6
+ const platform = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : null;
7
+ const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : null;
8
+ if (!platform || !arch) {
9
+ console.error(`sproutboat: unsupported platform ${process.platform}/${process.arch}`);
10
+ process.exit(1);
11
+ }
12
+ const packageName = `@sproutboat/cli-${platform}-${arch}`;
13
+ let executable;
14
+ try {
15
+ executable = require.resolve(`${packageName}/bin/sproutboat`);
16
+ } catch {
17
+ console.error(`sproutboat: optional package ${packageName} is missing for ${platform}/${arch}`);
18
+ console.error("Reinstall sproutboat without --omit=optional, or use a direct release download.");
19
+ process.exit(1);
20
+ }
21
+ const child = spawn(resolve(executable), process.argv.slice(2), { stdio: "inherit" });
22
+ child.once("error", (error) => {
23
+ console.error(`sproutboat: could not start bundled executable: ${error.message}`);
24
+ process.exit(1);
25
+ });
26
+ for (const signal of ["SIGINT", "SIGTERM"]) {
27
+ process.on(signal, () => child.kill(signal));
28
+ }
29
+ child.once("exit", (code, signal) => {
30
+ if (signal) process.kill(process.pid, signal);
31
+ else process.exit(code ?? 1);
32
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutboat",
3
- "version": "0.8.0",
3
+ "version": "0.10.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",
@@ -19,15 +19,19 @@
19
19
  "url": "git+https://github.com/baronunread/sproutboat-cli.git"
20
20
  },
21
21
  "bin": {
22
- "sproutboat": "src/main.ts"
22
+ "sproutboat": "bin/sproutboat.cjs"
23
23
  },
24
24
  "files": [
25
+ "CHANGELOG.md",
26
+ "SURFACE.md",
25
27
  "src",
26
- "!src/*.test.ts",
27
- "vendor",
28
- "SURFACE.md"
28
+ "bin/sproutboat.cjs",
29
+ "scripts",
30
+ "types",
31
+ "vendor"
29
32
  ],
30
33
  "type": "module",
34
+ "types": "./types/sproutboat.d.ts",
31
35
  "exports": {
32
36
  "./runtime/config": "./src/config.ts",
33
37
  "./runtime/source": "./src/source.ts",
@@ -35,13 +39,15 @@
35
39
  "./runtime/assets": "./src/assets.ts",
36
40
  "./runtime/broker": "./src/broker.ts",
37
41
  "./runtime/wrap": "./src/wrap.ts",
38
- "./runtime/prelude": "./src/native-fetch-prelude.js",
39
- "./package.json": "./package.json"
42
+ "./package.json": "./package.json",
43
+ "./types": "./types/sproutboat.d.ts"
40
44
  },
41
45
  "scripts": {
42
46
  "prepare": "lefthook install || true",
43
47
  "typecheck": "tsc --noEmit",
44
48
  "lint": "oxlint .",
49
+ "examples": "bun examples/smoke.ts",
50
+ "build:cli": "bun scripts/build-cli.ts",
45
51
  "fmt": "oxfmt .",
46
52
  "fmt:check": "oxfmt --check .",
47
53
  "test": "bun test",
@@ -54,8 +60,13 @@
54
60
  "kitchen-sink:standalone": "bun examples/kitchen-sink/harness-standalone.ts"
55
61
  },
56
62
  "dependencies": {
57
- "esbuild": "^0.28.2",
58
- "porffor": "github:CanadaHonk/porffor#alpha-4"
63
+ "@sproutboat/artifact": "^0.2.0",
64
+ "@sproutboat/assets": "^0.2.0",
65
+ "@sproutboat/config": "^0.3.0",
66
+ "@sproutboat/runtime": "^0.4.0",
67
+ "@sproutboat/toolchain": "^0.3.0",
68
+ "@sproutboat/wire": "^0.4.0",
69
+ "esbuild": "^0.28.2"
59
70
  },
60
71
  "devDependencies": {
61
72
  "@oxlint/plugins": "1.81.0",
@@ -65,7 +76,13 @@
65
76
  "oxlint": "1.81.0",
66
77
  "typescript": "7.0.2"
67
78
  },
79
+ "optionalDependencies": {
80
+ "@sproutboat/cli-darwin-arm64": "0.10.0",
81
+ "@sproutboat/cli-darwin-x64": "0.10.0",
82
+ "@sproutboat/cli-linux-arm64": "0.10.0",
83
+ "@sproutboat/cli-linux-x64": "0.10.0"
84
+ },
68
85
  "engines": {
69
- "bun": ">=1.4.0"
86
+ "node": ">=18"
70
87
  }
71
88
  }