preview ビルドを流す前に、EAS のダッシュボードを開いて環境変数を目で数えていた夜がありました。development・preview・production の三つに、プロジェクト単位とアカウント単位のスコープが重なります。画面を行き来しているうちに、どこまで確認し終えたのか分からなくなりました。
手で数えるのをやめようと思って eas env:list --json と打ちました。通りませんでした。
最初にお伝えしたいのは、これが打ち間違いではないということです。eas env:list に --json は用意されておりません。同じところで手が止まった方に向けて、代わりに何を使うのか、そしてその出力をどう扱えば安全なのかを書き残します。
eas env:list が受け取るフラグは五つです
eas-cli v22.0.0 のソース(packages/eas-cli/src/commands/env/list.ts)で定義されているフラグは、次の五つです。
| フラグ | 既定値 | 役割 |
|---|---|---|
--environment | (対話で選択) | 対象の環境。複数回の指定が可能 |
--format | short | 出力形式。選択肢は long と short のみ |
--scope | project | プロジェクト単位かアカウント単位か |
--include-sensitive | false | sensitive の値を伏せ字にせず表示 |
--include-file-content | false | ファイル型変数の中身を表示 |
引数として環境名をそのまま渡すこともできます。eas env:list production という書き方です。出力形式の選択肢は long と short の二つだけで、JSON はそこに含まれておりません。--json を足しても、定義されていないフラグとして扱われます。
ここで一つ、私が遠回りした点をお伝えします。ダッシュボードの表を見て「JSON で落とせるはずだ」と思い込み、フラグの一覧を確かめる前にオプション名を三つほど試しました。いま思えば、ソースの static override flags を先に読めば一分で済んだ話でした。
--format short の出力を、そのまま .env として使えない理由
--format short は NAME=value を一行ずつ出しますので、一見すると .env そのものに見えます。ただ、そのまま流用すると三か所で崩れます。
- 先頭に環境名の見出し行が入ります(
Environment: productionの形) - 変数が一つも無い環境では
No variables found for this environment.の一文だけが返ります - 変数名は太字で出力されますので、端末の条件によっては装飾の制御文字が混ざります
さらに厄介なのは、値そのものが読めない場合があることです。可視性が sensitive の変数は --include-sensitive を付けない限り伏せ字になり、secret の変数はそもそも EAS のサーバーから出てきません。伏せ字を値だと思って書き戻せば、伏せ字が本物の値を上書きします。
可視性の三段階と EXPO_PUBLIC_ 接頭辞の関係は別の軸の話ですので、そちらはEAS の secret は「アプリに入れない」設定ではありませんに譲ります。ここでは「読める値と読めない値が同じ行の形で並んで出てくる」という一点だけを押さえておければ十分です。
三十行ほどで JSON に変換します
見出し行を落とし、制御文字を除き、最初の = だけで分割します。伏せ字だけの値には readable: false を付けて、値ではなく印であることを残します。
#!/usr/bin/env python3
# eas env:list --format short --environment production | python3 eas_env_to_json.py production
import json
import re
import sys
ANSI = re.compile(r"\x1b\[[0-9;]*m")
HEADER = re.compile(r"^(Environment|Variables for this project|Account-wide variables)\b")
MASKED = re.compile(r"^\*+(\s*\(.*\))?$")
def parse(lines):
out = []
for raw in lines:
line = ANSI.sub("", raw).rstrip("\n").strip()
if not line or HEADER.match(line):
continue
if line.startswith("No variables found"):
continue
if "=" not in line:
continue
name, value = line.split("=", 1) # 値に = が含まれても壊さない
name = name.strip()
if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name):
continue # 装飾の残骸や説明行を捨てる
out.append({
"name": name,
"value": value,
"readable": not MASKED.match(value.strip()),
})
return out
if __name__ == "__main__":
env = sys.argv[1] if len(sys.argv) > 1 else None
json.dump(
{"environment": env, "variables": parse(sys.stdin.readlines())},
sys.stdout, ensure_ascii=False, indent=2,
)
print()v22.0.0 のソースが出力する short 形式を再現した入力で動かした結果です。
{
"environment": "production",
"variables": [
{ "name": "APP_VARIANT", "value": "production", "readable": true },
{ "name": "EXPO_PUBLIC_API_URL", "value": "https://api.example.com", "readable": true },
{ "name": "SENTRY_AUTH_TOKEN", "value": "*****", "readable": false }
]
}変数が一つも無い環境を渡しても、variables が空の配列になるだけで落ちません。Base64 のように値の中に = を含む文字列も、最初の = だけで分割していますので保たれます。
三つの環境を並べると、抜けが一行で見つかります
環境ごとに JSON を作ってから、次のスクリプトで並べます。
#!/usr/bin/env python3
# python3 eas_env_matrix.py env-development.json env-preview.json env-production.json
import json
import sys
envs, table = [], {}
for path in sys.argv[1:]:
with open(path, encoding="utf-8") as f:
data = json.load(f)
env = data.get("environment") or path
envs.append(env)
for v in data["variables"]:
table.setdefault(v["name"], {})[env] = "secret" if not v["readable"] else "ok"
missing = 0
print("NAME".ljust(28) + "".join(e.ljust(14) for e in envs))
for name in sorted(table):
row = [table[name].get(e, "-") for e in envs]
missing += row.count("-")
print(name.ljust(28) + "".join(c.ljust(14) for c in row))
print(f"\nmissing cells: {missing}")
sys.exit(1 if missing else 0) # 抜けがあれば CI を落とす手元で動かすと、次のように出ます。
NAME development preview production
APP_VARIANT ok ok ok
EXPO_PUBLIC_API_URL ok ok ok
SENTRY_AUTH_TOKEN - secret secret
missing cells: 1- が抜けです。この形にしてから、ダッシュボードで目を往復させる作業がなくなりました。終了コードを 1 で返していますので、ビルド前のジョブに挟んでおけば、設定を足し忘れたまま preview を流すことも防げます。
私が取り違えていたのは、読めない値と入っていない値の区別でした
最初のうち、私は伏せ字の行を「まだ入れていない変数」として数えていました。足りないと思って同じ名前を作り直し、production 側の値を上書きしかけたことがあります。原因は仕組みではなく、私の読み方でした。
伏せ字は「読めない」の印であって、「無い」の印ではありません。 この区別だけは、急いでいる日でも先に確かめるようにしています。
eas env:pull --environment production を使うときも同じ注意が要ります。このコマンドは .env.local を書き出しますが、secret の変数は値ではなくコメント行として出力されます。行の見た目だけで判断すると、サーバー側には確かに入っている値を、もう一度作り直してしまうのです。
環境変数の棚卸しは、SDK を上げる前にも効いてきます。ネイティブ側の設定がどこで消えるかを追う話はExpo SDK 57 に上げる前に、prebuild で消えるネイティブ変更を洗い出すにまとめてありますので、あわせてご覧ください。
まずは production を一度だけ JSON にして、日付を付けて手元に残してみてください。翌週の同じ出力と差分を取るだけで、いつ誰が何を足したのかが自分の手元に残ります。私はそこから始めました。
お読みいただきありがとうございました。