🛠️

TypeScriptを用いたnpxコマンドの開発とnpm公開手順

2025/01/10に公開

はじめに

npx コマンドを知っていますでしょうか?npxコマンドは、ローカルにインストールしなくても npm パッケージを実行できるコマンドです。

例えば、コード生成ツールやコードチェックの自動実行を、依存関係に影響を与えることなく実行できます。

本記事では、自作の npx コマンドを TypeScript で作成し、それを自身のスコープ(npmユーザー名)で、npm に公開する方法を解説します。npm公開することにより、他のユーザーも含めてオリジナルのコマンドを簡単に実行できるようになります。

参考: スコープを使うメリット

npm パッケージにスコープを使用することで、いくつかのメリットがあります。

  1. 名前の衝突を避ける
    スコープを使うことで、同じ名前のパッケージが他のパブリックパッケージと衝突するのを防ぐことができます。例えば、@atman-33/my-npx-command は他のパッケージ名と重なることがありません。

  2. パッケージの管理が容易
    スコープは、関連するパッケージをグループ化するために便利です。例えば、@atman-33 スコープ内で関連するツールやコマンドをまとめることができ、後から管理しやすくなります。

  3. 公開設定の柔軟性
    スコープ付きパッケージはデフォルトでプライベートになりますが、--access public オプションを使うことで、簡単にパブリックに公開できます。これにより、公開の際にアクセス管理がしやすくなります。

  4. チームや組織向けのパッケージ管理
    スコープは、チームや組織専用のパッケージを公開するための名前空間として使用できます。例えば、@atman-33 スコープ内のパッケージは、atman-33 というチームまたは組織に関連していることが明確になります。

本記事のサンプルパッケージ&コード
https://www.npmjs.com/package/@atman-33/my-npx-command

https://github.com/atman-33/my-npx-command

npxコマンド作成

1. npmアカウント作成

npmアカウントは、CLIから作成可能です。

npm adduser

上記のコマンド実行後、ブラウザ上のSign Up画面でアカウント登録を進めます。

image

2. プロジェクトの作成

プロジェクトの初期化

まず、新しいディレクトリを作成し、npm initを実行します。今回はスコープ付きとしました。

mkdir my-npx-command
cd my-npx-command
npm init --scope=atman-33 # 作成したnpmアカウント名と合わせること!

# --- 以下、npm init コマンド実行時の設定 ---
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (@atman-33/my-npx-command)  # npmパッケージとしてユニークにしておく
version: (1.0.0) 0.1.0   # セマンティックバージョンのルールに従って0.1.0から開始を推奨
description: sample npx command
entry point: (index.js)  # 不要なため後で削除
test command: 
git repository: 
keywords: 
author: atman-33
license: (ISC) MIT # 今回はMITを設定
About to write to /home/atman/sites/my-npx-command/package.json:

{
  "name": "@atman-33/my-npx-command",
  "version": "0.1.0",
  "description": "sample npx command",
  "main": "index.js",
  "directories": {
    "doc": "docs"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "atman-33",
  "license": "MIT"
}


Is this OK? (yes)

TypeScriptの設定

必要なパッケージをインストールします。

npm i -D typescript @types/node ts-node

gitを利用している場合は、node_modulesを含まないように.gitignoreを準備します。

.gitignore
/node_modules

次に、tsconfig.jsonを作成します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "CommonJS",
    "declaration": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "resolveJsonModule": true
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "src/**/*.test.ts"
  ]
}
補足: tsconfig.jsonの設定について
  1. target: "es2016"

    • このオプションは、コンパイル後の JavaScript のターゲットとなる ECMAScript のバージョンを指定します。ここでは es2016 を指定しており、ES6(ECMAScript 2015)以降の機能を使用しつつ、より新しい ES2016 に対応したコードに変換されます。
  2. module: "CommonJS"

    • このオプションは、モジュールシステムを指定します。CommonJS は、Node.js で一般的に使用されるモジュールシステムであり、他の JavaScript ツールやライブラリと互換性があります。
  3. declaration: true

    • このオプションを有効にすると、TypeScript が .d.ts ファイル(型定義ファイル)を生成します。このファイルは、TypeScript を使用していない JavaScript プロジェクトでも型チェックを可能にします。npm パッケージとして公開する場合、型情報を提供するために非常に重要です。
  4. sourceMap: true

    • このオプションを有効にすると、TypeScript のソースマップファイル(.map)も生成されます。これにより、トランスパイル後の JavaScript コードでエラーが発生した際に、元の TypeScript のソースコードとの対応関係を維持できます。デバッグ時に役立ちます。
  5. outDir: "./dist"

    • コンパイル後に出力される JavaScript ファイルを格納するディレクトリを指定します。ここでは ./dist フォルダに出力される設定になっています。
  6. rootDir: "./src"

    • ソースコードのルートディレクトリを指定します。ここでは ./src に指定しており、TypeScript コンパイラがこのディレクトリ内のコードをコンパイルします。
  7. esModuleInterop: true

    • このオプションは、importrequire の互換性を持たせるために使います。これを true に設定すると、CommonJS モジュールを ES6 モジュールの形式でインポートできるようになります。
  8. forceConsistentCasingInFileNames: true

    • ファイル名の大文字と小文字の一貫性を強制します。たとえば、import foo from './Foo'import foo from './foo' のようにファイル名の大文字小文字が一致しない場合、コンパイルエラーが発生します。これにより、異なるOS間で発生しうるファイルシステムの違いによるバグを防げます。
  9. strict: true

    • TypeScript の厳格な型チェックを有効にします。これにより、型安全が強化され、潜在的なバグを早期に発見しやすくなります。この設定は通常、TypeScript を使用する際におすすめされる設定です。
  10. skipLibCheck: true

    • 型定義ファイルのチェックをスキップします。通常、TypeScript はプロジェクトの依存関係に含まれる型定義ファイル(*.d.ts)をチェックしますが、このオプションを有効にすると、外部ライブラリの型定義ファイルのチェックをスキップできます。これによりコンパイル速度が向上することがあります。
  11. resolveJsonModule: true

    • JSON ファイルをモジュールとしてインポートできるようにする設定です。これを有効にすると、import config from './config.json' のように、JSON ファイルを直接 TypeScript でインポートできるようになります。
  12. include: ["src/**/*.ts"]

  • コンパイル対象として含めるファイルのパターンを指定します。ここでは、src フォルダ内のすべての TypeScript ファイル(.ts)がコンパイル対象になります。
  1. exclude: ["src/**/*.test.ts"]
  • コンパイル対象から除外するファイルのパターンを指定します。テスト用の TypeScript ファイル(.test.ts)が含まれる場合は、コンパイル対象から除外することで、不要なファイルを含まないようにできます。

3. コマンドの実装

src ディレクトリを作成し、その中に実行したいコードを記述します。

mkdir src

今回は、下記のようなディレクトリ構成で実装してみたいと思います。

+- src/
   |
   +- index.ts        # npxコマンドを登録
   |
   +- modules/
      |
      +- commands/
         |
         +- init.ts   # npxコマンド init の処理部分
         +- hello.ts  # npxコマンド hello の処理部分

commanderをインストール

Node.js 用コマンドラインインターフェース (CLI) ツールを構築するためのパッケージであるcommanderをインストールします。

npm i commander

src/index.tsの作成

まず、CLI のエントリーポイントとなるsrc/index.tsを作成します。このファイルでは commander を使い、サブコマンドの登録を行います。

src/index.ts
#!/usr/bin/env node
// ↑CLIツールとして実行するために必要

import { Command } from 'commander';
import { helloCommand } from './modules/commands/hello';
import { initCommand } from './modules/commands/init';

const program = new Command();

program
  .name('my-npx-command') // CLIツールの名前
  .description('sample npx command')
  .version('0.1.0');

// 各サブコマンドを登録
program.addCommand(initCommand);
program.addCommand(helloCommand);

// コマンドを実行
program.parse(process.argv);

src/modules/commands/init.tsの作成

initコマンドでは、専用のconfigファイルを生成します。

src/modules/commands/init.ts
import { Command } from 'commander';
import fs from 'fs';
import path from 'path';

const defaultConfig = {
  name: 'hoge',
};

export type Config = typeof defaultConfig;

export const initCommand = new Command('init')
  .description('Initialize my-npx-config.json')
  .action(() => {
    const filePath = path.resolve(process.cwd(), 'my-npx-config.json');

    if (fs.existsSync(filePath)) {
      console.error('my-npx-config.json already exists.');
      process.exit(1);
    }

    fs.writeFileSync(filePath, JSON.stringify(defaultConfig, null, 2), 'utf-8');
    console.log('my-npx-config.json has been created successfully.');
  });

src/modules/commands/hello.tsの作成

helloコマンドでは、configファイルに設定されたnameに対して、helloと呼びかけます。

src/modules/commands/hello.ts
import { Command } from 'commander';
import fs from 'fs';
import path from 'path';
import { Config } from './init';

export const helloCommand = new Command('hello').description('Say hello to someone.').action(() => {
  const filePath = path.resolve(process.cwd(), 'my-npx-config.json');

  if (!fs.existsSync(filePath)) {
    console.error(`Configuration file not found at ${filePath}`);
    process.exit(1);
  }

  const config: Config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
  console.log(`Hello, ${config.name}!`);
});

4. コマンドの動作確認

package.jsonの修正

package.jsonを修正して、動作確認用のスクリプトを追加します。

package.json
{
  "name": "@atman-33/my-npx-command",
  "version": "0.1.0",
  "description": "sample npx command",
- "main": "index.js",  // 不要なため削除
  // ...
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
+   "build": "tsc",
+   "start": "node dist/index.js",
+   "--- COMMAND SECTION ---": "---",
+   "init": "npm run build && npm run start init",
+   "hello": "npm run build && npm run start hello"
  },
  // ...
}

ビルドと実行

以下のコマンドで、CLIの動作を確認します。

# initコマンドをテスト
npm run init

# helloコマンドをテスト
npm run hello

期待通りの出力がされていれば成功です。

また、gitを利用している場合は、distを含まないように.gitignoreを修正しておきます。

.gitignore
/node_modules
/dist

5. npxコマンドの適用

package.jsonbin設定

npx コマンドとして使用するには、package.json にエントリを追加します。
このコマンドは、通常シンプルな形式が推奨されておりスコープ名は不要です。

package.json
{
  "bin": {
    "my-npx-command": "dist/index.js"
  }
}

これにより、my-npx-command コマンドがグローバルに利用可能になります。

ローカルでnpx動作確認

次に、ローカルで npx 経由で CLI をテストするために、リンクを作成します。

npm link

以下のようにコマンドを実行して動作を確認してください。

# initコマンド
npx my-npx-command init

# helloコマンド
npx my-npx-command hello

ビルドして実行した時と同様の動作となれば成功です。

npm公開

1. npmアカウントログイン

npmログインは、CLIで実行します。

npm login

2. .npmrcの設定

自作パッケージを誰でも扱えるように、.npmrcファイルを作成し、スコープをpublicに設定します。

.npmrc
access=public

3. npm公開用にpackage.jsonを修正

package.jsonに、npm公開用の設定を追加します。

設定しておくべき項目まとめ

  • name: パッケージの名前。npmレジストリでユニークである必要があります。
  • version: パッケージのバージョン。セマンティックバージョニングに従います。
  • description: パッケージの簡単な説明。検索結果に表示されます。
  • author: パッケージの作成者の名前と連絡先情報。
  • license: パッケージのライセンス情報。例: "MIT"。
  • keywords: パッケージを検索しやすくするためのキーワードのリスト。
  • repository: パッケージのソースコードリポジトリのURL。
  • homepage: パッケージのホームページのURL。
  • bin: コマンドラインツールとして使用する場合の実行ファイルのパス。
  • types: TypeScriptの型定義ファイルのパス。
  • files: npmに公開するファイルのリスト。

設定追加後のpackage.jsonは下記となります。

package.json
{
  "name": "@atman-33/my-npx-command",
  "version": "0.1.0",
  "description": "sample npx command",
  "directories": {
    "doc": "docs"
  },
  "author": "atman-33",
  "license": "MIT",
  "keywords": [
    "npx",
    "command"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/atman-33/my-npx-command.git"
  },
  "homepage": "https://github.com/atman-33/my-npx-command",
  "bin": {
    "my-npx-command": "dist/index.js"
  },
  "types": "dist/index.d.ts",
  "files": [
    "dist",
    "README.md",
    "package.json",
    "LICENSE"
  ],
  "scripts": {
   // ...
  }
}

追加した設定のfilesに含まれる、フォルダもしくはファイルがnpmに公開されます。
TypeScriptの場合、コンパイルされた中身であるdistフォルダは公開に必要ですが、srcは利用者には不要なため含めないのが基本です。

また、必要に応じてLICENSE(GitHubからテンプレートをベースに追加可能)、README.mdファイルを追加してください。README.mdは、公開したnpmパッケージのトップに表示されるため、作成しておくことを推奨します。

4. npm公開

下記のコマンドで公開します。

npm run build # 公開するのはdistフォルダ内のためソース変更時はビルド必要!
npm publish

5. 公開済みパッケージ更新

新しいバージョンを公開するには、バージョン番号を変更する必要があります。

バージョンを更新
npm には自動でバージョンを更新するコマンドがあります。

npm version [update_type]
[update_type] の値には以下を指定します:

patch: 小さなバグ修正(例: 1.0.0 → 1.0.1)
minor: 後方互換性のある新機能追加(例: 1.0.0 → 1.1.0)
major: 後方互換性のない変更(例: 1.0.0 → 2.0.0)

後は、先に説明した方法と同様にビルドしてからnpm publishを行ってください。

おわりに

以上の手順で、自作のnpxコマンドを作成し、npmに公開することができました。🎉

この記事を参考に、ぜひ便利なコマンドを作ってみてください!

Thinkingsテックブログ

Discussion