History

feat(source-control): add Bitbucket & Azure Devops providers (#2473)

Julius Marminge committed 6 months agov0.0.22-nightly.20260504.196

Commit 91a03e01 parent d501ebe59 files · +5834 −2327Browse files

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

Unified · Split

apps/server/src/sourceControl/SourceControlProviderRegistry.ts+104−22
@@ -1,40 +1,46 @@
11 import { Cache, Context, Duration, Effect, Exit, Layer } from "effect";
2−import { SourceControlProviderError } from "@t3tools/contracts";
2+import {
3+ SourceControlProviderError,
4+ type SourceControlProviderDiscoveryItem,
5+} from "@t3tools/contracts";
36 import type { SourceControlProviderKind } from "@t3tools/contracts";
47 import { detectSourceControlProviderFromRemoteUrl } from "@t3tools/shared/sourceControl";
58
6−import {
7− SourceControlProvider,
8− type SourceControlProviderContext,
9− type SourceControlProviderShape,
10−} from "./SourceControlProvider.ts";
9+import * as AzureDevOpsSourceControlProvider from "./AzureDevOpsSourceControlProvider.ts";
10+import * as BitbucketSourceControlProvider from "./BitbucketSourceControlProvider.ts";
1111 import * as GitHubSourceControlProvider from "./GitHubSourceControlProvider.ts";
1212 import * as GitLabSourceControlProvider from "./GitLabSourceControlProvider.ts";
13−import { VcsDriverRegistry } from "../vcs/VcsDriverRegistry.ts";
13+import * as SourceControlProvider from "./SourceControlProvider.ts";
14+import * as SourceControlProviderDiscovery from "./SourceControlProviderDiscovery.ts";
15+import { ServerConfig } from "../config.ts";
16+import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
17+import * as VcsProcess from "../vcs/VcsProcess.ts";
1418
1519 const PROVIDER_DETECTION_CACHE_CAPACITY = 2_048;
1620 const PROVIDER_DETECTION_CACHE_TTL = Duration.seconds(5);
1721
1822 export interface SourceControlProviderRegistration {
1923 readonly kind: SourceControlProviderKind;
20− readonly provider: SourceControlProviderShape;
24+ readonly provider: SourceControlProvider.SourceControlProviderShape;
25+ readonly discovery: SourceControlProviderDiscovery.SourceControlProviderDiscoverySpec;
2126 }
2227
2328 export interface SourceControlProviderHandle {
24− readonly provider: SourceControlProviderShape;
25− readonly context: SourceControlProviderContext | null;
29+ readonly provider: SourceControlProvider.SourceControlProviderShape;
30+ readonly context: SourceControlProvider.SourceControlProviderContext | null;
2631 }
2732
2833 export interface SourceControlProviderRegistryShape {
2934 readonly get: (
3035 kind: SourceControlProviderKind,
31− ) => Effect.Effect<SourceControlProviderShape, SourceControlProviderError>;
36+ ) => Effect.Effect<SourceControlProvider.SourceControlProviderShape, SourceControlProviderError>;
3237 readonly resolveHandle: (input: {
3338 readonly cwd: string;
3439 }) => Effect.Effect<SourceControlProviderHandle, SourceControlProviderError>;
3540 readonly resolve: (input: {
3641 readonly cwd: string;
37− }) => Effect.Effect<SourceControlProviderShape, SourceControlProviderError>;
42+ }) => Effect.Effect<SourceControlProvider.SourceControlProviderShape, SourceControlProviderError>;
43+ readonly discover: Effect.Effect<ReadonlyArray<SourceControlProviderDiscoveryItem>>;
3844 }
3945
4046 export class SourceControlProviderRegistry extends Context.Service<
@@ -42,7 +48,9 @@ export class SourceControlProviderRegistry extends Context.Service< · 1 unmodified line
4248 SourceControlProviderRegistryShape
4349 >()("t3/source-control/SourceControlProviderRegistry") {}
4450
45−function unsupportedProvider(kind: SourceControlProviderKind): SourceControlProviderShape {
51+function unsupportedProvider(
52+ kind: SourceControlProviderKind,
53+): SourceControlProvider.SourceControlProviderShape {
4654 const unsupported = (operation: string) =>
4755 Effect.fail(
4856 new SourceControlProviderError({
@@ -52,7 +60,7 @@ function unsupportedProvider(kind: SourceControlProviderKind): SourceControlProv · 3 unmodified lines
5260 }),
5361 );
5462
55− return SourceControlProvider.of({
63+ return SourceControlProvider.SourceControlProvider.of({
5664 kind,
5765 listChangeRequests: () => unsupported("listChangeRequests"),
5866 getChangeRequest: () => unsupported("getChangeRequest"),
@@ -78,7 +86,7 @@ function selectProviderContext( · 19 unmodified lines
7886 readonly name: string;
7987 readonly url: string;
8088 }>,
81−): SourceControlProviderContext | null {
89+): SourceControlProvider.SourceControlProviderContext | null {
8290 const candidates = remotes
8391 .map((remote) => {
8492 const provider = detectSourceControlProviderFromRemoteUrl(remote.url);
@@ -90,7 +98,7 @@ function selectProviderContext( · 5 unmodified lines
9098 }
9199 : null;
92100 })
93− .filter((value): value is SourceControlProviderContext => value !== null);
101+ .filter((value): value is SourceControlProvider.SourceControlProviderContext => value !== null);
94102
95103 return (
96104 candidates.find((candidate) => candidate.remoteName === "origin") ??
@@ -100,12 +108,60 @@ function selectProviderContext( · 3 unmodified lines
100108 );
101109 }
102110
111+function bindProviderContext(
112+ provider: SourceControlProvider.SourceControlProviderShape,
113+ context: SourceControlProvider.SourceControlProviderContext | null,
114+): SourceControlProvider.SourceControlProviderShape {
115+ if (context === null) {
116+ return provider;
117+ }
118+
119+ return SourceControlProvider.SourceControlProvider.of({
120+ kind: provider.kind,
121+ listChangeRequests: (input) =>
122+ provider.listChangeRequests({
123+ ...input,
124+ context: input.context ?? context,
125+ }),
126+ getChangeRequest: (input) =>
127+ provider.getChangeRequest({
128+ ...input,
129+ context: input.context ?? context,
130+ }),
131+ createChangeRequest: (input) =>
132+ provider.createChangeRequest({
133+ ...input,
134+ context: input.context ?? context,
135+ }),
136+ getRepositoryCloneUrls: (input) =>
137+ provider.getRepositoryCloneUrls({
138+ ...input,
139+ context: input.context ?? context,
140+ }),
141+ createRepository: (input) => provider.createRepository(input),
142+ getDefaultBranch: (input) =>
143+ provider.getDefaultBranch({
144+ ...input,
145+ context: input.context ?? context,
146+ }),
147+ checkoutChangeRequest: (input) =>
148+ provider.checkoutChangeRequest({
149+ ...input,
150+ context: input.context ?? context,
151+ }),
152+ });
153+}
154+
103155 export const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWithProviders")(
104156 function* (registrations: ReadonlyArray<SourceControlProviderRegistration>) {
105− const vcsRegistry = yield* VcsDriverRegistry;
106− const providers = new Map<SourceControlProviderKind, SourceControlProviderShape>(
107− registrations.map((registration) => [registration.kind, registration.provider]),
108− );
157+ const config = yield* ServerConfig;
158+ const process = yield* VcsProcess.VcsProcess;
159+ const vcsRegistry = yield* VcsDriverRegistry.VcsDriverRegistry;
160+ const providers = new Map<
161+ SourceControlProviderKind,
162+ SourceControlProvider.SourceControlProviderShape
163+ >(registrations.map((registration) => [registration.kind, registration.provider]));
164+ const discoverySpecs = registrations.map((registration) => registration.discovery);
109165
110166 const get: SourceControlProviderRegistryShape["get"] = (kind) =>
111167 Effect.succeed(providers.get(kind) ?? unsupportedProvider(kind));
@@ -125,7 +181,7 @@ export const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWit · 13 unmodified lines
125181
126182 const providerContextCache = yield* Cache.makeWith<
127183 string,
128− SourceControlProviderContext | null,
184+ SourceControlProvider.SourceControlProviderContext | null,
129185 SourceControlProviderError
130186 >(detectProviderContext, {
131187 capacity: PROVIDER_DETECTION_CACHE_CAPACITY,
@@ -136,8 +192,9 @@ export const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWit · 4 unmodified lines
136192 Cache.get(providerContextCache, input.cwd).pipe(
137193 Effect.map((context) => {
138194 const kind = context?.provider.kind ?? "unknown";
195+ const provider = providers.get(kind) ?? unsupportedProvider(kind);
139196 return {
140− provider: providers.get(kind) ?? unsupportedProvider(kind),
197+ provider: bindProviderContext(provider, context),
141198 context,
142199 } satisfies SourceControlProviderHandle;
143200 }),
@@ -147,6 +204,16 @@ export const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWit · 3 unmodified lines
147204 get,
148205 resolveHandle,
149206 resolve: (input) => resolveHandle(input).pipe(Effect.map((handle) => handle.provider)),
207+ discover: Effect.all(
208+ discoverySpecs.map((spec) =>
209+ SourceControlProviderDiscovery.probeSourceControlProvider({
210+ spec,
211+ process,
212+ cwd: config.cwd,
213+ }),
214+ ),
215+ { concurrency: "unbounded" },
216+ ),
150217 });
151218 },
152219 );
@@ -154,14 +221,29 @@ export const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWit · 1 unmodified line
154221 export const make = Effect.fn("makeSourceControlProviderRegistry")(function* () {
155222 const github = yield* GitHubSourceControlProvider.make();
156223 const gitlab = yield* GitLabSourceControlProvider.make();
224+ const bitbucket = yield* BitbucketSourceControlProvider.make();
225+ const bitbucketDiscovery = yield* BitbucketSourceControlProvider.makeDiscovery();
226+ const azureDevOps = yield* AzureDevOpsSourceControlProvider.make();
157227 return yield* makeWithProviders([
158228 {
159229 kind: "github",
160230 provider: github,
231+ discovery: GitHubSourceControlProvider.discovery,
161232 },
162233 {
163234 kind: "gitlab",
164235 provider: gitlab,
236+ discovery: GitLabSourceControlProvider.discovery,
237+ },
238+ {
239+ kind: "azure-devops",
240+ provider: azureDevOps,
241+ discovery: AzureDevOpsSourceControlProvider.discovery,
242+ },
243+ {
244+ kind: "bitbucket",
245+ provider: bitbucket,
246+ discovery: bitbucketDiscovery,
165247 },
166248 ]);
167249 });

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