145 lines · 5.0 KB · TypeScript
RawDownload
import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; import * as AzureDevOpsCli from "./AzureDevOpsCli.ts";import * as SourceControlProvider from "./SourceControlProvider.ts";import * as SourceControlProviderDiscovery from "./SourceControlProviderDiscovery.ts"; function providerError(  operation: string,  cause: AzureDevOpsCli.AzureDevOpsCliError,): SourceControlProviderError {  return new SourceControlProviderError({    provider: "azure-devops",    operation,    detail: cause.detail,    cause,  });} function parseAzureAuth(input: SourceControlProviderDiscovery.SourceControlAuthProbeInput) {  const account = input.stdout.trim().split(/\r?\n/)[0]?.trim();   if (input.exitCode !== 0) {    return SourceControlProviderDiscovery.providerAuth({      status: "unauthenticated",      detail:        SourceControlProviderDiscovery.firstSafeAuthLine(          SourceControlProviderDiscovery.combinedAuthOutput(input),        ) ?? "Run `az login` to authenticate Azure CLI.",    });  }   if (account !== undefined && account.length > 0) {    return SourceControlProviderDiscovery.providerAuth({      status: "authenticated",      account,      host: "dev.azure.com",    });  }   return SourceControlProviderDiscovery.providerAuth({    status: "unknown",    host: "dev.azure.com",    detail: "Azure CLI account status could not be parsed.",  });} export const discovery = {  type: "cli",  kind: "azure-devops",  label: "Azure DevOps",  executable: "az",  versionArgs: ["--version"],  authArgs: ["account", "show", "--query", "user.name", "-o", "tsv"],  parseAuth: parseAzureAuth,  installHint:    "Install the Azure command-line tools (`az`), then enable Azure DevOps support with `az extension add --name azure-devops`.",} satisfies SourceControlProviderDiscovery.SourceControlCliDiscoverySpec; function toChangeRequest(summary: {  readonly number: number;  readonly title: string;  readonly url: string;  readonly baseRefName: string;  readonly headRefName: string;  readonly state: "open" | "closed" | "merged";  readonly updatedAt: ChangeRequest["updatedAt"];}): ChangeRequest {  return {    provider: "azure-devops",    number: summary.number,    title: summary.title,    url: summary.url,    baseRefName: summary.baseRefName,    headRefName: summary.headRefName,    state: summary.state,    updatedAt: summary.updatedAt,    isCrossRepository: false,  };} export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* () {  const azure = yield* AzureDevOpsCli.AzureDevOpsCli;   return SourceControlProvider.SourceControlProvider.of({    kind: "azure-devops",    listChangeRequests: (input) => {      const source = SourceControlProvider.sourceControlRefFromInput(input);      return azure        .listPullRequests({          cwd: input.cwd,          headSelector: input.headSelector,          ...(source !== undefined ? { source } : {}),          state: input.state,          ...(input.limit !== undefined ? { limit: input.limit } : {}),        })        .pipe(          Effect.map((items) => items.map(toChangeRequest)),          Effect.mapError((error) => providerError("listChangeRequests", error)),        );    },    getChangeRequest: (input) =>      azure.getPullRequest(input).pipe(        Effect.map(toChangeRequest),        Effect.mapError((error) => providerError("getChangeRequest", error)),      ),    createChangeRequest: (input) => {      const source = SourceControlProvider.sourceControlRefFromInput(input);      return azure        .createPullRequest({          cwd: input.cwd,          baseBranch: input.baseRefName,          headSelector: input.headSelector,          ...(source !== undefined ? { source } : {}),          ...(input.target !== undefined ? { target: input.target } : {}),          title: input.title,          bodyFile: input.bodyFile,        })        .pipe(Effect.mapError((error) => providerError("createChangeRequest", error)));    },    getRepositoryCloneUrls: (input) =>      azure        .getRepositoryCloneUrls(input)        .pipe(Effect.mapError((error) => providerError("getRepositoryCloneUrls", error))),    createRepository: (input) =>      azure        .createRepository(input)        .pipe(Effect.mapError((error) => providerError("createRepository", error))),    getDefaultBranch: (input) =>      azure        .getDefaultBranch({ cwd: input.cwd })        .pipe(Effect.mapError((error) => providerError("getDefaultBranch", error))),    checkoutChangeRequest: (input) =>      azure        .checkoutPullRequest({          cwd: input.cwd,          reference: input.reference,          ...(input.context !== undefined ? { remoteName: input.context.remoteName } : {}),        })        .pipe(Effect.mapError((error) => providerError("checkoutChangeRequest", error))),  });}); export const layer = Layer.effect(SourceControlProvider.SourceControlProvider, make());