Phase 7 · Professional PortfolioModule 49~100 min read

Portfolio Project: Publish a Node.js CLI Package

Design, test, document, version, package, and publish a cross-platform CLI with a stable programmatic API and automated release workflow.

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.

ConceptWhat it meansDecision rule
Programmatic APIReusable functions independent of the terminalKeep parsing, colors, and process.exit outside the core
Exit codeMachine-readable command outcomeDocument stable codes and write diagnostics to stderr
Package exportsDeclared public entry pointsExpose only supported APIs and include types where promised

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Define the public CLI package boundary: inputs, outputs, invariants, ownership, and expected failures.
  2. Design the data or message contract before choosing implementation details.
  3. Implement the smallest correct path with dependencies passed explicitly.
  4. Add validation, failure translation, cleanup, and concurrency behavior.
  5. Verify the boundary with realistic data and at least one adversarial case.
  6. Measure or observe the behavior before optimizing or extracting abstractions.

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Keep the executable adapter thin

The bin maps argv and errors onto a library contract while the core remains directly testable.

bin/master-check.js
#!/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

Testing only imported functions misses executable permissions, shebangs, package bin mapping, argument quoting, and packed-file omissions.

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

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

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