0.1.38 • Published 2 months ago

@chandla/api-mock-server v0.1.38

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

(Sorry, in japanese language only.)

本ソフトウェアについて

本ソフトウェアはSPAなどのWebAPIを呼び出すクライアントの動作試験を行うために開発された、モックサーバです。
リクエストされたURLに対してレスポンスデータを返却する動作を行います。

ライセンスおよび免責

本ソフトウェアはMITライセンスで配布されています。 どのように利用しても構いませんが、すべて自己責任での利用となります。

本ソフトウェアの特徴

インストール方法

npm install @chandla/api-mock-server

または

yarn add @chandla/api-mock-server

単純利用目的であればグローバルインストールを推奨します。

起動方法

インストールすると起動用コマンドがnode_modules/.binにインストールされます。
package.json内のscriptであればコマンド単体での利用が可能です。

mock-server [options]

パスが通っていいない場合、

./node_modules/.bin/mock-server [options]

で起動することができます。

コマンドラインオプション

オプションパラメタ機能省略値
-cファイルパス本ソフトウェアの基本動作環境設定ファイルを指定します。なし
-pポート番号本ソフトウェアのListenポート番号を指定します。4010
-rファイルパス本ソフトウェアの入力ファイル(routesファイルという)のパスを指定します。./routes/routes.json
-sファイルパスまたはURL静的コンテンツ配信対象ディレクトリまたは、静的コンテンツを配信するサーバのURLを指定します。./public
-xなしCORSヘッダを有効化します。プリフライトリクエストなどにも対応します。
-yヘッダ名リストCORSヘッダを有効化した際、Access-Control-Allow-Headersに追加するヘッダ名をカンマ区切りで指定します。
-uディレクトリパスmultipartによるファイルアップロードの際のファイル一時格納ディレクトリを指定します。./upload
-m最大受信サイズリクエストを受信する最大サイズを指定します。"100kb"や"10mb"のように指定します。10mb

基本的な機能

1.最も基本的な使い方

最も基本的な使い方です。
routesファイルに以下のように指定することでレスポンスを返却します。 endpointsの要素を複数設定することで複数のAPIエンドポイントに対応することができます。

<routes.json>

{
  // エンドポイントURLリスト
  "endpoinnts": [
    {
      // エンドポイントパターン(express準拠。パスパラメタ可)
      "pattern": "/aaa/bbb",
      // httpメソッド
      "method": "GET",
      // 応答データリスト(条件判定による応答データの変更が可能)
      "matches": [
        {
          // 応答データ(metadata)のパス。routes.jsonからの相対パス。
          // 直接値を記述することも可能。
          // デフォルト設定ではファイル読み込み
          "metadata": "responses/response_a/define.json"
        }
      ]
    }
  ]
}

<responses/response_a/define.json>

// 応答データそのものではなく、ヘッダやレスポンスステータスを記載する
{
  // レスポンスヘッダ
  "headers":[
    {
      "name": "Content-Type",
      "value": "application/json"
    },
    {
      "name": "Access-Control-Expose-Headers",
      "value": "X-Test-Header"
    },
    {
      "name": "X-Test-Header",
      "value": "test-header-value"
    },
    {
      "name": "X-Not-Exposed-Header",
      "value": "be not exposed"
    }
  ],
  // レスポンスに設定するcookie
  "cookies":[{
    "name": "sample-cookie",
    "value": "cookie-value"
  }],
  // レスポンスステータス
  "status": 200,
  // レスポンスデータファイルのパス
  // JSONであれば直接値を記述することも可能
  "data": "response_a.json"
}

<responses/response_a/response_a.json>

// comment
{
  // comment
  "aaa": "1234",
  "bbb": 98765
}

このようにroutesファイルを作成することで/aaa/bbbにGETメソッドのリクエストを受信した場合にresponse_a.jsonファイルの内容を返却することができます。
この時レスポンスヘッダに設定したContent-Typeとファイルの内容がマッチしている必要があります。
レスポンスデータはJSON形式以外の場合、ファイルの内容をそのまま出力するので、ダウンロードAPIのモックとしても利用可能です。

2.metadataを直接記述する

routes.jsonのコメントにも記載されていますが、metadata部を直接記述することも可能です。
その場合、以下のように記述します。

<routes.json>

{
  // エンドポイントURLリスト
  "endpoinnts": [
    {
      // エンドポイントパターン(express準拠。パスパラメタ可)
      "pattern": "/aaa/bbb",
      // httpメソッド
      "method": "GET",
      // 応答データリスト(条件判定による応答データの変更が可能)
      "matches": [
        {
          // metadataを直接記述する場合はmetadataTypeをimmediateにする
          // 無指定の場合、fileが指定されたものとみなす
          "metadataType": "immediate",
          "metadata": {
            // レスポンスヘッダ
            "headers":[
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ],
            // レスポンスに設定するcookie
            "cookies":[{
              "name": "sample-cookie",
              "value": "cookie-value"
            }],
            // レスポンスステータス
            "status": 200,
            // レスポンスデータファイルのパス
            // JSONであれば直接値を記述することも可能
            "data": "response_a.json"
          }
        }
      ]
    }
  ]
}

3.レスポンスデータの内容を直接指定する

レスポンスデータ形式がJSONの場合、直接metadataの内容に含めることが可能です。
metadataが独立したファイルであっても、routesファイルに直接記述されていても同様です。
以下のように記述します。

<routes.json>

{
  // エンドポイントURLリスト
  "endpoinnts": [
    {
      // エンドポイントパターン(express準拠。パスパラメタ可)
      "pattern": "/aaa/bbb",
      // httpメソッド
      "method": "GET",
      // 応答データリスト(条件判定による応答データの変更が可能)
      "matches": [
        {
          // metadataを直接記述する場合はmetadataTypeをimmediateにする
          // 無指定の場合、fileが指定されたものとみなす
          "metadataType": "immediate",
          "metadata": {
            // レスポンスヘッダ
            "headers":[
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ],
            // レスポンスに設定するcookie
            "cookies":[{
              "name": "sample-cookie",
              "value": "cookie-value"
            }],
            // レスポンスステータス
            "status": 200,
            // レスポンスデータ
            // JSONであれば直接値を記述することも可能
            "datatype": "object", 
            "data": {
              // comment
              "aaa": "1234",
              "bbb": 98765
            }
          }
        }
      ]
    }
  ]
}

4.routesファイル変更時の動作

本ソフトウェアはroutesファイルの変更を自動検出します。
リクエストを受信した際にファイルの変更有無をタイムスタンプでチェックを行い、変更されていれば再読み込みを行ってから処理を行います。

5.エンドポイントURLにプレフィックスを付加する

複数のAPIのエンドポイントURLの先頭部分が固定的な文字列である場合、各エンドポイントのpatternに同じ文字列を記載することになります。
本ソフトウェアではURLのパスの共通部分を別途定義することができます。

<routes.json>

{
  // URLプレフィックスリスト(express準拠)
  // 単一の場合文字列1行でも可
  "prefix": [
    "/prefix00",
    "/prefix01"
  ],  
  // エンドポイントURLリスト
  "endpoinnts": [
    {
      // エンドポイントパターン(express準拠。パスパラメタ可)
      "pattern": "/aaa/bbb",
      // httpメソッド
      "method": "GET",
      // 応答データリスト(条件判定による応答データの変更が可能)
      "matches": [
        {
          // 応答データ(metadata)のパス。routes.jsonからの相対パス。
          // 直接値を記述することも可能。
          // デフォルト設定ではファイル読み込み
          "metadata": "responses/response_a/define.json"
        }
      ]
    }
  ]
}

このように定義することで、/prefix00/aaa/bbb/prefix01/aaa/bbbを同一のエンドポイントとして処理することができます。

6.リクエストの内容によって応答を切り替える

本ソフトウェアではリクエストデータの内容によってレスポンスデータを切り替えることができます。
matchesの要素にconditionsを加えることで条件判定が動作します。
conditionsの内容はjavascriptの式として評価されます。 結果はbooleanとしてtrueであればマッチしたものとみなします。
そのため空文字列や0やnullやundefinedを結果とする式はアンマッチとみなされます。

<routes.json>

{
  // エンドポイントURLリスト
  "endpoinnts": [
    {
      // エンドポイントパターン(express準拠。パスパラメタ可)
      "pattern": "/aaa/bbb",
      // httpメソッド
      "method": "GET",
      // 応答データリスト(条件判定による応答データの変更が可能)
      "matches": [
        {
          "metadata": "responses/response_a/define.json",
          // 条件式を指定リクエストパラメタのAAA項目が'BBB'であればマッチ
          "conditions": "data.AAA==='BBB'"
        },
        {
          // 条件式がない場合は強制的にマッチ
          "metadata": "responses/response_b/define.json"
        }
      ]
    }
  ]
}

評価に利用できる値は以下のオブジェクトに格納されています。 | オブジェクト名 | 説明 | | --- | --- | | data | パスパラメタ、クエリパラメタ、ボディのJSONデータをマージしたmapです | | headers | リクエストヘッダの内容をセットしたmapです | | cookies | cookieに設定された値をセットしたmapです |

応用編

1.静的コンテンツの配信

SPAの開発中はng servenextコマンドによってSPAのコンテンツ配信を行いますが、ビルド後の状態でテストを行うためにはwebサーバが必要になります。
本ソフトウェアはwebサーバとしての機能を持っており、-sオプションを指定することで、任意のディレクトリをドキュメントルートとしたコンテンツ配信を行うことができます。
コンテンツ配信とwebAPI機能の両方を持つことで同一オリジンでのSPAの動作確認を行うことができます。

2.静的コンテンツのリバースプロキシ

本番リリースされた環境の資産でサーバ側の動作だけを変更して動作確認したいケースに利用します。
静的コンテンツのファイルパスの代わりに、資産を配信しているwebサーバのURLを指定することで、静的資産を別のwebサーバから取得することができます。
https?で始まる文字列を指定した場合、webサーバを指定したものとみなします。
HTTP_PROXYHTTPS_PROXY環境変数が指定されている場合、プロキシサーバを通過することも可能です。

3.CORS対応

単体のwebAPIのモックとして利用する場合、クロスオリジンでの呼び出しとなります。
そのため、CORSヘッダやプリフライトリクエストに対応する必要があります。 -xオプションを利用することで対応可能です。
また、-yオプションを併用することでAccess-Control-Allow-Headersに利用可能なヘッダを追加することができます。
デフォルトではContent-Type、Authorization、access_tokenが有効となっています。

4.共通的なレスポンスヘッダ付与

全リクエストに対して共通的なレスポンスヘッダを付与したい場合、routesファイルに指定を行います。

<routes.json>

{
  // 共通レスポンスヘッダの付与
  "defaultHeaders": [
    {
      "name": "x-def-header",
      "value": "def-header-value"
    }
  ],
  // エンドポイントURLリスト
  "endpoinnts": [
    {
      // エンドポイントパターン(express準拠。パスパラメタ可)
      "pattern": "/aaa/bbb",
      // httpメソッド
      "method": "GET",
      // 応答データリスト(条件判定による応答データの変更が可能)
      "matches": [
        {
          "metadata": "responses/response_a/define.json",
          // 条件式を指定リクエストパラメタのAAA項目が'BBB'であればマッチ
          "conditions": "data.AAA==='BBB'"
        },
        {
          // 条件式がない場合は強制的にマッチ
          "metadata": "responses/response_b/define.json"
        }
      ]
    }
  ]
}

すべてのレスポンスに同一内容のヘッダを付与したい場合に限りますが、個々のmetadataに記述する必要がなくなります。

5.endpointsの分割

routesファイルのendpointsは別ファイルとすることが可能です。
また、複数のファイルを指定することも可能です。
これにより設定情報だけが異なるようなバリエーションを作成する際、省力化を図ることができます。

<routes.json> endpointsを他ファイルにする場合

{
  // 共通レスポンスヘッダの付与
  "defaultHeaders": [
    {
      "name": "x-def-header",
      "value": "def-header-value"
    }
  ],
  // エンドポイントファイル指定
  "endpointsType": "file",
  // エンドポイントファイルパス
  "endpointsPath": "endpoints.json"
}

<endpoints.json>

{
  // エンドポイントURLリスト
  "endpoinnts": [
    {
      // エンドポイントパターン(express準拠。パスパラメタ可)
      "pattern": "/aaa/bbb",
      // httpメソッド
      "method": "GET",
      // 応答データリスト(条件判定による応答データの変更が可能)
      "matches": [
        {
          "metadata": "responses/response_a/define.json",
          // 条件式を指定リクエストパラメタのAAA項目が'BBB'であればマッチ
          "conditions": "data.AAA==='BBB'"
        },
        {
          // 条件式がない場合は強制的にマッチ
          "metadata": "responses/response_b/define.json"
        }
      ]
    }
  ]
}

<routes.json> endpointsを指定ディレクトリに含まれるjsonファイルにする場合

{
  // 共通レスポンスヘッダの付与
  "defaultHeaders": [
    {
      "name": "x-def-header",
      "value": "def-header-value"
    }
  ],
  // エンドポイントファイル指定
  "endpointsType": "dir",
  // エンドポイントディレクトリパス
  "endpointsPath": "endpoints"
}

指定されたディレクトリをスキャンし、JSONファイルを探して内容に含まれるendpointsを取り込みます。

6.レスポンスデータの編集

レスポンスデータ形式がJSONの場合に限られますが、スクリプトを組み込むことでレスポンスデータの編集が可能です。
これによりデータの折り返しや加工が可能となります。 スクリプトはscriptsで指定したディレクトリに配置する必要があります。 呼び出しは「モジュール名:関数名」で記述します。

<routes.json>

{
  // 共通レスポンスヘッダの付与
  "defaultHeaders": [
    {
      "name": "x-def-header",
      "value": "def-header-value"
    }
  ],

  // スクリプト配置ディレクトリの指定
  "scripts": "./scripts",

  // デフォルトスクリプトの名前。「モジュール名:関数名」の形式で指定する。
  // すべてのレスポンスで動作します。
  "defaultScript": "sample1.js:aaa",

  // エンドポイントURLリスト
  "endpoinnts": [
    {
      // エンドポイントパターン(express準拠。パスパラメタ可)
      "pattern": "/aaa/bbb",
      // httpメソッド
      "method": "GET",
      // 応答データリスト(条件判定による応答データの変更が可能)
      "matches": [
        {
          "metadata": "responses/response_a/define.json",
          // 条件式を指定リクエストパラメタのAAA項目が'BBB'であればマッチ
          "conditions": "data.AAA==='BBB'"
        },
        {
          // 条件式がない場合は強制的にマッチ
          "metadata": "responses/response_b/define.json"
        }
      ]
    }
  ]
}

<responses/response_a/define.json>

// 応答データそのものではなく、ヘッダやレスポンスステータスを記載する
{
  // レスポンスヘッダ
  "headers":[
    {
      "name": "Content-Type",
      "value": "application/json"
    },
    {
      "name": "Access-Control-Expose-Headers",
      "value": "X-Test-Header"
    },
    {
      "name": "X-Test-Header",
      "value": "test-header-value"
    },
    {
      "name": "X-Not-Exposed-Header",
      "value": "be not exposed"
    }
  ],
  // レスポンスに設定するcookie
  "cookies":[{
    "name": "sample-cookie",
    "value": "cookie-value"
  }],
  // レスポンスステータス
  "status": 200,
  // レスポンスデータファイルのパス
  // JSONであれば直接値を記述することも可能
  "data": "response_a.json",

  // 編集用関数の呼び出し
  "edit": "sample1.js:bbb"
}

<scripts/sample1.js>

module.exports = {
  aaa: (req, res, state) => {
    console.log('the aaa.');
    if(state['aaa']){
      state['aaa'] ++;
    }else{
      state['aaa'] = 1;
    }
    console.log('value of the aaa = ' + state['aaa']);
  },
  bbb: (req, res, state) => {
    console.log('the bbb');
    // リクエストのAAAをレスポンスのbbbにセット
    res.data.bbb = req.data.AAA;
  }
};

デフォルトスクリプト、metadataeditの順番で処理されます。 編集関数の引数は以下の通りです。

引数名説明
reqリクエストデータ
req.headersリクエストヘッダを格納したmap
req.cookiesリクエストのcookieを格納したmap
req.dataリクエストボディのJSON、パスパラメタ、クエリパラメタをマージしたオブジェクト
resレスポンスデータ
res.statusレスポンスステータスコード
res.dataレスポンスデータのオブジェクト。レスポンスデータがJSONとして解釈可能な場合のみ有効
res.headersレスポンスヘッダを格納したmap
res.cookiesレスポンスのcookieを格納したmap
res.rawDataJSONとして解釈できない場合のレスポンスデータ
stateグローバルな記憶域として利用可能なmap。プロセス起動中は内容が保持される

7.バリデーション

設定が非常に複雑となりますが、OpenAPI Specification V3相当のバリデーションを行うことが可能です。
付属ツールのyaml2routesを利用するとOpenAPIのYAMLファイルから簡単に生成することができます。
これはバリデーションを行うためのスキーマ情報を保持する必要があるためです。
バリデーションにはopenapi-request-validatorを利用しています。 バリデーション用のスキーマにはOpenAPIRequestValidatorArgsオブジェクトを使用します。
endpointvalidatorArgsに設定を行うことでバリデーションが有効となります。
OpenAPIRequestValidatorArgsの詳細はこちらを参照。

8.マルチパートリクエスト

本ソフトウェアはリクエスト形式としてJSONを使用しますが、ファイルアップロードなどによるマルチパートデータを扱うことができます。
-uオプションで指定した一時格納ディレクトリが有効な場合に機能します。ファイルデータは一時格納ディレクトリに格納されます。
マルチパートに格納されたデータはreq.dataに統合されます。
この時のキー名はマルチパートのname属性で指定されたものになります。
また、endpointsbodyJsonを指定した場合、該当する項目をJSON文字列として解釈することを試みます。
bodyJsonUniontrueを指定すると解釈したJSONをreq.dataに統合し、指定したキーを削除します。
falseの場合、該当キーのデータをJSON解釈結果で置換します。

routesファイルのリファレンス

routesファイル(Routes型)

キー名必須説明
prefixstring | string[]-エンドポイントURLのプレフィックスを指定します。配列で複数指定可能です。
defaultHeadersHeader[]-デフォルトで付与するレスポンスヘッダを指定します。
scriptsstring-利用するスクリプトファイルの入ったディレクトリを指定します。
defaultScriptstring-デフォルトで実行するスクリプトを指定します。「module.js:funcname」のようにスクリプトファイル名と関数名をコロンでつないで指定します。
endpointsType"file" | "dir" | "immediate"-endpointsの定義方法を指定します。省略すると"immediate"が指定されたもとみなします。
endpointsPathstring-endpointsTypefileまたはdirを指定した場合に読み込み対象のファイルパスとして指定します。
endpointsEndpoint[]-エンドポイント定義を記述します。
customPropsRecord\<any>-任意の情報を記述できるmapです。ツール用の情報を記述することを想定しています。
versionstring-routesファイルのバージョンを記述します。ツールでの利用を想定しています。

Record\<T>

文字列をキー、T型を値として持つmapです。具体的には以下のような定義です。

interface {[key: string]: T}

Headers型

キー名必須説明
namestringヘッダ名を指定します。
valuestringヘッダ値を指定します。

Endpoint型

キー名必須説明
patternstringエンドポイントURLのパターンを記述します。express形式に準拠しており、パスパラメタは「:パスパラメタ名」で記述します。
method"GET" | "POST" | "PUT" | "DELETE" | "PATCH"HTTPメソッド名を記述します。
matchesPattern[]応答パターンの配列を指定します。
customPropsRecord\<any>-任意の情報を記述できるmapです。ツール用の情報を記述することを想定しています。
bodyJsonstring-指定したキー名のreq.dataに含まれる項目をJSONとして再解釈し、解釈に成功した場合項目の内容を解釈結果に置換します。
bodyJsonUnionboolean-bodyJsonとともに使用します。解釈結果をreq.dataに統合し、元のキーを削除します。

Pattern型

キー名必須説明
conditionsstring-このPatternを利用するかどうかを判定するためのjavascript式を記述します。"data.param1===\'AAAA\' || data.param2===\'BBBB\'"のように記述します。この項目を省略すると条件にヒットしたものとみなされ、以後のPatternは評価されません。
metadataType"file" | "immediate"-metadataの指定方法を記述します。fileを指定するとファイルからの読み込みになります。省略時はfileが指定されたものとみなします。
metadatastring | MetadatametadataTypefileを指定した場合、metadataを記述したファイル名として使用します。immediateを指定した場合にはMetadata型オブジェクトが記述されているものとみなします。
customPropsRecord\<any>-任意の情報を記述できるmapです。ツール用の情報を記述することを想定しています。

Metadata型

キー名必須説明
statusnumber-レスポンスステータスコードを指定します。省略時は200または204が指定されたものとみなします。
headersHeader[]-付与するレスポンスヘッダを指定します。
cookiesHeader[]-付与するcookieを指定します。
datatype"file" | "value" | "object"-dataの型を指定します。省略時はfileが指定されたものとみなします。
datastring | Record\<any>-レスポンスボディの内容を指定します。datatypeの指定により設定内容が変わります。file指定の場合:レスポンスデータファイルへのパスを指定します。相対パスの場合、routesファイルからの相対パスとなります。value指定の場合:レスポンスデータに内容をそのままセットします。object指定の場合:レスポンスデータオブジェクトを記述します。
editstring-レスポンスデータ編集のために実行するスクリプトを指定します。「module.js:funcname」のようにスクリプトファイル名と関数名をコロンでつないで指定します。
customPropsRecord\<any>-任意の情報を記述できるmapです。ツール用の情報を記述することを想定しています。

動作環境設定ファイルのリファレンス

-cオプションで指定するファイルの内容です。 | キー名 | 型 | 必須 | 説明 | | --- | --- | --- | --- | | port | number | - | 本ソフトウェアが使用するポート番号を指定します。省略時は4010を使用します。 | | routesPath | string | - | -rオプションを使用しない場合に読み込むroutesファイルのパスを指定します。省略時は""./routes/routes.json"です。 | | staticContents | string | - | -sオプションを使用しない場合に静的コンテンツ配信の対象とするディレクトリです。省略時は"./public"です。 | | enableCors | boolean | - | -xオプションがない場合のCORS対応の有無を指定します。省略時はfalse(なし)です。 | | allowHeaders | string | - | CORS対応ありの場合にAccess-Control-Allow-Headersに追加するヘッダ名のリストを指定します。ヘッダ名をカンマスペース区切りで列挙します。例:"x-data-XXX, x-data-YYY" | | maxReceiveSize | string | - | リクエストを受信する最大サイズを指定します。"100kb"や"10mb"のように指定します。省略時は10mbです。 |

付属ツール

本ソフトウェアに付属するツールを説明します。

yaml2routes

OpenAPI Specification v3形式のYAMLファイルをもとにroutesファイルを作成するツールです。
スキーマにexampleが指定されている場合、その値を利用してレスポンスデータを作成します。指定されていない場合、データ型に応じた値をレスポンスデータに設定します。

起動方法

yaml2routes -i {入力パス} -o {出力ファイル名} [options]

コマンドラインオプション

オプションパラメタ機能省略値
-iファイルパス必須パラメタです。入力となるYAMLファイルのパスまたはディレクトリを指定します。ディレクトリの場合スキャンを行いYAMLファイルを検索します。この場合、ディレクトリに含まれるすべてのYAMLファイルが読み込み対象となります。なし
-oファイルパス必須パラメタです。変換結果を出力するファイル名を指定します。なし
-p文字列変換結果のdataの内容を出力するディレクトリ名を指定します。指定しない場合、-oで指定したファイルと同じディレクトリに出力されます。なし
-sファイルパス出力するroutesファイルのひな形となるファイルを指定します。なし
-wなし本オプションを指定すると、バリデーション用のスキーマ情報を出力します。出力ファイルサイズが大きくなります。なし
-r数字レスポンスデータの設定度合いを指定します。0:可能な限りすべての項目に値を設定します。1:必須項目のみ値を設定します。2:必須項目のみ値を設定します。配列指定の場合には空配列を設定します。0
0.1.38

2 months ago

0.1.35

8 months ago

0.1.36

6 months ago

0.1.37

6 months ago

0.1.30

2 years ago

0.1.31

2 years ago

0.1.32

2 years ago

0.1.33

1 year ago

0.1.34

1 year ago

0.1.27

2 years ago

0.1.28

2 years ago

0.1.29

2 years ago

0.1.22

2 years ago

0.1.23

2 years ago

0.1.24

2 years ago

0.1.25

2 years ago

0.1.26

2 years ago

0.1.20

2 years ago

0.1.21

2 years ago

0.1.19

2 years ago

0.1.18

2 years ago

0.1.16

2 years ago

0.1.17

2 years ago

0.1.10

2 years ago

0.1.11

2 years ago

0.1.12

2 years ago

0.1.13

2 years ago

0.1.14

2 years ago

0.1.8

2 years ago

0.1.7

2 years ago

0.1.9

2 years ago

0.1.6

2 years ago

0.1.5

2 years ago

0.1.4

2 years ago

0.1.3

2 years ago

0.1.2

2 years ago

0.1.0

2 years ago