Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use commander in typescript

Tags:

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.

like image 573
Louis Roché Avatar asked May 05 '17 06:05

Louis Roché


2 Answers

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.

like image 174
Marco Eckstein Avatar answered Sep 25 '22 10:09

Marco Eckstein


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.

like image 29
Kedel Mihail Avatar answered Sep 21 '22 10:09

Kedel Mihail