History

Add remote repository publish and discovery support (#2482)

Julius Marminge committed 6 months ago

Commit d7969261 parent f7748a033 files · +2502 −157Browse files

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

Unified · Split

apps/server/src/sourceControl/SourceControlDiscovery.ts+44−50
@@ -13,20 +13,22 @@ import * as VcsProcess from "../vcs/VcsProcess.ts"; · 12 unmodified lines
13 13
14interface DiscoveryProbe {14interface DiscoveryProbe {
15 readonly label: string;15 readonly label: string;
16 readonly executable: string;16 readonly executable?: string;
17 readonly versionArgs: ReadonlyArray<string>;17 readonly versionArgs?: ReadonlyArray<string>;
18 readonly implemented: boolean;18 readonly implemented: boolean;
19 readonly installHint: string;19 readonly installHint: string;
20}20}
21 21
22type VcsProbe = DiscoveryProbe & {22type VcsProbe = DiscoveryProbe & {
23 readonly kind: VcsDriverKind;23 readonly kind: VcsDriverKind;
24 readonly executable: string;
25 readonly versionArgs: ReadonlyArray<string>;
24};26};
25 27
26type ProviderProbe = DiscoveryProbe & {28type ProviderProbe = DiscoveryProbe & {
27 readonly kind: SourceControlProviderKind;29 readonly kind: SourceControlProviderKind;
28 readonly authArgs: ReadonlyArray<string>;30 readonly authArgs?: ReadonlyArray<string>;
29 readonly parseAuth: (input: AuthProbeInput) => SourceControlProviderAuth;31 readonly parseAuth?: (input: AuthProbeInput) => SourceControlProviderAuth;
30};32};
31 33
32interface AuthProbeInput {34interface AuthProbeInput {
@@ -38,7 +40,7 @@ interface AuthProbeInput { · 5 unmodified lines
38interface DiscoveryProbeResult<Kind extends string> {40interface DiscoveryProbeResult<Kind extends string> {
39 readonly kind: Kind;41 readonly kind: Kind;
40 readonly label: string;42 readonly label: string;
41 readonly executable: string;43 readonly executable?: string;
42 readonly implemented: boolean;44 readonly implemented: boolean;
43 readonly status: "available" | "missing";45 readonly status: "available" | "missing";
44 readonly version: Option.Option<string>;46 readonly version: Option.Option<string>;
@@ -101,12 +103,8 @@ const SOURCE_CONTROL_PROVIDER_PROBES: ReadonlyArray<ProviderProbe> = [ · 56 unmodified lines
101 {103 {
102 kind: "bitbucket",104 kind: "bitbucket",
103 label: "Bitbucket",105 label: "Bitbucket",
104 executable: "bb",
105 versionArgs: ["--version"],
106 authArgs: ["auth", "status"],
107 parseAuth: parseBitbucketAuth,
108 implemented: false,106 implemented: false,
109 installHint: "Install a Bitbucket CLI (`bb`) and authenticate it for your Bitbucket workspace.",107 installHint: "Bitbucket provider support is not available yet.",
110 },108 },
111];109];
112 110
@@ -266,37 +264,6 @@ function parseAzureAuth(input: AuthProbeInput): SourceControlProviderAuth { · 153 unmodified lines
266 });264 });
267}265}
268 266
269function parseBitbucketAuth(input: AuthProbeInput): SourceControlProviderAuth {
270 const output = combinedAuthOutput(input);
271 const account = matchFirst(output, [
272 /Logged in to .* as\s+([^\s(]+)/iu,
273 /Logged in as\s+([^\s(]+)/iu,
274 /account:\s*([^\s(]+)/iu,
275 /user:\s*([^\s(]+)/iu,
276 /username:\s*([^\s(]+)/iu,
277 ]);
278 const host = parseCliHost(output) ?? "bitbucket.org";
279
280 if (input.exitCode !== 0) {
281 return providerAuth({
282 status: "unauthenticated",
283 host,
284 detail:
285 firstSafeAuthLine(output) ?? "Authenticate the Bitbucket CLI before enabling Bitbucket.",
286 });
287 }
288
289 if (account) {
290 return providerAuth({ status: "authenticated", account, host });
291 }
292
293 return providerAuth({
294 status: "unknown",
295 host,
296 detail: firstSafeAuthLine(output) ?? "Bitbucket CLI auth status could not be parsed.",
297 });
298}
299
300export interface SourceControlDiscoveryShape {267export interface SourceControlDiscoveryShape {
301 readonly discover: Effect.Effect<SourceControlDiscoveryResult>;268 readonly discover: Effect.Effect<SourceControlDiscoveryResult>;
302}269}
@@ -314,12 +281,27 @@ export const layer = Layer.effect( · 11 unmodified lines
314 281
315 const probe = <Kind extends VcsDriverKind | SourceControlProviderKind>(282 const probe = <Kind extends VcsDriverKind | SourceControlProviderKind>(
316 input: DiscoveryProbe & { readonly kind: Kind },283 input: DiscoveryProbe & { readonly kind: Kind },
317 ): Effect.Effect<DiscoveryProbeResult<Kind>> =>284 ): Effect.Effect<DiscoveryProbeResult<Kind>> => {
318 process285 const executable = input.executable;
286 const versionArgs = input.versionArgs;
287
288 if (!executable || !versionArgs) {
289 return Effect.succeed({
290 kind: input.kind,
291 label: input.label,
292 implemented: input.implemented,
293 status: "missing" as const,
294 version: Option.none<string>(),
295 installHint: input.installHint,
296 detail: Option.some(input.installHint),
297 } satisfies DiscoveryProbeResult<Kind>);
298 }
299
300 return process
319 .run({301 .run({
320 operation: "source-control.discovery.probe",302 operation: "source-control.discovery.probe",
321 command: input.executable,303 command: executable,
322 args: input.versionArgs,304 args: versionArgs,
323 cwd: config.cwd,305 cwd: config.cwd,
324 timeoutMs: 5_000,306 timeoutMs: 5_000,
325 maxOutputBytes: 8_000,307 maxOutputBytes: 8_000,
@@ -331,7 +313,7 @@ export const layer = Layer.effect( · 5 unmodified lines
331 ({313 ({
332 kind: input.kind,314 kind: input.kind,
333 label: input.label,315 label: input.label,
334 executable: input.executable,316 executable,
335 implemented: input.implemented,317 implemented: input.implemented,
336 status: "available" as const,318 status: "available" as const,
337 version: Option.orElse(firstNonEmptyLine(result.stdout), () =>319 version: Option.orElse(firstNonEmptyLine(result.stdout), () =>
@@ -345,7 +327,7 @@ export const layer = Layer.effect( · 7 unmodified lines
345 Effect.succeed({327 Effect.succeed({
346 kind: input.kind,328 kind: input.kind,
347 label: input.label,329 label: input.label,
348 executable: input.executable,330 executable,
349 implemented: input.implemented,331 implemented: input.implemented,
350 status: "missing" as const,332 status: "missing" as const,
351 version: Option.none<string>(),333 version: Option.none<string>(),
@@ -354,10 +336,22 @@ export const layer = Layer.effect( · 2 unmodified lines
354 } satisfies DiscoveryProbeResult<Kind>),336 } satisfies DiscoveryProbeResult<Kind>),
355 ),337 ),
356 );338 );
339 };
357 340
358 const probeProvider = (input: ProviderProbe) =>341 const probeProvider = (input: ProviderProbe) =>
359 probe(input).pipe(342 probe(input).pipe(
360 Effect.flatMap((item) => {343 Effect.flatMap((item) => {
344 const executable = input.executable;
345 const authArgs = input.authArgs;
346 const parseAuth = input.parseAuth;
347
348 if (!executable || !authArgs || !parseAuth) {
349 return Effect.succeed({
350 ...item,
351 auth: unknownAuth(input.installHint),
352 } satisfies SourceControlProviderDiscoveryItem);
353 }
354
361 if (item.status !== "available") {355 if (item.status !== "available") {
362 return Effect.succeed({356 return Effect.succeed({
363 ...item,357 ...item,
@@ -368,8 +362,8 @@ export const layer = Layer.effect( · 4 unmodified lines
368 return process362 return process
369 .run({363 .run({
370 operation: "source-control.discovery.auth",364 operation: "source-control.discovery.auth",
371 command: input.executable,365 command: executable,
372 args: input.authArgs,366 args: authArgs,
373 cwd: config.cwd,367 cwd: config.cwd,
374 allowNonZeroExit: true,368 allowNonZeroExit: true,
375 timeoutMs: 5_000,369 timeoutMs: 5_000,
@@ -381,7 +375,7 @@ export const layer = Layer.effect( · 5 unmodified lines
381 (result) =>375 (result) =>
382 ({376 ({
383 ...item,377 ...item,
384 auth: input.parseAuth(result),378 auth: parseAuth(result),
385 }) satisfies SourceControlProviderDiscoveryItem,379 }) satisfies SourceControlProviderDiscoveryItem,
386 ),380 ),
387 Effect.catch((cause) =>381 Effect.catch((cause) =>