oclif Open CLI Framework

repository·main·Indexed 43 days ago

https://github.com/oclif/core

A Node.js framework for building performant and scalable command-line interfaces. @oclif/core 5.0.0 provides a base library for creating everything from single-file utilities to complex plugin-based CLI ecosystems, featuring a strict flag/argument parser, native TypeScript support, and UX utilities for spinners, colors, and standardized error handling. Requires Node.js 18+.

Tokens
19K
Snippets
64
Records
104
Agent score
55%

What's inside oclif

  1. Overview of oclif features

    main

    oclif is a Node.js framework designed for building both simple and complex CLIs. Key features include:

    • Flag/Argument parsing: A custom, flexible, and strict parser.
    • Super Speed: Minimal overhead and dependencies; large CLIs load as fast as small ones.
    • CLI Generator: Scaffolds fully functional CLIs quickly.
    • Testing Helpers: Built-in support for mocking stdout/stderr and scaffolded tests.
    • Auto-documentation: Automatically generates --help output and README documentation.
    • Plugins & Hooks: Extend functionality via modular plugins or lifecycle hooks.
    • TypeScript Support: Native TypeScript support with automatic ts-node integration for plugins.
    • Autocomplete: Built-in support for command, flag, and flag-value completion.
    • Customizable: Most components, including the parser, can be swapped or replaced.
  2. Understand changes to Flag and Arg parsing

    main

    V2 introduces stricter parsing rules to prevent common CLI errors:

    1. Hyphenated Arguments: Any input starting with a hyphen (-) is now strictly treated as a flag. If no corresponding flag definition exists, oclif will throw an error. Consequently, your commands can no longer accept arguments that begin with a hyphen.
    2. Delimiters: Instead of returning an array from a flag's parse method (which was used for comma-separated values), you should now use the delimiter option on your flag definition. Oclif will split the string on the delimiter before passing it to the parser.
  3. Migrate from deprecated oclif libraries to @oclif/core

    main

    When migrating from deprecated libraries like @oclif/config, @oclif/command, @oclif/error, or @oclif/parser, you should consolidate your imports into @oclif/core.

    Example Migration:

    Before:

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

    After:

    import {Command, Flags, Topic, Help} from '@oclif/core';
  4. Requirements for oclif

    main
    To use @oclif/core, you must have Node.js 18+ installed. It is recommended to use LTS versions of Node.js. You can include the node package in your CLI distribution to ensure users are running a compatible version.
  5. Use oclif.execute() for ESM/CJS bin scripts

    main

    To simplify bin scripts and ensure compatibility with both ESM and CJS, use the oclif.execute() method. This replaces the manual setup of ts-node, process.env.NODE_ENV, and manual oclif.run() calls.

    CJS Implementation

    #!/usr/bin/env node
    (async () => {
      const oclif = await import('@oclif/core')
      await oclif.execute({type: 'cjs', development: true, dir: __dirname})
    })()

    ESM Implementation

    #!/usr/bin/env node
    (async () => {
      const oclif = await import('@oclif/core')
      await oclif.execute({type: 'esm', dir: import.meta.url})
    })()

    Note: ESM and CJS plugins still require specific tsconfig.json configurations managed by the user.

    import {ux} from '@oclif/core'
    ux.log('Hello World')
  6. Configure ts-node for auto-transpilation

    main
    In @oclif/core@V3, ts-node is no longer a direct dependency to reduce package size. If you are developing a linked plugin and require auto-transpilation, you must add ts-node as a devDependency in your plugin's package.json.
  7. Restore `-h`, `-v`, and `version` flags

    main

    @oclif/core no longer automatically adds -h (help), -v (version), or the version command. You can restore these behaviors via your package.json configuration.

    To restore short flags for help and version:

    {
      "oclif": {
        "additionalHelpFlags": ["-h"],
        "additionalVersionFlags": ["-v"]
      }
    }

    To restore the version command, install and add @oclif/plugin-version to your plugins list:

    {
      "dependencies": {
        "@oclif/plugin-version": "^3"
      },
      "oclif": {
        "plugins": [
          "@oclif/plugin-version"
        ]
      }
    }
  8. Use oclif for standalone CLI scripts

    main

    While using the oclif generator is recommended, you can use @oclif/core in a standalone script by extending the Command class. This approach allows you to define flags, arguments, and command logic within a single file.

    When running a standalone command, you must call Command.run() and provide a configuration object containing root and pjson. The pjson object allows you to define metadata like name, version, and the command discovery strategy (e.g., 'single') without needing a physical package.json file.

    Always use flush() to ensure all output is processed and handle(err) to manage errors gracefully.

    #!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning
    
    import * as fs from 'fs'
    import {Command, Flags, flush, handle} from '@oclif/core'
    
    class LS extends Command {
      static description = 'List the files in a directory.'
      static flags = {
        version: Flags.version(),
        help: Flags.help(),
        dir: Flags.string({
          char: 'd',
          default: process.cwd(),
        }),
      }
    
      async run() {
        const {flags} = await this.parse(LS)
        const files = fs.readdirSync(flags.dir)
        for (const f of files) {
          this.log(f)
        }
      }
    }
    
    LS.run(process.argv.slice(2), {
      root: import.meta.dirname,
      pjson: {
        name: 'ls',
        version: '0.0.1',
        oclif: {
          commands: {
            strategy: 'single',
            target: 'index.js',
          },
        },
      },
    }).then(
      async () => {
        await flush()
      },
      async (err) => {
        await handle(err)
      },
    )
  9. Migrate Command arguments to the new Args API

    main

    In @oclif/core@V2, Command.args has been updated to use a more structured API similar to flags. Instead of passing an array of objects, you now pass an object where keys are the argument names and values are defined using the Args class. This improves type safety, ensuring this.parse(MyCommand).args returns a typed object instead of {[name: string]: any}.

    Available Args types:

    • string
    • integer
    • boolean
    • url
    • file
    • directory
    • custom
    import { Command, Args } from '@oclif/core'
    
    export default class MyCommand extends Command {
      static args = {
        arg1: Args.string({description: 'an argument', required: true})
      }
    
      public async run(): Promise<void> {
        const {args} = await this.parse(MyCommand) // args is { arg1: string }
      }
    }