236 lines · 7.6 KB · TypeScript
RawDownload
import * as Effect from "effect/Effect";import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; import * as AzureDevOpsCli from "./AzureDevOpsCli.ts";import * as SourceControlProvider from "./SourceControlProvider.ts";import {  combinedAuthOutput,  firstSafeAuthLine,  providerAuth,  type SourceControlAuthProbeInput,  type SourceControlCliDiscoverySpec,} from "./SourceControlProviderDiscovery.ts"; function parseAzureAuth(input: SourceControlAuthProbeInput) {  const account = input.stdout.trim().split(/\r?\n/)[0]?.trim();   if (input.exitCode !== 0) {    return providerAuth({      status: "unauthenticated",      detail:        firstSafeAuthLine(combinedAuthOutput(input)) ?? "Run `az login` to authenticate Azure CLI.",    });  }   if (account !== undefined && account.length > 0) {    return providerAuth({      status: "authenticated",      account,      host: "dev.azure.com",    });  }   return 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"],  // `az` boots a fresh Python interpreter on every invocation, so even `az --version`  // takes ~6s on Windows and overruns the default budget, leaving the provider reported  // as missing on machines where it is installed. `gh` and `glab` answer in ~0.3s.  probeTimeoutMs: 20_000,  parseAuth: parseAzureAuth,  installHint:    "Install the Azure command-line tools (`az`), then enable Azure DevOps support with `az extension add --name azure-devops`.",} satisfies 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 isDraft?: boolean;  readonly closedAt?: string | null;  readonly mergedAt?: string | null;  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,    ...(summary.isDraft === true ? { isDraft: true } : {}),    closedAt: summary.closedAt ?? null,    mergedAt: summary.mergedAt ?? null,    updatedAt: summary.updatedAt,    isCrossRepository: false,  };} export const make = Effect.gen(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) =>              new SourceControlProviderError({                provider: "azure-devops",                operation: "listChangeRequests",                command: error.command,                cwd: input.cwd,                reference: SourceControlProvider.transportSafeSourceControlErrorValue(                  input.headSelector,                ),                detail: error.detail,                cause: error,              }),          ),        );    },    getChangeRequest: (input) =>      azure.getPullRequest(input).pipe(        Effect.map(toChangeRequest),        Effect.mapError(          (error) =>            new SourceControlProviderError({              provider: "azure-devops",              operation: "getChangeRequest",              command: error.command,              cwd: input.cwd,              reference: SourceControlProvider.transportSafeSourceControlErrorValue(                input.reference,              ),              detail: error.detail,              cause: 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) =>              new SourceControlProviderError({                provider: "azure-devops",                operation: "createChangeRequest",                command: error.command,                cwd: input.cwd,                reference: SourceControlProvider.transportSafeSourceControlErrorValue(                  input.headSelector,                ),                detail: error.detail,                cause: error,              }),          ),        );    },    getRepositoryCloneUrls: (input) =>      azure.getRepositoryCloneUrls(input).pipe(        Effect.mapError(          (error) =>            new SourceControlProviderError({              provider: "azure-devops",              operation: "getRepositoryCloneUrls",              command: error.command,              cwd: input.cwd,              repository: SourceControlProvider.transportSafeSourceControlErrorValue(                input.repository,              ),              detail: error.detail,              cause: error,            }),        ),      ),    createRepository: (input) =>      azure.createRepository(input).pipe(        Effect.mapError(          (error) =>            new SourceControlProviderError({              provider: "azure-devops",              operation: "createRepository",              command: error.command,              cwd: input.cwd,              repository: SourceControlProvider.transportSafeSourceControlErrorValue(                input.repository,              ),              detail: error.detail,              cause: error,            }),        ),      ),    getDefaultBranch: (input) =>      azure.getDefaultBranch({ cwd: input.cwd }).pipe(        Effect.mapError(          (error) =>            new SourceControlProviderError({              provider: "azure-devops",              operation: "getDefaultBranch",              command: error.command,              cwd: input.cwd,              detail: error.detail,              cause: error,            }),        ),      ),    checkoutChangeRequest: (input) =>      azure        .checkoutPullRequest({          cwd: input.cwd,          reference: input.reference,          ...(input.context !== undefined ? { remoteName: input.context.remoteName } : {}),        })        .pipe(          Effect.mapError(            (error) =>              new SourceControlProviderError({                provider: "azure-devops",                operation: "checkoutChangeRequest",                command: error.command,                cwd: input.cwd,                reference: SourceControlProvider.transportSafeSourceControlErrorValue(                  input.reference,                ),                detail: error.detail,                cause: error,              }),          ),        ),  });});