History

Add source control settings discovery

Julius Marminge committed 6 months ago

Commit dc2e2651 parent 128b43214 files · +644 −3Browse files

Showing the changes to apps/server/src/sourceControl/SourceControlDiscovery.ts (1 of 14 files). Show all

Unified · Split

apps/server/src/sourceControl/SourceControlDiscovery.ts+178−0
@@ -0,0 +1,178 @@
1+import {
2+ type SourceControlDiscoveryResult,
3+ type SourceControlProviderDiscoveryItem,
4+ type SourceControlProviderKind,
5+ type VcsDiscoveryItem,
6+ type VcsDriverKind,
7+} from "@t3tools/contracts";
8+import { Context, Effect, Layer, Option } from "effect";
9+
10+import { ServerConfig } from "../config.ts";
11+import * as VcsProcess from "../vcs/VcsProcess.ts";
12+
13+interface DiscoveryProbe {
14+ readonly label: string;
15+ readonly executable: string;
16+ readonly versionArgs: ReadonlyArray<string>;
17+ readonly implemented: boolean;
18+ readonly installHint: string;
19+}
20+
21+type VcsProbe = DiscoveryProbe & {
22+ readonly kind: VcsDriverKind;
23+};
24+
25+type ProviderProbe = DiscoveryProbe & {
26+ readonly kind: SourceControlProviderKind;
27+};
28+
29+const VCS_PROBES: ReadonlyArray<VcsProbe> = [
30+ {
31+ kind: "git",
32+ label: "Git",
33+ executable: "git",
34+ versionArgs: ["--version"],
35+ implemented: true,
36+ installHint: "Install Git from https://git-scm.com/downloads or with your package manager.",
37+ },
38+ {
39+ kind: "jj",
40+ label: "Jujutsu",
41+ executable: "jj",
42+ versionArgs: ["--version"],
43+ implemented: false,
44+ installHint: "Install Jujutsu with `brew install jj` or from https://github.com/jj-vcs/jj.",
45+ },
46+ {
47+ kind: "sapling",
48+ label: "Sapling",
49+ executable: "sl",
50+ versionArgs: ["--version"],
51+ implemented: false,
52+ installHint: "Install Sapling (`sl`) from https://sapling-scm.com/.",
53+ },
54+];
55+
56+const SOURCE_CONTROL_PROVIDER_PROBES: ReadonlyArray<ProviderProbe> = [
57+ {
58+ kind: "github",
59+ label: "GitHub",
60+ executable: "gh",
61+ versionArgs: ["--version"],
62+ implemented: true,
63+ installHint: "Install GitHub CLI with `brew install gh` or from https://cli.github.com/.",
64+ },
65+ {
66+ kind: "gitlab",
67+ label: "GitLab",
68+ executable: "glab",
69+ versionArgs: ["--version"],
70+ implemented: false,
71+ installHint:
72+ "Install GitLab CLI with `brew install glab` or from https://gitlab.com/gitlab-org/cli.",
73+ },
74+ {
75+ kind: "azure-devops",
76+ label: "Azure DevOps",
77+ executable: "az",
78+ versionArgs: ["--version"],
79+ implemented: false,
80+ installHint:
81+ "Install Azure CLI with `brew install azure-cli`, then add Azure DevOps support with `az extension add --name azure-devops`.",
82+ },
83+ {
84+ kind: "bitbucket",
85+ label: "Bitbucket",
86+ executable: "bb",
87+ versionArgs: ["--version"],
88+ implemented: false,
89+ installHint: "Install a Bitbucket CLI (`bb`) and authenticate it for your Bitbucket workspace.",
90+ },
91+];
92+
93+function firstNonEmptyLine(text: string): Option.Option<string> {
94+ const line = text
95+ .split(/\r?\n/)
96+ .map((entry) => entry.trim())
97+ .find((entry) => entry.length > 0);
98+ return line === undefined ? Option.none() : Option.some(line);
99+}
100+
101+function detailFromCause(cause: unknown): Option.Option<string> {
102+ if (cause instanceof Error && cause.message.trim().length > 0) {
103+ return Option.some(cause.message.trim());
104+ }
105+ return Option.none();
106+}
107+
108+export interface SourceControlDiscoveryShape {
109+ readonly discover: Effect.Effect<SourceControlDiscoveryResult>;
110+}
111+
112+export class SourceControlDiscovery extends Context.Service<
113+ SourceControlDiscovery,
114+ SourceControlDiscoveryShape
115+>()("t3/source-control/SourceControlDiscovery") {}
116+
117+export const layer = Layer.effect(
118+ SourceControlDiscovery,
119+ Effect.gen(function* () {
120+ const config = yield* ServerConfig;
121+ const process = yield* VcsProcess.VcsProcess;
122+
123+ const probe = <Kind extends VcsDriverKind | SourceControlProviderKind>(
124+ input: DiscoveryProbe & { readonly kind: Kind },
125+ ) =>
126+ process
127+ .run({
128+ operation: "source-control.discovery.probe",
129+ command: input.executable,
130+ args: input.versionArgs,
131+ cwd: config.cwd,
132+ timeoutMs: 5_000,
133+ maxOutputBytes: 8_000,
134+ truncateOutputAtMaxBytes: true,
135+ })
136+ .pipe(
137+ Effect.map((result) => ({
138+ kind: input.kind,
139+ label: input.label,
140+ executable: input.executable,
141+ implemented: input.implemented,
142+ status: "available" as const,
143+ version: Option.orElse(firstNonEmptyLine(result.stdout), () =>
144+ firstNonEmptyLine(result.stderr),
145+ ),
146+ installHint: input.installHint,
147+ detail: Option.none<string>(),
148+ })),
149+ Effect.catch((cause) =>
150+ Effect.succeed({
151+ kind: input.kind,
152+ label: input.label,
153+ executable: input.executable,
154+ implemented: input.implemented,
155+ status: "missing" as const,
156+ version: Option.none<string>(),
157+ installHint: input.installHint,
158+ detail: detailFromCause(cause),
159+ }),
160+ ),
161+ );
162+
163+ return SourceControlDiscovery.of({
164+ discover: Effect.all({
165+ versionControlSystems: Effect.all(
166+ VCS_PROBES.map((entry) => probe(entry)) as ReadonlyArray<Effect.Effect<VcsDiscoveryItem>>,
167+ { concurrency: "unbounded" },
168+ ),
169+ sourceControlProviders: Effect.all(
170+ SOURCE_CONTROL_PROVIDER_PROBES.map((entry) => probe(entry)) as ReadonlyArray<
171+ Effect.Effect<SourceControlProviderDiscoveryItem>
172+ >,
173+ { concurrency: "unbounded" },
174+ ),
175+ }),
176+ });
177+ }),
178+);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t