Files
kandev/apps/web/lib/plugins/change-request-creation.ts
T
João Salavisa 5257dfbaab fix(web): close the i18n coverage gaps in the non-JSX scanner and locale catalogs (#2727)
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 23:03:29 +00:00

152 lines
4.5 KiB
TypeScript

import type { PluginRepositoryProviderRegistration } from "./registry";
import type { PluginHostRepository } from "./types";
import { t } from "@/lib/i18n";
type TaskRepositoryLink = { repository_id: string; position?: number };
type CreationTask = {
id: string;
workspaceId?: string;
workspace_id?: string;
repositoryId?: string | null;
repositories?: readonly TaskRepositoryLink[];
};
type CreationRepository = Pick<PluginHostRepository, "id" | "name" | "provider"> &
Partial<Pick<PluginHostRepository, "workspace_id">>;
export type ChangeRequestProviderTarget = {
provider: PluginRepositoryProviderRegistration;
workspaceId: string;
taskId: string;
repositoryId: string;
repository: PluginHostRepository;
};
function taskRepositoryLinks(task: CreationTask): readonly TaskRepositoryLink[] {
if (task.repositories?.length) {
return [...task.repositories].sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
}
if (task.repositoryId) return [{ repository_id: task.repositoryId, position: 0 }];
return [];
}
export function resolveChangeRequestProviderTarget({
task,
repositories,
repositoryScope,
getProvider,
}: {
task: CreationTask | undefined;
repositories: readonly CreationRepository[];
repositoryScope?: string;
getProvider(id: string): PluginRepositoryProviderRegistration | undefined;
}): ChangeRequestProviderTarget | null {
if (!task) return null;
const workspaceId = task.workspaceId ?? task.workspace_id;
if (!workspaceId) return null;
const links = taskRepositoryLinks(task);
const linkedIds = new Set(links.map((link) => link.repository_id));
const linked = repositories.filter(
(repository) =>
linkedIds.has(repository.id) &&
(!repository.workspace_id || repository.workspace_id === workspaceId),
);
const matches =
repositoryScope === undefined || repositoryScope === ""
? linked.filter((repository) => repository.id === links[0]?.repository_id)
: linked.filter((repository) => repository.name === repositoryScope);
if (matches.length !== 1) return null;
const repository = matches[0];
const provider = getProvider(repository.provider);
if (!provider?.createChangeRequest) return null;
return {
provider,
workspaceId,
taskId: task.id,
repositoryId: repository.id,
repository: {
...repository,
workspace_id: repository.workspace_id ?? workspaceId,
},
};
}
type PushResult = { success: boolean; output?: string; error?: string };
type NativeCreateResult = {
success: boolean;
branch_pushed: boolean;
pr_url?: string;
provider?: string;
output?: string;
error?: string;
linked?: boolean;
association_error?: string;
};
export async function createChangeRequestWithProvider({
target,
push,
repositoryScope,
title,
body,
baseBranch,
draft,
branchAlreadyPushed,
sessionId,
signal,
}: {
target: ChangeRequestProviderTarget;
push(options: { setUpstream: boolean }, repositoryScope?: string): Promise<PushResult>;
repositoryScope?: string;
title: string;
body: string;
baseBranch?: string;
draft: boolean;
branchAlreadyPushed: boolean;
sessionId: string;
signal: AbortSignal;
}): Promise<NativeCreateResult> {
let pushOutput = "";
if (!branchAlreadyPushed) {
const pushed = await push({ setUpstream: true }, repositoryScope);
pushOutput = pushed.output ?? "";
if (!pushed.success) {
return {
success: false,
branch_pushed: false,
output: pushOutput,
...(pushed.error ? { error: pushed.error } : {}),
};
}
}
try {
const created = await target.provider.createChangeRequest!({
workspaceId: target.workspaceId,
taskId: target.taskId,
sessionId,
repositoryId: target.repositoryId,
repository: target.repository,
title,
body,
...(baseBranch ? { baseBranch } : {}),
draft: target.provider.supportsDraft === false ? false : draft,
signal,
});
return {
success: true,
branch_pushed: true,
pr_url: created.url,
provider: created.provider ?? target.provider.id,
output: created.output ?? pushOutput,
...(created.linked === undefined ? {} : { linked: created.linked }),
...(created.associationError ? { association_error: created.associationError } : {}),
};
} catch (error) {
return {
success: false,
branch_pushed: true,
output: pushOutput,
error: error instanceof Error ? error.message : t("integrations:changeRequestCreationFailed"),
};
}
}