Skip to content

Commit

Permalink
feat: version 2.0.0 (#539)
Browse files Browse the repository at this point in the history
* feat: improve interfaces

* feat: new command types

* feat: more changes

* chore: clean up

* chore: enable eslint rules

* chore: add files

* feat: remove parser/deps

* chore: more clean up

* chore: tests

* feat: rename globalFlags to baseFlags

* feat: typed args

* chore: renaming things and cleanup

* chore: make tests compilable

* chore: add args types test

* chore: get tests passing

* feat: flatten ux

* fix: ux.table export

* fix: read from stdin

* fix: args and cleanup

* chore: tests

* fix: arg and flag types

* fix: small bugs

* fix: handle non-existent flags

* chore: tests

* chore: compilation issues

* chore: update cli-ux docs

* feat: add execute

* refactor: clean up cli-ux imports

* test: begin removing fancy-test

* feat: remove ux.open

* chore: cleam up from merge

* feat: add delimiter option

* fix: deprecateAliases

* chore: update tests

* chore: update migration guide

* fix: add backwards compatability for v1 args

* fix: typing for default context

* fix: styledObject type and boolean flag parser

* fix: types
  • Loading branch information
mdonnalley committed Jan 12, 2023
1 parent 94e7f13 commit d5f45af
Show file tree
Hide file tree
Showing 104 changed files with 2,050 additions and 2,023 deletions.
4 changes: 1 addition & 3 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
"oclif-typescript"
],
"rules": {
"unicorn/no-abusive-eslint-disable": "off",
"unicorn/prefer-spread": "off",
"unicorn/prefer-module": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/import-style": "off",
Expand All @@ -16,6 +14,6 @@
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/explicit-module-boundary-types": "off"
"no-useless-constructor": "off"
}
}
274 changes: 150 additions & 124 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,181 +1,207 @@
Migrating to @oclif/core
Migrating to @oclif/core@V2
==============

Migrating to `@oclif/core` from the old oclif libraries (`@oclif/config`, `@oclif/command`, `@oclif/error`, `@oclif/parser`) is relatively straight forward.
## Breaking Changes

- [Migrating to @oclif/core](#migrating-to-oclifcore)
- [Update Imports](#update-imports)
- [Update your bin scripts](#update-your-bin-scripts)
- [Add `main` to your package.json](#add-main-to-your-packagejson)
- [Restore `-h`, `-v`, and `version`](#restore--h--v-and-version)
- [Configure the `topicSeparator`](#configure-the-topicseparator)
- [Update `this.parse` to `await this.parse`](#update-thisparse-to-await-thisparse)
- [Update `default` property on flag definitions](#update-default-property-on-flag-definitions)
- [Replace cli-ux library with `CliUx`](#replace-cli-ux-library-with-cliux)
### Command Args

## Update Imports
We updated the `Command.args` to more closely resemble flags

Replace imports from the old libraries with `@oclif/core`. For example,
**Before**

```typescript
import Help from '@oclif/plugin-help';
import {Topic} from '@oclif/config';
import {Command, Flags} from '@oclif/command'
```
import { Command } from '@oclif/core'

With this import:
export default MyCommand extends Command {
static args = [{name: arg1, description: 'an argument', required: true}]

```typescript
import {Command, Flags, Topic, Help} from '@oclif/core';
public async run(): Promise<void> {
const {args} = await this.parse(MyCommand) // args is useless {[name: string]: any}
}
}
```

## Update your bin scripts

`@oclif/core` now supports separate bin scripts for production and development.
**After**

You can copy these new bin scripts directly from our [example repository](https://github.com/oclif/hello-world/tree/main/bin).

## Add `main` to your package.json
```typescript
import { Command, Args } from '@oclif/core'

We recommend that all oclif plugins specify the `main` field in their package.json so that we can begin working on supporting Yarn v2.
export default MyCommand extends Command {
static args = {
arg1: Args.string({description: 'an argument', required: true})
}

```json
{
"main": "lib/index.js"
public async run(): Promise<void> {
const {args} = await this.parse(MyCommand) // args is { arg1: string }
}
}
```

All plugins will be required to have this field in the next major version of `@oclif/core`.
These are the available Args:
- string
- integer
- boolean
- url
- file
- directory
- custom

## Restore `-h`, `-v`, and `version`
### Interfaces

`@oclif/config` automatically added `-h` as a short flag for `--help`, `-v` as a short flag for `--version`, and `version` as an alias for `--version`.
- Removed `Interfaces.Command` since they were not usable for tests. These are replaced by types that are available under the `Command` namespace

`@oclif/core` removes these so you can now use those flags for whatever you want! However, we've added a way to restore that functionality if you want to keep it.
```
Interfaces.Command => Command.Cached
Interfaces.Command.Class => Command.Class
Interfaces.Command.Loadable => Command.Lodable
```

Simply add the `additionalHelpFlags` and `additionalVersionFlags` properties to the oclif section of your package.json:
- Removed the following interfaces from the export. Exporting all of these made it difficult to make non-breaking changes when modifying types and/or fixing compilation bugs. We are open to PRs to reintroduce these to the export if they are needed for your project
- Arg
- ArgInput
- ArgToken
- CLIParseErrorOptions
- CompletableFlag
- CompletableOptionFlag
- Completion
- CompletionContext
- Default
- DefaultContext
- Definition
- EnumFlagOptions
- FlagBase
- FlagInput
- FlagOutput
- FlagToken
- FlagUsageOptions
- Input
- List
- ListItem
- Metadata
- OptionalArg
- OptionFlagProps
- OutputArgs
- OutputFlags
- ParseFn
- ParserArg
- ParserInput
- ParserOutput
- ParsingToken
- RequiredArg

### CliUx

We flattened `CliUx.ux` into `ux` for ease of use

**Before**

```json
{
"oclif": {
"additionalHelpFlags": ["-h"],
"additionalVersionFlags": ["-v"]
}
}
```
```typescript
import {CliUx} from '@oclif/core'

To get the `version` command, install `@oclif/plugin-version` into your CLI:

```json
{
"dependencies": {
"@oclif/plugin-version": "^1"
},
"oclif": {
"plugins": [
"@oclif/plugin-version"
]
}
}
CliUx.ux.log('Hello World')
```

## Configure the `topicSeparator`
**After**

By default, the `topicSeparator` is set to a colon (`:`) to maintain backwards compatibility with existing CLIs. If you prefer, you can now set it to a space.
```typescript
import {ux} from '@oclif/core'

For colons:
```json
{
"oclif": {
"topicSeparator": ":"
}
}
ux.log('Hello World')
```

For spaces:
```json
{
"oclif": {
"topicSeparator": " "
}
}
```
#### CliUx.ux.open

**NOTE: Using colons always works, even if you set the `topicSeparator` to spaces.** This means that you can enable spaces in your CLI without introducing a breaking change to your users.
We removed the `open` method since it was a direct import/export of the [`open`](https://www.npmjs.com/package/open) package. If you need this functionality, then you should import `open` yourself.

## Update `this.parse` to `await this.parse`
### Flags

The `parse` method on `Command` is now asynchronous (more [here](https://oclif.io/blog/#async-command-parsing)). So you'll now need to `await` any calls to `this.parse`:
- Flags.custom replaces Flags.build, Flags.enum, and Flags.option
- Removed builtin `color` flag
- Renamed `globalFlags` to `baseFlags`
- `globalFlags` was a misleading name because the flags added there weren't actually global to the entire CLI. Instead, they were just flags that would be inherited by any command that extended the command class they were defined in.

`const { args, flags } = this.parse(MyCommand)` => `const { args, flags } = await this.parse(MyCommand)`
### Flag and Arg Parsing

## Update `default` property on flag definitions
- In v1, any input that didn't match a flag definition was assumed to be an argument. This meant that misspelled flags, e.g. `--hekp` were parsed as arguments, instead of throwing an error. In order to handle this, oclif now assumes that anything that starts with a hyphen must be a flag and will throw an error if no corresponding flag definition is found. **In other words, your command can no longer accept arguments that begin with a hyphen** (fixes https://github.com/oclif/core/issues/526)
- v1 allowed you to return an array from a flag's `parse`. This was added to support backwards compatibility for flags that separated values by commas (e.g. `my-flag=val1,val2`). However, this was problematic because it didn't allow the `parse` to manipulate the individual values. If you need this functionality, you can now set a `delimiter` option on your flags. By doing so, oclif will split the string on the delimiter before parsing.

The `default` property on flag definitions is now asynchronous. So you'll now need to await those.
## ESM/CJS Friendliness

Example:
Writing plugins with ESM has always been possible, but it requires [a handful of modifications](https://oclif.io/docs/esm) for it to work, especially in the bin scripts. In v2 we've introduced an `execute` method that the bin scripts can use to avoid having to make changes for ESM of CJS.

**CJS `bin/dev` before**
```typescript
import {Command, Flags} from '@oclif/core'
import {readFile} from 'fs/promises'
#!/usr/bin/env node

function getTeam(): Promise<string> {
return readFile('team.txt', 'utf-8')
}
const oclif = require('@oclif/core')

export const team = Flags.build({
char: 't',
description: 'team to use',
default: () => getTeam(),
})
const path = require('path')
const project = path.join(__dirname, '..', 'tsconfig.json')

export class MyCLI extends Command {
static flags = {
team: team(),
}
// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development'

async run() {
const {flags} = this.parse(MyCLI)
if (flags.team) console.log(`--team is ${flags.team}`)
}
}
```
require('ts-node').register({project})

## Replace cli-ux library with `CliUx`
// In dev mode, always show stack traces
oclif.settings.debug = true;

The [`cli-ux` library](https://github.com/oclif/cli-ux) has also been moved into `@oclif/core` in order to break a complex circular dependency between the two projects.

All the exports that were available from `cli-ux` are now available under the `CliUx` namespace, with the exception of the `cli` export which was identical to the `ux` export.
// Start the CLI
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
```

Old:
**CJS `bin/dev.js` after**
```typescript
#!/usr/bin/env node
// eslint-disable-next-line node/shebang
(async () => {
const oclif = await import('@oclif/core')
await oclif.execute({type: 'cjs', development: true, dir: __dirname})
})()
```

**ESM `bin/dev.js` before**
```typescript
import { cli } from 'cli-ux`
#!/usr/bin/env ts-node

cli.log('hello world')
cli.action.start('doing things')
cli.action.stop()
```
/* eslint-disable node/shebang */

New:
import oclif from '@oclif/core'
import path from 'node:path'
import url from 'node:url'
// eslint-disable-next-line node/no-unpublished-import
import {register} from 'ts-node'

```typescript
import { CliUx } from '@oclif/core`
// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development'

CliUx.ux.log('hello world')
CliUx.ux.action.start('doing things')
CliUx.ux.action.stop()
```
register({
project: path.join(path.dirname(url.fileURLToPath(import.meta.url)), '..', 'tsconfig.json'),
})

## Single command CLIs
// In dev mode, always show stack traces
oclif.settings.debug = true

Single command CLIs now are configured in a different way. To ensure your migrated CLI work as before, you have to add the following to your `oclif` configuration in the `package.json`:
// Start the CLI
oclif
.run(process.argv.slice(2), import.meta.url)
.then(oclif.flush)
.catch(oclif.Errors.handle)
```

```json
"oclif": {
"default": ".",
"commands": "./lib"
}
**ESM `bin/dev.js` after**
```typescript
#!/usr/bin/env node
// eslint-disable-next-line node/shebang
(async () => {
const oclif = await import('@oclif/core')
await oclif.execute({type: 'esm', dir: import.meta.url})
})()
```

Where `./lib` points to the folder in which your `tsconfig.json` is configured to output to (if you are using TypeScript), and your single command CLI entrypoint `index.(ts|js)` is located.
Note that ESM and CJS plugins still require different settings in the tsconfig.json - you will still need to make those modifications yourself.

## Other Changes
- Removed dependency on `@oclif/screen`
- Replaced `@oclif/linewrap` with `wordwrap`
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
base library for oclif CLIs

[![Version](https://img.shields.io/npm/v/@oclif/core.svg)](https://npmjs.org/package/@oclif/core)
[![CircleCI](https://circleci.com/gh/oclif/core/tree/main.svg?style=svg)](https://circleci.com/gh/oclif/core/tree/main)
[![Downloads/week](https://img.shields.io/npm/dw/@oclif/core.svg)](https://npmjs.org/package/@oclif/core)
[![License](https://img.shields.io/npm/l/@oclif/core.svg)](https://github.com/oclif/core/blob/main/package.json)


Migrating
=====

If you're migrating from the old oclif libraries (`@oclif/config`, `@oclif/command`, `@oclif/error`, `@oclif/parser`), see the [migration guide](./MIGRATION.md).
See the [migration guide](./MIGRATION.md) for an overview of breaking changes that occurred between v1 and v2.

The `@oclif/core` module now also includes the `cli-ux` module. Merging `cli-ux` into `@oclif/core` resolves a circular dependency between the two modules.
See the [cli-ux README](./src/cli-ux/README.md) for instructions on how to replace the `cli-ux` module with `@oclif/core`.
The [cli-ux README](./src/cli-ux/README.md) also contains detailed usage examples.
CLI UX
=====

The [ux README](./src/cli-ux/README.md) contains detailed usage examples of using the `ux` export.

Usage
=====
Expand Down
Loading

0 comments on commit d5f45af

Please sign in to comment.