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.
| Download ZIP reads blobs eight batches at a time and deflates files together | 1 | import type { ServiceBinding } from "./clients"; |
| 2 | import type { User } from "./identity"; | |
| 3 | import type { Result } from "./result"; | |
| 4 | ||
| 5 | /** | |
| 6 | * GitHub: signing in with a GitHub account, and bringing repositories | |
| 7 | * across through g1t's GitHub App. Mirrors `crates/contracts/src/github.rs`. | |
| 8 | * | |
| 9 | * The identity service keeps linked accounts and their user tokens; the | |
| Merge branch 'rm-mirroring' | 10 | * integrations service keeps installations, the repositories imported |
| 11 | * through them and the app's webhook. With no app configured, `enabled`/`configured` are false | |
| Download ZIP reads blobs eight batches at a time and deflates files together | 12 | * and the site shows no GitHub buttons. |
| 13 | */ | |
| 14 | ||
| 15 | export type GithubPurpose = "sign_in" | "link"; | |
| 16 | ||
| 17 | /** How a return from GitHub ended. */ | |
| 18 | export type GithubFinished = | |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 19 | | { |
| 20 | kind: "signed_in"; | |
| 21 | /** `twoFactorChallenge` is set, and `sessionToken` empty, while the account's two-factor code is still to come. */ | |
| 22 | signedIn: { user: User; sessionToken: string; twoFactorChallenge?: string | null }; | |
| 23 | created: boolean; | |
| 24 | next: string; | |
| 25 | } | |
| Download ZIP reads blobs eight batches at a time and deflates files together | 26 | | { kind: "linked"; login: string; next: string } |
| 27 | /** An account has one of its verified emails: sign in to it, then claim. */ | |
| 28 | | { kind: "needs_link"; pending: string; login: string; next: string } | |
| Merge branch 'open-signups' | 29 | /** A new account waiting on a username. */ |
| 30 | | { kind: "needs_username"; pending: string; login: string; suggestion: string; next: string }; | |
| Download ZIP reads blobs eight batches at a time and deflates files together | 31 | |
| 32 | export type GithubPending = { | |
| 33 | login: string; | |
| 34 | kind: "link" | "username"; | |
| 35 | suggestion: string | null; | |
| 36 | next: string; | |
| 37 | }; | |
| 38 | ||
| 39 | export type GithubAccount = { | |
| 40 | githubId: number; | |
| 41 | login: string; | |
| 42 | linkedAt: string; | |
| 43 | /** Whether g1t holds a working user token for it. */ | |
| 44 | authorized: boolean; | |
| 45 | }; | |
| 46 | ||
| 47 | export type GithubAccountView = { | |
| 48 | enabled: boolean; | |
| 49 | account: GithubAccount | null; | |
| 50 | hasPassword: boolean; | |
| 51 | }; | |
| 52 | ||
| 53 | export type GithubInstallation = { | |
| 54 | id: number; | |
| 55 | workspace: string; | |
| 56 | account: string; | |
| 57 | accountType: string; | |
| 58 | repositorySelection: "all" | "selected" | string; | |
| 59 | suspended: boolean; | |
| 60 | settingsUrl: string; | |
| 61 | createdAt: string; | |
| 62 | }; | |
| 63 | ||
| A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how. | 64 | /** |
| 65 | * An installation of the app the person's own GitHub account can see, | |
| 66 | * from `github_visible_installations`: how one installed on GitHub | |
| 67 | * directly is found and added to a workspace. | |
| 68 | */ | |
| 69 | export type GithubVisibleInstallation = { | |
| 70 | id: number; | |
| 71 | account: string; | |
| 72 | accountType: string; | |
| 73 | repositorySelection: "all" | "selected" | string; | |
| 74 | suspended: boolean; | |
| 75 | settingsUrl: string; | |
| 76 | /** The person's workspaces it is recorded in already. */ | |
| 77 | recordedIn: string[]; | |
| 78 | }; | |
| 79 | ||
| Download ZIP reads blobs eight batches at a time and deflates files together | 80 | export type GithubAppStatus = { |
| 81 | configured: boolean; | |
| 82 | installUrl: string | null; | |
| 83 | linked: boolean; | |
| 84 | installations: GithubInstallation[]; | |
| 85 | }; | |
| 86 | ||
| 87 | export type GithubRepository = { | |
| 88 | id: number; | |
| 89 | fullName: string; | |
| 90 | name: string; | |
| 91 | private: boolean; | |
| 92 | description: string | null; | |
| 93 | defaultBranch: string; | |
| Merge branch 'rm-mirroring' | 94 | /** The g1t repository already imported from it, as `workspace/name`. */ |
| Download ZIP reads blobs eight batches at a time and deflates files together | 95 | linkedTo: string | null; |
| 96 | }; | |
| 97 | ||
| 98 | export type GithubRepositories = { | |
| 99 | repositories: GithubRepository[]; | |
| 100 | total: number; | |
| 101 | page: number; | |
| 102 | perPage: number; | |
| 103 | }; | |
| 104 | ||
| Merge branch 'rm-mirroring' | 105 | /** Where a g1t repository was imported from on GitHub. The import is copied once: the g1t repository is then its own. */ |
| Download ZIP reads blobs eight batches at a time and deflates files together | 106 | export type GithubRepoLink = { |
| 107 | repoId: string; | |
| 108 | repo: string; | |
| 109 | installationId: number; | |
| 110 | githubRepoId: number; | |
| 111 | fullName: string; | |
| Merge branch 'rm-mirroring' | 112 | /** RFC 3339: when it was imported. */ |
| Download ZIP reads blobs eight batches at a time and deflates files together | 113 | syncedAt: string | null; |
| 114 | lastError: string | null; | |
| 115 | issuesImported: number; | |
| 116 | }; | |
| 117 | ||
| 118 | export type GithubImportInput = { | |
| 119 | installationId: number; | |
| 120 | githubRepoId: number; | |
| 121 | name?: string; | |
| 122 | private?: boolean; | |
| 123 | issues: boolean; | |
| 124 | }; | |
| 125 | ||
| 126 | async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> { | |
| 127 | const response = await service.fetch(`https://service/rpc/${method}`, { | |
| 128 | method: "POST", | |
| 129 | headers: { "content-type": "application/json" }, | |
| 130 | body: JSON.stringify(args), | |
| 131 | }); | |
| 132 | if (!response.ok) throw new Error(`${method} failed with status ${response.status}`); | |
| 133 | return (await response.json()) as T; | |
| 134 | } | |
| 135 | ||
| 136 | /** Signing in with GitHub: methods of the identity service. */ | |
| 137 | export function githubSignInClient(identity: ServiceBinding) { | |
| 138 | const call = <T>(method: string, args: object) => rpc<T>(identity, method, args); | |
| 139 | return { | |
| 140 | enabled: () => call<boolean>("github_enabled", {}), | |
| 141 | start: (input: { purpose: GithubPurpose; user?: User | null; redirectUri: string; next: string; inviteCode?: string | null }) => | |
| 142 | call<Result<{ authorizeUrl: string; state: string }>>("github_start", input), | |
| 143 | finish: (state: string, code: string) => call<Result<GithubFinished>>("github_finish", { state, code }), | |
| 144 | pending: (pending: string) => call<Result<GithubPending>>("github_pending", { pending }), | |
| Merge branch 'open-signups' | 145 | signUp: (pending: string, username: string) => |
| 146 | call<Result<{ user: User; sessionToken: string }>>("github_sign_up", { pending, username }), | |
| Download ZIP reads blobs eight batches at a time and deflates files together | 147 | claim: (pending: string, user: User) => call<Result<GithubAccount>>("github_claim", { pending, user }), |
| 148 | account: (user: User) => call<GithubAccountView>("github_account", { user }), | |
| 149 | unlink: (user: User) => call<Result<boolean>>("github_unlink", { user }), | |
| 150 | }; | |
| 151 | } | |
| 152 | ||
| 153 | /** g1t's GitHub App: methods of the integrations service. */ | |
| 154 | export function githubAppClient(integrations: ServiceBinding) { | |
| 155 | const call = <T>(method: string, args: object) => rpc<T>(integrations, method, args); | |
| 156 | return { | |
| 157 | status: (viewer: User, workspace: string) => call<Result<GithubAppStatus>>("github_status", { viewer, workspace }), | |
| 158 | addInstallation: (actor: User, workspace: string, installationId: number) => | |
| 159 | call<Result<GithubInstallation>>("github_add_installation", { actor, workspace, installationId }), | |
| A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how. | 160 | visibleInstallations: (actor: User) => call<Result<GithubVisibleInstallation[]>>("github_visible_installations", { actor }), |
| Download ZIP reads blobs eight batches at a time and deflates files together | 161 | removeInstallation: (actor: User, workspace: string, installationId: number) => |
| 162 | call<Result<boolean>>("github_remove_installation", { actor, workspace, installationId }), | |
| 163 | repositories: (actor: User, workspace: string, installationId: number, page?: number) => | |
| 164 | call<Result<GithubRepositories>>("github_repositories", { actor, workspace, installationId, page: page ?? null }), | |
| 165 | import: (actor: User, workspace: string, input: GithubImportInput) => | |
| 166 | call<Result<GithubRepoLink>>("github_import", { actor, workspace, ...input }), | |
| 167 | link: (repoId: string) => call<GithubRepoLink | null>("github_link", { repoId }), | |
| 168 | }; | |
| 169 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.