decopin-cli

↑ ↓ to navigate · Enter to open · Esc to close

Inheritednot-found.tsx and notFound()

not-found.tsx and notFound()

One view for an unknown subcommand and for a value the command could not find, with the suggestion worked out for you.

notFound() can be called from anywhere in a command. It renders the nearest not-found.tsx and picks the exit code, and if you hand it the valid values it works out the suggestion for you:

// app/user/show/data.tsx
import { notFound, type CmdProps } from 'decopin-cli';

const USERS = ['alice', 'bob', 'carol'];

export default function Data({ args }: CmdProps<'user/show'>) {
  if (!USERS.includes(args.name)) {
    notFound({ what: 'user', requested: args.name, available: USERS });
  }
  return { name: args.name };
}
$ ./dist/index.js user show alcie
✖ no such user: alcie
Did you mean: alice?

not-found.tsx is inherited from parent directories the way error.tsx is, so a subcommand can have its own wording. One view serves both cases, an unknown command and a notFound() call, and tells them apart with what. Under --json the failure is structured with code: "not-found".

// app/not-found.tsx
import { Danger, DidYouMean, type NotFoundProps } from 'decopin-cli';

export default function NotFound({
  what,
  requested,
  suggestion,
  available,
  program,
}: NotFoundProps) {
  const isCommand = what === 'command';
  return (
    <>
      <Danger>
        no such {what}: {requested}
      </Danger>
      <DidYouMean
        requested={requested}
        from={
          isCommand ? available.map((name) => `${program} ${name}`) : available
        }
        suggestion={
          suggestion === undefined || !isCommand
            ? suggestion
            : `${program} ${suggestion}`
        }
        label={isCommand ? 'available commands' : `available ${what}s`}
      />
    </>
  );
}

At the root, not-found.tsx is also the view for a command that does not exist at all (cli usre list), which is why it is listed under both inherited and root-only files.

Using the suggestion machinery yourself

The suggestion machinery is exported rather than kept inside the framework, so you can use it for your own values:

import { closest, DidYouMean, Line, Text } from 'decopin-cli';

const REGIONS = ['us-east-1', 'eu-west-1'];

export default function Command() {
  const asked = 'us-east-2';
  return (
    <>
      <Line>
        <Text dim>closest: {closest(asked, REGIONS)}</Text>
      </Line>
      <DidYouMean requested={asked} from={REGIONS} label="known regions" />
    </>
  );
}