diff --git a/docs/notifications.md b/docs/notifications.md deleted file mode 100644 index 8e2fb7aa..00000000 --- a/docs/notifications.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Notifications ---- - -Use [node-notifier](https://github.com/mikaelbr/node-notifier) for cross-platform OS notifications. - -Example: - -```typescript -import {Command} from '@oclif/core' -import * as notifier from 'node-notifier' - -export class MyCommand extends Command { - async run() { - notifier.notify({ - title: 'My notification', - message: 'Hello!' - }) - } -} -``` - -Demo: - -![notification demo](/img/notification_demo.gif) - -[node-notifier](https://github.com/mikaelbr/node-notifier) is capable of much more such as adding images, sounds, and even buttons and user input. diff --git a/docs/prompting.md b/docs/prompting.md deleted file mode 100644 index cd70a862..00000000 --- a/docs/prompting.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: Prompting ---- - -The [ux](https://github.com/oclif/core/blob/main/src/cli-ux/README.md) export provides a simple `cli.prompt()` function, for more complex input prompts, we recommend using the [inquirer](https://github.com/SBoudrias/Inquirer.js) library. - - -## `ux.prompt()` - -Prompt for basic input with `ux`: - -```typescript -import {Command, ux} from '@oclif/core' - -export class MyCommand extends Command { - async run() { - // just prompt for input - const name = await ux.prompt('What is your name?') - - // mask input after enter is pressed - const secondFactor = await ux.prompt('What is your two-factor token?', {type: 'mask'}) - - // hide input while typing - const password = await ux.prompt('What is your password?', {type: 'hide'}) - - this.log(`You entered: ${name}, ${secondFactor}, ${password}`) - } -} -``` - -Demo: - -![prompt demo](/img/prompt_demo.gif) - -## `inquirer` - -Here is an example command that uses [inquirer](https://github.com/SBoudrias/Inquirer.js). You will need to add `inquirer` and `@types/inquirer` (for TypeScript CLIs) for this to work. - -```typescript -import {Command, Flags} from '@oclif/core' -import * as inquirer from 'inquirer' - -export class MyCommand extends Command { - static flags = { - stage: Flags.string({options: ['development', 'staging', 'production']}) - } - - async run() { - const {flags} = await this.parse(MyCommand) - let stage = flags.stage - if (!stage) { - let responses: any = await inquirer.prompt([{ - name: 'stage', - message: 'select a stage', - type: 'list', - choices: [{name: 'development'}, {name: 'staging'}, {name: 'production'}], - }]) - stage = responses.stage - } - this.log(`the stage is: ${stage}`) - } -} -``` - -**NOTE**: inquirer >= v9 is an ESM package. If you aren't using ESM in your CLI/plugin, you should set [`moduleResolution` to `node16`](https://www.typescriptlang.org/tsconfig#moduleResolution) in your tsconfig.json and [import it using `await import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import): - -```typescript -import {Command, Flags} from '@oclif/core' - -export class MyCommand extends Command { - static flags = { - stage: Flags.string({options: ['development', 'staging', 'production']}) - } - - async run() { - const {flags} = await this.parse(MyCommand) - let stage = flags.stage - if (!stage) { - const { default: inquirer } = await import("inquirer") - let responses: any = inquirer.prompt([{ - name: 'stage', - message: 'select a stage', - type: 'list', - choices: [{name: 'development'}, {name: 'staging'}, {name: 'production'}], - }]) - stage = responses.stage - } - this.log(`the stage is: ${stage}`) - } -} -``` - - -Demo: - -![inquirer demo](/img/inquirer_demo.gif) diff --git a/docs/spinner.md b/docs/spinner.md deleted file mode 100644 index 9a96b6ad..00000000 --- a/docs/spinner.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Spinner ---- - -[@oclif/core](https://github.com/oclif/core) provides a simple `ux.action`, for more complex progress indicators we recommend using the [listr](https://www.npmjs.com/package/listr) library. - -## `ux.action` - -Shows a basic spinner - -```typescript -import {Command, ux} from '@oclif/core' - -export class MyCommand extends Command { - async run() { - // start the spinner - ux.action.start('starting a process') - // do some action... - // stop the spinner - ux.action.stop() // shows 'starting a process... done' - - // show on stdout instead of stderr - ux.action.start('starting a process', 'initializing', {stdout: true}) - // do some action... - // stop the spinner with a custom message - ux.action.stop('custom message') // shows 'starting a process... custom message' - } -} -``` - -This degrades gracefully when not connected to a TTY. It queues up any writes to stdout/stderr so they are displayed above the spinner. - -![action demo](/img/action.gif) - -## listr - -Here is an example of the complex workflows supported by [listr](https://www.npmjs.com/package/listr). - -![listr demo](/img/listr.gif) diff --git a/docs/table.md b/docs/table.md deleted file mode 100644 index 054eab69..00000000 --- a/docs/table.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -title: Table ---- - -## `ux.table` - -Displays tabular data - -```typescript -ux.table(data, columns, options) -``` - -Where: - -- `data`: array of data objects to display -- `columns`: [Table.Columns](https://github.com/oclif/core/blob/main/src/cli-ux/styled/table.ts) -- `options`: [Table.Options](https://github.com/oclif/core/blob/main/src/cli-ux/styled/table.ts) - -`ux.table.flags()` returns an object containing all the table flags to include in your command. - -```typescript -{ - columns: Flags.string({exclusive: ['additional'], description: 'only show provided columns (comma-seperated)'}), - sort: Flags.string({description: 'property to sort by (prepend '-' for descending)'}), - filter: Flags.string({description: 'filter property by partial string matching, ex: name=foo'}), - csv: Flags.boolean({exclusive: ['no-truncate'], description: 'output is csv format'}), - extended: Flags.boolean({char: 'x', description: 'show extra columns'}), - 'no-truncate': Flags.boolean({exclusive: ['csv'], description: 'do not truncate output to fit screen'}), - 'no-header': Flags.boolean({exclusive: ['csv'], description: 'hide table header from output'}), -} -``` - -Passing `{only: ['columns']}` or `{except: ['columns']}` as an argument into `cli.table.flags()` will allow/block those flags from the returned object. - -`ux.Table.Columns` defines the table columns and their display options. - -```typescript -const columns: ux.Table.Columns = { - // where `.name` is a property of a data object - name: {}, // "Name" inferred as the column header - id: { - header: 'ID', // override column header - minWidth: '10', // column must display at this width or greater - extended: true, // only display this column when the --extended flag is present - get: row => `US-O1-${row.id}`, // custom getter for data row object - }, -} -``` - -`ux.Table.Options` defines the table options, most of which are the parsed flags from the user for display customization, all of which are optional. - -```typescript -const options: ux.Table.Options = { - printLine: myLogger, // custom logger - columns: flags.columns, - sort: flags.sort, - filter: flags.filter, - csv: flags.csv, - extended: flags.extended, - 'no-truncate': flags['no-truncate'], - 'no-header': flags['no-header'], -} -``` - -Example class: - -```typescript -import {Command, ux} from '@oclif/core' -import axios from 'axios' - -export default class Users extends Command { - static flags = { - ...ux.table.flags() - } - - async run() { - const {flags} = await this.parse(Users) - const {data: users} = await axios.get('https://jsonplaceholder.typicode.com/users') - - ux.table(users, { - name: { - minWidth: 7, - }, - company: { - get: row => row.company && row.company.name - }, - id: { - header: 'ID', - extended: true - } - }, { - printLine: this.log.bind(this), - ...flags, // parsed flags - }) - } -} -``` - -Displays: - -```shell -$ example-cli users -Name Company -Leanne Graham Romaguera-Crona -Ervin Howell Deckow-Crist -Clementine Bauch Romaguera-Jacobson -Patricia Lebsack Robel-Corkery -Chelsey Dietrich Keebler LLC -Mrs. Dennis Schulist Considine-Lockman -Kurtis Weissnat Johns Group -Nicholas Runolfsdottir V Abernathy Group -Glenna Reichert Yost and Sons -Clementina DuBuque Hoeger LLC - -$ example-cli users --extended -Name Company ID -Leanne Graham Romaguera-Crona 1 -Ervin Howell Deckow-Crist 2 -Clementine Bauch Romaguera-Jacobson 3 -Patricia Lebsack Robel-Corkery 4 -Chelsey Dietrich Keebler LLC 5 -Mrs. Dennis Schulist Considine-Lockman 6 -Kurtis Weissnat Johns Group 7 -Nicholas Runolfsdottir V Abernathy Group 8 -Glenna Reichert Yost and Sons 9 -Clementina DuBuque Hoeger LLC 10 - -$ example-cli users --columns=name -Name -Leanne Graham -Ervin Howell -Clementine Bauch -Patricia Lebsack -Chelsey Dietrich -Mrs. Dennis Schulist -Kurtis Weissnat -Nicholas Runolfsdottir V -Glenna Reichert -Clementina DuBuque - -$ example-cli users --filter="company=Group" -Name Company -Kurtis Weissnat Johns Group -Nicholas Runolfsdottir V Abernathy Group - -$ example-cli users --sort=company -Name Company -Nicholas Runolfsdottir V Abernathy Group -Mrs. Dennis Schulist Considine-Lockman -Ervin Howell Deckow-Crist -Clementina DuBuque Hoeger LLC -Kurtis Weissnat Johns Group -Chelsey Dietrich Keebler LLC -Patricia Lebsack Robel-Corkery -Leanne Graham Romaguera-Crona -Clementine Bauch Romaguera-Jacobson -Glenna Reichert Yost and Sons -``` diff --git a/docs/themes.md b/docs/themes.md index 61d30b56..2cce56ce 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -2,15 +2,13 @@ title: Themes --- -oclif supports themes that users can either define for themselves or select from a variety of themes you ship with your CLI. +oclif supports themes that you can ship with your CLI, which users can then override if they choose. By default, the theme only applies to help output but you can extend the theme for your own purposes if you want. See [Extending Themes](#extending-themes) section below. ## theme.json -By default oclif will read themes from `~/.config//theme.json`. - -This file takes the following shape: +The theme file takes the following shape: ```json { @@ -56,6 +54,33 @@ The values for each of these must be one of the following: Any invalid values will be ignored. +## Shipping a Theme + +Shipping a theme with your CLI is very simple. + +First you need to create a new theme file (see above) in your CLI. Then, in your package.json, you just need to tell oclif where to find that file: + +```json +{ + "files": [ + "/theme.json", + "/oclif.manifest.json", + "/dist", + ], + "oclif": { + "theme": "theme.json" + } +} +``` + +It's important that you also add the file to the list of `files` so that it will be packed with your CLI whenever you publish to npm or when pack your CLI using `oclif pack`. + +## Overriding Themes + +If you've shipped a theme with your CLI, users can then override the theme by creating their own `theme.json` in the config directory of your CLI (`~/.config/` on unix, `%LOCALAPPDATA%\` on windows.) + +Users can specify one or all of the theme properties in their own `theme.json`, meaning that they can choose to only override a single property of the default theme. + ## Disabling Themes Themes can be disabled by using `_DISABLE_THEME` environment variable. diff --git a/docs/user_experience.md b/docs/user_experience.md new file mode 100644 index 00000000..5b8fbb3e --- /dev/null +++ b/docs/user_experience.md @@ -0,0 +1,21 @@ +--- +title: User Experience +--- + +oclif's philosophy is that developers should free to design any user experience that they want for their users. In other words, we try really hard to not make any UX decisions for you. + +So many times we utilize [hooks](./hooks.md) whenever a user experience is required (e.g. the provided command isn't found). That way you can design the exact experience you want your users to have. In the case of error handling, you're [able to override](./error_handling.md) oclif's default behavior. + +But to make it easy for you, `@oclif/core` exports a [`ux` module](https://github.com/oclif/core/blob/main/src/cli-ux/README.md) that offers several tools you can use to implement your desired user experience. + +However, due to time constraints we are not able to support this module as well as we would like. For that reason, we **strongly** recommend that you find npm libraries that specialize in the UX components you want to use. Here's a brief list of some of the libraries we like: + +- For prompts: [inquirer](https://www.npmjs.com/package/inquirer) +- For spinners: [ora](https://www.npmjs.com/package/ora) +- For progress bars: [cli-progress](https://www.npmjs.com/package/cli-progress) +- For hyperlinks: [hyperlink](https://www.npmjs.com/package/hyperlink) +- For tables: [tty-table](https://www.npmjs.com/package/tty-table), [cliui](https://www.npmjs.com/package/cliui) +- For trees: [object-treeify](https://www.npmjs.com/package/object-treeify) +- For colored JSON: [color-json](https://www.npmjs.com/package/color-json) +- For notifications: [node-notifier](https://www.npmjs.com/package/node-notifier) +- For rendering react components: [ink](https://www.npmjs.com/package/ink) diff --git a/website/sidebars.json b/website/sidebars.json index 2f1b76fb..c816f968 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -31,10 +31,7 @@ "aliases", "nsis-installer_customization", "base_class", - "prompting", - "spinner", - "table", - "notifications", + "user_experience", "debugging", "flexible_taxonomy", "flag_inheritance",