sproutboat 0.11.11 → 0.11.12

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 CHANGED
@@ -8,6 +8,21 @@ a breaking change.
8
8
  Reconstructed from git history on 2026-09-03 for everything through v0.4.11;
9
9
  maintained going forward by the `release` skill.
10
10
 
11
+ ## [0.11.12] - 2026-09-24
12
+
13
+ ### Changed
14
+ - Porffor alpha 9 handles ESM modules without the external esbuild executable.
15
+ The CLI now requires `@sproutboat/toolchain@^0.4.12`, runtime `^0.12.1`,
16
+ and wire `^0.9.1`.
17
+ Removed the CLI's esbuild dependency, platform binary copies, and compile-time
18
+ PATH lookup. Handler validation and shimming still use Bun's built-in bundler.
19
+ The required `esbuildVersion` manifest field remains for older artifact
20
+ readers and now records `unused`.
21
+ - Local `dev` rebuilds keep a stable generated entry path for each process, so
22
+ Porffor alpha 9 can reuse unchanged compiled C units. In a local `hello`
23
+ handler-edit comparison, compilation took 549 ms with the stable path versus
24
+ 1,505 ms with changing paths. Candidate binaries remain isolated.
25
+
11
26
  ## [0.11.11] - 2026-09-24
12
27
  ### Fixed
13
28
  - Updated the native runtime to `0.12.0` so repeated HMAC signing uses the raw
@@ -15,7 +30,6 @@ maintained going forward by the `release` skill.
15
30
  - Updated the toolchain to `0.4.11` so `Uint8Array.from({ length: 16 }, mapFn)`
16
31
  produces the intended key bytes in standalone native builds.
17
32
  - Added the two-round HMAC vector to broker and standalone conformance checks.
18
-
19
33
  ## [0.11.10] - 2026-09-18
20
34
  ### Changed
21
35
  - Bumped `@sproutboat/toolchain` to `0.4.10`, pinning Porffor to the
@@ -596,7 +610,8 @@ its own package.
596
610
  - Renamed the package to `sproutboat` (was `@sproutboat/cli`); dropped the
597
611
  `sprout` bin alias in favour of a user-defined shell alias.
598
612
 
599
- [Unreleased]: https://github.com/baronunread/sproutboat-cli/compare/v0.11.11...HEAD
613
+ [Unreleased]: https://github.com/baronunread/sproutboat-cli/compare/v0.11.12...HEAD
614
+ [0.11.12]: https://github.com/baronunread/sproutboat-cli/compare/v0.11.11...v0.11.12
600
615
  [0.11.11]: https://github.com/baronunread/sproutboat-cli/compare/v0.11.10...v0.11.11
601
616
  [0.11.10]: https://github.com/baronunread/sproutboat-cli/compare/v0.11.9...v0.11.10
602
617
  [0.11.9]: https://github.com/baronunread/sproutboat-cli/compare/v0.11.8...v0.11.9
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.11.11 · runs on Bun (use `bunx`, not `npx`)
6
+ **Package:** `sproutboat` 0.11.12 · runs on Bun (use `bunx`, not `npx`)
7
7
 
8
8
  ## Commands
9
9
 
@@ -76,7 +76,7 @@ usage: sproutboat <init [name] | check [project-dir] | types [project-dir] | dev
76
76
  | | |
77
77
  | --- | --- |
78
78
  | Zig | `0.16.0` (`zig cc -target x86_64-linux-musl`, static) |
79
- | Provenance stamp | `zig-musl/0.16.0+porffor/8f01541+uws/360c276d` |
79
+ | Provenance stamp | `zig-musl/0.16.0+porffor/de4eb58+uws/360c276d` |
80
80
  | Artifact schema | `2` |
81
81
  | Runtime | `native-fetch` |
82
82
  | Capability profile | `http-sync-v0` |
@@ -6,10 +6,9 @@ to its upstream source.
6
6
 
7
7
  | Component | Version or revision | Distribution role | License | Source |
8
8
  | --- | --- | --- | --- | --- |
9
- | Porffor | `a415d194e74948f0ac32b9d608153ea8720c71fd` | Acquired on first application build | MIT | https://github.com/CanadaHonk/porffor |
9
+ | Porffor | `de4eb588264885b3a1596f75010e371a2052033f` | Acquired on first application build | MIT | https://github.com/CanadaHonk/porffor |
10
10
  | Zig | `0.16.0` | Acquired cross-compiler | MIT | https://ziglang.org |
11
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
12
  | SQLite | `3.50.4` | Acquired for standalone builds | Public domain | https://sqlite.org |
14
13
  | BearSSL | `0.6` | Acquired for standalone builds | MIT | https://bearssl.org |
15
14
 
@@ -17,7 +16,7 @@ to its upstream source.
17
16
 
18
17
  | Component | SHA-256 |
19
18
  | --- | --- |
20
- | Porffor source archive | `677779c8be49efc28686378f665cd6fc4a8a2dd81e7b15a89298103ded960829` |
19
+ | Porffor source archive | `1a62187a93356b36cb6ac703ce9770c8917e4fa65545fd7c33ee8a180ae5a4c9` |
21
20
  | Embedded uWebSockets archive | `e83736f3f8cf9d56a1ebe6ea61625a7af12386763374d47c14cff472ada7484a` |
22
21
  | SQLite amalgamation archive | `1d3049dd0f830a025a53105fc79fd2ab9431aea99e137809d064d8ee8356b032` |
23
22
  | BearSSL source archive | `6705bba1714961b41a728dfc5debbe348d2966c117649392f8c8139efc83ff14` |
@@ -25,5 +24,4 @@ to its upstream source.
25
24
 
26
25
  The complete Apache-2.0 notice for uWebSockets and uSockets is retained in the
27
26
  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.
27
+ source archives acquired at the revisions above.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutboat",
3
- "version": "0.11.11",
3
+ "version": "0.11.12",
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",
@@ -64,10 +64,9 @@
64
64
  "@sproutboat/artifact": "^0.2.0",
65
65
  "@sproutboat/assets": "^0.3.0",
66
66
  "@sproutboat/config": "^0.4.0",
67
- "@sproutboat/runtime": "^0.12.0",
68
- "@sproutboat/toolchain": "^0.4.11",
69
- "@sproutboat/wire": "^0.9.0",
70
- "esbuild": "^0.28.2"
67
+ "@sproutboat/runtime": "^0.12.1",
68
+ "@sproutboat/toolchain": "^0.4.12",
69
+ "@sproutboat/wire": "^0.9.1"
71
70
  },
72
71
  "devDependencies": {
73
72
  "@oxlint/plugins": "1.81.0",
@@ -78,10 +77,10 @@
78
77
  "typescript": "7.0.2"
79
78
  },
80
79
  "optionalDependencies": {
81
- "@sproutboat/cli-darwin-arm64": "0.11.11",
82
- "@sproutboat/cli-darwin-x64": "0.11.11",
83
- "@sproutboat/cli-linux-arm64": "0.11.11",
84
- "@sproutboat/cli-linux-x64": "0.11.11"
80
+ "@sproutboat/cli-darwin-arm64": "0.11.12",
81
+ "@sproutboat/cli-darwin-x64": "0.11.12",
82
+ "@sproutboat/cli-linux-arm64": "0.11.12",
83
+ "@sproutboat/cli-linux-x64": "0.11.12"
85
84
  },
86
85
  "engines": {
87
86
  "node": ">=18"
@@ -15,7 +15,7 @@
15
15
  * GitHub's only x64 macOS image (`macos-13`) routinely sits queued for half an
16
16
  * hour while every other leg finishes in one minute.
17
17
  */
18
- import { chmod, copyFile, mkdir } from "node:fs/promises";
18
+ import { chmod, copyFile, mkdir, rm } from "node:fs/promises";
19
19
  import { resolve } from "node:path";
20
20
  import { ensureZigArchive, type ZigPlatform } from "../src/toolchain";
21
21
 
@@ -39,49 +39,11 @@ function hostTarget(): Target {
39
39
 
40
40
  const root = resolve(import.meta.dir, "..");
41
41
 
42
- /** esbuild ships one binary per platform (`@esbuild/<os>-<arch>`); a cross
43
- * target's copy is never installed locally (npm/bun refuse it — the package
44
- * declares `os`/`cpu` and gets skipped for any other host), so fetch that
45
- * platform's tarball from the registry instead, same shape as ensureZig's
46
- * prebuilt-archive fetches. */
47
- async function esbuildBinaryFor(target: Target): Promise<string> {
48
- const host = hostTarget();
49
- if (target.platform === host.platform && target.arch === host.arch) {
50
- try {
51
- return Bun.resolveSync(`@esbuild/${target.platform}-${target.arch}/bin/esbuild`, import.meta.dir);
52
- } catch {
53
- throw new Error("host esbuild binary is missing; run bun install first");
54
- }
55
- }
56
- // SAFETY: package.json is this repo's own manifest, with a required esbuild dependency entry.
57
- const pkg = (await Bun.file(resolve(root, "package.json")).json()) as { dependencies: { esbuild: string } };
58
- const version = pkg.dependencies.esbuild.replace(/^[\^~]/, "");
59
- const name = `${target.platform}-${target.arch}`;
60
- const cacheDir = resolve(
61
- process.env.SPROUTBOAT_TOOLCHAIN_CACHE ?? `${process.env.HOME}/.cache/sproutboat`,
62
- `esbuild-${version}-${name}`,
63
- );
64
- const cached = resolve(cacheDir, "esbuild");
65
- if (await Bun.file(cached).exists()) return cached;
66
- const res = await fetch(`https://registry.npmjs.org/@esbuild/${name}/-/${name}-${version}.tgz`);
67
- if (!res.ok) throw new Error(`could not fetch @esbuild/${name}@${version}: HTTP ${res.status}`);
68
- const tarPath = resolve(cacheDir, "esbuild.tgz");
69
- await mkdir(cacheDir, { recursive: true });
70
- await Bun.write(tarPath, await res.arrayBuffer());
71
- const untar = Bun.spawn(["tar", "-xzf", tarPath, "-C", cacheDir, "--strip-components=2", "package/bin/esbuild"], {
72
- stdout: "pipe",
73
- stderr: "pipe",
74
- });
75
- const [code, err] = await Promise.all([untar.exited, new Response(untar.stderr).text()]);
76
- if (code !== 0) throw new Error(`could not extract @esbuild/${name}: ${err.trim()}`);
77
- await chmod(cached, 0o755);
78
- return cached;
79
- }
80
-
81
42
  async function buildOne(target: Target): Promise<void> {
82
43
  const packageDir = resolve(root, "platform-packages", `${target.platform}-${target.arch}`);
83
44
  const out = resolve(packageDir, "bin", "sproutboat");
84
45
  await mkdir(resolve(out, ".."), { recursive: true });
46
+ await rm(resolve(packageDir, "bin", "esbuild"), { force: true });
85
47
  // SAFETY: package.json is this release's manifest and npm requires its version.
86
48
  const version = ((await Bun.file(resolve(root, "package.json")).json()) as { version: string }).version;
87
49
  // SAFETY: every checked-in platform package manifest owns a required string version.
@@ -103,9 +65,6 @@ async function buildOne(target: Target): Promise<void> {
103
65
  { cwd: root, stdout: "inherit", stderr: "inherit" },
104
66
  );
105
67
  if ((await child.exited) !== 0) throw new Error(`compile failed for ${target.platform}-${target.arch}`);
106
- const esbuild = await esbuildBinaryFor(target);
107
- const packagedEsbuild = resolve(packageDir, "bin", "esbuild");
108
- await copyFile(esbuild, packagedEsbuild);
109
68
  // The compressed archive (~50 MB), not the ~400 MB extracted install: Zig's
110
69
  // lib/ carries libc/libc++ sources for every target it can cross-compile to,
111
70
  // which only matters once, on extract. `ensureZig` (src/toolchain.ts) looks
@@ -114,7 +73,6 @@ async function buildOne(target: Target): Promise<void> {
114
73
  // same shape as the vendored uWebSockets archive.
115
74
  await copyFile(await ensureZigArchive(target.zigPlatform), resolve(packageDir, "bin", "zig.tar.xz"));
116
75
  await copyFile(resolve(root, "THIRD_PARTY_NOTICES.md"), resolve(packageDir, "THIRD_PARTY_NOTICES.md"));
117
- await chmod(packagedEsbuild, 0o755);
118
76
  await chmod(out, 0o755);
119
77
  }
120
78
 
package/src/build.ts CHANGED
@@ -47,6 +47,8 @@ export type BuildInput = {
47
47
  reuseSproutPath?: string;
48
48
  /** An isolated artifact directory, used by dev candidates to avoid mixing snapshots. */
49
49
  outputDirectory?: string;
50
+ /** Stable compiler input used only by the local dev loop. */
51
+ generatedPath?: string;
50
52
  };
51
53
 
52
54
  export type BuildOutput = {
@@ -182,6 +184,7 @@ export async function buildArtifact(input: BuildInput): Promise<BuildOutput> {
182
184
  else
183
185
  await compileSprout({
184
186
  sourcePath: input.sourcePath,
187
+ generatedPath: input.generatedPath,
185
188
  source: input.source,
186
189
  outPath: sproutPath,
187
190
  vars: input.config.vars ?? {},
package/src/bundle.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * #89 — bundle the handler before Porffor sees it.
3
3
  *
4
- * Porffor compiles one self-contained module, so until now a project was one
5
- * file with no imports: no router, no validation library, no SDK, no splitting
6
- * a codebase in two. Bundling first lifts that without touching the compiler.
4
+ * Porffor alpha 9 supports ESM imports. We still bundle first so capability
5
+ * validation sees every dependency and the runtime wrapper receives one
6
+ * controlled default export.
7
7
  *
8
8
  * Bun's bundler resolves relative imports across the project and bare
9
9
  * specifiers out of the project's own `node_modules`, then emits a single ESM
package/src/compile.ts CHANGED
@@ -10,7 +10,6 @@
10
10
  * unusable it stops with its specific integrity or archive error. Contributors
11
11
  * may explicitly opt into Porffor's source fallback.
12
12
  */
13
- import { existsSync } from "node:fs";
14
13
  import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
15
14
  import { dirname, resolve } from "node:path";
16
15
  import { ensurePorfforPatched } from "./patch-porffor";
@@ -50,6 +49,8 @@ const COMPILE_TIMEOUT_MS = Number(process.env.SPROUTBOAT_COMPILE_TIMEOUT_MS || 6
50
49
 
51
50
  export type CompileInput = {
52
51
  sourcePath: string;
52
+ /** Stable compiler input for dev rebuilds, independent of the candidate binary path. */
53
+ generatedPath?: string;
53
54
  /** The bundled module (#89). Falls back to reading `sourcePath` verbatim. */
54
55
  source?: string;
55
56
  outPath: string;
@@ -195,7 +196,8 @@ export async function compileSprout(input: CompileInput): Promise<void> {
195
196
  // OS may still be executing. The linker cannot overwrite either, so clear it
196
197
  // first rather than failing with "can't write output file".
197
198
  await rm(input.outPath, { force: true });
198
- const generatedPath = resolve(outDir, "sprout.generated.js");
199
+ const generatedPath = input.generatedPath ?? resolve(outDir, "sprout.generated.js");
200
+ await mkdir(dirname(generatedPath), { recursive: true });
199
201
  const [source, prelude] = await Promise.all([
200
202
  input.source === undefined ? readFile(input.sourcePath, "utf8") : Promise.resolve(input.source),
201
203
  loadPrelude(input.transport ?? "broker"),
@@ -222,22 +224,7 @@ export async function compileSprout(input: CompileInput): Promise<void> {
222
224
  // below instead and has no zigBin to contribute here.
223
225
  const zigDir = input.zigBin ? `${dirname(input.zigBin)}:` : "";
224
226
  const hostCompiler = input.target === "host" ? await resolveHostCompiler() : undefined;
225
- // The per-platform binary ships esbuild next to it; a compiled build finds it
226
- // there. Running from the npm package under Bun, `process.execPath` is Bun
227
- // itself and `npm i -g` puts no dependency `.bin` on PATH, so resolve the
228
- // esbuild dependency directly. `Bun.which` is the last resort.
229
- const packagedEsbuild = resolve(dirname(process.execPath), "esbuild");
230
- let esbuild: string | null = existsSync(packagedEsbuild) ? packagedEsbuild : null;
231
- if (!esbuild) {
232
- try {
233
- esbuild = Bun.resolveSync("esbuild/bin/esbuild", import.meta.dir);
234
- } catch {
235
- esbuild = null;
236
- }
237
- }
238
- esbuild ??= Bun.which("esbuild");
239
- if (!esbuild) throw new Error("esbuild could not be located; install it or reinstall sproutboat");
240
- const path = `${zigDir}${dirname(esbuild)}:${process.env.PATH ?? ""}`;
227
+ const path = `${zigDir}${process.env.PATH ?? ""}`;
241
228
  const command = PACKAGED
242
229
  ? [process.execPath, "__porffor", launcher]
243
230
  : [process.execPath, resolve(import.meta.dir, "main.ts"), "__porffor", launcher];
package/src/dev.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  * project, one port.
12
12
  */
13
13
  import { existsSync, watch, type FSWatcher } from "node:fs";
14
+ import { randomUUID } from "node:crypto";
14
15
  import { mkdir, readFile, rm } from "node:fs/promises";
15
16
  import { connect } from "node:net";
16
17
  import { dirname, resolve } from "node:path";
@@ -21,6 +22,7 @@ import { amber, dim, leaf, ok } from "./style";
21
22
  import type { SproutboatConfig } from "./config";
22
23
 
23
24
  const RESTART_DEBOUNCE_MS = 120;
25
+ const DEV_COMPILE_SESSION = randomUUID();
24
26
 
25
27
  export type DevInput = {
26
28
  projectDir: string;
@@ -121,6 +123,14 @@ async function start(input: DevInput, port: number): Promise<Running> {
121
123
  optimize: "dev",
122
124
  reuseSproutPath: input.reuseSproutPath,
123
125
  outputDirectory: candidateDir,
126
+ // Alpha 9 keys its C-unit cache by the entry's absolute path. Keep that
127
+ // path fixed across saves while each candidate keeps its own binary.
128
+ generatedPath: resolve(
129
+ input.projectDir,
130
+ ".sproutboat/dev-compile",
131
+ `${process.pid}-${DEV_COMPILE_SESSION}`,
132
+ "sprout.generated.js",
133
+ ),
124
134
  });
125
135
  } catch (error) {
126
136
  await rm(candidateDir, { recursive: true, force: true });
package/src/toolchain.ts CHANGED
@@ -677,14 +677,9 @@ export function porfforVersion(): string {
677
677
  }
678
678
 
679
679
  export function esbuildVersion(): string {
680
- try {
681
- const pkg = Bun.resolveSync("esbuild/package.json", import.meta.dir);
682
- // SAFETY: esbuild's package.json always has a string `version`; defaulted below.
683
- const parsed = JSON.parse(readFileSync(pkg, "utf8")) as { version?: string };
684
- return parsed.version || "unknown";
685
- } catch {
686
- return "unknown";
687
- }
680
+ // Legacy artifact field, retained so older control planes can read manifests.
681
+ // Handler bundling uses Bun.build and Porffor alpha 9 needs no esbuild binary.
682
+ return "unused";
688
683
  }
689
684
 
690
685
  function uwsCommit(): string {