| 1 | /** |
| 2 | * GitHub's client definition. Browser- and React Native-safe. |
| 3 | * |
| 4 | * @module source-control-github/client/definition |
| 5 | */ |
| 6 | import { |
| 7 | GitHubHost, |
| 8 | makeProviderSettingsSchema, |
| 9 | secretSetting, |
| 10 | pullRequestHostOf, |
| 11 | SourceControlProviderKind, |
| 12 | TrimmedNonEmptyString, |
| 13 | TrimmedString, |
| 14 | } from "@t3tools/contracts"; |
| 15 | import * as Effect from "effect/Effect"; |
| 16 | import * as Schema from "effect/Schema"; |
| 17 | import { |
| 18 | defineSourceControlClient, |
| 19 | isChangeRequestInProjectRepository, |
| 20 | isChangeRequestOnProjectHost, |
| 21 | isChangeRequestPath, |
| 22 | } from "@t3tools/source-control-core/client/definition"; |
| 23 | |
| 24 | const CHECKOUT_COMMAND = /^gh\s+pr\s+checkout\s+(.+)$/i; |
| 25 | const CHANGE_REQUEST_REFERENCE = |
| 26 | /^https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)(?:[/?#].*)?$/i; |
| 27 | |
| 28 | const KIND = SourceControlProviderKind.make("github"); |
| 29 | |
| 30 | export const GitHubHostChoice = Schema.Struct({ |
| 31 | account: Schema.optionalKey(TrimmedNonEmptyString), |
| 32 | enabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), |
| 33 | }); |
| 34 | export type GitHubHostChoice = typeof GitHubHostChoice.Type; |
| 35 | |
| 36 | /** |
| 37 | * Per-host choices, keyed by lowercased host such as `github.com`. `hosts` pins one of the logins |
| 38 | * `gh` holds for a host instead of its active one, or turns the host off; `tokens` holds a token |
| 39 | * per host, used before `GH_TOKEN` and friends, which win over `gh`. Source Control settings draws |
| 40 | * both with GitHub's own panels, since the logins come from `gh` at runtime. |
| 41 | */ |
| 42 | export const settings = makeProviderSettingsSchema({ |
| 43 | hosts: Schema.Record(GitHubHost, GitHubHostChoice).pipe( |
| 44 | Schema.withDecodingDefault(Effect.succeed({})), |
| 45 | Schema.annotateKey({ providerSettingsForm: { hidden: true } }), |
| 46 | ), |
| 47 | tokens: Schema.Record(GitHubHost, TrimmedString).pipe( |
| 48 | Schema.withDecodingDefault(Effect.succeed({})), |
| 49 | secretSetting, |
| 50 | Schema.annotateKey({ providerSettingsForm: { hidden: true } }), |
| 51 | ), |
| 52 | }); |
| 53 | export type GitHubSettings = typeof settings.Type; |
| 54 | |
| 55 | export const definition = defineSourceControlClient({ |
| 56 | kind: KIND, |
| 57 | label: "GitHub", |
| 58 | pickerLabel: "GitHub", |
| 59 | icon: "github", |
| 60 | changeRequest: { shortLabel: "PR", singular: "pull request" }, |
| 61 | repositoryPathHint: "owner/repo", |
| 62 | publicHost: "github.com", |
| 63 | publishDescription: "github.com", |
| 64 | publishHost: () => "github.com", |
| 65 | newRepositoryOwner: (account) => ({ owner: account }), |
| 66 | defaultCloneTransport: "https", |
| 67 | changeRequestUrl: ({ host, repository, number }) => |
| 68 | `https://${host}/${repository}/pull/${number}`, |
| 69 | changeRequestActions: new Set([ |
| 70 | "merge", |
| 71 | "ready", |
| 72 | "draft", |
| 73 | "close", |
| 74 | "reopen", |
| 75 | "delete-branch", |
| 76 | "update-branch", |
| 77 | "enable-auto-merge", |
| 78 | "disable-auto-merge", |
| 79 | "revert", |
| 80 | "approve-workflows", |
| 81 | ] as const), |
| 82 | checkoutCommand: ({ number }) => `gh pr checkout ${number}`, |
| 83 | authorProfileUrl: (login, repositoryUrl) => |
| 84 | login.endsWith("[bot]") |
| 85 | ? null |
| 86 | : new URL(`/${encodeURIComponent(login)}`, repositoryUrl).toString(), |
| 87 | referenceAutolinkRepositoryUrl: (repositoryUrl) => repositoryUrl, |
| 88 | reviewSummaryRequired: () => false, |
| 89 | checkoutCommandArgument: (input) => CHECKOUT_COMMAND.exec(input)?.[1]?.trim() ?? null, |
| 90 | isChangeRequestReference: (url) => CHANGE_REQUEST_REFERENCE.test(url), |
| 91 | changeRequestUrlHost: (url) => url.hostname, |
| 92 | // A GitHub web host is the checkout's own host, Enterprise installs included. |
| 93 | checkoutChangeRequestHost: (identity) => pullRequestHostOf(identity, KIND), |
| 94 | isChangeRequestInRepository: (identity, link) => |
| 95 | isChangeRequestInProjectRepository(KIND, identity, link), |
| 96 | canReadChangeRequestOnHost: (identity, link) => |
| 97 | isChangeRequestOnProjectHost(KIND, identity, link), |
| 98 | isChangeRequestUrl: (url) => isChangeRequestPath(url, "/pull/"), |
| 99 | settings, |
| 100 | }); |