importFiles

tcmts.cli.main.importFiles · #pbqhnlup · term · runs on the server

uses node:path

Import the files under root: the hash of each definition's code in them, by path, or undefined if they don't import. Following a mirror's files, a definition they haven't changed since tcm last saw them stays as the codebase has it (it may have changed it while write-back waited), and one gone from the files goes from the codebase.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { apply, checkDistinct, classifyPaths, importAssets } from "#codebase/tcmts/cli/main";
import { joinName, namespaceForFile, sourceFiles } from "#codebase/tcmts/cli/mirror";
import type { Mirror } from "#codebase/tcmts/cli/mirror";
import { bold, dim, red, yellow } from "#codebase/tcmts/cli/style";
import { UsageError } from "#codebase/tcmts/cli/ui";
import path_1 from "node:path";

export function importFiles(
  cb: Codebase,
  root: string,
  dir: string,
  mode: "add" | "update",
  into: string,
  verbose: boolean,
  exclude: string[],
  tests: boolean,
  following?: Mirror,
): Map<string, string> | undefined {
  const files = sourceFiles(root, new Set(exclude.map((e) => path_1.resolve(root, e))), tests);
  if (!files.length) throw new UsageError(`no .ts files under ${dir}`);
  // (files that aren't code come after it: a page's links to code need the code)
  const done = (held: Map<string, string> | undefined) => {
    const assets = held && importAssets(cb, root, dir, mode, into, verbose, exclude, following);
    return held && assets && new Map([...held, ...assets]);
  };
  console.log(dim(`analyzing ${files.length} files under ${dir}…`));
  cb.refreshShim();
  const a = cb.analyzeProject(
    files,
    (file, name) => cb.lineageSalt(joinName(into, namespaceForFile(root, file), name)),
    [],
    // (the augmentations these files hold new versions of are left out)
    cb.compilerOptions(root, "import", (p) => !into || p.startsWith(`${into}.`)),
  );
  for (const w of a.warnings) console.log(yellow(`warning: ${w}`));
  if (a.diagnostics.length || a.errors.length) {
    for (const d of a.diagnostics) process.stdout.write(d.formatted);
    for (const e of a.errors) console.log(red(e));
    console.log(
      red(`\n${a.errors.length + a.diagnostics.length} problem(s); nothing was imported.`),
    );
    return undefined;
  }
  const planned = a.defs.map((def) => ({
    def,
    path: joinName(into, namespaceForFile(root, def.file), def.nameHint),
  }));
  checkDistinct(
    planned,
    (p) => `${p.path} is defined in more than one file (one of them is ${p.def.file})`,
  );
  cb.lockPackages(a.packages);
  const held = new Map(planned.map((p) => [p.path, p.def.hash]));
  if (!following)
    return done(
      apply(cb, classifyPaths(cb, planned), mode, `import ${dir}`, !verbose) ? held : undefined,
    );
  const was = new Map<string, string>();
  for (const f of Object.values(following.files))
    for (const [leaf, h] of Object.entries(f.names)) was.set(joinName(f.ns, leaf), h);
  const changed = planned.filter((p) => was.get(p.path) !== p.def.hash);
  for (const p of changed) {
    const w = was.get(p.path);
    if (w && !cb.store.hashesAt(p.path, p.def.kind).includes(w))
      console.log(
        yellow(`  ! ${p.path} changed in the files and in the codebase; the files' change is kept`),
      );
  }
  const gone = [...was].filter(
    ([p, h]) =>
      !held.has(p) && cb.store.getDef(h)?.kind !== "file" && cb.store.hashesAt(p).includes(h),
  );
  if (!changed.length && !gone.length) console.log(dim("nothing to do"));
  const ok =
    !changed.length || apply(cb, classifyPaths(cb, changed), mode, `import ${dir}`, !verbose);
  for (const [p, h] of gone) {
    cb.store.removeName(p, h, `import ${dir}`);
    console.log(`${red("  - removed  ")} ${bold(p)} ${dim("(gone from the files)")}`);
  }
  return done(ok ? held : undefined);
}

uses

used by