| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Schema from "effect/Schema"; |
| 3 | import { |
| 4 | NonNegativeInt, |
| 5 | PositiveInt, |
| 6 | ProjectId, |
| 7 | ThreadId, |
| 8 | TrimmedNonEmptyString, |
| 9 | } from "./baseSchemas.ts"; |
| 10 | import { SourceControlProviderError, SourceControlProviderInfo } from "./sourceControl.ts"; |
| 11 | import { VcsDriverKind } from "./vcs.ts"; |
| 12 | |
| 13 | const TrimmedNonEmptyStringSchema = TrimmedNonEmptyString; |
| 14 | const GitFilePath = Schema.String.check(Schema.isNonEmpty()); |
| 15 | const GIT_LIST_BRANCHES_MAX_LIMIT = 200; |
| 16 | |
| 17 | // Domain Types |
| 18 | |
| 19 | export const GitStackedAction = Schema.Literals([ |
| 20 | "commit", |
| 21 | "push", |
| 22 | "create_pr", |
| 23 | "commit_push", |
| 24 | "commit_push_pr", |
| 25 | ]); |
| 26 | export type GitStackedAction = typeof GitStackedAction.Type; |
| 27 | export const GitActionProgressPhase = Schema.Literals(["branch", "commit", "push", "pr"]); |
| 28 | export type GitActionProgressPhase = typeof GitActionProgressPhase.Type; |
| 29 | export const GitActionProgressKind = Schema.Literals([ |
| 30 | "action_started", |
| 31 | "phase_started", |
| 32 | "hook_started", |
| 33 | "hook_output", |
| 34 | "hook_finished", |
| 35 | "action_finished", |
| 36 | "action_failed", |
| 37 | ]); |
| 38 | export type GitActionProgressKind = typeof GitActionProgressKind.Type; |
| 39 | export const GitActionProgressStream = Schema.Literals(["stdout", "stderr"]); |
| 40 | export type GitActionProgressStream = typeof GitActionProgressStream.Type; |
| 41 | const GitCommitStepStatus = Schema.Literals([ |
| 42 | "created", |
| 43 | "skipped_no_changes", |
| 44 | "skipped_not_requested", |
| 45 | ]); |
| 46 | const GitPushStepStatus = Schema.Literals([ |
| 47 | "pushed", |
| 48 | "skipped_not_requested", |
| 49 | "skipped_up_to_date", |
| 50 | ]); |
| 51 | const GitBranchStepStatus = Schema.Literals(["created", "skipped_not_requested"]); |
| 52 | const GitPrStepStatus = Schema.Literals(["created", "opened_existing", "skipped_not_requested"]); |
| 53 | const VcsStatusChangeRequestState = Schema.Literals(["open", "closed", "merged"]); |
| 54 | const GitPullRequestReference = TrimmedNonEmptyStringSchema; |
| 55 | const GitPullRequestState = Schema.Literals(["open", "closed", "merged"]); |
| 56 | const GitPreparePullRequestThreadMode = Schema.Literals(["local", "worktree"]); |
| 57 | export const GitRunStackedActionToastRunAction = Schema.Struct({ |
| 58 | kind: GitStackedAction, |
| 59 | }); |
| 60 | export type GitRunStackedActionToastRunAction = typeof GitRunStackedActionToastRunAction.Type; |
| 61 | const GitRunStackedActionToastCta = Schema.Union([ |
| 62 | Schema.Struct({ |
| 63 | kind: Schema.Literal("none"), |
| 64 | }), |
| 65 | Schema.Struct({ |
| 66 | kind: Schema.Literal("open_pr"), |
| 67 | label: TrimmedNonEmptyStringSchema, |
| 68 | url: Schema.String, |
| 69 | }), |
| 70 | Schema.Struct({ |
| 71 | kind: Schema.Literal("run_action"), |
| 72 | label: TrimmedNonEmptyStringSchema, |
| 73 | action: GitRunStackedActionToastRunAction, |
| 74 | }), |
| 75 | ]); |
| 76 | export type GitRunStackedActionToastCta = typeof GitRunStackedActionToastCta.Type; |
| 77 | const GitRunStackedActionToast = Schema.Struct({ |
| 78 | title: TrimmedNonEmptyStringSchema, |
| 79 | description: Schema.optional(TrimmedNonEmptyStringSchema), |
| 80 | cta: GitRunStackedActionToastCta, |
| 81 | }); |
| 82 | export type GitRunStackedActionToast = typeof GitRunStackedActionToast.Type; |
| 83 | |
| 84 | export const VcsRef = Schema.Struct({ |
| 85 | name: TrimmedNonEmptyStringSchema, |
| 86 | isRemote: Schema.optional(Schema.Boolean), |
| 87 | remoteName: Schema.optional(TrimmedNonEmptyStringSchema), |
| 88 | current: Schema.Boolean, |
| 89 | isDefault: Schema.Boolean, |
| 90 | worktreePath: TrimmedNonEmptyStringSchema.pipe(Schema.NullOr), |
| 91 | }); |
| 92 | export type VcsRef = typeof VcsRef.Type; |
| 93 | |
| 94 | const VcsWorktree = Schema.Struct({ |
| 95 | path: TrimmedNonEmptyStringSchema, |
| 96 | refName: TrimmedNonEmptyStringSchema, |
| 97 | }); |
| 98 | const GitResolvedPullRequest = Schema.Struct({ |
| 99 | number: PositiveInt, |
| 100 | title: TrimmedNonEmptyStringSchema, |
| 101 | url: Schema.String, |
| 102 | baseBranch: TrimmedNonEmptyStringSchema, |
| 103 | headBranch: TrimmedNonEmptyStringSchema, |
| 104 | state: GitPullRequestState, |
| 105 | }); |
| 106 | export type GitResolvedPullRequest = typeof GitResolvedPullRequest.Type; |
| 107 | |
| 108 | // RPC Inputs |
| 109 | |
| 110 | export const VcsStatusInput = Schema.Struct({ |
| 111 | cwd: TrimmedNonEmptyStringSchema, |
| 112 | }); |
| 113 | export type VcsStatusInput = typeof VcsStatusInput.Type; |
| 114 | |
| 115 | export const VcsStatusSubscriptionInput = Schema.Struct({ |
| 116 | ...VcsStatusInput.fields, |
| 117 | /** Passive observers receive cached remote status without retaining its refresh loop. */ |
| 118 | includeRemote: Schema.optional(Schema.Boolean), |
| 119 | }); |
| 120 | export type VcsStatusSubscriptionInput = typeof VcsStatusSubscriptionInput.Type; |
| 121 | |
| 122 | export const VcsPullInput = Schema.Struct({ |
| 123 | cwd: TrimmedNonEmptyStringSchema, |
| 124 | }); |
| 125 | export type VcsPullInput = typeof VcsPullInput.Type; |
| 126 | |
| 127 | export const GitRunStackedActionInput = Schema.Struct({ |
| 128 | actionId: TrimmedNonEmptyStringSchema, |
| 129 | cwd: TrimmedNonEmptyStringSchema, |
| 130 | action: GitStackedAction, |
| 131 | commitMessage: Schema.optional(TrimmedNonEmptyStringSchema.check(Schema.isMaxLength(10_000))), |
| 132 | featureBranch: Schema.optional(Schema.Boolean), |
| 133 | filePaths: Schema.optional(Schema.Array(GitFilePath).check(Schema.isMinLength(1))), |
| 134 | /** The thread the action runs beside; a pull request it creates is linked to it. */ |
| 135 | threadId: Schema.optional(ThreadId), |
| 136 | projectId: Schema.optional(ProjectId), |
| 137 | }); |
| 138 | export type GitRunStackedActionInput = typeof GitRunStackedActionInput.Type; |
| 139 | |
| 140 | export const VcsListRefsInput = Schema.Struct({ |
| 141 | cwd: TrimmedNonEmptyStringSchema, |
| 142 | query: Schema.optional(TrimmedNonEmptyStringSchema.check(Schema.isMaxLength(256))), |
| 143 | cursor: Schema.optional(NonNegativeInt), |
| 144 | includeMatchingRemoteRefs: Schema.optional(Schema.Boolean), |
| 145 | refKind: Schema.optional(Schema.Literals(["all", "local", "remote"])), |
| 146 | refresh: Schema.optional(Schema.Boolean), |
| 147 | limit: Schema.optional( |
| 148 | PositiveInt.check(Schema.isLessThanOrEqualTo(GIT_LIST_BRANCHES_MAX_LIMIT)), |
| 149 | ), |
| 150 | }); |
| 151 | export type VcsListRefsInput = typeof VcsListRefsInput.Type; |
| 152 | |
| 153 | export const VcsCreateWorktreeInput = Schema.Struct({ |
| 154 | cwd: TrimmedNonEmptyStringSchema, |
| 155 | refName: TrimmedNonEmptyStringSchema, |
| 156 | newRefName: Schema.optional(TrimmedNonEmptyStringSchema), |
| 157 | baseRefName: Schema.optional(TrimmedNonEmptyStringSchema), |
| 158 | path: Schema.NullOr(TrimmedNonEmptyStringSchema), |
| 159 | }); |
| 160 | export type VcsCreateWorktreeInput = typeof VcsCreateWorktreeInput.Type; |
| 161 | |
| 162 | export const GitPullRequestRefInput = Schema.Struct({ |
| 163 | cwd: TrimmedNonEmptyStringSchema, |
| 164 | reference: GitPullRequestReference, |
| 165 | }); |
| 166 | export type GitPullRequestRefInput = typeof GitPullRequestRefInput.Type; |
| 167 | |
| 168 | export const GitPreparePullRequestThreadInput = Schema.Struct({ |
| 169 | cwd: TrimmedNonEmptyStringSchema, |
| 170 | reference: GitPullRequestReference, |
| 171 | mode: GitPreparePullRequestThreadMode, |
| 172 | threadId: Schema.optional(ThreadId), |
| 173 | }); |
| 174 | export type GitPreparePullRequestThreadInput = typeof GitPreparePullRequestThreadInput.Type; |
| 175 | |
| 176 | export const VcsRemoveWorktreeInput = Schema.Struct({ |
| 177 | cwd: TrimmedNonEmptyStringSchema, |
| 178 | path: TrimmedNonEmptyStringSchema, |
| 179 | force: Schema.optional(Schema.Boolean), |
| 180 | }); |
| 181 | export type VcsRemoveWorktreeInput = typeof VcsRemoveWorktreeInput.Type; |
| 182 | |
| 183 | export const VcsCreateRefInput = Schema.Struct({ |
| 184 | cwd: TrimmedNonEmptyStringSchema, |
| 185 | refName: TrimmedNonEmptyStringSchema, |
| 186 | switchRef: Schema.optional(Schema.Boolean), |
| 187 | }); |
| 188 | export type VcsCreateRefInput = typeof VcsCreateRefInput.Type; |
| 189 | |
| 190 | export const VcsCreateRefResult = Schema.Struct({ |
| 191 | refName: TrimmedNonEmptyStringSchema, |
| 192 | }); |
| 193 | export type VcsCreateRefResult = typeof VcsCreateRefResult.Type; |
| 194 | |
| 195 | export const VcsSwitchRefInput = Schema.Struct({ |
| 196 | cwd: TrimmedNonEmptyStringSchema, |
| 197 | refName: TrimmedNonEmptyStringSchema, |
| 198 | }); |
| 199 | export type VcsSwitchRefInput = typeof VcsSwitchRefInput.Type; |
| 200 | |
| 201 | export const VcsInitInput = Schema.Struct({ |
| 202 | cwd: TrimmedNonEmptyStringSchema, |
| 203 | kind: Schema.optional(VcsDriverKind), |
| 204 | }); |
| 205 | export type VcsInitInput = typeof VcsInitInput.Type; |
| 206 | |
| 207 | // RPC Results |
| 208 | |
| 209 | const VcsStatusChangeRequest = Schema.Struct({ |
| 210 | number: PositiveInt, |
| 211 | title: TrimmedNonEmptyStringSchema, |
| 212 | url: Schema.String, |
| 213 | baseRef: TrimmedNonEmptyStringSchema, |
| 214 | headRef: TrimmedNonEmptyStringSchema, |
| 215 | state: VcsStatusChangeRequestState, |
| 216 | /** Optional for compatibility with older servers and providers. */ |
| 217 | isDraft: Schema.optional(Schema.Boolean), |
| 218 | /** |
| 219 | * Last provider-side activity (ISO), including comments and metadata edits. |
| 220 | * This is not the time a change request closed or merged. Optional for old |
| 221 | * servers and providers whose lookups do not report it. |
| 222 | */ |
| 223 | updatedAt: Schema.optional(Schema.NullOr(Schema.String)), |
| 224 | }); |
| 225 | |
| 226 | const VcsStatusLocalShape = { |
| 227 | isRepo: Schema.Boolean, |
| 228 | isJjWorkspace: Schema.optional(Schema.Boolean), |
| 229 | sourceControlProvider: Schema.optional(SourceControlProviderInfo), |
| 230 | hasPrimaryRemote: Schema.Boolean, |
| 231 | isDefaultRef: Schema.Boolean, |
| 232 | refName: Schema.NullOr(TrimmedNonEmptyStringSchema), |
| 233 | hasWorkingTreeChanges: Schema.Boolean, |
| 234 | workingTree: Schema.Struct({ |
| 235 | files: Schema.Array( |
| 236 | Schema.Struct({ |
| 237 | path: GitFilePath, |
| 238 | insertions: NonNegativeInt, |
| 239 | deletions: NonNegativeInt, |
| 240 | }), |
| 241 | ), |
| 242 | insertions: NonNegativeInt, |
| 243 | deletions: NonNegativeInt, |
| 244 | }), |
| 245 | /** |
| 246 | * Totals for the diff panel's Changes view: merge-base with the base branch to the |
| 247 | * working tree, untracked files included. Absent on older servers. |
| 248 | */ |
| 249 | branchChanges: Schema.optional( |
| 250 | Schema.Struct({ |
| 251 | baseRef: Schema.NullOr(TrimmedNonEmptyStringSchema), |
| 252 | insertions: NonNegativeInt, |
| 253 | deletions: NonNegativeInt, |
| 254 | }), |
| 255 | ), |
| 256 | }; |
| 257 | |
| 258 | const VcsStatusRemoteShape = { |
| 259 | hasUpstream: Schema.Boolean, |
| 260 | aheadCount: NonNegativeInt, |
| 261 | behindCount: NonNegativeInt, |
| 262 | aheadOfDefaultCount: Schema.optional(NonNegativeInt), |
| 263 | pr: Schema.NullOr(VcsStatusChangeRequest), |
| 264 | }; |
| 265 | |
| 266 | export const VcsStatusLocalResult = Schema.Struct(VcsStatusLocalShape); |
| 267 | export type VcsStatusLocalResult = typeof VcsStatusLocalResult.Type; |
| 268 | |
| 269 | export const VcsStatusRemoteResult = Schema.Struct(VcsStatusRemoteShape); |
| 270 | export type VcsStatusRemoteResult = typeof VcsStatusRemoteResult.Type; |
| 271 | |
| 272 | export const VcsStatusResult = Schema.Struct({ |
| 273 | ...VcsStatusLocalShape, |
| 274 | ...VcsStatusRemoteShape, |
| 275 | }); |
| 276 | export type VcsStatusResult = typeof VcsStatusResult.Type; |
| 277 | |
| 278 | export const VcsStatusStreamEvent = Schema.Union([ |
| 279 | Schema.TaggedStruct("snapshot", { |
| 280 | local: VcsStatusLocalResult, |
| 281 | remote: Schema.NullOr(VcsStatusRemoteResult), |
| 282 | }), |
| 283 | Schema.TaggedStruct("localUpdated", { |
| 284 | local: VcsStatusLocalResult, |
| 285 | }), |
| 286 | Schema.TaggedStruct("remoteUpdated", { |
| 287 | remote: Schema.NullOr(VcsStatusRemoteResult), |
| 288 | }), |
| 289 | ]); |
| 290 | export type VcsStatusStreamEvent = typeof VcsStatusStreamEvent.Type; |
| 291 | |
| 292 | export const VcsListRefsResult = Schema.Struct({ |
| 293 | refs: Schema.Array(VcsRef), |
| 294 | isRepo: Schema.Boolean, |
| 295 | hasPrimaryRemote: Schema.Boolean, |
| 296 | nextCursor: NonNegativeInt.pipe(Schema.NullOr), |
| 297 | totalCount: NonNegativeInt, |
| 298 | }); |
| 299 | export type VcsListRefsResult = typeof VcsListRefsResult.Type; |
| 300 | |
| 301 | export const VcsCreateWorktreeResult = Schema.Struct({ |
| 302 | worktree: VcsWorktree, |
| 303 | }); |
| 304 | export type VcsCreateWorktreeResult = typeof VcsCreateWorktreeResult.Type; |
| 305 | |
| 306 | export const GitResolvePullRequestResult = Schema.Struct({ |
| 307 | pullRequest: GitResolvedPullRequest, |
| 308 | }); |
| 309 | export type GitResolvePullRequestResult = typeof GitResolvePullRequestResult.Type; |
| 310 | |
| 311 | export const GitPreparePullRequestThreadResult = Schema.Struct({ |
| 312 | pullRequest: GitResolvedPullRequest, |
| 313 | branch: TrimmedNonEmptyStringSchema, |
| 314 | worktreePath: TrimmedNonEmptyStringSchema.pipe(Schema.NullOr), |
| 315 | /** |
| 316 | * False when the checkout could not be brought to the pull request head — a reused worktree |
| 317 | * holding local commits or uncommitted changes keeps its own state, so the code being handed |
| 318 | * over is older than the pull request. |
| 319 | */ |
| 320 | isOnPullRequestHead: Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(true))), |
| 321 | }); |
| 322 | export type GitPreparePullRequestThreadResult = typeof GitPreparePullRequestThreadResult.Type; |
| 323 | |
| 324 | export const VcsSwitchRefResult = Schema.Struct({ |
| 325 | refName: Schema.NullOr(TrimmedNonEmptyStringSchema), |
| 326 | }); |
| 327 | export type VcsSwitchRefResult = typeof VcsSwitchRefResult.Type; |
| 328 | |
| 329 | export const GitRunStackedActionResult = Schema.Struct({ |
| 330 | action: GitStackedAction, |
| 331 | branch: Schema.Struct({ |
| 332 | status: GitBranchStepStatus, |
| 333 | name: Schema.optional(TrimmedNonEmptyStringSchema), |
| 334 | }), |
| 335 | commit: Schema.Struct({ |
| 336 | status: GitCommitStepStatus, |
| 337 | commitSha: Schema.optional(TrimmedNonEmptyStringSchema), |
| 338 | subject: Schema.optional(TrimmedNonEmptyStringSchema), |
| 339 | }), |
| 340 | push: Schema.Struct({ |
| 341 | status: GitPushStepStatus, |
| 342 | branch: Schema.optional(TrimmedNonEmptyStringSchema), |
| 343 | upstreamBranch: Schema.optional(TrimmedNonEmptyStringSchema), |
| 344 | setUpstream: Schema.optional(Schema.Boolean), |
| 345 | }), |
| 346 | pr: Schema.Struct({ |
| 347 | status: GitPrStepStatus, |
| 348 | url: Schema.optional(Schema.String), |
| 349 | number: Schema.optional(PositiveInt), |
| 350 | baseBranch: Schema.optional(TrimmedNonEmptyStringSchema), |
| 351 | headBranch: Schema.optional(TrimmedNonEmptyStringSchema), |
| 352 | title: Schema.optional(TrimmedNonEmptyStringSchema), |
| 353 | }), |
| 354 | toast: GitRunStackedActionToast, |
| 355 | }); |
| 356 | export type GitRunStackedActionResult = typeof GitRunStackedActionResult.Type; |
| 357 | |
| 358 | export const VcsPullResult = Schema.Struct({ |
| 359 | status: Schema.Literals(["pulled", "skipped_up_to_date"]), |
| 360 | refName: TrimmedNonEmptyStringSchema, |
| 361 | upstreamRef: TrimmedNonEmptyStringSchema.pipe(Schema.NullOr), |
| 362 | }); |
| 363 | export type VcsPullResult = typeof VcsPullResult.Type; |
| 364 | |
| 365 | // RPC / domain errors |
| 366 | |
| 367 | // Well-known git failures, recognized from stderr at the driver and carried as |
| 368 | // a closed set of diagnostic tags. Git's stderr itself stays off the error: it |
| 369 | // echoes argv and remote URLs, which can hold credentials. The tag names the |
| 370 | // cause for logs and callers; it does not select a message. |
| 371 | export const GitCommandFailureReason = Schema.Literals([ |
| 372 | "authentication_failed", |
| 373 | "branch_already_exists", |
| 374 | "branch_checked_out_in_worktree", |
| 375 | "host_key_unverified", |
| 376 | "not_a_repository", |
| 377 | "path_already_exists", |
| 378 | "remote_unreachable", |
| 379 | "tag_would_be_clobbered", |
| 380 | ]); |
| 381 | export type GitCommandFailureReason = typeof GitCommandFailureReason.Type; |
| 382 | |
| 383 | export class GitCommandError extends Schema.TaggedError<GitCommandError>()("GitCommandError", { |
| 384 | operation: Schema.String, |
| 385 | command: Schema.String, |
| 386 | cwd: Schema.String, |
| 387 | argumentCount: Schema.optional(Schema.Number), |
| 388 | exitCode: Schema.optional(Schema.Number), |
| 389 | stdoutLength: Schema.optional(Schema.Number), |
| 390 | stderrLength: Schema.optional(Schema.Number), |
| 391 | outputLength: Schema.optional(Schema.Number), |
| 392 | reason: Schema.optional(GitCommandFailureReason), |
| 393 | detail: Schema.String, |
| 394 | cause: Schema.optional(Schema.Defect()), |
| 395 | }) { |
| 396 | override get message(): string { |
| 397 | const reason = this.reason === undefined ? "" : ` (${this.reason})`; |
| 398 | return `Git command failed in ${this.operation} (${this.cwd}): ${this.detail}${reason}`; |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | export class TextGenerationError extends Schema.TaggedError<TextGenerationError>()( |
| 403 | "TextGenerationError", |
| 404 | { |
| 405 | operation: Schema.String, |
| 406 | detail: Schema.String, |
| 407 | cause: Schema.optional(Schema.Defect()), |
| 408 | }, |
| 409 | ) { |
| 410 | override get message(): string { |
| 411 | return `Text generation failed in ${this.operation}: ${this.detail}`; |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | export class GitManagerError extends Schema.TaggedError<GitManagerError>()("GitManagerError", { |
| 416 | operation: Schema.String, |
| 417 | cwd: Schema.String, |
| 418 | detail: Schema.String, |
| 419 | cause: Schema.optional(Schema.Defect()), |
| 420 | }) { |
| 421 | override get message(): string { |
| 422 | return `Git manager failed in ${this.operation}: ${this.detail}`; |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | export class GitPullRequestMaterializationError extends Schema.TaggedError<GitPullRequestMaterializationError>()( |
| 427 | "GitPullRequestMaterializationError", |
| 428 | { |
| 429 | cwd: TrimmedNonEmptyStringSchema, |
| 430 | pullRequestNumber: PositiveInt, |
| 431 | headRepository: Schema.NullOr(TrimmedNonEmptyStringSchema), |
| 432 | headBranch: TrimmedNonEmptyStringSchema, |
| 433 | localBranch: TrimmedNonEmptyStringSchema, |
| 434 | cause: Schema.Defect(), |
| 435 | }, |
| 436 | ) { |
| 437 | override get message(): string { |
| 438 | return `Failed to materialize pull request #${this.pullRequestNumber} branch ${this.headBranch} as ${this.localBranch}.`; |
| 439 | } |
| 440 | } |
| 441 | |
| 442 | export const GitManagerServiceError = Schema.Union([ |
| 443 | GitManagerError, |
| 444 | GitPullRequestMaterializationError, |
| 445 | GitCommandError, |
| 446 | SourceControlProviderError, |
| 447 | TextGenerationError, |
| 448 | ]); |
| 449 | export type GitManagerServiceError = typeof GitManagerServiceError.Type; |
| 450 | |
| 451 | const GitActionProgressBase = Schema.Struct({ |
| 452 | actionId: TrimmedNonEmptyStringSchema, |
| 453 | cwd: TrimmedNonEmptyStringSchema, |
| 454 | action: GitStackedAction, |
| 455 | }); |
| 456 | |
| 457 | const GitActionStartedEvent = Schema.Struct({ |
| 458 | ...GitActionProgressBase.fields, |
| 459 | kind: Schema.Literal("action_started"), |
| 460 | phases: Schema.Array(GitActionProgressPhase), |
| 461 | }); |
| 462 | const GitActionPhaseStartedEvent = Schema.Struct({ |
| 463 | ...GitActionProgressBase.fields, |
| 464 | kind: Schema.Literal("phase_started"), |
| 465 | phase: GitActionProgressPhase, |
| 466 | label: TrimmedNonEmptyStringSchema, |
| 467 | }); |
| 468 | const GitActionHookStartedEvent = Schema.Struct({ |
| 469 | ...GitActionProgressBase.fields, |
| 470 | kind: Schema.Literal("hook_started"), |
| 471 | hookName: TrimmedNonEmptyStringSchema, |
| 472 | }); |
| 473 | const GitActionHookOutputEvent = Schema.Struct({ |
| 474 | ...GitActionProgressBase.fields, |
| 475 | kind: Schema.Literal("hook_output"), |
| 476 | hookName: Schema.NullOr(TrimmedNonEmptyStringSchema), |
| 477 | stream: GitActionProgressStream, |
| 478 | text: TrimmedNonEmptyStringSchema, |
| 479 | }); |
| 480 | const GitActionHookFinishedEvent = Schema.Struct({ |
| 481 | ...GitActionProgressBase.fields, |
| 482 | kind: Schema.Literal("hook_finished"), |
| 483 | hookName: TrimmedNonEmptyStringSchema, |
| 484 | exitCode: Schema.NullOr(Schema.Int), |
| 485 | durationMs: Schema.NullOr(NonNegativeInt), |
| 486 | }); |
| 487 | const GitActionFinishedEvent = Schema.Struct({ |
| 488 | ...GitActionProgressBase.fields, |
| 489 | kind: Schema.Literal("action_finished"), |
| 490 | result: GitRunStackedActionResult, |
| 491 | }); |
| 492 | const GitActionFailedEvent = Schema.Struct({ |
| 493 | ...GitActionProgressBase.fields, |
| 494 | kind: Schema.Literal("action_failed"), |
| 495 | phase: Schema.NullOr(GitActionProgressPhase), |
| 496 | message: TrimmedNonEmptyStringSchema, |
| 497 | }); |
| 498 | |
| 499 | export const GitActionProgressEvent = Schema.Union([ |
| 500 | GitActionStartedEvent, |
| 501 | GitActionPhaseStartedEvent, |
| 502 | GitActionHookStartedEvent, |
| 503 | GitActionHookOutputEvent, |
| 504 | GitActionHookFinishedEvent, |
| 505 | GitActionFinishedEvent, |
| 506 | GitActionFailedEvent, |
| 507 | ]); |
| 508 | export type GitActionProgressEvent = typeof GitActionProgressEvent.Type; |