How to Initialize and Structure a Node.js CLI Project with TypeScript
This guide walks you through creating a demo-cli project, configuring package.json, setting up bin scripts, understanding Linux and Node bin execution, choosing global versus local installation, adding TypeScript, ESLint, Prettier, lint‑staged, and finally publishing the package to npm.
1. Initialize Project
Create a project folder (e.g., demo-cli) and run the following command to initialize the project: npm init Follow the prompts to fill in basic package.json information.
1.1 Configure package.json
Open the project in VSCode and edit the important fields in package.json:
name : the package name used when others import it.
version : follows major.minor.patch semantics for upgrades.
main : entry file path for module resolution.
scripts : custom npm scripts.
When you run npm run , npm creates a new shell, adds node_modules/.bin to PATH , executes the script, then restores PATH .
1.1.5 bin
Map custom command names to executable files:
{
...
"bin": {
"demo-cli": "bin/demo-cli"
},
...
}If the command name matches the package name, you can shorten it:
{
...
"bin": "bin/demo-cli",
...
}1.2 How bin Commands Run
1.2.1 Role of Linux bin directories
Bash first checks if a command contains a slash; if not, it searches functions, built‑ins, then the directories listed in PATH . The type command helps locate the executable.
Common bin directories include /bin, /sbin, /usr/bin, /usr/local/bin, etc.
1.2.2 Node bin
Find the Node installation path with:
echo $PATHThe directory /Users/hopewlliu/.nvm/versions/node/v14.17.3/bin contains all global Node commands.
To create a symlink for a custom command:
ln -s ../lib/node_modules/@tencent/imserver-cli/bin/imserver ./imserver2Remove the symlink with:
rm ./imserver21.2.3 Global vs. Local Installation
1.2.3.1 Global installation
Installing with -g places the package under ~/.nvm/versions/node/.../lib/node_modules. If the package defines a bin field, npm creates a symlink in ~/.nvm/versions/node/.../bin.
1.2.3.2 Local installation
Local packages reside in the project’s node_modules. Custom commands are linked in node_modules/.bin and can be run via npm scripts, which temporarily add node_modules/.bin to PATH.
1.2.4 Execution principle of target files
CLI entry files should start with the shebang line: #!/usr/bin/env node This tells the system to use the node interpreter found in PATH. The file can be executed directly without specifying the interpreter.
2. Directory Structure
.
├── README.md
├── bin
│ └── demo-cli
├── dist
├── lib
├── node_modules // dependencies
├── package-lock.json
└── package.json2.1 README.md
Project introduction, usage instructions, options, etc.
2.2 bin
Stores executable files for custom commands.
2.3 dist
Compiled output for publishing.
2.4 lib
Source code. Ensure the main field in package.json points to the compiled entry.
3. Additional Configuration
3.1 TypeScript Support
Install development dependencies:
npm install --save-dev typescript @types/node rimrafCreate tsconfig.json (excerpt):
{
"compilerOptions": {
"baseUrl": ".",
"rootDir": "lib",
"lib": ["esnext"],
"module": "commonjs",
"outDir": "dist/lib",
"allowJs": true,
"strict": true,
"declaration": true,
"target": "es6",
"suppressImplicitAnyIndexErrors": true
},
"include": ["lib"]
}Add npm scripts for cleaning, development, and pre‑publish:
{
"scripts": {
"clean": "rimraf dist",
"dev": "npm run clean && tsc -w",
"prepublish": "npm run clean && tsc"
}
}3.2 ESLint & Prettier
Install:
npm i -D [email protected] @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-prettier eslint-plugin-prettier prettierConfigure .eslintrc.js:
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["prettier", "@typescript-eslint"],
"extends": ["prettier"],
"rules": {
"no-var": "error",
"prettier/prettier": "error"
}
}Ignore generated files with .eslintignore and .prettierignore (both contain dist and node_modules).
Prettier configuration ( .prettierrc):
{
"useTabs": false,
"printWidth": 120,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "always"
}3.3 Pre‑commit Linting
Install husky and lint‑staged: npm install -D husky lint-staged Add to package.json:
{
"lint-staged": {
"*.{js,ts}": ["prettier-eslint --write", "eslint --fix", "git add"]
}
}Initialize husky and add a pre‑commit hook:
npx husky install
npx husky add .husky/pre-commit "echo \"git commit trigger husky pre-commit hook\" && npx lint-staged"3.4 .gitignore & .npmignore
Typical entries:
node_modules
package-lock.json
dist.npmignore should also exclude source files and config files that are not needed in the published package.
4. Common CLI Libraries
commander – command and argument parsing
glob – file traversal
shelljs – shell command utilities
prompts / inquirer – interactive console input
fs-extra – file system operations
chalk – colored logging
debug – debugging utilities
execa – execute shell commands
5. Publishing to npm
First login or register: npm adduser // or npm login Then publish (prepublish script ensures the build runs first):
npm publish6. Summary
Creating a CLI demo involves many steps, especially configuring ESLint in VSCode. Understanding Linux command lookup and Node bin execution is crucial for CLI development, and the same principles can be extended to other languages such as C++ extensions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
