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.

g1t.sh
527 linesCodeBlameRaw
1import type { User, Viewer } from "./identity";
2import type { RepoPath } from "./repos";
3import type { Result } from "./result";
4
5/**
6 * GitHub Actions workflows, run on g1t as they are. Mirrors
7 * `crates/contracts/src/actions.rs`.
8 */
9
10export type WorkflowNote = {
11 severity: "info" | "warning" | "unsupported";
12 job: string | null;
13 message: string;
14};
15
16/** One `workflow_dispatch` input, as written in the workflow. */
17export type DispatchInput = {
18 description?: string;
19 required?: boolean;
20 default?: string | number | boolean;
21 type?: "string" | "boolean" | "number" | "choice" | "environment";
22 options?: string[];
23};
24
25/**
26 * `action_required`: a pull request's run from outside, waiting for someone
27 * with the Write role to approve it. `waiting`: its jobs are held by an
28 * environment's protection rules (a run's detail says so; lists do not).
29 */
30export type RunStatus = "pending" | "action_required" | "queued" | "in_progress" | "waiting" | "completed";
31export type Conclusion = "success" | "failure" | "cancelled" | "skipped";
32
33export type WorkflowRun = {
34 id: string;
35 workflowId: string;
36 path: string;
37 name: string;
38 title: string;
39 number: number;
40 attempt: number;
41 event: string;
42 ref: string;
43 sha: string;
44 pull: number | null;
45 status: RunStatus;
46 conclusion: Conclusion | null;
47 error: string | null;
48 actor: string | null;
49 createdAt: string;
50 startedAt: string | null;
51 finishedAt: string | null;
52};
53
54export type Workflow = {
55 id: string;
56 path: string;
57 name: string;
58 events: string[];
59 state: "active" | "disabled";
60 error: string | null;
61 notes: WorkflowNote[];
62 dispatch: Record<string, DispatchInput> | null;
63 lastRun: WorkflowRun | null;
64 /** For a managed workflow (`managed/<name>.yml`): what installed it. */
65 managedBy?: ManagedBy | null;
66};
67
68/** What installed a managed workflow in a repository. */
69export type ManagedBy = {
70 /** `g1t` for one g1t's services installed, or the workspace's name. */
71 origin: string;
72 /** A workspace's: `required` or `default`; empty for g1t's. */
73 mode: "required" | "default" | "";
74 /** A workspace's: the file it was copied from, `owner/repo/path@ref`. */
75 source: string | null;
76};
77
78/**
79 * A workflow a workspace's owners added to its repositories: it runs in
80 * each repository it targets as that repository's own, under
81 * `managed/<name>.yml`, from a copy of its source taken when it was saved.
82 */
83export type WorkspaceManagedWorkflow = {
84 name: string;
85 path: string;
86 /** Its `name:`, as repositories list it. */
87 workflowName: string;
88 /** `owner/repo/.g1t/workflows/file.yml@ref`. */
89 source: string;
90 sourceSha: string | null;
91 /** A private source runs in private repositories only. */
92 sourcePrivate: boolean;
93 mode: "required" | "default";
94 /** Names and patterns; empty is every repository. */
95 repositories: string[];
96 visibility: "all" | "public" | "private";
97 content: string;
98 createdAt: string;
99 updatedAt: string;
100 updatedBy: string;
101};
102
103export type WorkspaceManagedWorkflowChange = {
104 source?: string;
105 mode?: "required" | "default";
106 repositories?: string[];
107 visibility?: "all" | "public" | "private";
108 /** Copy the source again. */
109 refresh?: boolean;
110};
111
112export type StepState = {
113 number: number;
114 name: string;
115 status: "queued" | "in_progress" | "completed";
116 conclusion: Conclusion | null;
117 startedAt: string | null;
118 finishedAt: string | null;
119};
120
121export type Annotation = {
122 level: "error" | "warning" | "notice";
123 message: string;
124 title: string | null;
125 file: string | null;
126 line: number | null;
127};
128
129export type Job = {
130 id: string;
131 runId: string;
132 key: string;
133 name: string;
134 needs: string[];
135 /**
136 * `calling`: running the reusable workflow it calls, whose jobs follow it.
137 * `pending`: held by its environment's protection rules; `reason` says for what.
138 */
139 status: "waiting" | "pending" | "queued" | "in_progress" | "calling" | "completed";
140 conclusion: Conclusion | null;
141 steps: StepState[];
142 annotations: Annotation[];
143 reason: string | null;
144 startedAt: string | null;
145 finishedAt: string | null;
146 /** The environment it names, once its needs are done. */
147 environment?: string | null;
148 /** Its `runs-on` names self-hosted runners. */
149 selfHosted?: boolean;
150 /** The self-hosted runner that took it, by name. */
151 runner?: string | null;
152 /** Cancelled, and running its `if: always()` and `cancelled()` steps and post steps before it ends. */
153 cancelling?: boolean;
154 /** Where its deployment is (`environment.url`), for a job that deploys and says; the current attempt's only. */
155 environmentUrl?: string | null;
156 /** For a job that calls a reusable workflow: that workflow's file. Its jobs' keys are this job's key, `/`, their own. */
157 uses?: string | null;
158};
159
160/** One attempt of a run: the first, or a re-run. */
161export type RunAttempt = {
162 attempt: number;
163 status: RunStatus;
164 conclusion: Conclusion | null;
165 /** Who started it: whoever caused the run, then whoever re-ran it. */
166 actor: string | null;
167 /** It ran with debug logging. */
168 debug: boolean;
169 startedAt: string | null;
170 finishedAt: string | null;
171};
172
173/** What a job's steps wrote to `$GITHUB_STEP_SUMMARY`, in Markdown, masked. */
174export type JobSummary = { jobId: string; name: string; steps: { step: number; markdown: string }[] };
175
176/** A job's whole log, to download. `omitted`: left out of a run's logs that grew too large. */
177export type JobLogText = {
178 jobId: string;
179 name: string;
180 steps: StepState[];
181 chunks: LogChunk[];
182 done: boolean;
183 omitted?: boolean;
184};
185
186export type RunDetail = {
187 run: WorkflowRun;
188 jobs: Job[];
189 notes: WorkflowNote[];
190 /** For a pull request's run from outside: whether it waits for, or had, approval. */
191 approval?: RunApproval | null;
192 /** The environments whose protection rules hold its jobs, this attempt. */
193 pendingDeployments?: PendingDeployment[];
194 /** Every attempt, oldest first, the one shown (`run.attempt`) included. */
195 attempts?: RunAttempt[];
196};
197
198export type RunApproval = {
199 state: "required" | "approved";
200 /** Why it waits, in words. */
201 reason: string;
202 approvedBy: string | null;
203};
204
205/** One person or team who may approve an environment's jobs. */
206export type EnvironmentReviewer = { type: "user" | "team"; name: string };
207
208/** A branch or tag pattern an environment takes deployments from. */
209export type BranchPattern = { name: string; type: "branch" | "tag" };
210
211/** The most reviewers an environment may have. */
212export const MAX_ENVIRONMENT_REVIEWERS = 6;
213/** The longest wait timer, in minutes (30 days). */
214export const MAX_WAIT_MINUTES = 43_200;
215
216/**
217 * An environment and its protection rules. Jobs naming it with
218 * `environment:` wait until the rules let them through, and only then get
219 * its secrets.
220 */
221export type Environment = {
222 /** Lowercase. */
223 name: string;
224 reviewers: EnvironmentReviewer[];
225 preventSelfReview: boolean;
226 waitMinutes: number;
227 /** `protected`: branches the rules protect; `selected`: `branchPatterns`. */
228 branchPolicy: "all" | "protected" | "selected";
229 branchPatterns: BranchPattern[];
230 adminsBypass: boolean;
231 /** Whether it has rules saved; false for one only named by a workflow or a secret. */
232 protected: boolean;
233 updatedAt: string | null;
234 updatedBy: string | null;
235};
236
237/** What changes an environment's rules; left out is unchanged. */
238export type EnvironmentChange = Partial<
239 Pick<Environment, "reviewers" | "preventSelfReview" | "waitMinutes" | "branchPolicy" | "branchPatterns" | "adminsBypass">
240>;
241
242/** An environment holding a run's jobs, and where its rules stand. */
243export type PendingDeployment = {
244 environment: string;
245 state: "waiting" | "approved" | "rejected";
246 needsReview: boolean;
247 /** When its wait timer lets its jobs start. */
248 waitUntil: string | null;
249 reviewers: EnvironmentReviewer[];
250 /** The jobs it holds, by name. */
251 jobs: string[];
252 /** Whether the viewer may approve or reject it now. */
253 canReview: boolean;
254 reviewedBy: string | null;
255 comment: string | null;
256 reviewedAt: string | null;
257};
258
259/** Which pull requests' runs wait for approval, least strict first. */
260export const APPROVAL_POLICIES = ["first_time_contributors", "outside_contributors", "all_external_contributors"] as const;
261export type ApprovalPolicy = (typeof APPROVAL_POLICIES)[number];
262
263/** A repository's choices for its workflows. */
264export type ActionsSettings = {
265 /**
266 * What a workflow without `permissions:` gets. Unchosen, a repository made
267 * before restricted tokens keeps `write`; a newer one takes its
268 * workspace's default. Never more than `maxPermissions`.
269 */
270 defaultPermissions: "read" | "write";
271 /** Whether the repository chose it. */
272 defaultChosen: boolean;
273 /** The most the workspace lets a repository's default be. */
274 maxPermissions: "read" | "write";
275 approvalPolicy: ApprovalPolicy;
276 /** "Allow g1t Actions to create and approve pull requests". */
277 canApprovePullRequests: boolean;
278 /** Whether the workspace lets its repositories turn that on. */
279 workspaceAllowsPullRequests: boolean;
280 /**
281 * Who may use the repository's actions and reusable workflows while it is
282 * private: only itself (`none`), or private repositories of its workspace
283 * (`organization`). A public repository's are anyone's.
284 */
285 accessLevel: ActionsAccessLevel;
286 /**
287 * Whether the repository's workflows run at all. Unchosen, on for a
288 * repository and off for a fork (`parentId` set), so a fork starts no
289 * workflows until someone turns them on. While off, no run starts from any
290 * event, schedule or dispatch.
291 */
292 enabled: boolean;
293 /** Whether the repository chose `enabled`. */
294 enabledChosen: boolean;
295};
296
297/** Settings, Actions, Access. */
298export const ACTIONS_ACCESS_LEVELS = ["none", "organization"] as const;
299export type ActionsAccessLevel = (typeof ACTIONS_ACCESS_LEVELS)[number];
300
301/** What changes a repository's choices; `inherit` unchooses its default. */
302export type ActionsSettingsChange = {
303 defaultPermissions?: "read" | "write" | "inherit";
304 approvalPolicy?: ApprovalPolicy;
305 canApprovePullRequests?: boolean;
306 accessLevel?: ActionsAccessLevel;
307 /** Turns the repository's workflows on or off. */
308 enabled?: boolean;
309};
310
311/** A workspace's policy for its repositories' job tokens. */
312export type WorkspaceActionsSettings = {
313 /** What a repository made from now on gets by default. */
314 defaultPermissions: "read" | "write";
315 /** The most any repository's default may be. */
316 maxPermissions: "read" | "write";
317 canApprovePullRequests: boolean;
318};
319
320export type LogChunk = { seq: number; step: number; text: string };
321export type JobLog = { chunks: LogChunk[]; done: boolean };
322
323/**
324 * Who may read a secret or variable: `workflows` (`secrets.*`, `vars.*` in
325 * GitHub Actions) and `deployments` (a deploy build's environment and the
326 * running app's bindings). Agents, checks and the merge queue never read
327 * any.
328 */
329export type SettingReader = "workflows" | "deployments";
330
331/**
332 * One row of secrets and variables, as Vercel lists environment variables:
333 * a key, its type, the environments it applies to and who reads it. A key
334 * may have one row per environment. Secrets' values are never returned.
335 */
336export type Setting = {
337 id: string;
338 name: string;
339 /** `variable` is shown as Config. Config may become a secret, never back. */
340 kind: SettingKind;
341 /** A variable's value. */
342 value: string | null;
343 /** A project's (a repository's belong to its project) or the workspace's. */
344 scope: "project" | "workspace";
345 updatedAt: string;
346 availableTo: SettingReader[];
347 /** The environments it applies to; empty is every environment. */
348 environments: string[];
349 /** A workspace's row: the projects it reaches, by slug; empty is every one. */
350 projects: string[];
351 /** Where to rotate it, or who to ask. */
352 note: string | null;
353 updatedBy: string | null;
354 /**
355 * A brokered secret's hosts (`api.example.com`, `*.example.com`): jobs get
356 * a stand-in in place of its value, and g1t puts the real value on their
357 * requests to these hosts as they leave the sandbox. Empty when it is not
358 * brokered. Absent from a service older than brokering.
359 */
360 brokeredHosts?: string[];
361};
362
363/**
364 * A stand-in a running job was given for a brokered secret (`g1tsec_` and
365 * 48 hex characters), as the actions service's `brokered_secrets` answers
366 * the runner Worker, which fills in `value` on the job's requests to
367 * `hosts`.
368 */
369export type BrokeredSecret = { standIn: string; name: string; value: string; hosts: string[] };
370
371/** What saving a row sets beyond its value; left out is unchanged. */
372export type SettingOptions = {
373 /** The row to change; left out, the key's row for every environment. */
374 id?: string;
375 availableTo?: SettingReader[];
376 environments?: string[];
377 /** A workspace's row: project slugs; empty for every one. */
378 projects?: string[];
379 note?: string;
380 /** A secret's brokered hosts; empty stops brokering it. */
381 brokeredHosts?: string[];
382};
383
384export type SettingsOwner = { repo: RepoPath } | { workspace: string };
385export type SettingKind = "secret" | "variable";
386/** `all` lists both. */
387export type SettingKindFilter = SettingKind | "all";
388
389export type RunsFilter = {
390 workflow?: string;
391 branch?: string;
392 event?: string;
393 pull?: number;
394 sha?: string;
395 limit?: number;
396};
397
398export interface ActionsApi {
399 workflows(repo: RepoPath, viewer: Viewer): Promise<Result<Workflow[]>>;
400 runs(repo: RepoPath, viewer: Viewer, filter?: RunsFilter): Promise<Result<WorkflowRun[]>>;
401 /** The latest attempt, or an earlier one. */
402 run(repo: RepoPath, viewer: Viewer, id: string, attempt?: number): Promise<Result<RunDetail>>;
403 logs(repo: RepoPath, viewer: Viewer, job: string, after?: number): Promise<Result<JobLog>>;
404 /** The job summaries of an attempt (the latest by default), jobs without one left out. */
405 summaries(repo: RepoPath, viewer: Viewer, id: string, attempt?: number): Promise<Result<JobSummary[]>>;
406 /** A job's whole log, any attempt's, by the id the run gave the job. */
407 jobLogText(repo: RepoPath, viewer: Viewer, job: string): Promise<Result<JobLogText>>;
408 /** Every job's whole log for an attempt, to download as one archive. */
409 runLogs(repo: RepoPath, viewer: Viewer, id: string, attempt?: number): Promise<Result<JobLogText[]>>;
410 dispatch(
411 actor: User,
412 repo: RepoPath,
413 workflow: string,
414 ref: string | undefined,
415 inputs: Record<string, unknown>,
416 ): Promise<Result<WorkflowRun>>;
417 /** Running jobs clean up first; `force` (or cancelling a run that is cancelling) stops them outright. */
418 cancel(actor: User, repo: RepoPath, id: string, force?: boolean): Promise<Result<WorkflowRun>>;
419 /** A new attempt: every job, the failed ones, or `job` and what needs it; `debug` for debug logging. */
420 rerun(
421 actor: User,
422 repo: RepoPath,
423 id: string,
424 failedOnly?: boolean,
425 options?: { job?: string; debug?: boolean },
426 ): Promise<Result<WorkflowRun>>;
427 setWorkflowEnabled(actor: User, repo: RepoPath, workflow: string, enabled: boolean): Promise<Result<Workflow>>;
428 settings(actor: User, owner: SettingsOwner, kind: SettingKindFilter): Promise<Result<Setting[]>>;
429 /** `value` null keeps an existing entry's default value. */
430 setSetting(
431 actor: User,
432 owner: SettingsOwner,
433 kind: SettingKind,
434 name: string,
435 value: string | null,
436 options?: SettingOptions,
437 ): Promise<Result<Setting>>;
438 /** One row by `id`, or every row of the key. */
439 deleteSetting(actor: User, owner: SettingsOwner, kind: SettingKindFilter, name: string, id?: string): Promise<Result<boolean>>;
440 /** Lets a pull request's run from outside start. Write role. */
441 approveRun(actor: User, repo: RepoPath, id: string): Promise<Result<WorkflowRun>>;
442 pendingDeployments(repo: RepoPath, viewer: Viewer, id: string): Promise<Result<PendingDeployment[]>>;
443 /** Approves or rejects the jobs `environments` hold (every waiting one when empty). */
444 reviewDeployments(
445 actor: User,
446 repo: RepoPath,
447 id: string,
448 state: "approved" | "rejected",
449 environments?: string[],
450 comment?: string,
451 ): Promise<Result<PendingDeployment[]>>;
452 actionsSettings(repo: RepoPath, viewer: Viewer): Promise<Result<ActionsSettings>>;
453 /** Admin role. Left out is unchanged. */
454 setActionsSettings(actor: User, repo: RepoPath, change: ActionsSettingsChange): Promise<Result<ActionsSettings>>;
455 /** Members. */
456 workspaceActionsSettings(workspace: string, viewer: Viewer): Promise<Result<WorkspaceActionsSettings>>;
457 /** Owners. Left out is unchanged. */
458 setWorkspaceActionsSettings(
459 actor: User,
460 workspace: string,
461 change: Partial<WorkspaceActionsSettings>,
462 ): Promise<Result<WorkspaceActionsSettings>>;
463 /** Owners. */
464 workspaceManagedWorkflows(workspace: string, viewer: Viewer): Promise<Result<WorkspaceManagedWorkflow[]>>;
465 /** Owners. `create` adds one, which needs `source` and `mode`; otherwise left out is unchanged. */
466 setWorkspaceManagedWorkflow(
467 actor: User,
468 workspace: string,
469 name: string,
470 change: WorkspaceManagedWorkflowChange,
471 create?: boolean,
472 ): Promise<Result<WorkspaceManagedWorkflow>>;
473 /** Owners. */
474 removeWorkspaceManagedWorkflow(actor: User, workspace: string, name: string): Promise<Result<boolean>>;
475 /** Every environment the repository's rules, secrets, workflows or jobs name. */
476 environments(repo: RepoPath, viewer: Viewer): Promise<Result<Environment[]>>;
477 /** Admin role. */
478 setEnvironment(actor: User, repo: RepoPath, name: string, change: EnvironmentChange): Promise<Result<Environment>>;
479 deleteEnvironment(actor: User, repo: RepoPath, name: string): Promise<Result<boolean>>;
480 /** A repository's artifacts, newest first, or one run's. */
481 artifacts(repo: RepoPath, viewer: Viewer, filter?: { run?: string; name?: string; page?: number; per_page?: number }): Promise<Result<ArtifactList>>;
482 /** One artifact by id, or by run and name, with a token to download it for a few minutes. */
483 artifactDownload(repo: RepoPath, viewer: Viewer, by: { id?: number; run?: string; name?: string }): Promise<Result<ArtifactBlob>>;
484 /** Needs the Write role. */
485 deleteArtifact(actor: User, repo: RepoPath, id: number): Promise<Result<Artifact>>;
486 /** How long the repository keeps artifacts; with `days`, sets it (Maintain). */
487 artifactRetention(repo: RepoPath, viewer: Viewer, days?: number): Promise<Result<ArtifactRetention>>;
488}
489
490/**
491 * A workflow run's artifact, kept in R2 for its retention days. Mirrors
492 * `g1t_contracts::actions::Artifact` (which travels in `snake_case`).
493 */
494export type Artifact = {
495 id: number;
496 name: string;
497 size: number;
498 /** `sha256:<hex>`, when the uploader said. */
499 digest: string | null;
500 /** `zip`, or `tgz` for one an older runner sent. */
501 format: string;
502 run_id: string;
503 job_id: string;
504 repo_id: string;
505 expired: boolean;
506 created_at: string;
507 updated_at: string;
508 expires_at: string;
509 head_branch?: string | null;
510 head_sha?: string | null;
511};
512
513export type ArtifactList = { total_count: number; artifacts: Artifact[] };
514
515/** An artifact, where it is, and a signed token for the API's `/actions/toolkit/blobs/{blob}`. */
516export type ArtifactBlob = { artifact: Artifact; object: string; blob: string };
517
518export type ArtifactRetention = { days: number; maximum_allowed_days: number };
519
520/**
521 * Whether a workflow's path is a managed workflow's: one g1t installed in
522 * the repository without committing it (crates/actions/src/managed.rs), so
523 * no file in the repository holds it.
524 */
525export function isManagedWorkflow(path: string): boolean {
526 return path.startsWith("managed/");
527}