import { resolveService, SdkError, type FileTree, type ProjectRecord, type Session } from "@nibling/sdk"; type GitBase = NonNullable; type Did = Session["did"]; /** An expected failure whose message is written for the person at the keyboard. */ export class PlatformError extends Error { override name = "PlatformError"; } /** True for "no such project," as opposed to a network or server failure. A surface address that * names nothing hoists one level up only on this, never on any other kind of load failure. */ export function isNotFound(error: unknown): boolean { return error instanceof SdkError && error.code === "ERR_NIB_NOT_FOUND"; } /** `isNotFound`, or the actor itself not resolving: both mean the project page's address names nothing * it can find, as opposed to a network or server failure, which keeps the plain error message instead. */ export function isMiss(error: unknown): boolean { return error instanceof SdkError && (error.code === "ERR_NIB_NOT_FOUND" || error.code === "ERR_NIB_IDENTITY"); } /** A knot origin from a repo DID's `#tangled_knot` service, as the SDK's resolveRepo reads it. */ export async function knotOf(repo: Did): Promise { const knot = await resolveService(repo, "#tangled_knot"); if (!knot) throw new PlatformError(`${repo} names no tangled knot.`); return knot; } /** * Fetches a project's base commit. Import is a shallow fetch of the branch tip, so a base whose branch * has moved on cannot be loaded yet. */ export async function loadBase(base: GitBase): Promise { // isomorphic-git loads only for a project that has a git base. const [knot, { importRepo }] = await Promise.all([knotOf(base.repo), import("@nibling/sdk/git")]); const imported = await importRepo({ cloneUrl: `${knot}/${base.repo}`, repoDid: base.repo }, base.ref ? { ref: base.ref } : {}); if (imported.commit !== base.commit) { throw new PlatformError(`${base.ref ?? "The base branch"} moved from ${base.commit.slice(0, 7)} to ${imported.commit.slice(0, 7)}; loading an older base is not supported yet.`); } return base.path ? rerootTree(imported.files, base.path) : imported.files; } /** `tree` restricted to paths under `path`, with that prefix stripped so the subdirectory becomes the root. */ export function rerootTree(tree: FileTree, path: string): FileTree { const prefix = `${path}/`; const rerooted: FileTree = new Map(); for (const [p, bytes] of tree) if (p.startsWith(prefix)) rerooted.set(p.slice(prefix.length), bytes); return rerooted; }