Showing the changes to apps/server/src/sourceControl/SourceControlDiscovery.ts (1 of 33 files). Show all
Unified · Split
| @@ -13,20 +13,22 @@ import * as VcsProcess from "../vcs/VcsProcess.ts"; · 12 unmodified lines | ||
| 13 | 13 | |
| 14 | 14 | interface DiscoveryProbe { |
| 15 | 15 | readonly label: string; |
| 16 | − readonly executable: string; | |
| 17 | − readonly versionArgs: ReadonlyArray<string>; | |
| 16 | + readonly executable?: string; | |
| 17 | + readonly versionArgs?: ReadonlyArray<string>; | |
| 18 | 18 | readonly implemented: boolean; |
| 19 | 19 | readonly installHint: string; |
| 20 | 20 | } |
| 21 | 21 | |
| 22 | 22 | type VcsProbe = DiscoveryProbe & { |
| 23 | 23 | readonly kind: VcsDriverKind; |
| 24 | + readonly executable: string; | |
| 25 | + readonly versionArgs: ReadonlyArray<string>; | |
| 24 | 26 | }; |
| 25 | 27 | |
| 26 | 28 | type ProviderProbe = DiscoveryProbe & { |
| 27 | 29 | readonly kind: SourceControlProviderKind; |
| 28 | − readonly authArgs: ReadonlyArray<string>; | |
| 29 | − readonly parseAuth: (input: AuthProbeInput) => SourceControlProviderAuth; | |
| 30 | + readonly authArgs?: ReadonlyArray<string>; | |
| 31 | + readonly parseAuth?: (input: AuthProbeInput) => SourceControlProviderAuth; | |
| 30 | 32 | }; |
| 31 | 33 | |
| 32 | 34 | interface AuthProbeInput { |
| @@ -38,7 +40,7 @@ interface AuthProbeInput { · 5 unmodified lines | ||
| 38 | 40 | interface DiscoveryProbeResult<Kind extends string> { |
| 39 | 41 | readonly kind: Kind; |
| 40 | 42 | readonly label: string; |
| 41 | − readonly executable: string; | |
| 43 | + readonly executable?: string; | |
| 42 | 44 | readonly implemented: boolean; |
| 43 | 45 | readonly status: "available" | "missing"; |
| 44 | 46 | readonly version: Option.Option<string>; |
| @@ -101,12 +103,8 @@ const SOURCE_CONTROL_PROVIDER_PROBES: ReadonlyArray<ProviderProbe> = [ · 56 unmodified lines | ||
| 101 | 103 | { |
| 102 | 104 | kind: "bitbucket", |
| 103 | 105 | label: "Bitbucket", |
| 104 | − executable: "bb", | |
| 105 | − versionArgs: ["--version"], | |
| 106 | − authArgs: ["auth", "status"], | |
| 107 | − parseAuth: parseBitbucketAuth, | |
| 108 | 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 | |
| 269 | −function 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 | − | |
| 300 | 267 | export interface SourceControlDiscoveryShape { |
| 301 | 268 | readonly discover: Effect.Effect<SourceControlDiscoveryResult>; |
| 302 | 269 | } |
| @@ -314,12 +281,27 @@ export const layer = Layer.effect( · 11 unmodified lines | ||
| 314 | 281 | |
| 315 | 282 | const probe = <Kind extends VcsDriverKind | SourceControlProviderKind>( |
| 316 | 283 | input: DiscoveryProbe & { readonly kind: Kind }, |
| 317 | − ): Effect.Effect<DiscoveryProbeResult<Kind>> => | |
| 318 | − process | |
| 284 | + ): Effect.Effect<DiscoveryProbeResult<Kind>> => { | |
| 285 | + 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 | 301 | .run({ |
| 320 | 302 | operation: "source-control.discovery.probe", |
| 321 | − command: input.executable, | |
| 322 | − args: input.versionArgs, | |
| 303 | + command: executable, | |
| 304 | + args: versionArgs, | |
| 323 | 305 | cwd: config.cwd, |
| 324 | 306 | timeoutMs: 5_000, |
| 325 | 307 | maxOutputBytes: 8_000, |
| @@ -331,7 +313,7 @@ export const layer = Layer.effect( · 5 unmodified lines | ||
| 331 | 313 | ({ |
| 332 | 314 | kind: input.kind, |
| 333 | 315 | label: input.label, |
| 334 | − executable: input.executable, | |
| 316 | + executable, | |
| 335 | 317 | implemented: input.implemented, |
| 336 | 318 | status: "available" as const, |
| 337 | 319 | version: Option.orElse(firstNonEmptyLine(result.stdout), () => |
| @@ -345,7 +327,7 @@ export const layer = Layer.effect( · 7 unmodified lines | ||
| 345 | 327 | Effect.succeed({ |
| 346 | 328 | kind: input.kind, |
| 347 | 329 | label: input.label, |
| 348 | − executable: input.executable, | |
| 330 | + executable, | |
| 349 | 331 | implemented: input.implemented, |
| 350 | 332 | status: "missing" as const, |
| 351 | 333 | version: Option.none<string>(), |
| @@ -354,10 +336,22 @@ export const layer = Layer.effect( · 2 unmodified lines | ||
| 354 | 336 | } satisfies DiscoveryProbeResult<Kind>), |
| 355 | 337 | ), |
| 356 | 338 | ); |
| 339 | + }; | |
| 357 | 340 | |
| 358 | 341 | const probeProvider = (input: ProviderProbe) => |
| 359 | 342 | probe(input).pipe( |
| 360 | 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 | 355 | if (item.status !== "available") { |
| 362 | 356 | return Effect.succeed({ |
| 363 | 357 | ...item, |
| @@ -368,8 +362,8 @@ export const layer = Layer.effect( · 4 unmodified lines | ||
| 368 | 362 | return process |
| 369 | 363 | .run({ |
| 370 | 364 | operation: "source-control.discovery.auth", |
| 371 | − command: input.executable, | |
| 372 | − args: input.authArgs, | |
| 365 | + command: executable, | |
| 366 | + args: authArgs, | |
| 373 | 367 | cwd: config.cwd, |
| 374 | 368 | allowNonZeroExit: true, |
| 375 | 369 | timeoutMs: 5_000, |
| @@ -381,7 +375,7 @@ export const layer = Layer.effect( · 5 unmodified lines | ||
| 381 | 375 | (result) => |
| 382 | 376 | ({ |
| 383 | 377 | ...item, |
| 384 | − auth: input.parseAuth(result), | |
| 378 | + auth: parseAuth(result), | |
| 385 | 379 | }) satisfies SourceControlProviderDiscoveryItem, |
| 386 | 380 | ), |
| 387 | 381 | Effect.catch((cause) => |