watch

tcmts.cli.main.watch · #k7dhkcew · term · runs on the server

uses node:fs

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { fileLeaf } from "#codebase/tcmts/cli/files";
import { check } from "#codebase/tcmts/cli/main";
import { joinName } from "#codebase/tcmts/cli/mirror";
import { bold, dim, red } from "#codebase/tcmts/cli/style";
import fs from "node:fs";
import path from "node:path";

export function watch(cb: Codebase, into?: string, fresh = false) {
  const root = cb.lay.root;
  let timer: NodeJS.Timeout | undefined;
  let pending = new Set<string>();
  const runChecks = () => {
    const files = [...pending];
    pending = new Set();
    for (const f of files) {
      if (!fs.existsSync(f)) continue;
      console.log(dim(`\n── ${new Date().toLocaleTimeString()} ${"─".repeat(40)}`));
      try {
        // (said, not written: the file is open in an editor)
        check(cb, f, into, fresh, "say");
      } catch (e) {
        console.log(red((e as Error).stack ?? String(e)));
      }
    }
  };
  // a scratch file of code, or a file kept as written that's there to save: a page saying where it goes, or (with
  // --into) one the namespace holds, as `tcm edit` writes them (not an editor's own, like a swap file)
  const watched = (f: string): boolean => {
    if (/\.tsx?$/.test(f)) return !f.endsWith(".d.ts");
    const file = path.join(root, f);
    if (
      ["package.json", "tsconfig.json", ".mcp.json"].includes(f) ||
      !fs.existsSync(file) ||
      !fs.statSync(file).isFile()
    )
      return false;
    if (/^<!--\s*tcm:\s*(path|namespace)\s/.test(fs.readFileSync(file, "utf8"))) return true;
    try {
      return (
        into !== undefined &&
        cb.store
          .hashesAt(joinName(into, fileLeaf(f)))
          .some((h) => cb.store.getDef(h)?.kind === "file")
      );
    } catch {
      return false;
    }
  };
  const initial = fs
    .readdirSync(root)
    .filter(watched)
    .map((f) => path.join(root, f));
  console.log(`${bold("tcm")} watching ${root} ${dim("(Ctrl-C to stop)")}`);
  initial.forEach((f) => pending.add(f));
  runChecks();
  fs.watch(root, (_event, filename) => {
    if (!filename || !watched(filename)) return;
    pending.add(path.join(root, filename));
    clearTimeout(timer);
    timer = setTimeout(runChecks, 80);
  });
}

uses

used by