Showing the changes to apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts (1 of 59 files). Show all
Unified · Split
| @@ -0,0 +1,144 @@ | |||
| 1 | import { Effect, Layer } from "effect"; | ||
| 2 | import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; | ||
| 3 | |||
| 4 | import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; | ||
| 5 | import * as SourceControlProvider from "./SourceControlProvider.ts"; | ||
| 6 | import * as SourceControlProviderDiscovery from "./SourceControlProviderDiscovery.ts"; | ||
| 7 | |||
| 8 | function providerError( | ||
| 9 | operation: string, | ||
| 10 | cause: AzureDevOpsCli.AzureDevOpsCliError, | ||
| 11 | ): SourceControlProviderError { | ||
| 12 | return new SourceControlProviderError({ | ||
| 13 | provider: "azure-devops", | ||
| 14 | operation, | ||
| 15 | detail: cause.detail, | ||
| 16 | cause, | ||
| 17 | }); | ||
| 18 | } | ||
| 19 | |||
| 20 | function parseAzureAuth(input: SourceControlProviderDiscovery.SourceControlAuthProbeInput) { | ||
| 21 | const account = input.stdout.trim().split(/\r?\n/)[0]?.trim(); | ||
| 22 | |||
| 23 | if (input.exitCode !== 0) { | ||
| 24 | return SourceControlProviderDiscovery.providerAuth({ | ||
| 25 | status: "unauthenticated", | ||
| 26 | detail: | ||
| 27 | SourceControlProviderDiscovery.firstSafeAuthLine( | ||
| 28 | SourceControlProviderDiscovery.combinedAuthOutput(input), | ||
| 29 | ) ?? "Run `az login` to authenticate Azure CLI.", | ||
| 30 | }); | ||
| 31 | } | ||
| 32 | |||
| 33 | if (account && account.length > 0) { | ||
| 34 | return SourceControlProviderDiscovery.providerAuth({ | ||
| 35 | status: "authenticated", | ||
| 36 | account, | ||
| 37 | host: "dev.azure.com", | ||
| 38 | }); | ||
| 39 | } | ||
| 40 | |||
| 41 | return SourceControlProviderDiscovery.providerAuth({ | ||
| 42 | status: "unknown", | ||
| 43 | host: "dev.azure.com", | ||
| 44 | detail: "Azure CLI account status could not be parsed.", | ||
| 45 | }); | ||
| 46 | } | ||
| 47 | |||
| 48 | export const discovery = { | ||
| 49 | type: "cli", | ||
| 50 | kind: "azure-devops", | ||
| 51 | label: "Azure DevOps", | ||
| 52 | executable: "az", | ||
| 53 | versionArgs: ["--version"], | ||
| 54 | authArgs: ["account", "show", "--query", "user.name", "-o", "tsv"], | ||
| 55 | parseAuth: parseAzureAuth, | ||
| 56 | installHint: | ||
| 57 | "Install the Azure command-line tools (`az`), then enable Azure DevOps support with `az extension add --name azure-devops`.", | ||
| 58 | } satisfies SourceControlProviderDiscovery.SourceControlCliDiscoverySpec; | ||
| 59 | |||
| 60 | function toChangeRequest(summary: { | ||
| 61 | readonly number: number; | ||
| 62 | readonly title: string; | ||
| 63 | readonly url: string; | ||
| 64 | readonly baseRefName: string; | ||
| 65 | readonly headRefName: string; | ||
| 66 | readonly state: "open" | "closed" | "merged"; | ||
| 67 | readonly updatedAt: ChangeRequest["updatedAt"]; | ||
| 68 | }): ChangeRequest { | ||
| 69 | return { | ||
| 70 | provider: "azure-devops", | ||
| 71 | number: summary.number, | ||
| 72 | title: summary.title, | ||
| 73 | url: summary.url, | ||
| 74 | baseRefName: summary.baseRefName, | ||
| 75 | headRefName: summary.headRefName, | ||
| 76 | state: summary.state, | ||
| 77 | updatedAt: summary.updatedAt, | ||
| 78 | isCrossRepository: false, | ||
| 79 | }; | ||
| 80 | } | ||
| 81 | |||
| 82 | export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* () { | ||
| 83 | const azure = yield* AzureDevOpsCli.AzureDevOpsCli; | ||
| 84 | |||
| 85 | return SourceControlProvider.SourceControlProvider.of({ | ||
| 86 | kind: "azure-devops", | ||
| 87 | listChangeRequests: (input) => { | ||
| 88 | const source = SourceControlProvider.sourceControlRefFromInput(input); | ||
| 89 | return azure | ||
| 90 | .listPullRequests({ | ||
| 91 | cwd: input.cwd, | ||
| 92 | headSelector: input.headSelector, | ||
| 93 | ...(source ? { source } : {}), | ||
| 94 | state: input.state, | ||
| 95 | ...(input.limit !== undefined ? { limit: input.limit } : {}), | ||
| 96 | }) | ||
| 97 | .pipe( | ||
| 98 | Effect.map((items) => items.map(toChangeRequest)), | ||
| 99 | Effect.mapError((error) => providerError("listChangeRequests", error)), | ||
| 100 | ); | ||
| 101 | }, | ||
| 102 | getChangeRequest: (input) => | ||
| 103 | azure.getPullRequest(input).pipe( | ||
| 104 | Effect.map(toChangeRequest), | ||
| 105 | Effect.mapError((error) => providerError("getChangeRequest", error)), | ||
| 106 | ), | ||
| 107 | createChangeRequest: (input) => { | ||
| 108 | const source = SourceControlProvider.sourceControlRefFromInput(input); | ||
| 109 | return azure | ||
| 110 | .createPullRequest({ | ||
| 111 | cwd: input.cwd, | ||
| 112 | baseBranch: input.baseRefName, | ||
| 113 | headSelector: input.headSelector, | ||
| 114 | ...(source ? { source } : {}), | ||
| 115 | ...(input.target ? { target: input.target } : {}), | ||
| 116 | title: input.title, | ||
| 117 | bodyFile: input.bodyFile, | ||
| 118 | }) | ||
| 119 | .pipe(Effect.mapError((error) => providerError("createChangeRequest", error))); | ||
| 120 | }, | ||
| 121 | getRepositoryCloneUrls: (input) => | ||
| 122 | azure | ||
| 123 | .getRepositoryCloneUrls(input) | ||
| 124 | .pipe(Effect.mapError((error) => providerError("getRepositoryCloneUrls", error))), | ||
| 125 | createRepository: (input) => | ||
| 126 | azure | ||
| 127 | .createRepository(input) | ||
| 128 | .pipe(Effect.mapError((error) => providerError("createRepository", error))), | ||
| 129 | getDefaultBranch: (input) => | ||
| 130 | azure | ||
| 131 | .getDefaultBranch({ cwd: input.cwd }) | ||
| 132 | .pipe(Effect.mapError((error) => providerError("getDefaultBranch", error))), | ||
| 133 | checkoutChangeRequest: (input) => | ||
| 134 | azure | ||
| 135 | .checkoutPullRequest({ | ||
| 136 | cwd: input.cwd, | ||
| 137 | reference: input.reference, | ||
| 138 | ...(input.context ? { remoteName: input.context.remoteName } : {}), | ||
| 139 | }) | ||
| 140 | .pipe(Effect.mapError((error) => providerError("checkoutChangeRequest", error))), | ||
| 141 | }); | ||
| 142 | }); | ||
| 143 | |||
| 144 | export const layer = Layer.effect(SourceControlProvider.SourceControlProvider, make()); | ||