Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Notable changes to Swarmail. Versions follow [semantic versioning](https://semver.org).

## Unreleased

- The build lays the compiled bytecode out from a recorded profile (`scripts/swarmail.order`, Bun's
`--bytecode-order`), so what a run needs sits together in the executable. On Bun 1.4.3 hook commands used 6% to 18%
less peak memory and the idle server 5% less in benchmarks, with no clear change in start-up time or request latency.
`bun scripts/train-bytecode-order.ts` records the profile again; Bun before 1.4.3 ignores it.

## 0.5.2 - 2026-10-09

- Require agents to record whole-session closeouts and retire their own project
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,15 @@ bun run check # format, lint and type check
```

`bun scripts/build.ts --if-stale` rebuilds the binary only when a source file,
the dependency manifest or lockfile, or the Bun version changed. Restart the unit
the dependency manifest or lockfile, the bytecode-order profile, or the Bun
version changed. Restart the unit
to load a new build.

The build passes `scripts/swarmail.order` to Bun's `--bytecode-order` (Bun 1.4.3 or later; earlier versions ignore it).
After a change to what the server runs at start-up or in its common calls, record the profile again with
`bun scripts/train-bytecode-order.ts` and commit the new file. A profile from older sources still applies, because Bun
matches functions by a hash of their syntax.

## Sponsoring

Swarmail is built and maintained by one person. If it saves you time, you can
Expand Down
28 changes: 21 additions & 7 deletions scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env bun
// Compiles the swarmail command (src/cli.ts, or main.ts beside it when a build adds its own
// commands there, with bytecode and an embedded source map) into ~/.local/bin/swarmail, which the service, the host
// hooks and the git guard run. The binary hashes server sources, the dependency manifest and lockfile, and Bun.
// commands there, with bytecode laid out by scripts/swarmail.order and an embedded source map) into
// ~/.local/bin/swarmail, which the service, the host hooks and the git guard run. The binary hashes server sources,
// the dependency manifest and lockfile, the bytecode-order profile, and Bun.
// `--if-stale` rebuilds when one of those inputs changes.
// A running server keeps its old build until the service restarts.
// Usage: bun scripts/build.ts [--if-stale] [--out path]
Expand All @@ -14,7 +15,10 @@ import { binaryPath, homeDir } from "../src/paths.ts";
const REPO_ROOT = join(import.meta.dir, "..");
export const defaultBinary = (home = homeDir()) => binaryPath(home);

/** Hash the server sources, dependency manifest and lockfile, and the Bun version. */
/** The bytecode-order profile; scripts/train-bytecode-order.ts records it. Bun before 1.4.3 ignores the flag. */
export const orderProfile = (root = REPO_ROOT) => join(root, "scripts", "swarmail.order");

/** Hash the server sources, dependency manifest and lockfile, the bytecode-order profile, and the Bun version. */
export function sourceHash(root = REPO_ROOT, bun = Bun.version): string {
const dir = join(root, "src");
const hash = createHash("sha256").update(`bun ${bun}\0`);
Expand All @@ -26,8 +30,8 @@ export function sourceHash(root = REPO_ROOT, bun = Bun.version): string {
.update(readFileSync(join(dir, name)))
.update("\0");
}
// Compiled runtime dependencies change when the manifest or resolved versions change.
for (const name of ["package.json", "bun.lock"]) {
// Compiled runtime dependencies change when the manifest or resolved versions change; the profile changes the layout.
for (const name of ["package.json", "bun.lock", "scripts/swarmail.order"]) {
const path = join(root, name);
if (existsSync(path)) {
hash.update(`${name}\0`).update(readFileSync(path)).update("\0");
Expand Down Expand Up @@ -88,7 +92,15 @@ function replaceBinary(next: string, out: string): void {
}
}

export function buildSwarmail(root = REPO_ROOT, out = defaultBinary()): string {
/** `order` is the profile to lay the bytecode out from, or null for a build that records one. */
export function buildSwarmail(
root = REPO_ROOT,
out = defaultBinary(),
{
entry = buildEntry(root),
order = existsSync(orderProfile(root)) ? orderProfile(root) : null,
} = {},
): string {
const source = sourceHash(root);
// Bun adds .exe to a Windows outfile without one, so the suffix stays last.
const next = out.replace(/(\.exe)?$/i, ".new$1");
Expand All @@ -99,11 +111,13 @@ export function buildSwarmail(root = REPO_ROOT, out = defaultBinary()): string {
// No --smol: measured 2026-09-28, it saved 7 MB under load but doubled p99 latency.
"--compile",
"--bytecode",
// Measured on Bun 1.4.3: 6% to 18% less memory in hook commands and 5% in the idle server, same speed.
...(order ? [`--bytecode-order=${order}`] : []),
// Stack traces name the source file, line and function; --minify saved 40 KB of 84 MB and no startup time.
"--sourcemap",
"--define",
`SWARMAIL_SOURCE=${JSON.stringify(source)}`,
buildEntry(root),
entry,
"--outfile",
next,
],
Expand Down
Loading
Loading