20201204のNode.jsに関する記事は5件です。

npmについてまとめ

npmとは

Node Package Managerの略

Node.jsのパッケージ管理システムである。

2010年にIsaac Z. Schlueter氏によって開発された。

パッケージ管理システムとは

パッケージ管理システム(パッケージかんりシステム)は、オペレーティングシステム (OS) というひとつの環境で、各種のソフトウェアの導入と削除、そしてソフトウェア同士やライブラリとの依存関係を管理するシステムである。

要は世界の凄い人たちが作って公開しているモジュールをパッケージとして管理し、検索、閲覧、及びダウンロードして使えるよ〜というシステムです。

また、使用したいパッケージの依存パッケージ、そのバージョンまで自動で管理してくれます。

npmを使わないとどうなる?

例えばexpressというパッケージを使用したいとします。
expressは30ものパッケージと依存関係にあります。
スクリーンショット 2020-12-04 21.00.01.png

この場合expressの他にこの30ものパッケージを別途手動でダウンロードしなければexpressは動きません。
更にはこれらのパッケージもまたそれぞれ依存先を持っており、更にそのまた依存先のそのまた依存先の・・・・

・・とにかく全てのパッケージをダウンロードする必要があり、しかもバージョンの整合性もとらなければなりません。

そんな面倒なことも、npmが全て自動でやってくれる訳ですね(感謝)

package.json

package.jsonというJSONファイルにはそのパッケージ(プロジェクト)の情報が記述されています。
依存パッケージやそのバージョンもここで管理されています。

まとめ

npmについてざっと調べたことを書きました。
何気なく使っていたnpmのありがたみを知ることができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでの文字列送信方法

21803
項目表示の為には以下の項目を書く。
こんにちは。今回はPHPでのデータ送受信方法が分からないという人の為に説明します。

php>
ここのPOSTというのはデータ送信を示しています。もう一つGETという物が有りますが、このGETという物はURLのところの最後のところは相手側から送られてきたデータの事ですので、その内容が見えてしまいます。なので、データ送受信の際にはPOSTを使う事をお勧めします。
次にこのaction属性を使う事で送信先を指定することが出来ます。この場合ですとtestform.phpにデータを送信します。次にこのtypeについて説明します。このtypeで"text"と指定する事により、テキストボックスでデータを送信することが出来ます。
次に<?phpの内容について説明します。このphpというのは実行したい内容を書くところです。この中のコマンドを実行します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js+Sequelizeで楽観的ロックを使って動作を確認する

PONOS Advent Calendar 2020の8日目の記事です。

昨日は@e73ryoさんでした。

はじめに

前回に続いてまたSequelizeの話です。

データの整合性をたもつための排他制御として代表的なものとして悲観的ロックと楽観的ロックというアプローチがあります。
それ自体については沢山記事がありますのでここでは言及致しませんが、基本的に自身のデータしか操作しないようなケースにおいては、操作が競合する可能性が低いため、実現が簡単で不具合の生みにくい楽観的ロックを採用するケースが多いかなと思います。

ということでSequelizeで楽観的ロックを利用する方法をメモ的に書いておきたいと思います。
(といってもめっちゃ簡単な話なんですが)

この記事の対象者

Node.jsとSequelizeの基本的な知識があることを前提とします。
この記事ではこれらの細かい部分については言及しません。

検証環境

  • Node.js 12.19.0
  • Sequelize 6.3.5
  • Sequelize-CLI 6.2.0
  • MySQL 5.7.25

方法

まずは雛形のModelを作成する

今回もSequelize-CLIを使ってModelファイルとマイグレーションファイルを作成します。
ここではuserというテーブルにnameとageフィールドを持たせます。

npx sequelize model:generate --name user --underscored --attributes name:string,age:integer

※ --underscoredを指定しているのはテーブル定義上は名称をスネークケース、コード上はキャメルメースにしたいだけで、本質的には今回の件と関係ありません。
※ 生成されるファイル内容については前回と同様なので省略します。

Modelの設定を変更する

Sequelizeに楽観的ロックの動作を行わせるには、Modelにversion: trueを設定するだけです。

user.jsを編集する

'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class user extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  };
  user.init({
    name: DataTypes.STRING,
    age: DataTypes.INTEGER
  }, {
    sequelize,
    modelName: 'sample',
    underscored: true,
    version: true,
  });
  return user;
};

マイグレーションファイルにversionを追加する

テーブルにversion列が必要なので、定義を追加します。
ちなみにここではversionにデフォルト値を設定していませんが、ORマッパーはINSERT時にも自動的に値を設定するので動作します。

xxxxxxxxxxx-create-user.jsを編集する

'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      name: {
        type: Sequelize.STRING
      },
      age: {
        type: Sequelize.INTEGER
      },
      created_at: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE
      },
      version: {
        allowNull: false,
        type: Sequelize.INTEGER
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('users');
  }
};

動作を確認する

うまく動作するのかを検証します。
※ 結論からいうとうまく動作するので、Sequelizeでの楽観的ロックの設定だけ知りたい方は、「以上、終了」という感じです。

マイグレーションを実行してテーブルを準備します。

npx sequelize db:migrate

正常系

まずはインサートしてみる

await db.user.create({
    name: "sample",
    age: 10
});

下記のデータが登録されました。

id name age created_at updated_at version
1 sample 10 実行日時 実行日時 0

次はこれをアップデートしてみます。

const user = await db.user.findOne({
    where: {
        id: 1
    }
});
user.age += 1;
await user.save();

下記のようにデータが更新されました。
versionが自動的にインクリメントされており、どうやらうまく動作しているようです。

id name age created_at updated_at version
1 sample 11 以前の日時 今回の実行日時 1

異常系

それでは上記の手順に続いて、本当に楽観ロックが機能しているのかを確認します。

同時にアクセスがあり、トランザクションを開始してデータを取得して更新するケースを想定します。

この時二つのトランザクションA、トランザクションBは同じ状態のデータを取得することになります。
その後、トランザクションAのほうはage+1を行いコミットします。
トランザクションBのほうはage+10を行ってコミットするという想定でコードを書いてみます。

※ トランザクション分離レベルはREPEATABLE READで行っています。

// 同時にトランザクションを開始する。
let transactionA = await db.sequelize.transaction();
let transactionB = await db.sequelize.transaction();

try {
    // まずトランザクションAでデータを取得
    const userA = await db.user.findOne({
        where: {
            id: 1
        }
    }, { transaction: transactionA });
    console.log(`A age: ${userA.age} version: ${userA.version}`);

    // 次にトランザクションBでデータを取得
    const userB = await db.user.findOne({
        where: {
            id: 1
        }
    }, { transaction: transactionB });
    console.log(`B age: ${userB.age} version: ${userB.version}`);

    // トランザクションAがage+1してコミット
    userA.age += 1;
    console.log("A update");
    await userA.save({ transaction: transactionA });
    console.log("A commit");
    transactionA.commit();
    transactionA = null;

    // トランザクションBがage+10してコミット
    userB.age += 10;
    console.log("B update");
    await userB.save({ transaction: transactionB });
    console.log("B commit");
    transactionB.commit();
    transactionB = null;
} catch (error) {
    console.log(error);
    if (transactionA) {
        console.log("rollback A");
        transactionA.rollback();
    }
    if (transactionB) {
        console.log("rollback B");
        transactionB.rollback();
    }
}

これを実行すると下記の順でログが出力されました。

A age: 11 version: 1
B age: 11 version: 1
A update
A commit
B update
OptimisticLockError [SequelizeOptimisticLockError]: Attempting to update a stale model instance: user
(省略)
rollback B

このログから分かる通り、AもB同じデータを参照しており、Aはコミットに成功しましたが、Bはupdate実行時にOptimisticLockError例外が発生し、ロールバックされたようです。

結果のデータベースは下記のようになっています。

id name age created_at updated_at version
1 sample 12 以前の日時 今回の実行日時 2

Aが行った操作であるage+1が反映されていて、versionは1になっています。

まとめ

ということで動作検証も行ったので記事が長くなりましたが、Sequelizeで楽観ロックを導入するのはとても簡単でした。

明日は@kerimekaさんです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

? Microsoft Teams 開発の初心者向けガイド その3: メッセージ・エクステンション

みなさんこんにちは〜。この記事は、Microsoft Teams 開発の初心者向けガイド第3弾になります。前回の2つのチュートリアル ( タブの開発Bot 開発)も楽しんでもらえていたらうれしいです。

今回は、Teams UI からのユーザからのアクションで検索結果やメッセージを書き出す方法について説明します。
3-ext1-cover-1000x420.png
Teams アプリの作成にはいくつもの方法やツールがあるのですが、このチュートリアルでは、コンセプトを学んでもらうことを重視したいので、最低限のコードと最小限のツールセットを使用しています。また、マイクロソフト Azure から 環境をセットアップすることもできますが、今回はそのプロセスを使わず、気軽に学んでもらえるようにブラウザ上でコードを実行していきます。


このチュートリアルでは、メッセージング・エクステンション (Messaging Extensions / メッセージング拡張機能)という機能をつかった開発について紹介します。

メッセージング拡張機能には、アクションと検索という 2 種類があるのですが、今回は「アクション・コマンド」(操作コマンド)について紹介します。

チームの機能: 「アクション・コマンド」

アクション・コマンドを使用すると、情報を収集または表示するためのモーダル ポップアップをユーザーに表示できます。フォームを送信すると、アプリ、つまり Web サービスは、メッセージを会話に直接挿入したり、メッセージ作成ボックスに挿入してユーザーの選択でグループにメッセージを送信できるようにしたりできます。

Teams action command

メッセージ・アクションをつかって、メッセージ内容をモールス コードに変換するサービスを作る!

いまから作成していくアプリは、ユーザーが選んだ任意のメッセージに含まれるテキストを抽出してモールスコードに変換する、というものです。

アプリの動作は次のとおりです。

  1. ユーザーがメッセージをマウスオーバーしてメッセージメニューを開き、「そのほかの操作」(More actions) メニューからモールス bot を選択
  2. ユーザーからの操作がトリガーされると、ペイロードがメッセージングエンドポイントに送信。 (/api/messages)
  3. fetchTask の呼び出し。ここでメッセージ テキスト データが抽出されます。
  4. ポップアップ ダイアログが表示されます。ユーザーは必要に応じてテキストコンテンツを編集し、送信
  5. アプリは、テキストをモールスコードに変換し、コンテンツを返信として表示
  6. ユーザーは結果をクライアントに新規メッセージとして送信

結果は次のようになります。
morsebot-final.gif

? 必須科目

Teams にアプリをインストールできるようにするには、組織の管理者がアクセス許可を付与する必要があります。
それ以外の場合は、無料のMicrosoft 365 開発者プログラム に登録しましょう。このプログラムでは、開発者テナントのサンドボックス、サンプル データ パックに付属しているモック ユーザーデータなどが使え、サブスクリプションはなどでもリニューアルできます。

  • 開発の許可がある Teams テナント、または開発専用テナント (M365 開発者プログラムにサインアップしよう)
  • App Studio - チームクライアントのアプリメニューからアプリを探し、テナントのワークスペースにインストールしてね
  • JavaScript の基礎知識

? このチュートリアルで使用する技術

アクションの構築

? コード サンプルの取得

このチュートリアル シリーズでは、プロジェクトのコードのホスティングと実行を簡略化するために、サードパーティのツール、Glitchを使用しています。

Glitch は、node.jsコードを記述して実行できるWebベースのIDEなので、少なくとも今のところ、localhostのトンネル、デプロイを気にすることなく、Teamsアプリ開発の概念と基本を学ぶことに集中できます。

まず、この Glitch プロジェクトの remix ができるリンク、または下のボタンをクリックしてください。リミックスとは GitHub で言うところのリポジトのをフォークのようなようなもので、自分用のプロジェクトのコピーが生成されるため、元のコードに影響なく自分用に好きなように変更できます。
Remix on Glitch
さて、自分専用プロジェクトのリポを取得すると、自動的に独自のWebサーバーURLを取得します。たとえば、生成されたプロジェクトは、3単語ほどでできたランダムな語で構成されています。たとえばそのプロジェクト名が achieved-diligent-bell だったら、ウェブサーバーのURLは https://achieved-diligent-bell.glitch.me になります。必要に応じて名前をカスタマイズすることもできます。

⚙️ App Configuration: App Studioを使ってアプリ マニフェストを作成

Teams アプリパッケージの基本については前のチュートリアルを参照してください。

? App Studio を使う

App Studio 上部の Manifest Editor(マニフェスト エディター) タブをクリックし、Create a new app(新しいアプリの作成])を選択します。必要事項を入力してください。

そして App ID を生成してください。
exts00-app-studio-ext.png

? メッセージ・エクステンションの設定

左側のメニューから、 Capabilities > Massaging Extensions を選択
exts01-app-studio-ext.png
先に進み、設定するボタンをクリック。
exts02-app-studio-ext1.png
名前を付けます。

? App credentials

ボット名の横にある ID ('2cd53e8a-e698-4exx-...' のように見える) をコピーし、隠しファイルである.envファイルに環境変数として貼り付けます ('.env-sample' の名前を '.env' に変更します)。

App Passwordsの下で新規パスワードを生成し、それをコピーし、これも .envファイルに貼り付けます。

これらの情報は、bot アダプターを初期化するために使用されます。(index.js を参照してください。
exts03-app-studio-ext2.png
(上記の画面画像のステップ3は、このすぐ後に説明します。)

? アクションの設定

Messaging Endpoint で、サーバーを入力します。今回は、Glitch サーバ上でコードを動かしているので、 https://[your project].glitch.me/api/messages のように自分のプロジェクト名が入った URL がサーバとなります。

 Command までスクロールし [Add] をクリック

ダイアログ ボックスで
1. Allow users to trigger actions in external service... を選択
2. Fetch a dynamic set of parameters from your bot を選択
3. command IDtitle text を入力。 Massage チェックボックスを選択 (他のチェックボックスが事前に選択されている場合は、そのチェックボックスをオフにします。) 残りは空白のままにして保存します。
exts04a-app-studio-ext3.png
exts04b-app-studio-ext4.png
exts04c-app-studio-ext5.png

? App manifest パッケージをインストール

Finish > Test and distribute へ行きます。

エラーが発生した場合は、それを修正してください、そうでなければ、Install をクリックしてください。
exts05-app-studio-install.png
また、'manifest.json' を含む zip ファイルと、後でインストールまたは配布する 2 つのアイコン イメージをダウンロードすることもできます。

コード サンプルをそのままリミックスしている限り、この bot は既に動作するはずでが、試してみる前に、これがどのようにコーディングされているかを簡単に説明してみます。

? Microsoft Bot Framework

マイクロソフトボットフレームワーク は、インテリジェントなエンタープライズグレードのボットを構築できるオープンソースSDKです。

この SDK は、Teams だけでなく、Web やモバイル チャット、Skype、Facebook、Amazon Alexa、Slack、Twilio など、幅広い種類のチャット ボットで動作するように設計された強力なプラットフォームです。

? Initiating the bot service

まず、Glitch コード サンプル Repo に 2 つの JS ファイルがあります。 index.js and bots.js です。

ここでは、Express を使用して HTTP サーバーを設定し、HTTP リクエストをルーティングしています。サービスを開始する方法は、前の Bots チュートリアルと同じですが、これは初期化とボット アダプタの作成について、もう一度要約したコードが下のようになります。

// Import bot services
const { BotFrameworkAdapter } = require('botbuilder');

// Bot's main dialog
const { ReverseBot } = require('./bot');

// App credentials from .env
const adapter = new BotFrameworkAdapter({
  appId: process.env.MicrosoftAppId,
  appPassword: process.env.MicrosoftAppPassword
});

// Create the main dialog
const myBot = new MorseBot();

注:この例では、私はボットビルダーのバージョン4.10.0を使用しています。コードが期待どおりに動作しない場合は、使用しているバージョンを確認してください!

? Bot ロジックへの要求の転送

Express を使用して、着信リクエストをリッスンするルーティングを処理します。

app.post('/api/messages', (req, res) => {
  adapter.processActivity(req, res, async context => {
    await myBot.run(context);
  });
});

前の手順で、 App Studio で URL を設定しました。/api/messages は、クライアント要求に応答するアプリケーションのエンドポイント URL です。

?‍♀️ リクエストの処理

エンドポイントで要求を受信し、ボット ロジックに転送すると、アプリは要求のコンテクストを受け取り、bots.js でカスタム応答を作成します。

要求に対する適切なハンドラーを作成するために拡張された TeamsActivityHandler を参照してください。

class ReverseBot extends TeamsActivityHandler {

  // ユーザからのアクションによってトリガー
  handleTeamsMessagingExtensionFetchTask(context, action) {
    /*
         ここで表示するダイアログのコンテンツを adaptive card UI (modal dialog)を作成。
         ユーザがダイアログを確認して送信。
    */
  }

// FetchTask からユーザによって送信されたらトリガー
  handleTeamsMessagingExtensionSubmitAction(context, action) {
    // display the result 
  }

TeamsActivityHandler は、メッセージ イベント (*例えば onMembersAdded メソッドが会話にメンバーが追加されるたびに呼び出される) などのメッセージを処理し、返信を送信するチーム固有のクラスです。

ここでは、ユーザがメッセージに対しアクションを起こすと、handleTeamsMessagingExtensionFetchTask がトリガーされ、ボットが元のメッセージに関する情報を受け取ります。

これらについてはドキュメント「タスクモジュールを作成して送信する 」でもっと詳しく学ぶことができます。

? アダプティブ カードを使用したモーダル ダイアログの表示

ダイアログ UI は、 Microsoft のオープン ソースである アダプティブ カードを使って JSON で UI を構築することができます。アダプティブ カードは、Teams の他、Outlook のアクション可能なメッセージ、Cortana スキルなどさまざまな Microsoft ファミリーのサービスの開発で使うことができます。

handleTeamsMessagingExtensionFetchTask が呼び出されると、メッセージ コンテンツ テキストを取得し、応答としてダイアログとしてアダプティブ カードに表示します。
adaptive-card-message-action.png

アダプティブ カードとコンテンツを定義するには:

const card = {
  type: 'AdaptiveCard',
  version: '1.0'
};

card.body = [
        { type: 'TextBlock', text: 'The message to be encoded to Morse code:', weight: 'bolder'},
        { id: 'editedMessage', type: 'Input.Text', value: content },
      ];
      card.actions = [{
        data: { submitLocation: 'messagingExtensionFetchTask'},
        title: 'Encode to Morse!',
        type: 'Action.Submit'
      }];

const adaptiveCard = CardFactory.adaptiveCard(card);

return {
      task: {
        type: 'continue',
        value: {
          card: adaptiveCard
        }
      }
    };

抽出されたメッセージテキストを type: 'Input.Text' で表示し、ユーザーがモールスコード化するテキストを編集できるようにします。

完全なコードを表示するには、グリッチのコード サンプルの bot.js ファイルを参照してください。

? ユーザーの送信を処理する

ユーザーがタスク モジュールを送信すると、handleTeamsMessagingExtensionSubmitAction がトリガーされ、Web サービスはコマンド ID とパラメーター値が設定されたオブジェクトを受け取ります。

このサンプル コードでは、カスタム データ editedMessage が存在するかどうかだけを確認します。その場合は、値(ここでは文字列) を取得し、それをモールスに変換して、新しいメッセージとして作成するコンテンツを表示します。

async handleTeamsMessagingExtensionSubmitAction(context, action) {

    if(action.data.editedMessage) {
      const text = action.data.editedMessage;
      const morseText = await encodeToMorse(text);

  return {
    composeExtension: {
    type: 'result',
      attachmentLayout: 'list',
      attachments: [
        // the message UI component here
      ]
    }
  }

}

bots.js に示されているコードサンプルでは、結果メッセージを作成するために Bot Framework に付属のサムネイルカードと呼ばれるシンプルなUI カードを使用していますが、もちろん前述のアダプティブカードも使用できます。

?? メッセージアクションを試してみる

それでは、アクションを試してみましょう!
Teams クライアントに移動し、リッチ フォーマットや画像ではない、シンプルテキストで構成されたメッセージのいずれかをクリックしてみてください。

すべてが期待どおりに動作する場合は、任意のテキスト メッセージをモールス コードに変換できるはずです。

more-actions-ja.png

?

チュートリアルを楽しんでいただけましたか?モールス・コードへの変換は正直、あまり普段役に立つものではないでしょうけれど、みなさんはもっと良いユースケースを見つけて素晴らしいアプリを作成することを願っています!

次のチュートリアルでは、検索コマンドである別の種類のメッセージング拡張機能を構築する方法について説明します。次回お会いしましょう ?

?? この記事を英語で読みたいという方は dev.to のリンクからどうぞ!

? Learn more

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nodeプロジェクトで新規 dependency 追加時に、自動で脆弱性チェックを行う

概要

npm (yarn) によるパッケージ管理は非常に楽ですが、ときには脆弱性を含むパッケージを入れてしまうこともあるかもしれません。
CI/CD パイプラインで 脆弱性チェックを行うのも良いかもしれませんが、ローカルで依存パッケージを追加・コミットする前に脆弱性チェックを行い、無駄なコミットが含まれないようにするほうがスマートだと私は思っています。

そこで、 lint-stagedhuskyを使って、依存関係を追加した場合のcommitで脆弱性チェック(audit)をする設定をします。

関連パッケージ等

  • yarn (npmでも脆弱性チェックはできますが、ここではyarnを使います)
  • lint-staged
  • husky

やったこと(結論)

huskylint-stagedをdevDependenciesに入れて、package.json に以下を追加しましょう。

package.json
{
  "scripts": {
    "audit:moderate": "yarn audit --level moderate || EXIT_CODE=$? && if [ $EXIT_CODE -ge 4 ]; then exit EXIT_CODE; else exit 0; fi;"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "yarn.lock": [
      "audit:moderate"
    ]
  },
}

設定の詳細

  1. 脆弱性チェック
  2. 脆弱性チェックを、中レベル以上に限定する
  3. コミット時に脆弱性チェックが走るようにする
  4. 中レベル以上の脆弱性チェックが正常終了するようにする

1. 脆弱性チェック

yarn には 脆弱性チェックを行う audit というコマンドがあるので、それを使います。

詳細は公式ドキュメント:https://classic.yarnpkg.com/en/docs/cli/audit/#toc-yarn-audit

$ yarn audit

もし脆弱性のあるパッケージがある場合、下記の表のような形式で脆弱性のあるパッケージが出力されます。

$ yarn audit
yarn audit v1.22.5
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ low           │ Denial of Service                                            │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ node-fetch                                                   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=2.6.1 <3.0.0-beta.1|| >= 3.0.0-beta.9                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ next                                                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ next > @ampproject/toolbox-optimizer >                       │
│               │ @ampproject/toolbox-core > cross-fetch > node-fetch          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1556                        │
└───────────────┴──────────────────────────────────────────────────────────────┘

...

2. 脆弱性チェックを、中レベル以上に限定する

すべてのレベルの脆弱性を排除することが望ましいですが、依存パッケージのさらに依存パッケージの低レベル脆弱性まで気にすると、実運用する上では使えないパッケージが増えてしまいます。
例えば、React開発で最も優れている(と私が思う) Next.jsでも、上記のようにnode-fetchなどのパッケージの低レベル脆弱性が対応しきれていません。

そこで、中程度(moderate)以上の脆弱性のみを検知したいと思います。

$ yarn audit --level moderate
yarn audit v1.22.5
5 vulnerabilities found - Packages audited: 2235
Severity: 5 Low
✨  Done in 2.02s.

低レベル脆弱性が検知されているものの、レベルをmoderateにしているのでコマンドは脆弱性のレポートを表示することなく終了しています。

3. コミット時に脆弱性チェックが走るようにする

lint-stagedhuskyを使って、依存パッケージに変更があるコミットで自動で脆弱性チェックが走るようにします。

これらの設定は主にeslintprettierと組み合わせて使われており有名かと思うので割愛します。

依存パッケージに変更がある場合、yarn.lockが更新されているので、yarn.lockのコミットをトリガーに脆弱性チェックを行います。

package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
    }
  },
  "lint-staged": {
    "yarn.lock": [
      "yarn audit --level moderate"
    ]
  },
}

4. 中レベル以上の脆弱性チェックが正常終了するようにする

前述の脆弱性チェックコマンドは一見正常終了に見えますが、実はエラー終了扱いとなっています。

試しにprecommitを走らせると、下記のように。

$ git commit yarn.lock
husky > pre-commit (node v12.18.0)
✔ Preparing...
⚠ Running tasks...
  ↓ No staged files match *.{js,json,md,yaml,yml} [SKIPPED]
  ↓ No staged files match *.{ts,tsx} [SKIPPED]
  ↓ No staged files match package.json [SKIPPED]
  ❯ Running tasks for yarn.lock
    ✖ yarn audit:moderate [FAILED]
↓ Skipped because of errors from tasks. [SKIPPED]
✔ Reverting to original state because of errors...
✔ Cleaning up...

✖ yarn audit:moderate:
error Command failed with exit code 2.
$ yarn audit --level moderate  /Users/y.shogoro/dev/src/bitbucket.org/wanocoltd/tunecorejapan_frontend/yarn.lock
5 vulnerabilities found - Packages audited: 2235
Severity: 5 Low
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
husky > pre-commit hook failed (add --no-verify to bypass)

これは、yarn auditが検知した脆弱性のレベルに応じて終了コードを変更しているからです。
https://classic.yarnpkg.com/en/docs/cli/audit/#toc-yarn-audit

The command will exit with a non-0 exit code if there are issues of any severity found. The exit code will be a mask of the severities.

1 for INFO
2 for LOW
4 for MODERATE
8 for HIGH
16 for CRITICAL

今回は、中程度の脆弱性から検知すると決めたので、exit codeが4以上の場合のみ以上終了とみなすscriptにします。

package.json
{
  "scripts": {
    "audit:moderate": "yarn audit --level moderate || EXIT_CODE=$? && if [ $EXIT_CODE -ge 4 ]; then exit EXIT_CODE; else exit 0; fi;"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "yarn.lock": [
      "audit:moderate"
    ]
  },
}

以上で完成です。
これで、依存パッケージに変更があった際、中程度以上の脆弱性が自動でチェックされるようになりました。

余談

設定されている方も多いかと思いますが、lint-stagedhuskyを使った設定で、以下もおすすめです。

特に、なんでもかんでもlintかけたい気持ちなのでsort-package-jsonはイチオシです。

  • jsonmd, yamlなどにprettierをかけて整形する
  • package.jsonsort-package-jsonをかけて、keyの並べ替えを行う
  • js, tsjest --findRelatedTestsをかけて、関連するテストのみ実行する
package.json
{
  "lint-staged": {
    "*.{json,md,yaml,yml}": [
      "prettier --write"
    ],
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "jest --findRelatedTests"
    ],
    "package.json": [
      "sort-package-json"
    ],
  }
}

公式ページ
- prettier
- sort-package-json
- jest

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む