Skip to content
381 linesCodeBlameRaw
1import type { User, Viewer } from "./identity";
2import type { RepoPath } from "./repos";
3import type { Result } from "./result";
4
5/**
6 * Deployments: a project's production, deployed from its default branch on
7 * every push, and a live preview of every branch with an open pull request.
8 * Apps run as Workers in a Workers for Platforms namespace, so an app no
9 * one visits costs nothing. A paid feature: the workspace turns it on with
10 * a monthly plan (see `Feature` in `./billing`), and each project then
11 * chooses for itself.
12 */
13
14/** The domain apps are served on. Never g1t.sh, so they share no cookies with it. */
15export const DEPLOYMENTS_DOMAIN = "g1t.page";
16
17/** A project, as deployments name it. */
18export type ProjectRef = { workspace: string; slug: string };
19
20/** A project's deployment settings. */
21export type DeploySettings = {
22 /** Whether this project deploys at all. Off until someone turns it on. */
23 enabled: boolean;
24 /** A preview for every branch with an open pull request. */
25 previews: boolean;
26 /** The default branch deployed to production on every push. */
27 production: boolean;
28 /** Runs instead of the project's own `build` script. */
29 buildCommand: string | null;
30 /** What to serve, for a static site; found by itself when null. */
31 outputDir: string | null;
32 /** A preview no one has visited in this many days is taken down. */
33 idleDays: number;
34 /**
35 * Whether it builds through the deploy workflow g1t manages for it, a
36 * workflow run on each push and pull request like any other, rather
37 * than through Deployments' own build.
38 */
39 viaWorkflow?: boolean;
40 /** Where production is served. */
41 productionUrl: string;
42 /**
43 * The project's own domain for production, once it is active: the first
44 * custom domain added that serves the app rather than redirecting. Null
45 * until one is.
46 */
47 primaryDomain: string | null;
48 /**
49 * What the last finished build found the project to be: a Workers
50 * project (it has a Workers config), a static site its build wrote, or
51 * plain HTML served as it is. Null until a build has finished. Read-only.
52 */
53 detected: DetectedKind | null;
54};
55
56export type DetectedKind = "workers" | "static" | "html";
57
58export type DeployKind = "preview" | "production";
59
60export type DeployStatus =
61 /** Waiting for a sandbox. */
62 | "queued"
63 | "building"
64 /** What its app serves now. */
65 | "ready"
66 /** Built and served, until a newer build of the same app replaced it. */
67 | "replaced"
68 /** Built and served, until its app was taken down. */
69 | "down"
70 | "failed"
71 /** Not built: the workspace's plan is off, or the build was replaced. */
72 | "skipped";
73
74/** One build of one commit, and where it went. */
75export type Deployment = {
76 id: string;
77 kind: DeployKind;
78 /** For a preview: the branch, or `pr-<n>` for a pull request from a fork. */
79 branch: string | null;
80 /** For a preview: its pull request. */
81 number: number | null;
82 commit: string;
83 status: DeployStatus;
84 url: string;
85 /** Why it failed or was skipped. */
86 error: string | null;
87 /** What the build could not provide, such as bindings not provisioned yet. */
88 warnings: string[];
89 /** How long the build ran, in seconds; charged at the container price. */
90 buildSeconds: number | null;
91 createdBy: string;
92 /** RFC 3339. */
93 createdAt: string;
94 finishedAt: string | null;
95};
96
97/** An app that is up: production, or one branch's preview. */
98export type LiveApp = {
99 kind: DeployKind;
100 branch: string | null;
101 number: number | null;
102 url: string;
103 commit: string;
104 /** RFC 3339: when it was last deployed. */
105 deployedAt: string;
106};
107
108/** One project at a glance, for the workspace's page. */
109export type ProjectDeploys = {
110 slug: string;
111 enabled: boolean;
112 production: LiveApp | null;
113 previews: number;
114 /** The newest build, whatever its status. */
115 latest: Deployment | null;
116};
117
118/** What a workspace's apps used this month. Every request, CPU millisecond and build second is metered; apps are not. */
119export type DeployUsage = {
120 /** `YYYY-MM`. */
121 month: string;
122 requests: number;
123 cpuMs: number;
124 /** Apps up now (production and previews), and the most at once this month: for information, never charged. */
125 apps: number;
126 peakApps: number;
127 buildSeconds: number;
128 /** Charged so far this month for builds, in millionths of a dollar. */
129 buildMicros: number;
130 /** RFC 3339: when requests and CPU time last went up. */
131 countedAt: string | null;
132};
133
134/** Where custom domains point: a hostname on g1t.page that routes to the dispatcher. */
135export const CUSTOM_DOMAIN_TARGET = "domains.g1t.page";
136
137/**
138 * A custom domain's state: `pending` until its DNS points at g1t (or its
139 * ownership record is found), `verifying` while its certificate is
140 * issued, then `active`. `failed` says why in `error`; `removing` is on
141 * its way out.
142 */
143export type DomainStatus = "pending" | "verifying" | "active" | "failed" | "removing";
144
145/** A DNS record the domain's owner adds at their DNS provider. */
146export type DomainRecord = {
147 /** `ALIAS` stands for a flattened CNAME at the apex, whatever the provider calls it. */
148 type: "CNAME" | "TXT" | "ALIAS";
149 /** The record's full name. */
150 name: string;
151 value: string;
152 /** What it is for, in a few words. */
153 purpose: string;
154};
155
156/** A hostname of the project's own, serving its production. */
157export type Domain = {
158 id: string;
159 hostname: string;
160 /** What it serves: `production`. */
161 target: "production";
162 status: DomainStatus;
163 /** Cloudflare's state for its certificate, as given. */
164 sslStatus: string | null;
165 /** Whether it is a registrable domain itself (`example.com`), which needs a flattened CNAME. */
166 apex: boolean;
167 /** Every record to add: where traffic goes, then any Cloudflare asks for. */
168 records: DomainRecord[];
169 /** A hostname this one redirects to (308, path and query kept), for a www/apex pair. */
170 redirectTo: string | null;
171 /** Why it is not active yet, or failed. */
172 error: string | null;
173 createdBy: string;
174 createdAt: string;
175 verifiedAt: string | null;
176};
177
178export type ProjectDomains = {
179 domains: Domain[];
180 /** The hostname every domain points at. */
181 target: string;
182 /** False until custom domains are switched on for g1t.page; `notice` says so. */
183 available: boolean;
184 notice: string | null;
185 /** What one custom domain costs a month on the plan, in millionths of a dollar: g1t's cost plus 20%. */
186 monthlyMicros: number;
187 /** The workspace's custom domains now. */
188 used: number;
189};
190
191// ---- A repository's deployments, wherever they run ---------------------
192//
193// Every deployment of a repository, in one model: g1t.page builds (read
194// from the builds above, never copied), deployments g1t Actions makes for
195// a job with an `environment:`, and deployments any other system reports
196// through the API. These travel in `snake_case` between services too, as
197// the API shows them, so a deployment's `payload` reaches the API with its
198// keys as they were given.
199
200/** Where a deployment is, as its latest status says. */
201export type DeploymentState = "queued" | "in_progress" | "success" | "failure" | "error" | "inactive";
202
203export const DEPLOYMENT_STATES: readonly DeploymentState[] = ["queued", "in_progress", "success", "failure", "error", "inactive"];
204
205/**
206 * What made a deployment: `api` (reported with a token), `actions` (a g1t
207 * Actions job with an `environment:`) or `g1t_page` (a build on g1t.page).
208 */
209export type DeploymentSource = "api" | "actions" | "g1t_page";
210
211/** One deployment of one commit to one environment. */
212export type RepoDeployment = {
213 /** `dep_…` for a reported deployment, `dpl_…` for a g1t.page build. */
214 id: string;
215 /** Such as `production`, `staging` or `preview`. */
216 environment: string;
217 /** The branch, tag or commit asked for. */
218 ref: string;
219 sha: string;
220 /** What kind of deployment: `deploy` unless given, such as `deploy:migrations`. */
221 task: string;
222 description: string | null;
223 /** Whatever the reporter attached, as given. */
224 payload: Record<string, unknown>;
225 /** An environment that goes away, such as a pull request's preview. */
226 transient_environment: boolean;
227 /** An environment people use directly. */
228 production_environment: boolean;
229 /** Its latest status's state. */
230 state: DeploymentState;
231 /** Where it is served, from its latest status that gave one. */
232 environment_url: string | null;
233 /** Where its output can be read, from its latest status that gave one. */
234 log_url: string | null;
235 /** The username that created it, or `g1t`. */
236 creator: string;
237 source: DeploymentSource;
238 /** For `actions`: the workflow run, and its address on the site. */
239 run_id: string | null;
240 run_url: string | null;
241 /** For `g1t_page`: the project it built, and a preview's pull request. */
242 project: string | null;
243 number: number | null;
244 /** RFC 3339. */
245 created_at: string;
246 /** RFC 3339: its latest status. */
247 updated_at: string;
248};
249
250/** One status of a deployment: what it said, and when. */
251export type DeploymentStatus = {
252 id: string;
253 deployment_id: string;
254 state: DeploymentState;
255 description: string | null;
256 environment_url: string | null;
257 log_url: string | null;
258 creator: string;
259 /** RFC 3339. */
260 created_at: string;
261};
262
263/** A deployment with every status it has had, oldest first. */
264export type DeploymentDetail = RepoDeployment & { statuses: DeploymentStatus[] };
265
266/** An environment: a name deployments go to, made by the first. */
267export type DeploymentEnvironment = {
268 name: string;
269 /** Where its current deployment is served. */
270 url: string | null;
271 production_environment: boolean;
272 transient_environment: boolean;
273 /** How many deployments it has had. */
274 deployments_count: number;
275 /** Its newest deployment, whatever its state. */
276 latest: RepoDeployment | null;
277 /** The newest deployment that succeeded and is still active: what it serves. */
278 current: RepoDeployment | null;
279 /** RFC 3339: its newest deployment's latest status. */
280 updated_at: string;
281};
282
283/** A repository's environments, production first, and its count of deployments. */
284export type DeploymentEnvironments = {
285 /** Deployments across every environment. */
286 total_count: number;
287 environments: DeploymentEnvironment[];
288};
289
290/** Which deployments to list. Every field narrows the list. */
291export type DeploymentFilter = {
292 environment?: string | null;
293 ref?: string | null;
294 sha?: string | null;
295 task?: string | null;
296 state?: DeploymentState | null;
297 source?: DeploymentSource | null;
298 creator?: string | null;
299 /** From 1. */
300 page?: number | null;
301 /** 1 to 100; 30 unless given. */
302 per_page?: number | null;
303};
304
305/** One page of deployments, newest first. */
306export type DeploymentPage = {
307 deployments: RepoDeployment[];
308 total_count: number;
309 page: number;
310 per_page: number;
311};
312
313/** What `create_deployment` takes, as the API does. */
314export type NewDeployment = {
315 ref: string;
316 /** The commit; resolved from `ref` when left out. */
317 sha?: string | null;
318 environment?: string | null;
319 task?: string | null;
320 description?: string | null;
321 payload?: Record<string, unknown> | null;
322 transient_environment?: boolean | null;
323 production_environment?: boolean | null;
324 /** The state of its first status: `queued` unless given. */
325 state?: DeploymentState | null;
326 environment_url?: string | null;
327 log_url?: string | null;
328};
329
330/** What `create_deployment_status` takes. */
331export type NewDeploymentStatus = {
332 state: DeploymentState;
333 description?: string | null;
334 environment_url?: string | null;
335 log_url?: string | null;
336 /**
337 * On a success: the environment's older deployments that succeeded get
338 * an `inactive` status. True unless given.
339 */
340 auto_inactive?: boolean | null;
341};
342
343export interface DeploymentsApi {
344 /** Members of the workspace only. */
345 settings(project: ProjectRef, viewer: Viewer): Promise<Result<DeploySettings>>;
346 /** Members only. Turning deployments on needs the workspace's plan. */
347 updateSettings(actor: User, project: ProjectRef, changes: Partial<DeploySettings>): Promise<Result<DeploySettings>>;
348 /** The newest builds first, and what is up now. Members only. */
349 list(project: ProjectRef, viewer: Viewer): Promise<Result<{ deployments: Deployment[]; live: LiveApp[] }>>;
350 /** One build, with its log. Members only. */
351 get(project: ProjectRef, id: string, viewer: Viewer): Promise<Result<Deployment & { log: string | null }>>;
352 /** Builds production (`branch` null), or a branch's preview, again from its head. */
353 redeploy(actor: User, project: ProjectRef, branch: string | null): Promise<Result<Deployment>>;
354 /** Takes production (`branch` null), or a branch's preview, down now. */
355 takeDown(actor: User, project: ProjectRef, branch: string | null): Promise<Result<true>>;
356 /** Every project of a workspace at a glance. Members only. */
357 overview(workspace: string, viewer: Viewer): Promise<Result<ProjectDeploys[]>>;
358 /** What the workspace's apps used this month. Members only. */
359 usage(workspace: string, viewer: Viewer): Promise<Result<DeployUsage>>;
360 /** The project's custom domains. Members only. */
361 domains(project: ProjectRef, viewer: Viewer): Promise<Result<ProjectDomains>>;
362 /**
363 * Adds a custom domain for production. With `twin`, its www or apex twin
364 * is added too, redirecting to it. Members only; needs the Deployments plan.
365 */
366 addDomain(actor: User, project: ProjectRef, hostname: string, options?: { twin?: boolean }): Promise<Result<Domain[]>>;
367 /** Removes a custom domain, and any domain redirecting to it. Members only. */
368 removeDomain(actor: User, project: ProjectRef, id: string): Promise<Result<true>>;
369 /** Asks Cloudflare to check the domain again now. Members only. */
370 refreshDomain(actor: User, project: ProjectRef, id: string): Promise<Result<Domain>>;
371 /** A repository's deployments, newest first, filtered. Anyone who can read it. */
372 repoDeployments(repo: RepoPath, viewer: Viewer, filter?: DeploymentFilter): Promise<Result<DeploymentPage>>;
373 /** One deployment with its statuses, oldest first. Anyone who can read the repository. */
374 repoDeployment(repo: RepoPath, id: string, viewer: Viewer): Promise<Result<DeploymentDetail>>;
375 /** A repository's environments with their current and latest deployments. Anyone who can read it. */
376 environments(repo: RepoPath, viewer: Viewer): Promise<Result<DeploymentEnvironments>>;
377 /** Reports a deployment. Takes the Write role. */
378 createDeployment(actor: User, repo: RepoPath, input: NewDeployment): Promise<Result<DeploymentDetail>>;
379 /** Adds a status to a reported deployment. Takes the Write role. */
380 createDeploymentStatus(actor: User, repo: RepoPath, id: string, input: NewDeploymentStatus): Promise<Result<DeploymentStatus>>;
381}