From 08b1222d0bae670433c275ec70165c7b5048726f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 7 Oct 2026 17:26:24 -0700 Subject: [PATCH] refactor(server): GitHub services are named for the API they call, not gh (#16967) Co-authored-by: Claude Opus 5.5 (1M context) --- apps/server/scripts/evaluate-thread-titles.ts | 4 +- apps/server/scripts/measure-pr-preview.ts | 4 +- apps/server/src/git/GitManager.test.ts | 79 +++-- apps/server/src/git/GitManager.ts | 2 +- .../ThreadPullRequestService.ts | 2 +- ...i.test.ts => GitHubPullRequestApi.test.ts} | 272 +++++++++--------- ...lRequestCli.ts => GitHubPullRequestApi.ts} | 159 +++++----- .../GitHubPullRequestProvider.test.ts | 54 ++-- .../pullRequest/GitHubPullRequestProvider.ts | 12 +- .../PullRequestProviderRegistry.ts | 6 +- .../src/pullRequest/PullRequestService.ts | 2 +- .../pullRequest/gitHubConditionalChecks.ts | 4 +- apps/server/src/server.ts | 6 +- ...li.test.ts => GitHubRepositoryApi.test.ts} | 64 +++-- .../{GitHubCli.ts => GitHubRepositoryApi.ts} | 141 +++++---- .../GitHubSourceControlProvider.test.ts | 26 +- .../GitHubSourceControlProvider.ts | 14 +- .../SourceControlDiscovery.test.ts | 4 +- .../SourceControlProviderRegistry.test.ts | 6 +- apps/server/src/ws.ts | 4 +- 20 files changed, 410 insertions(+), 455 deletions(-) rename apps/server/src/pullRequest/{GitHubPullRequestCli.test.ts => GitHubPullRequestApi.test.ts} (94%) rename apps/server/src/pullRequest/{GitHubPullRequestCli.ts => GitHubPullRequestApi.ts} (96%) rename apps/server/src/sourceControl/{GitHubCli.test.ts => GitHubRepositoryApi.test.ts} (91%) rename apps/server/src/sourceControl/{GitHubCli.ts => GitHubRepositoryApi.ts} (90%) diff --git a/apps/server/scripts/evaluate-thread-titles.ts b/apps/server/scripts/evaluate-thread-titles.ts index 414bd99d07459bcc736088cfc2c63dfe70bcc9b7..a8d2f9d986d1c2e470bf2fdfcde77bd30599e80e 100644 --- a/apps/server/scripts/evaluate-thread-titles.ts +++ b/apps/server/scripts/evaluate-thread-titles.ts @@ -23,7 +23,7 @@ import { } from "../src/textGeneration/ThreadTitleContext.ts"; import * as ThreadTitleLinks from "../src/textGeneration/ThreadTitleLinks.ts"; import * as SourceControlProviderRegistry from "../src/sourceControl/SourceControlProviderRegistry.ts"; -import * as GitHubCli from "../src/sourceControl/GitHubCli.ts"; +import * as GitHubRepositoryApi from "../src/sourceControl/GitHubRepositoryApi.ts"; import * as GitLabCli from "../src/sourceControl/GitLabCli.ts"; import * as ForgejoCli from "../src/sourceControl/ForgejoCli.ts"; import * as AzureDevOpsCli from "../src/sourceControl/AzureDevOpsCli.ts"; @@ -152,7 +152,7 @@ await Effect.runPromise( SourceControlProviderRegistry.layer.pipe( Layer.provide( Layer.mergeAll( - GitHubCli.layer, + GitHubRepositoryApi.layer, GitLabCli.layer, ForgejoCli.layer, AzureDevOpsCli.layer, diff --git a/apps/server/scripts/measure-pr-preview.ts b/apps/server/scripts/measure-pr-preview.ts index c4a688b1aa7a91c36819af5b0b78b1d377c47afc..f1fc491b1a2bb655669c40ab8856d057af79f075 100644 --- a/apps/server/scripts/measure-pr-preview.ts +++ b/apps/server/scripts/measure-pr-preview.ts @@ -10,7 +10,7 @@ import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import { FetchHttpClient } from "effect/http"; -import * as GitHubPullRequestCli from "../src/pullRequest/GitHubPullRequestCli.ts"; +import * as GitHubPullRequestApi from "../src/pullRequest/GitHubPullRequestApi.ts"; import * as GitHubPullRequestProvider from "../src/pullRequest/GitHubPullRequestProvider.ts"; import * as GitHubApi from "../src/sourceControl/GitHubApi.ts"; import * as GitHubCredentials from "../src/sourceControl/GitHubCredentials.ts"; @@ -73,7 +73,7 @@ const measuredApi = Layer.effect( Layer.provide(NodeServices.layer), ); -const services = GitHubPullRequestCli.layer.pipe( +const services = GitHubPullRequestApi.layer.pipe( Layer.provideMerge(measuredApi), Layer.provideMerge(VcsProcess.layer), Layer.provideMerge(NodeServices.layer), diff --git a/apps/server/src/git/GitManager.test.ts b/apps/server/src/git/GitManager.test.ts index 61dd000633ef31c96d7b09b7216d1ebef8b9e644..f30af2c23c5b5554f3415f9660e68974e4a31c80 100644 --- a/apps/server/src/git/GitManager.test.ts +++ b/apps/server/src/git/GitManager.test.ts @@ -42,7 +42,7 @@ import { } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; import * as GitHubApi from "../sourceControl/GitHubApi.ts"; -import * as GitHubCli from "../sourceControl/GitHubCli.ts"; +import * as GitHubRepositoryApi from "../sourceControl/GitHubRepositoryApi.ts"; import { decodeGitHubPullRequestListJson } from "../sourceControl/gitHubPullRequests.ts"; import * as GitLabCli from "../sourceControl/GitLabCli.ts"; import * as TextGeneration from "../textGeneration/TextGeneration.ts"; @@ -92,7 +92,7 @@ interface FakeGhScenario { headRepositoryOwnerLogin?: string | null; }; repositoryCloneUrls?: Record; - failWith?: GitHubCli.GitHubCliError; + failWith?: GitHubRepositoryApi.GitHubRepositoryApiError; /** Let this many gh calls succeed before failWith kicks in (default 0 = fail immediately). */ failAfterCalls?: number; } @@ -111,7 +111,9 @@ type FakeGitTextGeneration = TextGeneration.TextGeneration["Service"]; type FakePullRequest = NonNullable; -function normalizeFakePullRequestSummary(raw: unknown): GitHubCli.GitHubPullRequestSummary | null { +function normalizeFakePullRequestSummary( + raw: unknown, +): GitHubRepositoryApi.GitHubPullRequestSummary | null { if (!raw || typeof raw !== "object") { return null; } @@ -381,8 +383,8 @@ function createTextGeneration( }; } -function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { - service: GitHubCli.GitHubCli["Service"]; +function createGitHubRepositoryApiWithFakeGh(scenario: FakeGhScenario = {}): { + service: GitHubRepositoryApi.GitHubRepositoryApi["Service"]; ghCalls: string[]; } { const prListQueue = [...(scenario.prListSequence ?? [])]; @@ -398,7 +400,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { const execute = (input: { readonly cwd: string; readonly args: ReadonlyArray; - }): Effect.Effect => { + }): Effect.Effect => { const args = [...input.args]; ghCalls.push(args.join(" ")); @@ -486,10 +488,9 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { return fakeGhOutput(""); }, catch: (error) => - GitHubCli.isGitHubCliError(error) + GitHubRepositoryApi.isGitHubRepositoryApiError(error) ? error - : new GitHubCli.GitHubCliCommandError({ - command: "gh", + : new GitHubRepositoryApi.GitHubRepositoryCommandError({ cwd: input.cwd, cause: error, }), @@ -502,8 +503,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { const cloneUrls = scenario.repositoryCloneUrls?.[repository]; if (!cloneUrls) { return Effect.fail( - new GitHubCli.GitHubCliCommandError({ - command: "gh", + new GitHubRepositoryApi.GitHubRepositoryCommandError({ cwd: input.cwd, cause: new Error(`Unexpected repository lookup: ${repository}`), }), @@ -523,8 +523,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { } return Effect.fail( - new GitHubCli.GitHubCliCommandError({ - command: "gh", + new GitHubRepositoryApi.GitHubRepositoryCommandError({ cwd: input.cwd, cause: new Error(`Unexpected gh command: ${args.join(" ")}`), }), @@ -577,7 +576,9 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { Effect.map((raw) => raw .map((entry) => normalizeFakePullRequestSummary(entry)) - .filter((entry): entry is GitHubCli.GitHubPullRequestSummary => entry !== null), + .filter( + (entry): entry is GitHubRepositoryApi.GitHubPullRequestSummary => entry !== null, + ), ), ), createPullRequest: (input) => @@ -617,7 +618,9 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { "number,title,url,baseRefName,headRefName,state,isDraft,mergedAt,closedAt,isCrossRepository,headRepository,headRepositoryOwner", ], }).pipe( - Effect.map((result) => JSON.parse(result.stdout) as GitHubCli.GitHubPullRequestSummary), + Effect.map( + (result) => JSON.parse(result.stdout) as GitHubRepositoryApi.GitHubPullRequestSummary, + ), ), getRepositoryCloneUrls: (input) => execute({ @@ -626,8 +629,7 @@ function createGitHubCliWithFakeGh(scenario: FakeGhScenario = {}): { }).pipe(Effect.map((result) => JSON.parse(result.stdout))), createRepository: (input) => Effect.fail( - new GitHubCli.GitHubCliCommandError({ - command: "gh", + new GitHubRepositoryApi.GitHubRepositoryCommandError({ cwd: input.cwd, cause: new Error(`Unexpected repository create: ${input.repository}`), }), @@ -691,7 +693,9 @@ function makeManager(input?: { ProjectionStore.ProjectionStoreV2 | ProjectStore.ProjectStoreV2 >; }) { - const { service: gitHubCli, ghCalls } = createGitHubCliWithFakeGh(input?.ghScenario); + const { service: gitHubRepositoryApi, ghCalls } = createGitHubRepositoryApiWithFakeGh( + input?.ghScenario, + ); const textGeneration = createTextGeneration(input?.textGeneration); const layerServerConfig = ServerConfig.layerTest(process.cwd(), { prefix: "t3-git-manager-test-", @@ -740,7 +744,7 @@ function makeManager(input?: { ), Effect.provide( Layer.merge( - Layer.succeed(GitHubCli.GitHubCli, gitHubCli), + Layer.succeed(GitHubRepositoryApi.GitHubRepositoryApi, gitHubRepositoryApi), Layer.mock(GitHubApi.GitHubApi)({}), ), ), @@ -1296,8 +1300,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { yield* runGit(repoDir, ["push", "-u", "origin", "feature/rate-limited"]); const { manager, ghCalls } = yield* makeManager({ ghScenario: { - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("rate limited"), }), @@ -1779,8 +1782,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("gh is not available on PATH"), }), @@ -2640,8 +2642,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { }, ]), }, - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("rate limited"), }), @@ -2717,8 +2718,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("gh is not available on PATH"), }), @@ -2743,8 +2743,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const upstreamCause = "GraphQL rate limit for user ID 51714798 and token secret-value"; const { manager } = yield* makeManager({ ghScenario: { - failWith: new GitHubCli.GitHubCliRateLimitError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryRateLimitError({ cwd: repoDir, cause: new Error(upstreamCause), }), @@ -2770,7 +2769,6 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { errorTag: "SourceControlProviderError", provider: "github", providerOperation: "listChangeRequests", - providerCommand: "gh", errorDetail: "GitHub API rate limit exceeded. Requests resume when the limit resets.", }); const loggedText = [ @@ -2801,8 +2799,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [JSON.stringify([existingPr])], - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("rate limited"), }), @@ -2844,8 +2841,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [JSON.stringify([existingPr])], - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("rate limited"), }), @@ -2892,8 +2888,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [JSON.stringify([existingPr])], - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("rate limited"), }), @@ -2931,8 +2926,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [JSON.stringify([existingPr])], - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("rate limited"), }), @@ -2973,8 +2967,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [JSON.stringify([existingPr])], - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("rate limited"), }), @@ -4618,8 +4611,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { - failWith: new GitHubCli.GitHubCliUnavailableError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryUnavailableError({ cwd: repoDir, cause: new Error("gh is not available on PATH"), }), @@ -4648,8 +4640,7 @@ it.layer(layerGitManagerTest)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { - failWith: new GitHubCli.GitHubCliAuthenticationError({ - command: "gh", + failWith: new GitHubRepositoryApi.GitHubRepositoryAuthenticationError({ cwd: repoDir, cause: new Error("gh is not authenticated"), }), diff --git a/apps/server/src/git/GitManager.ts b/apps/server/src/git/GitManager.ts index cbc0490a95c1d80b5b67c29a7c2f7b6b13a231da..e9b244dfbe86d060b03cff6ce7c255bbfd0f860d 100644 --- a/apps/server/src/git/GitManager.ts +++ b/apps/server/src/git/GitManager.ts @@ -1286,7 +1286,7 @@ export const make = Effect.gen(function* () { ? { provider: error.provider, providerOperation: error.operation, - providerCommand: error.command ?? "unknown", + ...(error.command === undefined ? {} : { providerCommand: error.command }), errorDetail: error.detail, } : {}), diff --git a/apps/server/src/orchestration-v2/ThreadPullRequestService.ts b/apps/server/src/orchestration-v2/ThreadPullRequestService.ts index 072bf930c038ebe0a1a199928a6d9eba041d7ed5..c1e0cff2658f615e792f6707ae24b69521d26176 100644 --- a/apps/server/src/orchestration-v2/ThreadPullRequestService.ts +++ b/apps/server/src/orchestration-v2/ThreadPullRequestService.ts @@ -345,7 +345,7 @@ export const make = Effect.gen(function* () { }).pipe(Effect.tap(() => Effect.sync(() => failBackfill(group)))), ), ), - // Wide enough that a sweep's GitHub branch lookups reach GitHubCli together and share one + // Wide enough that a sweep's GitHub branch lookups reach GitHubRepositoryApi together and share one // GraphQL document, instead of one `gh pr list` per branch. { concurrency: 32, discard: true }, ); diff --git a/apps/server/src/pullRequest/GitHubPullRequestCli.test.ts b/apps/server/src/pullRequest/GitHubPullRequestApi.test.ts similarity index 94% rename from apps/server/src/pullRequest/GitHubPullRequestCli.test.ts rename to apps/server/src/pullRequest/GitHubPullRequestApi.test.ts index 28aca8b6a9b37180b049b800942529c9c76f253d..c42bf6d147bfc76d361c995e76e18801155cd9f9 100644 --- a/apps/server/src/pullRequest/GitHubPullRequestCli.test.ts +++ b/apps/server/src/pullRequest/GitHubPullRequestApi.test.ts @@ -12,13 +12,13 @@ import * as Redacted from "effect/Redacted"; import { HttpClient, HttpClientResponse } from "effect/http"; -import { AllowGitHubReserve } from "../sourceControl/GitHubCli.ts"; +import { AllowGitHubReserve } from "../sourceControl/GitHubRepositoryApi.ts"; import * as GitHubApi from "../sourceControl/GitHubApi.ts"; import * as GitHubCredentials from "../sourceControl/GitHubCredentials.ts"; import * as GitHubGraphQlBudget from "../sourceControl/githubGraphQlBudget.ts"; import * as SourceControlRateLimit from "../sourceControl/SourceControlRateLimit.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; -import * as GitHubPullRequestCli from "./GitHubPullRequestCli.ts"; +import * as GitHubPullRequestApi from "./GitHubPullRequestApi.ts"; import { KnownWorkflowRuns } from "./gitHubConditionalChecks.ts"; const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); @@ -96,7 +96,7 @@ const mockApi = Layer.effect( ); const layer = it.layer( - GitHubPullRequestCli.layer.pipe( + GitHubPullRequestApi.layer.pipe( Layer.provideMerge(mockApi), Layer.provideMerge(GitHubGraphQlBudget.layer), Layer.provide(NodeCrypto.layer), @@ -489,7 +489,7 @@ it.effect( }), ), ); - const cli = yield* GitHubPullRequestCli.make.pipe( + const cli = yield* GitHubPullRequestApi.make.pipe( Effect.provide( GitHubApi.layer.pipe( Layer.provide(Layer.mergeAll(credentials, http)), @@ -537,7 +537,7 @@ it.effect( }), ); -layer("GitHubPullRequestCli.layer", (it) => { +layer("GitHubPullRequestApi.layer", (it) => { it.effect("admits only one concurrent preview above the reserve and resumes after reset", () => Effect.gen(function* () { const now = yield* Clock.currentTimeMillis; @@ -569,7 +569,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const input = { cwd: "/w", repository: "acme/web", @@ -623,7 +623,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const preview = yield* cli.getPullRequestPreview({ cwd: "/w", repository: "acme/web", @@ -653,7 +653,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockImplementation(() => Effect.yieldNow.pipe(Effect.as(output('{"id":123,"login":"viewer"}'))), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const results = yield* Effect.all( Array.from({ length: 4 }, () => cli.getRoutingIdentity({ cwd: "/w", host: "github.identity-flight.test" }), @@ -683,7 +683,7 @@ layer("GitHubPullRequestCli.layer", (it) => { return output('{"id":123,"login":"viewer"}'); }), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const input = { cwd: "/w", host: "github.identity-cancel.test" }; const first = yield* cli.getRoutingIdentity(input).pipe(Effect.forkChild); yield* Deferred.await(firstStarted); @@ -727,7 +727,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const reads = yield* Effect.forEach( [7, 8], @@ -795,7 +795,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const reads = yield* Effect.forEach( [7, 8], @@ -855,7 +855,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const read = yield* cli .getPullRequestSummary({ cwd: "/w", repository: "acme/web", host: "github.com", number: 7 }) @@ -895,7 +895,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const stack = yield* cli.getPullRequestStack({ cwd: "/w", @@ -943,7 +943,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const stack = yield* cli.getPullRequestStack({ cwd: "/w", repository: "acme/web", @@ -963,7 +963,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("reads an empty stacks listing as not stacked", () => Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output("[]"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const stack = yield* cli.getPullRequestStack({ cwd: "/w", @@ -986,7 +986,7 @@ layer("GitHubPullRequestCli.layer", (it) => { }), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const stack = yield* cli.getPullRequestStack({ cwd: "/w", @@ -1009,7 +1009,7 @@ layer("GitHubPullRequestCli.layer", (it) => { }), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestStack({ @@ -1032,7 +1032,7 @@ layer("GitHubPullRequestCli.layer", (it) => { status: 503, }); mockedExecute.mockReturnValueOnce(Effect.fail(failure)); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestStack({ cwd: "/w", @@ -1048,7 +1048,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("reports a stacks answer it cannot read against the stack read", () => Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output('[{"id":42}]'))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestStack({ @@ -1068,7 +1068,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("asks for one row more than the page, to probe for a next page", () => Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequests(3, 1)))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -1091,7 +1091,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("reports truncation from the extra row, counted before decoding", () => Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequests(11, 1)))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -1117,7 +1117,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1143,7 +1143,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1168,7 +1168,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1189,7 +1189,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("carries every repository and every qualifier into one search", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(searchPage([]))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.searchPullRequests({ cwd: "/w", @@ -1218,7 +1218,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("narrows a search to the author, and to merged on the merged tab", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(searchPage([]))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.searchPullRequests({ cwd: "/w", @@ -1240,7 +1240,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("keeps a searched-for qualifier inside the phrase", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(searchPage([]))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.searchPullRequests({ cwd: "/w", @@ -1263,7 +1263,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("refuses to search for a repository GitHub cannot address", () => Effect.gen(function* () { - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const failure = yield* Effect.flip( cli.searchPullRequests({ @@ -1295,7 +1295,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ]), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.searchPullRequests({ cwd: "/w", @@ -1341,7 +1341,7 @@ layer("GitHubPullRequestCli.layer", (it) => { .mockReturnValueOnce( Effect.succeed(searchPage([searchItem(1, "acme/web", "2026-07-03T00:00:00Z")], true)), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const read = () => cli.searchPullRequests({ cwd: "/w", @@ -1390,7 +1390,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", repository: "acme/web", @@ -1443,7 +1443,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", repository: "acme/web", @@ -1469,7 +1469,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce(Effect.succeed(emptySearch())); mockedExecute.mockReturnValueOnce(Effect.succeed(emptyList())); mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequests(1, 7)))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const input = { cwd: "/w", repository: "acme/web", @@ -1499,7 +1499,7 @@ layer("GitHubPullRequestCli.layer", (it) => { output(encodeJson({ data: { s0: { pullRequest: { additions: 4, deletions: 1 } } } })), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const stats = yield* cli.listPullRequestStats({ cwd: "/w", @@ -1521,7 +1521,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("refuses to look up counts for a repository GitHub cannot address", () => Effect.gen(function* () { - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const failure = yield* Effect.flip( cli.listPullRequestStats({ @@ -1545,7 +1545,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1575,7 +1575,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1603,7 +1603,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1641,7 +1641,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1667,7 +1667,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1701,7 +1701,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -1732,7 +1732,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output(listedPullRequests(1, 1, () => ({ checks: "PENDING" })))), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -1759,7 +1759,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output(listedPullRequests(2, 1, (number) => ({ isDraft: number === 1 })))), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -1784,7 +1784,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("carries the further narrowings into a batched search", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(searchPage([]))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.searchPullRequests({ cwd: "/w", @@ -1813,7 +1813,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1843,7 +1843,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1873,7 +1873,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -1895,7 +1895,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("carries on from the instant the last slice ended on", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output(pullRequests(3, 1)))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -1923,7 +1923,7 @@ layer("GitHubPullRequestCli.layer", (it) => { // answer means the text matched nothing, and listing everything instead would fill the // page with rows the reader did not search for. mockedExecute.mockReturnValueOnce(Effect.succeed(emptySearch())); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -1948,7 +1948,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output(listedPullRequests(3, 1, () => ({ state: "CLOSED" })))), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -1984,7 +1984,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -2020,7 +2020,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -2064,7 +2064,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ); }); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const batch = yield* cli.listPullRequests({ cwd: "/w", @@ -2094,7 +2094,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -2139,7 +2139,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("updates a stale branch with a merge commit unless asked to rebase", () => Effect.gen(function* () { route(["query PullRequestActionState", actionState()]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2168,7 +2168,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("merges with the strategy it was asked for", () => Effect.gen(function* () { route(["query PullRequestActionState", actionState()]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2199,7 +2199,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ], ["query PullRequestActionState", actionState()], ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", repository: "acme/web", @@ -2243,7 +2243,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ["query PullRequestMergeMessage", mergeMessage(body, queued)], ["query PullRequestActionState", actionState({ isMergeQueueEnabled: queued })], ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", repository: "acme/web", @@ -2269,7 +2269,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ], ["query PullRequestActionState", actionState()], ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", repository: "acme/web", @@ -2294,7 +2294,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("does not fetch a message for rebase merges", () => Effect.gen(function* () { route(["query PullRequestActionState", actionState()]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", repository: "acme/web", @@ -2317,7 +2317,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ["query PullRequestMergeMessage", { data: { repository: { pullRequest: null } } }], ["query PullRequestActionState", actionState()], ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const result = yield* Effect.result( cli.runPullRequestAction({ cwd: "/w", @@ -2336,7 +2336,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("arms auto-merge with the same strategy a merge would have used", () => Effect.gen(function* () { route(["query PullRequestActionState", actionState()]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2366,7 +2366,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("merges at once when auto-merge is asked of a pull request that is ready now", () => Effect.gen(function* () { route(["query PullRequestActionState", actionState({ mergeStateStatus: "CLEAN" })]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2387,7 +2387,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("takes auto-merge back off without naming a strategy", () => Effect.gen(function* () { route([NODE_ID_QUERY, nodeIdAnswer("PR_7")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2405,7 +2405,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("opens a pull request that reverts a merged pull request", () => Effect.gen(function* () { route([NODE_ID_QUERY, nodeIdAnswer("PR_7")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2441,7 +2441,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2458,7 +2458,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("finds and approves every workflow waiting on a maintainer", () => Effect.gen(function* () { workflowApprovalRoutes(() => crossRepositoryDetail(), heads([7]), workflowRuns([10, 11])); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2490,7 +2490,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("counts revalidated runs that wait on a maintainer as GitHub reports them", () => Effect.gen(function* () { workflowApprovalRoutes(() => crossRepositoryDetail(), heads([7]), workflowRuns([])); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const runs = yield* cli .listWorkflowRunsRequiringApproval({ @@ -2532,7 +2532,7 @@ layer("GitHubPullRequestCli.layer", (it) => { heads([7]), workflowRuns([10]), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.runPullRequestAction({ @@ -2568,7 +2568,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.as(output(encodeJson(workflowRuns([10])))), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const runs = yield* cli.listWorkflowRunsRequiringApproval({ cwd: "/w", @@ -2588,7 +2588,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("refuses workflow approval when one head belongs to several pull requests", () => Effect.gen(function* () { workflowApprovalRoutes(() => crossRepositoryDetail(), heads([7, 8]), workflowRuns([])); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.listWorkflowRunsRequiringApproval({ @@ -2636,7 +2636,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.runPullRequestAction({ @@ -2670,7 +2670,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ); }); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.listWorkflowRunsRequiringApproval({ @@ -2701,7 +2701,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("returns a pull request to draft by converting it", () => Effect.gen(function* () { route([NODE_ID_QUERY, nodeIdAnswer("PR_7")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.runPullRequestAction({ cwd: "/w", @@ -2719,7 +2719,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("sends a comment body as a variable, never inside the document", () => Effect.gen(function* () { route([NODE_ID_QUERY, nodeIdAnswer("PR_7")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.commentOnPullRequest({ cwd: "/w", @@ -2743,7 +2743,7 @@ layer("GitHubPullRequestCli.layer", (it) => { : emptySearch(), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listPullRequests({ cwd: "/w", @@ -2777,7 +2777,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.listReviewThreadComments({ cwd: "/w", @@ -2794,7 +2794,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("serves a diff GitHub hands over whole in one request, with no next slice", () => Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output("diff --git a/a b/a"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const diff = yield* cli.getPullRequestDiff({ cwd: "/w", @@ -2823,7 +2823,7 @@ layer("GitHubPullRequestCli.layer", (it) => { // GitHub answers 406 rather than a diff past 300 changed files. mockedExecute.mockReturnValueOnce(Effect.fail(diffRefused)); mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequestFiles(2, 1)))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const diff = yield* cli.getPullRequestDiff({ cwd: "/w", @@ -2846,7 +2846,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.fail(diffRefused)); mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequestFiles(100, 0)))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const diff = yield* cli.getPullRequestDiff({ cwd: "/w", @@ -2866,7 +2866,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.fail(diffRefused)); mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequestFiles(100, 0)))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const target = { cwd: "/w", repository: "acme/web", host: "github.com", number: 7 }; const first = yield* cli.getPullRequestDiff(target); @@ -2884,7 +2884,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("refuses a cursor it never handed out rather than reading it into a request", () => Effect.gen(function* () { - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDiff({ @@ -2906,7 +2906,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output(`{"files":${pullRequestFiles(2, 1)}}`)), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const diff = yield* cli.getPullRequestDiff({ cwd: "/w", @@ -2933,7 +2933,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output(`{"files":${pullRequestFiles(100, 0)}}`)), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const target = { cwd: "/w", repository: "acme/web", @@ -2957,7 +2957,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("refuses a commit that is not a sha rather than reading it into a request", () => Effect.gen(function* () { - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDiff({ @@ -2980,7 +2980,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.succeed(output(encodeJson({ sha: "a1b2c3d", parents: [] }))), ); mockedExecute.mockReturnValueOnce(Effect.succeed(output("root contents\n"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const contents = yield* cli.getPullRequestDiffFileContents({ cwd: "/w", @@ -3003,7 +3003,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("reports unusable diff revisions as a structured error", () => Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output("not-a-sha\tstill-not-a-sha\n"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDiffFileContents({ @@ -3030,7 +3030,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequestRefs))); mockedExecute.mockReturnValueOnce(Effect.succeed(output("partial", true))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDiffFileContents({ @@ -3058,7 +3058,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequestRefs))); mockedExecute.mockReturnValueOnce(Effect.succeed(output("binary�contents", false, true))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDiffFileContents({ @@ -3084,7 +3084,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequestRefs))); mockedExecute.mockReturnValueOnce(Effect.succeed(output("before�after"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const contents = yield* cli.getPullRequestDiffFileContents({ cwd: "/w", @@ -3103,7 +3103,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("ends the diff on a page with no files rather than asking for it again", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output("[]"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const diff = yield* cli.getPullRequestDiff({ cwd: "/w", @@ -3122,7 +3122,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.fail(diffRefused)); mockedExecute.mockReturnValueOnce(Effect.succeed(output("not json"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDiff({ @@ -3139,7 +3139,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("skips the avatar lookup when a listing named nobody", () => Effect.gen(function* () { - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const avatars = yield* cli.listActorAvatars({ cwd: "/w", @@ -3172,7 +3172,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const avatars = yield* cli.listActorAvatars({ cwd: "/w", @@ -3190,7 +3190,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("fails when the authenticated account has no login", () => Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output(" "))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip(cli.getViewerLogin({ cwd: "/w", host: "github.com" })); @@ -3203,7 +3203,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output('{"id":456,"login":"enterprise-user"}')), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const login = yield* cli.getViewerLogin({ cwd: "/w", host: "github.acme.com" }); @@ -3215,7 +3215,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("reuses verified credentials offline and refuses an unverified replacement", () => Effect.gen(function* () { - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const input = { cwd: "/w", host: "github.identity-cache.test" }; mockedCredential.mockImplementation((host) => Effect.succeed({ token: Redacted.make("test-credential-a"), fingerprint: `${host}:a` }), @@ -3263,7 +3263,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("sends a whole review as one request", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output("{}"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.submitReview({ cwd: "/w", @@ -3294,7 +3294,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("sends a reply body as a variable, never inside the document", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output("{}"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.replyToReviewThread({ cwd: "/w", @@ -3314,7 +3314,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("resolves and unresolves through the mutation each one needs", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output("{}"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setReviewThreadResolution({ cwd: "/w", @@ -3343,7 +3343,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("confirms a given subject belongs to the named pull request, then reacts to it", () => Effect.gen(function* () { route([SUBJECT_SCOPE_QUERY, subjectScope("IC_1", "PR_kwDOA")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setReaction({ cwd: "/w", @@ -3379,7 +3379,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.setReaction({ @@ -3402,7 +3402,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("looks up the pull request's own node id when no subject was given", () => Effect.gen(function* () { route([NODE_ID_QUERY, nodeIdAnswer("PR_kwDOA")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; // Its own pull request: a node id looked up once is remembered for the life of the service. yield* cli.setReaction({ @@ -3423,7 +3423,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("takes a reaction back through the remove mutation", () => Effect.gen(function* () { route([SUBJECT_SCOPE_QUERY, subjectScope("IC_1", "PR_kwDOA")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setReaction({ cwd: "/w", @@ -3443,7 +3443,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("rewrites only the words a request named", () => Effect.gen(function* () { route([NODE_ID_QUERY, nodeIdAnswer("PR_kwDOA")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const rewrite = (fields: { readonly title?: string; readonly body?: string }) => cli.updatePullRequest({ cwd: "/w", @@ -3470,7 +3470,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("rewrites a remark through the mutation its kind needs", () => Effect.gen(function* () { route([SUBJECT_SCOPE_QUERY, subjectScope("IC_1", "PR_kwDOA")]); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const rewrite = (kind: "issue-comment" | "review-comment") => cli.updateComment({ cwd: "/w", @@ -3512,7 +3512,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.updateComment({ @@ -3537,7 +3537,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("fails the read when gh returns something unreadable", () => Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.succeed(output('{"message":"not found"}'))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDetail({ @@ -3586,7 +3586,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const input = { cwd: "/w", repository: "acme/web", @@ -3664,7 +3664,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const detail = yield* cli.getPullRequestDetail({ cwd: "/w", repository: "acme/web", @@ -3720,7 +3720,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const detail = yield* cli.getPullRequestDetail({ cwd: "/w", repository: "acme/web", @@ -3768,7 +3768,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDetail({ cwd: "/w", @@ -3802,7 +3802,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const input = { cwd: "/w", repository: "acme/web", @@ -3822,7 +3822,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.gen(function* () { mockedExecute.mockReturnValueOnce(Effect.fail(diffRefused)); mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequestFiles(1, 1), true))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getPullRequestDiff({ @@ -3847,7 +3847,7 @@ layer("GitHubPullRequestCli.layer", (it) => { Effect.succeed(output("diff --git a/a b/a\n@@ -1 +1 @@", true)), ); mockedExecute.mockReturnValueOnce(Effect.succeed(output(pullRequestFiles(1, 1)))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const slice = yield* cli.getPullRequestDiff({ cwd: "/w", @@ -3872,7 +3872,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output(reviewThreadsPage([thread("PRRT_2", "c2")], null))), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const conversation = yield* cli.listReviewThreadComments({ cwd: "/w", @@ -3902,7 +3902,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const conversation = yield* cli.listReviewThreadComments({ cwd: "/w", @@ -3929,7 +3929,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const conversation = yield* cli.listReviewThreadComments({ cwd: "/w", @@ -3954,7 +3954,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output(threadCommentsPage(["c2", "c3"], null, 3))), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const page = yield* cli.getReviewThreadComments({ cwd: "/w", @@ -3983,7 +3983,7 @@ layer("GitHubPullRequestCli.layer", (it) => { mockedExecute.mockReturnValueOnce( Effect.succeed(output(threadCommentsPage(["foreign"], null, 1, "PR_8"))), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const error = yield* Effect.flip( cli.getReviewThreadComments({ @@ -4021,7 +4021,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const access = yield* cli.getViewerAccess({ cwd: "/w", @@ -4069,7 +4069,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const input = { cwd: "/w", repository: "acme/web", @@ -4134,7 +4134,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.getViewerAccess({ cwd: "/w", @@ -4165,7 +4165,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("asks GitHub to review, naming the collection a request is added to", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output("{}"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setReviewerRequest({ cwd: "/w", @@ -4191,7 +4191,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("takes a request back by deleting from the same collection it was added to", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output("{}"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setReviewerRequest({ cwd: "/w", @@ -4233,7 +4233,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const list = yield* cli.listReviewerCandidates({ cwd: "/w", @@ -4256,7 +4256,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("puts labels on by posting to the issue's own collection, all at once", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output("[]"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setLabels({ cwd: "/w", @@ -4280,7 +4280,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("takes labels off one at a time, naming each in the path encoded", () => Effect.gen(function* () { mockedExecute.mockReturnValue(Effect.succeed(output("[]"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setLabels({ cwd: "/w", @@ -4329,7 +4329,7 @@ layer("GitHubPullRequestCli.layer", (it) => { .mockReturnValueOnce(page(0, true)) .mockReturnValueOnce(page(1, true)) .mockReturnValueOnce(page(2, false)); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const viewed = yield* cli.getPullRequestFilesViewed({ cwd: "/w", @@ -4377,7 +4377,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ), ); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const viewed = yield* cli.getPullRequestFilesViewed({ cwd: "/w", @@ -4401,7 +4401,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ) .mockReturnValueOnce(Effect.succeed(output("{}"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setPullRequestFilesViewed({ cwd: "/w", @@ -4428,7 +4428,7 @@ layer("GitHubPullRequestCli.layer", (it) => { it.effect("asks the host nothing when nothing was pressed", () => Effect.gen(function* () { - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; yield* cli.setPullRequestFilesViewed({ cwd: "/w", @@ -4451,7 +4451,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ) .mockReturnValue(Effect.succeed(output("{}"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const pullRequest = { cwd: "/w", repository: "acme/web", host: "github.com", number: 24 }; yield* cli.setPullRequestFilesViewed({ @@ -4485,7 +4485,7 @@ layer("GitHubPullRequestCli.layer", (it) => { ), ) .mockReturnValueOnce(Effect.succeed(output("{}"))); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const write = () => cli.setPullRequestFilesViewed({ cwd: "/w", @@ -4522,7 +4522,7 @@ layer("GitHubPullRequestCli.layer", (it) => { output(encodeJson({ data: { repository: { pullRequest: { id: `PR_${number}` } } } })), ); }); - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; const tick = (number: number) => cli.setPullRequestFilesViewed({ cwd: "/w", @@ -4534,7 +4534,7 @@ layer("GitHubPullRequestCli.layer", (it) => { yield* tick(HOT); // A cache's worth of cold pull requests, with the open one pressed in between each of them. - for (let filled = 0; filled < GitHubPullRequestCli.NODE_ID_CACHE_CAPACITY; filled += 1) { + for (let filled = 0; filled < GitHubPullRequestApi.NODE_ID_CACHE_CAPACITY; filled += 1) { yield* tick(HOT + 1 + filled); yield* tick(HOT); } diff --git a/apps/server/src/pullRequest/GitHubPullRequestCli.ts b/apps/server/src/pullRequest/GitHubPullRequestApi.ts similarity index 96% rename from apps/server/src/pullRequest/GitHubPullRequestCli.ts rename to apps/server/src/pullRequest/GitHubPullRequestApi.ts index ce944b931aa86acae146524d0f99596075435bbc..116a8a742d1a7c0a0c61810ba59e30f076c60f37 100644 --- a/apps/server/src/pullRequest/GitHubPullRequestCli.ts +++ b/apps/server/src/pullRequest/GitHubPullRequestApi.ts @@ -42,7 +42,7 @@ import { type PullRequestPreview, } from "@t3tools/contracts"; -import { AllowGitHubReserve } from "../sourceControl/GitHubCli.ts"; +import { AllowGitHubReserve } from "../sourceControl/GitHubRepositoryApi.ts"; import * as GitHubApi from "../sourceControl/GitHubApi.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as SourceControlRateLimit from "../sourceControl/SourceControlRateLimit.ts"; @@ -138,7 +138,6 @@ import type { ProviderChangeRequestSummary, ProviderListCursor } from "./PullReq export class GitHubPullRequestReadError extends Schema.TaggedError()( "GitHubPullRequestReadError", { - command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, cause: Schema.Defect(), @@ -149,11 +148,10 @@ export class GitHubPullRequestReadError extends Schema.TaggedError()( "GitHubViewerLoginUnavailableError", { - command: Schema.Literal("gh"), cwd: Schema.String, }, ) { @@ -162,11 +160,10 @@ export class GitHubViewerLoginUnavailableError extends Schema.TaggedError()( "GitHubPullRequestUpdatedAtUnavailableError", { - command: Schema.Literal("gh"), cwd: Schema.String, repository: Schema.String, number: Schema.Int, @@ -181,7 +178,6 @@ export class GitHubPullRequestUpdatedAtUnavailableError extends Schema.TaggedErr export class GitHubDiffCursorError extends Schema.TaggedError()( "GitHubDiffCursorError", { - command: Schema.Literal("gh"), cwd: Schema.String, }, ) { @@ -194,7 +190,6 @@ export class GitHubDiffCursorError extends Schema.TaggedError()( "GitHubDiffCommitError", { - command: Schema.Literal("gh"), cwd: Schema.String, }, ) { @@ -207,7 +202,6 @@ export class GitHubDiffCommitError extends Schema.TaggedError()( "GitHubDiffRevisionsUnavailableError", { - command: Schema.Literal("gh"), cwd: Schema.String, number: Schema.Int, commit: Schema.optional(Schema.String), @@ -224,7 +218,6 @@ export class GitHubDiffRevisionsUnavailableError extends Schema.TaggedError()( "GitHubDiffFileContentsUnavailableError", { - command: Schema.Literal("gh"), cwd: Schema.String, path: Schema.String, reason: Schema.Literals(["oversized", "binary"]), @@ -246,7 +239,6 @@ export class GitHubDiffFileContentsUnavailableError extends Schema.TaggedError()( "GitHubRepositorySelectorError", { - command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, }, @@ -260,7 +252,6 @@ export class GitHubRepositorySelectorError extends Schema.TaggedError()( "GitHubSubjectScopeError", { - command: Schema.Literal("gh"), cwd: Schema.String, operation: Schema.String, }, @@ -274,7 +265,6 @@ export class GitHubSubjectScopeError extends Schema.TaggedError()( "GitHubWorkflowApprovalRefusedError", { - command: Schema.Literal("gh"), cwd: Schema.String, number: Schema.Int, reason: Schema.Literals(["head-list-truncated", "head-not-unique", "run-list-truncated"]), @@ -297,7 +287,6 @@ export class GitHubWorkflowApprovalRefusedError extends Schema.TaggedError()( "GitHubWorkflowApprovalHeadUnavailableError", { - command: Schema.Literal("gh"), cwd: Schema.String, number: Schema.Int, }, @@ -311,7 +300,6 @@ export class GitHubWorkflowApprovalHeadUnavailableError extends Schema.TaggedErr export class GitHubWorkflowApprovalHeadChangedError extends Schema.TaggedError()( "GitHubWorkflowApprovalHeadChangedError", { - command: Schema.Literal("gh"), cwd: Schema.String, number: Schema.Int, }, @@ -321,7 +309,7 @@ export class GitHubWorkflowApprovalHeadChangedError extends Schema.TaggedError {} class PullRequestWatchFingerprintRead extends Request.Class< @@ -435,7 +423,7 @@ class PullRequestWatchFingerprintRead extends Request.Class< readonly number: number; }, GitHubPullRequestWatchFingerprint | null, - GitHubPullRequestCliError + GitHubPullRequestApiError > {} export interface GitHubPullRequestSearchBatch { @@ -460,8 +448,8 @@ export interface GitHubPullRequestFilesViewed { readonly truncated: boolean; } -export class GitHubPullRequestCli extends Context.Service< - GitHubPullRequestCli, +export class GitHubPullRequestApi extends Context.Service< + GitHubPullRequestApi, { readonly withVerifiedCredential: ( input: { readonly cwd: string; readonly host: string }, @@ -470,18 +458,18 @@ export class GitHubPullRequestCli extends Context.Service< readonly viewer: string; readonly credentialFingerprint: string; }) => Effect.Effect, - ) => Effect.Effect; + ) => Effect.Effect; readonly getRoutingIdentity: (input: { readonly cwd: string; readonly host: string; }) => Effect.Effect< { readonly accountId: string; readonly viewer: string }, - GitHubPullRequestCliError + GitHubPullRequestApiError >; readonly getViewerLogin: (input: { readonly cwd: string; readonly host: string; - }) => Effect.Effect; + }) => Effect.Effect; readonly listPullRequests: (input: { readonly cwd: string; @@ -497,7 +485,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly cursor?: ProviderListCursor | undefined; /** Further narrowings, as qualifiers on the search and as a local pass on the fallback. */ readonly filters?: PullRequestListFilters | undefined; - }) => Effect.Effect; + }) => Effect.Effect; /** * The same listing for a whole host in one search. `limit` is the size of the slice across @@ -516,7 +504,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly query?: string | undefined; readonly cursor?: ProviderListCursor | undefined; readonly filters?: PullRequestListFilters | undefined; - }) => Effect.Effect; + }) => Effect.Effect; /** The line counts the search leaves out, for rows already on the page. */ readonly listPullRequestStats: (input: { @@ -526,14 +514,14 @@ export class GitHubPullRequestCli extends Context.Service< readonly repository: string; readonly number: number; }>; - }) => Effect.Effect, GitHubPullRequestCliError>; + }) => Effect.Effect, GitHubPullRequestApiError>; readonly getPullRequestSummary: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; /** * What a watch compares between passes, batched like summaries. Null when GitHub gave no * answer for this pull request, so the watch reads it in full instead. @@ -543,7 +531,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; readonly revalidateChecks: Effect.Success; @@ -552,7 +540,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; readonly getPullRequestPreview: (input: { readonly cwd: string; @@ -561,7 +549,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly number: number; }) => Effect.Effect< Omit, - GitHubPullRequestCliError + GitHubPullRequestApiError >; readonly listWorkflowRunsRequiringApproval: (input: { @@ -573,7 +561,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly headBranch: string; readonly headRepositoryOwner: string; readonly isCrossRepository: true; - }) => Effect.Effect, GitHubPullRequestCliError>; + }) => Effect.Effect, GitHubPullRequestApiError>; /** * The host-native stack this pull request is in, or null when it is in none — which is also @@ -585,14 +573,14 @@ export class GitHubPullRequestCli extends Context.Service< readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; readonly getPullRequestActivity: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; readonly getPullRequestDiff: (input: { readonly cwd: string; @@ -603,7 +591,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly cursor?: string | undefined; /** One commit's own changes, rather than everything the pull request carries. */ readonly commit?: string | undefined; - }) => Effect.Effect; + }) => Effect.Effect; readonly getPullRequestDiffFileContents: (input: { readonly cwd: string; @@ -616,7 +604,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly newPath: string; }) => Effect.Effect< { readonly oldContents: string; readonly newContents: string }, - GitHubPullRequestCliError + GitHubPullRequestApiError >; /** @@ -628,7 +616,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; /** * Clears files, or puts them back, as one request. GitHub takes a single path per mutation, @@ -640,14 +628,14 @@ export class GitHubPullRequestCli extends Context.Service< readonly host: string; readonly number: number; readonly files: ReadonlyArray<{ readonly path: string; readonly viewed: boolean }>; - }) => Effect.Effect; + }) => Effect.Effect; readonly listReviewThreadComments: (input: { readonly cwd: string; readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; /** One request for a listing's authors, since no `gh` JSON field reports an avatar. */ readonly listActorAvatars: (input: { @@ -655,7 +643,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly repository: string; readonly host: string; readonly ids: ReadonlyArray; - }) => Effect.Effect, GitHubPullRequestCliError>; + }) => Effect.Effect, GitHubPullRequestApiError>; readonly getReviewThreadComments: (input: { readonly cwd: string; @@ -664,7 +652,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly number: number; readonly threadId: string; readonly cursor: string; - }) => Effect.Effect; + }) => Effect.Effect; /** The viewer's standing on its own, for deciding a write without reading the whole detail. */ readonly getViewerAccess: (input: { @@ -674,7 +662,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly number: number; /** Manual action checks may use the quota held back from automatic reads. */ readonly allowReserve?: boolean | undefined; - }) => Effect.Effect; + }) => Effect.Effect; /** Who this pull request may be sent to, and who it has already been sent to. */ readonly listReviewerCandidates: (input: { @@ -682,7 +670,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; readonly setReviewerRequest: (input: { readonly cwd: string; @@ -695,7 +683,7 @@ export class GitHubPullRequestCli extends Context.Service< }>; /** False deletes the same collection a request posts to, which takes the request back. */ readonly requested: boolean; - }) => Effect.Effect; + }) => Effect.Effect; /** The repository's labels, and which of them this pull request already wears. */ readonly listLabelCandidates: (input: { @@ -703,7 +691,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly repository: string; readonly host: string; readonly number: number; - }) => Effect.Effect; + }) => Effect.Effect; readonly setLabels: (input: { readonly cwd: string; @@ -713,7 +701,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly labels: ReadonlyArray; /** False takes each label off; true adds each to whatever is already there. */ readonly applied: boolean; - }) => Effect.Effect; + }) => Effect.Effect; readonly runPullRequestAction: (input: { readonly cwd: string; @@ -726,7 +714,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly removeAgentCreditsOnMerge?: boolean; readonly mergeMethod?: PullRequestMergeMethod; readonly updateMethod?: PullRequestUpdateMethod; - }) => Effect.Effect; + }) => Effect.Effect; readonly commentOnPullRequest: (input: { readonly cwd: string; @@ -734,7 +722,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly host: string; readonly number: number; readonly body: string; - }) => Effect.Effect; + }) => Effect.Effect; readonly submitReview: (input: { readonly cwd: string; @@ -744,7 +732,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly verdict: PullRequestReviewVerdict; readonly body: string; readonly comments: ReadonlyArray; - }) => Effect.Effect; + }) => Effect.Effect; readonly replyToReviewThread: (input: { readonly cwd: string; @@ -752,7 +740,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly host: string; readonly threadId: string; readonly body: string; - }) => Effect.Effect; + }) => Effect.Effect; readonly setReviewThreadResolution: (input: { readonly cwd: string; @@ -760,7 +748,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly host: string; readonly threadId: string; readonly resolved: boolean; - }) => Effect.Effect; + }) => Effect.Effect; /** * Adds a reaction to a remark, or takes it back. `subjectId` is any node GitHub calls @@ -776,7 +764,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly subjectId?: string | undefined; readonly content: PullRequestReactionContent; readonly reacted: boolean; - }) => Effect.Effect; + }) => Effect.Effect; /** Rewrites the pull request's own words, leaving whichever of the two was not given. */ readonly updatePullRequest: (input: { @@ -786,7 +774,7 @@ export class GitHubPullRequestCli extends Context.Service< readonly number: number; readonly title?: string | undefined; readonly body?: string | undefined; - }) => Effect.Effect; + }) => Effect.Effect; /** * Rewrites a remark. `commentId` is trusted to be whatever node it names, so it is confirmed @@ -801,9 +789,9 @@ export class GitHubPullRequestCli extends Context.Service< readonly commentId: string; readonly kind: "issue-comment" | "review-comment"; readonly body: string; - }) => Effect.Effect; + }) => Effect.Effect; } ->()("t3/pullRequest/GitHubPullRequestCli") {} +>()("t3/pullRequest/GitHubPullRequestApi") {} /** * The GraphQL API takes owner and name as separate arguments, so `owner/repo` is split here. @@ -1134,10 +1122,9 @@ export const make = Effect.gen(function* () { }), ), ); - const captureVerifiedCredential = Effect.fn("GitHubPullRequestCli.captureVerifiedCredential")( + const captureVerifiedCredential = Effect.fn("GitHubPullRequestApi.captureVerifiedCredential")( function* (input: { readonly cwd: string; readonly host: string }) { - const unavailable = () => - new GitHubViewerLoginUnavailableError({ command: "gh", cwd: input.cwd }); + const unavailable = () => new GitHubViewerLoginUnavailableError({ cwd: input.cwd }); const host = input.host.toLowerCase(); // A missing or signed-out credential keeps its own error, so the page can say which. const { token, fingerprint: key } = yield* api.credential(host); @@ -1183,7 +1170,7 @@ export const make = Effect.gen(function* () { ); }, ); - const withVerifiedCredential: GitHubPullRequestCli["Service"]["withVerifiedCredential"] = ( + const withVerifiedCredential: GitHubPullRequestApi["Service"]["withVerifiedCredential"] = ( input, use, ) => @@ -1199,7 +1186,7 @@ export const make = Effect.gen(function* () { ), ), ); - const getRoutingIdentity: GitHubPullRequestCli["Service"]["getRoutingIdentity"] = (input) => + const getRoutingIdentity: GitHubPullRequestApi["Service"]["getRoutingIdentity"] = (input) => captureVerifiedCredential(input).pipe( Effect.map(({ accountId, viewer }) => ({ accountId, viewer })), ); @@ -1219,7 +1206,7 @@ export const make = Effect.gen(function* () { readonly host: string; readonly number: number; readonly operation: string; - }): Effect.Effect => { + }): Effect.Effect => { const { owner, name } = parseRepositorySelector(input.repository); const key = `${input.host} ${owner}/${name} ${input.number}`; const held = nodeIds.get(key); @@ -1287,7 +1274,7 @@ export const make = Effect.gen(function* () { }) => api.graphql(input).pipe(Effect.asVoid); const readError = (cwd: string, operation: string, cause: unknown) => - new GitHubPullRequestReadError({ command: "gh", cwd, operation, cause }); + new GitHubPullRequestReadError({ cwd, operation, cause }); /** Decodes an answer, reporting a failure against the read that made it. */ const decodeWith = ( @@ -1311,7 +1298,7 @@ export const make = Effect.gen(function* () { readonly variables?: Readonly>; readonly query: string; readonly decode: (raw: string) => Result.Result; - }): Effect.Effect => + }): Effect.Effect => api .graphql({ host: input.host, @@ -1329,7 +1316,7 @@ export const make = Effect.gen(function* () { readonly operation: string; readonly path: string; readonly decode: (raw: string) => Result.Result; - }): Effect.Effect => + }): Effect.Effect => api .rest({ host: input.host, operation: input.operation, path: input.path }) .pipe( @@ -1355,7 +1342,7 @@ export const make = Effect.gen(function* () { readonly number: number; readonly page: number; readonly commit?: string | undefined; - }): Effect.Effect => { + }): Effect.Effect => { const { owner, name } = parseRepositorySelector(input.repository); const paging = `per_page=${DIFF_FILES_PAGE_SIZE}&page=${input.page}`; return api @@ -1407,11 +1394,11 @@ export const make = Effect.gen(function* () { ); }; - const getPullRequestDiffFileContents: GitHubPullRequestCli["Service"]["getPullRequestDiffFileContents"] = + const getPullRequestDiffFileContents: GitHubPullRequestApi["Service"]["getPullRequestDiffFileContents"] = (input) => Effect.gen(function* () { if (input.commit !== undefined && !isCommitSha(input.commit)) { - return yield* new GitHubDiffCommitError({ command: "gh", cwd: input.cwd }); + return yield* new GitHubDiffCommitError({ cwd: input.cwd }); } const { owner, name } = parseRepositorySelector(input.repository); const refsResponse = yield* api.rest({ @@ -1444,7 +1431,6 @@ export const make = Effect.gen(function* () { !isCommitSha(headRef) ) { return yield* new GitHubDiffRevisionsUnavailableError({ - command: "gh", cwd: input.cwd, number: input.number, ...(input.commit === undefined ? {} : { commit: input.commit }), @@ -1469,7 +1455,6 @@ export const make = Effect.gen(function* () { response.truncated || response.body.includes("\0") || response.invalidUtf8 ? Effect.fail( new GitHubDiffFileContentsUnavailableError({ - command: "gh", cwd: input.cwd, path: filePath, reason: response.truncated ? "oversized" : "binary", @@ -1497,7 +1482,7 @@ export const make = Effect.gen(function* () { * mixing two revisions' checks. */ const readAllCheckContexts = ( - input: Parameters[0], + input: Parameters[0], allowReserve: boolean, ) => Effect.gen(function* () { @@ -1534,7 +1519,7 @@ export const make = Effect.gen(function* () { return { headSha, contexts, truncated: after !== null }; }); - const getPullRequestDetail: GitHubPullRequestCli["Service"]["getPullRequestDetail"] = (input) => { + const getPullRequestDetail: GitHubPullRequestApi["Service"]["getPullRequestDetail"] = (input) => { const { owner, name } = parseRepositorySelector(input.repository); return AllowGitHubReserve.pipe( Effect.flatMap((allowReserve) => @@ -1659,7 +1644,7 @@ export const make = Effect.gen(function* () { return runs; }); - const listWorkflowRunsRequiringApproval: GitHubPullRequestCli["Service"]["listWorkflowRunsRequiringApproval"] = + const listWorkflowRunsRequiringApproval: GitHubPullRequestApi["Service"]["listWorkflowRunsRequiringApproval"] = (input) => Effect.all( [ @@ -1681,7 +1666,6 @@ export const make = Effect.gen(function* () { if (heads.length > workflowApprovalLimit) { return Effect.fail( new GitHubWorkflowApprovalRefusedError({ - command: "gh", cwd: input.cwd, number: input.number, reason: "head-list-truncated", @@ -1693,7 +1677,6 @@ export const make = Effect.gen(function* () { if (exactHeads.length !== 1 || exactHeads[0]?.number !== input.number) { return Effect.fail( new GitHubWorkflowApprovalRefusedError({ - command: "gh", cwd: input.cwd, number: input.number, reason: "head-not-unique", @@ -1711,7 +1694,6 @@ export const make = Effect.gen(function* () { runs.length > workflowApprovalLimit ? Effect.fail( new GitHubWorkflowApprovalRefusedError({ - command: "gh", cwd: input.cwd, number: input.number, reason: "run-list-truncated", @@ -1830,7 +1812,7 @@ export const make = Effect.gen(function* () { RequestResolver.setDelay(SUMMARY_BATCH_WINDOW), RequestResolver.batchN(STAT_ALIASES_PER_REQUEST), ); - const getPullRequestSummary: GitHubPullRequestCli["Service"]["getPullRequestSummary"] = (input) => + const getPullRequestSummary: GitHubPullRequestApi["Service"]["getPullRequestSummary"] = (input) => Effect.request(new PullRequestSummaryRead(input), summaryResolver); // Every watched pull request on a host in one read per pass. A pull request the batch has no @@ -1884,10 +1866,10 @@ export const make = Effect.gen(function* () { RequestResolver.setDelay(SUMMARY_BATCH_WINDOW), RequestResolver.batchN(STAT_ALIASES_PER_REQUEST), ); - const getPullRequestWatchFingerprint: GitHubPullRequestCli["Service"]["getPullRequestWatchFingerprint"] = + const getPullRequestWatchFingerprint: GitHubPullRequestApi["Service"]["getPullRequestWatchFingerprint"] = (input) => Effect.request(new PullRequestWatchFingerprintRead(input), watchFingerprintResolver); - return GitHubPullRequestCli.of({ + return GitHubPullRequestApi.of({ withVerifiedCredential, revalidateChecks, getRoutingIdentity, @@ -1901,7 +1883,6 @@ export const make = Effect.gen(function* () { if (query === null) { return Effect.fail( new GitHubRepositorySelectorError({ - command: "gh", cwd: input.cwd, operation: "listPullRequests", }), @@ -1913,7 +1894,7 @@ export const make = Effect.gen(function* () { page: ( after: string | null, rows: number, - ) => Effect.Effect, + ) => Effect.Effect, ) => Effect.gen(function* () { const items: GitHubPullRequestListItem[] = []; @@ -1930,7 +1911,7 @@ export const make = Effect.gen(function* () { const read = ( continues: boolean, requestedRows = input.limit + 1, - ): Effect.Effect => + ): Effect.Effect => collect(requestedRows, (after, rows) => continues ? graphqlRead({ @@ -2047,7 +2028,6 @@ export const make = Effect.gen(function* () { if (query === null) { return Effect.fail( new GitHubRepositorySelectorError({ - command: "gh", cwd: input.cwd, operation: "searchPullRequests", }), @@ -2085,7 +2065,6 @@ export const make = Effect.gen(function* () { if (query === null) { return Effect.fail( new GitHubRepositorySelectorError({ - command: "gh", cwd: input.cwd, operation: "listPullRequestStats", }), @@ -2214,14 +2193,14 @@ export const make = Effect.gen(function* () { ...(input.commit === undefined ? {} : { commit: input.commit }), }); if (input.commit !== undefined && !isCommitSha(input.commit)) { - return Effect.fail(new GitHubDiffCommitError({ command: "gh", cwd: input.cwd })); + return Effect.fail(new GitHubDiffCommitError({ cwd: input.cwd })); } // A cursor only ever comes from the files walk, so a reader carrying one is already past // the point where `gh pr diff` had anything to say. if (input.cursor !== undefined) { const page = diffCursorPage(input.cursor); return page === null - ? Effect.fail(new GitHubDiffCursorError({ command: "gh", cwd: input.cwd })) + ? Effect.fail(new GitHubDiffCursorError({ cwd: input.cwd })) : filesPage(page); } // `gh pr diff` speaks for the whole pull request and has no way to name one commit of it. @@ -2283,7 +2262,6 @@ export const make = Effect.gen(function* () { ? Effect.succeed({ comments, nextCursor }) : Effect.fail( new GitHubSubjectScopeError({ - command: "gh", cwd: input.cwd, operation: "getReviewThreadComments", }), @@ -2297,7 +2275,7 @@ export const make = Effect.gen(function* () { const { owner, name } = parseRepositorySelector(input.repository); const threadPage = ( cursor: string | null, - ): Effect.Effect => + ): Effect.Effect => graphqlRead({ cwd: input.cwd, host: input.host, @@ -2522,7 +2500,6 @@ export const make = Effect.gen(function* () { if (detail.headSha == null || detail.headRepositoryOwner == null) { return Effect.fail( new GitHubWorkflowApprovalHeadUnavailableError({ - command: "gh", cwd: input.cwd, number: input.number, }), @@ -2547,7 +2524,6 @@ export const make = Effect.gen(function* () { if (current.headSha == null || current.headRepositoryOwner == null) { return Effect.fail( new GitHubWorkflowApprovalHeadUnavailableError({ - command: "gh", cwd: input.cwd, number: input.number, }), @@ -2562,7 +2538,6 @@ export const make = Effect.gen(function* () { ) { return Effect.fail( new GitHubWorkflowApprovalHeadChangedError({ - command: "gh", cwd: input.cwd, number: input.number, }), @@ -2737,7 +2712,7 @@ export const make = Effect.gen(function* () { after: string | null, collected: ReadonlyArray, pagesLeft: number, - ): Effect.Effect => + ): Effect.Effect => graphqlRead({ cwd: input.cwd, host: input.host, @@ -2799,7 +2774,6 @@ export const make = Effect.gen(function* () { ? Effect.succeed(givenSubjectId) : Effect.fail( new GitHubSubjectScopeError({ - command: "gh", cwd: input.cwd, operation: "setReaction", }), @@ -2850,7 +2824,6 @@ export const make = Effect.gen(function* () { ? Effect.succeed(input.commentId) : Effect.fail( new GitHubSubjectScopeError({ - command: "gh", cwd: input.cwd, operation: "updateComment", }), @@ -2871,4 +2844,4 @@ export const make = Effect.gen(function* () { }); }); -export const layer = Layer.effect(GitHubPullRequestCli, make); +export const layer = Layer.effect(GitHubPullRequestApi, make); diff --git a/apps/server/src/pullRequest/GitHubPullRequestProvider.test.ts b/apps/server/src/pullRequest/GitHubPullRequestProvider.test.ts index 88079ebb94c7f8ae31e36f906eaaace6d96f2661..f00cbfaf5d3246a4aa9598c7508560f385a9415e 100644 --- a/apps/server/src/pullRequest/GitHubPullRequestProvider.test.ts +++ b/apps/server/src/pullRequest/GitHubPullRequestProvider.test.ts @@ -7,8 +7,8 @@ import type { PullRequestReaction } from "@t3tools/contracts"; import { decodePullRequestDetailJson } from "./gitHubPullRequestJson.ts"; import * as GitHubApi from "../sourceControl/GitHubApi.ts"; -import * as GitHubCli from "../sourceControl/GitHubCli.ts"; -import * as GitHubPullRequestCli from "./GitHubPullRequestCli.ts"; +import * as GitHubRepositoryApi from "../sourceControl/GitHubRepositoryApi.ts"; +import * as GitHubPullRequestApi from "./GitHubPullRequestApi.ts"; import type { GitHubPullRequestCore } from "./gitHubPullRequestJson.ts"; import { gitHubViewerPermissions, loginAvatarUrl, make } from "./GitHubPullRequestProvider.ts"; import type { GitHubReviewThreadComments } from "./gitHubPullRequestJson.ts"; @@ -31,13 +31,12 @@ it.effect("maps credential verification failures without relabeling operation fa let operations = 0; const provider = yield* make.pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, withVerifiedCredential: (_input, use) => verificationFails ? Effect.fail( - new GitHubPullRequestCli.GitHubViewerLoginUnavailableError({ - command: "gh", + new GitHubPullRequestApi.GitHubViewerLoginUnavailableError({ cwd: "/w", }), ) @@ -76,7 +75,7 @@ it.effect("refreshes checks without permissions or comparison reads", () => ); const provider = yield* make.pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => Effect.sync(() => { @@ -119,7 +118,7 @@ it.effect("uses one narrow read for a linked pull request summary", () => let summaryReads = 0; const provider = yield* make.pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestSummary: () => Effect.sync(() => { @@ -169,7 +168,7 @@ it.effect("declares host-native stacks and passes the one the CLI reads through" }; const provider = yield* make.pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestStack: (input) => Effect.succeed(input.number === 7 ? stack : null), }), @@ -189,12 +188,11 @@ it.effect("reports a failed stack read against its own operation", () => Effect.gen(function* () { const provider = yield* make.pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestStack: () => Effect.fail( - new GitHubPullRequestCli.GitHubPullRequestReadError({ - command: "gh", + new GitHubPullRequestApi.GitHubPullRequestReadError({ cwd: "/w", operation: "getPullRequestStack", cause: new Error("unreadable"), @@ -343,7 +341,7 @@ describe("gitHubViewerPermissions", () => { } }).pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => Effect.succeed({ @@ -437,7 +435,7 @@ describe("gitHubViewerPermissions", () => { ]); }).pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => Effect.succeed({ @@ -541,7 +539,7 @@ it.effect( Effect.gen(function* () { const provider = yield* make.pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ getPullRequestDetail: () => Effect.succeed({ ...openDetail, comparison: { behindBy: 2, viewerCanUpdate: true } }), listWorkflowRunsRequiringApproval: () => @@ -582,7 +580,7 @@ it.effect("does not classify same-repository gates as fork workflow approvals", expect(detail.checks).toEqual([]); }).pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => Effect.succeed({ ...openDetail, isCrossRepository: false }), listWorkflowRunsRequiringApproval: () => @@ -621,13 +619,12 @@ it.effect("keeps an unsafe workflow approval scope visible as unknown", () => ]); }).pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => Effect.succeed(openDetail), listWorkflowRunsRequiringApproval: () => Effect.fail( - new GitHubPullRequestCli.GitHubWorkflowApprovalRefusedError({ - command: "gh", + new GitHubPullRequestApi.GitHubWorkflowApprovalRefusedError({ cwd: "/w", number: 7, reason: "head-not-unique", @@ -664,7 +661,7 @@ it.effect("propagates workflow discovery rate limits", () => expect(error.reason).toBe("rate-limited"); }).pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => Effect.succeed(openDetail), listWorkflowRunsRequiringApproval: () => @@ -713,7 +710,7 @@ describe("getViewerPermissions", () => { expect(permissions.actions).not.toContain("update-branch"); }).pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => Effect.die("Unexpected detail read"), getViewerAccess: () => Effect.sync(() => (accessReads++, access)), @@ -723,13 +720,13 @@ describe("getViewerPermissions", () => { }); const layerWithDetail = ( - detail: Effect.Effect, + detail: Effect.Effect, onDetail: (allowReserve: boolean) => void = () => {}, ) => - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => - GitHubCli.AllowGitHubReserve.pipe( + GitHubRepositoryApi.AllowGitHubReserve.pipe( Effect.tap((allowReserve) => Effect.sync(() => onDetail(allowReserve))), Effect.flatMap(() => detail), ), @@ -789,12 +786,11 @@ describe("getViewerPermissions", () => { expect(permissions.actions).not.toContain("update-branch"); }).pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestDetail: () => Effect.fail( - new GitHubPullRequestCli.GitHubPullRequestReadError({ - command: "gh", + new GitHubPullRequestApi.GitHubPullRequestReadError({ cwd: "/w", operation: "getPullRequestDetail", cause: new Error("head changed"), @@ -869,7 +865,7 @@ describe("getChangeRequest commits", () => { }; const layerWith = (commits: GitHubReviewThreadComments["commits"]) => - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestActivity: () => Effect.succeed({ @@ -956,7 +952,7 @@ describe("getChangeRequestActivity dismissed reviews", () => { viewer: { canUpdate: true, didAuthor: false }, }; const layerFor = (body: string) => - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, getPullRequestActivity: () => Effect.succeed({ author: null, comments: [dismissedReview(body)], commits: [] }), @@ -1040,7 +1036,7 @@ describe("editing", () => { ]); }).pipe( Effect.provide( - Layer.mock(GitHubPullRequestCli.GitHubPullRequestCli)({ + Layer.mock(GitHubPullRequestApi.GitHubPullRequestApi)({ revalidateChecks: (_input, read) => read, updatePullRequest: (input) => Effect.sync(() => void rewrites.push(input)), updateComment: (input) => Effect.sync(() => void rewrites.push(input)), diff --git a/apps/server/src/pullRequest/GitHubPullRequestProvider.ts b/apps/server/src/pullRequest/GitHubPullRequestProvider.ts index 74c5852a48a49eb331fd92d93334e3fb78895ac5..5e6f2420729949a87dd1e1762659c6c6232708d5 100644 --- a/apps/server/src/pullRequest/GitHubPullRequestProvider.ts +++ b/apps/server/src/pullRequest/GitHubPullRequestProvider.ts @@ -7,8 +7,8 @@ import type { PullRequestViewerPermissions, } from "@t3tools/contracts"; -import * as GitHubCli from "../sourceControl/GitHubCli.ts"; -import * as GitHubPullRequestCli from "./GitHubPullRequestCli.ts"; +import * as GitHubRepositoryApi from "../sourceControl/GitHubRepositoryApi.ts"; +import * as GitHubPullRequestApi from "./GitHubPullRequestApi.ts"; import { PullRequestProviderError, type PullRequestProviderFailure, @@ -104,7 +104,7 @@ export function gitHubViewerPermissions(access: GitHubViewerAccess): PullRequest /** The tags that mean GitHub is out of reach for this account, rather than one request failing. */ export function gitHubProviderFailure( - error: GitHubPullRequestCli.GitHubPullRequestCliError, + error: GitHubPullRequestApi.GitHubPullRequestApiError, ): PullRequestProviderFailure { switch (error._tag) { case "GitHubCliMissingError": @@ -197,9 +197,9 @@ const rendersEmpty = (body: string): boolean => body.replace(//g, "").trim().length === 0; export const make = Effect.gen(function* () { - const cli = yield* GitHubPullRequestCli.GitHubPullRequestCli; + const cli = yield* GitHubPullRequestApi.GitHubPullRequestApi; - const fail = (operation: string) => (error: GitHubPullRequestCli.GitHubPullRequestCliError) => + const fail = (operation: string) => (error: GitHubPullRequestApi.GitHubPullRequestApiError) => new PullRequestProviderError({ provider: "github", operation, @@ -526,7 +526,7 @@ export const make = Effect.gen(function* () { // comparison, so one read usually answers what used to take three. When that heavier read // fails, the light access read still answers, withholding only update-branch. return cli.getPullRequestDetail(input).pipe( - Effect.provideService(GitHubCli.AllowGitHubReserve, true), + Effect.provideService(GitHubRepositoryApi.AllowGitHubReserve, true), Effect.map((pullRequest) => gitHubViewerPermissions({ ...pullRequest.viewerAccess, diff --git a/apps/server/src/pullRequest/PullRequestProviderRegistry.ts b/apps/server/src/pullRequest/PullRequestProviderRegistry.ts index 60de91d0e99af4b99ffce017b52b39365a2efcc2..efbe37f991eeecb69fbe623694739c05f5d6ca11 100644 --- a/apps/server/src/pullRequest/PullRequestProviderRegistry.ts +++ b/apps/server/src/pullRequest/PullRequestProviderRegistry.ts @@ -16,7 +16,7 @@ import * as AzureDevOpsPullRequestCli from "./AzureDevOpsPullRequestCli.ts"; import * as AzureDevOpsPullRequestProvider from "./AzureDevOpsPullRequestProvider.ts"; import * as BitbucketPullRequestApi from "./BitbucketPullRequestApi.ts"; import * as BitbucketPullRequestProvider from "./BitbucketPullRequestProvider.ts"; -import * as GitHubPullRequestCli from "./GitHubPullRequestCli.ts"; +import * as GitHubPullRequestApi from "./GitHubPullRequestApi.ts"; import * as GitHubPullRequestProvider from "./GitHubPullRequestProvider.ts"; import * as GitLabPullRequestCli from "./GitLabPullRequestCli.ts"; import * as GitLabPullRequestProvider from "./GitLabPullRequestProvider.ts"; @@ -61,11 +61,11 @@ export const make = Effect.map( export const layer = Layer.effect(PullRequestProviderRegistry, make).pipe( Layer.provide( - GitHubPullRequestCli.layer.pipe( + GitHubPullRequestApi.layer.pipe( Layer.provide( GitHubApi.layer.pipe( Layer.provide(GitHubCredentials.layer), - // The same layers GitHubCli merges, so both share one budget and one pause per host. + // The same layers GitHubRepositoryApi merges, so both share one budget and one pause per host. Layer.provide(GitHubGraphQlBudget.layer), Layer.provide(SourceControlRateLimit.layer), ), diff --git a/apps/server/src/pullRequest/PullRequestService.ts b/apps/server/src/pullRequest/PullRequestService.ts index 3da0b9ba2e03b40d173e33858c6e3456294dff43..9e2b81d97d5f5545e4755c73bb665541b35831d3 100644 --- a/apps/server/src/pullRequest/PullRequestService.ts +++ b/apps/server/src/pullRequest/PullRequestService.ts @@ -77,7 +77,7 @@ import { import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; import { detectSourceControlProviderFromRemoteUrl } from "@t3tools/shared/sourceControl"; -import { AllowGitHubReserve } from "../sourceControl/GitHubCli.ts"; +import { AllowGitHubReserve } from "../sourceControl/GitHubRepositoryApi.ts"; import * as ProjectService from "../project/ProjectService.ts"; import * as ServerSettings from "../serverSettings.ts"; import * as PullRequestFilesViewed from "../persistence/PullRequestFilesViewed.ts"; diff --git a/apps/server/src/pullRequest/gitHubConditionalChecks.ts b/apps/server/src/pullRequest/gitHubConditionalChecks.ts index 8172bf544074c6aa7776bfaf2a35869294caeebe..6975da251936bbab015b8544e033ece84e8f4452 100644 --- a/apps/server/src/pullRequest/gitHubConditionalChecks.ts +++ b/apps/server/src/pullRequest/gitHubConditionalChecks.ts @@ -9,7 +9,7 @@ import { PositiveInt, type PullRequestChecks } from "@t3tools/contracts"; import * as GitHubApi from "../sourceControl/GitHubApi.ts"; import type { GitHubPullRequestDetail } from "./gitHubPullRequestJson.ts"; -import type { GitHubPullRequestCliError } from "./GitHubPullRequestCli.ts"; +import type { GitHubPullRequestApiError } from "./GitHubPullRequestApi.ts"; import type { ProviderRepositoryRef } from "./PullRequestProvider.ts"; const HeadSchema = Schema.Struct({ @@ -70,7 +70,7 @@ export const makeChecksRevalidator = Effect.gen(function* () { Pick & { workflowApprovalsRequired?: number; }, - GitHubPullRequestCliError + GitHubPullRequestApiError >, ) => Effect.gen(function* () { diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index ebbc73758a437b85e9c18318763a4f08e4e08feb..fdd7d6c7165f4e16ed52ec8e9add1d2694ecb0a0 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -50,7 +50,7 @@ import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts"; import * as CheckpointStore from "./checkpointing/CheckpointStore.ts"; import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts"; import * as BitbucketApi from "./sourceControl/BitbucketApi.ts"; -import * as GitHubCli from "./sourceControl/GitHubCli.ts"; +import * as GitHubRepositoryApi from "./sourceControl/GitHubRepositoryApi.ts"; import * as GitLabCli from "./sourceControl/GitLabCli.ts"; import * as ForgejoCli from "./sourceControl/ForgejoCli.ts"; import * as TextGeneration from "./textGeneration/TextGeneration.ts"; @@ -270,7 +270,7 @@ const layerSourceControlProviderRegistry = SourceControlProviderRegistry.layer.p Layer.mergeAll( AzureDevOpsCli.layer, BitbucketApi.layer, - GitHubCli.layer, + GitHubRepositoryApi.layer, GitLabCli.layer, ForgejoCli.layer, ), @@ -565,7 +565,7 @@ const layerRuntimeCoreDependenciesBase = Layer.mergeAll( Layer.provideMerge(layerServerSettings), // The asset route uses the registry's GitHub credential for private PR media. Layer.provideMerge(layerSourceControlProviderRegistry), - Layer.provideMerge(GitHubCli.layer), + Layer.provideMerge(GitHubRepositoryApi.layer), Layer.provideMerge(layerGit), Layer.provideMerge(layerVcs), Layer.provideMerge(Layer.mergeAll(layerTerminal, layerPreview, layerDevice)), diff --git a/apps/server/src/sourceControl/GitHubCli.test.ts b/apps/server/src/sourceControl/GitHubRepositoryApi.test.ts similarity index 91% rename from apps/server/src/sourceControl/GitHubCli.test.ts rename to apps/server/src/sourceControl/GitHubRepositoryApi.test.ts index 3954617008a58cfbc94c7603ce29b84c57ac8370..688216211f1fd15e22c2466fdcd13a3c27f5755f 100644 --- a/apps/server/src/sourceControl/GitHubCli.test.ts +++ b/apps/server/src/sourceControl/GitHubRepositoryApi.test.ts @@ -11,7 +11,7 @@ import { ChildProcessSpawner } from "effect/process"; import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as GitHubApi from "./GitHubApi.ts"; -import * as GitHubCli from "./GitHubCli.ts"; +import * as GitHubRepositoryApi from "./GitHubRepositoryApi.ts"; import * as GitHubCredentials from "./GitHubCredentials.ts"; const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); @@ -51,7 +51,7 @@ const node = (number: number, headRefName: string, owner = "acme") => ({ }); /** - * A GitHubCli over a mocked GitHubApi, git driver and process. `remotes` is what + * A GitHubRepositoryApi over a mocked GitHubApi, git driver and process. `remotes` is what * `git remote -v` prints; `git` records every driver call. */ function harness(input: { @@ -92,7 +92,10 @@ function harness(input: { args.args[0] === "remote" ? processOutput(input.remotes) : processOutput("", 1), ), }); - const layer = Layer.effect(GitHubCli.GitHubCli, GitHubCli.make).pipe( + const layer = Layer.effect( + GitHubRepositoryApi.GitHubRepositoryApi, + GitHubRepositoryApi.make, + ).pipe( Layer.provide( Layer.mergeAll( driver, @@ -111,7 +114,7 @@ describe("selectGitHubBaseRepository", () => { .flatMap(([name, url]) => [`${name}\t${url} (fetch)`, `${name}\t${url} (push)`]) .join("\n"); const select = (input: { remotes: string; resolved?: string }) => - GitHubCli.selectGitHubBaseRepository({ resolved: "", host: "github.com", ...input }); + GitHubRepositoryApi.selectGitHubBaseRepository({ resolved: "", host: "github.com", ...input }); it("picks the repository gh reads without a prompt", () => { assert.deepStrictEqual(select({ remotes: remotes(["fork", "git@github.com:me/web.git"]) }), { @@ -175,7 +178,7 @@ describe("selectGitHubBaseRepository", () => { }); }); -describe("GitHubCli repository resolution", () => { +describe("GitHubRepositoryApi repository resolution", () => { it.effect("reads the repository gh would pick from the remotes", () => { const paths: string[] = []; const { layer } = harness({ @@ -197,7 +200,7 @@ describe("GitHubCli repository resolution", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; assert.strictEqual(yield* gh.getDefaultBranch({ cwd: "/repo" }), "trunk"); assert.deepStrictEqual(paths, ["github.com repos/acme/web"]); }).pipe(Effect.provide(layer)); @@ -221,16 +224,19 @@ describe("GitHubCli repository resolution", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; yield* gh.getDefaultBranch({ cwd: "/repo" }); // A provider's host hint for the same alias (`github` here) resolves the same way. yield* gh.getDefaultBranch({ cwd: "/repo", rateLimitHost: "github" }); assert.deepStrictEqual(hosts, ["github.com repos/acme/web", "github.com repos/acme/web"]); assert.strictEqual( - GitHubCli.gitHubApiHostForRemote("git@github.example.com:a/b.git"), + GitHubRepositoryApi.gitHubApiHostForRemote("git@github.example.com:a/b.git"), "github.example.com", ); - assert.strictEqual(GitHubCli.gitHubApiHostForRemote("git@gitlab.com:a/b.git"), null); + assert.strictEqual( + GitHubRepositoryApi.gitHubApiHostForRemote("git@gitlab.com:a/b.git"), + null, + ); }).pipe(Effect.provide(layer)); }); @@ -240,15 +246,15 @@ describe("GitHubCli repository resolution", () => { api: {}, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; const error = yield* gh.getDefaultBranch({ cwd: "/repo" }).pipe(Effect.flip); - assert.strictEqual(error._tag, "GitHubCliCommandError"); + assert.strictEqual(error._tag, "GitHubRepositoryCommandError"); assert.include(String((error.cause as Error).message), "No GitHub repository"); }).pipe(Effect.provide(layer)); }); }); -describe("GitHubCli.listPullRequestsByHead", () => { +describe("GitHubRepositoryApi.listPullRequestsByHead", () => { const remotes = remotesOutput(["origin", "git@github.com:acme/web.git"]); it.effect("reads a background sweep's staggered heads in one GraphQL document", () => { @@ -271,7 +277,7 @@ describe("GitHubCli.listPullRequestsByHead", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; const lookup = (headSelector: string) => gh .listPullRequestsByHead({ @@ -324,7 +330,7 @@ describe("GitHubCli.listPullRequestsByHead", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; const lookups = yield* Effect.all( Array.from({ length: 26 }, (_, index) => gh.listPullRequestsByHead({ @@ -368,7 +374,7 @@ describe("GitHubCli.listPullRequestsByHead", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; const open = yield* gh .listOpenPullRequests({ cwd: "/repo", headSelector: "me:main", limit: 1 }) .pipe(Effect.forkChild); @@ -397,24 +403,24 @@ describe("GitHubCli.listPullRequestsByHead", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; const read = (headSelector: string) => gh .listPullRequestsByHead({ cwd: "/repo", headSelector, state: "open", limit: 1 }) .pipe(Effect.flip, Effect.forkChild); const missing = yield* read("missing"); yield* TestClock.adjust("500 millis"); - assert.strictEqual((yield* Fiber.join(missing))._tag, "GitHubCliUnavailableError"); + assert.strictEqual((yield* Fiber.join(missing))._tag, "GitHubRepositoryUnavailableError"); const limited = yield* read("limited"); yield* TestClock.adjust("500 millis"); const error = yield* Fiber.join(limited); - assert.strictEqual(error._tag, "GitHubCliRateLimitError"); + assert.strictEqual(error._tag, "GitHubRepositoryRateLimitError"); assert.propertyVal(error, "retryAt", 123); }).pipe(Effect.provide(layer)); }); }); -describe("GitHubCli.getPullRequest", () => { +describe("GitHubRepositoryApi.getPullRequest", () => { it.effect("reads a pull request by number, and by URL on its own repository", () => { const variables: Array = []; const { layer } = harness({ @@ -428,7 +434,7 @@ describe("GitHubCli.getPullRequest", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; assert.strictEqual((yield* gh.getPullRequest({ cwd: "/repo", reference: "#42" })).number, 42); yield* gh.getPullRequest({ cwd: "/repo", @@ -449,14 +455,14 @@ describe("GitHubCli.getPullRequest", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; const error = yield* gh.getPullRequest({ cwd: "/repo", reference: "7" }).pipe(Effect.flip); assert.strictEqual(error._tag, "GitHubPullRequestNotFoundError"); }).pipe(Effect.provide(layer)); }); }); -describe("GitHubCli writes", () => { +describe("GitHubRepositoryApi writes", () => { it.effect("creates a cross-repository pull request with an owner:branch head", () => { const requests: Array = []; const { layer } = harness({ @@ -476,7 +482,7 @@ describe("GitHubCli writes", () => { const fs = yield* FileSystem.FileSystem; const bodyFile = yield* fs.makeTempFileScoped({ suffix: ".md" }); yield* fs.writeFileString(bodyFile, "Body"); - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; yield* gh.createPullRequest({ cwd: "/repo", baseBranch: "main", @@ -515,7 +521,7 @@ describe("GitHubCli writes", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; const urls = yield* gh.createRepository({ cwd: "/repo", repository: "acme/new", @@ -531,7 +537,7 @@ describe("GitHubCli writes", () => { }); }); -describe("GitHubCli.checkoutPullRequest", () => { +describe("GitHubRepositoryApi.checkoutPullRequest", () => { const repository = (fullName: string, defaultBranch = "main") => restResponse({ full_name: fullName, @@ -551,7 +557,7 @@ describe("GitHubCli.checkoutPullRequest", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; yield* gh.checkoutPullRequest({ cwd: "/repo", reference: "5" }); assert.deepStrictEqual(git, [ [ @@ -588,7 +594,7 @@ describe("GitHubCli.checkoutPullRequest", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; yield* Effect.flip(gh.checkoutPullRequest({ cwd: "/repo", reference: "6", force: true })); // Nothing touched the local branches: `main` must not be reset to the fork's commit. assert.deepStrictEqual(git, []); @@ -609,7 +615,7 @@ describe("GitHubCli.checkoutPullRequest", () => { }, }); return Effect.gen(function* () { - const gh = yield* GitHubCli.GitHubCli; + const gh = yield* GitHubRepositoryApi.GitHubRepositoryApi; yield* gh.checkoutPullRequest({ cwd: "/repo", reference: "6", force: true }); assert.deepStrictEqual(git, [ [ @@ -632,7 +638,7 @@ describe("GitHubCli.checkoutPullRequest", () => { it("names the local branch the way gh pr checkout does", () => { const name = (headRefName: string, isCrossRepository: boolean) => - GitHubCli.pullRequestCheckoutBranchName({ + GitHubRepositoryApi.pullRequestCheckoutBranchName({ headRefName, headOwner: "someone", isCrossRepository, diff --git a/apps/server/src/sourceControl/GitHubCli.ts b/apps/server/src/sourceControl/GitHubRepositoryApi.ts similarity index 90% rename from apps/server/src/sourceControl/GitHubCli.ts rename to apps/server/src/sourceControl/GitHubRepositoryApi.ts index d12e5f334febdc989e8efd1b5a32aa316afef763..595d869d72275071fe7078ea1193387b33941007 100644 --- a/apps/server/src/sourceControl/GitHubCli.ts +++ b/apps/server/src/sourceControl/GitHubRepositoryApi.ts @@ -33,24 +33,23 @@ import { export const AllowGitHubReserve = GitHubApi.AllowGitHubReserve; -const gitHubCliFailureFields = { - command: Schema.Literal("gh"), +const gitHubFailureFields = { cwd: Schema.String, cause: Schema.Defect(), } as const; -export class GitHubCliUnavailableError extends Schema.TaggedError()( - "GitHubCliUnavailableError", - gitHubCliFailureFields, +export class GitHubRepositoryUnavailableError extends Schema.TaggedError()( + "GitHubRepositoryUnavailableError", + gitHubFailureFields, ) { override get message(): string { return "No GitHub credential on the server. Set GH_TOKEN, or install the GitHub CLI and run `gh auth login`."; } } -export class GitHubCliAuthenticationError extends Schema.TaggedError()( - "GitHubCliAuthenticationError", - gitHubCliFailureFields, +export class GitHubRepositoryAuthenticationError extends Schema.TaggedError()( + "GitHubRepositoryAuthenticationError", + gitHubFailureFields, ) { override get message(): string { // A missing or turned-off credential already says what to do about it. @@ -60,9 +59,9 @@ export class GitHubCliAuthenticationError extends Schema.TaggedError()( - "GitHubCliRateLimitError", - { ...gitHubCliFailureFields, retryAt: Schema.optionalKey(Schema.Finite) }, +export class GitHubRepositoryRateLimitError extends Schema.TaggedError()( + "GitHubRepositoryRateLimitError", + { ...gitHubFailureFields, retryAt: Schema.optionalKey(Schema.Finite) }, ) { override get message(): string { return "GitHub API rate limit exceeded. Requests resume when the limit resets."; @@ -71,16 +70,16 @@ export class GitHubCliRateLimitError extends Schema.TaggedError()( "GitHubPullRequestNotFoundError", - gitHubCliFailureFields, + gitHubFailureFields, ) { override get message(): string { return "Pull request not found. Check the PR number or URL and try again."; } } -export class GitHubCliCommandError extends Schema.TaggedError()( - "GitHubCliCommandError", - { ...gitHubCliFailureFields, httpStatus: Schema.optional(Schema.Int) }, +export class GitHubRepositoryCommandError extends Schema.TaggedError()( + "GitHubRepositoryCommandError", + { ...gitHubFailureFields, httpStatus: Schema.optional(Schema.Int) }, ) { override get message(): string { // GitHub's own reason ("A pull request already exists…") or the failed step's, when known. @@ -92,15 +91,14 @@ export class GitHubCliCommandError extends Schema.TaggedError()( "GitHubPullRequestListDecodeError", - gitHubCliDecodeFields, + gitHubDecodeFields, ) { override get message(): string { return "GitHub returned an invalid pull request list."; @@ -109,7 +107,7 @@ export class GitHubPullRequestListDecodeError extends Schema.TaggedError()( "GitHubChangeRequestListDecodeError", - gitHubCliDecodeFields, + gitHubDecodeFields, ) { override get message(): string { return "GitHub returned an invalid change request list."; @@ -118,7 +116,7 @@ export class GitHubChangeRequestListDecodeError extends Schema.TaggedError()( "GitHubPullRequestDecodeError", - gitHubCliDecodeFields, + gitHubDecodeFields, ) { override get message(): string { return "GitHub returned an invalid pull request."; @@ -127,53 +125,56 @@ export class GitHubPullRequestDecodeError extends Schema.TaggedError()( "GitHubRepositoryDecodeError", - gitHubCliDecodeFields, + gitHubDecodeFields, ) { override get message(): string { return "GitHub returned an invalid repository."; } } -export const GitHubCliError = Schema.Union([ - GitHubCliUnavailableError, - GitHubCliAuthenticationError, - GitHubCliRateLimitError, +export const GitHubRepositoryApiError = Schema.Union([ + GitHubRepositoryUnavailableError, + GitHubRepositoryAuthenticationError, + GitHubRepositoryRateLimitError, GitHubPullRequestNotFoundError, - GitHubCliCommandError, + GitHubRepositoryCommandError, GitHubPullRequestListDecodeError, GitHubChangeRequestListDecodeError, GitHubPullRequestDecodeError, GitHubRepositoryDecodeError, ]); -export type GitHubCliError = typeof GitHubCliError.Type; +export type GitHubRepositoryApiError = typeof GitHubRepositoryApiError.Type; -export const isGitHubCliError = Schema.is(GitHubCliError); +export const isGitHubRepositoryApiError = Schema.is(GitHubRepositoryApiError); /** Maps a GitHub API failure onto the errors callers of this service already handle. */ -function fromGitHubApiError(cwd: string, error: GitHubApi.GitHubApiError): GitHubCliError { - const context = { command: "gh" as const, cwd, cause: error }; +function fromGitHubApiError( + cwd: string, + error: GitHubApi.GitHubApiError, +): GitHubRepositoryApiError { + const context = { cwd, cause: error }; switch (error._tag) { case "GitHubCliMissingError": - return new GitHubCliUnavailableError(context); + return new GitHubRepositoryUnavailableError(context); case "GitHubNotSignedInError": case "GitHubHostDisabledError": case "GitHubApiAuthenticationError": - return new GitHubCliAuthenticationError(context); + return new GitHubRepositoryAuthenticationError(context); case "GitHubCliFailedError": - return new GitHubCliCommandError(context); + return new GitHubRepositoryCommandError(context); case "GitHubApiRateLimitError": - return new GitHubCliRateLimitError({ + return new GitHubRepositoryRateLimitError({ ...context, ...(error.retryAt === undefined ? {} : { retryAt: error.retryAt }), }); case "SourceControlRateLimitPausedError": - return new GitHubCliRateLimitError({ ...context, retryAt: error.retryAt }); + return new GitHubRepositoryRateLimitError({ ...context, retryAt: error.retryAt }); case "GitHubApiNotFoundError": return new GitHubPullRequestNotFoundError(context); case "GitHubApiResponseError": - return new GitHubCliCommandError({ ...context, httpStatus: error.status }); + return new GitHubRepositoryCommandError({ ...context, httpStatus: error.status }); case "GitHubApiRequestError": - return new GitHubCliCommandError(context); + return new GitHubRepositoryCommandError(context); } } @@ -208,15 +209,15 @@ export interface GitHubRepositoryCloneUrls { readonly sshUrl: string; } -export class GitHubCli extends Context.Service< - GitHubCli, +export class GitHubRepositoryApi extends Context.Service< + GitHubRepositoryApi, { readonly listOpenPullRequests: (input: { readonly cwd: string; readonly headSelector: string; readonly limit?: number; readonly rateLimitHost?: string; - }) => Effect.Effect, GitHubCliError>; + }) => Effect.Effect, GitHubRepositoryApiError>; /** * Pull requests whose head is `headSelector` (a branch, or `owner:branch` for a fork), in the @@ -230,24 +231,24 @@ export class GitHubCli extends Context.Service< readonly limit: number; /** The checkout's GitHub API host. Without it, the host comes from the git remotes. */ readonly rateLimitHost?: string; - }) => Effect.Effect, GitHubCliError>; + }) => Effect.Effect, GitHubRepositoryApiError>; readonly getPullRequest: (input: { readonly cwd: string; readonly reference: string; readonly rateLimitHost?: string; - }) => Effect.Effect; + }) => Effect.Effect; readonly getRepositoryCloneUrls: (input: { readonly cwd: string; readonly repository: string; - }) => Effect.Effect; + }) => Effect.Effect; readonly createRepository: (input: { readonly cwd: string; readonly repository: string; readonly visibility: SourceControlRepositoryVisibility; - }) => Effect.Effect; + }) => Effect.Effect; readonly createPullRequest: (input: { readonly cwd: string; @@ -255,20 +256,20 @@ export class GitHubCli extends Context.Service< readonly headSelector: string; readonly title: string; readonly bodyFile: string; - }) => Effect.Effect; + }) => Effect.Effect; readonly getDefaultBranch: (input: { readonly cwd: string; readonly rateLimitHost?: string; - }) => Effect.Effect; + }) => Effect.Effect; readonly checkoutPullRequest: (input: { readonly cwd: string; readonly reference: string; readonly force?: boolean; - }) => Effect.Effect; + }) => Effect.Effect; } ->()("t3/sourceControl/GitHubCli") {} +>()("t3/sourceControl/GitHubRepositoryApi") {} /** * The repository `gh pr list` reads in a checkout, picked the way gh picks one without a @@ -385,7 +386,7 @@ class PullRequestsByHeadRead extends Request.Class< readonly allowReserve: boolean; }, ReadonlyArray, - GitHubCliError + GitHubRepositoryApiError > {} /** One aliased `pullRequests` connection per lookup, each head and state passed as a variable. */ @@ -549,7 +550,7 @@ export const make = Effect.gen(function* () { const gitRead = (cwd: string, args: ReadonlyArray) => process.run({ - operation: "GitHubCli.resolveRepository", + operation: "GitHubRepositoryApi.resolveRepository", command: "git", args, cwd, @@ -558,13 +559,13 @@ export const make = Effect.gen(function* () { }); const commandFailure = (cwd: string, detail: string) => - new GitHubCliCommandError({ command: "gh", cwd, cause: new Error(detail) }); + new GitHubRepositoryCommandError({ cwd, cause: new Error(detail) }); /** * The repository `gh` would act on in `cwd`: GH_REPO, else the remote `gh` would pick, else * the remote the caller resolved the provider from, else the best-ranked GitHub remote. */ - const resolveRepository = Effect.fn("GitHubCli.resolveRepository")(function* (input: { + const resolveRepository = Effect.fn("GitHubRepositoryApi.resolveRepository")(function* (input: { readonly cwd: string; readonly host?: string | undefined; }) { @@ -623,7 +624,7 @@ export const make = Effect.gen(function* () { cwd: string, input: GitHubApi.GitHubGraphQlInput, decode: (raw: string) => Result.Result, - onDecodeFailure: (cause: unknown) => GitHubCliError, + onDecodeFailure: (cause: unknown) => GitHubRepositoryApiError, ) => api.graphql(input).pipe( Effect.mapError((error) => fromGitHubApiError(cwd, error)), @@ -646,7 +647,7 @@ export const make = Effect.gen(function* () { Pick >; readonly allowReserve: boolean; - readonly onDecodeFailure: (cause: unknown) => GitHubCliError; + readonly onDecodeFailure: (cause: unknown) => GitHubRepositoryApiError; }) => { const query = buildPullRequestsByHeadQuery(input.lookups); return graphqlJson( @@ -668,7 +669,6 @@ export const make = Effect.gen(function* () { decoded.data.repository === null ? Effect.fail( new GitHubPullRequestNotFoundError({ - command: "gh", cwd: input.cwd, cause: new Error("The repository could not be read."), }), @@ -697,8 +697,7 @@ export const make = Effect.gen(function* () { locator: { host, owner, name }, lookups: entries.map((entry) => entry.request), allowReserve, - onDecodeFailure: (cause) => - new GitHubChangeRequestListDecodeError({ command: "gh", cwd, cause }), + onDecodeFailure: (cause) => new GitHubChangeRequestListDecodeError({ cwd, cause }), }).pipe( Effect.map((aliases) => { for (const [index, entry] of entries.entries()) { @@ -724,7 +723,7 @@ export const make = Effect.gen(function* () { RequestResolver.setDelay(BACKGROUND_HEAD_LOOKUP_BATCH_WINDOW), ); - const listByHead = Effect.fn("GitHubCli.listByHead")(function* (input: { + const listByHead = Effect.fn("GitHubRepositoryApi.listByHead")(function* (input: { readonly cwd: string; readonly headSelector: string; readonly state: PullRequestListState; @@ -759,7 +758,7 @@ export const make = Effect.gen(function* () { const toSummaries = (rows: ReadonlyArray) => rows.map(pullRequestSummary); - const readPullRequest = Effect.fn("GitHubCli.readPullRequest")(function* (input: { + const readPullRequest = Effect.fn("GitHubRepositoryApi.readPullRequest")(function* (input: { readonly cwd: string; readonly reference: string; readonly rateLimitHost?: string | undefined; @@ -787,7 +786,6 @@ export const make = Effect.gen(function* () { }))[0]; if (found === undefined) { return yield* new GitHubPullRequestNotFoundError({ - command: "gh", cwd: input.cwd, cause: new Error("No pull request has this head branch."), }); @@ -799,7 +797,7 @@ export const make = Effect.gen(function* () { ? parsed.locator : yield* resolveRepository({ cwd: input.cwd, host: input.rateLimitHost }); const decodeFailure = (cause: unknown) => - new GitHubPullRequestDecodeError({ command: "gh", cwd: input.cwd, cause }); + new GitHubPullRequestDecodeError({ cwd: input.cwd, cause }); const decoded = yield* graphqlJson( input.cwd, { @@ -815,7 +813,6 @@ export const make = Effect.gen(function* () { const node = decoded.data.repository?.pullRequest; if (node == null) { return yield* new GitHubPullRequestNotFoundError({ - command: "gh", cwd: input.cwd, cause: new Error("The pull request does not exist."), }); @@ -825,7 +822,7 @@ export const make = Effect.gen(function* () { return record; }); - const readRepository = Effect.fn("GitHubCli.readRepository")(function* ( + const readRepository = Effect.fn("GitHubRepositoryApi.readRepository")(function* ( cwd: string, locator: GitHubRepositoryLocator, ) { @@ -838,7 +835,6 @@ export const make = Effect.gen(function* () { const decoded = decodeRawRepository(response.body); if (Result.isFailure(decoded)) { return yield* new GitHubRepositoryDecodeError({ - command: "gh", cwd, cause: decoded.failure, }); @@ -846,23 +842,23 @@ export const make = Effect.gen(function* () { return decoded.success; }); - const readViewerLogin = Effect.fn("GitHubCli.readViewerLogin")(function* ( + const readViewerLogin = Effect.fn("GitHubRepositoryApi.readViewerLogin")(function* ( cwd: string, host: string, ) { const response = yield* rest(cwd, { host, operation: "getViewer", path: "user" }); const decoded = decodeViewerLogin(response.body); if (Result.isFailure(decoded)) { - return yield* new GitHubCliCommandError({ command: "gh", cwd, cause: decoded.failure }); + return yield* new GitHubRepositoryCommandError({ cwd, cause: decoded.failure }); } return decoded.success.login; }); const gitFailure = (cwd: string) => (cause: unknown) => - new GitHubCliCommandError({ command: "gh", cwd, cause }); + new GitHubRepositoryCommandError({ cwd, cause }); const runGit = (cwd: string, operation: string, args: ReadonlyArray) => - git.execute({ operation: `GitHubCli.checkoutPullRequest.${operation}`, cwd, args }); + git.execute({ operation: `GitHubRepositoryApi.checkoutPullRequest.${operation}`, cwd, args }); /** * `gh pr checkout` in plain git: the head branch is fetched from the remote that holds it (a @@ -870,8 +866,8 @@ export const make = Effect.gen(function* () { * track the head. A head branch that is gone is read from the base's `refs/pull//head`. * An existing branch fast-forwards, or with `force` is reset to the pull request. */ - const checkoutPullRequest: GitHubCli["Service"]["checkoutPullRequest"] = Effect.fn( - "GitHubCli.checkoutPullRequest", + const checkoutPullRequest: GitHubRepositoryApi["Service"]["checkoutPullRequest"] = Effect.fn( + "GitHubRepositoryApi.checkoutPullRequest", )(function* (input) { const reference = parsePullRequestReference(input.reference); const pullRequest = yield* readPullRequest({ cwd: input.cwd, reference: input.reference }); @@ -994,7 +990,7 @@ export const make = Effect.gen(function* () { }).pipe(Effect.mapError(gitFailure(input.cwd))); }); - return GitHubCli.of({ + return GitHubRepositoryApi.of({ listPullRequestsByHead: (input) => AllowGitHubReserve.pipe( Effect.flatMap((allowReserve) => listByHead({ ...input, allowReserve })), @@ -1043,7 +1039,6 @@ export const make = Effect.gen(function* () { const decoded = decodeRawRepository(response.body); if (Result.isFailure(decoded)) { return yield* new GitHubRepositoryDecodeError({ - command: "gh", cwd: input.cwd, cause: decoded.failure, }); @@ -1083,7 +1078,7 @@ export const make = Effect.gen(function* () { }); }); -export const layer = Layer.effect(GitHubCli, make).pipe( +export const layer = Layer.effect(GitHubRepositoryApi, make).pipe( Layer.provideMerge(GitHubApi.layer), Layer.provideMerge(GitHubCredentials.layer), Layer.provideMerge(GitHubGraphQlBudget.layer), diff --git a/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts b/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts index f3f7f31ed4e3b2d7def9a1259684de615131617d..89398b63478604b9c392d16f19f7e99c50463315 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts @@ -8,7 +8,7 @@ import { ChildProcessSpawner } from "effect/process"; import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as GitHubApi from "./GitHubApi.ts"; -import * as GitHubCli from "./GitHubCli.ts"; +import * as GitHubRepositoryApi from "./GitHubRepositoryApi.ts"; import { parseGitHubAuthStatus } from "./gitHubAuthStatus.ts"; import * as GitHubSourceControlProvider from "./GitHubSourceControlProvider.ts"; @@ -27,12 +27,15 @@ const processResult = ( }); function makeProvider( - github: Partial, + github: Partial, api: Partial = {}, ) { return GitHubSourceControlProvider.make.pipe( Effect.provide( - Layer.merge(Layer.mock(GitHubCli.GitHubCli)(github), Layer.mock(GitHubApi.GitHubApi)(api)), + Layer.merge( + Layer.mock(GitHubRepositoryApi.GitHubRepositoryApi)(github), + Layer.mock(GitHubApi.GitHubApi)(api), + ), ), ); } @@ -87,10 +90,9 @@ it.effect("maps GitHub PR summaries into provider-neutral change requests", () = }), ); -it.effect("adds safe request context while retaining GitHub CLI causes", () => +it.effect("adds safe request context while retaining GitHub API causes", () => Effect.gen(function* () { - const cause = new GitHubCli.GitHubPullRequestNotFoundError({ - command: "gh", + const cause = new GitHubRepositoryApi.GitHubPullRequestNotFoundError({ cwd: "/repo", cause: new Error("raw upstream detail that should remain in the cause"), }); @@ -109,7 +111,6 @@ it.effect("adds safe request context while retaining GitHub CLI causes", () => { provider: error.provider, operation: error.operation, - command: error.command, cwd: error.cwd, reference: error.reference, detail: error.detail, @@ -117,7 +118,6 @@ it.effect("adds safe request context while retaining GitHub CLI causes", () => { provider: "github", operation: "getChangeRequest", - command: "gh", cwd: "/repo", reference: "https://github.com/pingdotgg/t3code/pull/42", detail: "Pull request not found. Check the PR number or URL and try again.", @@ -130,8 +130,9 @@ it.effect("adds safe request context while retaining GitHub CLI causes", () => it.effect("lists change request history through the batched head lookup", () => Effect.gen(function* () { - let lookup: Parameters[0] | null = - null; + let lookup: + | Parameters[0] + | null = null; const provider = yield* makeProvider({ listPullRequestsByHead: (input) => { lookup = input; @@ -183,8 +184,9 @@ it.effect("lists change request history through the batched head lookup", () => it.effect("creates GitHub PRs through provider-neutral input names", () => Effect.gen(function* () { - let createInput: Parameters[0] | null = - null; + let createInput: + | Parameters[0] + | null = null; const provider = yield* makeProvider({ createPullRequest: (input) => { createInput = input; diff --git a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts index cf06c6efdfcd015f4345f10c62b5a0b085f590e3..a3b7a22f8d55942cb455601ff817f9c9f1854d61 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts @@ -15,7 +15,7 @@ import { HostProcessEnvironment } from "@t3tools/shared/hostProcess"; import * as ServerSettings from "../serverSettings.ts"; import * as GitHubApi from "./GitHubApi.ts"; -import * as GitHubCli from "./GitHubCli.ts"; +import * as GitHubRepositoryApi from "./GitHubRepositoryApi.ts"; import { effectiveGitHubAccount, findAuthenticatedGitHubAccount, @@ -40,7 +40,7 @@ const decodeLinkSubject = Schema.decodeUnknownEffect( ), ); -function toChangeRequest(summary: GitHubCli.GitHubPullRequestSummary): ChangeRequest { +function toChangeRequest(summary: GitHubRepositoryApi.GitHubPullRequestSummary): ChangeRequest { return { provider: "github", number: summary.number, @@ -264,7 +264,7 @@ export const makeDiscovery = Effect.gen(function* () { }); export const make = Effect.gen(function* () { - const github = yield* GitHubCli.GitHubCli; + const github = yield* GitHubRepositoryApi.GitHubRepositoryApi; const api = yield* GitHubApi.GitHubApi; const listChangeRequests: SourceControlProvider.SourceControlProvider["Service"]["listChangeRequests"] = @@ -286,7 +286,6 @@ export const make = Effect.gen(function* () { new SourceControlProviderError({ provider: "github", operation: "listChangeRequests", - command: error.command, cwd: input.cwd, reference: SourceControlProvider.transportSafeSourceControlErrorValue( input.headSelector, @@ -325,7 +324,6 @@ export const make = Effect.gen(function* () { new SourceControlProviderError({ provider: "github", operation: "listChangeRequests", - command: error.command, cwd: input.cwd, reference: SourceControlProvider.transportSafeSourceControlErrorValue( input.headSelector, @@ -403,7 +401,6 @@ export const make = Effect.gen(function* () { new SourceControlProviderError({ provider: "github", operation: "getChangeRequest", - command: error.command, cwd: input.cwd, reference: SourceControlProvider.transportSafeSourceControlErrorValue( input.reference, @@ -428,7 +425,6 @@ export const make = Effect.gen(function* () { new SourceControlProviderError({ provider: "github", operation: "createChangeRequest", - command: error.command, cwd: input.cwd, reference: SourceControlProvider.transportSafeSourceControlErrorValue( input.headSelector, @@ -445,7 +441,6 @@ export const make = Effect.gen(function* () { new SourceControlProviderError({ provider: "github", operation: "getRepositoryCloneUrls", - command: error.command, cwd: input.cwd, repository: SourceControlProvider.transportSafeSourceControlErrorValue( input.repository, @@ -462,7 +457,6 @@ export const make = Effect.gen(function* () { new SourceControlProviderError({ provider: "github", operation: "createRepository", - command: error.command, cwd: input.cwd, repository: SourceControlProvider.transportSafeSourceControlErrorValue( input.repository, @@ -486,7 +480,6 @@ export const make = Effect.gen(function* () { new SourceControlProviderError({ provider: "github", operation: "getDefaultBranch", - command: error.command, cwd: input.cwd, detail: error.message, cause: error, @@ -500,7 +493,6 @@ export const make = Effect.gen(function* () { new SourceControlProviderError({ provider: "github", operation: "checkoutChangeRequest", - command: error.command, cwd: input.cwd, reference: SourceControlProvider.transportSafeSourceControlErrorValue( input.reference, diff --git a/apps/server/src/sourceControl/SourceControlDiscovery.test.ts b/apps/server/src/sourceControl/SourceControlDiscovery.test.ts index 54de8cc14fde7864ce80a8e67f0b1c72be682a34..d9bbb51d7a61678f0bd583f5366ae4d6f610102a 100644 --- a/apps/server/src/sourceControl/SourceControlDiscovery.test.ts +++ b/apps/server/src/sourceControl/SourceControlDiscovery.test.ts @@ -18,7 +18,7 @@ import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; import * as BitbucketApi from "./BitbucketApi.ts"; import * as GitHubApi from "./GitHubApi.ts"; -import * as GitHubCli from "./GitHubCli.ts"; +import * as GitHubRepositoryApi from "./GitHubRepositoryApi.ts"; import * as GitLabCli from "./GitLabCli.ts"; import * as ForgejoCli from "./ForgejoCli.ts"; import * as ForgejoSourceControlProvider from "./ForgejoSourceControlProvider.ts"; @@ -40,7 +40,7 @@ const layerSourceControlProviderRegistryTest = (input: { Layer.mock(AzureDevOpsCli.AzureDevOpsCli)({}), Layer.mock(BitbucketApi.BitbucketApi)(input.bitbucket), ServerSettings.ServerSettingsService.layerTest(), - Layer.mock(GitHubCli.GitHubCli)({}), + Layer.mock(GitHubRepositoryApi.GitHubRepositoryApi)({}), Layer.mock(GitHubApi.GitHubApi)({}), Layer.mock(GitLabCli.GitLabCli)({}), Layer.mock(ForgejoCli.ForgejoCli)({ listLogins: () => Effect.succeed([]) }), diff --git a/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts b/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts index 08751017b35133f03171ba34e8be2dd391da3f15..c9a7164c8b3107d1922c58d18b7e4d327a76a172 100644 --- a/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts +++ b/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts @@ -15,7 +15,7 @@ import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; import * as BitbucketApi from "./BitbucketApi.ts"; import * as GitHubApi from "./GitHubApi.ts"; -import * as GitHubCli from "./GitHubCli.ts"; +import * as GitHubRepositoryApi from "./GitHubRepositoryApi.ts"; import * as GitLabCli from "./GitLabCli.ts"; import * as ForgejoCli from "./ForgejoCli.ts"; import * as SourceControlProviderRegistry from "./SourceControlProviderRegistry.ts"; @@ -42,7 +42,7 @@ function makeRegistry(input: { readonly url: string; }>; readonly process?: Partial; - readonly github?: Partial; + readonly github?: Partial; readonly githubApi?: Partial; readonly gitlab?: Partial; readonly resolve?: VcsDriverRegistry.VcsDriverRegistry["Service"]["resolve"]; @@ -98,7 +98,7 @@ function makeRegistry(input: { Layer.mock(AzureDevOpsCli.AzureDevOpsCli)({}), Layer.mock(BitbucketApi.BitbucketApi)({}), ServerSettings.ServerSettingsService.layerTest(), - Layer.mock(GitHubCli.GitHubCli)(input.github ?? {}), + Layer.mock(GitHubRepositoryApi.GitHubRepositoryApi)(input.github ?? {}), Layer.mock(GitHubApi.GitHubApi)(input.githubApi ?? {}), Layer.mock(GitLabCli.GitLabCli)(input.gitlab ?? {}), Layer.mock(ForgejoCli.ForgejoCli)({ listLogins: () => Effect.succeed([]) }), diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index af3fc819a4c71538c35a6647555380ea15ad5b81..5f64ecb12f87ecc45d0de631ccce9c5be9c05028 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -229,7 +229,7 @@ import * as SourceControlDiscovery from "./sourceControl/SourceControlDiscovery. import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts"; import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts"; import * as BitbucketApi from "./sourceControl/BitbucketApi.ts"; -import * as GitHubCli from "./sourceControl/GitHubCli.ts"; +import * as GitHubRepositoryApi from "./sourceControl/GitHubRepositoryApi.ts"; import * as GitLabCli from "./sourceControl/GitLabCli.ts"; import * as ForgejoCli from "./sourceControl/ForgejoCli.ts"; import * as SourceControlProviderRegistry from "./sourceControl/SourceControlProviderRegistry.ts"; @@ -3190,7 +3190,7 @@ export const layer = Layer.unwrap( Layer.mergeAll( AzureDevOpsCli.layer, BitbucketApi.layer, - GitHubCli.layer, + GitHubRepositoryApi.layer, GitLabCli.layer, ForgejoCli.layer, ), -- 2.54.0