Timezone Tetris in Panama City
Last month I was pair‑programming with Carla, a junior dev clocked in from Málaga, when our React codebase needed yet another boilerplate component. Typing the same props scaffold for the third time, I joked, “We could write a CLI to spit this out faster than our internet can lag.” Forty‑five minutes later—between thunderstorms rolling over Panama City—we had a working create‑expat‑component
command, built on oclif 4.21.0npm. That late‑night sprint reminded me how a tiny command‑line helper can erase hours of repetitive UI drudgery.
Why a Custom CLI Matters in 2025
Component libraries, Storybook setups, and Vite configs multiply the steps a newcomer must memorize. A bespoke CLI funnels tribal knowledge into one ergonomic npx
call, leveling the field for boot‑camp grads and remote contractors alike. oclif, the Open CLI Framework maintained by Salesforce, abstracts flag parsing, plugin loading, and even auto‑update logic, so you can stay focused on generating gorgeous React code instead of parsing process.argv
oclif.github.io.
Toolbelt at a Glance
Tool / Concept | One‑liner purpose |
---|---|
oclif 4.21 | Framework for Node/TypeScript CLIs. |
ts‑node | Run TypeScript without pre‑compile. |
chalk | Terminal string styling. |
CLI Command | What it does |
---|---|
npm i -g oclif | Installs oclif generator globally. |
oclif generate mycli | Scaffolds a new CLI project. |
npm run dev | Runs CLI in watch mode with ts‑node. |
npm pack && npm i -g *.tgz | Test install locally. |
Concept Primer — Plain English
- Command: a file exporting a
run()
method; becomesmycli hello
. - Flags & Args: auto‑parsed with yargs‑like syntax—but typed.
- Plugins: separate npm packages that extend your CLI without touching core.
- Hooks: lifecycle events (
init
,command_not_found
) to inject cross‑cutting concerns.oclif.github.io
Step‑by‑Step Walkthrough
1 — Scaffold
bashCopyEditnpm i -g oclif
oclif generate react-cli # prompts for TypeScript, npm scope, etc.
cd react-cli && npm i
The generator outputs a /src/commands
folder with a sample hello.ts
.
2 — Create a component
Command
tsCopyEdit// src/commands/component.ts
import { Args, Command, Flags } from '@oclif/core';
import { writeFileSync, mkdirSync } from 'fs';
import * as path from 'path';
import chalk from 'chalk';
export default class Component extends Command {
static description = 'Generate a typed React component';
static args = {
name: Args.string({ required: true, description: 'Component name' }),
};
static flags = {
style: Flags.boolean({ char: 's', description: 'Add CSS module' }),
};
async run() {
const { args, flags } = await this.parse(Component);
const dir = path.resolve('src/components', args.name);
mkdirSync(dir, { recursive: true });
const jsx = `import React from 'react';
${flags.style ? `import styles from './${args.name}.module.css';\n` : ''}
export function ${args.name}() {
return <div${flags.style ? ' className={styles.root}' : ''}>${args.name}</div>;
}
`;
writeFileSync(path.join(dir, `${args.name}.tsx`), jsx);
this.log(chalk.green(`✓ Created ${args.name}.tsx`));
}
}
Line by line
Args.string
auto‑generatesmycli component Button
.Flags.boolean
yields-s
to optionally emit a CSS module.mkdirSync()
ensures nested folders; no shelling out.chalk.green
provides a visual success cue.
3 — Run in Dev Mode
bashCopyEditnpm run dev component Button -s
ts-node-dev
watches for file changes—perfect for rapid tweaks while your teammate in Brazil reviews the diff.
4 — Bundle & Publish
bashCopyEditnpm version minor
npm run build # oclif compiles + ts-node-to-js
npm publish --access public
Now anyone can bootstrap components with npx @your-scope/react-cli component Card -s
.
Common Pitfalls & Fixes
- “Cannot find module ts‑node/register”
Happens when local vs. global dependencies mismatch. Fix: addts-node
todevDependencies
even if you installed it globally. - Flag Parsing Collisions
Commands sharing-s
across plugins can clash. Fix: leverage oclif’s//oclif.manifest.json
to scope flags or switch to verbose--style
. - Big Binary Size on Windows
pkg
builds inflate from unnecessary node modules. Fix: list extraneous files inoclif.pack.exclude
field.
Remote‑Work Insight Box
Reviewing CLI PRs async is easier than UI code. A teammate in Santo Domingo can run
npm run test
inside Docker—no browser context, no design diff. The repeatability of a CLI shrinks onboarding from days to minutes, especially when your team straddles six time zones.
Performance & Accessibility Checkpoints
- Launch Time: Node CLI cold‑starts add ~150 ms; enable
pjson.oclif.bin
to point directly to compiled JS for prod builds. - Autocomplete: Use
oclif plugin-autocomplete
to generate Zsh/Fish completion scripts—key for power users navigating quickly. - Lighthouse? The CLI never hits the browser, but the React code it spits out should pass Lighthouse a11y audits; bake ARIA defaults into templates.
- Security: Sign your npm package to avoid supply‑chain spoofing; GitHub Actions can run
npm audit
on every push. - Cross‑OS Testing: GitHub CI matrix on
ubuntu-latest
,macos-latest
,windows-latest
guarantees your CLI works from Costa Rica cafés to corporate laptops in New York.
Choosing the Right Distribution Strategy
Strategy | Pros | Cons |
---|---|---|
npm Publish | Instant npx usage; semver tags. | Users need Node installed. |
Standalone Tarball | Single binary; no Node runtime. | Larger upload; tricky native deps. |
Homebrew Tap | Mac devs get brew install . | Extra maintenance. |
For internal teams, npm plus a private registry often wins; external dev‑tooling audiences may appreciate a one‑shot binary.
Wrap‑Up: Key Takeaways
- React workflows benefit from small, repeatable CLIs—erase boilerplate, enforce conventions.
- oclif 4.21 brings typed flags, plugin architecture, and init generators that cut setup to five minutes.
- Treat each command like a micro‑feature: design flags, write unit tests, document in
README.md
. - Guard against dependency drift, flag collisions, and bloated binaries.
- A well‑tuned CLI is the ultimate remote‑team accelerator—scripts don’t sleep when you log off.
Have improvements or stories from your own custom React CLI? Drop a comment below and let’s keep optimising across continents.