import { defineDoc } from "@earendil-works/pi-durable"; import type { Context } from "@earendil-works/chord"; import { directory } from "@berry/api/environment.ts"; import { type Project, type ProjectIndex, type ProjectInput, workspacePath, } from "@berry/api/projects.ts"; import type { Environments } from "./environment.ts"; export const Projects = defineDoc({ kind: "berry.projects", version: 1, scope: "session", initial: () => ({}), checkpointWhen: () => true, }); export const ProjectOrder = defineDoc<{ ids: string[] }>({ kind: "berry.project-order", version: 1, scope: "session", initial: () => ({ ids: [] }), checkpointWhen: () => true, }); export async function resolveProject( input: ProjectInput, previous: Project | undefined, environments: Environments, context: Context, ): Promise { const name = input.name.trim(); if (!name) throw new Error("A project needs a name"); if (!input.roots.length) throw new Error("A project needs a workspace"); if (!Number.isInteger(input.defaultRoot) || !input.roots[input.defaultRoot]) { throw new Error("Choose a default workspace"); } const roots = []; for (const root of input.roots) { const saved = previous?.roots.find((saved) => saved.environment === root.environment && saved.cwd === root.cwd ); if (saved) roots.push(saved); else { const env = await environments.open(root.environment, null, context); roots.push({ environment: root.environment, cwd: await directory(env, root.cwd, context), platform: env.platform, }); } } return { name, roots, defaultRoot: input.defaultRoot }; } export function validateRoots(projects: ProjectIndex, id: string, project: Project): void { const seen = new Set(); for ( const [, value] of [ ...Object.entries(projects).filter(([key]) => key !== id), [id, project] as const, ] ) { for (const root of value.roots) { const path = root.environment + ":" + workspacePath(root.cwd, root.platform); if (seen.has(path)) { throw new Error(`Workspace ${root.environment}:${root.cwd} is already in a project`); } seen.add(path); } } }