1import * as Schema from "effect/Schema";
2import { PositiveInt, TrimmedNonEmptyString } from "./baseSchemas.ts";
3import { VcsDriverKind } from "./vcs.ts";
4
5/**
6 * `SourceControlProviderKind` — open branded slug naming a source control host, such as `github`.
7 *
8 * Open on purpose, like `ProviderDriverKind`: a server can report a host this client does not
9 * ship a definition for, and clients fall back to a generic presentation instead of failing to
10 * decode. `unknown` is what the server reports for a remote it cannot attribute to any host.
11 */
12export const SourceControlProviderKind = TrimmedNonEmptyString.check(
13 Schema.isMaxLength(64),
14 Schema.isPattern(/^[a-z][a-z0-9-]*$/),
15).pipe(Schema.brand("SourceControlProviderKind"));
16export type SourceControlProviderKind = typeof SourceControlProviderKind.Type;
17
18export const SourceControlProviderInfo = Schema.Struct({
19 kind: SourceControlProviderKind,
20 name: TrimmedNonEmptyString,
21 baseUrl: Schema.String,
22});
23export type SourceControlProviderInfo = typeof SourceControlProviderInfo.Type;
24
25export const ChangeRequestState = Schema.Literals(["open", "closed", "merged"]);
26export type ChangeRequestState = typeof ChangeRequestState.Type;
27
28export const ChangeRequest = Schema.Struct({
29 provider: SourceControlProviderKind,
30 number: PositiveInt,
31 title: TrimmedNonEmptyString,
32 url: Schema.String,
33 baseRefName: TrimmedNonEmptyString,
34 headRefName: TrimmedNonEmptyString,
35 /** The head commit, when the provider's read includes it. */
36 headSha: Schema.optional(TrimmedNonEmptyString),
37 state: ChangeRequestState,
38 /** Present when the provider can tell that an open change request is still a draft. */
39 isDraft: Schema.optional(Schema.Boolean),
40 closedAt: Schema.optional(Schema.NullOr(Schema.String)),
41 mergedAt: Schema.optional(Schema.NullOr(Schema.String)),
42 updatedAt: Schema.Option(Schema.DateTimeUtc),
43 isCrossRepository: Schema.optional(Schema.Boolean),
44 headRepositoryNameWithOwner: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
45 headRepositoryOwnerLogin: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
46});
47export type ChangeRequest = typeof ChangeRequest.Type;
48
49export const SourceControlRepositoryCloneUrls = Schema.Struct({
50 nameWithOwner: TrimmedNonEmptyString,
51 url: TrimmedNonEmptyString,
52 sshUrl: TrimmedNonEmptyString,
53});
54export type SourceControlRepositoryCloneUrls = typeof SourceControlRepositoryCloneUrls.Type;
55
56export const SourceControlRepositoryVisibility = Schema.Literals(["private", "public"]);
57export type SourceControlRepositoryVisibility = typeof SourceControlRepositoryVisibility.Type;
58
59export const SourceControlCloneProtocol = Schema.Literals(["auto", "ssh", "https"]);
60export type SourceControlCloneProtocol = typeof SourceControlCloneProtocol.Type;
61
62export const SourceControlRepositoryInfo = Schema.Struct({
63 provider: SourceControlProviderKind,
64 nameWithOwner: TrimmedNonEmptyString,
65 url: TrimmedNonEmptyString,
66 sshUrl: TrimmedNonEmptyString,
67});
68export type SourceControlRepositoryInfo = typeof SourceControlRepositoryInfo.Type;
69
70export const SourceControlRepositoryLookupInput = Schema.Struct({
71 provider: SourceControlProviderKind,
72 repository: TrimmedNonEmptyString,
73 cwd: Schema.optional(TrimmedNonEmptyString),
74});
75export type SourceControlRepositoryLookupInput = typeof SourceControlRepositoryLookupInput.Type;
76
77export const SourceControlCloneRepositoryInput = Schema.Struct({
78 provider: Schema.optional(SourceControlProviderKind),
79 repository: Schema.optional(TrimmedNonEmptyString),
80 remoteUrl: Schema.optional(TrimmedNonEmptyString),
81 destinationPath: TrimmedNonEmptyString,
82 protocol: Schema.optional(SourceControlCloneProtocol),
83});
84export type SourceControlCloneRepositoryInput = typeof SourceControlCloneRepositoryInput.Type;
85
86export const SourceControlCloneRepositoryResult = Schema.Struct({
87 cwd: TrimmedNonEmptyString,
88 remoteUrl: TrimmedNonEmptyString,
89 repository: Schema.NullOr(SourceControlRepositoryInfo),
90});
91export type SourceControlCloneRepositoryResult = typeof SourceControlCloneRepositoryResult.Type;
92
93export const SourceControlPublishRepositoryInput = Schema.Struct({
94 cwd: TrimmedNonEmptyString,
95 provider: SourceControlProviderKind,
96 repository: TrimmedNonEmptyString,
97 visibility: SourceControlRepositoryVisibility,
98 remoteName: Schema.optional(TrimmedNonEmptyString),
99 protocol: Schema.optional(SourceControlCloneProtocol),
100});
101export type SourceControlPublishRepositoryInput = typeof SourceControlPublishRepositoryInput.Type;
102
103export const SourceControlPublishStatus = Schema.Literals(["pushed", "remote_added"]);
104export type SourceControlPublishStatus = typeof SourceControlPublishStatus.Type;
105
106export const SourceControlPublishRepositoryResult = Schema.Struct({
107 repository: SourceControlRepositoryInfo,
108 remoteName: TrimmedNonEmptyString,
109 remoteUrl: TrimmedNonEmptyString,
110 branch: TrimmedNonEmptyString,
111 upstreamBranch: Schema.optional(TrimmedNonEmptyString),
112 status: SourceControlPublishStatus,
113});
114export type SourceControlPublishRepositoryResult = typeof SourceControlPublishRepositoryResult.Type;
115
116export const SourceControlDiscoveryStatus = Schema.Literals(["available", "missing"]);
117export type SourceControlDiscoveryStatus = typeof SourceControlDiscoveryStatus.Type;
118
119export const SourceControlProviderAuthStatus = Schema.Literals([
120 "authenticated",
121 "unauthenticated",
122 "unknown",
123]);
124export type SourceControlProviderAuthStatus = typeof SourceControlProviderAuthStatus.Type;
125
126export const SourceControlProviderAuth = Schema.Struct({
127 status: SourceControlProviderAuthStatus,
128 account: Schema.Option(TrimmedNonEmptyString),
129 host: Schema.Option(TrimmedNonEmptyString),
130 detail: Schema.Option(TrimmedNonEmptyString),
131 /** Every login the provider CLI holds, across hosts. Only GitHub reports these today. */
132 accounts: Schema.optionalKey(
133 Schema.Array(
134 Schema.Struct({
135 host: TrimmedNonEmptyString,
136 account: TrimmedNonEmptyString,
137 active: Schema.Boolean,
138 authenticated: Schema.Boolean,
139 error: Schema.optionalKey(TrimmedNonEmptyString),
140 /** Set when the login comes from a token variable such as `GH_TOKEN`, which wins over Settings. */
141 environmentVariable: Schema.optionalKey(TrimmedNonEmptyString),
142 }),
143 ),
144 ),
145});
146export type SourceControlProviderAuth = typeof SourceControlProviderAuth.Type;
147
148const SourceControlDiscoverySharedFields = {
149 label: TrimmedNonEmptyString,
150 executable: Schema.optional(TrimmedNonEmptyString),
151 status: SourceControlDiscoveryStatus,
152 version: Schema.Option(TrimmedNonEmptyString),
153 installHint: TrimmedNonEmptyString,
154 detail: Schema.Option(TrimmedNonEmptyString),
155} as const;
156
157export const VcsDiscoveryItem = Schema.Struct({
158 kind: VcsDriverKind,
159 implemented: Schema.Boolean,
160 ...SourceControlDiscoverySharedFields,
161});
162export type VcsDiscoveryItem = typeof VcsDiscoveryItem.Type;
163
164export const SourceControlProviderDiscoveryItem = Schema.Struct({
165 kind: SourceControlProviderKind,
166 ...SourceControlDiscoverySharedFields,
167 auth: SourceControlProviderAuth,
168});
169export type SourceControlProviderDiscoveryItem = typeof SourceControlProviderDiscoveryItem.Type;
170
171export const SourceControlDiscoveryResult = Schema.Struct({
172 versionControlSystems: Schema.Array(VcsDiscoveryItem),
173 sourceControlProviders: Schema.Array(SourceControlProviderDiscoveryItem),
174});
175export type SourceControlDiscoveryResult = typeof SourceControlDiscoveryResult.Type;
176
177export class SourceControlProviderError extends Schema.TaggedError<SourceControlProviderError>()(
178 "SourceControlProviderError",
179 {
180 provider: SourceControlProviderKind,
181 operation: Schema.String,
182 cwd: Schema.String,
183 command: Schema.optional(Schema.String),
184 repository: Schema.optional(Schema.String),
185 reference: Schema.optional(Schema.String),
186 detail: Schema.String,
187 cause: Schema.optional(Schema.Defect()),
188 },
189) {
190 override get message(): string {
191 return `Source control provider ${this.provider} failed in ${this.operation}: ${this.detail}`;
192 }
193}
194
195export class SourceControlRepositoryError extends Schema.TaggedError<SourceControlRepositoryError>()(
196 "SourceControlRepositoryError",
197 {
198 provider: SourceControlProviderKind,
199 operation: Schema.String,
200 detail: Schema.String,
201 cause: Schema.optional(Schema.Defect()),
202 },
203) {
204 override get message(): string {
205 return `Source control repository operation ${this.operation} failed for ${this.provider}: ${this.detail}`;
206 }
207}