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.
| Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9 | 1 | /** |
| 2 | * Builds opened from a g1t Actions job (`start_from_workflow`): who may | |
| 3 | * open one, what the run builds, and whether it is trusted with the | |
| 4 | * project's build secrets. Pure, so it is tested apart from the service. | |
| 5 | * | |
| 6 | * The job builds in its own checkout and pays for its own time; the | |
| 7 | * service only opens the build (its row, its token, the commit's status | |
| 8 | * and what the project's settings give it) and puts what the job sends. | |
| 9 | */ | |
| 10 | ||
| A project can build with a workflow. Turning on **Build with a workflow** under Settings, Deployments installs a managed workflow named `g1t` that calls g1t's reusable deploy workflow (`g1t/workflows/.g1t/workflows/deploy.yml@v1`, published from `workflows/` by the actions release) with the project's settings, and pushes and pull requests then start it rather than a build of Deployments' own: the same previews and production, at the same addresses, reported as `g1t / deploy`, now the check of the workflow's job, listed on the Actions page with its runs and logs. A pull request from a fork runs it with a read-only token, as on GitHub, and still gets its preview without the project's build secrets: a read-only job token may open a preview build only from a run of a managed workflow. Fork pull requests run the repository's managed workflows too. Its jobs are Actions time, at cost plus 20% on g1t's machines and free on a self-hosted runner. A workflow named `g1t` in `.g1t/workflows/` takes its place. The deployments and Actions guides say so. | 11 | import { isManagedWorkflow, tokenAllows, type RepoPath, type User } from "@g1t/contracts"; |
| Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9 | 12 | |
| 13 | /** The run a job token was made for, as far as these rules read it. */ | |
| 14 | export type RunFacts = { | |
| 15 | event: string; | |
| 16 | ref: string; | |
| 17 | sha: string; | |
| 18 | pull: number | null; | |
| 19 | status: string; | |
| 20 | }; | |
| 21 | ||
| 22 | /** What a run deploys: production, a branch's preview, or a pull request's (its branch looked up). */ | |
| 23 | export type RunTarget = | |
| 24 | | { kind: "production" } | |
| 25 | | { kind: "branch"; branch: string } | |
| 26 | | { kind: "pull"; number: number } | |
| 27 | | { error: string }; | |
| 28 | ||
| A project can build with a workflow. Turning on **Build with a workflow** under Settings, Deployments installs a managed workflow named `g1t` that calls g1t's reusable deploy workflow (`g1t/workflows/.g1t/workflows/deploy.yml@v1`, published from `workflows/` by the actions release) with the project's settings, and pushes and pull requests then start it rather than a build of Deployments' own: the same previews and production, at the same addresses, reported as `g1t / deploy`, now the check of the workflow's job, listed on the Actions page with its runs and logs. A pull request from a fork runs it with a read-only token, as on GitHub, and still gets its preview without the project's build secrets: a read-only job token may open a preview build only from a run of a managed workflow. Fork pull requests run the repository's managed workflows too. Its jobs are Actions time, at cost plus 20% on g1t's machines and free on a self-hosted runner. A workflow named `g1t` in `.g1t/workflows/` takes its place. The deployments and Actions guides say so. | 29 | /** Why a token that cannot write deployments is refused, where nothing lets it through. */ |
| 30 | export const NEEDS_WRITE = "The job's token needs `deployments: write` in the workflow's or job's `permissions:`."; | |
| 31 | ||
| Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9 | 32 | /** |
| 33 | * The run and job a caller's token was made for, if it is a workflow job's | |
| A project can build with a workflow. Turning on **Build with a workflow** under Settings, Deployments installs a managed workflow named `g1t` that calls g1t's reusable deploy workflow (`g1t/workflows/.g1t/workflows/deploy.yml@v1`, published from `workflows/` by the actions release) with the project's settings, and pushes and pull requests then start it rather than a build of Deployments' own: the same previews and production, at the same addresses, reported as `g1t / deploy`, now the check of the workflow's job, listed on the Actions page with its runs and logs. A pull request from a fork runs it with a read-only token, as on GitHub, and still gets its preview without the project's build secrets: a read-only job token may open a preview build only from a run of a managed workflow. Fork pull requests run the repository's managed workflows too. Its jobs are Actions time, at cost plus 20% on g1t's machines and free on a self-hosted runner. A workflow named `g1t` in `.g1t/workflows/` takes its place. The deployments and Actions guides say so. | 34 | * token for `repo`; otherwise why not. `writes` says whether it may write |
| 35 | * deployments: one that may not is let through only by `readOnlyAllowed`. | |
| Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9 | 36 | */ |
| A project can build with a workflow. Turning on **Build with a workflow** under Settings, Deployments installs a managed workflow named `g1t` that calls g1t's reusable deploy workflow (`g1t/workflows/.g1t/workflows/deploy.yml@v1`, published from `workflows/` by the actions release) with the project's settings, and pushes and pull requests then start it rather than a build of Deployments' own: the same previews and production, at the same addresses, reported as `g1t / deploy`, now the check of the workflow's job, listed on the Actions page with its runs and logs. A pull request from a fork runs it with a read-only token, as on GitHub, and still gets its preview without the project's build secrets: a read-only job token may open a preview build only from a run of a managed workflow. Fork pull requests run the repository's managed workflows too. Its jobs are Actions time, at cost plus 20% on g1t's machines and free on a self-hosted runner. A workflow named `g1t` in `.g1t/workflows/` takes its place. The deployments and Actions guides say so. | 37 | export function jobOf( |
| 38 | actor: User | null | undefined, | |
| 39 | repo: RepoPath, | |
| 40 | ): { runId: string; jobId: string; writes: boolean } | { error: string } { | |
| Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9 | 41 | const token = actor?.token; |
| 42 | if (!token?.job?.run_id || !token.job.job_id) { | |
| 43 | return { error: "Only a g1t Actions job's token (G1T_TOKEN) can open a g1t.page build." }; | |
| 44 | } | |
| 45 | const own = `${repo.namespace}/${repo.name}`.toLowerCase(); | |
| 46 | if (!token.repo || token.repo.toLowerCase() !== own) { | |
| 47 | return { error: `This job's token is for ${token.repo ?? "another repository"}, not ${repo.namespace}/${repo.name}.` }; | |
| 48 | } | |
| A project can build with a workflow. Turning on **Build with a workflow** under Settings, Deployments installs a managed workflow named `g1t` that calls g1t's reusable deploy workflow (`g1t/workflows/.g1t/workflows/deploy.yml@v1`, published from `workflows/` by the actions release) with the project's settings, and pushes and pull requests then start it rather than a build of Deployments' own: the same previews and production, at the same addresses, reported as `g1t / deploy`, now the check of the workflow's job, listed on the Actions page with its runs and logs. A pull request from a fork runs it with a read-only token, as on GitHub, and still gets its preview without the project's build secrets: a read-only job token may open a preview build only from a run of a managed workflow. Fork pull requests run the repository's managed workflows too. Its jobs are Actions time, at cost plus 20% on g1t's machines and free on a self-hosted runner. A workflow named `g1t` in `.g1t/workflows/` takes its place. The deployments and Actions guides say so. | 49 | return { runId: token.job.run_id, jobId: token.job.job_id, writes: tokenAllows(token.scopes, "deployments:write") }; |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Whether a job whose token may not write deployments may still open a | |
| 54 | * build: only a preview, from the deploy workflow g1t manages for the | |
| 55 | * project (a run of `managed/…`). A pull request from a fork runs with a | |
| 56 | * read-only token, as on GitHub, and still gets the preview g1t.page has | |
| 57 | * always built for it, without the project's build secrets. | |
| 58 | */ | |
| 59 | export function readOnlyAllowed(run: { path?: string | null }, target: RunTarget): boolean { | |
| 60 | return isManagedWorkflow(run.path ?? "") && "kind" in target && target.kind !== "production"; | |
| Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9 | 61 | } |
| 62 | ||
| 63 | /** | |
| 64 | * What a run builds. A run for a pull request builds that pull request's | |
| 65 | * preview; a run on the project's production branch (a push, a dispatch | |
| 66 | * or a schedule there) builds production; a run on any other branch | |
| 67 | * builds that branch's preview. A tag deploys nothing: give the branch. | |
| 68 | */ | |
| 69 | export function targetOf(run: Pick<RunFacts, "event" | "ref" | "pull">, productionBranch: string): RunTarget { | |
| 70 | if (run.pull != null && run.event.startsWith("pull_request")) return { kind: "pull", number: run.pull }; | |
| 71 | const ref = run.ref.trim(); | |
| 72 | const branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.startsWith("refs/") ? null : ref; | |
| 73 | if (!branch) { | |
| 74 | return { error: `This run is for ${ref || "no ref"}: g1t.page deploys branches and pull requests. Run the workflow on a branch.` }; | |
| 75 | } | |
| 76 | return branch === productionBranch ? { kind: "production" } : { kind: "branch", branch }; | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Whether a run's build gets the project's build secrets: never for a | |
| 81 | * pull request from a fork, one whose author could not push here, or a | |
| 82 | * run that had to be approved to start. A run on a branch of the | |
| 83 | * repository is trusted: only people who can push make one. | |
| 84 | */ | |
| 85 | export function runTrusted(input: { | |
| 86 | approval: { state: string } | null | undefined; | |
| 87 | pull: { fork?: RepoPath | null } | null; | |
| 88 | /** Whether whoever the pull request is for can push to the repository. */ | |
| 89 | insider: boolean; | |
| 90 | }): boolean { | |
| 91 | if (input.approval) return false; | |
| 92 | if (input.pull) return !input.pull.fork && input.insider; | |
| 93 | return true; | |
| 94 | } | |
| 95 | ||
| 96 | /** The commit to build: the one given (a whole or abbreviated hex id), else the run's. */ | |
| 97 | export function commitOf(given: unknown, run: Pick<RunFacts, "sha">): string | { error: string } { | |
| 98 | if (given == null || given === "") return run.sha; | |
| 99 | if (typeof given !== "string" || !/^[0-9a-f]{7,64}$/i.test(given.trim())) { | |
| 100 | return { error: "`sha` is a commit's id, in hex." }; | |
| 101 | } | |
| 102 | return given.trim().toLowerCase(); | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's. | 106 | * Why a build its workflow job left open failed, from how the job ended |
| 107 | * (its conclusion: `cancelled`, `failure`, `success`, …). | |
| 108 | */ | |
| 109 | export function jobEndedMessage(conclusion: string): string { | |
| 110 | switch (conclusion) { | |
| 111 | case "cancelled": | |
| 112 | return "The workflow job was cancelled before the build finished."; | |
| 113 | case "failure": | |
| 114 | return "The workflow job failed before the build finished."; | |
| 115 | case "timed_out": | |
| 116 | return "The workflow job timed out before the build finished."; | |
| 117 | default: | |
| 118 | return "The workflow job ended without finishing the build."; | |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | /** | |
| Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9 | 123 | * The project to build: the one named, or the repository's one project |
| 124 | * with Deployments on when none is named. | |
| 125 | */ | |
| 126 | export function projectNamed<P extends { slug: string }>( | |
| 127 | projects: P[], | |
| 128 | enabled: (project: P) => boolean, | |
| 129 | slug: string | null | undefined, | |
| 130 | ): P | { error: string; code: "not_found" | "invalid" | "conflict" } { | |
| 131 | const wanted = typeof slug === "string" ? slug.trim().toLowerCase() : ""; | |
| 132 | if (wanted) { | |
| 133 | return projects.find((p) => p.slug.toLowerCase() === wanted) ?? { error: `This repository has no project ${wanted}.`, code: "not_found" }; | |
| 134 | } | |
| 135 | const on = projects.filter(enabled); | |
| 136 | if (on.length === 1) return on[0]; | |
| 137 | if (on.length === 0) return { error: "No project of this repository has Deployments on.", code: "conflict" }; | |
| 138 | return { error: `Name the project to build: ${on.map((p) => p.slug).join(", ")}.`, code: "invalid" }; | |
| 139 | } | |
| 140 | ||
| 141 | /** | |
| 142 | * What `start_from_workflow` answers, in `snake_case` as the API sends it: | |
| 143 | * the build's id and token for `g1t-runner deploy`, what it is, where it | |
| 144 | * will be served, and what the project gives its build. | |
| 145 | */ | |
| 146 | export type WorkflowBuild = { | |
| 147 | deploy_id: string; | |
| 148 | /** For this build alone, until it finishes: `DEPLOY_TOKEN`. */ | |
| 149 | token: string; | |
| 150 | kind: "production" | "preview"; | |
| 151 | project: string; | |
| 152 | branch: string | null; | |
| 153 | number: number | null; | |
| 154 | commit: string; | |
| 155 | /** The app's address on g1t.page. */ | |
| 156 | url: string; | |
| 157 | /** Whether the build got the project's build secrets. */ | |
| 158 | trusted: boolean; | |
| 159 | /** The project's settings, for a build that names none of its own. */ | |
| 160 | root_dir: string; | |
| 161 | build_command: string | null; | |
| 162 | output_dir: string | null; | |
| Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's. | 163 | /** What it is built as (`auto`, `static`, `workers` or `docker`), and its Dockerfile's path when set. */ |
| 164 | build_as: string; | |
| 165 | dockerfile: string | null; | |
| Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9 | 166 | /** The variables builds of this kind get. */ |
| 167 | build_env: Record<string, string>; | |
| 168 | /** The secrets builds of this kind get; empty for an untrusted run. */ | |
| 169 | build_secrets: Record<string, string>; | |
| 170 | }; |
This file's history is long; its oldest lines are credited to the oldest commit read.