sproutboat 0.4.11 → 0.6.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/README.md CHANGED
@@ -1,16 +1,34 @@
1
- # sproutboat
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
3
+ <img src="docs/logo-light.svg" alt="sproutboat" width="220" height="32">
4
+ </picture>
2
5
 
3
- The CLI for [Sproutboat](https://github.com/baronunread/sproutboat). It is a
4
- Wrangler-shaped tool that compiles a `fetch` handler to a native binary and
5
- ships it to any Sproutboat control plane. MIT licensed.
6
+ https://sproutboat.com
6
7
 
7
- Full reference: [sproutboat.com/docs](https://sproutboat.com/docs)
8
- (plain text for agents: [sproutboat.com/llms.txt](https://sproutboat.com/llms.txt)).
8
+ The CLI for [Sproutboat](https://github.com/baronunread/sproutboat). Compiles a
9
+ `fetch` handler to a native binary and ships it to any Sproutboat control plane.
10
+
11
+ ## Overview
12
+
13
+ Wrangler-shaped, MIT licensed. `build` and `deploy` cross-compile your handler
14
+ to a static `linux-x86_64` binary with [Porffor](https://porffor.dev) and Zig —
15
+ no Docker, no root, nothing to run as a daemon.
16
+
17
+ Full reference: [sproutboat.com/docs](https://sproutboat.com/docs) (plain text
18
+ for agents: [sproutboat.com/llms.txt](https://sproutboat.com/llms.txt)).
19
+
20
+ ## Using
9
21
 
10
22
  ```sh
11
- bunx sproutboat login --api-url https://control.example.com # one browser approval
12
23
  bunx sproutboat init hello
13
24
  cd hello
25
+ bunx sproutboat dev # runs it right here, no control plane needed
26
+ ```
27
+
28
+ Happy with it? Ship it:
29
+
30
+ ```sh
31
+ bunx sproutboat login --api-url https://control.example.com # one browser approval
14
32
  bunx sproutboat deploy
15
33
  ```
16
34
 
@@ -20,33 +38,30 @@ Or install it once and drop the `bunx`:
20
38
  bun add -g sproutboat # then: sproutboat deploy, sproutboat tail, ...
21
39
  ```
22
40
 
23
- Want it shorter? Alias it yourself: `alias sprout='sproutboat'`.
24
-
25
- The `login` browser step is one-time. It writes a long-lived token to
41
+ `login` is one-time. It writes a long-lived token to
26
42
  `~/.config/sproutboat/credentials.json`, keyed by API URL, so you can hold
27
- logins for several instances at once. For CI or an agent, skip `login`
28
- entirely and set `SPROUTBOAT_API_URL` + `SPROUTBOAT_TOKEN` (copy the token out
29
- of that file).
43
+ logins for several instances at once. For CI or an agent, skip it and set
44
+ `SPROUTBOAT_API_URL` + `SPROUTBOAT_TOKEN`.
30
45
 
31
46
  ## Commands
32
47
 
48
+ The everyday loop:
49
+
33
50
  | Command | What it does |
34
51
  | --- | --- |
35
52
  | `init [name]` | Scaffold `sproutboat.jsonc` + `src/index.js` |
36
- | `check` | Validate the config and entry point |
37
- | `build` | Cross-compile the sprout binary (Porffor + Zig) |
38
- | `deploy [--dry-run] [--no-wait] [--no-provision] [--artifact <dir>]` | Build, auto-provision id-less storage bindings, upload, wait until the URL serves |
39
- | `login [--api-url <url>] [--token <token>]` | Browser device flow, or store a token directly |
40
- | `tail [name] [--sprout]` | Recent request logs; `--sprout` streams the running sprout + broker output |
41
- | `versions list [name]` | Deployed versions |
42
- | `rollback <id>` | Activate a previous version |
43
- | `secrets [list \| set <NAME> [value] \| rm <NAME>]` | Encrypted project secrets, read as `env.NAME` |
44
- | `resource [list \| create <kind> <name> \| rename <id> <name> \| delete <id>]` | Account-level KV / D1 / R2 / queue stores |
45
- | `domains [list \| add <host> \| verify <host> \| rm <host>]` | Attach your own hostname (TXT + A, apex allowed) |
46
- | `delete --yes` | Delete the project, every version, and its route |
53
+ | `dev [--port <n>]` | Run it here against a real broker, rebuilding on save |
54
+ | `deploy` | Build, provision bindings, upload, wait until the URL serves |
55
+ | `tail [--sprout]` | Recent request logs, or the running sprout's output |
56
+ | `rollback <id>` | Re-activate a previous version |
57
+
58
+ Then `check`, `build`, `versions`, `delete`, `login` / `logout` / `whoami`, and
59
+ one command per storage product `kv`, `d1`, `r2`, `queues`, `secrets`,
60
+ `domains` each with the same five verbs.
47
61
 
48
62
  Run `sproutboat` with no arguments for the grouped list.
49
- [`SURFACE.md`](SURFACE.md) is the generated inventory of every command and env var.
63
+ [`SURFACE.md`](SURFACE.md) is the generated inventory: every command, every
64
+ argument, every env var, kept honest by a drift test.
50
65
 
51
66
  ## Config
52
67
 
@@ -73,43 +88,47 @@ Run `sproutboat` with no arguments for the grouped list.
73
88
  ```
74
89
 
75
90
  A bare `"CACHE"` binding is auto-provisioned on `deploy`: the CLI creates an
76
- account-level resource, writes its id back into `sproutboat.jsonc`, and the
77
- store then survives redeploys. Pass `--no-provision` to keep it a throwaway
78
- per-deploy store instead, or `sproutboat resource create` to make one up front
79
- and share its id across projects.
80
-
81
- The handler is one `export default { fetch(request) }`, optionally with
82
- `scheduled(event)` / `queue(batch)` handlers and Durable Object classes above
83
- it. `env` is a global (not a parameter), and every binding call is synchronous.
84
- See [`examples/kitchen-sink/`](examples/kitchen-sink) for one app that uses
85
- every binding.
91
+ account-level resource and writes its id back into `sproutboat.jsonc`, so the
92
+ store survives redeploys. `--no-provision` keeps it a throwaway per-deploy
93
+ store instead.
94
+
95
+ The handler is `export default { fetch(request) }`. It may import from other
96
+ files in the project and from its own `node_modules`, and may also export
97
+ `scheduled(event)` / `queue(batch)` handlers and Durable Object classes. `env`
98
+ is a global, not a parameter, and every binding call is synchronous.
99
+ [`examples/kitchen-sink/`](examples/kitchen-sink) uses every binding.
100
+
101
+ `sproutboat dev` runs against a real local broker, so bindings behave like
102
+ production without a deploy — except `secrets`, which live only in the control
103
+ plane. Put values in a `.dev.vars` file next to `sproutboat.jsonc`
104
+ (`API_KEY=whatever`, one per line, gitignored) and `env.API_KEY` resolves under
105
+ `dev` only.
86
106
 
87
107
  ## Requirements
88
108
 
89
- - [Bun](https://bun.sh) 1.4+
90
-
91
- `build` / `deploy` cross-compile the handler to a static `linux-x86_64` binary
92
- with Porffor and Zig (the CLI fetches Zig automatically on first use). The
93
- package ships a prebuilt uWebSockets and compiles nothing else from source. No
94
- Docker, no root. On Windows, build from WSL.
95
-
96
- If that prebuilt is unusable (a `porffor` pin bump before the archive is
97
- refreshed), the first build falls back to compiling uWebSockets locally, which
98
- needs `git` and `make` on `PATH`. `SPROUTBOAT_UWS_TARBALL=<archive>` overrides
99
- the shipped one.
109
+ [Bun](https://bun.sh) 1.4+. The CLI fetches Zig automatically on first use and
110
+ ships a prebuilt uWebSockets, so it compiles nothing else from source. On
111
+ Windows, build from WSL.
100
112
 
101
113
  ## Limits
102
114
 
103
115
  Capability profile `http-sync-v0`: one synchronous `fetch` handler, optional
104
116
  `scheduled` / `queue` handlers, no streaming, no WebSockets. Binding values are
105
- text/JSON and travel one at a time over the loopback frame (32 MiB cap);
106
- large-object R2 is [#56](https://github.com/baronunread/sproutboat/issues/56).
107
- The sprout upload caps at 16 MiB, assets at 64 MiB / 4096 files. Node
108
- compatibility is Porffor alpha, so `import`/`require`, `process`, `node:*`, and
109
- parsing date strings do not work; `sproutboat check` catches most of it. Full
110
- list at [sproutboat.com/docs](https://sproutboat.com/docs).
117
+ text/JSON, one at a time over a 32 MiB loopback frame. Sprout uploads cap at
118
+ 16 MiB, assets at 64 MiB / 4096 files.
119
+
120
+ Porffor is pre-1.0, so some JavaScript does not survive the compile — CommonJS
121
+ `require`, dynamic `import()`, anything reaching `process`/`Bun`/`Deno`/`node:*`,
122
+ and `new Proxy(...)` (it compiles, then silently ignores every trap).
123
+ `sproutboat check` catches most of it before a build. The full list, and the
124
+ current sharp edges, are at [sproutboat.com/docs](https://sproutboat.com/docs).
125
+
126
+ ## Bugs
127
+
128
+ File issues at
129
+ [baronunread/sproutboat/issues](https://github.com/baronunread/sproutboat/issues).
111
130
 
112
- ---
131
+ ## Contributing
113
132
 
114
- Contributors: [`MIGRATION.md`](MIGRATION.md) maps this repo back to the monorepo.
115
- [`SURFACE.md`](SURFACE.md) is a generated inventory of every command and env var.
133
+ PRs welcome. [`MIGRATION.md`](MIGRATION.md) maps this repo back to the
134
+ monorepo it was extracted from.
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.4.11 · runs on Bun (use `bunx`, not `npx`)
6
+ **Package:** `sproutboat` 0.6.0 · runs on Bun (use `bunx`, not `npx`)
7
7
 
8
8
  ## Commands
9
9
 
@@ -11,19 +11,25 @@
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
- | `build` | `[project-dir]` | Cross-compile the native-fetch sprout (Porffor + Zig). |
14
+ | `dev` | `[project-dir] [--port <n>] [--no-watch]` | Run the project on this machine against a real broker, rebuilding on save. |
15
+ | `build` | `[project-dir] [--target host]` | Cross-compile the native-fetch sprout (Porffor + Zig). `--target host` builds for this machine instead, to run locally — not deployable. |
15
16
  | `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. |
16
- | `versions` | `list [project-dir]` | List the project's deployed versions. |
17
+ | `versions` | `<list | view <version-id>> [project-dir]` | List the project's deployed versions, or show one version's artifact and bindings. |
17
18
  | `rollback` | `<version-id> [project-dir]` | Re-activate a previous version. |
18
19
  | `tail` | `[project-dir] [--sprout]` | Print recent request logs; --sprout prints the running sprout + broker stdout/stderr instead. |
19
- | `domains` | `[list | add <host> | verify <host> | rm <host>] [project-dir]` | Attach a custom domain to the project (TXT-verified). No sub-command lists. |
20
- | `secrets` | `[list | set <NAME> [value] | rm <NAME>] [project-dir]` | Manage encrypted project secrets (read as env.NAME). `set` takes the value from the arg or stdin; applies on next deploy. |
21
- | `resource` | `[list [kind] | create <kind> <name> | rename <id> <name> | delete <id>]` | Manage account-level storage resources (kv | d1 | r2 | queue). `create` prints the id to reference from sproutboat.jsonc bindings. |
20
+ | `kv` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>>` | KV namespaces. `create` prints the id to bind from sproutboat.jsonc. |
21
+ | `d1` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>>` | D1 databases. `create` prints the id to bind from sproutboat.jsonc. |
22
+ | `r2` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>>` | R2 buckets. `create` prints the id to bind from sproutboat.jsonc. |
23
+ | `queues` | `<list | create <name> | info <name> | rename <name> <new> | delete <name>>` | Queues. `create` prints the id to bind from sproutboat.jsonc; consumers are not implemented yet. |
24
+ | `domains` | `<list | add <host> | verify <host> | delete <host>> [project-dir]` | Attach a custom domain to the project (TXT-verified). No sub-command lists. |
25
+ | `secrets` | `<list | put <NAME> [--value <value>] | delete <NAME>> [project-dir]` | Manage encrypted project secrets (read as env.NAME). `put` reads the value from stdin unless --value is given, so it stays out of shell history; applies on next deploy. |
22
26
  | `delete` | `[project-dir] [--name <project>] --yes` | Delete the project, every version, and its route. |
23
27
  | `login` | `[--api-url <url>] [--token <token>]` | Device-code browser flow, or store <token> for <url> directly. |
28
+ | `logout` | `[--api-url <url>]` | Forget the stored credential for the active endpoint, or for <url>. |
29
+ | `whoami` | — | Show the active endpoint and the account the stored token belongs to. |
24
30
 
25
31
  ```
26
- usage: sproutboat <init [name] | check [project-dir] | build [project-dir] | deploy [project-dir] [--dry-run] [--artifact <dir>] [--no-wait] [--no-provision] | versions list [project-dir] | rollback <version-id> [project-dir] | tail [project-dir] [--sprout] | domains [list | add <host> | verify <host> | rm <host>] [project-dir] | secrets [list | set <NAME> [value] | rm <NAME>] [project-dir] | resource [list [kind] | create <kind> <name> | rename <id> <name> | delete <id>] | delete [project-dir] [--name <project>] --yes | login [--api-url <url>] [--token <token>]>
32
+ usage: sproutboat <init [name] | check [project-dir] | dev [project-dir] [--port <n>] [--no-watch] | build [project-dir] [--target host] | 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>
27
33
  ```
28
34
 
29
35
  ## Environment variables
package/package.json CHANGED
@@ -1,27 +1,33 @@
1
1
  {
2
2
  "name": "sproutboat",
3
- "version": "0.4.11",
3
+ "version": "0.6.0",
4
4
  "description": "Wrangler-shaped CLI for Sproutboat. Deploys workers to any control plane via --api-url / SPROUTBOAT_API_URL.",
5
- "license": "MIT",
6
- "type": "module",
7
- "homepage": "https://github.com/baronunread/sproutboat-cli",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/baronunread/sproutboat-cli.git"
11
- },
12
- "bugs": "https://github.com/baronunread/sproutboat-cli/issues",
13
5
  "keywords": [
14
- "sproutboat",
15
6
  "cli",
16
7
  "deploy",
8
+ "edge",
17
9
  "porffor",
18
10
  "serverless",
19
- "edge",
11
+ "sproutboat",
20
12
  "wrangler"
21
13
  ],
14
+ "homepage": "https://github.com/baronunread/sproutboat-cli",
15
+ "bugs": "https://github.com/baronunread/sproutboat-cli/issues",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/baronunread/sproutboat-cli.git"
20
+ },
22
21
  "bin": {
23
22
  "sproutboat": "src/main.ts"
24
23
  },
24
+ "files": [
25
+ "src",
26
+ "!src/*.test.ts",
27
+ "vendor",
28
+ "SURFACE.md"
29
+ ],
30
+ "type": "module",
25
31
  "exports": {
26
32
  "./runtime/config": "./src/config.ts",
27
33
  "./runtime/source": "./src/source.ts",
@@ -32,17 +38,12 @@
32
38
  "./runtime/prelude": "./src/native-fetch-prelude.js",
33
39
  "./package.json": "./package.json"
34
40
  },
35
- "files": [
36
- "src",
37
- "!src/*.test.ts",
38
- "vendor",
39
- "SURFACE.md"
40
- ],
41
- "engines": {
42
- "bun": ">=1.4.0"
43
- },
44
41
  "scripts": {
42
+ "prepare": "lefthook install || true",
45
43
  "typecheck": "tsc --noEmit",
44
+ "lint": "oxlint .",
45
+ "fmt": "oxfmt .",
46
+ "fmt:check": "oxfmt --check .",
46
47
  "test": "bun test",
47
48
  "example:kitchen-sink": "bun examples/kitchen-sink/harness.ts",
48
49
  "surface": "UPDATE_SURFACE=1 bun test src/surface.test.ts",
@@ -55,7 +56,14 @@
55
56
  "porffor": "github:CanadaHonk/porffor#alpha-4"
56
57
  },
57
58
  "devDependencies": {
59
+ "@oxlint/plugins": "1.81.0",
58
60
  "@types/bun": "1.4.0",
61
+ "lefthook": "2.1.12",
62
+ "oxfmt": "0.66.0",
63
+ "oxlint": "1.81.0",
59
64
  "typescript": "5.9.2"
65
+ },
66
+ "engines": {
67
+ "bun": ">=1.4.0"
60
68
  }
61
69
  }
package/src/assets.ts CHANGED
@@ -21,13 +21,26 @@ export type AssetManifest = {
21
21
  };
22
22
 
23
23
  const TYPES = new Map<string, string>([
24
- ["html", "text/html; charset=utf-8"], ["css", "text/css; charset=utf-8"],
25
- ["js", "text/javascript; charset=utf-8"], ["mjs", "text/javascript; charset=utf-8"],
26
- ["json", "application/json; charset=utf-8"], ["map", "application/json; charset=utf-8"],
27
- ["txt", "text/plain; charset=utf-8"], ["xml", "application/xml; charset=utf-8"],
28
- ["svg", "image/svg+xml"], ["png", "image/png"], ["jpg", "image/jpeg"], ["jpeg", "image/jpeg"],
29
- ["gif", "image/gif"], ["webp", "image/webp"], ["avif", "image/avif"], ["ico", "image/x-icon"],
30
- ["woff2", "font/woff2"], ["woff", "font/woff"], ["ttf", "font/ttf"], ["wasm", "application/wasm"],
24
+ ["html", "text/html; charset=utf-8"],
25
+ ["css", "text/css; charset=utf-8"],
26
+ ["js", "text/javascript; charset=utf-8"],
27
+ ["mjs", "text/javascript; charset=utf-8"],
28
+ ["json", "application/json; charset=utf-8"],
29
+ ["map", "application/json; charset=utf-8"],
30
+ ["txt", "text/plain; charset=utf-8"],
31
+ ["xml", "application/xml; charset=utf-8"],
32
+ ["svg", "image/svg+xml"],
33
+ ["png", "image/png"],
34
+ ["jpg", "image/jpeg"],
35
+ ["jpeg", "image/jpeg"],
36
+ ["gif", "image/gif"],
37
+ ["webp", "image/webp"],
38
+ ["avif", "image/avif"],
39
+ ["ico", "image/x-icon"],
40
+ ["woff2", "font/woff2"],
41
+ ["woff", "font/woff"],
42
+ ["ttf", "font/ttf"],
43
+ ["wasm", "application/wasm"],
31
44
  ["webmanifest", "application/manifest+json"],
32
45
  ]);
33
46
 
@@ -68,10 +81,17 @@ export function walkAssets(dir: string) {
68
81
  if (ent.name.startsWith(".")) continue;
69
82
  const childAbs = join(abs, ent.name);
70
83
  const childRel = posix.join(rel, ent.name);
71
- if (ent.isDirectory()) { recurse(childAbs, childRel); continue; }
84
+ if (ent.isDirectory()) {
85
+ recurse(childAbs, childRel);
86
+ continue;
87
+ }
72
88
  if (!ent.isFile()) continue;
73
89
  const body = readFileSync(childAbs);
74
- out[`/${childRel}`] = { hash: createHash("sha256").update(body).digest("hex"), size: body.byteLength, type: contentType(ent.name) };
90
+ out[`/${childRel}`] = {
91
+ hash: createHash("sha256").update(body).digest("hex"),
92
+ size: body.byteLength,
93
+ type: contentType(ent.name),
94
+ };
75
95
  }
76
96
  };
77
97
  recurse(dir, "");