mv

tcmts.cli.main.mv · #ev5sxxpe · term · runs on the server

uses tcmts.cli.files.followMove, which uses Buffer

import { IDENT_PATH } from "#codebase/tcmts/cli/codebase";
import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { FILE_PATH, followMove } from "#codebase/tcmts/cli/files";
import { loadMirror } from "#codebase/tcmts/cli/mirror";
import { dim } from "#codebase/tcmts/cli/style";
import { UsageError } from "#codebase/tcmts/cli/ui";

export function mv(cb: Codebase, from: string, to: string) {
  const exact = cb.store.names(undefined, true).filter((n) => n.path === from);
  const moves = exact.length
    ? exact.map((n) => ({ ...n, dest: to }))
    : cb.store
        .names(undefined, true)
        .filter((n) => n.path.startsWith(from + "."))
        .map((n) => ({ ...n, dest: to + n.path.slice(from.length) }));
  if (!moves.length) throw new UsageError(`'${from}' not found`);
  // (a file's own name, like `getting-started`, may have dashes)
  for (const m of moves)
    if (!(m.kind === "file" ? FILE_PATH : IDENT_PATH).test(m.dest))
      throw new UsageError(`'${m.dest}' is not a valid name`);
  for (const m of moves)
    if (cb.store.hashesAt(m.dest).length) throw new UsageError(`'${m.dest}' already exists`);
  cb.store.transaction(() => {
    for (const m of moves) {
      cb.store.removeName(m.path, m.hash, `mv ${from} ${to}`);
      cb.store.addName(m.dest, m.hash, m.kind, `mv ${from} ${to}`);
    }
  });
  // pages linking to a file that moved link to where it is now (and one that moved links from there)
  for (const m of moves.filter((x) => x.kind === "file")) {
    const relinked = followMove(cb, m.path, m.dest);
    cb.putDefs([...relinked.values()]);
    const replaced = new Map<string, string>();
    cb.store.transaction(() => {
      for (const [page, rec] of relinked)
        for (const old of cb.store.hashesAt(page)) {
          cb.store.replaceName(page, old, rec.hash, "file", `mv ${from} ${to} (links)`);
          replaced.set(old, rec.hash);
        }
    });
    if (relinked.size)
      console.log(dim(`  links to it changed in ${[...relinked.keys()].join(", ")}`));
    // (and what uses those pages, the docs site's list of them say, follows them, as it follows an update)
    for (const [old, now] of replaced) cb.store.addPatch(old, now);
    if (replaced.size) cb.propagate(replaced);
  }
  cb.refreshShim();
  console.log(`moved ${moves.length} name${moves.length > 1 ? "s" : ""}`);
  const mirror = loadMirror(cb);
  for (const m of moves) {
    // a mirror renames the code itself, so the runtime name follows
    if (mirror && m.dest.startsWith(mirror.into ? `${mirror.into}.` : "")) continue;
    const rec = cb.store.getDef(m.hash)!;
    const runtime = rec.nameHint;
    if (rec.nominal && runtime !== m.dest.slice(m.dest.lastIndexOf(".") + 1)) {
      console.log(
        dim(
          `  note: ${m.dest} is still named \`${runtime}\` at runtime (\`.name\` is part of its hash); changing that means editing it`,
        ),
      );
    }
  }
}

uses

used by