implementing-cli-patterns
CLI User Experience Patterns
Overview
Implement consistent, user-friendly CLI interactions using the project's console module, progress tracking, and reporter patterns. All CLI output should go through src/cli/console.ts rather than raw console.log.
When to Use
- Implementing command output formatting
- Adding progress indicators for operations
- Creating interactive user prompts
- Formatting error messages for the terminal
- Designing reporter output for deploy/diff results
- Not for: Business logic, GraphQL operations, or service layer code
CLI Stack
| Tool | Purpose | Location |
|---|---|---|
| chalk | Colored terminal output | Via src/cli/console.ts |
| ora | Spinner/progress indicators | src/cli/progress.ts |
| @inquirer/prompts | Interactive user prompts | Commands layer |
| tslog | Structured logging | Internal logging |
Console Module
Located in src/cli/console.ts. Always use this instead of raw console.log:
| Method | Color | Prefix | Use For |
|---|---|---|---|
error() |
Red | x | Error messages |
success() |
Green | check | Success confirmations |
warning() |
Yellow | warn | Warnings, degraded results |
hint() |
Cyan | bulb | Actionable suggestions |
important() |
Bold | - | Key information |
info() |
Blue | i | Informational messages |
code() |
Gray bg | - | Code/command snippets |
Import: import { console } from '@/cli/console';
Progress Indicators
- Single operations: Use
oraspinner with.start(),.succeed(),.fail() - Bulk operations: Use
BulkOperationProgressfromsrc/cli/progress.tsfor progress bars with success/failure tracking
Interactive Prompts
Available prompt types from @inquirer/prompts:
| Prompt | Import | Use For |
|---|---|---|
confirm |
@inquirer/prompts |
Yes/no decisions, destructive action guards |
select |
@inquirer/prompts |
Single choice from options |
checkbox |
@inquirer/prompts |
Multi-select (e.g., entity selection) |
input |
@inquirer/prompts |
Text input with validation |
password |
@inquirer/prompts |
Masked input for tokens/secrets |
See references/prompt-patterns.md for full examples.
Reporter Patterns
Three main reporters in src/cli/reporters/:
| Reporter | Purpose | Key Output |
|---|---|---|
deployment-reporter |
Deploy results | Table with created/updated/deleted/failed counts |
diff reporter |
Diff results | + create (green), ~ update (blue), - delete (red) |
duplicate reporter |
Validation issues | Duplicate identifiers with locations |
See references/reporter-patterns.md for full implementations.
Error Message Formatting
Two error formatters in the project:
formatError(error: BaseError): General errors with code, context, and suggestionsformatGraphQLError(error: GraphQLError): GraphQL-specific with operation name and path
Pattern: Show error message, then optional code/context in gray, then actionable suggestions via console.hint().
Exit Codes
export const ExitCodes = {
SUCCESS: 0,
GENERAL_ERROR: 1,
VALIDATION_ERROR: 2,
NETWORK_ERROR: 3,
AUTH_ERROR: 4,
CONFLICT_ERROR: 5,
} as const;
Always use named exit codes, never raw numbers.
Best Practices
- Use
src/cli/console.tsfor all output (never rawconsole.log) - Provide progress feedback for any operation over ~1 second
- Include actionable suggestions with every error message
- Confirm destructive operations with
confirmprompt - Support non-interactive mode (skip prompts in CI/headless)
- Use consistent color semantics (green=success, red=error, yellow=warning)
Common Mistakes
| Mistake | Fix |
|---|---|
Using raw console.log |
Import and use console from @/cli/console |
| Missing progress feedback | Add ora spinner for any async operation |
Raw exit codes (process.exit(1)) |
Use ExitCodes.GENERAL_ERROR constants |
| Blocking prompts in CI | Check for --yes flag or CI env var to skip prompts |
| No suggestions on errors | Always add console.hint() with next steps |
| Debug output in production | Use tslog with appropriate log levels |
References
{baseDir}/src/cli/console.ts- Console module{baseDir}/src/cli/progress.ts- Progress tracking{baseDir}/src/cli/reporters/- Reporter implementations- references/reporter-patterns.md - Full reporter code
- references/prompt-patterns.md - Full prompt examples
Related Skills
- Complete entity workflow: See
adding-entity-typesfor CLI integration patterns - Error handling: See
reviewing-typescript-codefor error message standards
Quick Reference Rule
For a condensed quick reference, see .claude/rules/cli-development.md (automatically loaded when editing src/cli/**/*.ts and src/commands/**/*.ts files).
More from saleor/configurator
creating-changesets
Creates changesets for semantic versioning. Use when adding changesets, preparing releases, determining version bumps (patch/minor/major), generating changelog entries, or documenting breaking changes.
66reviewing-typescript-code
Reviews TypeScript code for project-specific quality patterns. Use when writing entities, services, repositories, comparators, formatters, bootstrap methods, deployment stages, diff support, or reviewing PRs with functional patterns. Do NOT use for general TypeScript tutorials or non-Configurator projects.
38designing-zod-schemas
Designs Zod schemas following Zod-first development. Use when creating validation schemas, branded types, discriminated unions, transforms, refinements, or inferring TypeScript types with z.infer.
22understanding-saleor-domain
Explains Saleor e-commerce domain and Configurator business rules. Use when working with entity identification (slug vs name), YAML config structure, entity relationships, deployment pipeline stages, or synchronization logic. Do NOT use for general TypeScript questions or non-Saleor e-commerce platforms.
21analyzing-test-coverage
Creates and analyzes tests using Vitest and MSW patterns. Use when writing unit tests, integration tests, analyzing coverage gaps, setting up MSW handlers, vi.fn mocks, test builders, or debugging test failures. Do NOT use for non-test TypeScript code.
18writing-graphql-operations
Creates GraphQL queries and mutations using gql.tada and urql. Use when writing operations, implementing repositories, updating schema, or testing GraphQL code.
18