A card-based stepper container for @clack/prompts with back-navigation, async steps, and conditional flow. npmx.dev/package/clack-stepper

Files
README.md

clack-stepper #

A card-based stepper container for @clack/prompts. Like group(), but with back-navigation, async steps, and conditional flow.

Each step renders in a fixed terminal region with a breadcrumb header. Going back (esc/ctrl+c) clears the current card and re-renders the previous step in place.

Install #

npm install clack-stepper

Usage #

import * as p from "@clack/prompts";
import { stepper } from "clack-stepper";

p.intro("Project setup");

const result = await stepper(
  {
    name: {
      label: "Name",
      run: () => p.text({ message: "Project name?" }),
    },
    language: {
      label: "Language",
      run: ({ results }) =>
        p.select({
          message: `Language for ${results.name}?`,
          options: [
            { value: "js", label: "JavaScript" },
            { value: "py", label: "Python" },
          ],
        }),
    },
    framework: {
      label: "Framework",
      when: ({ results }) => results.language === "js",
      run: async ({ results }) => {
        const opts = await fetchFrameworks(results.language);
        return p.select({ message: "Framework?", options: opts });
      },
    },
  },
  {
    onCancel: () => {
      p.cancel("Cancelled.");
      process.exit(0);
    },
  },
);

p.outro("Done!");

API #

stepper(steps, options?) #

Returns a promise that resolves to an object keyed by step name, containing each step's result.

Step definition #

Each key in the steps object defines a step:

Property Type Description
label string Display text for the breadcrumb header.
run (ctx) => Promise<T> The prompt to run. Typically a @clack/prompts call.
when (ctx) => boolean Optional. Step is skipped when this returns false. Re-evaluated on each visit.

The ctx argument contains:

  • results -- accumulated answers from completed steps (partial, typed).
  • io -- I/O streams for forwarding to prompts (see Testing below).

Options #

Property Type Description
onCancel ({ results }) => void Called when the user escapes past the first step.
output Writable Custom output stream for the stepper's chrome. Defaults to process.stdout.
promptIO { input?, output? } I/O streams forwarded to step callbacks via ctx.io.

How it works #

  • esc or ctrl+c on any step goes back to the previous step.
  • esc on the first step triggers onCancel.
  • Steps with when are re-evaluated each time the stepper reaches them, so changing an earlier answer can add or remove later steps.
  • Any async work (fetching data, spinners) can happen inside run before calling a prompt.

Testing #

The stepper's logic can be tested by mocking the run functions directly:

import { stepper } from "clack-stepper";

const result = await stepper(
  {
    name: { label: "Name", run: () => Promise.resolve("my-app") },
    lang: { label: "Lang", run: () => Promise.resolve("js") },
  },
  { output: mockOutput },
);

Return a Symbol from run to simulate cancel/back-navigation.

For integration tests with real @clack/prompts, pass mock streams via promptIO and spread ctx.io into prompt options:

const result = await stepper(
  {
    name: {
      label: "Name",
      run: ({ io }) => p.text({ message: "Name?", ...io }),
    },
  },
  { output: mockOutput, promptIO: { input: mockInput, output: mockPromptOutput } },
);