Use commander in typescript - node.js

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.

Related

Typescript extending a generic type

I have the following generic interface in my typescript code:
interface BaseResponse<T> {
status_code: string;
data: T;
}
I thought I would be able to use that base interface, without specifying the base's type parameter, in a generic function like this:
class MyService {
static async post<T extends BaseResponse>(path: string, data: any): Promise<T> {
// implementation here
}
}
But this gives the following error:
Generic type 'BaseResponse<T>' requires 1 type argument(s).(2314)
I can fix this error by updating the code like so:
class MyService {
static async post<T extends BaseResponse<U>, U>(path: string, data: any): Promise<T> {
// implementation here
}
}
But this requires me to pass two type parameters when I call the function as below. I was hoping I could only pass one and it could infer the second, but that gives me the error Expected 2 type arguments, but got 1.(2558). Is there any way to accomplish this?
// What I want to be able to do (Causes error mentioned above):
const response1 = await MyService.post<CustomerResponse>('/customers', postData);
// What I have to do instead (note the two type parameters)
const response2 = await MyService.post<CustomerResponse, CustomerData>('/customers', postData);

coordinates does not exist in type 'GeometryDataType'

This is a follow up on this question. Now I am using the point object correctly however I get this error:
src/actions/collectGpsData.ts:22:43 - error TS2322: Type '{ type: string; coordinates: any[]; }' is not assignable to type 'GeometryDataType'.
Object literal may only specify known properties, and '"coordinates"' does not exist in type 'GeometryDataType'.
22 place.location = { "type": "Point", "coordinates": [point.latitude, point.longitude] };
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Found 1 error.
import { Action } from "actionhero"; // No GeometryDataType sequelize.js definition
import { Place } from "../models/Place";
export class CollectGpsData extends Action {
constructor() {
super();
this.name = "collectGpsData";
this.description = "Collects GPS data and inserts it into the database";
this.outputExample = {};
this.inputs = {
gpsdata: {
required:true
}
}
}
async run(data) {
var GPSdata = data.params.gpsdata;
GPSdata.forEach(point => {
var place = new Place();
place.deviceimei = point.deviceimei;
place.location = { "type": "Point", "coordinates": [point.latitude, point.longitude] }; //error line
});
}
}
Why am I getting this error? and how can I avoid this error in the future?
I've answered it in your GitHub issue:
Hello #krlicmuhamed!
As #nainkunal933 noted, please include a complete code sample next time. The code you posted does not show how User is defined, for example. Please use a sequelize-sscce.
That said, I took the time to look into what's happening and tried to figure it out.
So, first of all, this issue is typescript-only. The code works fine in runtime. Please include this information directly next time.
I ventured into your stackoverflow question and comments and found this:
https://gist.github.com/krlicmuhamed/199c0bc3560a08718b553f3f609acbcd#file-places-ts-L22
Did you find any documentation instructing you to use this specific type explicitly here? If yes, please post the link so I can fix it. Regardless, I don't blame you because we really should have a set of recommended types to apply on each data type. It turns out GeometryDataType is not the correct type to use in this case.
The solution is to install #types/geojson:
npm install --save-dev #types/geojson
Then, import Geometry:
import type { Geometry } from '#types/geojson'
And then replace that line of code in which you put GeometryDataType with Geometry:
#AllowNull(false)
#Column(DataTypes.GEOMETRY)
- location: GeometryDataType;
+ location: Geometry;

Reference imported object based on env

In my TypeScript Node app I wish to reference the exported object that matches my NODE_ENV variable.
config.ts
const test: { [index: string]: any } = {
param1: "x",
param2: {
name: "John"
}
}
const dev: { [index: string]: any } = {
param1: "y",
param2: {
name: "Mary"
}
}
export { test, dev }
main.ts
const environment = process.env.NODE_ENV || "development";
import * as config from "./config.ts";
const envConfig = config[environment]; //gives error Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'typeof import("/path_to_config.ts")'.ts(7053)
Just make the implicit any explicit:
const envConfig: any = (config as any)[environment];
This error often arises when you try to access a property of an object via ['propertyName'] instead of .propertyName, since that form bypasses TypeScript's type checking in many cases.
You could do a bit better than any by defining a type which is constrained to all possible values (which you could export from your config.tsx) e.g.
type configType ='test' | 'dev'
const envConfig = config[environment as configType];

How to extend built-in function/add a property to a function?

process.hrtime.bigint() does not exist in the standard lib. I'm trying to add it in.
Here's what I've got:
types/bigint.d.ts
// https://github.com/Microsoft/TypeScript/issues/15096#issuecomment-400186862
type BigInt = number
declare const BigInt: typeof Number;
declare namespace NodeJS {
export interface Process {
hrtime: HighResTime
}
export interface HighResTime {
(time?: [number, number]): [number, number];
bigint(): BigInt
}
}
Which I've added to my typeRoots in tsconfig.json:
"typeRoots": ["./node_modules/#types","./types"]
Using like:
///<reference path="../types/bigint.d.ts"/>
export default class ProgressBar {
private startTime?: BigInt;
start() {
this.startTime = process.hrtime.bigint();
this.render();
}
But I get:
/home/me/Projects/xxx/node_modules/ts-node/src/index.ts:261
return new TSError(diagnosticText, diagnosticCodes)
^
TSError: тип Unable to compile TypeScript:
src/ProgressBar.ts(19,41): error TS2339: Property 'bigint' does not exist on type '(time?: [number, number] | undefined) => [number, number]'.
If I remove the <reference> to bigint.d.ts then it gets even worse, and complains the type BigInt doesn't exist (TS2304) -- which doesn't make much sense to me, i thought that was the point of the typeRoot?
But the bigger issue is that even with the reference, TS still doesn't like process.hrtime.bigint(), so I'm guessing I didn't extend the Process interface properly. How do I do it right?

eslint-plugin-import how to add flow declared modules to exceptions

I have a file in flow-typed directory with some common type declarations like:
common-types.js
// #flow
declare module 'common-types' {
declare export type RequestState = {
setLoading: () => void,
setFinished: () => void,
setError: (error: AxiosFailure) => void,
reset: () => void,
status: LoadingStatus,
error: AxiosFailure | void,
};
declare export type LoadingStatus = '' | 'loading' | 'finished';
declare export type ErrorObject = { [key: string]: string | string[] | Error };
declare export type AxiosFailure = {
status: number,
data: ErrorObject,
}
}
Now I import it like this in files:
import type { RequestState } from 'common-types';
but I get eslint-plugin-import errors about missing file extension as well as unable to resolve path to module 'common-types'
How do I deal with it?
I found a solution. As #logansmyth suggested in comment
Your types should just pass your code along with your data
The problem I had was with webpack aliases. Flow pointed me errors about modules not being installed. Hovewer I found out that I can use mappers in .flowconfig like:
module.name_mapper='^common' ->'<PROJECT_ROOT>/src/common'
along with webpack aliases, which makes eslint-plugin-import and flow happy as well properly type-checking. Now I import types along with common components, no messing with flow-typed directory.

Resources