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
Summary
@oclif/corecurrently 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, globalfetch,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
@oclif/coreis installed everywhere; reducing its dependency tree benefits the entire ecosystem downstream.Current State
engines.node>=18.0.0lts/*,24,latest(Ubuntu + Windows)Runtime dependencies and native candidates
ansisutil.styleText()ansi-escapesutil.styleText()(partial)wrap-ansiutil.styleText()(partial)supports-colorprocess.stdout.isTTY+NO_COLOR/FORCE_COLORdebugutil.debuglog()/console.debug()minimatchfs.glob()tinyglobbyfs.glob()is-wslprocess.env.WSL_DISTRO_NAMEcheckget-package-typepackage.jsontypeinspectionclean-stackPackages intentionally kept (no good native equivalent, low overhead):
ejs,semver,lilconfig,cli-spinners,string-width,widest-line,wordwrap,indent-string.Proposed Plan
Phase 0 — Zero-cost cleanups (no Node bump, any minor release)
These can be done today without raising the minimum Node version:
debugwithutil.debuglog().supports-colorwithprocess.stdout.isTTY+NO_COLOR/FORCE_COLORhandling.is-wslwith aprocess.env.WSL_DISTRO_NAMEcheck.clean-stackandget-package-typeas tiny internal helpers.Phase 1 — Drop Node 18, adopt
util.styleText(next major, e.g. v5)engines.nodeto>=20.0.0(or>=22.0.0if timing aligns — see note onstyleTextstabilization).ansis+ansi-escapes+wrap-ansiwithutil.styleText().Phase 2 — Drop Node 20, adopt
fs.glob(subsequent major, e.g. v6)engines.nodeto>=22.0.0.minimatch+tinyglobbywithfs.glob()/fs.promises.glob().Expected Impact
Benefits:
util.styleText,fs.glob).Risks & Mitigations
styleTextcolor-depth handling,fs.globpattern semantics vs.minimatch). → Add compatibility tests before swapping; keep a thin internal wrapper so behavior can be tuned in one place.util.styleTextis 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
styleTextandfs.globin a single major bump?minimatch/ansisbeing present as transitive deps that would need coordination?References
util.styleText()— https://nodejs.org/api/util.html#utilstyletextformat-text-optionsfs.glob()— https://nodejs.org/api/fs.html#fspromisesglobpattern-optionsutil.debuglog()— https://nodejs.org/api/util.html#utildebuglogsection-callback