'How to build a TypeScript shell command that installs portably and globally?
I would like to build a command-line tool written in TypeScript that becomes available in the $PATH
when installed. My requirements:
- I can run and test it from within the project folder (e.g.,
yarn command
,npm run command
) - It should be installable via
npm install -g mycommand
- When installed, it can be invoked like a regular shell command
- It doesn't require other NPM modules to be explicitly globally installed
- (soft requirement) I'd rather avoid having to use Webpack
A common solution is to use ts-node
in the shebang line (#!/usr/bin/env ts-node
) but that requires that ts-node
is globally installed.
What I have so far...
My project structure:
├── cli.ts
├── package.json
├── tsconfig.json
└── util.ts
package.json
:
{
"name": "mycommand",
"version": "1.0.0",
"main": "./bin/cli.js",
"bin": {
"mycommand": "./bin/cli.js"
},
"scripts": {
"build": "tsc -p .",
"mycommand": "ts-node ./cli.ts",
},
"dependencies": {
"@types/cli-color": "^2.0.2",
"@types/inquirer": "^8.2.1",
"@types/node": "^17.0.29",
"@types/shelljs": "^0.8.11",
"@types/yargs": "^17.0.10",
"cli-color": "^2.0.2",
"inquirer": "^8.2.3",
"shelljs": "^0.8.4",
"ts-node": "^10.7.0",
"typescript": "^4.6.3",
"yargs": "^17.4.1"
}
}
tsconfig.json
:
{
"compilerOptions": {
"outDir": "bin",
"composite": true,
"incremental": true,
"strict": true,
"target": "ES5",
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": false,
"noUnusedLocals": true,
"declaration": true,
"declarationMap": true,
"downlevelIteration": true,
"moduleResolution": "node",
"importHelpers": true,
"module": "ESNext",
"skipLibCheck": true,
"sourceMap": true,
"useDefineForClassFields": true,
"forceConsistentCasingInFileNames": true
},
"ts-node": {
"compilerOptions": {
"target": "ES2017",
"module": "commonjs"
}
}
}
cli.ts
:
#!/usr/bin/env ts-node
import * as yargs from "yargs";
import * from "./util";
yargs
.command({
// ...
})
.help()
.vargs
This allows me to run the script locally using yarn mycommand
and to install it via yarn install -g
but the shell complains that ts-node
is not found (since the shebang line.
Solution 1:[1]
Three modifications that need to be made:
The
compilerOptions
intsconfig.json
need to output the correct module type supported by the version of Node:... "lib": [ "es2021" ], "module": "UMD", "target": "es2021", ...
The TSConfig project has recommended bases for various environments. The above snippet is based on the recommendation for Node 16+, which my project targets.
The shebang line should use
node
, notts-node
. Running it locally through a Yarn/NPM script overrides it anyway:#!/usr/bin/env node
Add a
preinstall
script to transpile the TypeScript file into JavaScript:... "scripts": { "preinstall": "yarn build", ...
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Gingi |