Skip to content

Deprecate legacy Node.js versions and replace third-party dependencies with native Node.js alternatives #1627

Description

@gperdomor

Summary

@oclif/core currently supports Node 18 and ships 17 runtime dependencies, several of which now have first-class, native equivalents in modern Node.js (util.styleText, fs.glob, global fetch, process.stdout.isTTY, etc.).

This proposal is to (1) raise the minimum supported Node.js version in step with the LTS schedule, and (2) progressively remove third-party dependencies in favor of native APIs. The goal is fewer transitive dependencies, a smaller install footprint, less supply-chain surface, and a more maintainable core.

Motivation

  • Fewer dependencies = less risk. Every runtime dependency is a potential security advisory, breaking change, and install-time cost. As a foundational CLI framework, @oclif/core is installed everywhere; reducing its dependency tree benefits the entire ecosystem downstream.
  • Native APIs have caught up. Utilities that used to require packages (color styling, globbing, fetch, TTY/color detection) are now built into the Node.js runtime and maintained by the Node core team.
  • Alignment with the Node.js LTS cycle. Node 18 has reached end-of-life; keeping it as the floor blocks adoption of stable native APIs.

Current State

Item Value
engines.node >=18.0.0
Stated policy "Node 18+ is supported. We support the LTS versions of Node."
Runtime dependencies 17
CI Node matrix lts/*, 24, latest (Ubuntu + Windows)

Runtime dependencies and native candidates

Package Purpose Native alternative Min Node
ansis ANSI color output util.styleText() 20 (stable 22+)
ansi-escapes ANSI escape sequences util.styleText() (partial) 20
wrap-ansi ANSI-aware wrapping util.styleText() (partial) 20
supports-color Color capability detection process.stdout.isTTY + NO_COLOR/FORCE_COLOR any
debug Debug logging util.debuglog() / console.debug() any
minimatch Glob pattern matching fs.glob() 22
tinyglobby Glob file matching fs.glob() 22
is-wsl Detect WSL process.env.WSL_DISTRO_NAME check any
get-package-type Detect ESM/CJS package.json type inspection any
clean-stack Stack trace cleaning Small in-repo helper any

Packages intentionally kept (no good native equivalent, low overhead): ejs, semver, lilconfig, cli-spinners, string-width, widest-line, wordwrap, indent-string.

Note: util.styleText was introduced in Node 20 (experimental) and stabilized in Node 22. fs.glob / fs.promises.glob was added in Node 22. These land at the Node version bumps described below.

Proposed Plan

Phase 0 — Zero-cost cleanups (no Node bump, any minor release)

These can be done today without raising the minimum Node version:

  • Replace debug with util.debuglog().
  • Replace supports-color with process.stdout.isTTY + NO_COLOR / FORCE_COLOR handling.
  • Replace is-wsl with a process.env.WSL_DISTRO_NAME check.
  • Inline clean-stack and get-package-type as tiny internal helpers.

Phase 1 — Drop Node 18, adopt util.styleText (next major, e.g. v5)

  • Bump engines.node to >=20.0.0 (or >=22.0.0 if timing aligns — see note on styleText stabilization).
  • Replace ansis + ansi-escapes + wrap-ansi with util.styleText().
  • Update CI matrix to drop Node 18.

Phase 2 — Drop Node 20, adopt fs.glob (subsequent major, e.g. v6)

  • Bump engines.node to >=22.0.0.
  • Replace minimatch + tinyglobby with fs.glob() / fs.promises.glob().
  • Update CI matrix to drop Node 20.

Expected Impact

Milestone Runtime deps Reduction
Today (Node 18+) 17
After Phase 0 + 1 (Node 20+) ~14 ~18%
After Phase 2 (Node 22+) ~12 ~29%

Benefits:

  • Fewer transitive dependencies → fewer advisories and faster installs.
  • Standard, runtime-maintained APIs (util.styleText, fs.glob).
  • Smaller and simpler dependency graph for every downstream oclif CLI.

Risks & Mitigations

  • Breaking change for users on old Node. → Ship behind clear major-version bumps, document a migration guide, and announce the deprecation timeline in advance.
  • Behavioral differences vs. mature libraries (e.g. styleText color-depth handling, fs.glob pattern semantics vs. minimatch). → Add compatibility tests before swapping; keep a thin internal wrapper so behavior can be tuned in one place.
  • util.styleText is only stable in Node 22. → If targeting Node 20 in Phase 1, gate/verify against its experimental status, or align the color swap with the Node 22 floor.

Open Questions

  • Should Phase 1 target Node 20 or jump straight to Node 22 to unlock styleText and fs.glob in a single major bump?
  • What is the desired deprecation lead time (one minor cycle? a full major?) before removing a Node version from the support matrix?
  • Are there downstream plugins that depend on minimatch/ansis being present as transitive deps that would need coordination?

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions