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.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With