sfwで依存パッケージのインストールを安全に ― 悪意あるパッケージをブロックする

はじめに

以前に Aikido Security が提供する npm 向けのサプライチェーン攻撃検出ツールを紹介しました。

その記事では npm エコシステムを狙った悪意あるパッケージや自己増殖型ワームの手口と、その検出・対処の重要性を取り上げました。今回の記事では、それら単一エコシステム向けの対策を踏まえた上で、より広範に適用できるツールとして Socket が公開した sfw(Socket Firewall)を取り上げます。

sfw は npm に限らず複数のパッケージ管理ツール(例:yarn、pnpm、pip、cargo など)で動作し、パッケージのダウンロード・インストール時にリアルタイムで検査して危険と判断したものをブロックします。単一言語の脅威に対処する手法を横展開するだけでなく、トランジティブ依存や CI 環境での導入を想定した運用面の利便性が特徴です。本稿では、前回の事例を参照しつつ、sfw の導入手順、実際の使い方、運用上の注意点を具体例で示します。導入検討者が短時間で安全性評価と導入判断を行えるように構成しています。

Socket Firewallとは

Socket Firewall(sfw) は、ソフトウェア・サプライチェーン攻撃を防ぐために Socket 社が開発した軽量セキュリティツールです。

既存の脆弱性スキャナや静的解析ツールと異なり、依存パッケージをインストールする瞬間に介入し、危険なパッケージをブロックする「リアルタイム防御層」として設計されています。

このツールは、開発者のマシンやCI環境で動作し、パッケージマネージャ(npm、yarn、pnpm、pip、uv、cargoなど)の通信を監視します。内部では一時的にHTTPプロキシを起動し、インストール要求をSocketのクラウド側データベースに照会します。もし既知のマルウェア、クリプトマイナー、バックドア、自己増殖型コード、あるいは疑わしいスクリプト挙動が検知されると、インストールをブロックして警告を出力します。

これにより、開発者が「気づかないうちに」危険な依存関係を組み込んでしまうリスクを防ぎます。

目的と特徴

Socket Firewallの狙いは、依存関係の安全性を「事後にスキャンして確認する」のではなく、事前に阻止する(shift-left security) ことです。従来のソースコードスキャンやパッケージ検証ツールは、脆弱性やリスクが既に環境に入り込んだ後に検出する仕組みでした。sfw はその前段階で動作し、不審なコードをインストール前に遮断します。

また、npm向けのツールにとどまらず、複数のパッケージ管理システムを横断的にサポートしている点も特徴的です。これは、Node.jsだけでなくPythonやRustなど、異なるエコシステムを扱う開発チームにとって大きな利点です。

単一言語専用のセキュリティ対策では防げなかった「マルチスタック開発環境」におけるサプライチェーン攻撃の防御を、統一的に実現します。

提供形態と位置づけ

Socket Firewall は無料で利用可能な「Free 版」が中心ですが、将来的には企業向けの「Enterprise 版」も予定されています。

Free 版では匿名テレメトリが有効で、利用状況や検出結果がSocketに送信されます。一方、Enterprise 版ではポリシー制御やプライベートレジストリ対応、テレメトリ無効化、可視化ダッシュボードなどの機能が追加される見込みです。

このように sfw は、開発フェーズの早い段階で不正コードの侵入を防ぐ “real-time package firewall” として位置づけられます。既存の脆弱性スキャンや署名検証と併用することで、サプライチェーン攻撃への多層防御(defense in depth)を実現します。

インストールと初期設定

Socket Firewall(sfw)は、Socket社が提供するクロスプラットフォーム対応のCLIツールです。

npm、pip、cargoなど複数のパッケージマネージャの通信を横断的に監視し、インストール時点で悪意のあるパッケージを検知・遮断します。

ここでは、公式の手順に基づき、導入から初期設定、動作確認までを詳細に説明します。

インストール方法

Socket Firewallはnpmパッケージとして提供されており、次のコマンド一つで導入できます。

npm i -g sfw

このコマンドでCLIバイナリがグローバルインストールされ、sfwコマンドとしてシステムPATHに登録されます。

バージョン確認

インストール後、以下のコマンドでバージョンを確認します。

$ sfw --version
✔ downloading latest sfw binary...
Socket Firewall Free, version 0.12.17

バージョンが表示されれば正常にセットアップされています。

エラーが出る場合は、npm list -g sfwでインストール状態を確認してください。

Socket Firewallは、設定ファイルも特別な権限も不要で、わずか1コマンドで導入できる点が最大の強みです。

CI/CD環境での利用例

Socket Firewall(sfw)はローカル開発環境だけでなく、CI/CDパイプラインにも容易に組み込める設計になっています。特に、依存パッケージを自動で取得するビルドジョブやデプロイ前検証プロセスでは、インストール時点でのリアルタイム検査がセキュリティリスク低減に非常に有効です。

ここでは、GitHub Actionsでの導入方法を示します。

GitHub Actions での利用

Socket 公式が SocketDev/action というアクションを提供しています。

npmやyarnなど、Node.jsベースの依存関係インストールを行うステップをsfw経由に置き換えるだけで利用可能です。

on: push

jobs:
  job-id:
    # Socket Firewall supports Linux, Windows, and macOS
    runs-on: ubuntu-latest
    steps:
      # add Socket Firewall to the runner environment
      - uses: socketdev/action@v1
        with:
          mode: firewall
          firewall-version: latest # or use a pinned version (see releases)
            
      # setup your project (e.g. checkout, setup-node, etc...)
      - uses: actions/checkout@v5
      
      # example usage
      - run: sfw npm ci
      - run: sfw npm install lodash
      - run: sfw pip install requests

上記例では、npm installコマンドが sfw npm installに変更することで、全ての依存関係がSocket Firewallの検査を経てからインストールされます。悪意のあるパッケージが検出された場合はステップが失敗し、ビルド全体が中断されます。

これにより、リポジトリへの不正パッケージ混入をパイプライン段階で防止できます。

まとめ

Socket Firewall は、依存関係の安全性を「後から確認する」のではなく「インストール時に防ぐ」というアプローチを実現します。

npmを標的とした自己増殖型ワーム「Shai-Hulud」によるサプライチェーン攻撃により、パッケージに感染したワームやマルウェアによってGitHubなどのトークンを盗まれるリスクが高まっています。ローカル環境ではウィルス対策ソフトなどによって防げる場合がありますが、CI/CD環境ではそういったソフトウェアがインストールされていないため防ぐことが困難です。

Socket Firewall は悪意のあるプログラムをインストールする前にチェックし、インストールすることをブロックしてくれます。Aikido Securityが提供するツールと同様、開発環境にもCI環境にも簡単に導入できるため、サプライチェーン攻撃への一次防御として非常に有効です。

参考文献

npm史上最大規模となる自己増殖型ワーム「Shai-Hulud」によるサプライチェーン攻撃

はじめに

2025年9月15日、JavaScript の主要パッケージエコシステムである npm において、これまでにない深刻なサプライチェーン攻撃が発覚しました。攻撃に使われたマルウェアは「Shai-Hulud」と名付けられており、その特徴は単なるパッケージ改ざんにとどまらず、自己伝播(ワーム)機能を備えている点にあります。これにより、感染したパッケージを利用した開発環境や CI/CD 環境から認証情報を奪い取り、さらに別のパッケージを自動的に改ざんして公開するという、従来の攻撃よりもはるかに広範な拡散が可能となりました。

被害は短期間で拡大し、確認されただけでも 200件近い npm パッケージが改ざんされており、その中には広く利用される有名ライブラリや大手企業関連のパッケージも含まれていました。OSS エコシステムは世界中の開発者や企業が依存する基盤であり、サプライチェーン攻撃は一部のパッケージ利用者だけでなく、そこからさらに派生する数多くのソフトウェアやサービスへ影響を与える可能性があります。

今回の Shai-Hulud 攻撃は、サイバー攻撃者がいかに OSS エコシステムを効率的な攻撃対象と見なしているかを改めて示すものであり、npm を利用するすべての開発者・組織にとって重大な警鐘となっています。本記事では、攻撃の概要や技術的な特徴を整理するとともに、想定されるリスクと具体的な対応方法について解説します。

背景

近年、ソフトウェアサプライチェーン攻撃は頻度と巧妙性を増しています。オープンソースパッケージは多くのプロジェクトで基盤的に利用されており,単一の改ざんが間接的に多数のシステムへ波及するリスクを常に伴います。特に JavaScript/npm エコシステムでは依存関係の深さと枝分かれが大きく,一つの小さなユーティリティが数千の最終アプリケーションに取り込まれることが珍しくありません。結果として,攻撃者は影響範囲を指数的に拡大できる利点を得ます。

npm は公開・配布のプロセスを自動化するためにトークンや CI ワークフローに依存していますが,これらは適切に管理されないと大きな攻撃面となります。長期有効の publish トークン,権限が過大な CI ランナー,組織共有の認証情報は侵害時に「自動で書き換える」「自動で公開する」といった自己伝播的な悪用を可能にします。加えて,postinstall 等の実行時フックはビルドや開発環境で任意コードを実行するため,ここに悪意あるコードが紛れ込むと検出が遅れやすい設計上の脆弱性があります。

運用面でも課題があります。開発者は多数の依存を素早く取り込みたいため,package-lock による固定や署名付き配布を怠りがちです。企業では利便性のためにトークンを共有したり,CI 用イメージやランナーを長期間使い回したりする運用が残存します。これらの実務的な慣行は,攻撃者にとって短時間で大規模な被害を生む温床となります。

過去のサプライチェーン攻撃の教訓から,検出と封じ込めには「開発環境・CI・レジストリ」の三点同時対応が必要であることが分かっています。Shai-Hulud のように自己伝播性を持つ攻撃は,これら三領域のいずれか一つでも緩みがあると急速に広がります。したがって,本件は単なるパッケージ単位の問題ではなく,組織の開発・配布プロセス全体を見直す契機として位置づけるべき事象です。

攻撃の技術的特徴

初期侵入

攻撃者は npm の publish トークンや GitHub の Personal Access Token(PAT)などの認証情報を取得して改ざんに利用しました。トークン取得経路としてはフィッシングや公開設定ミス、漏洩した CI 設定などが想定されます。これらのトークンはパッケージ公開権限を直接与えるため,侵害されると改ざんが短時間で実行され得ます。

改ざん手法

改ざん版には postinstall フックやバンドル化された実行スクリプト(例:bundle.js)が組み込まれます。npm install 時に自動実行されるため,開発者や CI が気づきにくく,ビルド段階でコードが動作する設計上の盲点を突きます。

情報収集と流出

実行スクリプトはローカル環境(環境変数、.npmrc 等)とクラウド環境(IMDS 等のメタデータエンドポイント)を探索して認証情報を収集します。収集したデータは攻撃者管理下の GitHub リポジトリやハードコードされた webhook にコミット/POST される仕組みが確認されています。

自己伝播(ワーム化)

感染した環境に残る有効トークンを悪用し,攻撃者は他パッケージを自動で改ざん・公開します。依存関係を介して連鎖的に拡散する点が本件の特徴です。短命で終わらない仕組みになっているため封じ込めが難しくなります。

持続化と権限操作

攻撃スクリプトは GitHub Actions ワークフローを追加したり,リポジトリを private→public に変更するなどして持続化と露出拡大を図ります。これにより単発検出後も再侵害や情報漏えいが継続するリスクが残ります。

検出困難性と難読化

実行コードはバンドル・難読化され,ファイル名やワークフロー名を変えることで痕跡を隠します。postinstall の存在自体が通常の開発フローの一部であるため,単純な目視だけでは発見されにくい設計です。

想定される影響と懸念

1. 認証情報・機密情報の流出と二次被害

改ざんされたパッケージの postinstall や実行スクリプトが開発端末・CI・クラウドのメタデータからトークンやキーを収集し外部に送信します。流出した認証情報は即時に不正利用され、以下の二次被害を引き起こす可能性があります。

  • リポジトリの不正操作(コミット、ワークフロー変更、公開設定切替)によるさらなる改ざん。
  • クラウド資源の不正利用(インスタンス起動、ストレージ操作、データベースアクセス)。
  • サードパーティサービスの乗っ取り(npm、CIサービス、サードパーティAPI)。

2. 依存関係を介した連鎖的な感染拡大

npm の依存グラフは深く広いため、ワーム的に拡散すると多数のプロジェクトに連鎖的影響が及びます。特に共有ライブラリやユーティリティが汚染されると、最終的な配布物や商用サービスにもマルウェアが混入するリスクが高まります。結果として被害の「範囲」と「追跡可能性」が急速に拡大し、封じ込めコストが指数的に増加します。

3. ビルド・デプロイチェーンの汚染と運用停止リスク

CI/CD パイプラインやビルドアーティファクトにマルウェアが混入すると、デプロイ先環境まで影響が及びます。企業は安全確認のためにビルド/デプロイを一時停止せざるを得なくなり、サービス停止やリリース遅延、ビジネス機会の損失につながります。

4. 検出困難性と長期的残存リスク

postinstall 実行や難読化されたスクリプトは発見が遅れやすく、感染が既に広がった後で検出されるケースが多くなります。さらに、改ざんコードが複数ファイルやワークフローに分散して保存されると、完全除去が難しく「再発」や「潜伏」が残るリスクがあります。

5. 信頼性・ブランド・法務的影響

顧客やパートナーに供給するソフトウェアにマルウェアが混入した場合、信頼失墜や契約違反、損害賠償請求につながる可能性があります。規制業界(金融・医療など)では報告義務や罰則が発生する場合があり、法務・コンプライアンス対応の負荷が増します。

6. インシデント対応コストと人的負荷

影響範囲の特定、シークレットのローテーション、CI の再構築、監査ログ解析、顧客対応など、対応工数とコストは大きくなります。特に短時間で多数のチーム・プロジェクトにまたがる場合、人的リソースの逼迫と対応優先順位の決定が課題となります。

7. 長期的なサプライチェーン健全性の劣化

繰り返しの改ざん事件は OSS 利用に対する過度な懸念を生み、外部ライブラリの採用抑制や自家製化(in-house)への回帰を促す可能性があります。これにより開発効率が低下しエコシステム全体の健全性に悪影響が及ぶ恐れがあります。

8. 観測・検出のギャップによる見落とし

短時間に大量の npm publish やワークフロー変更が行われた場合でも、既存の監視ルールでは閾値を超えるまで気付かない運用が珍しくありません。ログ保持期間やログの粒度が不十分だと、フォレンジック調査の精度が低下します。

マルウェアのチェック方法


セキュリティ専門企業のAikido Securityから対策パッケージが提供されています。

特徴

  • npmやyarnなどのパッケージマネージャのコマンドをラップし、パッケージインストール前にマルウェアチェックを実施します。
  • チェックはAikido Intelというオープンソースの脅威インテリジェンスに照らし合わせて検証します。
  • デフォルトではマルウェアが検出されるとインストールをブロックしてコマンドを終了します。これはユーザーに許可を求めるモードにも設定変更可能です。
  • 対応シェルは、Bash、Zsh、Fish、PowerShell、PowerShell Core
  • Node.js 18以上に対応してます。

といった特徴を持っています。

使い方

npmコマンドを使ってAikido Security Chainパッケージをインストールします。

$ npm install -g @aikidosec/safe-chain

added 110 packages in 6s

29 packages are looking for funding
  run `npm fund` for details

次に以下のコマンドを実行してシェル統合を設定します。

$ safe-chain setup
Setting up shell aliases. This will wrap safe-chain around npm, npx, and yarn commands.

Detected 3 supported shell(s): Zsh, Bash, Fish.
- Zsh: Setup successful
- Bash: Setup successful
- Fish: Setup successful

Please restart your terminal to apply the changes.

使用できるようにするにはターミナルを再起動してください。exec $SHELL -lでも動作しました。

インストールができたかどうかは以下のコマンドで確認します。インストールしようとしているパッケージはマルウェアとしてフラグされている無害なパッケージでシェル統合が成功しコマンドが正常にラップされている場合はブロックが成功します。

$ npm install safe-chain-test
✖ Malicious changes detected:
 - safe-chain-test@0.0.1-security

Exiting without installing malicious packages.

成功すればマルウェアのチェックが有効になっています。このチェックはパッケージのインストール時に行われるため、すでにプロジェクトがある場合は、一旦node_modulesを削除してからnpm installしてください。

$ rm -rf node_modules
$ npm install
✔ No malicious packages detected.
npm warn deprecated source-map@0.8.0-beta.0: The work that was done in this beta branch won't be included in future versions

added 312 packages, and audited 313 packages in 2s

73 packages are looking for funding
  run `npm fund` for details

1 low severity vulnerability

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

これは私が開発中のライブラリで試した結果です。基本的に外部ライブラリに依存していないので、マルウェアは検出されませんでした。

一部開発用パッケージやMCP関連パッケージも標的になっていたので、グローバルインストールされているパッケージについても確認してください。グローバルパッケージの場合は対象パッケージを再度インストールすることでチェックができます。

アンインストール

アンインストールする場合は、

safe-chain teardown

でエイリアスを除去し、

npm uninstall -g @aikidosec/safe-chain

でパッケージをアンイストールし、ターミナルを再起動してください。

CI/CDへの組み込み方法

CI/CDへの組み込み方法についてもガイドされています。

マルウェアを検知するためにディスクをスキャンするため、多くのパッケージに依存している場合はCIにかかる時間の増大やコスト増大を招く場合があります。影響を考慮して導入の可否を判断してください。

GitHub Actionsでの組み込み方法について見ていきます。

私が実際に使っている.github/workflows/ci.yamlは以下のようになっています。

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - run: npm install -g @aikidosec/safe-chain
 
      - run: npm install
      - run: npm run lint
      - run: npm run build --if-present
      - run: npm test

npm installを行う前にパッケージをインストールします。

      - run: npm install -g @aikidosec/safe-chain

      - run: npm install

次にnpm installのコマンドをaikido-npmに差し替えます。

      - run: aikido-npm install

これらの修正を行なった.github/workflows/ci.yamlは以下のようになります。

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - run: npm install -g @aikidosec/safe-chain

      - run: aikido-npm install
      - run: npm run lint
      - run: npm run build --if-present
      - run: npm test

以下はGitHub Actionsの実行結果の抜粋です。マルウェアのチェックが成功していることが確認できます。

Run aikido-npm install
Scanning 312 package(s)...
No malicious packages detected.
npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE   package: '@isaacs/balanced-match@4.0.1',
npm warn EBADENGINE   required: { node: '20 || >=22' },
npm warn EBADENGINE   current: { node: 'v18.20.8', npm: '10.8.2' }
npm warn EBADENGINE }

CI/CDへの組み込みも比較的簡単に実施可能です。

まとめ

今回の Shai-Hulud 攻撃は、npm エコシステムにおけるサプライチェーンの脆弱性を突いた、これまでにない規模と性質を持つ事例でした。単なるパッケージ改ざんにとどまらず、インストール時に自動実行されるスクリプトを利用して認証情報を盗み取り、その情報を活用して別のパッケージを改ざんするという「自己伝播」の性質を持つ点が特に深刻です。これにより、短期間で数百件規模のパッケージが感染する事態となり、ソフトウェアサプライチェーン全体の信頼性に大きな影響を与えました。

本記事では、攻撃の仕組みと影響だけでなく、実際に開発者や企業が取るべき対応についても整理しました。具体的には、感染パッケージの特定と除去、シークレットの全面ローテーション、CI/CD 環境のクリーン再構築、リポジトリやログの監査といった即時対応が必須です。さらに、長期的には権限の最小化や ephemeral runner の利用、SBOM の生成とソフトウェアコンポーネント解析、そして Aikido Safe Chain のようなマルウェア検証ツールの活用など、セキュリティを日常の開発プロセスに組み込む工夫が欠かせません。

特に、CI/CD への統合は鍵となります。開発者が手動で確認するだけでは限界があるため、依存関係の取得やビルドのたびに自動でパッケージを検証し、IOC や脅威インテリジェンスに基づいてブロックする仕組みを導入することで、攻撃の拡大を未然に防げます。OSS に依存する開発体制を維持する以上、こうした仕組みは「特別な対策」ではなく「標準的な衛生管理」として定着させる必要があります。

Shai-Hulud は終息したインシデントではなく、今後の攻撃者の戦術を示す前兆と捉えるべきです。攻撃はますます自動化・巧妙化し、検出をすり抜けて広範囲に広がることが想定されます。したがって、本件を単なる一過性の脅威と見るのではなく、ソフトウェアサプライチェーン防御の基盤整備を加速させるきっかけとすることが重要です。OSS エコシステムと開発者コミュニティの健全性を守るためには、開発者一人ひとりがセキュリティ意識を高め、組織全体として持続的な監視と改善の仕組みを整備していくことが求められます。

参考文献

LaravelにTailwind CSSをインストールする

基本的には公式の手順に従っていきます。公式の手順とは異なり、sail環境で実行するため、すべてのコマンドにsailがついています。また、Viteを使います。

Tailwind CSSをインストールする

すでにnpm installしてある状態で、次のコマンドを実行します。

$ sail npm install -D tailwindcss postcss autoprefixer
$ sail npx tailwindcss init -p 

コマンドを実行すると、tailwind.config.jspostcss.config.jsが作成されます。

テンプレートのパスを設定する

tailwind.config.jsを編集してテンプレートファイルのパスを追加します。

修正前のtailwind.config.jsは次のようになっています。

/** @type {import('tailwindcss').Config} */
export default {
  content: [],
  theme: {
    extend: {},
  },
  plugins: [],
}

contentを次のように編集します。

/** @type {import('tailwindcss').Config} */
export default {
  content: [
      "./resources/**/*.blade.php",
      "./resources/**/*.js",
      "./resources/**/*.vue",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

公式の手順どおりに編集していますが、Vue.jsを使わないのであれば最後の1行は不要です。

CSSファイルにTailwindディレクティブを追加する

@tailwindディレクティブをresources/css/app.cssに追加します。

@tailwind base;
@tailwind components;
@tailwind utilities;

ビルドプロセスを開始する

すでにnpm run devを実行中の場合は一度停止してから再度実行してください。

$ sail npm run dev

これでインストール作業は完了し、Tailwind CSSが使用可能な状態になります。

Tailwind CSSを使ってみる

では実際に使ってみましょう。

welcome.blade.phpを修正して機能しているかを確認します。

まずは、<head>タグに@vite('resources/css/app.css')を埋め込みます。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
      ...
      @vite('resources/css/app.css')
      ...
    </head>

これで使用する準備が整いましたが、welcome.blade.phpの場合、すでに<style>タグでTailwind CSSが埋め込まれているので、これをコメントアウトしておきます。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        ...
        <!-- Styles -->
        <!-- style>
            /* ! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com */ ...
        </style -->
  </head>

この修正は他のBladeファイルでは不要です。

では実際に使ってみます。

公式サイトの手順にあった<h1>タグを記述します。

        <h1 class="text-3xl font-bold underline">
            Hello World!
        </h1>

画面を表示してみて、

のように表示され、デベロッパーツール上でも

のようにapp.cssに定義されたTailwind CSSのクラスが適用されていることが確認できたら正常にインストールできています。

Biomeを使ってLintとFormatをおこなう

フロンドエンド向け垂直統合ツールチェーンRomeが開発終了となり、Rome開発チームのひとりが作成したのがBiomeです。

Rustで構築されたBiomeは既存のFormatter、Linterと比べ非常に高速に動作するように設計されています。設定も非常に簡単でエディタの拡張機能・プラグインも提供されていることから今後のデファクトスタンダードになりそうな予感があります。

本記事ではBiomeのインストールから基本的な使い方、エディタの統合について解説します。

Biomeをインストール

Biomeはグローバルインストールすることは可能ですが推奨されていないため、プロジェクトにインストールします。また、バージョン範囲演算子を使用しないでインストールすることを強く推奨しています。

$ mkdir biome-example
$ cd biome-example
$ npm init -y
$ npm install --save-dev --save-exact @biomejs/biome
$ npx @biomejs/biome init

Node.jsをインストールせずにBiomeを使用したい場合は、Homebrewなどを使ってインストールするスタンドアロン実行形式での利用も可能ですが、本記事では割愛します。

インストール後に初期化をおこなうとbiome.jsonが生成されます。

{
  "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}

基本的な設定は済んでいるので、すぐに使い始められます。

Biomeの使い方

Biomeの基本的な使い方を見ていきます。

Formatter

Prettierのようにソースコードを整形してくれます。指定したディレクトリ内のファイルに対してソースコードの整形をおこなうには次のようなコマンドを実行します。

$ npx @biomejs/biome format src --write

このコマンドではsrcディレクトリを指定してフォーマットを行っていますが、単一ファイルやワイルドカード指定も可能です。

Linter

こちらはESLintの置き換えになります。次のようなコマンドを実行します。

$ npx @biomejs/biome lint src

こちらも単一ファイルの指定やワイルドカードでの指定が可能です。

Formatter+Lint+α

前述のFormatterとLinterに加えて、インポート文の再構成をまとめて実行するには次のようなコマンドを実行します。

$ npx @biomejs/biome check --apply src

通常はこのコマンドを使うことになると思います。

package.jsonにスクリプトを設定する

コマンドが少し長いのでpackage.jsonにスクリプトとして定義しておきましょう。

{
  "name": "biome-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "format": "biome format src --write",
    "lint": "biome lint src",
    "check": "biome check --apply src"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@biomejs/biome": "1.5.3"
  }
}

Visual Studio Codeの拡張機能を使う

開発時に都度コマンド入力するのは手間ですので、エディタ向けの拡張機能を利用するのがよいでしょう。

まずはVisual Studio Codeの拡張機能を使ってみます。

settings.jsonに以下の設定を追加します。

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "editor.codeActionsOnSave": {
    "quickfix.biome": true,
    "source.organizeImports.biome": true
  }
}

以下の設定で、保存時にFormatterが実行されるように設定しています。

  "editor.formatOnSave": true,

デフォルトのFormatterはPrettierにしています。

  "editor.defaultFormatter": "esbenp.prettier-vscode",

言語モードがJavaScriptの場合、FormatterはBiomeになるよう設定しています。

  "[javascript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },

保存時のアクションに、 Biomeによるフォーマット("quickfix.biome": true)とインポート文の再構成("source.organizeImports.biome": true)をおこなうように設定します。

この設定を行うことで、ファイルの保存時にソースコードの整形とインポート文の再構成をおこなってくれるようになります。

WebStormのプラグインを使う

IntelliJ向けのプラグインも提供されていますので、WebStormで使ってみます。

こちらは⌥⇧ ⌘ L or Ctrl+Alt+Lでファイルの整形ダイアログを表示して必要なオプションにチェックを入れて実行ボタンを押します。

まとめ

Biomeのインストール方法からエディタとの統合までを確認しました。 1つのパッケージで導入できてデフォルトで利用可能となっているだけでなく、エディタとの統合もサポートされているため、小さく始めて大きく育てるには非常に適したツールチェーンだといえます。

これまではPrettierとESLintを使っていましたが今後はBiomeを使ってみようと思います。

textlintで文章の校正を行う

文章を書く時、文法的な誤りや用語の揺らぎは避けたいものです。しかし、自分だけでは気づかないところがあったり、煩雑すぎたりする場合があります。そこで、textlintというツールを使えば、文章の校正を行うことができます。本記事では、textlintについて詳しく紹介し、文章の校正に役立てる方法を解説します。

今回試したコードはhttps://github.com/t0k0sh1/emendationにあります。

textlintがチェック対象としているのは、テキストファイルやマークダウンファイルなどのテキストファイルのみです。WordやExcelなどのファイルを処理する場合はテキストを抽出するなどの工夫をする必要があります。

textlintの導入

自然言語向けのLinterであるtextlintを導入します。

ここでは、校正チェックを行うプロジェクトemendationを作成します。textlintはNode.jsで利用可能はパッケージですので、npm initコマンドで初期化しておきます。

$ mkdir emendation
$ cd emendation
$ npm init -y

textlintのインストール

textlintをインストールします。特段前提条件となるパッケージやライブラリはなく、普通にインストール可能です。

$ npm install textlint

ルールプリセットのインストール

次にルールプリセットをインストールします。技術書や技術系の記事、設計書の執筆を行っている場合は、技術書向けのルールを使用するのがよいでしょう。

$ npm install textlint-rule-preset-ja-technical-writing

lintスクリプトの設定

package.jsonlintを実行できるスクリプトを追加します。filesディレクトリ内にあるテキストファイルを対象にするようにしています。

  "scripts": {
    "lint": "textlint ./files/*.txt"
  },

ルールの適用方法

textlintで使用するルールを適用する方法には、以下の2つの方法があります。

  1. textlintコマンドに—presetオプションで使いたいルールを指定する
  2. .textlintrc.jsonに適用するルールを記述する

実用上は細かいカスタマイズを必要とするため、後者の方法でルールを適用するようにします。

まずは、textlint --initコマンドで.textlintrc.jsonファイルを作成します。

$ textlint --init
.textlintrc.json is created.

コマンドを実行すると以下のようなファイルが生成されます。

{
  "plugins": {},
  "filters": {},
  "rules": {}
}

生成時点ではルールは何も設定されていません。先ほどインストールしたルールプリセットを適用する記述を追加します。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": true
  }
}

動作を確認するために、filesディレクトリに以下のようなsample.txtを作成します。

Vueで作成した確認ダイアログを表示し、OKボタンをクリックしたときだけ処理を続行する方法をボタンクリックの場合とフォームを使ったサブミットの2パターンの実装方法を見ていきました
この考え方は他のフレームワークでも適用可能です。今回はVue 3.3.4で動作確認していますが、Vue 3で実装された機能を使っていないため、Vue 2でも動作すると思います。

このファイルは1行目の「。」が漏れているので、この点が検出されると思います。

では、実際に実行してみます。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:89  error  文末が"。"で終わっていません。           ja-technical-writing/ja-no-mixed-period
  2:84  error  弱い表現: "思います" が使われています。  ja-technical-writing/ja-no-weak-phrase

✖ 2 problems (2 errors, 0 warnings)

2つのエラーが検出されました。

1つは想定どおり「。」が漏れていることを検出していますが、もう1つは想定していなかったエラーです。このエラーは「〜だと思う」という弱い言い回しを検出しているようです。

ルールのカスタマイズ①不要なルールの抑止

不要なルールの抑止の説明を行うためにja-no-weak-phraseのルールを無効化してみます。以下のように記述することでルールを無効化できます。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase": false
    }
  }
}

では、再度実行してルールが抑止されていることを確認します。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:89  error  文末が"。"で終わっていません。  ja-technical-writing/ja-no-mixed-period

✖ 1 problem (1 error, 0 warnings)

先ほどまで検知されていたja-no-weak-phraseのルールが検知されなくなっていることが確認できました。

ルールのカスタマイズ②独自の辞書ルールの追加

辞書ルールを定義するファイルを作成することで、独自の辞書ルールを追加することが可能です。

ファイル名に制約はないですが、ここではprh.yamlファイルを作成します。

まずはルールを使用するように定義を修正します。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase": false
    },
    "prh": {
      "rulePaths": ["./prh.yml"]
    }
  }
}

次に辞書ルールを定義します。

version: 1
rules:
  - expected: View
    patterns:
      - Vue

ここでは動作を確認するために、ViewVueとタイポする想定でルールを作成しています。expectedに正しい書き方、patternsに誤った書き方を記述します。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  Vue => View                     prh
  1:89  error    文末が"。"で終わっていません。  ja-technical-writing/ja-no-mixed-period
  2:28  ✓ error  Vue => View                     prh
  2:49  ✓ error  Vue => View                     prh
  2:72  ✓ error  Vue => View                     prh

✖ 5 problems (5 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

Vueがエラーとして検出できることを確認しました。

辞書ルールを定義することで、固有名詞の揺らぎや言い回しの不統一などを検知することが可能になります。

辞書ルール定義の発展した方法

辞書ルールが増えたときに管理が煩雑になりがちです。これを解消するために辞書ルールを分割することを考えます。

以下のように記述することで、ルールを外出しすることが可能です。

version: 1

imports:
  - ./rules/すでに.yml
  - ./rules/かつ.yml
  - ./rules/また.yml

さらに各ルールを指定パスに定義しておきます。

version: 1
rules:
  - expected: すでに
    patterns:
      - 既に
version: 1
rules:
  - expected: かつ
    patterns:
      - /且つ|且/
version: 1
rules:
  - expected: また$1
    patterns:
      - /又(、|は|は、)/
specs:
  - from: 又
  - to: また

この例は、副詞をひらく書き方を正として、ひらいていない書き方を検出するルールとなります。

以下のように例文を用意して動作確認します。

既に会議は始まっており、私たちは遅れている。且つ、プレゼンテーション資料もまだ完成していない。又は、次の会議までに資料を仕上げ、改めて説明することも一つの選択肢だ。

以下が実行結果です。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  既に => すでに                                                                                                  prh
  1:23  ✓ error  且つ => かつ                                                                                                      prh
  1:48  ✓ error  又は => または                                                                                                  prh
  1:75  ✓ error  一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers

✖ 4 problems (4 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

辞書ルールで定義した箇所が検出されることが確認できます。

自動訂正を行う

textlintにはルールによって自動訂正を行うことができます。–fixオプションを使うことで、訂正可能なルールについて自動的に訂正を行うことが可能です。

package.jsonlint:fixスクリプトを追加します。

  "scripts": {
    "lint": "textlint ./files/*.txt",
    "lint:fix": "textlint --fix ./files/*.txt"
  },

先ほどチェックしたテキストに対して、lint:fixコマンドを実行することで、自動訂正されることを確認します。

まずは、修正前のテキストを再掲します。

既に会議は始まっており、私たちは遅れている。且つ、プレゼンテーション資料もまだ完成していない。又は、次の会議までに資料を仕上げ、改めて説明することも一つの選択肢だ。

再度、チェックのみを行います。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  既に => すでに                                                                                                  prh
  1:23  ✓ error  且つ => かつ                                                                                                      prh
  1:48  ✓ error  又は => または                                                                                                  prh
  1:75  ✓ error  一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers

✖ 4 problems (4 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

検出したエラーのうち、チェックマークがついているものが自動訂正可能なものです。できないものもありますが、今回検出したエラーはすべて自動訂正可能です。

では、lint:fixコマンドを実行して自動訂正します。

$ npm run lint:fix

> emendation@1.0.0 lint:fix
> textlint --fix ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:75  ✔   一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers
  1:1   ✔   既に => すでに                                                                                                  prh
  1:23  ✔   且つ => かつ                                                                                                      prh
  1:48  ✔   又は => または                                                                                                  prh

✔ Fixed 4 problems

メッセージからも訂正されたことが確認できますが、修正後のテキストも確認しておきます。

すでに会議は始まっており、私たちは遅れている。かつ、プレゼンテーション資料もまだ完成していない。または、次の会議までに資料を仕上げ、改めて説明することも1つの選択肢だ。

検知された点が修正されていることが確認できました。

ルールを書くときの注意点(正規表現)

「かつ」が正しい書き方だとした場合、誤った書き方は「且つ」または「且」となります。このような場合、正規表現で長い方を先に書くようにすることで、「且つ」を「かつつ」に訂正しないようにできます。

version: 1
rules:
  - expected: かつ
    patterns:
      - /且つ|且/

また、「又」が誤った書き方で「また」に訂正したい場合、「又」が別の単語で使われていてそちらを訂正しないようにするためには、正規表現で接続詞としての「又」のみを検知しつつ、適切に訂正できるようになります。この例では、正規表現(、|は|は、)部分は$1で充当されるため、「又は」は「または」に訂正されるようになります。

version: 1
rules:
  - expected: また$1
    patterns:
      - /又(、|は|は、)/

正規表現の設定ミスなどにより思わぬ訂正が行われる可能性もあります。必ずチェックで正しく検出できているかを確認のうえ、訂正するようにしてください。

まとめ

textlintは、文章の自動校正ツールとして非常に優れています。自己チェックと併用してtextlintを使うことで、より良い文章を効率的に作成することができるようになるでしょう。ぜひ、textlintを使ってみてください。

Node.jsでTypeScriptの環境を構築する

Node.jsでTypeScriptの環境を構築します。

構築する環境について

本記事では、以下のバージョンで構築しています。

$ node -v
v18.12.1
$ npm -v
9.1.2

以降の手順で、 Node.jsのバージョンが必要となりますので、必ず確認してください。

TypeScriptをインストールする

TypeScriptをインストールしていきます。

$ npm install -D typescript @types/node@18

使用しているバージョンがv18.12.1ですので、@typesパッケージも同じバージョンのものを指定します。

インストールできたらインストールされているバージョンを確認します。

$ npx tsc --version
Version 4.9.4

上記のようにtscコマンドを使ってバージョンが表示できればインストールは完了しています。

tsconfig.jsonを作成する

次にTypeScriptの設定ファイルのtsconfig.jsonを作成します。まずは、以下のコマンドを実行してファイルを作成します。

$ npx tsc --init

このままでも動作しますが、一部設定を変更しておきます。

{
  "compilerOptions": {
-   // "outDir": "./",               /* Specify an output folder for all emitted files. */
+   "outDir": "./dist",              /* Specify an output folder for all emitted files. */
- }
+ },
+ "include": [
+   "src/**/*"
+ ]
}

ここでは、TypeScriptファイルの格納先(include)とコンパイルして作成されるJavaScriptファイルの出力先(outDir)を追加・変更しています。

環境の構築時期やTypeScriptのバージョン、プロジェクトの都合によっては他の設定も変更することがありますが、ここでは省略します。

動作確認

実際に動作確認をしてみましょう。まずはsrc/index.tsを作成し、ファイルの中身は以下のようにします。

function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("John"));

以下のコマンドを実行し、src/index.tsをコンパイルします。

$ npx tsc

ファイルはdist/index.jsにされ、設定の違いで多少異なるかもしれませんが、以下のように出力されます。

"use strict";
function greet(name) {
    return `Hello, ${name}!`;
}
console.log(greet("John"));

実行にはJavaScriptファイルを使用します。

$ node dist/index.js
Hello, John!

エラーなくメッセージが表示されれば正しくコンパイルされています。

package.jsonにスクリプトを追加する

package.jsonにスクリプトを追加してnpmコマンドでビルドできるようにします。

{
  "scripts": {
-   "test": "echo \"Error: no test specified\" && exit 1"
+   "build": "tsc",
+   "watch": "tsc --watch"
  },
}

ここでは2つのスクリプトを追加しています。ひとつはビルドを行うスクリプト(build)で、もう一つは変更を監視して変更があればビルドを行うスクリプトです(watch)。

追加したコマンドは以下のように実行します。

$ npm run build
$ npm run watch

[Node.js][npm]ローカルパッケージを更新する

ローカルパッケージを更新する手順について説明します。

本記事では、複数のパッケージを一気にバージョンアップする方法については解説しません。ライブラリやフレームワークによっては公式でバージョンアップデート方法やマイグレーション方法を説明している場合があります。そのような場合は本手順ではなく、公式の手順に従うようにしてください。

また、パッケージのバージョンアップは十分注意して行うようにしてください。バグフィックスであっても意図しない動作をする可能性がありますので、十分テストした上でバージョンアップすることが大切です。

手順

sassパッケージをアップデートする例で手順を説明します。

{
  ...
  "devDependencies": {
    "sass": "^1.56.1"
  }
}

執筆時点では、sassパッケージの最新バージョンは1.56.2ですので、1.56.1から1.56.2へのバージョンアップを試みます。

アップデートの有無を確認する

エディタの機能等で最新バージョンを確認できる方は必要ありませんが、コマンドで最新バージョンが存在するかを確認することができます。

$ npm outdated
Package  Current  Wanted  Latest  Location           Depended by
sass      1.56.1  1.56.2  1.56.2  node_modules/sass  npm-update-sample

前述のとおり、現在バージョン(Current)は1.56.1で最新バージョン(Latest)は1.56.2であることがわかります。

各項目の意味は以下のとおりです。

  • Package:最新バージョンがあるパッケージ(現在のバージョンが最新バージョンの場合は表示されない)
  • Current:現在インストールされているバージョン
  • Wanted:package.jsonに記載されたセマンティックバージョンの条件を満たす最新バージョン
  • Latest:最新バージョン
  • Location:インストール先
  • Depended by:このパッケージに依存しているパッケージ

パッケージをアップデートする

アップデートを確認できましたので、実際にアップデートを行っていきます。

ここでは、すべてのパッケージをアップデートするのではなく、バージョンアップしたいsassパッケージだけをアップデートし、1.56.2へ狙い撃ちでバージョンアップする方法を説明します。

パッケージのアップデートはnpm updateコマンドで行いますが、このコマンドでは対象パッケージを指定することまではできますが、特定のバージョンを指定してアップデートすることはできません。最新バージョンではなく、特定のバージョンを指定してアップデートしたい場合はnpm installコマンドを用います。

$ npm install sass@1.56.2

changed 1 package, and audited 39 packages in 669ms

4 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

sassパッケージはdevDependenciesにありますが、バージョンアップするときはオプションなしでアップデートできます。

{
  ...
  "devDependencies": {
    "sass": "^1.56.2"
  }
}

package.jsonを確認するとバージョンアップしていることが確認できます。

モバイルバージョンを終了