Skip to content

4.0.0

Compare
Choose a tag to compare
@svc-cli-bot svc-cli-bot released this 04 Jun 16:17
· 234 commits to main since this release

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 of ux.log
  • stderr - rename of ux.logToStderr

New

  • colorizeJson - Apply color theme to arbitrary JSON.

Removed

  • annotation
  • anykey
  • confirm
  • debug
  • done (use ux.action.stop() instead)
  • flush (still available via @oclif/core/flush)
  • info (use ux.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:

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