4.0.0
Slimmer ux
module
As described here, we're removing most of the methods in the ux
module. We're simply unable to adequately support the feature set that ux
offers and think that most people would benefit from using dedicated libraries that are better supported.
We are, however, keeping some of the functionality. The new ux
module will contain the following:
Unchanged
colorize
error
exit
action
- will be unchanged from previous version except that the spinner color will be configurable using themes.warn
Renamed
stdout
- rename ofux.log
stderr
- rename ofux.logToStderr
New
colorizeJson
- Apply color theme to arbitrary JSON.
Removed
annotation
anykey
confirm
debug
done
(useux.action.stop()
instead)flush
(still available via@oclif/core/flush
)info
(useux.stdout
instead)progress
prompt
styledHeader
styledJSON
styledObject
table
trace
tree
url
wait
What you'll need to do
You will need to replace everything that ux
was doing with dedicated libraries. Here are a few suggestions:
- For prompts: inquirer
- For progress bars: cli-progress
- For hyperlinks: hyperlink
- For tables: tty-table, cliui
- For trees: object-treeify
- For notifications: node-notifier
- For links: terminal-link
- For rendering react components: ink
Theme-able spinner and JSON output
The color of the spinner can now be customized using the spinner
key in your theme.
The JSON output can also now be customized with these keys:
brace
bracket
colon
comma
key
string
number
boolean
null
Customizable Logger
In the current major version, we exclusively use debug for debug logs. In the next major, we're going to export a Logger
interface that will allow you to provide a custom logger for @oclif/core
to use. This will be useful if you want all the @oclif/core
debug logs to go through your own logger.
The default logger will continue to use debug
under the hood. So if you choose to use the default, you can continue to use the DEBUG
environment variable to access the debug logs in the console. The only breaking change will be that the namespace for all the logs with be prefixed with a root namespace, oclif
.
So if you're used to using DEBUG=config:* my-cli do stuff
, you'll need to start doing this instead: DEBUG=oclif:config:* my-cli do stuff
Interface
export type Logger = {
debug: (formatter: unknown, ...args: unknown[]) => void
error: (formatter: unknown, ...args: unknown[]) => void
info: (formatter: unknown, ...args: unknown[]) => void
trace: (formatter: unknown, ...args: unknown[]) => void
warn: (formatter: unknown, ...args: unknown[]) => void
child: (namespace: string) => Logger
namespace: string
}
Usage
// oclif-logger.ts
import { format } from 'node:util';
import { Interfaces } from '@oclif/core';
import { Logger } from './my-cli-logger';
export const customLogger = (namespace: string): Interfaces.Logger => {
const myLogger = new Logger(namespace);
return {
child: (ns: string, delimiter?: string) => customLogger(`${namespace}${delimiter ?? ':'}${ns}`),
debug: (formatter: unknown, ...args: unknown[]) => myLogger.debug(format(formatter, ...args)),
error: (formatter: unknown, ...args: unknown[]) => myLogger.error(format(formatter, ...args)),
info: (formatter: unknown, ...args: unknown[]) => myLogger.info(format(formatter, ...args)),
trace: (formatter: unknown, ...args: unknown[]) => myLogger.trace(format(formatter, ...args)),
warn: (formatter: unknown, ...args: unknown[]) => myLogger.warn(format(formatter, ...args)),
namespace,
};
};
export const logger = customLogger('sf');
// bin/run.js
#!/usr/bin/env node
async function main() {
const {execute} = await import('@oclif/core');
const { logger } = await import('../dist/oclif-logger.js');
await oclif.execute({
dir: import.meta.url,
loadOptions: {
root: import.meta.dirname,
logger,
},
});
}
await main();
You can also provide the logger to Config
, in the event that you instantiate Config
before calling run
or execute
import {Config, run} from '@oclif/core'
const config = await config.load({
logger,
});
await run(process.argv.slice(2), config)
Support for rc
files
Currently the configuration for oclif must live inside the oclif
section of your CLI or plugin's package.json. This can be difficult if you have a large amount of configuration, you want to dynamically change the configuration, or want to ensure that your configuration is correctly typed.
To solve this, we can now use lilconfig to read in a variety of rc files.
Despite being able to use an rc file, @oclif/core
will still be dependent on your package.json to get the name
, version
, and dependencies
. We could ask that you put those value in your rc file, but duplicating that information across two files feels like something people would rather not do.
If you choose to use an rc file, one thing you must consider is that there will be a slight performance hit due to needing to search for the rc file in addition to the package.json.
This is the list of supported files. Please feel free to create a PR to add support for other files
.oclifrc
.oclifrc.json
.oclifrc.js
.oclifrc.mjs
.oclifrc.cjs
oclif.config.js
oclif.config.mjs
oclif.config.cjs
Top level exports
We'll have top level exports for:
args
command
config
errors
execute
flags
flush
handle
help
hooks
interfaces
logger
performance
run
settings
util/ids
ux
The current way of accessing these looks like this:
import {run, flush, handle} from '@oclif/core'
With top level exports, you could access those like this:
import run from '@oclif/core/run'
import flush from '@oclif/core/flush'
import handle from '@oclif/core/handle'
The benefit of this is that you'll be able to import those utilities without also importing everything else that @oclif/core
exports.
As a result of this change, deep imports (e.g. import {Command} from '@oclif/core/lib/command.js
) will no longer work.
Bundling support for custom help classes
In case you missed it, we introduced new command discovery strategies that make bundling possible. In order to do that, the location of commands and hooks needed to be configured using a target
(i.e. a file or directory containing the commands or hooks) and an identifier
(i.e. the name of the export inside the target
).
This change originally only worked for commands and hooks but now also works for custom help classes so that those can be bundled as well.
exactOptionalPropertyTypes
We enabled exactOptionalPropertyTypes
(fixes #960) for improved type safety
Interfaces
Interfaces.PJSON
Interfaces.PJSON
has now been simplified to a single type instead of Interfaces.PJSON.CLI
and Interfaces.PJSON.Plugin
Interfaces.OclifConfiguration
There's a new Interfaces.OclifConfiguration
that represents everything that could be added to the oclif
section of your package.json (or rc file). This is particularly helpful if you want to use a .oclifrc.ts
and ensure that your oclif configuration matches the expected type.
Runtime auto-transpilation of linked ESM plugins with tsx
If your ESM plugin has a devDependency on tsx
, then you oclif can now auto-transpile the code at runtime