sproutboat 0.4.8 → 0.4.10
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/main.ts +21 -34
- 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.10 · 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] [--no-provision]
|
|
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. 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. |
|
|
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] [--no-provision]
|
|
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/main.ts
CHANGED
|
@@ -39,22 +39,9 @@ function jsonObject(value: JsonValue): JsonObject | undefined {
|
|
|
39
39
|
return value instanceof Object && !Array.isArray(value) ? value : undefined;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
type DeploymentSummary = { artifact: string; hostname: string; active: boolean };
|
|
43
42
|
type VersionSummary = { id: string; artifact: string; deployedAt: string; active: boolean };
|
|
44
43
|
type CliAuthorization = { deviceCode: string; userCode: string; verificationUri: string; interval: number; expiresAt: string };
|
|
45
44
|
|
|
46
|
-
function parseDeploymentList(source: string): DeploymentSummary[] | undefined {
|
|
47
|
-
const value = parseJsonValue(source);
|
|
48
|
-
if (!Array.isArray(value)) return undefined;
|
|
49
|
-
const deployments: DeploymentSummary[] = [];
|
|
50
|
-
for (const item of value) {
|
|
51
|
-
const record = jsonObject(item);
|
|
52
|
-
if (!record || !isString(record.artifact) || !isString(record.hostname) || (record.active !== true && record.active !== false)) return undefined;
|
|
53
|
-
deployments.push({ artifact: record.artifact, hostname: record.hostname, active: record.active });
|
|
54
|
-
}
|
|
55
|
-
return deployments;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
45
|
function parseVersionList(source: string): VersionSummary[] | undefined {
|
|
59
46
|
const value = parseJsonValue(source);
|
|
60
47
|
if (!Array.isArray(value)) return undefined;
|
|
@@ -67,13 +54,14 @@ function parseVersionList(source: string): VersionSummary[] | undefined {
|
|
|
67
54
|
return deployments;
|
|
68
55
|
}
|
|
69
56
|
|
|
70
|
-
function parseUrlResponse(source: string): { url: string; id?: string; artifact?: string } | undefined {
|
|
57
|
+
function parseUrlResponse(source: string): { url: string; id?: string; artifact?: string; unchanged: boolean } | undefined {
|
|
71
58
|
const record = jsonObject(parseJsonValue(source));
|
|
72
59
|
if (!record || !isString(record.url)) return undefined;
|
|
73
60
|
return {
|
|
74
61
|
url: record.url,
|
|
75
62
|
id: isString(record.id) ? record.id : undefined,
|
|
76
63
|
artifact: isString(record.artifact) ? record.artifact : undefined,
|
|
64
|
+
unchanged: record.unchanged === true,
|
|
77
65
|
};
|
|
78
66
|
}
|
|
79
67
|
|
|
@@ -263,23 +251,6 @@ async function deploy(args: string[]) {
|
|
|
263
251
|
return;
|
|
264
252
|
}
|
|
265
253
|
const { apiUrl, token } = await apiCredentials();
|
|
266
|
-
const digest = artifactManifest.binaryHash.replace(/^sha256:/, "");
|
|
267
|
-
// The "already active" shortcut keys on the sprout binary hash alone. Assets
|
|
268
|
-
// and bindings.json can change while the handler stays byte-identical, so skip
|
|
269
|
-
// it whenever the artifact ships either sidecar (or the caller passed --force).
|
|
270
|
-
const hasSidecars = (await Bun.file(resolve(artifactDir, "assets.json")).exists())
|
|
271
|
-
|| (await Bun.file(resolve(artifactDir, "bindings.json")).exists());
|
|
272
|
-
if (digest && !hasSidecars && !args.includes("--force")) {
|
|
273
|
-
const response = await fetch(`${apiUrl.replace(/\/$/, "")}/api/projects/${projectName}/deployments`, { headers: { "x-api-key": token } });
|
|
274
|
-
const deployments = parseDeploymentList(await responseText(response, "could not check existing deployments"));
|
|
275
|
-
if (!deployments) fail("could not parse deployment list response");
|
|
276
|
-
const active = deployments.find((deployment) => deployment.active && deployment.artifact === digest);
|
|
277
|
-
if (active) {
|
|
278
|
-
console.log(ok(`nothing to deploy — artifact ${digest.slice(0, 12)} is already active`));
|
|
279
|
-
console.log(`https://${active.hostname}`);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
254
|
const form = new FormData();
|
|
284
255
|
form.set("manifest", new File([await manifest.arrayBuffer()], "manifest.json", { type: "application/json" }));
|
|
285
256
|
form.set("sprout", new File([await sprout.arrayBuffer()], "sprout", { type: "application/octet-stream" }));
|
|
@@ -310,6 +281,11 @@ async function deploy(args: string[]) {
|
|
|
310
281
|
const body = await responseText(response, "deployment rejected");
|
|
311
282
|
const deployed = parseUrlResponse(body);
|
|
312
283
|
if (!deployed) fail("deployment response did not include a URL");
|
|
284
|
+
if (deployed.unchanged) {
|
|
285
|
+
console.log(ok(`nothing to deploy — ${projectName} is already serving this exact artifact`));
|
|
286
|
+
console.log(` ${deployed.url}`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
313
289
|
console.log(`\n${leaf("🌱")} ${bold(leaf(`Deployed ${projectName}`))}`);
|
|
314
290
|
console.log(` ${bold(deployed.url)}`);
|
|
315
291
|
if (deployed.id) console.log(dim(` version ${deployed.id}${deployed.artifact ? ` · artifact ${deployed.artifact.slice(0, 12)}` : ""}`));
|
|
@@ -433,21 +409,32 @@ async function tail(args: string[]) {
|
|
|
433
409
|
process.stdout.write(await responseText(response, "could not read logs"));
|
|
434
410
|
}
|
|
435
411
|
|
|
436
|
-
type DomainView = {
|
|
412
|
+
type DomainView = {
|
|
413
|
+
hostname: string;
|
|
414
|
+
verified: boolean;
|
|
415
|
+
verification: { type: string; name: string; value: string } | null;
|
|
416
|
+
serverAddresses: string[];
|
|
417
|
+
warning?: string;
|
|
418
|
+
};
|
|
437
419
|
function parseDomain(source: string): DomainView | undefined {
|
|
438
420
|
const record = jsonObject(parseJsonValue(source));
|
|
439
421
|
if (!record || !isString(record.hostname) || typeof record.verified !== "boolean") return undefined;
|
|
440
422
|
const v = jsonObject(record.verification ?? null);
|
|
441
423
|
const verification = v && isString(v.type) && isString(v.name) && isString(v.value) ? { type: v.type, name: v.name, value: v.value } : null;
|
|
442
|
-
|
|
424
|
+
const serverAddresses = Array.isArray(record.serverAddresses) ? record.serverAddresses.filter(isString) : [];
|
|
425
|
+
return { hostname: record.hostname, verified: record.verified, verification, serverAddresses, warning: isString(record.warning) ? record.warning : undefined };
|
|
443
426
|
}
|
|
444
427
|
function printDomain(domain: DomainView) {
|
|
445
428
|
const status = domain.verified ? "verified" : "unverified";
|
|
446
429
|
console.log(`${status.padEnd(10)} ${domain.hostname}`);
|
|
447
430
|
if (domain.verification) {
|
|
448
|
-
console.log(
|
|
431
|
+
console.log(" add these DNS records, then run: sproutboat domains verify " + domain.hostname);
|
|
449
432
|
console.log(` ${domain.verification.type} ${domain.verification.name} "${domain.verification.value}"`);
|
|
433
|
+
if (domain.serverAddresses[0]) {
|
|
434
|
+
console.log(` A ${domain.hostname} ${domain.serverAddresses[0]} (point the hostname here, DNS-only / not proxied)`);
|
|
435
|
+
}
|
|
450
436
|
}
|
|
437
|
+
if (domain.warning) console.log(amber(` ! ${domain.warning}`));
|
|
451
438
|
}
|
|
452
439
|
|
|
453
440
|
async function domains(args: string[]) {
|
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] [--no-provision]
|
|
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
|
|
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. 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." },
|
|
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>",
|