importAssets

tcmts.cli.main.importAssets · #pj2hryjv · term · runs on the server

uses node:path

Import the files under root that aren't code (Markdown, CSS, images), as files of the codebase: their hashes, by name, or undefined if they don't import. Following a mirror, one it hasn't changed stays as the codebase has it, and one gone from the files goes from the codebase.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { fileDefinition } from "#codebase/tcmts/cli/files";
import { apply, classifyPaths } from "#codebase/tcmts/cli/main";
import { assetFiles, assetFormat, assetName, joinName } from "#codebase/tcmts/cli/mirror";
import type { Mirror } from "#codebase/tcmts/cli/mirror";
import { bold, dim, red, yellow } from "#codebase/tcmts/cli/style";
import type { DefRecord } from "#codebase/tcmts/core/types";
import fs from "node:fs";
import path_1 from "node:path";

export function importAssets(
  cb: Codebase,
  root: string,
  dir: string,
  mode: "add" | "update",
  into: string,
  verbose: boolean,
  exclude: string[],
  following?: Mirror,
): Map<string, string> | undefined {
  let failed = false;
  // a file that can't be named in the codebase (a dot in its name) is a problem with it, said, not a stop
  const named = assetFiles(root, new Set(exclude.map((e) => path_1.resolve(root, e)))).flatMap(
    (f) => {
      try {
        return [{ f, name: assetName(root, into, f) }];
      } catch (e) {
        console.log(red(`${path_1.relative(root, f)}: ${(e as Error).message}`));
        failed = true;
        return [];
      }
    },
  );
  const names = new Set(named.map((n) => n.name));
  const planned: { def: DefRecord; path: string; warnings: string[] }[] = [];
  for (const { f, name } of named) {
    const { def, problems, warnings } = fileDefinition(
      cb,
      name,
      assetFormat(f),
      fs.readFileSync(f),
      names,
    );
    for (const p of problems) console.log(red(`${path_1.relative(root, f)}: ${p}`));
    failed ||= problems.length > 0;
    planned.push({ def, path: name, warnings });
  }
  if (failed) return undefined;
  const held = new Map(planned.map((p) => [p.path, p.def.hash]));
  const was = new Map<string, string>();
  // by what the codebase has for them: a removed file's mark (.tcm-files) may be gone with it
  const isFile = (f: Mirror["files"][string]) =>
    Object.values(f.names).some((h) => cb.store.getDef(h)?.kind === "file");
  if (following)
    for (const f of Object.values(following.files))
      if (isFile(f))
        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);
  // what's wrong with the files that changed (the others said so when they did)
  for (const p of changed) for (const w of p.warnings) console.log(yellow(`warning: ${w}`));
  if (changed.length && !apply(cb, classifyPaths(cb, changed), mode, `import ${dir}`, !verbose))
    return undefined;
  for (const [p, h] of was) {
    if (held.has(p) || !cb.store.hashesAt(p).includes(h)) continue;
    cb.store.removeName(p, h, `import ${dir}`);
    console.log(`${red("  - removed  ")} ${bold(p)} ${dim("(gone from the files)")}`);
  }
  return held;
}

uses

used by