Skip to content
192 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 artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.1/**
2 * Code a page cites, and whether a change touched it. Pure.
3 *
4 * A citation names a repository (`owner/name`) and a path in it: a file
5 * (`src/export.ts`), a folder (`src/export`, everything under it) or a
6 * glob (`src/**\/*.sql`, `*` within a folder, `**` across folders, `?` one
7 * character). Its kind says what at that path the page describes: the
8 * path itself, a symbol, an endpoint or an environment variable, named by
9 * its label. A page's citations come from its text (the editor's citation
10 * chips, and links to files in a repository) and from its header.
11 */
12import type { DocCitation, DocCitationKind, DocDescribes } from "@g1t/contracts";
13
14import { projectRef } from "./search.ts";
15
16const KINDS = new Set<DocCitationKind>(["path", "symbol", "endpoint", "env"]);
17const MAX_PATH = 400;
18const MAX_LABEL = 200;
19/** Citations a page keeps from its text, and in its header. */
20export const MAX_CITATIONS = 100;
21export const MAX_DESCRIBES = 20;
22
23/** A path as kept: no leading or trailing slash, no `.`/`..` parts, no doubled slashes; null when empty or unsafe. */
24export function cleanPath(path: unknown): string | null {
25 const parts = String(path ?? "")
26 .trim()
27 .replace(/\\/g, "/")
28 .split("/")
29 .filter((p) => p && p !== ".");
30 if (!parts.length || parts.some((p) => p === ".." || /[\u0000-\u001f]/.test(p))) return null;
31 const out = parts.join("/");
32 return out.length <= MAX_PATH ? out : null;
33}
34
35function isGlob(path: string): boolean {
36 return /[*?]/.test(path);
37}
38
39/** A citation as stored, or null when it names no repository or path. */
40export function cleanCitation(raw: Partial<DocCitation> | null | undefined, source: DocCitation["source"]): DocCitation | null {
41 if (!raw || typeof raw !== "object") return null;
42 const repo = projectRef(String(raw.repo ?? ""));
43 const path = cleanPath(raw.path);
44 if (!repo || !path) return null;
45 const kind = KINDS.has(raw.kind as DocCitationKind) ? (raw.kind as DocCitationKind) : "path";
46 const label = kind === "path" ? null : String(raw.label ?? "").replace(/\s+/g, " ").trim().slice(0, MAX_LABEL) || null;
47 const ref = /^[0-9a-f]{7,64}$/i.test(String(raw.ref ?? "")) ? String(raw.ref).toLowerCase() : /^[A-Za-z0-9._/-]{1,100}$/.test(String(raw.ref ?? "")) ? String(raw.ref) : null;
48 return { repo, path, kind, label, ref, source };
49}
50
51/** The header's "Describes" list as stored: valid entries, once each, at most MAX_DESCRIBES. */
52export function cleanDescribes(list: unknown): DocDescribes[] {
53 if (!Array.isArray(list)) return [];
54 const seen = new Set<string>();
55 const out: DocDescribes[] = [];
56 for (const item of list) {
57 const c = cleanCitation({ ...(item as object), kind: "path" }, "header");
58 if (!c || seen.has(`${c.repo}\n${c.path}`)) continue;
59 seen.add(`${c.repo}\n${c.path}`);
60 out.push({ repo: c.repo, path: c.path });
61 if (out.length >= MAX_DESCRIBES) break;
62 }
63 return out;
64}
65
66/** The folder a glob starts from: its parts before the first with a wildcard. */
67export function globRoot(path: string): string {
68 const parts = path.split("/");
69 const i = parts.findIndex((p) => isGlob(p));
70 return (i < 0 ? parts : parts.slice(0, i)).join("/");
71}
72
73/**
74 * Where a citation links: the file (or folder) in Code at the commit it
75 * was cited at, or the default branch (`HEAD`) when none is known. A
76 * glob links to the folder it starts from.
77 */
78export function citationHref(c: Pick<DocCitation, "repo" | "path" | "ref">): string {
79 const ref = encodeURIComponent(c.ref || "HEAD");
80 const glob = isGlob(c.path);
81 const path = (glob ? globRoot(c.path) : c.path)
82 .split("/")
83 .filter(Boolean)
84 .map(encodeURIComponent)
85 .join("/");
86 const kind = glob || !/\.[A-Za-z0-9]{1,10}$/.test(c.path) ? "tree" : "blob";
87 return `/${c.repo}/${kind}/${ref}${path ? `/${path}` : ""}`;
88}
89
90/** A citation's text in a page's Markdown: a link to the code. */
91export function citationMarkdown(c: Pick<DocCitation, "repo" | "path" | "ref" | "kind" | "label">): string {
92 const shown = c.kind !== "path" && c.label ? c.label : c.path;
93 const ticks = shown.includes("`") ? "``" : "`";
94 return `[${ticks}${shown}${ticks}](${citationHref(c)})`;
95}
96
97/**
98 * Links in Markdown to files and folders in a repository on this site:
99 * `/<owner>/<repo>/blob|tree/<ref>/<path>`, absolute or on this origin.
100 * The ref is one segment (a commit or a branch without a slash).
101 */
102export function citationsFromMarkdown(markdown: string): DocCitation[] {
103 const out: DocCitation[] = [];
104 const seen = new Set<string>();
105 const link = /\]\(\s*<?((?:https?:\/\/[^/\s)]+)?\/([A-Za-z0-9][A-Za-z0-9._-]*)\/([A-Za-z0-9._-]+)\/(?:blob|tree)\/([^/\s)#?]+)\/([^\s)#?>]+))[^)]*\)/g;
106 for (const m of String(markdown ?? "").matchAll(link)) {
107 if (m[1]!.startsWith("http") && !/^https?:\/\/([a-z0-9-]+\.)*g1t\.(sh|dev)(:\d+)?\//i.test(m[1]!)) continue;
108 let path: string;
109 let ref: string;
110 try {
111 path = m[5]!
112 .split("/")
113 .map((p) => decodeURIComponent(p))
114 .join("/");
115 ref = decodeURIComponent(m[4]!);
116 } catch {
117 continue;
118 }
119 const c = cleanCitation({ repo: `${m[2]}/${m[3]}`, path, ref: ref === "HEAD" ? null : ref, kind: "path" }, "body");
120 if (!c || seen.has(`${c.repo}\n${c.path}`)) continue;
121 seen.add(`${c.repo}\n${c.path}`);
122 out.push(c);
123 }
124 return out;
125}
126
127/**
128 * A page's citations from its text: the citation chips (`nodes`), then
129 * links to code not already a chip's own. At most MAX_CITATIONS.
130 */
131export function bodyCitations(nodes: Partial<DocCitation>[], markdown: string): DocCitation[] {
132 const out: DocCitation[] = [];
133 const seen = new Set<string>();
134 const key = (c: DocCitation) => `${c.repo}\n${c.path}\n${c.kind}\n${c.label ?? ""}`;
135 const chips = new Set<string>();
136 for (const n of nodes) {
137 const c = cleanCitation(n, "body");
138 if (!c || seen.has(key(c))) continue;
139 seen.add(key(c));
140 chips.add(citationHref(c));
141 out.push(c);
142 }
143 for (const c of citationsFromMarkdown(markdown)) {
144 // A chip's own link is the chip.
145 if (chips.has(citationHref(c)) || out.some((o) => o.repo === c.repo && o.path === c.path)) continue;
146 if (seen.has(key(c))) continue;
147 seen.add(key(c));
148 out.push(c);
149 }
150 return out.slice(0, MAX_CITATIONS);
151}
152
153/** A glob as a regular expression over a whole path. */
154function globExpression(glob: string): RegExp {
155 let out = "";
156 for (let i = 0; i < glob.length; i++) {
157 const ch = glob[i]!;
158 if (ch === "*") {
159 if (glob[i + 1] === "*") {
160 // `**/` is any folders, or none; a trailing `**` anything below.
161 if (glob[i + 2] === "/") {
162 out += "(?:.*/)?";
163 i += 2;
164 } else {
165 out += ".*";
166 i += 1;
167 }
168 } else out += "[^/]*";
169 } else if (ch === "?") out += "[^/]";
170 else out += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
171 }
172 return new RegExp(`^${out}$`);
173}
174
175/**
176 * Whether a change to `changed` touches what `cited` names: the same file,
177 * anything under a cited folder, or a path a glob matches.
178 */
179export function touches(cited: string, changed: string): boolean {
180 if (isGlob(cited)) return globExpression(cited).test(changed);
181 return changed === cited || changed.startsWith(`${cited}/`);
182}
183
184/** Of `changed` paths, those any of the citations names; at most `max`. */
185export function touchedPaths(citations: Pick<DocCitation, "path">[], changed: string[], max = 20): string[] {
186 const out: string[] = [];
187 for (const path of changed) {
188 if (citations.some((c) => touches(c.path, path))) out.push(path);
189 if (out.length >= max) break;
190 }
191 return out;
192}