sproutboat 0.4.7 → 0.4.9

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 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.7 · runs on Bun (use `bunx`, not `npx`)
6
+ **Package:** `sproutboat` 0.4.9 · 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]` | 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. |
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. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutboat",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Wrangler-shaped CLI for Sproutboat. Deploys workers to any control plane via --api-url / SPROUTBOAT_API_URL.",
5
5
  "license": "MIT",
6
6
  "type": "module",
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,18 +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
- if (digest) {
268
- const response = await fetch(`${apiUrl.replace(/\/$/, "")}/api/projects/${projectName}/deployments`, { headers: { "x-api-key": token } });
269
- const deployments = parseDeploymentList(await responseText(response, "could not check existing deployments"));
270
- if (!deployments) fail("could not parse deployment list response");
271
- const active = deployments.find((deployment) => deployment.active && deployment.artifact === digest);
272
- if (active) {
273
- console.log(ok(`nothing to deploy — artifact ${digest.slice(0, 12)} is already active`));
274
- console.log(`https://${active.hostname}`);
275
- return;
276
- }
277
- }
278
254
  const form = new FormData();
279
255
  form.set("manifest", new File([await manifest.arrayBuffer()], "manifest.json", { type: "application/json" }));
280
256
  form.set("sprout", new File([await sprout.arrayBuffer()], "sprout", { type: "application/octet-stream" }));
@@ -305,6 +281,11 @@ async function deploy(args: string[]) {
305
281
  const body = await responseText(response, "deployment rejected");
306
282
  const deployed = parseUrlResponse(body);
307
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
+ }
308
289
  console.log(`\n${leaf("🌱")} ${bold(leaf(`Deployed ${projectName}`))}`);
309
290
  console.log(` ${bold(deployed.url)}`);
310
291
  if (deployed.id) console.log(dim(` version ${deployed.id}${deployed.artifact ? ` · artifact ${deployed.artifact.slice(0, 12)}` : ""}`));
package/src/surface.ts CHANGED
@@ -32,7 +32,7 @@ export const COMMANDS: readonly Command[] = [
32
32
 
33
33
  { name: "deploy", group: "Ship", emoji: "🚀",
34
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." },
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>",