iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🗂️

npm Project Setup for Building CLI Tools with TypeScript

に公開

Target Audience

  • Familiar with TypeScript
  • Writing mostly web apps, but wanting to write command-line tools as well
  • Not knowing where to put personal tools and just putting them in ~/bin, etc.

Where to Put Personal Scripts

Writing a quick shell script and putting it in ~/bin—this is about me. Those are hard to maintain. You should stop doing that.

Recently, I've been organizing CLI tools in a different way. To get straight to the point, you likely have a folder for your project list in your home directory; create a new project folder for CLI commands there. Put them in there.

$ tree ~
myProjects
├── project1
├── project2
└── project3
$ mkdir myProjects/my-utils

How to Create a Package

You probably know that commands become available when you install an npm package. We'll use that. "Installing is a hassle, I don't want to do it." Exactly. Actually, you don't need to install it. If you npm link, you can directly execute scripts from that project from anywhere in the system.

Here, I'm using tsx to execute TypeScript scripts directly. I don't bother with building because it's a hassle. Having fewer steps is the right way to go.

$ cd myProjects/my-utils
$ hx package.json
$ cat package.json
{
  "name": "my-utils",
  "version": "1.0.0",
  "bin": {
    "mytool": "./cli.js"
  }
}
$ cat ./cli.js
#!/usr/bin/env tsx
console.log('Hello world!)
$ chmod +x ./cli.js
$ npm link
$ mytool
Hello world!
$ ls -l $(realpath $(which  mytool))
-rwxr-xr-x 1 my-name staff 48  4  9 17:03 /Users/my-name/myProjects/myutils/cli.js
# Files in the project can now be executed directly

What if you want to delete that folder? You should be managing it with GitHub before being told, so you can install it from there.

$ cd ~/myProjects/my-utils
$ npm unlink -g my-utils
$ which mytool
$ npm install -g git+https://github.com/my-name/my-utils.git
$ mytool
Hello world!

Since you can install it this way, you can delete the local folder at any time.

If you want to do maintenance, just run npm uninstall -g once, clone it, and run npm link again to return to the previous state.

npm uninstall -g my-utils
gh repo clone my-name/my-utils
cd my-utils
npm link

Folder Structure So Far

The minimal folder structure is as follows:

myProjects
├── myutils
│   ├── cli.js
│   ├── package-lock.json
│   └── package.json

Q&A

  • Do I need main in package.json?
    • No, you don't. That is for specifying which file to load when this package is imported. This package won't be imported, so it's unnecessary.
  • What about authors, repository, homepage, etc.?
    • If the warnings are annoying, feel free to fill them in.

Folder Structure of a Recently Created CLI Tool

I wrote a tool that reads data from a Google Sheet and writes it to a Google Calendar. This is what its folder structure looks like.

It runs with tsx. However, I wanted to experiment and see if it would also work if bundled. It did.

my-tool-latest
├── README.md
├── package.json                    # mtl(./bin/index.ts) and my-tool-latest(./dist/index.js) in bin
├── pnpm-lock.yaml
├── bin
│   ├── index.ts                    # Entry point for direct tsx execution, imports src/index.ts
│   └── my-tool-latest.js           # Entry point for bundle execution, imports dist/index.js
├── src                             # ✍🏻 TypeScript source directory. This is the workspace.
│   ├── index.ts                    # The actual entry point
│   ├── consola.ts
│   ├── fetchSpreadsheet.ts
│   ├── getFlatEvents.ts
│   └── registerEvents.ts
├── dist                            # Directory for bundled files
│   └── index.js
├── .env                            # Environment variable settings
├── .gitignore
├── .prettierrc
├── .vscode
│   └── tasks.json
├── client_secret_5xxxxxxxx8-vxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com.json # A key-like file for accessing Google services
└── tsup.config.ts

Conclusion

The npm command installation feature is fantastic. Say goodbye to brew Formulae.

Discussion