Vercel Logo

Wire the Sandbox Workflow into the CLI

We have two pieces. A lifecycle function that does the work, and a CLI that knows when to do it. Time to connect them.

This is one of those lessons where the code change is small but the moment is big. The first time you run pnpm review <url> and watch it boot a Sandbox, clone the repo, and print back a structured result is when the project stops feeling like a tutorial and starts feeling like a tool.

Outcome

Replace the console.log placeholder in the action callback with a real call to runSandboxLifecycle, and print the returned result.

Fast Track

  1. Import runSandboxLifecycle from ./sandbox-lifecycle.
  2. Call it inside the validated action callback.
  3. Log the structured result.

Hands-on exercise

Open src/cli.ts and add the import:

import { Command } from 'commander';
import { runSandboxLifecycle } from './sandbox-lifecycle';
 
function isValidGitHubRepoUrl(input: string): boolean {
  return /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/?$/.test(input);
}
 
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) => {
    if (!isValidGitHubRepoUrl(repoUrl)) {
      console.error(`Invalid GitHub repository URL: ${repoUrl}`);
      console.error('Expected format: https://github.com/<owner>/<repo>');
      return;
    }
 
    console.log(`Reviewing ${repoUrl}...`);
    const result = await runSandboxLifecycle(repoUrl);
 
    console.log(`Sandbox: ${result.sandboxId}`);
    console.log(`Clone exit code: ${result.cloneExitCode}`);
    console.log(`Files:\n${result.files}`);
    console.log(`README preview:\n${result.readmePreview}`);
  });
 
program.parse();

Notice we're not catching the error from runSandboxLifecycle yet. If the clone fails, the function throws, and we'll see an unhandled rejection in the terminal. We'll fix that in the next lesson when we add real exit-code handling. For now, the happy path is the goal.

Also: since runSandboxLifecycle is no longer being called from the bottom of src/sandbox-lifecycle.ts, you can delete the temporary main() test caller we added in lesson 1.4. The CLI is the real caller now.

Troubleshooting: import path

TypeScript imports omit the .ts extension (./sandbox-lifecycle, not ./sandbox-lifecycle.ts). If you see "cannot find module", drop the extension.

Troubleshooting: output too noisy

Logging the full ls -la output is verbose. That's fine for now since we're confirming the wire is connected. We'll trim what gets printed when we add the proper reporter in Chapter 5.

Try It

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

Expected output:

Reviewing https://github.com/vercel/examples...
Sandbox: sbx_7N2k4A...
Clone exit code: 0
Files:
total 32
drwxr-xr-x  ... README.md
drwxr-xr-x  ... examples
README preview:
# Vercel Examples

This repository contains...

That's the whole first half of the course in one CLI invocation. Validation, Sandbox boot, clone, file inspection, clean shutdown.

Commit

git add src/cli.ts src/sandbox-lifecycle.ts
git commit -m "feat(cli): wire sandbox lifecycle into the review command"

Done-When

  • CLI imports runSandboxLifecycle from ./sandbox-lifecycle
  • Valid URL triggers a real Sandbox run
  • Invalid URL still exits early without booting a Sandbox
  • Result fields (sandboxId, cloneExitCode, files, readmePreview) all print

Solution

src/cli.ts
import { Command } from 'commander';
import { runSandboxLifecycle } from './sandbox-lifecycle';
 
function isValidGitHubRepoUrl(input: string): boolean {
  return /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/?$/.test(input);
}
 
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) => {
    if (!isValidGitHubRepoUrl(repoUrl)) {
      console.error(`Invalid GitHub repository URL: ${repoUrl}`);
      console.error('Expected format: https://github.com/<owner>/<repo>');
      return;
    }
 
    console.log(`Reviewing ${repoUrl}...`);
    const result = await runSandboxLifecycle(repoUrl);
 
    console.log(`Sandbox: ${result.sandboxId}`);
    console.log(`Clone exit code: ${result.cloneExitCode}`);
    console.log(`Files:\n${result.files}`);
    console.log(`README preview:\n${result.readmePreview}`);
  });
 
program.parse();

Was this helpful?

supported.