sproutboat 0.53.1 → 0.55.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.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `sproutboat domains` on the Sproutboat Cloud. The Cloud has no domains resource: an app's routes are the `hosts` of its
3
+ * spec. `list` reads them. Adding or removing a custom domain is an operator step for now (it needs a DNS record and a
4
+ * route on the Cloud's edge as well as the entry in the spec), so the other verbs say so instead of calling a route the
5
+ * Cloud does not have.
6
+ */
7
+ import { CloudError } from "./cloud";
8
+ import { makeCall, type CloudCredentials } from "./cloud-deploy";
9
+ import { isSafeInteger, isString, jsonObject } from "./json";
10
+
11
+ /** Names under this zone belong to the platform: `<project>.<team>.sproutboat.app` is every app's own route. */
12
+ export const PLATFORM_ZONE = ".sproutboat.app";
13
+
14
+ export type CloudRoute = { route: string; custom: boolean };
15
+ export type CloudRoutes = { routes: CloudRoute[]; applied: boolean | undefined; generation: number | undefined };
16
+
17
+ /** The routes of a deployed app, in the order the spec holds them, each marked custom when it is not under sproutboat.app. */
18
+ export async function listCloudRoutes(credentials: CloudCredentials, project: string): Promise<CloudRoutes> {
19
+ const result = await makeCall(credentials)({ method: "GET", path: `/v1/apps/${project}`, ok: [404] });
20
+ if (result.status === 404)
21
+ throw new CloudError(`${project} is not deployed yet, so it has no routes: run \`sproutboat deploy\` first`);
22
+ const hosts = jsonObject(result.body?.spec ?? null)?.hosts;
23
+ const routes = (Array.isArray(hosts) ? hosts.filter(isString) : []).map((route) => ({
24
+ route,
25
+ custom: !route.split("/")[0]!.endsWith(PLATFORM_ZONE),
26
+ }));
27
+ const apply = jsonObject(result.body?.apply ?? null);
28
+ const generation = result.body?.record_generation;
29
+ return {
30
+ routes,
31
+ applied: apply ? apply.state === "applied" : undefined,
32
+ generation: isSafeInteger(generation) ? generation : undefined,
33
+ };
34
+ }
35
+
36
+ /** The sentence for `add`, `verify` and `delete` against a Cloud. */
37
+ export function cloudDomainsUnsupported(sub: string, host: string): string {
38
+ const verb = sub === "delete" ? "removed" : "added";
39
+ return [
40
+ `custom domains on the Cloud are ${verb} by a Sproutboat operator for now, so \`domains ${sub}\` changes nothing here.`,
41
+ `Ask for ${host} to be routed to this app; \`sproutboat domains list\` shows what is routed, and \`sproutboat deploy\``,
42
+ "keeps every route it did not add itself, so a domain an operator routed stays through later deploys.",
43
+ ].join("\n");
44
+ }
45
+
46
+ /** The lines `domains list` prints for a Cloud app. */
47
+ export function describeCloudRoutes(project: string, listed: CloudRoutes): string[] {
48
+ if (listed.routes.length === 0) return [`${project} has no routes yet`];
49
+ const width = Math.max(...listed.routes.map((entry) => entry.route.length));
50
+ const lines = listed.routes.map((entry) =>
51
+ `${(entry.custom ? "custom" : "built-in").padEnd(9)} ${entry.route.padEnd(width)}`.trimEnd(),
52
+ );
53
+ if (listed.applied === false) lines.push("(the newest record is not applied on a host yet)");
54
+ return lines;
55
+ }