import { type SourceControlProviderAuth, type SourceControlDiscoveryResult, type SourceControlProviderDiscoveryItem, type SourceControlProviderKind, type VcsDiscoveryItem, type VcsDriverKind, } from "@t3tools/contracts"; import { Context, Effect, Layer, Option } from "effect"; import { ServerConfig } from "../config.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; interface DiscoveryProbe { readonly label: string; readonly executable?: string; readonly versionArgs?: ReadonlyArray; readonly implemented: boolean; readonly installHint: string; } type VcsProbe = DiscoveryProbe & { readonly kind: VcsDriverKind; readonly executable: string; readonly versionArgs: ReadonlyArray; }; type ProviderProbe = DiscoveryProbe & { readonly kind: SourceControlProviderKind; readonly authArgs?: ReadonlyArray; readonly parseAuth?: (input: AuthProbeInput) => SourceControlProviderAuth; }; interface AuthProbeInput { readonly stdout: string; readonly stderr: string; readonly exitCode: VcsProcess.VcsProcessOutput["exitCode"]; } interface DiscoveryProbeResult { readonly kind: Kind; readonly label: string; readonly executable?: string; readonly implemented: boolean; readonly status: "available" | "missing"; readonly version: Option.Option; readonly installHint: string; readonly detail: Option.Option; } const VCS_PROBES: ReadonlyArray = [ { kind: "git", label: "Git", executable: "git", versionArgs: ["--version"], implemented: true, installHint: "Install Git from https://git-scm.com/downloads or with your package manager.", }, { kind: "jj", label: "Jujutsu", executable: "jj", versionArgs: ["--version"], implemented: false, installHint: "Install Jujutsu with `brew install jj` or from https://github.com/jj-vcs/jj.", }, ]; const SOURCE_CONTROL_PROVIDER_PROBES: ReadonlyArray = [ { kind: "github", label: "GitHub", executable: "gh", versionArgs: ["--version"], authArgs: ["auth", "status"], parseAuth: parseGitHubAuth, implemented: true, installHint: "Install GitHub CLI with `brew install gh` or from https://cli.github.com/.", }, { kind: "gitlab", label: "GitLab", executable: "glab", versionArgs: ["--version"], authArgs: ["auth", "status"], parseAuth: parseGitLabAuth, implemented: true, installHint: "Install GitLab CLI with `brew install glab` or from https://gitlab.com/gitlab-org/cli.", }, { kind: "azure-devops", label: "Azure DevOps", executable: "az", versionArgs: ["--version"], authArgs: ["account", "show", "--query", "user.name", "-o", "tsv"], parseAuth: parseAzureAuth, implemented: false, installHint: "Install Azure CLI with `brew install azure-cli`, then add Azure DevOps support with `az extension add --name azure-devops`.", }, { kind: "bitbucket", label: "Bitbucket", implemented: false, installHint: "Bitbucket provider support is not available yet.", }, ]; function firstNonEmptyLine(text: string): Option.Option { const line = text .split(/\r?\n/) .map((entry) => entry.trim()) .find((entry) => entry.length > 0); return line === undefined ? Option.none() : Option.some(line); } function detailFromCause(cause: unknown): Option.Option { if (cause instanceof Error && cause.message.trim().length > 0) { return Option.some(cause.message.trim()); } return Option.none(); } function authAccount(account: string | undefined): Option.Option { const trimmed = account?.trim(); return trimmed === undefined || trimmed.length === 0 ? Option.none() : Option.some(trimmed); } function authHost(host: string | undefined): Option.Option { const trimmed = host?.trim(); return trimmed === undefined || trimmed.length === 0 ? Option.none() : Option.some(trimmed); } function authDetail(detail: string | undefined): Option.Option { const trimmed = detail?.trim(); return trimmed === undefined || trimmed.length === 0 ? Option.none() : Option.some(trimmed); } function providerAuth(input: { readonly status: SourceControlProviderAuth["status"]; readonly account?: string | undefined; readonly host?: string | undefined; readonly detail?: string | undefined; }): SourceControlProviderAuth { return { status: input.status, account: authAccount(input.account), host: authHost(input.host), detail: authDetail(input.detail), }; } function unknownAuth(detail?: string): SourceControlProviderAuth { return providerAuth({ status: "unknown", detail }); } function combinedAuthOutput(input: AuthProbeInput): string { return [input.stdout, input.stderr].filter((entry) => entry.trim().length > 0).join("\n"); } function sanitizedAuthLines(text: string): ReadonlyArray { return text .split(/\r?\n/) .map((entry) => entry.trim()) .filter((entry) => entry.length > 0) .filter((entry) => !/^[-\s]*token(?:\s+scopes?)?:/iu.test(entry)); } function firstSafeAuthLine(text: string): string | undefined { return sanitizedAuthLines(text)[0]; } function parseCliHost(text: string): string | undefined { return sanitizedAuthLines(text) .map((line) => line.replace(/^[^a-z0-9]+/iu, "")) .find((line) => /^[a-z0-9][a-z0-9.-]*(?::\d+)?$/iu.test(line)); } function matchFirst(text: string, patterns: ReadonlyArray): string | undefined { for (const pattern of patterns) { const match = pattern.exec(text); const value = match?.[1]?.trim(); if (value && value.length > 0) return value; } return undefined; } function parseGitHubAuth(input: AuthProbeInput): SourceControlProviderAuth { const output = combinedAuthOutput(input); const account = matchFirst(output, [ /Logged in to .* account\s+([^\s(]+)/iu, /Logged in to .* as\s+([^\s(]+)/iu, ]); const host = parseCliHost(output); if (input.exitCode !== 0) { return providerAuth({ status: "unauthenticated", host, detail: firstSafeAuthLine(output) ?? "Run `gh auth login` to authenticate GitHub CLI.", }); } if (account) { return providerAuth({ status: "authenticated", account, host }); } return providerAuth({ status: "unknown", host, detail: firstSafeAuthLine(output) ?? "GitHub CLI auth status could not be parsed.", }); } function parseGitLabAuth(input: AuthProbeInput): SourceControlProviderAuth { const output = combinedAuthOutput(input); const account = matchFirst(output, [ /Logged in to .* as\s+([^\s(]+)/iu, /Logged in to .* account\s+([^\s(]+)/iu, /account:\s*([^\s(]+)/iu, ]); const host = parseCliHost(output); if (input.exitCode !== 0) { return providerAuth({ status: "unauthenticated", host, detail: firstSafeAuthLine(output) ?? "Run `glab auth login` to authenticate GitLab CLI.", }); } if (account) { return providerAuth({ status: "authenticated", account, host }); } return providerAuth({ status: "unknown", host, detail: firstSafeAuthLine(output) ?? "GitLab CLI auth status could not be parsed.", }); } function parseAzureAuth(input: AuthProbeInput): SourceControlProviderAuth { 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 && 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 interface SourceControlDiscoveryShape { readonly discover: Effect.Effect; } export class SourceControlDiscovery extends Context.Service< SourceControlDiscovery, SourceControlDiscoveryShape >()("t3/source-control/SourceControlDiscovery") {} export const layer = Layer.effect( SourceControlDiscovery, Effect.gen(function* () { const config = yield* ServerConfig; const process = yield* VcsProcess.VcsProcess; const probe = ( input: DiscoveryProbe & { readonly kind: Kind }, ): Effect.Effect> => { const executable = input.executable; const versionArgs = input.versionArgs; if (!executable || !versionArgs) { return Effect.succeed({ kind: input.kind, label: input.label, implemented: input.implemented, status: "missing" as const, version: Option.none(), installHint: input.installHint, detail: Option.some(input.installHint), } satisfies DiscoveryProbeResult); } return process .run({ operation: "source-control.discovery.probe", command: executable, args: versionArgs, cwd: config.cwd, timeoutMs: 5_000, maxOutputBytes: 8_000, truncateOutputAtMaxBytes: true, }) .pipe( Effect.map( (result) => ({ kind: input.kind, label: input.label, executable, implemented: input.implemented, status: "available" as const, version: Option.orElse(firstNonEmptyLine(result.stdout), () => firstNonEmptyLine(result.stderr), ), installHint: input.installHint, detail: Option.none(), }) satisfies DiscoveryProbeResult, ), Effect.catch((cause) => Effect.succeed({ kind: input.kind, label: input.label, executable, implemented: input.implemented, status: "missing" as const, version: Option.none(), installHint: input.installHint, detail: detailFromCause(cause), } satisfies DiscoveryProbeResult), ), ); }; const probeProvider = (input: ProviderProbe) => probe(input).pipe( Effect.flatMap((item) => { const executable = input.executable; const authArgs = input.authArgs; const parseAuth = input.parseAuth; if (!executable || !authArgs || !parseAuth) { return Effect.succeed({ ...item, auth: unknownAuth(input.installHint), } satisfies SourceControlProviderDiscoveryItem); } if (item.status !== "available") { return Effect.succeed({ ...item, auth: unknownAuth("CLI is not installed."), } satisfies SourceControlProviderDiscoveryItem); } return process .run({ operation: "source-control.discovery.auth", command: executable, args: authArgs, cwd: config.cwd, allowNonZeroExit: true, timeoutMs: 5_000, maxOutputBytes: 8_000, truncateOutputAtMaxBytes: true, }) .pipe( Effect.map( (result) => ({ ...item, auth: parseAuth(result), }) satisfies SourceControlProviderDiscoveryItem, ), Effect.catch((cause) => Effect.succeed({ ...item, auth: unknownAuth(Option.getOrUndefined(detailFromCause(cause))), } satisfies SourceControlProviderDiscoveryItem), ), ); }), ); return SourceControlDiscovery.of({ discover: Effect.all({ versionControlSystems: Effect.all( VCS_PROBES.map((entry) => probe(entry)) as ReadonlyArray>, { concurrency: "unbounded" }, ), sourceControlProviders: Effect.all( SOURCE_CONTROL_PROVIDER_PROBES.map((entry) => probeProvider(entry)) as ReadonlyArray< Effect.Effect >, { concurrency: "unbounded" }, ), }), }); }), );