Releases: clibuilder/clibuilder
Release list
clibuilder@11.1.0
Minor Changes
-
6c03448: Adopt
standard-log@^13.2.0andstandard-log-color@^13.2.0(from^12.1.2/^12.1.1), and bump the devDependencies@repobuddy/jestto^6.0.0and@unional/fixtureto^5.0.0.standard-log@13.2.0is the release that widened@just-func/typesfrom^0.5.0to^0.6.0. The previously-resolved@just-func/types@0.5.1depends ontype-plus@^5.0.0, and a caret on a0.xrange cannot cross that minor, so every consumer of the olderstandard-logdragged a staletype-plus@5.6.0andtersify@3.12.1into its tree regardless of what it pinned itself. Adoptingstandard-log13 removes that stale transitivetype-plus/tersifypair from consumers' resolved trees.No source changes were required:
clibuilder's own build, type-check, and test suite are unaffected.standard-logandstandard-log-color's major version bumps did not change their public API (their own release notes describe unrelated changes: a build-tooling switch and dropped ES5 output), but they are runtimedependenciesofclibuilderwhose types leak into its emitted declarations, and consumers now resolve a new major of a runtime dependency — hence aminorrather thanpatch.
clibuilder@11.0.0
Major Changes
-
d3507e4: Pin
type-plusto8.0.0-beta.10.Published
clibuilder@10.1.0declarestype-plus: ^7.0.0. Consumers that also
depend on the type-plus 8 line —mocktomataandnode-supported-releases
resolve type-plus through clibuilder — end up with two majors of type-plus
(and oftersify) in one tree. This release moves clibuilder onto 8.This is a major for two independent reasons:
- type-plus types leak into clibuilder's emitted declarations.
esm/cli.d.ts,
esm/builder.d.tsandesm/state.d.tsallimport type ... from 'type-plus'
(RequiredPick,UnionOfValues), so consumers compile against type-plus 8
directly and inherit its newpeerDependencies: { typescript: '>= 5.6.0' }.
type-plus 5, 6 and 7 declared no typescript peer at all. - The repo's TypeScript range moves from
^5.0.4to^5.9.3. Independently of
type-plus, that raises the compiler floor for anyone building against these
declarations.
type-plusis pinned exactly rather than caret-ranged.^8.0.0-beta.10resolves
to>=8.0.0-beta.10 <9.0.0-0, admitting every later 8.0.0 prerelease plus
8.0.0and8.1.0. The 8 line is a prerelease line where breaking changes land
between betas — beta.10 to beta.11 changedEqual's signature and removed
isType.f. An exact version makes each bump a reviewable PR instead of something
a lockfile refresh can do silently.Also bumps the
tersifydevDependency to^4, matching the copy type-plus 8 and
assertron@11.6already bring in.engines.nodeis unchanged at>= 20.19,
which already satisfies the>= 20that type-plus 8'sunpartial@^1.0.7
dependency requires. - type-plus types leak into clibuilder's emitted declarations.
clibuilder@10.1.0
Minor Changes
-
c71f536: Report usage errors and exit non-zero.
A clibuilder cli could not fail.
lookupCommandalready built a typed list of everything wrong with
an invocation — unknown option, missing argument, extra arguments, a value of the wrong type — and
builderthrew it away and printed the help message with an exit code of0. A command that knew
it had failed had no way to say so either.- Usage errors are now reported by name (
unknown option --bogus,missing required argument <target>) ahead of the help message, and the cli exits2. - A config that fails its schema exits
1. - A command fails by throwing the new
CliError, which carries anexitCodeand optionalhelp
lines.parse()still resolves rather than rejecting, so the failure is reported instead of
surfacing as an unhandled rejection. - New exports:
CliError,exitCodes(success/error/usage— 0/1/2), andisCliError(). testCommand()returns theexitCodealongsideresultandmessages, so a test can assert a
failure without ending the test run.--helpand--versionare accepted by every command, including sub-commands that declare no
options of their own, and both still exit0.
The exit code is recorded on
process.exitCoderather than applied withprocess.exit(), so
pending stdout writes are not truncated.This changes the behavior of every cli built with clibuilder: an invocation with a typo used to
exit0and now exits2. The TypeScript surface is additive, which is why this is a minor rather
than a major, but a caller downstream of your cli that ignored the exit code will now see it fail. - Usage errors are now reported by name (
-
2f4afde: Add
--formattoplugins list.plugins list --format <toon|text|json>picks how the installed plugins are rendered,
matchingplugins search. The default istoon, following the Agent eXperience
Interface — a cli's plugin list is read by an agent far more often than by a person:plugins[2]: my-cli-plugin,@acme/my-cli-plugin-deploy help[1]: Run `plugins search` to find more plugins on npmNothing installed is stated as the answer rather than left as silence, and it says
installedbecause that is the whole difference from whatsearchreports — nothing
installed here says nothing about what exists on npm:plugins: 0 installed plugins found with keywords: my-cli-plugin help[1]: Run `plugins search` to find plugins to install--format textis the previous human-readable prose, unchanged, including its separate
wording for none, one, and several.toonandjsonreport one shape whatever the
count.--format jsonemits{ "plugins": [...] }alone, with no help line, so it
survives a pipe intojq. The command still returns the plugin names to its caller. -
4925f20: Match
plugins searchkeywords disjunctively, and add--formatto its output.A cli declaring several keywords used to find only packages carrying all of them, so
a plugin tagged with one of the cli's keywords was never listed. Each keyword is now
searched on its own and the results are unioned, deduped, in keyword order.plugins search --format <toon|text|json>picks how that result is rendered. The default
istoon, following the Agent eXperience Interface — a cli's plugin search is read by an
agent far more often than by a person, and toon is the cheaper read for one:packages[2]: pkg-x,pkg-y help[1]: Run `plugins list` to see which of them are installed--format textis the previous human-readable prose, unchanged.--format jsonemits
{ "packages": [...] }alone, with no help line, so it survives a pipe intojq.--fields keywordsadds which of the cli's keywords found each package — the question
disjunctive matching makes worth asking, since one over-broad keyword can now pull in a
package that is not a plugin at all. It is off by default: a cli with a single keyword
would spend the column on a value every row repeats.packages[3]{name,keywords}: my-cli-plugin-alpha,my-cli-plugin shared-plugin,my-cli-plugin unional unional-tool,unional
Patch Changes
-
033a38a: Reject an option value the option's
typedoes not accept.A value that failed the option's schema used to be dropped and replaced by the option's
default, so--format yamlquietly renderedtoonand the caller had no way to tell.
It is now reported as a usage error and the cli exits with2, the same as an unknown
option. An enum lists what it would have accepted, so the invocation can be fixed in one
step:error: invalid value for option --format: expected one of: toon, text, json, received "yaml"This applies to every option whose
typeclibuilder does not convert itself —z.enum
above all. Booleans and numbers already reported their own conversion errors and are
unchanged.
clibuilder@9.2.0
Minor Changes
-
8703dcc: Add a typed plugin registry for content contributions and cross-plugin capabilities.
-
d709f62: Coerce argument and untyped-option values to match their declared types.
Both fixes close a gap where the type system and the runtime disagreed about what
run(args)
receives.- Positional arguments are now coerced through the same conversion path options already use.
type: z.number()yields anumber,type: z.array(z.number())yieldsnumber[], and a value
that fails its schema is a usage error instead of being silently dropped. An array argument is
variadic: it consumes the remaining positionals. Previously every argument arrived as a raw string,
andz.array(z.number())ended upundefined. - An option declared without a
typenow defaults toz.optional(z.boolean())instead of
z.optional(z.string()), so a flag yields a realtruerather than the string'true'.
RunArgshas always inferred this case asboolean | undefined.
Note two behavior changes for code that relied on the old runtime values:
--flag=somevalueon an option with no declaredtypeis now a usage error. Declare
type: z.string()to keep accepting a string value.- An argument declared with a type that has no string conversion (
z.enum,z.literal) now has the
raw argv string handed to its schema, which accepts or rejects it. It is no longer passed through
unvalidated.
- Positional arguments are now coerced through the same conversion path options already use.
-
9b629d0: Config subsystem: jsonc support, a public lookup/load API, and
--show-config.JSONC config (#341)
.jsoncand.jsonc-suffixed rc files are now searched for, and.jsonfiles may contain comments
and trailing commas. Parsing goes throughjsonc-parser, so comment-like sequences inside strings
are left alone —"http://example.com"is no longer at risk of being truncated at the//.The format is now chosen by extension rather than by trying every parser in turn. Extension-less
candidates such as.apprcstill infer their format from content (JSONC, then YAML, then module).
One consequence: a.jsonfile that is not valid JSON now reports a parse error naming the file,
where before it was silently retried as YAML and could load as something unintended.Config lookup/load API (#488)
clibuildernow exports its config resolution, so plugins and tools can ask where a config came
from, not just what it holds:lookupConfig({ cwd }, name)— resolve the origin without reading the fileresolveConfig({ cwd, ui }, name)— the config plus its provenanceloadConfig,readConfigFile,getConfigFilenames,getConfigFormat,describeConfigSource
A resolved
sourceis{ type: 'file', path, format },{ type: 'package.json', path, property },
or{ type: 'none' }. These are read-only; writing config back to disk is not supported yet.--show-config(#317)A cli declaring
confignow accepts--show-config, which prints the resolved config and where it
was loaded from — the matching file path, thepackage.jsonproperty, or that nothing was found.
Clis without config do not advertise the option.
Patch Changes
-
2efd5dc: Fix optional and variadic arguments in the help output.
The
Arguments:section tested theisOptionalmethod instead of calling it, so a typed required
argument rendered as[name]and an optional one as<name>, exactly backwards. Arguments now use
the same notation as options:<name>when required,[name]when optional, with the declared type
(=string,=number,=boolean) and a...variadic marker.Arguments: <src=string> the source [host=string] the host <files=string...> the files -
3fda89f: Resolve command aliases such as
plugins lswhen parsing CLI input.
clibuilder@10.0.0
Major Changes
-
7b17641: Require Node.js >= 20.19 and update
find-installed-packagesto^4.0.0.engines.nodemoves from>= 18to>= 20.19, matching what
find-installed-packages@4supports. Node 18 and 19 reached end of life, so
there is no supported runtime left below that floor.v4 also changes how plugins are discovered. It walks the declared dependency
graph and asks the runtime's module resolver where each package lives, instead
of scanningnode_modules. That fixes discovery under pnpm, Yarn Plug'n'Play,
and workspaces where the tree is hoisted to the repo root. The trade-off: only
declared dependencies are reported, so a plugin sitting innode_modulesthat
nothing depends on is no longer found — add it to yourpackage.json
dependencies if you were relying on that.
clibuilder@9.1.0
Minor Changes
-
e8bb823: Resolve the config file with a single upward directory walk instead of one walk per candidate name.
getConfigFilenames()produces ~28 candidate names, and each one used to be searched with its own
findUpSync()call — 28 walks fromcwdto the filesystem root to answer one question. Each ancestor
directory is now read once and matched against every candidate. From a directory 13 levels below the
config, the lookup drops from 165statcalls to 13readdircalls (~0.51 ms to ~0.15 ms); when no
config exists, from 580statcalls to 40.Behavior change — nearest config now wins. Because candidates used to be searched one full walk at
a time, a lower-priority name in a nearer directory lost to a higher-priority name in a farther
one: afoorc.jsonin a grandparent beat afoo.yamlin the current directory. Resolution is now
directory-first — the nearest ancestor containing any candidate wins, and the candidate order only
breaks ties within that directory. This matches how other config loaders behave. Projects with config
files at more than one level of the tree may now load a different file.find-upis no longer a dependency; the walk used forpackage.jsonlookup moved to an internal
helper as well. -
deac99f: Add
clibuilder/compile-cache, an opt-in helper to turn on node's V8 compile cache from your cli's bin script.import { enableCompileCache } from "clibuilder/compile-cache"; enableCompileCache(); const { cli } = await import("clibuilder");
Measured on the
test-appsfixtures (node 24, median of 25 warm runs), startup drops from 86ms to 81ms for theESMbuild and from 43ms to 37ms for theCJSbuild.The helper never throws, and does nothing on runtimes without the API (node < 22.1,
bun,deno).NODE_COMPILE_CACHEtakes precedence when the user sets it.
Patch Changes
-
10cea5a: Load
find-installed-packagesandsearch-packageslazily.They are only needed by
plugins listandplugins search, butcommands.tssits on the startup path of every CLI invocation. They are now pulled in with a dynamicimport()at call time, cutting roughly 20ms off startup. -
f462467: Point the package's
homepage,bugs, andrepositoryURLs atclibuilder/clibuilder. They still namedunional/clibuilder, which the repository moved away from — npm rendered the old owner on the package page and linked issues to a redirect. -
4cc248e: Update
tmpandjs-yaml -
28ae843: Update dependencies (find-installed-packages, tmp, ts-jest, npm-run-all2, rimraf) and modernize internals: replace
.then()chains with async/await inbuilder.tsandplugins.ts, bumpengines.nodeto reflect actual ESM support. -
3b0870c: Update
search-packagesto^2.2.0.It replaces its
npm searchshell-out with a directfetch()against the registry, soplugins searchno longer requiresnpmonPATH— it now works for standalone CLI installs and under bun/deno — and drops the process-spawn cost from the command. The APIclibuilderuses is unchanged.
clibuilder@9.0.0
Major Changes
- de5c0b5: Drop support of global config.
It is not really a use case to begin with,
and it is causing problems in CI as CI nowadays not settingUSERPROFILEvar as assumed.
clibuilder@8.0.17
Patch Changes
- 38920e0: Update readme
clibuilder@8.0.16
Patch Changes
- 0ead505: Default keywords to the name of the cli if it is plugin cli and keywords are not specified.
clibuilder@8.0.14
Patch Changes
- 9acc311: Fix running parent command withou
run()will show help.