Node Command Line Interface Tool in TypeScript.

YouTube Video
Introduction.
In NodeJS you can create a command line tool which will run on the NodeJS platform. Most of the developer tools like Grunt, Gulp, even npm it self run like this. But NodeJS only run JavaScript which is nice but if you want to use TypeScript you have to compile the TypeScript code to JavaScript first
So in this post I’m going to explain how to use TypeScript as development language and how to compile the TypeScript to JavaScript so it can be run on the NodeJS platform.
We will be creating a tool to calculate factorial of a number given as an argument to the tool.
Prerequisite.
Since everything from development tools to the end product will be running on NodeJS install NodeJS.
- Download and install for Node.js
-
Using NVM
or
- Using OS default package manager (search for node or nodejs)
Also you need a good editor, you can use the operating system’s default text editor but for TypeScript Visual Studio Code is a better choice since it has native support for TypeScript so it make your development easier.
TypeScript Project
Folder structure.
The Initial folder structure will be as below.
ts-factorial-cli
├── package.json
└── src
The root folder name can be anything its up to you.
src
folder is where out typescript code resides. package.json
is for NodeJS metadata file, to create it use the command npm init
which will ask some questions answer those and the file will be created which will somewhat looks like the following.
{
"name": "ts-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Karthikeyan Vaithilingam",
"license": "ISC"
}
TypeScript Node Module
To add TypeScript to the project we will be installing typescript
node package. To do that use the following command.
$ npm install typescript --save-dev
TypeScript install as a development dependency thats what --save-dev
flag denotes. If you need a Node module to be used in compiled JavaScript code use --save
flag.
The above command will add a entry in the package.json's
devDependencies section
{
"name": "ts-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Karthikeyan Vaithilingam",
"license": "ISC",
"devDependencies": {
"typescript": "^2.6.1"
}
}
TypeScript Config file.
The configuration for the TypeScript compiler will be maintain in tsconfig.json
, create a file with the same name and have the following content.
{
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
],
"compilerOptions": {
"module": "commonjs",
"target": "es6"
}
}
include
is a list of patterns for the source to be compiled.exclude
is a list of patterns for the TypeScript compiler not to include.compilerOptions
module
which JavaScript module loader to be used in compiled JavaScript.target
target version of JavaScript.
Note: NodeJS dose not support es2015’s modules at least not yet at the time of writing this post.
TypeScript compile npm script.
To compile TypeScript we will be using npm scripts to do so.
In package.json
add the following section.
"scripts": {
"build": "tsc --outDir bin"
}
To run the above script use npm run build
this will create a folder called bin
and the compiled javascript will be placed there.
Now create a file in src
folder named main.ts
and have console.log('Test')
since TypeScript is a super set os JavaScript all valid JavaScript statements are valid TypeScript statements.
Then do npm run build
which will create the compiled JavaScript file bin/main.js
the content will be identical to main.ts because there is nothing to compile since the statement is JavaScript.
Run the JavaScript with node ./bin/main.js
you get Hello World
as output.
Now let us test TypeScript class. Change the contest of main.ts
to the following
export class Main{
static log(val:any){
console.log(val);
}
}
Main.log('Hello World');
The compiled ES6 JavaScript code will be.
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class Main {
static log(val) {
console.log(val);
}
}
exports.Main = Main;
Main.log('Hello World');
Now lets try with ES5 to do so change the tsconfig.json
compiler options to "target": "es5"
and do npm run build
. The result will be.
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Main = /** @class */ (function () {
function Main() {
}
Main.log = function (val) {
console.log(val);
};
return Main;
}());
exports.Main = Main;
Main.log('Hello World');
Diff between ES6 and ES5
Factorial In TypeScript
In the above created project we will add factorial class to do the factorial.
Create a file named factorial.ts
in src
folder with the following content.
export class Factorial {
private num: number;
constructor(num: number) {
this.num = num;
}
getFactorial(): number {
return this.doFact(this.num);
}
private doFact(num: number ): number {
return num <= 1 ? 1 : num * this.doFact(num - 1);
}
}
You can have the factorial function in the same file. I’m having it in a separate file to demonstrate the class and import functionally of TypeScript.
Now in main.ts replace the existing content to the following.
import { Factorial } from './factorial';
const argc = process.argv.splice(2);
if (argc.length != 1) {
console.error(`Invalid number of arguments ${argc}`);
process.exit(1);
}
const inputNumber = argc[0];
if (! /^\d+$/.test(inputNumber)) {
console.error(`Invalid input ${inputNumber}`);
process.exit(1);
}
const fact = new Factorial(+inputNumber);
console.log(`Factorial of ${inputNumber} is ${fact.getFactorial()}`);
If you build now using npm run build
you will get compilation errors as follows.
$ npm run build > [email protected] build /Volumes/Tech/Dev/Projects/Else/ThrowAway/Typescript/ts-factorial-cli > tsc --outDir bin src/main.ts(3,14): error TS2304: Cannot find name 'process'. src/main.ts(7,5): error TS2304: Cannot find name 'process'. src/main.ts(14,5): error TS2304: Cannot find name 'process'. npm ERR! code ELIFECYCLE npm ERR! errno 2 npm ERR! [email protected] build: `tsc --outDir bin` npm ERR! Exit status 2 npm ERR! npm ERR! Failed at the [email protected] build script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! /Users/karthik/.npm/_logs/2017-11-22T12_57_18_029Z-debug.log
This because TypeScript compiler doesn’t know what is process
is, to solve this we have to install node types using npm install @types/node --save-dev
which will add type definition for NodeJS APIs. Now your build will be successful.
The main file will accept the input number as argument and print the factorial of it. Test it with the following.
$ node ./bin/main.js 5 Factorial of 5 is 120
Command Line Tool
To make the script to a command line tool there two steps to be done so it can be executed with the node
command.
1. She Bang.
In *nix environment to execute a file directly she bang is used for mentioning the executor.
So add the following in the src\main.ts
at the first line
#!/usr/bin/env node
2. NPM Bin
To inform npm that there is a command line executable available in the node module we have to add the following section.
"bin": {
"factorial": "./bin/main.js"
}
The key of the property will be the command name and the value will be the compiled JavaScript file for that command.
Testing the Tool
To test the tool there are two ways.
npm link
npm link
will create a symbolic link to you project folder in the global node_modules so you project will behave a it is an install global node module. This is very much useful for development since all the changes are effective immediately.
npm install -g
Apart form npm link
you can also do npm install -g
which will copy the the project in the global node modules folder.
Testing the command
To test simply use factorial <number>
.
$ factorial 5 Factorial of 5 is 120
Reference.
A sample factorial tool source is available Github. This repo has some additional features also like linting.
- Anaconda Proxy Repository in Nexus OSS 3
- Node Command Line Interface Tool in TypeScript.
- Continuous Deployment for Jekyll using Bitbucket Pipeline to deploy in Github
- Grunt Watch and Livereload (Using BrowserSync) in Jekyll
- Java Thick Client with Kerberos for RESTful Service
- Install Gradle in Cloud9 IDE
- Localhost Authentication for Spring Kerberos
- JasperReport with Gradle
- Jasper Reports Font Extension
- JDK Folder from Installation EXE