🦜

BunでEnvファイルを管理・共有しやすくするNode.jsライブラリを作りました

2023/10/20に公開

Why Bun?

Bunが単なるパッケージマネージャーやテストツールととして使えるという情報を聞いたので試してみた。加えて

  • typescript・nodejsファイルを実行可能(なので、build時に別ツールでcommonjs・esmライブラリに変換可能。BunのRoadmapでもcommonjsへの変換を目指しているが、現状(2023/10/20)では正しく動かない。)
  • node_modulesのインストール・コード実行がnode.jsと比較して高速(故にtestコードも高速実行可)

以上からCLIのような単純な機能を持つライブラリの開発ではBunで開発して、node.jsライブラリとしてpublishするということが比較的簡単にできると思ったのでTryしてみた。

公開リポジトリ

https://github.com/MOCHI-inc-JAPAN/syncenv

解決したかった問題

社内の開発用の環境設定を共有する時に、権限がない人に毎回共有するのが面倒なのでsecret mangerにすべての開発必要情報を保存し、secret情報にアクセス権限があるメンバーであれば自分で開発環境変数をダウンロードできるようにしたかった。
また、モノレポ構成でそれぞれのprojectで必要な環境変数ごとにenvfileを生成指示を出すのが面倒なので、secret managerから取得した変数を指定の場所にファイル生成するツールが欲しかったが、汎用的なものがなかった。

考慮事項

自社のプロジェクトのケースに応じて以下のケースにも対応できるようにした

  • .envrcに一括で環境を指定できるプロジェクトケース
  • モノレポに.env.exampleのようなファイルが配置されているケースがあるので、それをテンプレートとして一部の環境変数のみ置換可能にしたい
  • GCP以外のstorageを使いたいケースにも対応。(本番環境などとも共通化することを考えると推奨できないが、cloud storage, S3などから取得したファイルをそのまま出力するなど)
  • テンプレートなしでもファイルを生成したり、secretで設定する必要がない環境変数はそのまま文字列で指定できるようにしたい
  • 環境変数やファイルの取得場所を柔軟に拡張できるようにしたかった
  • 一部ローカル環境で開発者自身のidなどを埋め込みたい事例があるので、ファイルの生成過程に任意の文字列処理を埋め込むことが出来るようにしたかった(Pipe処理を実装)
  • 開発時にほぼ必ず使うプログラミング言語で、他のプログラミング言語開発環境でも簡単に使える言語にしたかった(nodejsライブラリを選択)
  • configファイルはデフォルトのパスを設定するがコマンドライン引数などで実行時に変更可能にして環境ごとに切り替えるのを容易にしたかった

使い方

npm i -g @tkow/syncenv

などで、パッケージをインストールする。

設定ファイルとしてyaml,json,jsのmodule.exportsにフォーマットに沿ったconfigオブジェクトを定義する。設定ファイルはデフォルトでは

[
  "package.json",
  ".syncenvrc",
  ".syncenvrc.json",
  ".syncenvrc.yaml",
  ".syncenvrc.yml",
  ".syncenvrc.js",
  "syncenv.config.js"
]

の優先順位で読み込まれる。

設定ファイルを作成したら

synenv

を実行する。

settingというpropertyに単体あるいは配列で4種類のconfigオブジェクトを定義できる。

共通仕様として基本的にはsettingのにはreplacesというkeyに置換対象にしたいplaceholderのID、置換時に利用したい置換関数の引数をvalueに渡す。生成したいファイルのコンテンツあるいは設定の中の${name} または、$nameの形式で記述された文字列をplaceholderとしてreplaces関数および、pipe関数の最終実行結果を埋め込む方式になっている

replacesの値は__${pluginId}:pluginが定義している置換関数の引数__というフォーマットで、置換方法を指定する。defaultRepacerにpluginのidを指定すると、設定された値はdefaultReplacerの置換関数の引数として処理される。

providerはpluginsという設定項目にnode_modulesやpluginフォーマットのnode.jsファイルのパスを指定する。デフォルトではplaceholderIdに対して, valueに与えられた値をそのまま埋め込むだけだが、カスタムPluginを定義することで、どんなnode.js関数でもplaceholderの埋め込みに利用できる。

また、typeに.env,.envrc,file,templateの四種類の入出力方式を利用できる。

.envrc,.env

envに設定した値をoutput_dirに指定したdirectoryに出力する。filenameでファイル名を変更可能。

setting:
  - type: '.envrc'
    output_dir: ./artifacts
    env:
      NOT_REPLACED: NOT_REPLACED
      REPLACED: ${TO_BE_REPLACE}
    replaces:
      TO_BE_REPLACE: replaceId
  - type: '.env'
    output_dir: ./artifacts
    filename: .env.dev
    env:
      NOT_REPLACED: NOT_REPLACED
      REPLACED: ${TO_BE_REPLACE}
      REPLACED_PROVIDER: $TO_BE_REPLACE_PROVIDER
    replaces:
      TO_BE_REPLACE: replaceId
      TO_BE_REPLACE_PROVIDER: __default:replaceId__

以上の出力は./artifacts/.envrcに

export NOT_REPLACED=NOT_REPLACED
export REPLACED=replaceId

./artifacts/.env.devに

NOT_REPLACED=NOT_REPLACED
REPLACED=replaceId

となる。

file

環境構築では環境変数だけでなく、認証jsonファイルなどもあるので、contentsをそのままファイルに出力でする方法を提供している。binaryには未対応なので注意。placeholderでファイルのコンテンツ内容を指定し、その位置に対してpluginのreplaces結果を埋め込む。ファイルをそのまま出力したい場合はplaceholder: $CONTENTSなどと一変数を指定し、replacesにCONTENTSに埋め込みたい会いたいの設定などを行う。

setting:
  - type: file
    output_path: ./artifacts/file-output.txt
    placeholder: $TO_BE_REPLACE $TO_BE_REPLACE_PROVIDER
    replaces:
      TO_BE_REPLACE: replaceId1
      TO_BE_REPLACE_PROVIDER: replaceId2

以上の出力は./artifacts/file-output.txtに

replaceId replaceId2

となる。

template

すでに運用中のモノレポなどで.envのexampleファイルなどのテンプレートを用意している場合、そのファイルにplaceholderを盛り込むことでその位置に文字列を埋め込むことが出来る。また、replacesにplaceholderとして指定されなかった$nameのフォーマットはそのまま保持されるので、実行後の環境の環境変数として併用が可能になる。

setting:
  - type: template
    input_path: ./fixtures/.env.template
    output_path: ./artifacts/template-output.env
    replaces:
      TO_BE_REPLACE: replaceId

としてtempalteを./fixtures/.env.templateに以下の内容で配置した場合、

NOT_REPLACED: NOT_REPLACED
REPLACED: ${TO_BE_REPLACE}

出力は

./artifacts/template-output.envに

NOT_REPLACED: NOT_REPLACED
REPLACED: replaceId

となる。

Pipe

開発者ごとに必要なパラメータが異なるなどの都合で、取得したパラメータの一部を置換したいことがある。
このために、pipesプロパティでreplacesのkey、pipeIdとその引数を指定してreplecesの実行結果の文字列を置換することが出来る。デフォルトではreplace,trimを利用可能なpipeとして提供しているが、pluginで追加することも可能。replaceは正規表現の置換にも対応している。

setting:
  - type: '.env'
    output_dir: ./packages/database
    env:
      DATABASE_HOST: $DATABASE_URL
    replaces:
      DATABASE_HOST: localhost:5432
    pipes:
      DATABASE_HOST: replace(5432, 54321)

以上のconfigでの実行結果は./packages/database/.envに

DATABASE_HOST=localhost:54321

となる

Builtin Plugin

現在はGCPのSecret Managerを利用した置換関数のみ組み込みのpluginを提供している。
replacersにはGCP SecretManagerのversionIdを入れるとその値を取得して、置換文字列として埋め込む。例えば、GCPのSecretManagerを利用してprojects/11111111/secrets/SecretのID/versions/1のIDにlocalhost:5432を保存している場合

plugins: ['gcp']
setting:
  - type: '.env'
    output_dir: ./packages/database
    env:
      NODE_ENV: production
      DATABASE_HOST: $DATABASE_URL
    replaces:
      DATABASE_HOST: projects/11111111/secrets/SecretのID/versions/1
    defaultReplacer: 'gcp'

は./packages/database/.envに

NODE_ENV= production
DATABASE_HOST=localhost:5432

を出力する。

Bunの良いところ

  • package installとtest実行非常に高速でDXが非常によい
  • 導入もインストールするだけで簡単

Bunのまだ辛いところ

  • __dirname・import_metaなどの挙動が結構怪しい。ESMでbuildしても実行エラーになることがある。
  • binary buildがマシンに依存(cross compileができない)
  • testにおいてライブラリmockができない(DIなどの設計で回避)
  • commonjsでの出力に対応していない。(このライブラリではswcを利用することで解決)

まだ落とし穴はあるもののnodejsのパッケージマネージャーやjestと比較すると高速に動作する点が無視できない程度に開発体験が非常に良くなったので、今後も単純なライブラリを作成する場合はBunを使っていきたいと思った。

株式会社MOCHI

Discussion