pullIntoScratch

tcmts.studio.pullIntoScratch · #ukppu4ll · term · runs on the server

uses node:fs

Pull definitions (names) into the scratch file file (text being what it holds), as tcm edit <names> --file does: each written with what the file holds kept (a definition saved as it is there stays, written again with them), one the file has already left as it is; JSX into a .ts file makes it a .tsx one (file, its name now). What the file holds then, and what was done.

import { classify, edit } from "#codebase/tcmts/cli/main";
import { HttpError } from "#codebase/tcmts/server";
import { analyzedScratch, editableCodebase, scratchPath } from "#codebase/tcmts/studio";
import fs from "node:fs";
import path from "node:path";
import { format, stripVTControlCharacters } from "node:util";

export async function pullIntoScratch(
  file: string,
  text: string,
  names: string[],
): Promise<{ text: string; said: string; file?: string }> {
  const cb = editableCodebase();
  let abs = scratchPath(cb, file, text);
  if (!/\.tsx?$/.test(file))
    throw new HttpError(400, `${file} isn't code: definitions are pulled into a .ts or .tsx file.`);
  if (!names.length) return { text, said: "" };
  for (const n of names) {
    const recs = (n.startsWith("#") ? [cb.lookupOne(n)] : cb.lookup(n)).map((h) =>
      cb.store.getDef(h),
    );
    if (!recs.length || recs.some((r) => !r))
      throw new HttpError(404, `There's no ${n} in the codebase.`);
    if (recs.some((r) => r!.kind === "file"))
      throw new HttpError(
        400,
        `${n} is a file kept as written: Edit, on its page, opens it apart.`,
      );
  }
  // JSX goes in a .tsx file: a .ts one becomes one (what it holds reads the same there)
  let moved: string | undefined;
  if (
    file.endsWith(".ts") &&
    names.some((n) =>
      (n.startsWith("#") ? [cb.lookupOne(n)] : cb.lookup(n)).some((h) => cb.store.getDef(h)?.jsx),
    )
  ) {
    const to = abs.replace(/\.ts$/, ".tsx");
    if (fs.existsSync(to))
      throw new HttpError(
        409,
        `It's JSX, which goes in a .tsx file, and ${path.basename(to)} is there already: pull it into that.`,
      );
    fs.rmSync(abs, { force: true });
    abs = to;
    moved = path.basename(to);
  }
  fs.writeFileSync(abs, text);
  // (what the file holds saved as it is: written again with them, so a file of saved definitions keeps them)
  const a = analyzedScratch(cb, abs, text);
  let kept: string[] = [];
  try {
    if (!a.diagnostics.length && !a.errors.length)
      kept = classify(cb, a.defs, a.namespace ?? "")
        .filter((r) => r.status === "identical")
        .map((r) => r.path);
  } catch {
    kept = [];
  }
  const said: string[] = [];
  const log = console.log;
  console.log = (...args: unknown[]) => void said.push(stripVTControlCharacters(format(...args)));
  try {
    edit(cb, [...new Set([...kept, ...names])], abs, false);
  } catch (e) {
    throw new HttpError(400, (e as Error).message);
  } finally {
    console.log = log;
  }
  // (said of the file by its name: where it is, the page says)
  const told = said.join("\n").replaceAll(abs, path.basename(abs)).trim();
  return { text: fs.readFileSync(abs, "utf8"), said: told, ...(moved ? { file: moved } : {}) };
}

uses

used by