pr_01m47d15m3e54sn21z27rpy5n9/services/deployments/src/names.ts

59 lines2,390 bytesCodeBlame
1/**
2 * Where an app is served, the way Vercel names them: production at
3 * `<project>-<workspace>.g1t.page`, and a branch's preview at
4 * `<project>-git-<branch>-<workspace>.g1t.page`. The first label is also
5 * the app's script name in the dispatch namespace, so the dispatcher needs
6 * nothing but the hostname to find it. The service makes sure no two apps
7 * get the same name; this only spells them.
8 */
9
10import { DEPLOYMENTS_DOMAIN } from "@g1t/contracts";
11
12/** A hostname label is at most this long. */
13const MAX_LABEL = 63;
14
15/** Lowercase letters, digits and single hyphens, as a label allows. */
16export function clean(text: string): string {
17 return text
18 .toLowerCase()
19 .replace(/[^a-z0-9]+/g, "-")
20 .replace(/-{2,}/g, "-")
21 .replace(/^-|-$/g, "");
22}
23
24/** A short, stable fingerprint of `text`. */
25export async function fingerprint(text: string, bytes = 3): Promise<string> {
26 const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
27 return [...new Uint8Array(digest).slice(0, bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
28}
29
30/**
31 * The label for production (`branch` null) or a branch's preview. Too long
32 * for a label, the branch and then the project are shortened, and a
33 * fingerprint of the whole keeps it unique.
34 */
35export async function label(workspace: string, project: string, branch: string | null): Promise<string> {
36 const w = clean(workspace);
37 const p = clean(project);
38 const b = branch == null ? null : clean(branch);
39 const full = b == null ? `${p}-${w}` : `${p}-git-${b}-${w}`;
40 if (full.length <= MAX_LABEL) return full;
41 const tail = `-${await fingerprint(`${workspace}/${project}/${branch ?? ""}`)}-${w}`.slice(0, 40);
42 const room = MAX_LABEL - tail.length;
43 const head = b == null ? p : `${p.slice(0, Math.max(8, Math.floor(room / 2)))}-git-${b}`;
44 return `${head.slice(0, room).replace(/-+$/, "")}${tail}`;
45}
46
47/** The same label made unique, when another app already holds it. */
48export async function uniqueLabel(base: string, key: string): Promise<string> {
49 const suffix = `-${await fingerprint(key, 2)}`;
50 return `${base.slice(0, MAX_LABEL - suffix.length).replace(/-+$/, "")}${suffix}`;
51}
52
53export function appHost(script: string): string {
54 return `${script}.${DEPLOYMENTS_DOMAIN}`;
55}
56
57export function appUrl(script: string): string {
58 return `https://${appHost(script)}`;
59}