sproutboat 0.4.6 → 0.4.7
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/SURFACE.md +3 -3
- package/package.json +1 -1
- package/src/config.ts +20 -0
- package/src/main.ts +50 -1
- package/src/report.ts +1 -1
- package/src/surface.ts +2 -2
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.
|
|
6
|
+
**Package:** `sproutboat` 0.4.7 · runs on Bun (use `bunx`, not `npx`)
|
|
7
7
|
|
|
8
8
|
## Commands
|
|
9
9
|
|
|
@@ -12,7 +12,7 @@
|
|
|
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
14
|
| `build` | `[project-dir]` | Cross-compile the native-fetch sprout (Porffor + Zig). |
|
|
15
|
-
| `deploy` | `[project-dir] [--dry-run] [--artifact <dir>] [--no-wait]` | Build (unless --artifact), print the report, upload, wait until the URL serves. --dry-run stops before upload; --no-wait skips the health check. |
|
|
15
|
+
| `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. --dry-run stops before upload; --no-wait skips the health check; --no-provision leaves id-less bindings as ephemeral deploy-scoped stores. |
|
|
16
16
|
| `versions` | `list [project-dir]` | List the project's deployed versions. |
|
|
17
17
|
| `rollback` | `<version-id> [project-dir]` | Re-activate a previous version. |
|
|
18
18
|
| `tail` | `[project-dir] [--sprout]` | Print recent request logs; --sprout prints the running sprout + broker stdout/stderr instead. |
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| `login` | `[--api-url <url>] [--token <token>]` | Device-code browser flow, or store <token> for <url> directly. |
|
|
24
24
|
|
|
25
25
|
```
|
|
26
|
-
usage: sproutboat <init [name] | check [project-dir] | build [project-dir] | deploy [project-dir] [--dry-run] [--artifact <dir>] [--no-wait] | 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>]>
|
|
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>]>
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
## Environment variables
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -14,6 +14,26 @@ export function resourceRefs(field: readonly ResourceRef[] | undefined): Array<{
|
|
|
14
14
|
return (field ?? []).map((entry) => (isString(entry) ? { binding: entry } : { binding: entry.binding, id: entry.id }));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Rewrite a bare `"BINDING"` token inside `<field>: [ … ]` to
|
|
19
|
+
* `{ "binding": "BINDING", "id": "<id>" }`, leaving the rest of the source
|
|
20
|
+
* (comments, spacing) untouched — used by `deploy`'s auto-provisioner to pin an
|
|
21
|
+
* id back into `sproutboat.jsonc`. Binding names are UPPER_SNAKE, so a plain
|
|
22
|
+
* `"BINDING"` match inside the array is unambiguous. No-op if not found.
|
|
23
|
+
*/
|
|
24
|
+
export function pinBindingId(source: string, field: string, binding: string, id: string): string {
|
|
25
|
+
const array = new RegExp(`("${field}"\\s*:\\s*\\[)([\\s\\S]*?)(\\])`);
|
|
26
|
+
// the bare token must be a whole array element — at the start of the array or
|
|
27
|
+
// right after a comma — never `"binding": "NAME"` inside an already-pinned
|
|
28
|
+
// { … } object.
|
|
29
|
+
const element = new RegExp(`(^\\s*|,\\s*)"${binding}"(\\s*,|\\s*$)`);
|
|
30
|
+
return source.replace(array, (whole, open: string, inner: string, close: string) =>
|
|
31
|
+
element.test(inner)
|
|
32
|
+
? open + inner.replace(element, `$1{ "binding": "${binding}", "id": "${id}" }$2`) + close
|
|
33
|
+
: whole,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
export type SproutboatConfig = {
|
|
18
38
|
$schema?: string;
|
|
19
39
|
name: string;
|
package/src/main.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, resolve } from "node:path";
|
|
4
|
-
import { parseConfig, type SproutboatConfig } from "./config";
|
|
4
|
+
import { parseConfig, pinBindingId, resourceRefs, type SproutboatConfig } from "./config";
|
|
5
5
|
import { validateHttpSyncSource } from "./source";
|
|
6
6
|
import { buildArtifact } from "./build";
|
|
7
7
|
import { validateManifest, type ArtifactManifest } from "./manifest";
|
|
@@ -177,6 +177,52 @@ async function build(directory?: string) {
|
|
|
177
177
|
return { project, artifact };
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
const PROVISION_FIELDS = [
|
|
181
|
+
["kv_namespaces", "kv"],
|
|
182
|
+
["d1_databases", "d1"],
|
|
183
|
+
["r2_buckets", "r2"],
|
|
184
|
+
["queues", "queue"],
|
|
185
|
+
] as const;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* #74 auto-provisioning, wrangler-style. A bare-string KV/D1/R2/queue binding
|
|
189
|
+
* with no id gets an account-level resource created (`<project>-<binding>`) on
|
|
190
|
+
* deploy, and the id is written back into `sproutboat.jsonc`. `--no-provision`
|
|
191
|
+
* skips this — those bindings then get an ephemeral deploy-scoped store.
|
|
192
|
+
*/
|
|
193
|
+
async function provisionBindings(directory = process.cwd()): Promise<void> {
|
|
194
|
+
const configPath = resolve(directory, "sproutboat.jsonc");
|
|
195
|
+
let source = await readFile(configPath, "utf8");
|
|
196
|
+
const parsed = parseConfig(source);
|
|
197
|
+
if (!parsed.ok) return; // build() re-reads and reports the config error
|
|
198
|
+
|
|
199
|
+
const bare: Array<{ field: string; kind: string; binding: string }> = [];
|
|
200
|
+
for (const [field, kind] of PROVISION_FIELDS) {
|
|
201
|
+
for (const ref of resourceRefs(parsed.value[field])) {
|
|
202
|
+
if (!ref.id) bare.push({ field, kind, binding: ref.binding });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (bare.length === 0) return;
|
|
206
|
+
|
|
207
|
+
const { apiUrl, token } = await apiCredentials();
|
|
208
|
+
for (const { field, kind, binding } of bare) {
|
|
209
|
+
const name = `${parsed.value.name}-${binding.toLowerCase().replace(/_/g, "-")}`;
|
|
210
|
+
const body = await responseText(
|
|
211
|
+
await fetch(`${apiUrl}/api/resources`, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: { "x-api-key": token, "content-type": "application/json" },
|
|
214
|
+
body: JSON.stringify({ kind, name, ifExists: "return" }),
|
|
215
|
+
}),
|
|
216
|
+
`could not provision a ${kind} resource for env.${binding}`,
|
|
217
|
+
);
|
|
218
|
+
const record = jsonObject(jsonObject(parseJsonValue(body))?.resource ?? null);
|
|
219
|
+
if (!record || !isString(record.id)) fail(`provision response for env.${binding} was not a resource`);
|
|
220
|
+
source = pinBindingId(source, field, binding, record.id);
|
|
221
|
+
console.log(ok(`provisioned ${kind} ${bold(name)} → ${record.id}`));
|
|
222
|
+
}
|
|
223
|
+
await writeFile(configPath, source);
|
|
224
|
+
}
|
|
225
|
+
|
|
180
226
|
async function deploy(args: string[]) {
|
|
181
227
|
const artifactIndex = args.indexOf("--artifact");
|
|
182
228
|
const dryRun = args.includes("--dry-run");
|
|
@@ -190,6 +236,9 @@ async function deploy(args: string[]) {
|
|
|
190
236
|
: usageError("deploy: --artifact needs a directory", "deploy [project-dir] [--dry-run] [--artifact <dir>] [--no-wait]");
|
|
191
237
|
projectName = "";
|
|
192
238
|
} else {
|
|
239
|
+
// wrangler-style: create resources for id-less bindings and pin the ids
|
|
240
|
+
// back into sproutboat.jsonc before the build bakes them into the artifact.
|
|
241
|
+
if (!args.includes("--no-provision") && !dryRun) await provisionBindings(directory);
|
|
193
242
|
const built = await build(directory);
|
|
194
243
|
projectName = built.project.config.name;
|
|
195
244
|
artifactDir = built.artifact.artifactDir;
|
package/src/report.ts
CHANGED
|
@@ -35,7 +35,7 @@ function bindingRows(config: SproutboatConfig): string[][] {
|
|
|
35
35
|
};
|
|
36
36
|
const resourceList = (refs: Parameters<typeof resourceRefs>[0], type: string) => {
|
|
37
37
|
for (const ref of resourceRefs(refs)) {
|
|
38
|
-
rows.push([`env.${ref.binding}`, type, ref.id ?? "no
|
|
38
|
+
rows.push([`env.${ref.binding}`, type, ref.id ?? "deploy-scoped store (--no-provision) — drop the flag to auto-provision a persistent resource"]);
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
resourceList(config.kv_namespaces, "kv");
|
package/src/surface.ts
CHANGED
|
@@ -31,8 +31,8 @@ export const COMMANDS: readonly Command[] = [
|
|
|
31
31
|
summary: "Cross-compile the native-fetch sprout (Porffor + Zig)." },
|
|
32
32
|
|
|
33
33
|
{ name: "deploy", group: "Ship", emoji: "🚀",
|
|
34
|
-
args: "[project-dir] [--dry-run] [--artifact <dir>] [--no-wait]", brief: "[project-dir] [--dry-run]",
|
|
35
|
-
summary: "Build (unless --artifact), print the report, upload, wait until the URL serves. --dry-run stops before upload; --no-wait skips the health check." },
|
|
34
|
+
args: "[project-dir] [--dry-run] [--artifact <dir>] [--no-wait] [--no-provision]", brief: "[project-dir] [--dry-run]",
|
|
35
|
+
summary: "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. --dry-run stops before upload; --no-wait skips the health check; --no-provision leaves id-less bindings as ephemeral deploy-scoped stores." },
|
|
36
36
|
{ name: "versions", group: "Ship", emoji: "📜", args: "list [project-dir]",
|
|
37
37
|
summary: "List the project's deployed versions." },
|
|
38
38
|
{ name: "rollback", group: "Ship", emoji: "⏮", args: "<version-id> [project-dir]", brief: "<version-id>",
|