Skip to content

Commit

Permalink
Merge pull request #230 from oclif/mdonnalley/internals
Browse files Browse the repository at this point in the history
feat: update internals/api focused docs
  • Loading branch information
mdonnalley authored Mar 28, 2024
2 parents dd403dd + b549f38 commit 743f86a
Show file tree
Hide file tree
Showing 14 changed files with 365 additions and 189 deletions.
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

0 comments on commit 743f86a

Please sign in to comment.