import { CONTEXT_KINDS, contextPathForKind, MAX_CONTEXT_DOCUMENT_CHARACTERS } from '@code/primitives/project'; import type { PreparedPublicGitSource, PublicGitImportResult, RepositoryContextDocument } from '@code/primitives/project'; const GITHUB_HOST = "github.com"; const GITEA_HOST = "git.openputer.com"; const GITHUB_REQUEST_HEADERS = { Accept: "application/vnd.github+json", "User-Agent": "zopu-project-importer", } as const; const GITEA_REQUEST_HEADERS = { Accept: "application/json", "User-Agent": "zopu-project-importer", } as const; type Fetch = ( input: string | URL | Request, init?: RequestInit ) => Promise; interface PublicGitProvider { readonly headers: Readonly>; readonly metadataUrl: (source: PreparedPublicGitSource) => string; readonly rawFileUrl: ( source: PreparedPublicGitSource, branch: string, path: string ) => string; readonly repositoryNotFoundMessage: string; } const candidatePaths = { agents: ["AGENTS.md"], business: ["business.md", "docs/business.md"], design: ["design.md", "docs/design.md"], product: ["product.md", "docs/product.md"], readme: ["README.md", "README", "readme.md"], tech: ["tech.md", "docs/tech.md"], } as const; const encodePath = (path: string) => path .split("/") .map((segment) => encodeURIComponent(segment)) .join("/"); const providerForSource = ( source: PreparedPublicGitSource ): PublicGitProvider => { if (source.host === GITHUB_HOST) { return { headers: GITHUB_REQUEST_HEADERS, metadataUrl: (candidate) => `https://api.github.com/repos/${encodePath(candidate.repositoryPath)}`, rawFileUrl: (candidate, branch, path) => `https://raw.githubusercontent.com/${encodePath(candidate.repositoryPath)}/${encodePath(branch)}/${encodePath(path)}`, repositoryNotFoundMessage: "Public GitHub repository not found", }; } if (source.host === GITEA_HOST) { return { headers: GITEA_REQUEST_HEADERS, metadataUrl: (candidate) => `https://${GITEA_HOST}/api/v1/repos/${encodePath(candidate.repositoryPath)}`, rawFileUrl: (candidate, branch, path) => `https://${GITEA_HOST}/api/v1/repos/${encodePath(candidate.repositoryPath)}/raw/${encodePath(path)}?ref=${encodeURIComponent(branch)}`, repositoryNotFoundMessage: "Public Gitea repository not found", }; } throw new Error( "Repository import supports public GitHub and git.openputer.com URLs" ); }; const fetchTextCandidate = async ( fetcher: Fetch, provider: PublicGitProvider, source: PreparedPublicGitSource, branch: string, path: string ): Promise => { const response = await fetcher(provider.rawFileUrl(source, branch, path), { headers: provider.headers, }); if (response.status === 404) { return null; } if (!response.ok) { throw new Error( `${source.host} returned ${response.status} while reading ${path}` ); } const content = await response.text(); if (content.trim().length === 0) { return null; } if (content.length > MAX_CONTEXT_DOCUMENT_CHARACTERS) { throw new Error(`${path} exceeds the 200000 character import limit`); } return content; }; const fetchContextDocument = async ( fetcher: Fetch, provider: PublicGitProvider, source: PreparedPublicGitSource, branch: string, kind: (typeof CONTEXT_KINDS)[number] ): Promise => { for (const candidate of candidatePaths[kind]) { const content = await fetchTextCandidate( fetcher, provider, source, branch, candidate ); if (content) { return { content, kind, path: contextPathForKind(kind), }; } } return null; }; const readDefaultBranch = async ( fetcher: Fetch, provider: PublicGitProvider, source: PreparedPublicGitSource ): Promise => { const response = await fetcher(provider.metadataUrl(source), { headers: provider.headers, }); if (!response.ok) { throw new Error( response.status === 404 ? provider.repositoryNotFoundMessage : `${source.host} returned ${response.status} while reading the repository` ); } const metadata = (await response.json()) as { default_branch?: unknown }; if ( typeof metadata.default_branch !== "string" || metadata.default_branch.length === 0 ) { throw new Error(`${source.host} did not return a default branch`); } return metadata.default_branch; }; export const inspectPublicGit = async ( source: PreparedPublicGitSource, options: { readonly fetch?: Fetch } = {} ): Promise => { const fetcher = options.fetch ?? globalThis.fetch; const provider = providerForSource(source); const defaultBranch = await readDefaultBranch(fetcher, provider, source); const candidates = await Promise.all( CONTEXT_KINDS.map((kind) => fetchContextDocument(fetcher, provider, source, defaultBranch, kind) ) ); const documents = candidates.filter( (document): document is RepositoryContextDocument => document !== null ); return { defaultBranch, documents, warnings: CONTEXT_KINDS.filter( (kind) => !documents.some((document) => document.kind === kind) ).map((kind) => ({ message: "Canonical context file was not found in the repository", path: contextPathForKind(kind), })), }; };