Skip to content

g1t/packages/contracts/src/projects.ts

225 lines10,201 bytesCodeBlame
1import type { User, Viewer } from "./identity";
2import type { RepoPath } from "./repos";
3import type { Result } from "./result";
4
5/**
6 * Projects: the thing a workspace builds and runs. A project has exactly
7 * one source, where its code lives; everything about running it
8 * (deployments, environments, domains, secrets and variables) belongs to
9 * the project, while branches, pull requests and review stay with the
10 * repository. One repository may carry several projects, each from its own
11 * root directory. Every repository on g1t gets a project of its own name.
12 */
13
14/** Where a project's code lives. */
15export type ProjectSource =
16 /** A repository hosted on g1t. */
17 | { kind: "hosted"; repoId: string; repo: RepoPath; rootDir: string; defaultBranch: string }
18 /** Mirrored from another host; coming next. */
19 | { kind: "mirror"; provider: "github" | "gitlab" | "bitbucket"; url: string; rootDir: string };
20
21export type Project = {
22 id: string;
23 /** The workspace's slug. */
24 workspace: string;
25 /** Unique in its workspace; the project's address is `g1t.sh/<workspace>/<slug>`. */
26 slug: string;
27 name: string;
28 /** The project's own description, or its repository's while it has none. */
29 description: string | null;
30 /** Whether `description` is its repository's, following it as it changes. */
31 descriptionInherited: boolean;
32 source: ProjectSource;
33 /** Whether its repository is private: only people with a role on it can see it. */
34 private: boolean;
35 /** Whether its repository is archived: read-only, kept for reference. */
36 archived: boolean;
37 /** Whether it is the project its repository's workflows read secrets from. */
38 primary: boolean;
39 /** What a person set it to be; each part null while it is left to detection. */
40 setting: ProjectSetting;
41 /** What it is: from `setting`, or for what is left out, from its deployments, packages and files. */
42 kind: ProjectKind;
43 /** Why it is that kind. */
44 kindReason: KindReason;
45 /**
46 * Where it runs: `g1t` when g1t deploys it (Deployments, on g1t.page),
47 * `elsewhere` when it is deployed by other means, at `productionUrl`.
48 * Null for what is not deployed (a library, a tool) and for an app nobody
49 * has said yet while Deployments are off.
50 */
51 runs: ProjectRuns | null;
52 /** Where production is when it runs elsewhere; null when nobody has given one. */
53 productionUrl: string | null;
54 /** What detection decides, whatever the setting is, to show beside it. */
55 detected: { kind: ProjectKind; reason: KindReason };
56 /** The ecosystem its files say it publishes to, for how to publish; null when none says. */
57 ecosystem: ProjectEcosystem | null;
58 /** Its homepage, docs and other links, shown wherever the project is. */
59 links: ProjectLinks;
60 createdBy: string;
61 /** RFC 3339. */
62 createdAt: string;
63 updatedAt: string;
64 /** RFC 3339: when its repository was last pushed to; null until it is, after projects began keeping it. */
65 pushedAt: string | null;
66 /**
67 * How active it is lately: each push, issue or pull request opened or
68 * closed, review, comment and deployment counts one, halving every week.
69 * 0 for none.
70 */
71 activity: number;
72};
73
74/**
75 * What a project is. An app (or a site) runs somewhere and gets a
76 * production card; a library is published and installed; a tool, such as
77 * a CLI, ships as releases people install; docs are documentation or site
78 * content; other is anything else, such as configuration or research.
79 */
80export type ProjectKind = "app" | "library" | "tool" | "docs" | "other";
81
82/** Every kind, in the order pages offer them. */
83export const PROJECT_KINDS: readonly ProjectKind[] = ["app", "library", "tool", "docs", "other"];
84
85/** Where an app or a site runs: deployed by g1t on g1t.page, or deployed by other means. */
86export type ProjectRuns = "g1t" | "elsewhere";
87
88/** What a person set; null for each part left to detection. */
89export type ProjectSetting = { kind: ProjectKind | null; runs: ProjectRuns | null };
90
91/** One of a project's own links: a label and an http(s) address. */
92export type ProjectLink = { label: string; url: string };
93
94export type ProjectLinks = {
95 /** Its homepage: its own, or its repository's website while it has none. */
96 homepage: string | null;
97 /** Whether `homepage` is its repository's website, following it as it changes. */
98 homepageInherited: boolean;
99 /** Where its documentation is read. */
100 docs: string | null;
101 /** Any others, in the order given, at most `MAX_PROJECT_LINKS`. */
102 custom: ProjectLink[];
103};
104
105/** How many links of its own a project keeps besides its homepage and docs. */
106export const MAX_PROJECT_LINKS = 10;
107/** The longest link label. */
108export const MAX_LINK_LABEL = 40;
109/** The longest link address. */
110export const MAX_LINK_URL = 255;
111
112/** A change to a project; only what is given changes. */
113export type ProjectChanges = {
114 name?: string;
115 /** Null or blank goes back to the repository's. */
116 description?: string | null;
117 rootDir?: string;
118 /** What it is; `auto` leaves it to detection. Anything but an app or docs stops it running anywhere. */
119 kind?: ProjectKind | "auto";
120 /** Where it runs; `auto` leaves it to Deployments. Setting it makes it an app unless it is docs. */
121 runs?: ProjectRuns | "auto";
122 /** Production's address when it runs elsewhere; null or blank clears it. */
123 productionUrl?: string | null;
124 /** Null or blank goes back to the repository's website. */
125 homepage?: string | null;
126 /** Null or blank clears it. */
127 docsUrl?: string | null;
128 /** Replaces its other links. */
129 links?: ProjectLink[];
130};
131
132/**
133 * What decided the kind: the setting, Deployments being on, a package its
134 * repository publishes, its files, or nothing (an app, by default).
135 * `detail` says it in a sentence.
136 */
137export type KindReason = { by: "set" | "deployments" | "packages" | "files" | "default"; detail: string };
138
139/** Where a library's files say it is published. */
140export type ProjectEcosystem = "composer" | "npm" | "cargo" | "go" | "python";
141
142export type NewProject = {
143 name: string;
144 description?: string | null;
145 /** The hosted repository it builds from. */
146 repo: RepoPath;
147 /** Where in the repository it lives; empty for the whole repository. */
148 rootDir?: string;
149};
150
151/** One end of a dependency, as a page shows it. */
152export type DependencyLink = {
153 slug: string;
154 name: string;
155 /** The variable carrying the other project's address, such as `API_URL`. */
156 as: string | null;
157 /** Declared on the site, or in the project's `.g1t/project.yml`. */
158 source: "ui" | "file";
159};
160
161/** What a project uses, and what uses it. */
162export type Dependencies = { dependsOn: DependencyLink[]; usedBy: DependencyLink[] };
163
164/** A project's dependencies by id, for services. */
165export type ProjectGraph = {
166 dependsOn: { id: string; slug: string; workspace: string; as: string | null }[];
167 usedBy: { id: string; slug: string; workspace: string; as: string | null }[];
168};
169
170/**
171 * What a person keeps at hand in a workspace: the projects they pinned, in
172 * their order, then the ones they opened last that they have not pinned.
173 * Only projects they can still see.
174 */
175export type ProjectShortcuts = { pinned: Project[]; recent: Project[] };
176
177export interface ProjectsApi {
178 /** A workspace's projects, by name. Members, or anyone for public repositories. */
179 list(workspace: string, viewer: Viewer): Promise<Result<Project[]>>;
180 get(workspace: string, slug: string, viewer: Viewer): Promise<Result<Project>>;
181 /** The projects built from a repository, its primary one first. For services. */
182 byRepo(repoId: string): Promise<Project[]>;
183 /** Members only. */
184 create(actor: User, workspace: string, input: NewProject): Promise<Result<Project>>;
185 /**
186 * Members with a role that may change its settings. Making it something
187 * that does not run (a library, a tool, other) while Deployments are on
188 * is refused: they are turned off first.
189 */
190 update(actor: User, workspace: string, slug: string, changes: ProjectChanges): Promise<Result<Project>>;
191 /** For deployments: Deployments were turned on or off for the project. */
192 deploymentsChanged(projectId: string, enabled: boolean): Promise<void>;
193 /** What a project uses and what uses it. Whoever may see the project. */
194 dependencies(workspace: string, slug: string, viewer: Viewer): Promise<Result<Dependencies>>;
195 /**
196 * `slug` uses `on`, with `as` the variable that carries `on`'s address.
197 * Members only; refused if it would make a cycle.
198 */
199 addDependency(actor: User, workspace: string, slug: string, on: string, as: string | null): Promise<Result<Dependencies>>;
200 /** Members only. A dependency from `.g1t/project.yml` is changed there. */
201 removeDependency(actor: User, workspace: string, slug: string, on: string): Promise<Result<Dependencies>>;
202 /** For services: a project's dependencies by id. */
203 graph(projectId: string): Promise<ProjectGraph>;
204 /** A person's pinned and recent projects in a workspace. Empty for anyone else. */
205 shortcuts(workspace: string, viewer: Viewer): Promise<ProjectShortcuts>;
206 /**
207 * Pins a project the person can see, at `position` (0 first) or at the
208 * end; pinning one already pinned moves it. A person's own, at most 8 a
209 * workspace. Returns their pins, in order.
210 */
211 pin(actor: User, workspace: string, slug: string, position?: number | null): Promise<Result<Project[]>>;
212 /** Unpins it. Returns their pins, in order. */
213 unpin(actor: User, workspace: string, slug: string): Promise<Result<Project[]>>;
214 /** Puts their pins in this order: every pinned project's slug, once. */
215 reorderPins(actor: User, workspace: string, slugs: string[]): Promise<Result<Project[]>>;
216 /** The person opened the project: it leads their recent ones. */
217 visited(actor: User, projectId: string): Promise<void>;
218 /**
219 * For lists of public repositories, such as Explore: each one's own
220 * project's address to show, by `namespace/name` in lower case:
221 * production when it is deployed elsewhere, else its homepage, else its
222 * docs. Repositories without one are left out. At most 100 asked at once.
223 */
224 publicLinks(repos: RepoPath[]): Promise<Record<string, string>>;
225}