sproutboat 0.14.0 → 0.16.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 CHANGED
@@ -10,6 +10,61 @@ maintained going forward by the `release` skill.
10
10
 
11
11
  ## Unreleased
12
12
 
13
+ ## [0.16.0] - 2026-10-10
14
+
15
+ ### Added
16
+
17
+ - Proxy and revocable Proxy are accepted in handlers with Porffor alpha-16.
18
+ Native regression checks cover property traps, enumeration, callable and
19
+ constructible proxies, revocation, and frozen-property invariants.
20
+
21
+ - `URLSearchParams` and `FormData` provide live keys, values and entries
22
+ iterators. `itty-router` 5.0.24 `IttyRouter` passes GET parameters, Unicode
23
+ POST bodies and a 404 fallback. A basic tRPC 11.19.0 server caller also works.
24
+
25
+ ### Changed
26
+
27
+ - Pin Porffor alpha-16 (`43087d4`) through `@sproutboat/toolchain` 0.6.0
28
+ and `@sproutboat/runtime` 0.15.0. Collection iterator methods and `matchAll`
29
+ now return iterators. Typed-array `from`, `set` offset and `join` fixes are
30
+ supplied upstream, so their local source patches are removed.
31
+ - Native output handles Unicode console strings, clamped-array ties round
32
+ to even, and class static initializers use the class as `this`.
33
+
34
+ ### Compatibility limits
35
+
36
+ - Proxy support alone does not establish compatibility for every package.
37
+ `qs` 6.16.0 parses and serializes nested query data in a native HTTP probe.
38
+ Standard `itty-router` 5.0.24 `Router` still hits a labelled-break compiler
39
+ error. String iteration still splits astral characters into UTF-16 halves.
40
+
41
+
42
+ ## [0.15.0] - 2026-10-05
43
+
44
+ ### Changed
45
+
46
+ - Sprouts compile with Porffor alpha-15 (`@sproutboat/toolchain` 0.5.0).
47
+ Async functions now compile to state machines, and `console.log` no longer
48
+ adds colour codes when its output is piped. The compatibility suite is
49
+ unchanged at 30/32, and binary size, compile time, start-up and throughput
50
+ are level with alpha-13.
51
+
52
+ ### Performance
53
+
54
+ - Rebuilding after a small change is about 3 times faster: 2.1 to 2.4 s
55
+ instead of 6.8 to 7.7 s for the standalone example app. Builds now compile
56
+ from one path per project, so Porffor reuses the C units that didn't change.
57
+ Porffor's build directories that haven't been used for a week are removed;
58
+ they used to pile up without limit.
59
+
60
+ ### Fixed
61
+
62
+ - Handlers using marked, or any bundled class whose static methods create
63
+ instances of the class itself, work again. Since 0.12.6 they failed with
64
+ `TypeError: value is not a constructor` (baronunread/sproutboat#256).
65
+ - An ISO date's timezone offset could flip sign, so `-00:30` sometimes parsed
66
+ as `+00:30`, depending on what sat next to the string in memory.
67
+
13
68
  ## [0.14.0] - 2026-10-04
14
69
 
15
70
  ### Changed
@@ -804,7 +859,8 @@ its own package.
804
859
  - Renamed the package to `sproutboat` (was `@sproutboat/cli`); dropped the
805
860
  `sprout` bin alias in favour of a user-defined shell alias.
806
861
 
807
- [Unreleased]: https://github.com/baronunread/sproutboat-cli/compare/v0.14.0...HEAD
862
+ [Unreleased]: https://github.com/baronunread/sproutboat-cli/compare/v0.15.0...HEAD
863
+ [0.15.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.14.0...v0.15.0
808
864
  [0.14.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.13.0...v0.14.0
809
865
  [0.13.0]: https://github.com/baronunread/sproutboat-cli/compare/v0.12.7...v0.13.0
810
866
  [0.12.7]: https://github.com/baronunread/sproutboat-cli/compare/v0.12.6...v0.12.7
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.14.0
6
+ **Package:** `sproutboat` 0.16.0
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/547c781+uws/360c276d` |
79
+ | Provenance stamp | `zig-musl/0.16.0+porffor/43087d4+uws/360c276d` |
80
80
  | Artifact schema | `2` |
81
81
  | Runtime | `native-fetch` |
82
82
  | Capability profile | `http-sync-v0` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutboat",
3
- "version": "0.14.0",
3
+ "version": "0.16.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",
@@ -64,24 +64,24 @@
64
64
  "@sproutboat/artifact": "^0.3.0",
65
65
  "@sproutboat/assets": "^0.3.0",
66
66
  "@sproutboat/config": "^0.5.0",
67
- "@sproutboat/runtime": "^0.14.0",
68
- "@sproutboat/toolchain": "^0.4.24",
67
+ "@sproutboat/runtime": "^0.15.0",
68
+ "@sproutboat/toolchain": "^0.6.0",
69
69
  "@sproutboat/wire": "^0.11.0"
70
70
  },
71
71
  "devDependencies": {
72
- "@oxlint/plugins": "1.81.0",
73
- "@types/bun": "1.4.0",
72
+ "@oxlint/plugins": "1.85.0",
73
+ "@types/bun": "1.4.2",
74
74
  "cookie": "2.0.1",
75
- "lefthook": "2.1.12",
76
- "oxfmt": "0.66.0",
77
- "oxlint": "1.81.0",
75
+ "lefthook": "2.1.14",
76
+ "oxfmt": "0.70.0",
77
+ "oxlint": "1.85.0",
78
78
  "typescript": "7.0.2"
79
79
  },
80
80
  "optionalDependencies": {
81
- "@sproutboat/cli-darwin-arm64": "0.14.0",
82
- "@sproutboat/cli-darwin-x64": "0.14.0",
83
- "@sproutboat/cli-linux-arm64": "0.14.0",
84
- "@sproutboat/cli-linux-x64": "0.14.0"
81
+ "@sproutboat/cli-darwin-arm64": "0.16.0",
82
+ "@sproutboat/cli-darwin-x64": "0.16.0",
83
+ "@sproutboat/cli-linux-arm64": "0.16.0",
84
+ "@sproutboat/cli-linux-x64": "0.16.0"
85
85
  },
86
86
  "engines": {
87
87
  "node": ">=18"
package/src/build.ts CHANGED
@@ -248,24 +248,38 @@ export async function buildArtifact(input: BuildInput): Promise<BuildOutput> {
248
248
  const [sqliteObject, tls] = embedded
249
249
  ? await Promise.all([ensureSqliteObject({ target, zigBin }), ensureBearssl({ target, zigBin })])
250
250
  : [null, null];
251
- await compileSprout({
252
- sourcePath: input.sourcePath,
253
- generatedPath: input.generatedPath,
254
- generatedSource,
255
- outPath,
256
- vars: input.config.vars ?? {},
257
- bindings,
258
- zigBin,
259
- target: input.target,
260
- compatibilityDate: input.config.compatibility_date,
261
- transport: input.transport,
262
- appName: input.config.name,
263
- assets: bakedAssets,
264
- extraLink: [...(sqliteObject ? [sqliteObject] : []), ...(tls ? tls.objects : [])],
265
- extraCflags: tls ? ["-I", tls.includeDir] : [],
266
- optimize: input.optimize,
267
- versionMetadata,
268
- });
251
+ // Porffor caches compiled C units per entry path, so compile from one
252
+ // path per project and build shape: unchanged units (the uWebSockets
253
+ // server alone is ~1.75 s of clang) are reused instead of rebuilt, and
254
+ // Porffor keeps one build dir per shape instead of one per artifact.
255
+ const unitDir = resolve(
256
+ input.projectDir,
257
+ ".sproutboat/porffor-units",
258
+ `${input.target ?? "linux-x86_64"}-${input.transport ?? "broker"}-${input.optimize ?? "release"}`,
259
+ );
260
+ const unitLock = input.generatedPath ? null : await acquireArtifactLock(unitDir);
261
+ try {
262
+ await compileSprout({
263
+ sourcePath: input.sourcePath,
264
+ generatedPath: input.generatedPath ?? resolve(unitDir, "sprout.generated.js"),
265
+ generatedSource,
266
+ outPath,
267
+ vars: input.config.vars ?? {},
268
+ bindings,
269
+ zigBin,
270
+ target: input.target,
271
+ compatibilityDate: input.config.compatibility_date,
272
+ transport: input.transport,
273
+ appName: input.config.name,
274
+ assets: bakedAssets,
275
+ extraLink: [...(sqliteObject ? [sqliteObject] : []), ...(tls ? tls.objects : [])],
276
+ extraCflags: tls ? ["-I", tls.includeDir] : [],
277
+ optimize: input.optimize,
278
+ versionMetadata,
279
+ });
280
+ } finally {
281
+ if (unitLock) await rm(unitLock, { recursive: true, force: true });
282
+ }
269
283
  };
270
284
  let compileCache: BuildOutput["compileCache"] = "bypass";
271
285
  const compileStartedAt = performance.now();
package/src/compile.ts CHANGED
@@ -10,7 +10,8 @@
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 { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
13
+ import { chmod, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
14
+ import { homedir } from "node:os";
14
15
  import { dirname, resolve } from "node:path";
15
16
  import { ensurePorfforPatched } from "./patch-porffor";
16
17
  import { ensureUWebSockets, ensureUWebSocketsHost, resolveHostCompiler, UwsUnavailableError } from "./toolchain";
@@ -113,6 +114,32 @@ export async function loadPrelude(transport: Transport = "broker"): Promise<stri
113
114
  return core.replace(TRANSPORT_MARKER, chosen);
114
115
  }
115
116
 
117
+ let porfforBuildsPruned = false;
118
+ /**
119
+ * Porffor writes one build dir per entry path under ~/.cache/porffor/build and
120
+ * never removes any (one machine had 1,395 of them, 6.3 GB). Drop the ones not
121
+ * compiled in for a week; the only cost is a recompile. Each dir's `flags`
122
+ * stamp is rewritten on every compile, so its mtime is "last used".
123
+ */
124
+ export async function prunePorfforBuilds(
125
+ root = resolve(process.env.HOME ?? homedir(), ".cache/porffor/build"),
126
+ now = Date.now(),
127
+ ): Promise<number> {
128
+ porfforBuildsPruned = true;
129
+ const cutoff = now - 7 * 24 * 60 * 60 * 1000;
130
+ const entries: string[] = await readdir(root).catch(() => []);
131
+ let removed = 0;
132
+ for (const name of entries) {
133
+ const dir = resolve(root, name);
134
+ const used = (await stat(resolve(dir, "flags")).catch(() => null)) ?? (await stat(dir).catch(() => null));
135
+ if (used && used.mtimeMs < cutoff) {
136
+ await rm(dir, { recursive: true, force: true });
137
+ removed++;
138
+ }
139
+ }
140
+ return removed;
141
+ }
142
+
116
143
  /** Child env for the Porffor run. `SB_EXTRA_LINK` is read by the patched link
117
144
  * step (#15) and is absent entirely for a normal build. `cc`/`cxx` override
118
145
  * Porffor's own `CC`/`CXX` read for a **host** build (musl builds hardcode
@@ -191,6 +218,9 @@ export async function compileSprout(input: CompileInput): Promise<void> {
191
218
  }
192
219
  }
193
220
 
221
+ // Once per process, in the background: housekeeping must never slow a build.
222
+ if (!porfforBuildsPruned) void prunePorfforBuilds().catch(() => {});
223
+
194
224
  const outDir = dirname(input.outPath);
195
225
  await mkdir(outDir, { recursive: true });
196
226
  // The artifact dir is content-addressed, so rebuilding unchanged source lands