What you'll learn
A professional CLI has a stable programmatic core, a thin command adapter, predictable exit codes, cross-platform filesystem behavior, complete package metadata, automated tests, and a secure provenance-aware release.
By the end of this lesson, you'll be able to:
- Separate CLI parsing from library behavior
- Design cross-platform UX and exits
- Publish a minimal package
- Automate versioning and trusted release
Core mental model
Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.
| Concept | What it means | Decision rule |
|---|---|---|
| Programmatic API | Reusable functions independent of the terminal | Keep parsing, colors, and process.exit outside the core |
| Exit code | Machine-readable command outcome | Document stable codes and write diagnostics to stderr |
| Package exports | Declared public entry points | Expose only supported APIs and include types where promised |
Professional workflow
Build and verify Node.js programs from the terminal in small, observable steps.
- Define the public CLI package boundary: inputs, outputs, invariants, ownership, and expected failures.
- Design the data or message contract before choosing implementation details.
- Implement the smallest correct path with dependencies passed explicitly.
- Add validation, failure translation, cleanup, and concurrency behavior.
- Verify the boundary with realistic data and at least one adversarial case.
- Measure or observe the behavior before optimizing or extracting abstractions.
Keep the feedback loop short
Guided code lab
Keep the executable adapter thin
The bin maps argv and errors onto a library contract while the core remains directly testable.
#!/usr/bin/env node
import { runCheck } from '../dist/index.js';
try {
const result = await runCheck(process.argv.slice(2), { cwd: process.cwd() });
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
} catch (error) {
process.stderr.write((error instanceof Error ? error.message : String(error)) + '\n');
process.exitCode = error.code === 'INVALID_INPUT' ? 2 : 1;
}Production practice
Contract
The CLI documents arguments, environment, stdin/stdout/stderr, exit codes, filesystem effects, supported Node versions, and public JS/TS exports.
Verification
Test core functions, spawn the real bin on Windows/Linux/macOS paths, verify package contents with npm pack, install the tarball, and run provenance checks.
Operations
Use scoped publish tokens or trusted publishing, 2FA, protected tags, changelogs, deprecation policy, and a vulnerability contact.
Common failure mode
Independent workshop
Build and publish a configuration-auditing Node.js CLI.
Your finished workshop must include:
- Library/CLI separation
- Stable exit codes
- Cross-platform tests
- Minimal package exports
- npm pack inspection
- Automated secure release
Definition of done
Recap & quick check
Key takeaways
- CLI adapters stay thin
- Streams and exits are contracts
- Exports define public surface
- Pack what users need
- Publishing is a security event
Quick check
1. Where should process.exitCode be set?
2. What verifies published contents before release?
3. What belongs on stderr?
Next: Capstone: Production SaaS API