Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

81 linesCodeBlameRaw
1/**
2 * What `#N` names, asked of the work service (`resolveRef`, the rules in
3 * lib/refs.ts), as a page to send the browser to or as the card a
4 * reference shows on hover.
5 */
6import type { RefTarget, RepoPath, Viewer } from "@g1t/contracts";
7
8import { itemPath } from "./refs";
9import { work } from "./services.server";
10
11/** Where `#number` is read: a repository, or a workspace alone. */
12export type RefScope = { repo: RepoPath } | { workspace: string };
13
14/**
15 * What a reference's card shows (components/chat/ref-chip.tsx): a work item
16 * or a pull request, as the viewer may see it, and where it is.
17 */
18export type RefCard = {
19 kind: "item" | "pull";
20 /** A pull request's repository, `owner/name`; null for a work item. */
21 repo: string | null;
22 /** The workspace a work item is numbered in, or a pull request's repository's. */
23 workspace: string;
24 number: number;
25 title: string;
26 state: string;
27 /** Its page. */
28 to: string;
29};
30
31/** What `#number` names for the viewer, read in `scope`; none when the service cannot say. */
32export async function resolveRef(viewer: Viewer, scope: RefScope, number: number): Promise<RefTarget> {
33 if (!Number.isInteger(number) || number < 1) return { kind: "none" };
34 return work.resolveRef(viewer, scope, number).catch((): RefTarget => ({ kind: "none" }));
35}
36
37/** The page a resolved reference is: a work item's or a pull request's; null for none. */
38export function refTargetPath(target: RefTarget): string | null {
39 if (target.kind === "work_item") return itemPath(target.workspace, target.number);
40 if (target.kind === "pull") return `/${target.repo}/pull/${target.number}`;
41 return null;
42}
43
44/** Where `#number`, read in `scope`, leads; null when it names nothing the viewer can see. */
45export async function refPath(viewer: Viewer, scope: RefScope, number: number): Promise<string | null> {
46 return refTargetPath(await resolveRef(viewer, scope, number));
47}
48
49/** The card for `#number` read in `scope`; null when it names nothing the viewer can see. */
50export async function refCard(viewer: Viewer, scope: RefScope, number: number): Promise<RefCard | null> {
51 const target = await resolveRef(viewer, scope, number);
52 if (target.kind === "work_item") {
53 const found = await work.getWorkItem(target.workspace, target.number, viewer).catch(() => null);
54 if (!found?.ok) return null;
55 const { item } = found.value;
56 return {
57 kind: "item",
58 repo: null,
59 workspace: item.workspace,
60 number: item.number,
61 title: item.title,
62 state: item.state === "closed" && item.reason === "not_planned" ? "not planned" : item.state,
63 to: itemPath(item.workspace, item.number),
64 };
65 }
66 if (target.kind === "pull") {
67 const [namespace = "", name = ""] = target.repo.split("/");
68 const found = await work.getPull({ namespace, name }, target.number, viewer).catch(() => null);
69 if (!found?.ok) return null;
70 return {
71 kind: "pull",
72 repo: target.repo,
73 workspace: namespace.toLowerCase(),
74 number: target.number,
75 title: found.value.pull.title,
76 state: found.value.pull.status,
77 to: `/${target.repo}/pull/${target.number}`,
78 };
79 }
80 return null;
81}