// @effect-diagnostics nodeBuiltinImport:offimport * as NodeFS from "node:fs";import * as NodePath from "node:path";import * as NodeChildProcess from "node:child_process"; import * as NodeServices from "@effect/platform-node/NodeServices";import { it } from "@effect/vitest";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Logger from "effect/Logger";import * as Option from "effect/Option";import * as PlatformError from "effect/PlatformError";import * as References from "effect/References";import * as Scope from "effect/Scope";import { ChildProcessSpawner } from "effect/unstable/process";import { expect } from "vite-plus/test";import type { GitActionProgressEvent, GitPreparePullRequestThreadInput, ThreadId,} from "@t3tools/contracts"; import { DEFAULT_SERVER_SETTINGS, GitCommandError, ProviderDriverKind, ProviderInstanceId, TextGenerationError,} from "@t3tools/contracts";import * as GitHubCli from "../sourceControl/GitHubCli.ts";import * as TextGeneration from "../textGeneration/TextGeneration.ts";import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";import * as VcsProcess from "../vcs/VcsProcess.ts";import * as GitHubSourceControlProvider from "../sourceControl/GitHubSourceControlProvider.ts";import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";import * as ServerConfig from "../config.ts";import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts";import * as ProviderRegistry from "../provider/Services/ProviderRegistry.ts";import * as ServerSettings from "../serverSettings.ts";import * as GitManager from "./GitManager.ts"; interface FakeGhScenario { prListSequence?: string[]; prListByHeadSelector?: Record<string, string>; prListSequenceByHeadSelector?: Record<string, string[]>; createdPrUrl?: string; defaultBranch?: string; pullRequest?: { number: number; title: string; url: string; baseRefName: string; headRefName: string; state?: "open" | "closed" | "merged"; isDraft?: boolean; isCrossRepository?: boolean; headRepositoryNameWithOwner?: string | null; headRepositoryOwnerLogin?: string | null; }; repositoryCloneUrls?: Record<string, { url: string; sshUrl: string }>; failWith?: GitHubCli.GitHubCliError; /** Let this many gh calls succeed before failWith kicks in (default 0 = fail immediately). */ failAfterCalls?: number;} function fakeGhOutput(stdout: string): VcsProcess.VcsProcessOutput { return { exitCode: ChildProcessSpawner.ExitCode(0), stdout, stderr: "", stdoutTruncated: false, stderrTruncated: false, };} type FakeGitTextGeneration = TextGeneration.TextGeneration["Service"]; type FakePullRequest = NonNullable<FakeGhScenario["pullRequest"]>; function normalizeFakePullRequestSummary(raw: unknown): GitHubCli.GitHubPullRequestSummary | null { if (!raw || typeof raw !== "object") { return null; } const record = raw as Record<string, unknown>; const number = record.number; const title = record.title; const url = record.url; const baseRefName = record.baseRefName; const headRefName = record.headRefName; const headRepository = typeof record.headRepository === "object" && record.headRepository !== null ? (record.headRepository as Record<string, unknown>) : null; const headRepositoryOwner = typeof record.headRepositoryOwner === "object" && record.headRepositoryOwner !== null ? (record.headRepositoryOwner as Record<string, unknown>) : null; if ( typeof number !== "number" || typeof title !== "string" || typeof url !== "string" || typeof baseRefName !== "string" || typeof headRefName !== "string" ) { return null; } const state = typeof record.state === "string" ? record.state === "OPEN" || record.state === "open" ? "open" : record.state === "CLOSED" || record.state === "closed" ? "closed" : "merged" : undefined; const isDraft = typeof record.isDraft === "boolean" ? record.isDraft : undefined; const isCrossRepository = typeof record.isCrossRepository === "boolean" ? record.isCrossRepository : undefined; const headRepositoryNameWithOwner = typeof record.headRepositoryNameWithOwner === "string" ? record.headRepositoryNameWithOwner : typeof headRepository?.nameWithOwner === "string" ? headRepository.nameWithOwner : undefined; const headRepositoryOwnerLogin = typeof record.headRepositoryOwnerLogin === "string" ? record.headRepositoryOwnerLogin : typeof headRepositoryOwner?.login === "string" ? headRepositoryOwner.login : undefined; return { number, title, url, baseRefName, headRefName, ...(state ? { state } : {}), ...(isDraft === true ? { isDraft: true } : {}), ...(isCrossRepository !== undefined ? { isCrossRepository } : {}), ...(headRepositoryNameWithOwner ? { headRepositoryNameWithOwner } : {}), ...(headRepositoryOwnerLogin ? { headRepositoryOwnerLogin } : {}), };} function runGitSyncForFakeGh(cwd: string, args: readonly string[]): void { const result = NodeChildProcess.spawnSync("git", args, { cwd, encoding: "utf8", }); if (result.status === 0) { return; } throw new Error( `Failed to simulate gh checkout with git ${args.join(" ")}: ${result.stderr?.trim() || "unknown error"}`, );} function makeTempDir( prefix: string,): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ prefix }); });} function removePath( targetPath: string,): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.remove(targetPath, { recursive: true, force: true }); });} function makeDirectory( dirPath: string,): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(dirPath, { recursive: true }); });} function runGit( cwd: string, args: readonly string[], allowNonZeroExit = false,): Effect.Effect< { readonly exitCode: GitVcsDriver.ExecuteGitResult["exitCode"]; readonly stdout: string; readonly stderr: string; }, GitCommandError, GitVcsDriver.GitVcsDriver> { return Effect.gen(function* () { const git = yield* GitVcsDriver.GitVcsDriver; const result = yield* git.execute({ operation: "GitManager.test.runGit", cwd, args, allowNonZeroExit, }); return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr, }; });} function initRepo( cwd: string,): Effect.Effect< void, PlatformError.PlatformError | GitCommandError, FileSystem.FileSystem | Scope.Scope | GitVcsDriver.GitVcsDriver> { return Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; yield* runGit(cwd, ["init", "--initial-branch=main"]); yield* runGit(cwd, ["config", "user.email", "test@example.com"]); yield* runGit(cwd, ["config", "user.name", "Test User"]); yield* fs.writeFileString(NodePath.join(cwd, "README.md"), "hello\n"); yield* runGit(cwd, ["add", "README.md"]); yield* runGit(cwd, ["commit", "-m", "Initial commit"]); });} function createBareRemote(): Effect.Effect< string, PlatformError.PlatformError | GitCommandError, FileSystem.FileSystem | Scope.Scope | GitVcsDriver.GitVcsDriver> { return Effect.gen(function* () { const remoteDir = yield* makeTempDir("t3code-git-remote-"); yield* runGit(remoteDir, ["init", "--bare"]); return remoteDir; });} function configureRemote( cwd: string, remoteName: string, remotePath: string, fetchNamespace: string,): Effect.Effect<void, GitCommandError, GitVcsDriver.GitVcsDriver> { return Effect.gen(function* () { yield* runGit(cwd, ["config", `remote.${remoteName}.url`, remotePath]); yield* runGit(cwd, [ "config", "--replace-all", `remote.${remoteName}.fetch`, `+refs/heads/*:refs/remotes/${fetchNamespace}/*`, ]); });} function configureVisibleRemoteUrlWithLocalRewrite( cwd: string, remoteName: string, visibleUrl: string, localRemotePath: string,): Effect.Effect<void, GitCommandError, GitVcsDriver.GitVcsDriver> { return Effect.gen(function* () { yield* runGit(cwd, ["config", `remote.${remoteName}.url`, visibleUrl]); yield* runGit(cwd, ["config", `url.${localRemotePath}.insteadOf`, visibleUrl]); });} function createTextGeneration( overrides: Partial<FakeGitTextGeneration> = {},): TextGeneration.TextGeneration["Service"] { const implementation: FakeGitTextGeneration = { generateCommitMessage: (input) => Effect.succeed({ subject: "Implement stacked git actions", body: "", ...(input.includeBranch ? { branch: "feature/implement-stacked-git-actions" } : {}), }), generatePrContent: () => Effect.succeed({ title: "Add stacked git actions", body: "## Summary\n- Add stacked git workflow\n\n## Testing\n- Not run", }), generateBranchName: () => Effect.succeed({ branch: "update-workflow", }), generateThreadTitle: () => Effect.succeed({ title: "Update workflow", }), ...overrides, }; return { generateCommitMessage: (input) => implementation.generateCommitMessage(input).pipe( Effect.mapError( (cause) => new TextGenerationError({ operation: "generateCommitMessage", detail: "fake text generation failed", ...(cause !== undefined ? { cause } : {}), }), ), ), generatePrContent: (input) => implementation.generatePrContent(input).pipe( Effect.mapError( (cause) => new TextGenerationError({ operation: "generatePrContent", detail: "fake text generation failed", ...(cause !== undefined ? { cause } : {}), }), ), ), generateBranchName: (input) => implementation.generateBranchName(input).pipe( Effect.mapError( (cause) => new TextGenerationError({ operation: "generateBranchName", detail: "fake text generation failed", ...(cause !== undefined ? { cause } : {}), }), ), ), generateThreadTitle: (input) => implementation.generateThreadTitle(input).pipe( Effect.mapError( (cause) => new TextGenerationError({ operation: "generateThreadTitle", detail: "fake text generation failed", ...(cause !== undefined ? { cause } : {}), }), ), ), };} function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { service: GitHubCli.GitHubCli["Service"]; ghCalls: string[];} { const prListQueue = [...(scenario.prListSequence ?? [])]; const prListQueueByHeadSelector = new Map( Object.entries(scenario.prListSequenceByHeadSelector ?? {}).map(([headSelector, values]) => [ headSelector, [...values], ]), ); const ghCalls: string[] = []; const execute: GitHubCli.GitHubCli["Service"]["execute"] = (input) => { const args = [...input.args]; ghCalls.push(args.join(" ")); if (scenario.failWith && ghCalls.length > (scenario.failAfterCalls ?? 0)) { return Effect.fail(scenario.failWith); } if (args[0] === "pr" && args[1] === "list") { const headSelectorIndex = args.findIndex((value) => value === "--head"); const headSelector = headSelectorIndex >= 0 && headSelectorIndex < args.length - 1 ? args[headSelectorIndex + 1] : undefined; const mappedQueue = typeof headSelector === "string" ? prListQueueByHeadSelector.get(headSelector)?.shift() : undefined; const mappedStdout = typeof headSelector === "string" ? scenario.prListByHeadSelector?.[headSelector] : undefined; const stdout = (mappedQueue ?? mappedStdout ?? prListQueue.shift() ?? "[]") + "\n"; return Effect.succeed(fakeGhOutput(stdout)); } if (args[0] === "pr" && args[1] === "create") { return Effect.succeed( fakeGhOutput( (scenario.createdPrUrl ?? "https://github.com/pingdotgg/codething-mvp/pull/101") + "\n", ), ); } if (args[0] === "pr" && args[1] === "view") { const pullRequest: FakePullRequest = scenario.pullRequest ?? { number: 101, title: "Pull request", url: "https://github.com/pingdotgg/codething-mvp/pull/101", baseRefName: "main", headRefName: "feature/pull-request", state: "open", }; return Effect.succeed( fakeGhOutput( JSON.stringify({ ...pullRequest, ...(pullRequest.headRepositoryNameWithOwner ? { headRepository: { nameWithOwner: pullRequest.headRepositoryNameWithOwner, }, } : {}), ...(pullRequest.headRepositoryOwnerLogin ? { headRepositoryOwner: { login: pullRequest.headRepositoryOwnerLogin, }, } : {}), }) + "\n", ), ); } if (args[0] === "pr" && args[1] === "checkout") { return Effect.try({ try: () => { const headBranch = scenario.pullRequest?.headRefName; if (headBranch) { const existingBranch = NodeChildProcess.spawnSync( "git", ["show-ref", "--verify", "--quiet", `refs/heads/${headBranch}`], { cwd: input.cwd, encoding: "utf8", }, ); if (existingBranch.status === 0) { runGitSyncForFakeGh(input.cwd, ["checkout", headBranch]); } else { runGitSyncForFakeGh(input.cwd, ["checkout", "-b", headBranch]); } } return fakeGhOutput(""); }, catch: (error) => GitHubCli.isGitHubCliError(error) ? error : new GitHubCli.GitHubCliCommandError({ command: "gh", cwd: input.cwd, cause: error, }), }); } if (args[0] === "repo" && args[1] === "view") { const repository = args[2]; if (typeof repository === "string" && args.includes("nameWithOwner,url,sshUrl")) { const cloneUrls = scenario.repositoryCloneUrls?.[repository]; if (!cloneUrls) { return Effect.fail( new GitHubCli.GitHubCliCommandError({ command: "gh", cwd: input.cwd, cause: new Error(`Unexpected repository lookup: ${repository}`), }), ); } return Effect.succeed( fakeGhOutput( JSON.stringify({ nameWithOwner: repository, url: cloneUrls.url, sshUrl: cloneUrls.sshUrl, }) + "\n", ), ); } return Effect.succeed(fakeGhOutput(`${scenario.defaultBranch ?? "main"}\n`)); } return Effect.fail( new GitHubCli.GitHubCliCommandError({ command: "gh", cwd: input.cwd, cause: new Error(`Unexpected gh command: ${args.join(" ")}`), }), ); }; return { service: { execute,Lines 1–500 of 5612. Load more
// @effect-diagnostics nodeBuiltinImport:off import * as NodeFS from "node:fs"; import * as NodePath from "node:path"; import * as NodeChildProcess from "node:child_process"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Logger from "effect/Logger"; import * as Option from "effect/Option"; import * as PlatformError from "effect/PlatformError"; import * as References from "effect/References"; import * as Scope from "effect/Scope"; import { ChildProcessSpawner } from "effect/unstable/process"; import { expect } from "vite-plus/test"; import type { GitActionProgressEvent, GitPreparePullRequestThreadInput, ThreadId, } from "@t3tools/contracts"; import { DEFAULT_SERVER_SETTINGS, GitCommandError, ProviderDriverKind, ProviderInstanceId, TextGenerationError, } from "@t3tools/contracts"; import * as GitHubCli from "../sourceControl/GitHubCli.ts"; import * as TextGeneration from "../textGeneration/TextGeneration.ts"; import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as GitHubSourceControlProvider from "../sourceControl/GitHubSourceControlProvider.ts"; import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts"; import * as ServerConfig from "../config.ts"; import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts"; import * as ProviderRegistry from "../provider/Services/ProviderRegistry.ts"; import * as ServerSettings from "../serverSettings.ts"; import * as GitManager from "./GitManager.ts"; interface FakeGhScenario { prListSequence?: string[]; prListByHeadSelector?: Record<string, string>; prListSequenceByHeadSelector?: Record<string, string[]>; createdPrUrl?: string; defaultBranch?: string; pullRequest?: { number: number; title: string; url: string; baseRefName: string; headRefName: string; state?: "open" | "closed" | "merged"; isDraft?: boolean; isCrossRepository?: boolean; headRepositoryNameWithOwner?: string | null; headRepositoryOwnerLogin?: string | null; }; repositoryCloneUrls?: Record<string, { url: string; sshUrl: string }>; failWith?: GitHubCli.GitHubCliError; /** Let this many gh calls succeed before failWith kicks in (default 0 = fail immediately). */ failAfterCalls?: number; } function fakeGhOutput(stdout: string): VcsProcess.VcsProcessOutput { return { exitCode: ChildProcessSpawner.ExitCode(0), stdout, stderr: "", stdoutTruncated: false, stderrTruncated: false, }; } type FakeGitTextGeneration = TextGeneration.TextGeneration["Service"]; type FakePullRequest = NonNullable<FakeGhScenario["pullRequest"]>; function normalizeFakePullRequestSummary(raw: unknown): GitHubCli.GitHubPullRequestSummary | null { if (!raw || typeof raw !== "object") { return null; } const record = raw as Record<string, unknown>; const number = record.number; const title = record.title; const url = record.url; const baseRefName = record.baseRefName; const headRefName = record.headRefName; const headRepository = typeof record.headRepository === "object" && record.headRepository !== null ? (record.headRepository as Record<string, unknown>) : null; const headRepositoryOwner = typeof record.headRepositoryOwner === "object" && record.headRepositoryOwner !== null ? (record.headRepositoryOwner as Record<string, unknown>) : null; if ( typeof number !== "number" || typeof title !== "string" || typeof url !== "string" || typeof baseRefName !== "string" || typeof headRefName !== "string" ) { return null; } const state = typeof record.state === "string" ? record.state === "OPEN" || record.state === "open" ? "open" : record.state === "CLOSED" || record.state === "closed" ? "closed" : "merged" : undefined; const isDraft = typeof record.isDraft === "boolean" ? record.isDraft : undefined; const isCrossRepository = typeof record.isCrossRepository === "boolean" ? record.isCrossRepository : undefined; const headRepositoryNameWithOwner = typeof record.headRepositoryNameWithOwner === "string" ? record.headRepositoryNameWithOwner : typeof headRepository?.nameWithOwner === "string" ? headRepository.nameWithOwner : undefined; const headRepositoryOwnerLogin = typeof record.headRepositoryOwnerLogin === "string" ? record.headRepositoryOwnerLogin : typeof headRepositoryOwner?.login === "string" ? headRepositoryOwner.login : undefined; return { number, title, url, baseRefName, headRefName, ...(state ? { state } : {}), ...(isDraft === true ? { isDraft: true } : {}), ...(isCrossRepository !== undefined ? { isCrossRepository } : {}), ...(headRepositoryNameWithOwner ? { headRepositoryNameWithOwner } : {}), ...(headRepositoryOwnerLogin ? { headRepositoryOwnerLogin } : {}), }; } function runGitSyncForFakeGh(cwd: string, args: readonly string[]): void { const result = NodeChildProcess.spawnSync("git", args, { cwd, encoding: "utf8", }); if (result.status === 0) { return; } throw new Error( `Failed to simulate gh checkout with git ${args.join(" ")}: ${result.stderr?.trim() || "unknown error"}`, ); } function makeTempDir( prefix: string, ): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ prefix }); }); } function removePath( targetPath: string, ): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.remove(targetPath, { recursive: true, force: true }); }); } function makeDirectory( dirPath: string, ): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(dirPath, { recursive: true }); }); } function runGit( cwd: string, args: readonly string[], allowNonZeroExit = false, ): Effect.Effect< { readonly exitCode: GitVcsDriver.ExecuteGitResult["exitCode"]; readonly stdout: string; readonly stderr: string; }, GitCommandError, GitVcsDriver.GitVcsDriver > { return Effect.gen(function* () { const git = yield* GitVcsDriver.GitVcsDriver; const result = yield* git.execute({ operation: "GitManager.test.runGit", cwd, args, allowNonZeroExit, }); return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr, }; }); } function initRepo( cwd: string, ): Effect.Effect< void, PlatformError.PlatformError | GitCommandError, FileSystem.FileSystem | Scope.Scope | GitVcsDriver.GitVcsDriver > { return Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; yield* runGit(cwd, ["init", "--initial-branch=main"]); yield* runGit(cwd, ["config", "user.email", "test@example.com"]); yield* runGit(cwd, ["config", "user.name", "Test User"]); yield* fs.writeFileString(NodePath.join(cwd, "README.md"), "hello\n"); yield* runGit(cwd, ["add", "README.md"]); yield* runGit(cwd, ["commit", "-m", "Initial commit"]); }); } function createBareRemote(): Effect.Effect< string, PlatformError.PlatformError | GitCommandError, FileSystem.FileSystem | Scope.Scope | GitVcsDriver.GitVcsDriver > { return Effect.gen(function* () { const remoteDir = yield* makeTempDir("t3code-git-remote-"); yield* runGit(remoteDir, ["init", "--bare"]); return remoteDir; }); } function configureRemote( cwd: string, remoteName: string, remotePath: string, fetchNamespace: string, ): Effect.Effect<void, GitCommandError, GitVcsDriver.GitVcsDriver> { return Effect.gen(function* () { yield* runGit(cwd, ["config", `remote.${remoteName}.url`, remotePath]); yield* runGit(cwd, [ "config", "--replace-all", `remote.${remoteName}.fetch`, `+refs/heads/*:refs/remotes/${fetchNamespace}/*`, ]); }); } function configureVisibleRemoteUrlWithLocalRewrite( cwd: string, remoteName: string, visibleUrl: string, localRemotePath: string, ): Effect.Effect<void, GitCommandError, GitVcsDriver.GitVcsDriver> { return Effect.gen(function* () { yield* runGit(cwd, ["config", `remote.${remoteName}.url`, visibleUrl]); yield* runGit(cwd, ["config", `url.${localRemotePath}.insteadOf`, visibleUrl]); }); } function createTextGeneration( overrides: Partial<FakeGitTextGeneration> = {}, ): TextGeneration.TextGeneration["Service"] { const implementation: FakeGitTextGeneration = { generateCommitMessage: (input) => Effect.succeed({ subject: "Implement stacked git actions", body: "", ...(input.includeBranch ? { branch: "feature/implement-stacked-git-actions" } : {}), }), generatePrContent: () => Effect.succeed({ title: "Add stacked git actions", body: "## Summary\n- Add stacked git workflow\n\n## Testing\n- Not run", }), generateBranchName: () => Effect.succeed({ branch: "update-workflow", }), generateThreadTitle: () => Effect.succeed({ title: "Update workflow", }), ...overrides, }; return { generateCommitMessage: (input) => implementation.generateCommitMessage(input).pipe( Effect.mapError( (cause) => new TextGenerationError({ operation: "generateCommitMessage", detail: "fake text generation failed", ...(cause !== undefined ? { cause } : {}), }), ), ), generatePrContent: (input) => implementation.generatePrContent(input).pipe( Effect.mapError( (cause) => new TextGenerationError({ operation: "generatePrContent", detail: "fake text generation failed", ...(cause !== undefined ? { cause } : {}), }), ), ), generateBranchName: (input) => implementation.generateBranchName(input).pipe( Effect.mapError( (cause) => new TextGenerationError({ operation: "generateBranchName", detail: "fake text generation failed", ...(cause !== undefined ? { cause } : {}), }), ), ), generateThreadTitle: (input) => implementation.generateThreadTitle(input).pipe( Effect.mapError( (cause) => new TextGenerationError({ operation: "generateThreadTitle", detail: "fake text generation failed", ...(cause !== undefined ? { cause } : {}), }), ), ), }; } function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { service: GitHubCli.GitHubCli["Service"]; ghCalls: string[]; } { const prListQueue = [...(scenario.prListSequence ?? [])]; const prListQueueByHeadSelector = new Map( Object.entries(scenario.prListSequenceByHeadSelector ?? {}).map(([headSelector, values]) => [ headSelector, [...values], ]), ); const ghCalls: string[] = []; const execute: GitHubCli.GitHubCli["Service"]["execute"] = (input) => { const args = [...input.args]; ghCalls.push(args.join(" ")); if (scenario.failWith && ghCalls.length > (scenario.failAfterCalls ?? 0)) { return Effect.fail(scenario.failWith); } if (args[0] === "pr" && args[1] === "list") { const headSelectorIndex = args.findIndex((value) => value === "--head"); const headSelector = headSelectorIndex >= 0 && headSelectorIndex < args.length - 1 ? args[headSelectorIndex + 1] : undefined; const mappedQueue = typeof headSelector === "string" ? prListQueueByHeadSelector.get(headSelector)?.shift() : undefined; const mappedStdout = typeof headSelector === "string" ? scenario.prListByHeadSelector?.[headSelector] : undefined; const stdout = (mappedQueue ?? mappedStdout ?? prListQueue.shift() ?? "[]") + "\n"; return Effect.succeed(fakeGhOutput(stdout)); } if (args[0] === "pr" && args[1] === "create") { return Effect.succeed( fakeGhOutput( (scenario.createdPrUrl ?? "https://github.com/pingdotgg/codething-mvp/pull/101") + "\n", ), ); } if (args[0] === "pr" && args[1] === "view") { const pullRequest: FakePullRequest = scenario.pullRequest ?? { number: 101, title: "Pull request", url: "https://github.com/pingdotgg/codething-mvp/pull/101", baseRefName: "main", headRefName: "feature/pull-request", state: "open", }; return Effect.succeed( fakeGhOutput( JSON.stringify({ ...pullRequest, ...(pullRequest.headRepositoryNameWithOwner ? { headRepository: { nameWithOwner: pullRequest.headRepositoryNameWithOwner, }, } : {}), ...(pullRequest.headRepositoryOwnerLogin ? { headRepositoryOwner: { login: pullRequest.headRepositoryOwnerLogin, }, } : {}), }) + "\n", ), ); } if (args[0] === "pr" && args[1] === "checkout") { return Effect.try({ try: () => { const headBranch = scenario.pullRequest?.headRefName; if (headBranch) { const existingBranch = NodeChildProcess.spawnSync( "git", ["show-ref", "--verify", "--quiet", `refs/heads/${headBranch}`], { cwd: input.cwd, encoding: "utf8", }, ); if (existingBranch.status === 0) { runGitSyncForFakeGh(input.cwd, ["checkout", headBranch]); } else { runGitSyncForFakeGh(input.cwd, ["checkout", "-b", headBranch]); } } return fakeGhOutput(""); }, catch: (error) => GitHubCli.isGitHubCliError(error) ? error : new GitHubCli.GitHubCliCommandError({ command: "gh", cwd: input.cwd, cause: error, }), }); } if (args[0] === "repo" && args[1] === "view") { const repository = args[2]; if (typeof repository === "string" && args.includes("nameWithOwner,url,sshUrl")) { const cloneUrls = scenario.repositoryCloneUrls?.[repository]; if (!cloneUrls) { return Effect.fail( new GitHubCli.GitHubCliCommandError({ command: "gh", cwd: input.cwd, cause: new Error(`Unexpected repository lookup: ${repository}`), }), ); } return Effect.succeed( fakeGhOutput( JSON.stringify({ nameWithOwner: repository, url: cloneUrls.url, sshUrl: cloneUrls.sshUrl, }) + "\n", ), ); } return Effect.succeed(fakeGhOutput(`${scenario.defaultBranch ?? "main"}\n`)); } return Effect.fail( new GitHubCli.GitHubCliCommandError({ command: "gh", cwd: input.cwd, cause: new Error(`Unexpected gh command: ${args.join(" ")}`), }), ); }; return { service: { execute,