| 1 | /** |
| 2 | * GitCafe's client definition. Browser- and React Native-safe. |
| 3 | * |
| 4 | * @module source-control-gitcafe/client/definition |
| 5 | */ |
| 6 | import { |
| 7 | makeProviderSettingsSchema, |
| 8 | secretSetting, |
| 9 | SourceControlProviderKind, |
| 10 | TrimmedString, |
| 11 | } from "@t3tools/contracts"; |
| 12 | import * as Effect from "effect/Effect"; |
| 13 | import * as Schema from "effect/Schema"; |
| 14 | import { |
| 15 | defineSourceControlClient, |
| 16 | isChangeRequestInProjectRepository, |
| 17 | isChangeRequestOnProjectHost, |
| 18 | isChangeRequestPath, |
| 19 | } from "@t3tools/source-control-core/client/definition"; |
| 20 | |
| 21 | /** GitCafe has no self-hosted installs: production and staging are its only hosts. */ |
| 22 | const GITCAFE_HOSTS = new Set(["git.cafe", "staging.git.cafe"]); |
| 23 | |
| 24 | const KIND = SourceControlProviderKind.make("gitcafe"); |
| 25 | |
| 26 | /** A git.cafe token, used before `CAFE_TOKEN` and the `cafe` login. */ |
| 27 | export const settings = makeProviderSettingsSchema({ |
| 28 | token: TrimmedString.pipe( |
| 29 | secretSetting, |
| 30 | Schema.withDecodingDefault(Effect.succeed("")), |
| 31 | Schema.annotateKey({ |
| 32 | title: "Token", |
| 33 | description: |
| 34 | "A git.cafe token, used before CAFE_TOKEN and the cafe login, so GitCafe works without the CLI signed in.", |
| 35 | providerSettingsForm: { control: "password" }, |
| 36 | }), |
| 37 | ), |
| 38 | }); |
| 39 | export type GitCafeSettings = typeof settings.Type; |
| 40 | |
| 41 | export const definition = defineSourceControlClient({ |
| 42 | kind: KIND, |
| 43 | label: "GitCafe", |
| 44 | pickerLabel: "GitCafe", |
| 45 | icon: "gitcafe", |
| 46 | changeRequest: { shortLabel: "PR", singular: "pull request" }, |
| 47 | repositoryPathHint: "owner/repo", |
| 48 | // Left unattributed so a reference with no repository identity behind it gets no checkout |
| 49 | // command guessed from its hostname alone. |
| 50 | publicHost: null, |
| 51 | publishDescription: "git.cafe", |
| 52 | publishHost: () => "git.cafe", |
| 53 | newRepositoryOwner: (account) => (account ? { owner: account } : null), |
| 54 | defaultCloneTransport: "ssh", |
| 55 | changeRequestUrl: ({ host, repository, number }) => |
| 56 | `https://${host}/${repository}/pulls/${number}`, |
| 57 | changeRequestActions: new Set([ |
| 58 | "ready", |
| 59 | "draft", |
| 60 | "close", |
| 61 | "reopen", |
| 62 | "merge", |
| 63 | "update-branch", |
| 64 | ] as const), |
| 65 | checkoutCommand: ({ number }) => `cafe pr checkout ${number}`, |
| 66 | authorProfileUrl: () => null, |
| 67 | referenceAutolinkRepositoryUrl: () => null, |
| 68 | reviewSummaryRequired: () => false, |
| 69 | // `/pulls/` is Forgejo's shape too, so only GitCafe's own hosts claim it. |
| 70 | // `cafe pr checkout` and git.cafe URLs were never accepted in reference fields. |
| 71 | checkoutCommandArgument: () => null, |
| 72 | isChangeRequestReference: () => false, |
| 73 | changeRequestUrlHost: (url) => url.hostname, |
| 74 | checkoutChangeRequestHost: () => null, |
| 75 | isChangeRequestInRepository: (identity, link) => |
| 76 | isChangeRequestInProjectRepository(KIND, identity, link), |
| 77 | canReadChangeRequestOnHost: (identity, link) => |
| 78 | isChangeRequestOnProjectHost(KIND, identity, link), |
| 79 | isChangeRequestUrl: (url) => { |
| 80 | try { |
| 81 | return ( |
| 82 | GITCAFE_HOSTS.has(new URL(url).hostname.toLowerCase()) && |
| 83 | isChangeRequestPath(url, "/pulls/") |
| 84 | ); |
| 85 | } catch { |
| 86 | return false; |
| 87 | } |
| 88 | }, |
| 89 | settings, |
| 90 | }); |