runPatch

tcmts.cli.run.runPatch · #vyy3njer · term · runs on the server

uses tcmts.cli.main.execute, which uses tcmts.cli.help.usageError, which uses tcmts.cli.help.usageOf, which uses tcmts.cli.style.bold, which uses node:util

Runs tcm patch.

import { routePatch } from "#codebase/tcmts/cli/commands";
import { execute, patch, patchImportsOf, patchMany } from "#codebase/tcmts/cli/main";
import type { PatchEdit, PatchImport } from "#codebase/tcmts/cli/main";
import type { Context } from "#codebase/tcmts/cli/run";
import { dim } from "#codebase/tcmts/cli/style";
import { UsageError } from "#codebase/tcmts/cli/ui";
import { parse } from "@bomb.sh/router";

export function runPatch({ cb, cmd, rest }: Context): number {
  const { model } = execute(cmd, parse(routePatch(), { argv: rest }));
  // several definitions, saved together
  if (model.patches !== undefined) {
    if (model.name) throw new UsageError("tcm patch takes a <name> or --patches, not both");
    let patches: { name: string; edits: PatchEdit[]; all?: boolean }[];
    try {
      patches = (
        JSON.parse(model.patches) as {
          name?: unknown;
          edits?: { old?: unknown; new?: unknown }[];
          all?: unknown;
          imports?: unknown;
        }[]
      ).map((p, i) => ({
        name: String(p.name ?? ""),
        edits: (p.edits ?? []).map((e) => ({
          old: String(e.old ?? ""),
          new: String(e.new ?? ""),
        })),
        all: p.all === true,
        imports: patchImportsOf(p.imports, `--patches[${i}].imports`),
      }));
    } catch (e) {
      if (e instanceof UsageError) throw e;
      throw new UsageError(`--patches isn't JSON: ${(e as Error).message}`);
    }
    return patchMany(cb, patches) ? 0 : 1;
  }
  if (!model.name)
    throw new UsageError(
      'usage: tcm patch <name> --old <text> --new <text> (or --edits \'[{"old": "…", "new": "…"}]\')',
    );
  let edits: PatchEdit[] = [];
  if (model.edits !== undefined) {
    try {
      edits = (JSON.parse(model.edits) as PatchEdit[]).map((e) => ({
        old: String(e.old ?? ""),
        new: String(e.new ?? ""),
      }));
    } catch (e) {
      throw new UsageError(`--edits isn't JSON: ${(e as Error).message}`);
    }
  }
  if (model.old !== undefined) edits.push({ old: model.old, new: model.new ?? "" });
  if (!edits.length) throw new UsageError("tcm patch needs --old and --new, or --edits");
  let imports: PatchImport[] = [];
  try {
    imports = patchImportsOf(
      model.imports === undefined ? undefined : JSON.parse(model.imports),
      "--imports",
    );
  } catch (e) {
    if (e instanceof UsageError) throw e;
    throw new UsageError(`--imports isn't JSON: ${(e as Error).message}`);
  }
  // --old and --new are as written: a `\n` in them (as a shell's '…' leaves it) is a backslash and an n, as it is
  // in code ("a\nb"); one that looks meant as a line break is said
  const escape = /\\[nt]/;
  const literally =
    "(--old and --new are taken as written: a \\n is a backslash and an n, not a line break; write a line break as one, inside the quotes or as $'…\\n…' in a shell, or give --edits as JSON)";
  if (model.new !== undefined && escape.test(model.new) && !escape.test(model.old ?? ""))
    console.log(
      dim(
        "note: a \\n or \\t in --new is kept as written, a backslash and a letter (a line break is written as one, inside the quotes or as $'…\\n…' in a shell, or in --edits JSON)",
      ),
    );
  try {
    return patch(cb, model.name, edits, model.all, imports) ? 0 : 1;
  } catch (e) {
    if (e instanceof UsageError && escape.test(model.old ?? "") && /not found/.test(e.message))
      throw new UsageError(`${e.message}\n${literally}`);
    throw e;
  }
}

uses

used by