import type { AttachTaskWorkspaceSourcesRequest, WorkspaceRepositorySourceRequest, WorkspaceSourceRequest, } from "@/lib/types/http"; import { getWorkspaceSourceCapabilities } from "./executor-capabilities"; import { looksLikeSupportedRemoteURL } from "./remote-url"; import { t } from "@/lib/i18n"; export type WorkspaceSourceRow = { key: string; kind: "repository" | "folder"; sourceType?: "saved_repository" | "local_repository" | "remote_repository" | "folder"; repositoryId?: string; localPath?: string; remoteUrl?: string; provider?: string; providerRepoId?: string; providerOwner?: string; providerName?: string; baseBranch?: string; checkoutBranch?: string; displayName?: string; }; type RowKind = NonNullable | WorkspaceSourceRow["kind"]; let nextKey = 0; export function addWorkspaceSourceRow( rows: WorkspaceSourceRow[], kind: RowKind, executorType?: string | null, ): WorkspaceSourceRow[] { if (kind === "folder" && !getWorkspaceSourceCapabilities(executorType).canAddFolders) return rows; nextKey += 1; const key = `workspace-source-${nextKey}`; const sourceType = kind === "repository" ? "saved_repository" : kind; return [ ...rows, sourceType === "folder" ? { key, kind: "folder", sourceType, localPath: "", displayName: "" } : { key, kind: "repository", sourceType, baseBranch: "" }, ]; } export function removeWorkspaceSourceRow( rows: WorkspaceSourceRow[], key: string, ): WorkspaceSourceRow[] { return rows.filter((row) => row.key !== key); } export function updateWorkspaceSourceRow( rows: WorkspaceSourceRow[], key: string, patch: Partial, ): WorkspaceSourceRow[] { return rows.map((row) => row.key === key ? { ...row, ...patch, key: row.key, kind: row.kind } : row, ); } export function getWorkspaceSourceValidation(rows: WorkspaceSourceRow[]): Record { const errors: Record = {}; const repositoryKeys = new Set(); const folderPaths = new Set(); for (const row of rows) { const error = validateRow(row, repositoryKeys, folderPaths); if (error) errors[row.key] = error; } return errors; } export function buildWorkspaceSourcesPayload( rows: WorkspaceSourceRow[], ): AttachTaskWorkspaceSourcesRequest { return { sources: rows.map(toRequestSource) }; } function validateRow( row: WorkspaceSourceRow, repositoryKeys: Set, folderPaths: Set, ): string | null { if (row.kind === "folder") { const path = canonicalPath(row.localPath); if (!path) return t("workspaces:sourceChooseAFolder"); if (folderPaths.has(path)) return t("workspaces:sourceFolderAlreadySelected"); folderPaths.add(path); return null; } const locator = repositoryLocator(row); if (row.remoteUrl && !row.provider && !isValidRemoteUrl(row.remoteUrl)) { return t("workspaces:sourceEnterValidRemoteUrl"); } if (!locator || !row.baseBranch?.trim()) return t("workspaces:sourceChooseRepoAndBranch"); const duplicateKey = `${locator}\u0000${row.baseBranch.trim()}\u0000${row.checkoutBranch?.trim() ?? ""}`; if (repositoryKeys.has(duplicateKey)) return t("workspaces:sourceRepoBranchAlreadySelected"); repositoryKeys.add(duplicateKey); return null; } function repositoryLocator(row: WorkspaceSourceRow): string { return row.repositoryId || canonicalPath(row.localPath) || row.remoteUrl?.trim() || ""; } function canonicalPath(path: string | undefined): string { return path?.trim().replace(/\/+$/, "") ?? ""; } function isValidRemoteUrl(value: string): boolean { return looksLikeSupportedRemoteURL(value.trim()); } function toRequestSource(row: WorkspaceSourceRow): WorkspaceSourceRequest { if (row.kind === "folder") { return { kind: "folder", local_path: canonicalPath(row.localPath), ...(row.displayName?.trim() ? { display_name: row.displayName.trim() } : {}), }; } return { kind: "repository", ...repositoryLocation(row), ...repositoryProvider(row), base_branch: row.baseBranch?.trim() ?? "", ...(row.checkoutBranch?.trim() ? { checkout_branch: row.checkoutBranch.trim() } : {}), }; } function repositoryLocation( row: WorkspaceSourceRow, ): Pick { if (row.repositoryId) return { repository_id: row.repositoryId }; if (row.localPath) return { local_path: canonicalPath(row.localPath) }; return row.remoteUrl ? { remote_url: row.remoteUrl.trim() } : {}; } function repositoryProvider( row: WorkspaceSourceRow, ): Pick< WorkspaceRepositorySourceRequest, "provider" | "provider_repo_id" | "provider_owner" | "provider_name" > { return { ...(row.provider ? { provider: row.provider } : {}), ...(row.providerRepoId ? { provider_repo_id: row.providerRepoId } : {}), ...(row.providerOwner ? { provider_owner: row.providerOwner } : {}), ...(row.providerName ? { provider_name: row.providerName } : {}), }; }