projectInfo

tcmts.share.projectInfo · #nnhev5wp · term · runs anywhere

The project a codebase is: named for the package at its top namespace (or the namespace; with several, the codebase's directory), 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 || cb.lay.root.split(/[\\/]/).filter(Boolean).pop() || "codebase"),
    description: own?.description ?? firstSentence(),
    root,
    packages: packages.sort((a, b) => (a.name < b.name ? -1 : 1)),
    ...(readme ? { readme } : {}),
    ...(contributing ? { contributing } : {}),
  };
}

uses

used by