Vercel Logo

Scaffold the repo-review CLI

A reusable function is great. Nobody's typing pnpm tsx src/sandbox-lifecycle.ts https://github.com/... every day, though.

We need a command-line surface. Something a person can run from a terminal, something a CI job can call, something with a name. We'll use commander because it handles the boring parts (parsing args, generating help text, exit behavior) and we'd rather not.

Outcome

Create src/cli.ts with a review <repoUrl> command using commander. The command parses its argument and prints what it received. No Sandbox work yet.

Fast Track

  1. Add commander to the project.
  2. Create src/cli.ts and import Command.
  3. Register a review <repoUrl> command whose action logs the URL.

Hands-on exercise

commander is already in the starter's package.json, so no install needed. If you were spinning this up from scratch, you'd run:

pnpm add commander

Create src/cli.ts:

import { Command } from 'commander';
 
const program = new Command();
 
program
  .name('repo-review')
  .description('Clone and review a GitHub repository in a Sandbox')
  .version('0.1.0');
 
program
  .command('review <repoUrl>')
  .description('Run a Sandbox review against a GitHub repository URL')
  .action(async (repoUrl: string) => {
    console.log(`Would review: ${repoUrl}`);
  });
 
await program.parseAsync();

That's the whole skeleton. The action callback is where the Sandbox lifecycle will live in lesson 2.3, but right now we're just confirming the plumbing works. If you run it with a URL and see the URL echoed back, the CLI surface is in place.

Add a script to your package.json so we don't have to type the long form every time:

package.json
{
  "scripts": {
    "review": "tsx src/cli.ts review"
  }
}

Now pnpm review <url> runs your CLI.

Troubleshooting: command not found

If pnpm review reports "command not found", check that the script is in your package.json and that tsx is installed (pnpm add -D tsx if not). The commander part of "command not found" is misleading; the error usually comes from missing scripts, not missing commander.

Troubleshooting: help text empty

program.parseAsync() reads from process.argv and waits for async action handlers. If you run pnpm review with no args, commander prints "missing required argument 'repoUrl'", not your description. That's expected behavior, not a bug.

Try It

pnpm review https://github.com/vercel/examples

Expected output:

Would review: https://github.com/vercel/examples

And without an argument, you should see commander's built-in error:

pnpm review
error: missing required argument 'repoUrl'

Both behaviors are good. The first proves the action runs; the second proves commander is enforcing the argument shape for us.

Commit

git add src/cli.ts package.json
git commit -m "feat(cli): scaffold review command with commander"

Done-When

  • commander is installed
  • src/cli.ts registers a review <repoUrl> command
  • pnpm review <url> echoes the URL back
  • pnpm review (no args) shows commander's missing-argument error

Solution

src/cli.ts
import { Command } from 'commander';
 
const program = new Command();
 
program
  .name('repo-review')
  .description('Clone and review a GitHub repository in a Sandbox')
  .version('0.1.0');
 
program
  .command('review <repoUrl>')
  .description('Run a Sandbox review against a GitHub repository URL')
  .action(async (repoUrl: string) => {
    console.log(`Would review: ${repoUrl}`);
  });
 
await program.parseAsync();
package.json (scripts section)
{
  "scripts": {
    "review": "tsx src/cli.ts review"
  }
}

Was this helpful?

supported.