Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update internals/api focused docs #230

Merged
merged 13 commits into from
Mar 28, 2024
14 changes: 4 additions & 10 deletions docs/base_class.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@ For large CLIs with multiple plugins, it's useful to put this base class into it
// src/baseCommand.ts
import {Command, Flags, Interfaces} from '@oclif/core'

enum LogLevel {
debug = 'debug',
info = 'info',
warn = 'warn',
error = 'error',
}

export type Flags<T extends typeof Command> = Interfaces.InferredFlags<typeof BaseCommand['baseFlags'] & T['flags']>
export type Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>

Expand All @@ -26,10 +19,11 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {

// define flags that can be inherited by any command that extends BaseCommand
static baseFlags = {
'log-level': Flags.custom<LogLevel>({
summary: 'Specify level for logging.',
options: Object.values(LogLevel),
'log-level': Flags.option({
default: 'info',
helpGroup: 'GLOBAL',
options: ['debug', 'warn', 'error', 'info', 'trace'] as const,
summary: 'Specify level for logging.',
})(),
}

Expand Down
35 changes: 26 additions & 9 deletions docs/error_handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Error Handling
---

oclif handles intentionally - and unintentionally - thrown errors in two places. First in the `Command.catch` method and then, finally, in the bin/run `catch` handler where the Error is printed and the CLI exits. This error flow makes it possible for you to control and respond to errors that occur in your CLI as you see fit.
oclif handles intentionally - and unintentionally - thrown errors in two places. First in the `Command.catch` method and then, finally, in the `bin/run.js` `catch` handler where the Error is printed and the CLI exits. This error flow makes it possible for you to control and respond to errors that occur in your CLI as you see fit.

## Error Handling in the `catch` method

Expand All @@ -20,24 +20,41 @@ export default class Hello extends Command {
}
```

If this type of error handling is being implemented across multiple commands consider using a Custom Base Class (https://oclif.io/docs/base_class#docsNav) for your commands and overriding the `catch` method.
If this type of error handling is being implemented across multiple commands consider using a [Custom Base Class](./base_class.md) for your commands and overriding the `catch` method.

## bin/run.js `catch` handler

Every oclif CLI has a ./bin/run.js file that is the entry point of command invocation. Errors that occur in the CLI, including re-thrown errors from a Command, are caught here in the bin/run.js `catch` handler.
Every oclif CLI has a `./bin/run.js` file that is the entry point of command invocation. Errors that occur in the CLI, including re-thrown errors from a Command, are caught and handled by oclif's `handle` function, which displays the error to the user.

```js
.catch(require('@oclif/core/handle'))
If you generated your CLI using `oclif generate`, then you will see an `execute` function that's responsible for running the CLI and catching any errors. You can, however, implement this yourself if you need to customize the error handling.

Here's the generic `bin/run.js` that comes with `oclif generate`:

```javascript
#!/usr/bin/env node

import {execute} from '@oclif/core'

await execute({dir: import.meta.url})
```

This catch handler uses the `@oclif/errors/handle` function to display (and cleanup, if necessary) the error to the user. This handler can be swapped for any function that receives an error argument.
To customize error handling, you'll want to use oclif's `run` function instead of `execute`:

```javascript
#!/usr/bin/env node

import {run, handle, flush} from '@oclif/core'

await run(process.argv.slice(2), import.meta.url)
.catch(async (error) => handle(error))
.finally(async () => flush())
```

If you chose to implement your own handler here, we still recommend you delegate finally to the `@oclif/core/handle` function for clean-up and exiting logic.
The `catch` handler can be swapped for any function that receives an error argument. If you chose to implement your own handler here, we still recommend you delegate finally to the `handle` function for clean-up and exiting logic.

```js
.catch((error) => {
const oclifHandler = require('@oclif/core/handle');
// do any extra work with error
return oclifHandler(error);
return handle(error);
})
```
2 changes: 1 addition & 1 deletion docs/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: ESM
---

Version 3.0.0 of `@oclif/core` officially supports ESM plugin development and CJS/ESM interoperability, meaning that you can have a root plugin written with CJS and your bundled plugins written in ESM or vice versa.
Version 3.0.0 of `@oclif/core` officially supports ESM plugin development and CJS/ESM interoperability, meaning that you can have a root plugin written with CJS and your plugins written in ESM or vice versa.

- [Interoperability Overview](#interoperability-overview)
- [ESM Root plugin](#esm-root-plugin)
Expand Down
64 changes: 64 additions & 0 deletions docs/flag_inheritance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: Flag Inheritance
---

There are some instances where you might want to define a flag once for all of your commands. In this case you can add a base flag to an abstract base `Command` class. For example,


```typescript
import { Command, Flags } from '@oclif/core';

export abstract class BaseCommand extends Command {
static baseFlags = {
interactive: Flags.boolean({
char: 'i',
description: 'Run command in interactive mode',
// Show this flag under a separate GLOBAL section in help.
helpGroup: 'GLOBAL',
}),
};
}
```

Any command that extends `BaseCommand` will now have an `--interactive` flag on it.

If you are going to stack multiple layers of abstract `Command` classes, then you must spread the `baseFlags` to ensure that the flags are properly inherited. For example,

```typescript
import { Command, Flags } from '@oclif/core';

export abstract class FirstBaseCommand extends Command {
static baseFlags = {
interactive: Flags.boolean({
char: 'i',
description: 'Run command in interactive mode',
// Show this flag under a separate GLOBAL section in help.
helpGroup: 'GLOBAL',
}),
};
}

export abstract class SecondBaseCommand extends FirstBaseCommand {
static baseFlags = {
...FirstBaseCommand.baseFlags,
'log-level': Flags.option({
default: 'info',
description: 'Specify log level',
helpGroup: 'GLOBAL',
options: ['debug', 'warn', 'error', 'info', 'trace'] as const,
summary: 'Specify level for logging.',
char: 'l',
})(),
};
}

export abstract class ThirdBaseCommand extends SecondBaseCommand {
static baseFlags = {
...SecondBaseCommand.baseFlags,
verbose: Flags.boolean({
description: 'Show verbose output.',
char: 'v'
})
};
}
```
26 changes: 11 additions & 15 deletions docs/flexible_taxonomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,31 @@ If you'd like for your customers to execute commands without adhereing to the de

There are two main benefits to enabling flexible taxonomy:

1. It makes your CLI more user-friendly. For example, you might have a command, `my-cli foobars:list`. If a user mistakenly enters `my-cli list:foobars` then oclif will automatically know that it should execute `foobars:list` instead of throwing an error.
1. It makes your CLI more user-friendly. For example, you might have a command, `my-cli foobars list`. If a user mistakenly enters `my-cli list foobars` then oclif will automatically know that it should execute `foobars list` instead of throwing an error.
2. It gives you the opportunity to prompt a user for the right command if they only provide part of a command. This makes individual commands more discoverable, especially if you have a large number of commands. See [Hook Implementation](#hook-implementation) for more details.

### Hook Implementation

When `flexibleTaxonomy` is enabled, oclif will run the `command_incomplete` hook anytime a user enters an incomplete command (e.g. the command is `one:two:three` but they only entered `two`). This hook gives you the opportunity to create an interactive user experience.
When `flexibleTaxonomy` is enabled, oclif will run the `command_incomplete` hook anytime a user enters an incomplete command (e.g. the command is `one two three` but they only entered `two`). This hook gives you the opportunity to create an interactive user experience.

This example shows how you can use the [inquirer](#https://www.npmjs.com/package/inquirer) package to prompt the user for which command they would like to run:
This example shows how you can use the [inquirer](https://www.npmjs.com/package/inquirer) package to prompt the user for which command they would like to run:

```typescript
import { Hook, toConfiguredId, toStandardizedId } from "@oclif/core";
import { prompt } from "inquirer";
import { Hook, toConfiguredId, toStandardizedId } from '@oclif/core';
import { select } from '@inquirer/prompts';

const hook: Hook.CommandIncomplete = async function ({
config,
matches,
argv,
}) {
const { command } = await prompt<{ command: string }>([
{
name: "command",
type: "list",
message: "Which of these commands would you like to run?",
choices: matches.map((p) => toConfiguredId(p.id, config)),
},
]);
const command = await select({
message: 'Which of these commands would you like to run?',
choices: matches.map((p) => toConfiguredId(p.id, config)),
});

if (argv.includes("--help") || argv.includes("-h")) {
return config.runCommand("help", [toStandardizedId(command, config)]);
if (argv.includes('--help') || argv.includes('-h')) {
return config.runCommand('help', [toStandardizedId(command, config)]);
}

return config.runCommand(toStandardizedId(command, config), argv);
Expand Down
21 changes: 0 additions & 21 deletions docs/global_flags.md

This file was deleted.

Loading