projectInfo
The project a codebase is: named for the package at its top namespace (or the namespace), described by that package's doc or the first sentence of its README. Manifests are read from their source, not evaluated.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { inLibrary, librariesOf } from "#codebase/tcmts/cli/libraries"; import { absolute, isManifestPath, nsOf } from "#codebase/tcmts/cli/manifests"; import { manifestFields } from "#codebase/tcmts/share"; import type { PackageInfo, ProjectInfo } from "#codebase/tcmts/share"; import type { NameRow } from "#codebase/tcmts/store"; export function projectInfo(cb: Codebase, rows: NameRow[]): ProjectInfo { const tops = [...new Set(rows.map((r) => r.path.split(".")[0]!))]; const root = tops.length === 1 && rows.some((r) => r.path.includes(".")) ? tops[0]! : ""; const packages: PackageInfo[] = []; // (a library's packages are its own) const libraries = librariesOf(rows, (h) => cb.store.getDef(h)?.sourceText ?? ""); for (const r of rows) { if (!isManifestPath(r.path) || r.kind === "type" || inLibrary(libraries, r.path)) continue; const rec = cb.store.getDef(r.hash); const m = manifestFields(rec?.sourceText ?? ""); const name = typeof m.name === "string" ? m.name : undefined; if (!name) continue; const ns = nsOf(r.path); const strings = (v: unknown) => Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : []; const record = <T>(v: unknown, f: (x: unknown) => T | undefined) => Object.fromEntries( Object.entries(v && typeof v === "object" && !Array.isArray(v) ? v : {}).flatMap( ([k, x]) => { const y = f(x); return y === undefined ? [] : [[k, y]]; }, ), ) as Record<string, T>; const whole = (x: unknown) => (typeof x === "string" ? absolute(ns, x) : undefined); const bin = record(m.bin, whole); const description = (typeof m.description === "string" ? m.description : undefined) ?? rec?.doc ?.replace(/^\/\*\*|\*\/$/g, "") .replace(/^\s*\* ?/gm, "") .trim() .split("\n")[0]; packages.push({ name, namespace: ns, manifestPath: r.path, bins: Object.keys(bin), bin, exports: record(m.exports, (x) => strings(x).map((t) => absolute(ns, t))), apps: record(m.apps, whole), suites: record(m.suites, (x) => strings(x).map((t) => absolute(ns, t))), files: strings(m.files), with: strings(m.with), ...(typeof m.packageJson === "string" ? { packageJson: absolute(ns, m.packageJson) } : {}), ...(description ? { description } : {}), }); } const file = (leaf: string) => rows.find((r) => r.path === (root ? `${root}.${leaf}` : leaf) && r.kind === "file"); const readme = file("README"); const contributing = file("CONTRIBUTING"); const own = packages.find((p) => p.namespace === root); const firstSentence = () => { const text = readme ? (cb.store.getDef(readme.hash)?.sourceText ?? "") : ""; const para = text .split(/\n\s*\n/) .map((p) => p.trim()) .find((p) => p && !p.startsWith("#") && !p.startsWith("```") && !p.startsWith("<")); const plain = (para ?? "") .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1") .replace(/[*_`]/g, "") .replace(/\s+/g, " "); return (/^.*?[.!?](\s|$)/.exec(plain)?.[0] ?? plain).trim(); }; return { name: own?.name ?? (root || "codebase"), description: own?.description ?? firstSentence(), root, packages: packages.sort((a, b) => (a.name < b.name ? -1 : 1)), ...(readme ? { readme } : {}), ...(contributing ? { contributing } : {}), }; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.libraries.inLibrary
- tcmts.cli.libraries.librariesOf
- tcmts.cli.manifests.absolute
- tcmts.cli.manifests.isManifestPath
- tcmts.cli.manifests.nsOf
- tcmts.share.PackageInfo
- tcmts.share.ProjectInfo
- tcmts.share.manifestFields
- tcmts.store.NameRow