usageError

tcmts.cli.help.usageError · #3df7zopo · term · runs on the server

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

A parse failure, as tcm reports it: what's wrong (with the option meant, for one that's misspelt), and what the command takes, as its help says: its usage, arguments and options. --branch given to a command that changes things is refused, saying which commands read another branch.

import { routes } from "#codebase/tcmts/cli/commands";
import { childrenOf, distance, lookup, paramsOf, readsBranches, usageOf } from "#codebase/tcmts/cli/help";
import { dim } from "#codebase/tcmts/cli/style";

export function usageError(
  path: string[],
  result: { code: string; issues?: { message: string; path?: unknown[] }[] },
): string {
  const where = `tcm ${path.join(" ")}`.trim();
  const unexpected = (result.issues ?? []).flatMap(
    (i) => /^unexpected "(.*)"$/.exec(i.message)?.[1] ?? [],
  );
  // `tcm update --branch <b>`: reading another branch is for the commands that only read
  if (path.length && !readsBranches(path) && unexpected.some((u) => /^--branch(=|$)/.test(u))) {
    const readers = Object.keys(routes()).filter((c) => readsBranches([c]));
    return `${where}: --branch reads another branch, changing nothing, so only the commands that just read take it (${readers.join(", ")}); ${where} would change the codebase. To work on that branch, \`tcm switch <branch>\` (a session moves with tcm_cli ["switch", "<branch>"])`;
  }
  const options = path.length
    ? paramsOf(lookup(path))
        .filter((p) => p.cli.syntax!.type === "option")
        .map((p) => p.name)
    : [];
  const problems =
    result.code === "method-not-allowed"
      ? [
          `${where} needs a command: ${childrenOf(lookup(path))
            .map((c) => c.name)
            .join(", ")}`,
        ]
      : (result.issues ?? []).map((i) => {
          const param = Array.isArray(i.path) && i.path.length ? String(i.path.at(-1)) : undefined;
          if (param) return `${param}: ${i.message}`;
          const given = /^unexpected "(.*)"$/.exec(i.message)?.[1];
          if (given === undefined) return i.message;
          // a misspelt option: the one meant (few letters off: a command has a few options, and `--prot` is one
          // swap from `--port`)
          const option = /^--(?:no-)?([^=]+)/.exec(given)?.[1];
          const scored = option
            ? [...options, "help"].map((o) => [o, distance(option, o)] as const)
            : [];
          const best = Math.min(...scored.map(([, d]) => d));
          const meant =
            option && best <= Math.max(1, Math.floor(option.length / 2))
              ? scored.filter(([, d]) => d === best).map(([o]) => o)
              : [];
          return `unexpected '${given}'${meant.length ? ` (did you mean ${meant.map((m) => `--${m}`).join(" or ")}?)` : ""}`;
        });
  // `tcm run lib.main --verbose`: the flag is for the definition
  const flag = path[0] === "run" && unexpected.some((u) => u.startsWith("-"));
  return `${where}: ${problems.join("; ")}${flag ? " (arguments for the definition that start with - go after `--`)" : ""}${path.length ? `\n\n${usageOf(path).join("\n")}` : `\n${dim(`see \`${where} --help\``)}`}`;
}

uses

used by