Search code examples
node.jstypescriptdefinitelytypednode-commander

Use commander in typescript


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.


Solution

  • 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.