Use commander in typescript
Asked Answered
Q

2

10

I try to use commander in typescript and I could like to give a proper type to my cli. So I start with this code:

import * as program from "commander";

const cli = program
  .version("1.0.0")
  .usage("[options]")
  .option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
  .parse(process.argv);

console.log(cli.debug)

But I get this error:

example.ts(9,17): error TS2339: Property 'debug' does not exist on type 'Command'.

So I tried to add an interface, as documented here:

import * as program from "commander";

interface InterfaceCLI extends commander.Command {
  debug?: boolean;
}

const cli: InterfaceCLI = program
  .version("1.0.0")
  .usage("[options]")
  .option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
  .parse(process.argv);

console.log(cli.debug)

and I get this error:

example.ts(3,32): error TS2503: Cannot find namespace 'commander'.

From what I understand, cli is actually a class of type commander.Command So I tried to add a class:

import * as program from "commander";

class Cli extends program.Command {
    public debug: boolean;
}

const cli: Cli = program
  .version("1.0.0")
  .usage("[options]")
  .option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
  .parse(process.argv);

console.log(cli.debug)

Which gives me this error:

example.ts(7,7): error TS2322: Type 'Command' is not assignable to type 'Cli'.
  Property 'debug' is missing in type 'Command'.

I don't know how to add a property to the Command class, either in my file or in a new .d.ts file.

Queenie answered 5/5, 2017 at 6:23 Comment(0)
L
5

With your first code snippet and the following dependencies, I do not get an error:

"dependencies": {
    "commander": "^2.11.0"
},
"devDependencies": {
  "@types/commander": "^2.9.1",
  "typescript": "^2.4.1"
}

Typescript interprets cli.debug as any. I guess the type declarations have been updated. So, if you are fine with any, the problem is solved.

If you really want to tell Typescript the type of debug, declaration merging would in principle be the way to go. It basically works like this:

class C {
    public foo: number;
}

interface C {
    bar: number;
}

const c = new C();
const fooBar = c.foo + c.bar;

However, there is a problem: program.Command is not a type but a variable. So, you cannot do this:

interface program.Command {
    debug: boolean;
}

And while you can do this:

function f1(): typeof program.Command {
    return program.Command;
}

type T = typeof program.Command;

function f2(): T {
    return program.Command;
}

You can neither do this:

interface typeof program.Command {
}

Nor this:

type T = typeof program.Command;

interface T {
}

I do not know whether this problem could be solved or not.

Lexicographer answered 8/7, 2017 at 16:24 Comment(1)
Just as a note Commander now ships with its own types, no need anymore to install @types/commanderFerine
O
1

I think I found the solution, I don't really know if it's a good practice, but nontheless.

import { Command } from 'commander';
const cli = new Command();

interface InterfaceCLI{
  debug?: boolean;
}

cli
  .version("1.0.0")
  .usage("[options]")
  .option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
  .parse(process.argv);

const { debug } : InterfaceCli = <InterfaceCli><unknown>cli;
console.log(debug) // here debug is your cli.debug, I just used object destructuring

In the line before the last one I'm using type casting, I'm first casting to unkown for non-overlapping types, and then, finally, casting to our interface - InterfaceCli.

Onyx answered 24/3, 2020 at 12:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.