Open History to see the last commit
1import {
2 type CommandId,
3 type GitRunStackedActionResult,
4 type OrchestrationProjectShell,
5 type ThreadId,
6} from "@t3tools/contracts";
7import * as Cause from "effect/Cause";
8import * as Effect from "effect/Effect";
9import * as Option from "effect/Option";
10
11import * as Orchestrator from "../orchestration-v2/Orchestrator.ts";
12import * as ProjectService from "../project/ProjectService.ts";
13import { sourceControlDefinitions } from "../sourceControl/sourceControlDefinitions.ts";
14
15export interface CreatedPullRequestKey {
16 readonly host: string;
17 readonly repository: string;
18 readonly number: number;
19 readonly url: string;
20}
21
22export function createdPullRequestKey(
23 result: Pick<GitRunStackedActionResult, "pr">,
24 project: OrchestrationProjectShell | undefined,
25): CreatedPullRequestKey | null {
26 const { status, number, url } = result.pr;
27 if ((status !== "created" && status !== "opened_existing") || number === undefined || !url) {
28 return null;
29 }
30 const parsed = sourceControlDefinitions.parseChangeRequestUrl(url)?.link;
31 if (parsed !== undefined) {
32 return { host: parsed.host, repository: parsed.repository, number: parsed.number, url };
33 }
34 const identity = project?.repositoryIdentity;
35 const kind = identity?.provider;
36 const repository = sourceControlDefinitions.repositorySelector(identity);
37 if (!identity || kind === undefined || repository === null) return null;
38 return {
39 host: sourceControlDefinitions.changeRequestHost(identity, kind),
40 repository: repository.toLowerCase(),
41 number,
42 url,
43 };
44}
45
46/**
47 * Never fails: the git action already succeeded and its result is on its way to the client, so
48 * a link that cannot be made is logged and dropped.
49 */
50export const linkCreatedPullRequest: <E>(input: {
51 readonly threadId: ThreadId;
52 readonly result: Pick<GitRunStackedActionResult, "pr">;
53 readonly commandId: Effect.Effect<CommandId, E>;
54}) => Effect.Effect<void, never, Orchestrator.OrchestratorV2 | ProjectService.ProjectService> =
55 Effect.fn("linkCreatedPullRequest")(
56 function* <E>(input: {
57 readonly threadId: ThreadId;
58 readonly result: Pick<GitRunStackedActionResult, "pr">;
59 readonly commandId: Effect.Effect<CommandId, E>;
60 }) {
61 const engine = yield* Orchestrator.OrchestratorV2;
62 const projects = yield* ProjectService.ProjectService;
63 const thread = yield* engine
64 .getThreadShell(input.threadId)
65 .pipe(Effect.map(Option.fromNullishOr));
66 if (Option.isNone(thread)) return;
67 const project = Option.getOrUndefined(yield* projects.getShell(thread.value.projectId));
68 const key = createdPullRequestKey(input.result, project);
69 if (key === null) return;
70 const commandId = yield* input.commandId;
71 yield* engine
72 .dispatch({
73 type: "thread.pull-request.link",
74 commandId,
75 threadId: input.threadId,
76 ...key,
77 source: "created",
78 })
79 .pipe(Effect.asVoid);
80 },
81 (effect, input) =>
82 effect.pipe(
83 Effect.catchCause((cause) =>
84 Cause.hasInterruptsOnly(cause)
85 ? Effect.failCause(cause as Cause.Cause<never>)
86 : Effect.logWarning("failed to link created pull request to thread", {
87 threadId: input.threadId,
88 }),
89 ),
90 ),
91 );