| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | import * as Context from "effect/Context"; |
| 2 | 2 | import * as Effect from "effect/Effect"; |
| 3 | 3 | import * as Layer from "effect/Layer"; |
| 4 | import * as Match from "effect/Match"; |
| 4 | 5 | import * as Option from "effect/Option"; |
| 5 | 6 | import * as Result from "effect/Result"; |
| 6 | 7 | import * as Schema from "effect/Schema"; |
| ... | ... | @@ -21,13 +22,56 @@ import type * as SourceControlProvider from "./SourceControlProvider.ts"; |
| 21 | 22 | |
| 22 | 23 | const DEFAULT_TIMEOUT_MS = 30_000; |
| 23 | 24 | |
| 24 | | export class GitLabCliError extends Schema.TaggedErrorClass<GitLabCliError>()("GitLabCliError", { |
| 25 | | operation: Schema.String, |
| 26 | | command: Schema.String, |
| 25 | const gitLabCliExecutionErrorContext = { |
| 26 | operation: Schema.Literal("execute"), |
| 27 | command: Schema.Literal("glab"), |
| 27 | 28 | cwd: Schema.String, |
| 28 | | detail: Schema.String, |
| 29 | | cause: Schema.optional(Schema.Defect()), |
| 30 | | }) { |
| 29 | cause: Schema.Defect(), |
| 30 | }; |
| 31 | |
| 32 | const gitLabCliDecodeErrorContext = { |
| 33 | command: Schema.Literal("glab"), |
| 34 | cwd: Schema.String, |
| 35 | cause: Schema.Defect(), |
| 36 | }; |
| 37 | |
| 38 | export class GitLabCliUnavailableError extends Schema.TaggedErrorClass<GitLabCliUnavailableError>()( |
| 39 | "GitLabCliUnavailableError", |
| 40 | gitLabCliExecutionErrorContext, |
| 41 | ) { |
| 42 | get detail(): string { |
| 43 | return "GitLab CLI (`glab`) is required but not available on PATH."; |
| 44 | } |
| 45 | |
| 46 | override get message(): string { |
| 47 | return `GitLab CLI failed in ${this.operation}: ${this.detail}`; |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | export class GitLabCliAuthenticationError extends Schema.TaggedErrorClass<GitLabCliAuthenticationError>()( |
| 52 | "GitLabCliAuthenticationError", |
| 53 | gitLabCliExecutionErrorContext, |
| 54 | ) { |
| 55 | get detail(): string { |
| 56 | return "GitLab CLI is not authenticated. Run `glab auth login` and retry."; |
| 57 | } |
| 58 | |
| 59 | override get message(): string { |
| 60 | return `GitLab CLI failed in ${this.operation}: ${this.detail}`; |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | export class GitLabMergeRequestNotFoundError extends Schema.TaggedErrorClass<GitLabMergeRequestNotFoundError>()( |
| 65 | "GitLabMergeRequestNotFoundError", |
| 66 | { |
| 67 | ...gitLabCliExecutionErrorContext, |
| 68 | reference: Schema.String, |
| 69 | }, |
| 70 | ) { |
| 71 | get detail(): string { |
| 72 | return `Merge request ${this.reference} was not found. Check the MR number or URL and try again.`; |
| 73 | } |
| 74 | |
| 31 | 75 | override get message(): string { |
| 32 | 76 | return `GitLab CLI failed in ${this.operation}: ${this.detail}`; |
| 33 | 77 | } |
| ... | ... | @@ -37,52 +81,145 @@ export class GitLabCliError extends Schema.TaggedErrorClass<GitLabCliError>()("G |
| 37 | 81 | readonly operation: "execute"; |
| 38 | 82 | readonly command: "glab"; |
| 39 | 83 | readonly cwd: string; |
| 84 | readonly reference: string; |
| 40 | 85 | }, |
| 41 | | error: VcsError | unknown, |
| 86 | error: VcsError, |
| 42 | 87 | ): GitLabCliError { |
| 43 | | const lower = errorText(error).toLowerCase(); |
| 44 | | |
| 45 | | if (lower.includes("command not found: glab") || isVcsProcessSpawnError(error)) { |
| 46 | | return new GitLabCliError({ |
| 47 | | ...context, |
| 48 | | detail: "GitLab CLI (`glab`) is required but not available on PATH.", |
| 49 | | cause: error, |
| 50 | | }); |
| 88 | if (error._tag === "VcsProcessExitError" && error.failureKind === "not-found") { |
| 89 | return new GitLabMergeRequestNotFoundError({ ...context, cause: error }); |
| 51 | 90 | } |
| 52 | 91 | |
| 53 | | if ( |
| 54 | | lower.includes("authentication failed") || |
| 55 | | lower.includes("not logged in") || |
| 56 | | lower.includes("glab auth login") || |
| 57 | | lower.includes("token") |
| 58 | | ) { |
| 59 | | return new GitLabCliError({ |
| 60 | | ...context, |
| 61 | | detail: "GitLab CLI is not authenticated. Run `glab auth login` and retry.", |
| 62 | | cause: error, |
| 63 | | }); |
| 64 | | } |
| 92 | return GitLabCliCommandError.fromVcsError( |
| 93 | { |
| 94 | operation: context.operation, |
| 95 | command: context.command, |
| 96 | cwd: context.cwd, |
| 97 | }, |
| 98 | error, |
| 99 | ); |
| 100 | } |
| 101 | } |
| 65 | 102 | |
| 66 | | if ( |
| 67 | | lower.includes("merge request not found") || |
| 68 | | lower.includes("not found") || |
| 69 | | lower.includes("404") |
| 70 | | ) { |
| 71 | | return new GitLabCliError({ |
| 72 | | ...context, |
| 73 | | detail: "Merge request not found. Check the MR number or URL and try again.", |
| 74 | | cause: error, |
| 75 | | }); |
| 76 | | } |
| 103 | export class GitLabCliCommandError extends Schema.TaggedErrorClass<GitLabCliCommandError>()( |
| 104 | "GitLabCliCommandError", |
| 105 | gitLabCliExecutionErrorContext, |
| 106 | ) { |
| 107 | get detail(): string { |
| 108 | return "GitLab CLI command failed."; |
| 109 | } |
| 77 | 110 | |
| 78 | | return new GitLabCliError({ |
| 79 | | ...context, |
| 80 | | detail: "GitLab CLI command failed.", |
| 81 | | cause: error, |
| 111 | override get message(): string { |
| 112 | return `GitLab CLI failed in ${this.operation}: ${this.detail}`; |
| 113 | } |
| 114 | |
| 115 | static fromVcsError( |
| 116 | context: { |
| 117 | readonly operation: "execute"; |
| 118 | readonly command: "glab"; |
| 119 | readonly cwd: string; |
| 120 | }, |
| 121 | error: VcsError, |
| 122 | ): GitLabCliError { |
| 123 | return Match.valueTags(error, { |
| 124 | VcsProcessSpawnError: (cause) => new GitLabCliUnavailableError({ ...context, cause }), |
| 125 | VcsProcessExitError: (cause) => { |
| 126 | switch (cause.failureKind) { |
| 127 | case "authentication": |
| 128 | return new GitLabCliAuthenticationError({ ...context, cause }); |
| 129 | case "not-found": |
| 130 | case "command-failed": |
| 131 | case undefined: |
| 132 | return new GitLabCliCommandError({ ...context, cause }); |
| 133 | } |
| 134 | }, |
| 135 | VcsProcessTimeoutError: (cause) => new GitLabCliCommandError({ ...context, cause }), |
| 136 | VcsOutputDecodeError: (cause) => new GitLabCliCommandError({ ...context, cause }), |
| 137 | VcsRepositoryDetectionError: (cause) => new GitLabCliCommandError({ ...context, cause }), |
| 138 | VcsUnsupportedOperationError: (cause) => new GitLabCliCommandError({ ...context, cause }), |
| 82 | 139 | }); |
| 83 | 140 | } |
| 84 | 141 | } |
| 85 | 142 | |
| 143 | export class GitLabMergeRequestListDecodeError extends Schema.TaggedErrorClass<GitLabMergeRequestListDecodeError>()( |
| 144 | "GitLabMergeRequestListDecodeError", |
| 145 | { |
| 146 | ...gitLabCliDecodeErrorContext, |
| 147 | operation: Schema.Literal("listMergeRequests"), |
| 148 | }, |
| 149 | ) { |
| 150 | get detail(): string { |
| 151 | return "GitLab CLI returned invalid MR list JSON."; |
| 152 | } |
| 153 | |
| 154 | override get message(): string { |
| 155 | return `GitLab CLI failed in ${this.operation}: ${this.detail}`; |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | export class GitLabMergeRequestDecodeError extends Schema.TaggedErrorClass<GitLabMergeRequestDecodeError>()( |
| 160 | "GitLabMergeRequestDecodeError", |
| 161 | { |
| 162 | ...gitLabCliDecodeErrorContext, |
| 163 | operation: Schema.Literal("getMergeRequest"), |
| 164 | reference: Schema.String, |
| 165 | }, |
| 166 | ) { |
| 167 | get detail(): string { |
| 168 | return "GitLab CLI returned invalid merge request JSON."; |
| 169 | } |
| 170 | |
| 171 | override get message(): string { |
| 172 | return `GitLab CLI failed in ${this.operation}: ${this.detail}`; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | export class GitLabRepositoryDecodeError extends Schema.TaggedErrorClass<GitLabRepositoryDecodeError>()( |
| 177 | "GitLabRepositoryDecodeError", |
| 178 | { |
| 179 | ...gitLabCliDecodeErrorContext, |
| 180 | operation: Schema.Literals(["getRepositoryCloneUrls", "createRepository", "getDefaultBranch"]), |
| 181 | repository: Schema.optional(Schema.String), |
| 182 | }, |
| 183 | ) { |
| 184 | get detail(): string { |
| 185 | return "GitLab CLI returned invalid repository JSON."; |
| 186 | } |
| 187 | |
| 188 | override get message(): string { |
| 189 | return `GitLab CLI failed in ${this.operation}: ${this.detail}`; |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | export class GitLabNamespaceDecodeError extends Schema.TaggedErrorClass<GitLabNamespaceDecodeError>()( |
| 194 | "GitLabNamespaceDecodeError", |
| 195 | { |
| 196 | ...gitLabCliDecodeErrorContext, |
| 197 | operation: Schema.Literal("createRepository"), |
| 198 | namespacePath: Schema.String, |
| 199 | }, |
| 200 | ) { |
| 201 | get detail(): string { |
| 202 | return "GitLab CLI returned invalid namespace JSON."; |
| 203 | } |
| 204 | |
| 205 | override get message(): string { |
| 206 | return `GitLab CLI failed in ${this.operation}: ${this.detail}`; |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | export const GitLabCliError = Schema.Union([ |
| 211 | GitLabCliUnavailableError, |
| 212 | GitLabCliAuthenticationError, |
| 213 | GitLabMergeRequestNotFoundError, |
| 214 | GitLabCliCommandError, |
| 215 | GitLabMergeRequestListDecodeError, |
| 216 | GitLabMergeRequestDecodeError, |
| 217 | GitLabRepositoryDecodeError, |
| 218 | GitLabNamespaceDecodeError, |
| 219 | ]); |
| 220 | export type GitLabCliError = typeof GitLabCliError.Type; |
| 221 | export const isGitLabCliError = Schema.is(GitLabCliError); |
| 222 | |
| 86 | 223 | export interface GitLabMergeRequestSummary { |
| 87 | 224 | readonly number: number; |
| 88 | 225 | readonly title: string; |
| ... | ... | @@ -102,17 +239,6 @@ export interface GitLabRepositoryCloneUrls { |
| 102 | 239 | readonly sshUrl: string; |
| 103 | 240 | } |
| 104 | 241 | |
| 105 | | function errorText(error: VcsError | unknown): string { |
| 106 | | if (typeof error === "object" && error !== null) { |
| 107 | | const tag = "_tag" in error && typeof error._tag === "string" ? error._tag : ""; |
| 108 | | const detail = "detail" in error && typeof error.detail === "string" ? error.detail : ""; |
| 109 | | const message = "message" in error && typeof error.message === "string" ? error.message : ""; |
| 110 | | return [tag, detail, message].filter(Boolean).join("\n"); |
| 111 | | } |
| 112 | | |
| 113 | | return String(error); |
| 114 | | } |
| 115 | | |
| 116 | 242 | export class GitLabCli extends Context.Service< |
| 117 | 243 | GitLabCli, |
| 118 | 244 | { |
| ... | ... | @@ -168,15 +294,6 @@ export class GitLabCli extends Context.Service< |
| 168 | 294 | } |
| 169 | 295 | >()("t3/sourceControl/GitLabCli") {} |
| 170 | 296 | |
| 171 | | function isVcsProcessSpawnError(error: unknown): boolean { |
| 172 | | return ( |
| 173 | | typeof error === "object" && |
| 174 | | error !== null && |
| 175 | | "_tag" in error && |
| 176 | | error._tag === "VcsProcessSpawnError" |
| 177 | | ); |
| 178 | | } |
| 179 | | |
| 180 | 297 | const RawGitLabRepositoryCloneUrlsSchema = Schema.Struct({ |
| 181 | 298 | path_with_namespace: TrimmedNonEmptyString, |
| 182 | 299 | web_url: TrimmedNonEmptyString, |
| ... | ... | @@ -192,6 +309,14 @@ const RawGitLabNamespaceSchema = Schema.Struct({ |
| 192 | 309 | id: Schema.Number, |
| 193 | 310 | }); |
| 194 | 311 | |
| 312 | const decodeGitLabRepositoryCloneUrls = Schema.decodeEffect( |
| 313 | Schema.fromJsonString(RawGitLabRepositoryCloneUrlsSchema), |
| 314 | ); |
| 315 | const decodeGitLabDefaultBranch = Schema.decodeEffect( |
| 316 | Schema.fromJsonString(RawGitLabDefaultBranchSchema), |
| 317 | ); |
| 318 | const decodeGitLabNamespace = Schema.decodeEffect(Schema.fromJsonString(RawGitLabNamespaceSchema)); |
| 319 | |
| 195 | 320 | function normalizeRepositoryCloneUrls( |
| 196 | 321 | raw: Schema.Schema.Type<typeof RawGitLabRepositoryCloneUrlsSchema>, |
| 197 | 322 | ): GitLabRepositoryCloneUrls { |
| ... | ... | @@ -202,27 +327,6 @@ function normalizeRepositoryCloneUrls( |
| 202 | 327 | }; |
| 203 | 328 | } |
| 204 | 329 | |
| 205 | | function decodeGitLabJson<S extends Schema.Top>( |
| 206 | | raw: string, |
| 207 | | schema: S, |
| 208 | | operation: "getRepositoryCloneUrls" | "getDefaultBranch" | "createRepository", |
| 209 | | invalidDetail: string, |
| 210 | | cwd: string, |
| 211 | | ): Effect.Effect<S["Type"], GitLabCliError, S["DecodingServices"]> { |
| 212 | | return Schema.decodeEffect(Schema.fromJsonString(schema))(raw).pipe( |
| 213 | | Effect.mapError( |
| 214 | | (error) => |
| 215 | | new GitLabCliError({ |
| 216 | | operation, |
| 217 | | command: "glab", |
| 218 | | cwd, |
| 219 | | detail: invalidDetail, |
| 220 | | cause: error, |
| 221 | | }), |
| 222 | | ), |
| 223 | | ); |
| 224 | | } |
| 225 | | |
| 226 | 330 | function stateArgs(state: "open" | "closed" | "merged" | "all"): ReadonlyArray<string> { |
| 227 | 331 | switch (state) { |
| 228 | 332 | case "open": |
| ... | ... | @@ -283,7 +387,10 @@ function parseRepositoryPath(repository: string): { |
| 283 | 387 | export const make = Effect.gen(function* () { |
| 284 | 388 | const process = yield* VcsProcess.VcsProcess; |
| 285 | 389 | |
| 286 | | const execute: GitLabCli["Service"]["execute"] = (input) => |
| 390 | const run = ( |
| 391 | input: Parameters<GitLabCli["Service"]["execute"]>[0], |
| 392 | mapError: (error: VcsError) => GitLabCliError, |
| 393 | ) => |
| 287 | 394 | process |
| 288 | 395 | .run({ |
| 289 | 396 | operation: "GitLabCli.execute", |
| ... | ... | @@ -292,14 +399,32 @@ export const make = Effect.gen(function* () { |
| 292 | 399 | cwd: input.cwd, |
| 293 | 400 | timeoutMs: input.timeoutMs ?? DEFAULT_TIMEOUT_MS, |
| 294 | 401 | }) |
| 295 | | .pipe( |
| 296 | | Effect.mapError((error) => |
| 297 | | GitLabCliError.fromVcsError( |
| 298 | | { operation: "execute", command: "glab", cwd: input.cwd }, |
| 299 | | error, |
| 300 | | ), |
| 301 | | ), |
| 302 | | ); |
| 402 | .pipe(Effect.mapError(mapError)); |
| 403 | |
| 404 | const execute: GitLabCli["Service"]["execute"] = (input) => |
| 405 | run(input, (error) => |
| 406 | GitLabCliCommandError.fromVcsError( |
| 407 | { operation: "execute", command: "glab", cwd: input.cwd }, |
| 408 | error, |
| 409 | ), |
| 410 | ); |
| 411 | |
| 412 | const executeMergeRequest = (input: { |
| 413 | readonly cwd: string; |
| 414 | readonly reference: string; |
| 415 | readonly args: ReadonlyArray<string>; |
| 416 | }) => |
| 417 | run(input, (error) => |
| 418 | GitLabMergeRequestNotFoundError.fromVcsError( |
| 419 | { |
| 420 | operation: "execute", |
| 421 | command: "glab", |
| 422 | cwd: input.cwd, |
| 423 | reference: input.reference, |
| 424 | }, |
| 425 | error, |
| 426 | ), |
| 427 | ); |
| 303 | 428 | |
| 304 | 429 | return GitLabCli.of({ |
| 305 | 430 | execute, |
| ... | ... | @@ -326,11 +451,10 @@ export const make = Effect.gen(function* () { |
| 326 | 451 | Effect.flatMap((decoded) => { |
| 327 | 452 | if (!Result.isSuccess(decoded)) { |
| 328 | 453 | return Effect.fail( |
| 329 | | new GitLabCliError({ |
| 454 | new GitLabMergeRequestListDecodeError({ |
| 330 | 455 | operation: "listMergeRequests", |
| 331 | 456 | command: "glab", |
| 332 | 457 | cwd: input.cwd, |
| 333 | | detail: "GitLab CLI returned invalid MR list JSON.", |
| 334 | 458 | cause: decoded.failure, |
| 335 | 459 | }), |
| 336 | 460 | ); |
| ... | ... | @@ -342,8 +466,9 @@ export const make = Effect.gen(function* () { |
| 342 | 466 | ), |
| 343 | 467 | ), |
| 344 | 468 | getMergeRequest: (input) => |
| 345 | | execute({ |
| 469 | executeMergeRequest({ |
| 346 | 470 | cwd: input.cwd, |
| 471 | reference: input.reference, |
| 347 | 472 | args: ["mr", "view", input.reference, "--output", "json"], |
| 348 | 473 | }).pipe( |
| 349 | 474 | Effect.map((result) => result.stdout.trim()), |
| ... | ... | @@ -352,11 +477,11 @@ export const make = Effect.gen(function* () { |
| 352 | 477 | Effect.flatMap((decoded) => { |
| 353 | 478 | if (!Result.isSuccess(decoded)) { |
| 354 | 479 | return Effect.fail( |
| 355 | | new GitLabCliError({ |
| 480 | new GitLabMergeRequestDecodeError({ |
| 356 | 481 | operation: "getMergeRequest", |
| 357 | 482 | command: "glab", |
| 358 | 483 | cwd: input.cwd, |
| 359 | | detail: "GitLab CLI returned invalid merge request JSON.", |
| 484 | reference: input.reference, |
| 360 | 485 | cause: decoded.failure, |
| 361 | 486 | }), |
| 362 | 487 | ); |
| ... | ... | @@ -374,12 +499,17 @@ export const make = Effect.gen(function* () { |
| 374 | 499 | }).pipe( |
| 375 | 500 | Effect.map((result) => result.stdout.trim()), |
| 376 | 501 | Effect.flatMap((raw) => |
| 377 | | decodeGitLabJson( |
| 378 | | raw, |
| 379 | | RawGitLabRepositoryCloneUrlsSchema, |
| 380 | | "getRepositoryCloneUrls", |
| 381 | | "GitLab CLI returned invalid repository JSON.", |
| 382 | | input.cwd, |
| 502 | decodeGitLabRepositoryCloneUrls(raw).pipe( |
| 503 | Effect.mapError( |
| 504 | (cause) => |
| 505 | new GitLabRepositoryDecodeError({ |
| 506 | operation: "getRepositoryCloneUrls", |
| 507 | command: "glab", |
| 508 | cwd: input.cwd, |
| 509 | repository: input.repository, |
| 510 | cause, |
| 511 | }), |
| 512 | ), |
| 383 | 513 | ), |
| 384 | 514 | ), |
| 385 | 515 | Effect.map(normalizeRepositoryCloneUrls), |
| ... | ... | @@ -393,12 +523,17 @@ export const make = Effect.gen(function* () { |
| 393 | 523 | }).pipe( |
| 394 | 524 | Effect.map((result) => result.stdout.trim()), |
| 395 | 525 | Effect.flatMap((raw) => |
| 396 | | decodeGitLabJson( |
| 397 | | raw, |
| 398 | | RawGitLabNamespaceSchema, |
| 399 | | "createRepository", |
| 400 | | "GitLab CLI returned invalid namespace JSON.", |
| 401 | | input.cwd, |
| 526 | decodeGitLabNamespace(raw).pipe( |
| 527 | Effect.mapError( |
| 528 | (cause) => |
| 529 | new GitLabNamespaceDecodeError({ |
| 530 | operation: "createRepository", |
| 531 | command: "glab", |
| 532 | cwd: input.cwd, |
| 533 | namespacePath, |
| 534 | cause, |
| 535 | }), |
| 536 | ), |
| 402 | 537 | ), |
| 403 | 538 | ), |
| 404 | 539 | Effect.map((namespace) => namespace.id), |
| ... | ... | @@ -428,12 +563,17 @@ export const make = Effect.gen(function* () { |
| 428 | 563 | ), |
| 429 | 564 | Effect.map((result) => result.stdout.trim()), |
| 430 | 565 | Effect.flatMap((raw) => |
| 431 | | decodeGitLabJson( |
| 432 | | raw, |
| 433 | | RawGitLabRepositoryCloneUrlsSchema, |
| 434 | | "createRepository", |
| 435 | | "GitLab CLI returned invalid repository JSON.", |
| 436 | | input.cwd, |
| 566 | decodeGitLabRepositoryCloneUrls(raw).pipe( |
| 567 | Effect.mapError( |
| 568 | (cause) => |
| 569 | new GitLabRepositoryDecodeError({ |
| 570 | operation: "createRepository", |
| 571 | command: "glab", |
| 572 | cwd: input.cwd, |
| 573 | repository: input.repository, |
| 574 | cause, |
| 575 | }), |
| 576 | ), |
| 437 | 577 | ), |
| 438 | 578 | ), |
| 439 | 579 | Effect.map(normalizeRepositoryCloneUrls), |
| ... | ... | @@ -467,19 +607,24 @@ export const make = Effect.gen(function* () { |
| 467 | 607 | }).pipe( |
| 468 | 608 | Effect.map((result) => result.stdout.trim()), |
| 469 | 609 | Effect.flatMap((raw) => |
| 470 | | decodeGitLabJson( |
| 471 | | raw, |
| 472 | | RawGitLabDefaultBranchSchema, |
| 473 | | "getDefaultBranch", |
| 474 | | "GitLab CLI returned invalid repository JSON.", |
| 475 | | input.cwd, |
| 610 | decodeGitLabDefaultBranch(raw).pipe( |
| 611 | Effect.mapError( |
| 612 | (cause) => |
| 613 | new GitLabRepositoryDecodeError({ |
| 614 | operation: "getDefaultBranch", |
| 615 | command: "glab", |
| 616 | cwd: input.cwd, |
| 617 | cause, |
| 618 | }), |
| 619 | ), |
| 476 | 620 | ), |
| 477 | 621 | ), |
| 478 | 622 | Effect.map((value) => value.default_branch ?? null), |
| 479 | 623 | ), |
| 480 | 624 | checkoutMergeRequest: (input) => |
| 481 | | execute({ |
| 625 | executeMergeRequest({ |
| 482 | 626 | cwd: input.cwd, |
| 627 | reference: input.reference, |
| 483 | 628 | args: ["mr", "checkout", input.reference], |
| 484 | 629 | }).pipe(Effect.asVoid), |
| 485 | 630 | }); |