| 1 | /** |
| 2 | * A new work item's choices, as the site's New work item has them |
| 3 | * (apps/web/app/routes/workspace/work/new.tsx): the repositories of its |
| 4 | * own workspace it may land in, and the one it is filed from, ticked as |
| 5 | * a target to start with. |
| 6 | * |
| 7 | * Kept free of React Native imports so `npm test` runs it under Node. |
| 8 | */ |
| 9 | |
| 10 | /** A workspace's project as the API answers: only its repository, `owner/name`, when it has one. */ |
| 11 | export type ProjectRepo = { repository: string | null }; |
| 12 | |
| 13 | /** The repositories of a workspace's projects, `owner/name`, each once, in the order given. Another workspace's are left out. */ |
| 14 | export function workspaceRepos(workspace: string, projects: readonly ProjectRepo[]): string[] { |
| 15 | const own = workspace.toLowerCase(); |
| 16 | const seen = new Map<string, string>(); |
| 17 | for (const project of projects) { |
| 18 | const path = project.repository; |
| 19 | if (!path || path.split("/")[0]?.toLowerCase() !== own) continue; |
| 20 | if (!seen.has(path.toLowerCase())) seen.set(path.toLowerCase(), path); |
| 21 | } |
| 22 | return [...seen.values()]; |
| 23 | } |
| 24 | |
| 25 | /** |
| 26 | * The repository a new item is filed from, `owner/name`, from a link's |
| 27 | * `repo` (`web` or `acme/web`): only one of this workspace, else none. |
| 28 | */ |
| 29 | export function originOf(workspace: string, repo: string | null | undefined): string | null { |
| 30 | const parts = (repo ?? "").split("/").filter(Boolean); |
| 31 | if (parts.length === 1) return `${workspace}/${parts[0]}`; |
| 32 | if (parts.length === 2 && parts[0]!.toLowerCase() === workspace.toLowerCase()) return `${workspace}/${parts[1]}`; |
| 33 | return null; |
| 34 | } |