Skip to content
82 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor1/**
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
App addresses do not look like other sites: a workspace named like a domain loses that hyphen (flagon-io -> flagonio)7 * get the same name; this only spells them. A workspace ending in a domain
8 * ending is written without that hyphen (`hostWorkspace`); an app under the
9 * older spelling is moved to the new one, and its old name redirects.
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor10 */
11
12import { DEPLOYMENTS_DOMAIN } from "@g1t/contracts";
13
14/** A hostname label is at most this long. */
15const MAX_LABEL = 63;
16
17/** Lowercase letters, digits and single hyphens, as a label allows. */
18export function clean(text: string): string {
19 return text
20 .toLowerCase()
21 .replace(/[^a-z0-9]+/g, "-")
22 .replace(/-{2,}/g, "-")
23 .replace(/^-|-$/g, "");
24}
25
App addresses do not look like other sites: a workspace named like a domain loses that hyphen (flagon-io -> flagonio)26/**
27 * Domain endings a workspace's name often finishes with, as a company's
28 * domain does (`acme-com`, `flagon-io`). In a hostname they lose their
29 * hyphen: `app-acme-com.g1t.page` reads to browsers as a look-alike of
30 * acme.com, and Chrome warns the people most likely to open it, those who
31 * visit acme.com, that the page "looks fake".
32 */
33const DOMAIN_ENDINGS = new Set([
34 "com", "net", "org", "io", "co", "dev", "app", "ai", "sh", "xyz", "tech", "cloud", "so", "gg", "me",
35 "us", "uk", "de", "ca", "eu", "fr", "nl", "au", "in", "jp", "site", "page", "tools", "studio", "inc",
36]);
37
38/** The workspace as a hostname writes it: `flagon-io` as `flagonio`, `acme-co-uk` as `acmecouk`. */
39export function hostWorkspace(workspace: string): string {
40 const parts = clean(workspace).split("-");
41 let at = parts.length;
42 while (at > 1 && DOMAIN_ENDINGS.has(parts[at - 1])) at--;
43 if (at === parts.length) return parts.join("-");
44 return `${parts.slice(0, at - 1).concat(parts.slice(at - 1).join("")).join("-")}`;
45}
46
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor47/** A short, stable fingerprint of `text`. */
48export async function fingerprint(text: string, bytes = 3): Promise<string> {
49 const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
50 return [...new Uint8Array(digest).slice(0, bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
51}
52
53/**
54 * The label for production (`branch` null) or a branch's preview. Too long
55 * for a label, the branch and then the project are shortened, and a
56 * fingerprint of the whole keeps it unique.
57 */
58export async function label(workspace: string, project: string, branch: string | null): Promise<string> {
App addresses do not look like other sites: a workspace named like a domain loses that hyphen (flagon-io -> flagonio)59 const w = hostWorkspace(workspace);
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor60 const p = clean(project);
61 const b = branch == null ? null : clean(branch);
62 const full = b == null ? `${p}-${w}` : `${p}-git-${b}-${w}`;
63 if (full.length <= MAX_LABEL) return full;
64 const tail = `-${await fingerprint(`${workspace}/${project}/${branch ?? ""}`)}-${w}`.slice(0, 40);
65 const room = MAX_LABEL - tail.length;
66 const head = b == null ? p : `${p.slice(0, Math.max(8, Math.floor(room / 2)))}-git-${b}`;
67 return `${head.slice(0, room).replace(/-+$/, "")}${tail}`;
68}
69
70/** The same label made unique, when another app already holds it. */
71export async function uniqueLabel(base: string, key: string): Promise<string> {
72 const suffix = `-${await fingerprint(key, 2)}`;
73 return `${base.slice(0, MAX_LABEL - suffix.length).replace(/-+$/, "")}${suffix}`;
74}
75
76export function appHost(script: string): string {
77 return `${script}.${DEPLOYMENTS_DOMAIN}`;
78}
79
80export function appUrl(script: string): string {
81 return `https://${appHost(script)}`;
82}

This file's history is long; its oldest lines are credited to the oldest commit read.