TypeScriptを用いたnpxコマンドの開発とnpm公開手順
はじめに
npx
コマンドを知っていますでしょうか?npx
コマンドは、ローカルにインストールしなくても npm パッケージを実行できるコマンドです。
例えば、コード生成ツールやコードチェックの自動実行を、依存関係に影響を与えることなく実行できます。
本記事では、自作の npx コマンドを TypeScript で作成し、それを自身のスコープ(npmユーザー名)で、npm に公開する方法を解説します。npm公開することにより、他のユーザーも含めてオリジナルのコマンドを簡単に実行できるようになります。
参考: スコープを使うメリット
npm パッケージにスコープを使用することで、いくつかのメリットがあります。
-
名前の衝突を避ける
スコープを使うことで、同じ名前のパッケージが他のパブリックパッケージと衝突するのを防ぐことができます。例えば、@atman-33/my-npx-command
は他のパッケージ名と重なることがありません。 -
パッケージの管理が容易
スコープは、関連するパッケージをグループ化するために便利です。例えば、@atman-33
スコープ内で関連するツールやコマンドをまとめることができ、後から管理しやすくなります。 -
公開設定の柔軟性
スコープ付きパッケージはデフォルトでプライベートになりますが、--access public
オプションを使うことで、簡単にパブリックに公開できます。これにより、公開の際にアクセス管理がしやすくなります。 -
チームや組織向けのパッケージ管理
スコープは、チームや組織専用のパッケージを公開するための名前空間として使用できます。例えば、@atman-33
スコープ内のパッケージは、atman-33
というチームまたは組織に関連していることが明確になります。
本記事のサンプルパッケージ&コード
npxコマンド作成
1. npmアカウント作成
npmアカウントは、CLIから作成可能です。
npm adduser
上記のコマンド実行後、ブラウザ上のSign Up画面でアカウント登録を進めます。
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
を準備します。
/node_modules
次に、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の設定について
-
target: "es2016"
- このオプションは、コンパイル後の JavaScript のターゲットとなる ECMAScript のバージョンを指定します。ここでは
es2016
を指定しており、ES6(ECMAScript 2015)以降の機能を使用しつつ、より新しい ES2016 に対応したコードに変換されます。
- このオプションは、コンパイル後の JavaScript のターゲットとなる ECMAScript のバージョンを指定します。ここでは
-
module: "CommonJS"
- このオプションは、モジュールシステムを指定します。
CommonJS
は、Node.js で一般的に使用されるモジュールシステムであり、他の JavaScript ツールやライブラリと互換性があります。
- このオプションは、モジュールシステムを指定します。
-
declaration: true
- このオプションを有効にすると、TypeScript が
.d.ts
ファイル(型定義ファイル)を生成します。このファイルは、TypeScript を使用していない JavaScript プロジェクトでも型チェックを可能にします。npm パッケージとして公開する場合、型情報を提供するために非常に重要です。
- このオプションを有効にすると、TypeScript が
-
sourceMap: true
- このオプションを有効にすると、TypeScript のソースマップファイル(
.map
)も生成されます。これにより、トランスパイル後の JavaScript コードでエラーが発生した際に、元の TypeScript のソースコードとの対応関係を維持できます。デバッグ時に役立ちます。
- このオプションを有効にすると、TypeScript のソースマップファイル(
-
outDir: "./dist"
- コンパイル後に出力される JavaScript ファイルを格納するディレクトリを指定します。ここでは
./dist
フォルダに出力される設定になっています。
- コンパイル後に出力される JavaScript ファイルを格納するディレクトリを指定します。ここでは
-
rootDir: "./src"
- ソースコードのルートディレクトリを指定します。ここでは
./src
に指定しており、TypeScript コンパイラがこのディレクトリ内のコードをコンパイルします。
- ソースコードのルートディレクトリを指定します。ここでは
-
esModuleInterop: true
- このオプションは、
import
とrequire
の互換性を持たせるために使います。これをtrue
に設定すると、CommonJS モジュールを ES6 モジュールの形式でインポートできるようになります。
- このオプションは、
-
forceConsistentCasingInFileNames: true
- ファイル名の大文字と小文字の一貫性を強制します。たとえば、
import foo from './Foo'
とimport foo from './foo'
のようにファイル名の大文字小文字が一致しない場合、コンパイルエラーが発生します。これにより、異なるOS間で発生しうるファイルシステムの違いによるバグを防げます。
- ファイル名の大文字と小文字の一貫性を強制します。たとえば、
-
strict: true
- TypeScript の厳格な型チェックを有効にします。これにより、型安全が強化され、潜在的なバグを早期に発見しやすくなります。この設定は通常、TypeScript を使用する際におすすめされる設定です。
-
skipLibCheck: true
- 型定義ファイルのチェックをスキップします。通常、TypeScript はプロジェクトの依存関係に含まれる型定義ファイル(
*.d.ts
)をチェックしますが、このオプションを有効にすると、外部ライブラリの型定義ファイルのチェックをスキップできます。これによりコンパイル速度が向上することがあります。
- 型定義ファイルのチェックをスキップします。通常、TypeScript はプロジェクトの依存関係に含まれる型定義ファイル(
-
resolveJsonModule: true
- JSON ファイルをモジュールとしてインポートできるようにする設定です。これを有効にすると、
import config from './config.json'
のように、JSON ファイルを直接 TypeScript でインポートできるようになります。
- JSON ファイルをモジュールとしてインポートできるようにする設定です。これを有効にすると、
-
include: ["src/**/*.ts"]
- コンパイル対象として含めるファイルのパターンを指定します。ここでは、
src
フォルダ内のすべての TypeScript ファイル(.ts
)がコンパイル対象になります。
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 を使い、サブコマンドの登録を行います。
#!/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ファイルを生成します。
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と呼びかけます。
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
を修正して、動作確認用のスクリプトを追加します。
{
"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
を修正しておきます。
/node_modules
/dist
5. npxコマンドの適用
package.json
のbin
設定
npx
コマンドとして使用するには、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
.npmrc
の設定
2. 自作パッケージを誰でも扱えるように、.npmrc
ファイルを作成し、スコープをpublicに設定します。
access=public
package.json
を修正
3. npm公開用にpackage.json
に、npm公開用の設定を追加します。
設定しておくべき項目まとめ
- name: パッケージの名前。npmレジストリでユニークである必要があります。
- version: パッケージのバージョン。セマンティックバージョニングに従います。
- description: パッケージの簡単な説明。検索結果に表示されます。
- author: パッケージの作成者の名前と連絡先情報。
- license: パッケージのライセンス情報。例: "MIT"。
- keywords: パッケージを検索しやすくするためのキーワードのリスト。
- repository: パッケージのソースコードリポジトリのURL。
- homepage: パッケージのホームページのURL。
- bin: コマンドラインツールとして使用する場合の実行ファイルのパス。
- types: TypeScriptの型定義ファイルのパス。
- files: npmに公開するファイルのリスト。
設定追加後の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
に公開することができました。🎉
この記事を参考に、ぜひ便利なコマンドを作ってみてください!
Discussion