editDefinition

tcmts.studio.editDefinition · #fsg275a3 · term · runs on the server

uses node:path

Write the definition name into a scratch file to edit, as tcm edit <name> does: code into scratch.ts (or scratch.tsx for JSX), beside what it holds already; a file kept as written into one of its own, named as it is (or, being edited there already, that one); a conflicted name, its definitions merged, into a file named after it. While a merge waits apart, as merged (tcm edit writes it). The file's name.

import { fileName, isBytes } from "#codebase/tcmts/cli/files";
import { edit, editFiles } from "#codebase/tcmts/cli/main";
import { conflictAt } from "#codebase/tcmts/cli/merge";
import { HttpError } from "#codebase/tcmts/server";
import { editableCodebase, keptFile, mergeWaiting, runTcm, scratchDir, setKeptInto } from "#codebase/tcmts/studio";
import fs from "node:fs";
import path from "node:path";

export async function editDefinition(name: string): Promise<string> {
  const cb = editableCodebase();
  const dir = scratchDir(cb);
  // (with a merge waiting apart, only a command sees the branch as merged; a conflict's file is named after it: tcm
  // edit writes them, and says where)
  if (mergeWaiting(cb) || conflictAt(cb, name)) {
    const out = await runTcm(cb.lay.root, ["edit", name], { branch: true });
    const wrote = /^(?:wrote|added) .+ to (\S+)$/m.exec(out.text)?.[1];
    if (out.code !== 0 || !wrote)
      throw new HttpError(
        400,
        out.text.split("\n").slice(0, 8).join("\n") || `tcm edit ${name} failed`,
      );
    return path.basename(wrote);
  }
  const file = cb
    .lookup(name)
    .map((h) => cb.store.getDef(h))
    .find((r) => r?.kind === "file");
  try {
    if (file) {
      if (isBytes(file))
        throw new HttpError(
          400,
          `${name} isn't text: it's edited as the file it is, outside the studio.`,
        );
      const out = path.join(dir, fileName(name.slice(name.lastIndexOf(".") + 1), file));
      if (fs.existsSync(out) && keptFile(cb, out) === name) return path.basename(out);
      editFiles(cb, [name], out, false);
      setKeptInto(cb, out, name);
      return path.basename(out);
    }
    let wrote = "";
    // (the last file it asks for is the one it writes)
    edit(cb, [name], (jsx) => (wrote = path.join(dir, jsx ? "scratch.tsx" : "scratch.ts")), false);
    return path.basename(wrote);
  } catch (e) {
    if (e instanceof HttpError) throw e;
    throw new HttpError(400, (e as Error).message);
  }
}

uses

used by