sproutboat 0.21.0 → 0.23.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 +42 -0
- package/SURFACE.md +3 -3
- package/package.json +8 -8
- package/src/build.ts +94 -14
- package/src/compile.ts +49 -24
- package/src/main.ts +33 -4
- package/src/standalone-build.ts +17 -2
- package/src/surface.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,48 @@ maintained going forward by the `release` skill.
|
|
|
10
10
|
|
|
11
11
|
## Unreleased
|
|
12
12
|
|
|
13
|
+
## [0.23.0] - 2026-10-22
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Experimental local backup of a D1 database to LTX files, in standalone
|
|
18
|
+
binaries (`@sproutboat/runtime` 0.22.0). It's off by default. To turn it on, set
|
|
19
|
+
the compatibility date to `2026-10-22` or later, `SB_LTX_ENABLE=1`, and
|
|
20
|
+
`SB_LTX_DATABASE=<binding>`. Every committed transaction is then written to a
|
|
21
|
+
durable LTX file, and the app resumes from the last one after a restart.
|
|
22
|
+
- Only one process may write to the database.
|
|
23
|
+
- Files stay on local disk; uploading them is not part of this release.
|
|
24
|
+
- See the runtime's `LTX_REPLICATION.md` for limits and recovery behaviour.
|
|
25
|
+
|
|
26
|
+
## [0.22.0] - 2026-10-22
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `sproutboat build --standalone --emit-only` writes
|
|
31
|
+
`.sproutboat/porffor-units/linux-x86_64-embedded-release/sprout.generated.js`,
|
|
32
|
+
byte for byte what a full standalone build compiles, prints its path and
|
|
33
|
+
stops. It runs every check that can fail a build, but acquires no Zig,
|
|
34
|
+
SQLite, BearSSL or Porffor and writes no binary, so it takes well under a
|
|
35
|
+
second where a full build takes tens of seconds. Without `--standalone` it
|
|
36
|
+
emits the broker-transport module the same way.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- The `d1`, `queue` and `analytics` examples read the request body with
|
|
41
|
+
`await request.text()` rather than as `request.body`. From compatibility date
|
|
42
|
+
`2026-10-21`, `request.body` is a `ReadableStream`, as in Workers, so
|
|
43
|
+
`JSON.parse(request.body)` threw `SyntaxError: Unexpected token` and a D1
|
|
44
|
+
example moved to a current date answered every POST with a 500. The new form
|
|
45
|
+
works at every date. `bun run examples` now also builds the d1 example at
|
|
46
|
+
`2026-10-22` (`d1@2026-10-22`), so an example that only works before that
|
|
47
|
+
date fails the run.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- `@sproutboat/toolchain` 0.12.1: heap-backed BigInts stay alive through
|
|
52
|
+
conservative garbage collection, which fixes crashes and wrong checksums in
|
|
53
|
+
BigInt-heavy code such as CRC64 and LTX encoding. `@sproutboat/wire` 0.14.1.
|
|
54
|
+
|
|
13
55
|
## [0.21.0] - 2026-10-21
|
|
14
56
|
|
|
15
57
|
### Changed
|
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.
|
|
6
|
+
**Package:** `sproutboat` 0.23.0
|
|
7
7
|
|
|
8
8
|
## Commands
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
| `check` | `[project-dir]` | Validate the config and entry point without building. |
|
|
14
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. |
|
|
15
15
|
| `dev` | `[project-dir] [--port <n>] [--no-watch]` | Run the project on this machine against a real broker, rebuilding on save. |
|
|
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
|
+
| `build` | `[project-dir] [--target host] [--standalone] [--emit-only]` | 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. `--emit-only` stops after writing `.sproutboat/porffor-units/<shape>/sprout.generated.js`, the module Porffor would compile, and prints its path: every check still runs, nothing is compiled. |
|
|
17
17
|
| `toolchain` | `doctor [--json]` | Report the selected compiler inputs, cache locations, overrides, and host prerequisites without downloading or compiling anything. |
|
|
18
18
|
| `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. |
|
|
19
19
|
| `versions` | `<list | view <version-id>> [project-dir]` | List the project's deployed versions, or show one version's artifact and bindings. |
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
| `complete` | `[bash | zsh | fish]` | Print a shell completion script for the top-level commands (defaults to $SHELL). |
|
|
33
33
|
|
|
34
34
|
```
|
|
35
|
-
usage: sproutboat <init [name] | check [project-dir] | types [project-dir] | dev [project-dir] [--port <n>] [--no-watch] | build [project-dir] [--target host] [--standalone] | toolchain doctor [--json] | 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 | complete [bash | zsh | fish]>
|
|
35
|
+
usage: sproutboat <init [name] | check [project-dir] | types [project-dir] | dev [project-dir] [--port <n>] [--no-watch] | build [project-dir] [--target host] [--standalone] [--emit-only] | toolchain doctor [--json] | 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 | complete [bash | zsh | fish]>
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Environment variables
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sproutboat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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,9 +64,9 @@
|
|
|
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.
|
|
68
|
-
"@sproutboat/toolchain": "^0.12.
|
|
69
|
-
"@sproutboat/wire": "^0.14.
|
|
67
|
+
"@sproutboat/runtime": "^0.22.0",
|
|
68
|
+
"@sproutboat/toolchain": "^0.12.1",
|
|
69
|
+
"@sproutboat/wire": "^0.14.2"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@oxlint/plugins": "1.85.0",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"typescript": "7.0.2"
|
|
79
79
|
},
|
|
80
80
|
"optionalDependencies": {
|
|
81
|
-
"@sproutboat/cli-darwin-arm64": "0.
|
|
82
|
-
"@sproutboat/cli-darwin-x64": "0.
|
|
83
|
-
"@sproutboat/cli-linux-arm64": "0.
|
|
84
|
-
"@sproutboat/cli-linux-x64": "0.
|
|
81
|
+
"@sproutboat/cli-darwin-arm64": "0.23.0",
|
|
82
|
+
"@sproutboat/cli-darwin-x64": "0.23.0",
|
|
83
|
+
"@sproutboat/cli-linux-arm64": "0.23.0",
|
|
84
|
+
"@sproutboat/cli-linux-x64": "0.23.0"
|
|
85
85
|
},
|
|
86
86
|
"engines": {
|
|
87
87
|
"node": ">=18"
|
package/src/build.ts
CHANGED
|
@@ -111,12 +111,13 @@ async function acquireArtifactLock(artifactDir: string): Promise<string> {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
114
|
+
* Everything a build derives before the native compile: bindings, assets,
|
|
115
|
+
* version metadata and the wrapped module itself. Every check that can fail a
|
|
116
|
+
* build on its source or config (a missing asset dir, oversized baked assets,
|
|
117
|
+
* a handler without a default export) runs here, so `emitGenerated` fails
|
|
118
|
+
* exactly where `buildArtifact` would.
|
|
118
119
|
*/
|
|
119
|
-
|
|
120
|
+
async function prepareBuild(input: BuildInput) {
|
|
120
121
|
const source = input.source ?? (await readFile(input.sourcePath));
|
|
121
122
|
const sourceHash = digest(source);
|
|
122
123
|
const target = input.target ?? "linux-x86_64";
|
|
@@ -220,6 +221,93 @@ export async function buildArtifact(input: BuildInput): Promise<BuildOutput> {
|
|
|
220
221
|
input.transport,
|
|
221
222
|
versionMetadata,
|
|
222
223
|
);
|
|
224
|
+
return {
|
|
225
|
+
sourceHash,
|
|
226
|
+
target,
|
|
227
|
+
embedded,
|
|
228
|
+
host,
|
|
229
|
+
bindings,
|
|
230
|
+
assetDir,
|
|
231
|
+
assetManifest,
|
|
232
|
+
bakedAssets,
|
|
233
|
+
builtAt,
|
|
234
|
+
versionId,
|
|
235
|
+
versionMetadata,
|
|
236
|
+
generatedSource,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Porffor caches compiled C units per entry path, so compile from one path per
|
|
242
|
+
* project and build shape: unchanged units (the uWebSockets server alone is
|
|
243
|
+
* ~1.75 s of clang) are reused instead of rebuilt, and Porffor keeps one build
|
|
244
|
+
* dir per shape instead of one per artifact. `sprout.generated.js` lives here.
|
|
245
|
+
*/
|
|
246
|
+
function unitDirFor(input: BuildInput): string {
|
|
247
|
+
return resolve(
|
|
248
|
+
input.projectDir,
|
|
249
|
+
".sproutboat/porffor-units",
|
|
250
|
+
`${input.target ?? "linux-x86_64"}-${input.transport ?? "broker"}-${input.optimize ?? "release"}`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Write the wrapped module a build would compile, at the same path, and stop.
|
|
256
|
+
*
|
|
257
|
+
* Runs every check `buildArtifact` runs before compiling, then hands
|
|
258
|
+
* `compileSprout` `emitOnly`: no Zig, SQLite, BearSSL, Porffor or uWebSockets
|
|
259
|
+
* is acquired, nothing is compiled, and no artifact directory is written.
|
|
260
|
+
*/
|
|
261
|
+
export async function emitGenerated(input: BuildInput): Promise<{ generatedPath: string }> {
|
|
262
|
+
const { bindings, bakedAssets, versionMetadata, generatedSource } = await prepareBuild(input);
|
|
263
|
+
const unitDir = unitDirFor(input);
|
|
264
|
+
const generatedPath = input.generatedPath ?? resolve(unitDir, "sprout.generated.js");
|
|
265
|
+
// Same lock as a full build, so an emit never races a compile reading the file.
|
|
266
|
+
const unitLock = input.generatedPath ? null : await acquireArtifactLock(unitDir);
|
|
267
|
+
try {
|
|
268
|
+
await compileSprout({
|
|
269
|
+
sourcePath: input.sourcePath,
|
|
270
|
+
generatedPath,
|
|
271
|
+
generatedSource,
|
|
272
|
+
outPath: resolve(unitDir, "sprout"),
|
|
273
|
+
vars: input.config.vars ?? {},
|
|
274
|
+
bindings,
|
|
275
|
+
target: input.target,
|
|
276
|
+
compatibilityDate: input.config.compatibility_date,
|
|
277
|
+
transport: input.transport,
|
|
278
|
+
appName: input.config.name,
|
|
279
|
+
assets: bakedAssets,
|
|
280
|
+
optimize: input.optimize,
|
|
281
|
+
versionMetadata,
|
|
282
|
+
emitOnly: true,
|
|
283
|
+
});
|
|
284
|
+
} finally {
|
|
285
|
+
if (unitLock) await rm(unitLock, { recursive: true, force: true });
|
|
286
|
+
}
|
|
287
|
+
return { generatedPath };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Cross-compile a handler into a linux-x86_64 native-fetch server binary with
|
|
292
|
+
* Porffor + Zig — no Docker. The binary is not run here (the build host may not
|
|
293
|
+
* be linux); the control plane starts it once on deploy and rejects it if it
|
|
294
|
+
* does not come up.
|
|
295
|
+
*/
|
|
296
|
+
export async function buildArtifact(input: BuildInput): Promise<BuildOutput> {
|
|
297
|
+
const {
|
|
298
|
+
sourceHash,
|
|
299
|
+
target,
|
|
300
|
+
embedded,
|
|
301
|
+
host,
|
|
302
|
+
bindings,
|
|
303
|
+
assetDir,
|
|
304
|
+
assetManifest,
|
|
305
|
+
bakedAssets,
|
|
306
|
+
builtAt,
|
|
307
|
+
versionId,
|
|
308
|
+
versionMetadata,
|
|
309
|
+
generatedSource,
|
|
310
|
+
} = await prepareBuild(input);
|
|
223
311
|
const compileKey = digest(
|
|
224
312
|
JSON.stringify({
|
|
225
313
|
generatedSource,
|
|
@@ -248,15 +336,7 @@ export async function buildArtifact(input: BuildInput): Promise<BuildOutput> {
|
|
|
248
336
|
const [sqliteObject, tls] = embedded
|
|
249
337
|
? await Promise.all([ensureSqliteObject({ target, zigBin }), ensureBearssl({ target, zigBin })])
|
|
250
338
|
: [null, null];
|
|
251
|
-
|
|
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
|
-
);
|
|
339
|
+
const unitDir = unitDirFor(input);
|
|
260
340
|
const unitLock = input.generatedPath ? null : await acquireArtifactLock(unitDir);
|
|
261
341
|
try {
|
|
262
342
|
await compileSprout({
|
package/src/compile.ts
CHANGED
|
@@ -93,6 +93,13 @@ export type CompileInput = {
|
|
|
93
93
|
* cannot execute the deploy artifact.
|
|
94
94
|
*/
|
|
95
95
|
target?: "linux-x86_64" | "host";
|
|
96
|
+
/**
|
|
97
|
+
* Write `generatedPath` exactly as a full compile would, then stop: no
|
|
98
|
+
* Porffor, no uWebSockets, no compiler, nothing at `outPath`. For callers
|
|
99
|
+
* that need the wrapped module (prelude, bindings, handler) and never run the
|
|
100
|
+
* binary, such as a qualification run that only inspects the source.
|
|
101
|
+
*/
|
|
102
|
+
emitOnly?: boolean;
|
|
96
103
|
};
|
|
97
104
|
|
|
98
105
|
/**
|
|
@@ -197,8 +204,48 @@ export function porfforArgs(
|
|
|
197
204
|
return args;
|
|
198
205
|
}
|
|
199
206
|
|
|
200
|
-
/**
|
|
207
|
+
/** The wrapped module the compiler sees: `generatedSource` if given, else the source wrapped here. */
|
|
208
|
+
async function generateSource(input: CompileInput): Promise<string> {
|
|
209
|
+
if (input.generatedSource !== undefined) return input.generatedSource;
|
|
210
|
+
const [source, prelude] = await Promise.all([
|
|
211
|
+
input.source === undefined ? readFile(input.sourcePath, "utf8") : Promise.resolve(input.source),
|
|
212
|
+
loadPrelude(input.transport ?? "broker"),
|
|
213
|
+
]);
|
|
214
|
+
return wrapNativeFetchHandler(
|
|
215
|
+
source,
|
|
216
|
+
prelude,
|
|
217
|
+
input.vars,
|
|
218
|
+
input.bindings ?? EMPTY_BINDINGS,
|
|
219
|
+
undefined,
|
|
220
|
+
input.compatibilityDate,
|
|
221
|
+
input.appName,
|
|
222
|
+
input.assets,
|
|
223
|
+
input.transport,
|
|
224
|
+
input.versionMetadata,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Write the wrapped module to `generatedPath` (default: beside `outPath`) and return that path. */
|
|
229
|
+
async function writeGenerated(input: CompileInput): Promise<string> {
|
|
230
|
+
const generatedPath = input.generatedPath ?? resolve(dirname(input.outPath), "sprout.generated.js");
|
|
231
|
+
const generatedSource = await generateSource(input);
|
|
232
|
+
await mkdir(dirname(generatedPath), { recursive: true });
|
|
233
|
+
await writeFile(generatedPath, generatedSource);
|
|
234
|
+
return generatedPath;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Compile `sourcePath` to a native binary at `outPath` (mode 0555).
|
|
239
|
+
*
|
|
240
|
+
* With `emitOnly`, write `generatedPath` and return before any of the native
|
|
241
|
+
* compile's inputs are acquired. Generation needs only the runtime prelude and
|
|
242
|
+
* the wrapper, never Porffor itself.
|
|
243
|
+
*/
|
|
201
244
|
export async function compileSprout(input: CompileInput): Promise<void> {
|
|
245
|
+
if (input.emitOnly) {
|
|
246
|
+
await writeGenerated(input);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
202
249
|
const porffor = await ensurePorffor();
|
|
203
250
|
await ensurePorfforPatched(porffor);
|
|
204
251
|
|
|
@@ -238,29 +285,7 @@ export async function compileSprout(input: CompileInput): Promise<void> {
|
|
|
238
285
|
// OS may still be executing. The linker cannot overwrite either, so clear it
|
|
239
286
|
// first rather than failing with "can't write output file".
|
|
240
287
|
await rm(input.outPath, { force: true });
|
|
241
|
-
const generatedPath =
|
|
242
|
-
await mkdir(dirname(generatedPath), { recursive: true });
|
|
243
|
-
const generatedSource =
|
|
244
|
-
input.generatedSource ??
|
|
245
|
-
(await (async () => {
|
|
246
|
-
const [source, prelude] = await Promise.all([
|
|
247
|
-
input.source === undefined ? readFile(input.sourcePath, "utf8") : Promise.resolve(input.source),
|
|
248
|
-
loadPrelude(input.transport ?? "broker"),
|
|
249
|
-
]);
|
|
250
|
-
return wrapNativeFetchHandler(
|
|
251
|
-
source,
|
|
252
|
-
prelude,
|
|
253
|
-
input.vars,
|
|
254
|
-
input.bindings ?? EMPTY_BINDINGS,
|
|
255
|
-
undefined,
|
|
256
|
-
input.compatibilityDate,
|
|
257
|
-
input.appName,
|
|
258
|
-
input.assets,
|
|
259
|
-
input.transport,
|
|
260
|
-
input.versionMetadata,
|
|
261
|
-
);
|
|
262
|
-
})());
|
|
263
|
-
await writeFile(generatedPath, generatedSource);
|
|
288
|
+
const generatedPath = await writeGenerated(input);
|
|
264
289
|
|
|
265
290
|
const launcher = resolve(porffor, "runtime/index.js");
|
|
266
291
|
// A musl build shells `zig` bare (Porffor hardcodes the literal command), so
|
package/src/main.ts
CHANGED
|
@@ -6,11 +6,11 @@ import { isBoolean, isSafeInteger, isString, jsonObject, parseJsonValue, type Js
|
|
|
6
6
|
import type { AssetFiles } from "./assets";
|
|
7
7
|
import { parseConfig, pinBindingId, resourceRefs, type SproutboatConfig } from "./config";
|
|
8
8
|
import { validateHttpSyncSource } from "./source";
|
|
9
|
-
import { buildArtifact } from "./build";
|
|
9
|
+
import { buildArtifact, emitGenerated } from "./build";
|
|
10
10
|
import { generateTypes, TYPES_FILE } from "./types-gen";
|
|
11
11
|
import { bundleHandler, BundleError, type BundleResult } from "./bundle";
|
|
12
12
|
import { runDev } from "./dev";
|
|
13
|
-
import { buildStandalone } from "./standalone-build";
|
|
13
|
+
import { buildStandalone, emitStandalone } from "./standalone-build";
|
|
14
14
|
import { hostTarget, validateManifest, type ArtifactManifest } from "./manifest";
|
|
15
15
|
import { CLI_VERSION, printDeployReport } from "./report";
|
|
16
16
|
import { activeApiUrl, forgetToken, savedToken, saveToken } from "./credentials";
|
|
@@ -353,6 +353,33 @@ async function buildStandaloneBinary(directory: string | undefined, target: "lin
|
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
/**
|
|
357
|
+
* `build --emit-only`: run the build up to and including writing
|
|
358
|
+
* `sprout.generated.js` (prelude, bindings and handler, exactly what Porffor
|
|
359
|
+
* would compile), print its path and stop. Every source and config check still
|
|
360
|
+
* fails the build; no compiler, SQLite or BearSSL is acquired and no binary is
|
|
361
|
+
* written.
|
|
362
|
+
*/
|
|
363
|
+
async function emitOnly(directory: string | undefined, target: "linux-x86_64" | "host", standalone: boolean) {
|
|
364
|
+
const project = await readProject(directory);
|
|
365
|
+
console.log(
|
|
366
|
+
dim(`Emitting ${standalone ? "the standalone " : ""}${project.config.name} source (${target}, no native compile)…`),
|
|
367
|
+
);
|
|
368
|
+
try {
|
|
369
|
+
const input = {
|
|
370
|
+
projectDir: project.directory,
|
|
371
|
+
config: project.config,
|
|
372
|
+
sourcePath: project.sourcePath,
|
|
373
|
+
source: project.bundle.code,
|
|
374
|
+
target,
|
|
375
|
+
};
|
|
376
|
+
const { generatedPath } = standalone ? await emitStandalone(input) : await emitGenerated(input);
|
|
377
|
+
console.log(generatedPath);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
356
383
|
/** #62 — build for this machine, run it against a real broker, rebuild on save. */
|
|
357
384
|
async function dev(args: string[]) {
|
|
358
385
|
const directory = args.find((arg) => !arg.startsWith("--") && !/^\d+$/.test(arg));
|
|
@@ -1314,8 +1341,10 @@ switch (command) {
|
|
|
1314
1341
|
case "build": {
|
|
1315
1342
|
const hostBuild = args.includes("--target") && args[args.indexOf("--target") + 1] === "host";
|
|
1316
1343
|
const directory = args.find((arg) => !arg.startsWith("--") && arg !== "host");
|
|
1317
|
-
|
|
1318
|
-
|
|
1344
|
+
const target = hostBuild ? "host" : "linux-x86_64";
|
|
1345
|
+
if (args.includes("--emit-only")) await emitOnly(directory, target, args.includes("--standalone"));
|
|
1346
|
+
else if (args.includes("--standalone")) await buildStandaloneBinary(directory, target);
|
|
1347
|
+
else await build(directory, target);
|
|
1319
1348
|
break;
|
|
1320
1349
|
}
|
|
1321
1350
|
case "toolchain":
|
package/src/standalone-build.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { chmod, cp, mkdir } from "node:fs/promises";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
|
-
import { buildArtifact, type BuildInput } from "./build";
|
|
12
|
+
import { buildArtifact, emitGenerated, type BuildInput } from "./build";
|
|
13
13
|
import type { Bindings } from "./wrap";
|
|
14
14
|
|
|
15
15
|
export type StandaloneBuildInput = BuildInput & {
|
|
@@ -37,11 +37,26 @@ export function unsupportedBindings(bindings: Pick<Partial<Bindings>, "services"
|
|
|
37
37
|
return reasons;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
function assertStandaloneSupported(input: BuildInput): void {
|
|
41
41
|
const blocked = unsupportedBindings(input.config);
|
|
42
42
|
if (blocked.length > 0) {
|
|
43
43
|
throw new Error(`cannot build a standalone binary for this project:\n - ${blocked.join("\n - ")}`);
|
|
44
44
|
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `sproutboat build --standalone --emit-only`: the standalone build up to and
|
|
49
|
+
* including `sprout.generated.js`, byte for byte what a full standalone build
|
|
50
|
+
* compiles, at the same path. Every check that fails a standalone build fails
|
|
51
|
+
* this too; nothing native is acquired or compiled and no binary is copied out.
|
|
52
|
+
*/
|
|
53
|
+
export async function emitStandalone(input: BuildInput): Promise<{ generatedPath: string }> {
|
|
54
|
+
assertStandaloneSupported(input);
|
|
55
|
+
return emitGenerated({ ...input, transport: "embedded" });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function buildStandalone(input: StandaloneBuildInput): Promise<StandaloneBuildResult> {
|
|
59
|
+
assertStandaloneSupported(input);
|
|
45
60
|
const artifact = await buildArtifact({ ...input, transport: "embedded" });
|
|
46
61
|
|
|
47
62
|
// The sprout *is* the binary: assets and bindings are compiled into it, and
|
package/src/surface.ts
CHANGED
|
@@ -97,10 +97,10 @@ export const COMMANDS: readonly Command[] = [
|
|
|
97
97
|
name: "build",
|
|
98
98
|
group: "Develop",
|
|
99
99
|
emoji: "🔨",
|
|
100
|
-
args: "[project-dir] [--target host] [--standalone]",
|
|
100
|
+
args: "[project-dir] [--target host] [--standalone] [--emit-only]",
|
|
101
101
|
brief: "[project-dir]",
|
|
102
102
|
summary:
|
|
103
|
-
"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.",
|
|
103
|
+
"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. `--emit-only` stops after writing `.sproutboat/porffor-units/<shape>/sprout.generated.js`, the module Porffor would compile, and prints its path: every check still runs, nothing is compiled.",
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
name: "toolchain",
|