おでーぶでおでーぶ

いろいろ書く。いろいろ。

v1 scheme 互換の v2 scheme 署名 apk を手動で作成する

source.android.com

モチベーション

今更なんでそんなことを手動で・・・と思うかもしれませんが、AndroidStudio 3.5 かつ minSdkVersion 24 以上の場合、署名 scheme が自動で判定されるようになりました。

v2 schemeAndroid N、つまり API 24 から動作します。AS 3.5 では Android N 以上をターゲットとするアプリに v1 scheme 互換を維持する必要はないとして、自動的に v1 scheme を行わないようになりました。それが例え v1SigningEnabled true && v2SigningEnabled false であったとしても、です。*1もっと言うと GUI 経由でも無視されます。*2

v2 scheme での署名を施す場合、apk 内部を変更したあとは再度署名を行う必要があります。*3 殆どの Android 開発者には関係のない話だと思いますが、諸般の事情により v1 scheme による署名でないといけない悲しい人類が存在します。そうです、僕です。

余談ですが、この仕様に対する疑念は IssueTracker にすでに上がっており、「仕様である」という一時対応から一転して再アサインがなされています。 https://issuetracker.google.com/issues/136691451

2019/09/27 追記 ↑は 3.5.1 で fix されたようです。

v1 scheme 互換を維持した v2 scheme 署名を行う方法(手動)

  • unsigned apk を作成する
  • jarsigner による署名を行う
  • zipalign による最適化
  • apksigner による v1 互換を維持した v2 署名

TL;DR

SigningConfig に明示的に null を渡したのち、以下を埋めつつ実行する。

export PATH="<android sdk home>/build-tools/<build tools version>:$PATH" # 28.0.3 で動作確認済
which jarsigner zipalign apksigner # すべて解決されることを確認してください。
keystore_path="<keystore のパス>"
keystore_alias="<keystore の alias>"
unsigned_apk_path="<署名無し apk のパス>"
output_apk_path="<最終出力先の apk のパス>"
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore "$keystore_path" "$unsigned_apk_path" "$keystore_alias"
zipalign -f -v 4 "$unsigned_apk_path" "$output_apk_path"
apksigner sign --ks "$keystore_path" --v1-signing-enabled=true --v2-signing-enabled=true "$output_apk_path"
apksigner verify --min-sdk-version=23 --verbose "$output_apk_path" # v1/v2 ともに verified であることを確認してください。

unsigned apk を作成する

BuildType#setSigningConfig(null) を呼びましょう。

jarsigner による v1 scheme 署名

v1 scheme は要するに jarsigning です。jarsigner を使って SHA1withRSA で署名します。SHA256withRSA で署名する場合は API 18 以下で動作しません。*4

$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore "<a file path of keystore>" "<a file path of unsigned apk := output path>" "<key store alias>"

なお上記コマンドの場合、署名 apk は上書き保存です。

zipalign による最適化

本記事のように jarsigning を行う場合、zipalign は jarsigner より あとに 行う必要があります。

zipalign -f -v 4 "<a file path of v1-signed apk>" "<a file path of v1-signed and zipaligned apk>"

zipalign をなぜ行う必要があるかについてはAndroidにおいてなぜzipalignをやる必要があるのかに譲ります。

v2 scheme の署名を必要としない場合、この時点で v1 scheme による署名 apk が生成されています。

apksigner による v1 互換を維持した v2 署名

apksigner を使って、v1 scheme 互換を維持したまま、v2 scheme による署名を施します。

apksigner sign --ks "<a file path of keystore>" --v1-signing-enabled=true --v2-signing-enabled=true "<a file path of v1-signed and aligned apk := output path>"

なお上記コマンドの場合、署名 apk は上書き保存です。

これで v1 scheme 互換の v2 scheme 署名 apk が完成です。最後に以下のコマンドを実行して確認しておきましょう。

apksigner verify --min-sdk-version=23 --verbose "<a file path of apk>"

おまけ : なぜ apksigner verify --min-sdk-version=23 なのか

ワークアラウンドです。まず以下のコマンドで apksigner のバージョンが確認できますが、このバージョンは一切信用できないことを知りましょう。

apksigner version

apksigner はコマンドラインツールとしての interface であり、内部では apksig と呼ばれるプロジェクトが署名挙動を担っています。したがって、apksigner のバージョンとは interface のバージョンであり、挙動のバージョニングを指しません。具体的な例としてあげるならば、BuildTools version 27.0.3 と 28.0.3 に含まれる apksigner はともにバージョン 0.8 ですが、apksig は変更されています。*5

この問題は apksigner を使う一部の人間にとって非常にツラいものです。そうです、僕が辛かったんです。

例えば apksigner は AndroidManifest に記述された minSdkVersion をデフォルト利用し、署名実行時に v1 scheme を利用するかどうかなどを判定します。*6必要のないことはしなくて良いという挙動については make sense という感じですね。さて、verify コマンドを見てみましょう。

apksigner verify --verbose "<a file path of apk>"

verify コマンドではその apk が v1 scheme として verified であるかどうか、v2 scheme として verified であるかどうかを確認できます。これは minSdkVersion に依らず、apk の状態を検証するものです。いや、でした

現在*7、このコマンドも AndroidManifest に記述された minSdkVersion を見るようになってしまったようです。結果、v1 scheme 互換の v2 scheme 署名 apk (minSdkVersion >= 24) は v1 scheme unverified 扱いになります。正確に署名状態が知りたい方は --min-sdk-version=23 を渡すようにしましょう。

おまけのおまけ

署名に興味のある方は src/apksigner/java/com/android/apksigner/help_sign.txt - platform/tools/apksig - Git at Google 辺りから追っておきましょう。v3 scheme (Android Pから) による key rotate support なども知っておくと面白いですね。

*1:この場合は app-debug.apk などの通常名称のまま unsigned apk になります。

*2:同じオプションをいじってるだけなので、それはそうという感じですが。

*3:つまり v2 scheme で署名済み apk を修正した場合は署名の妥当性が崩れる仕様であり、v2 scheme がセキュアと呼ばれる仕様の1つです。

*4:minSdkVersion 19以上にしたいですね。

*5:なお現在は 0.9 に上がっていることがリポジトリの最新リビジョンで確認できます。

*6:ただしオプションで明示的に渡すことで、上書きできます。

*7:BuildTools version 28.0.3

Kotlin Fest 2019 で Deep Dive into Kotlin DSL という発表をした

Kotlin Fest 2019 - connpass

「Kotlinを愛でる」をビジョンに、Kotlinに関する知見の共有と、Kotlinファンの交流の場を提供する技術カンファレンスです。

というカンファレンスで、45分枠「Deep Dive into Kotlin DSL」を英語でやりました。

speakerdeck.com

内容

  • DSL の基礎知識
  • Kotlin DSL を記述する上での言語仕様、機能の紹介
  • DSL を開発する上での Tips

Kotlin DSL 自体は別に特別な機能でもなんでもないので、 Deep Dive とは・・・って感じなんですが、DSL の発表になってもそれはまた主旨が違うので、「Kotlin らしさを維持した DSL とはどうやっていくか」みたいな点を出来る限り意識しました。

suspend は取り上げませんでした。継続における中断以外に suspend を使うべきか否かという話が DSL どうこう以前の論点として未だに残っていると思っているからです。*1

所感

英語でやってよかった、というのが今の心境です。日本語だと多分後悔とか色々あったんじゃないかなと思ってます。

英語でやると決めた当時、対外的に多くの成果物(e.g. 発表、記事)をアウトプットしてる人と とある縁で関わりがあり、そのモチベーションの一端を聞き、それだけでも自分にはない軸で行動に移してる点に一種の尊敬を覚えました。その流れで「俺は何か自分の成果物を外に出しているだろうか、頑張っているだろうか」ってところでちょっと思う節があり、「英語でやってみた方が自分のためになる」と信じてやってみることにしました。

追記: 「英語でやってみた方が自分のためになる」というのがなかなか突飛だったのでその説明を。英語でのコミュニケーション自体にはかなり抵抗が減っていたのですが、見知らぬ多くの日本人の前で英語を話すことについて恐怖を感じてました。*2そのため、あえて日本人の前で英語登壇をやることが自信になるんじゃないか、と思った次第です。海外のミートアップでの登壇は勿論したいですね。

なんだかんだ発表後は楽しさが残ったので、自分のためになるかは今後分かるとして、今は良かったという感じです。

英語での登壇

CfP へ応募した proposal の内容は KotlinFest 2019 CfP に応募した - おでーぶでおでーぶ から辿れるんですが、 英語発表でもいい という備考を(見栄で)残していたところ、accept と同時に「可能なら英語でやって欲しい」という旨の連絡が来た*3こと、それと少し上に書いた「英語でやってみた方が自分のためになる」という考えの元に英語でやることにしました。

今までに英語で何か発表をしたか・・・というと、

  • 大学院2年 (4年前) のとき、海外カンファレンスでの発表 20分くらい (スライドも原稿も先生の手直しが入って、ほぼ100%書き換えられたので実質 Text to Speach だった)
  • ここ2年ほどで、身内のコミュニティで10人程度相手に3-5分程度の英語 LT を2-3回

という感じの経験しかなかったです。少なくとも5分以上の英語原稿を書いたことはなかったです。

そんな経験の中で45分の英語発表に不安がなかったかと言われると「あった」と答えるし、45分の発表を(多分)無事に終えた今でも「これからも不安というか思うところは少なからずあるだろう」って思ってます。

ただ、「英語原稿が意味不明で伝わらなかったらどうしよう」「発表時に噛んだりして伝わらなかったらどうしよう」って気持ちよりも「おっしゃ、やってやるぞ」という気持ちの方が強く、かつ日本語だろうが英語だろうが伝えるべき内容は間違いなく同一なので、資料作っていて「これ誰かに発表原稿を見てもらう時間さえあれば別に英語発表だろうが関係ないのでは・・・?」という考えに至りました。*4 もちろん英語原稿を作るの労力はどんなに前向きに考えようと変わらなかったです。

今後も機会があれば時間に関係なく英語でやってみるつもりです。*5

やらかし

日本語への同通を採択時は知らされていなかったので「日本語資料も用意します」と豪語してしまい、有言実行ということで日本語の注釈付きのスライドを用意することにしました。*6

ギリギリ当て終わったので、発表する前に資料リンクをツイートしました。したつもりでした。だがしかし、

はい。ハッシュタグを付け忘れたことに気付いたのは発表終了後です。すみませんでした・・・

雑記

英語が不安っていうよりも、そもそも人前で何かを発表するという行為自体が凄く苦手で、緊張がマッハでした。*7 時々イベントで日本語登壇をしてますが、相手が10人くらいだろうが普通に手が震えるし、手先は冷たくて顔は火照るし、緊張性発汗で跳満です。

なんか CyberAgent のブースで寿司打やって遊んだらコーヒーを貰えることになりました。最近コーヒーにハマっているので、嬉しい。

さいごに

  • 前日にも関わらずシュッと発表原稿の軽い推敲をしてくれた Matthew Vern
  • 発表前の練習(35分)に付き合ってくれて、伝わりにくい表現を指摘・修正してくれた Shohei Kawano
  • Kotlin Fest オーガナイザー、スタッフの皆さん、通訳の皆さん

ありがとうございました!

あ、Have a nice Kotlin! 🍻

*1:ArrowKt みたいに副作用を suspend で表現するという話だとか

*2:発音を間違える、良い発音で喋る・・・これで笑われるような状況だった中学校生活を未だに覚えていて、(信頼をおいていない、おく前の)日本人の前で話すとどう思われるんだろうと不安になったりします。

*3:英語だから採択されたというわけではなく、あくまでも可能なら英語でという話

*4:そもそも日本語だったら意味不明じゃないだとか、噛まないだとか、そういう考え自体が本当に成り立つんだっけ・・・みたいな気持ちになっています

*5:日本にいるなら日本語でやれよっていう考えの人もいることは知ってますが、正直そういう人の意見よりは自己満足の方が優位です。ただ英語ハラスメントは最悪の極みなので、機会に委ねます。

*6:発表者部屋で黙々と日本語の意訳をつけていましたが、そこまで余裕がなかったので口語と文語が入り混じり、文調が迷走している点は大目に見てください。

*7:ちなみにジムはちゃんと行きました。

新しいベッドマットレスを買ったら快眠マンになったので買った方がいいよという話

今の家に引っ越して約4年。そのときに買ったニ○リのベッドマットレスを使ってきた。

購入当初はしっかりした作りで超快眠だったけれど、さすがにへたってきたのか、最近はマットレスの中央(ケツの部分)が沈みやすくなってしまった。

そのまま仰向けで寝ると腰が曲がったままになるせいか、腰が痛くて起きられないということを何回か経験して、マットレスの端っこで寝るというワークアラウンドに頼るように。

加えて、体の形に完璧にフィットするようにマットレスが沈んで密着してくるわけ。つまりめちゃくちゃ暑い。尋常じゃない。浮きたい、と本気で思うくらいには暑い。

最近の起床時間が疎らになってきたことで睡眠の質落ちてるのかな・・・と思ってきていたし、だらだらしてないで今すぐ買い換えないと!って思うキッカケもあったので、数週間前に買い替えを決意してその日のうちにお店探し。

運良く近所にオーダーメイド枕の専門店があったので記憶しておいたら、たまたま行った歯医者の隣の店舗で、麻酔も切れないまま突撃。サクッと測定してその場で枕をオーダーメイド。そのままマットレス選びへ。

西川を取り扱っていたので air の新しいシリーズを根拠無しに買おうと思ったのだけど、高かったり有名なマットレスが良いわけじゃない、ということで測定を受けて決めることに。

寝ている状態でケツの部分が沈まないだとか、ボコボコ素材や寝返りの際の音が気にならないかとか、色んな観点で見るように言われ、多くのマットレスを試させてもらえた。

本当にしっくり来るという感覚を味わって購入したのは以下のモデル。*1

www.nishikawasangyo.co.jp

今は仰向けで寝ても腰が全く痛くならずに爆睡できるようになった。寝付きも目覚めもスッキリ。*2

3つ折りで軽いから簡単に干せるだとか、3つのマットレスを入れ替えれば仮に最初真ん中だったマットレスがへたってもやっていけるだとか、実用面の利点もかなり魅力的。

少し高い買い物だったけれど、その価値はこの数週間で十二分に証明されたと思えるくらい快眠になったので最高。1回海外旅行を我慢するだけで腰が救われるのだから、大変オトク。

あとやはりプロはすごいので、一緒に選んでもらった方が圧倒的に良い。自分で選ぶときは「なんとなく寝やすそう」くらいでしかなかったのだけど、リラックスの具合のせいで「お店でちょっと気になる程度の違和感は、家だとまあまあ気になるくらいの違和感になる」という話も割と自分では納得行くもので、こんなに真面目に寝具を選んだのは初めてだったんじゃないかと。

自分は入眠で苦労はしてないのだけど、もし睡眠周りに色々不安な方がいたら、寝具を変えることもかなり効くんじゃないかなと思う。枕だけなら1万円くらいだったので、ぜひ。

おまけ

お店はここ

pillowstand.com

睡眠測定アプリはこれ

play.google.com

*1:正確にはベッドマットレスじゃないんだけど、フレームに載せて使える

*2:一瞬でベッドを出られるとは言ってません

internal app sharing と DeployGate の機能差早見表

この記事は DeployGate 社員としてではなく、アプリ開発者の見解として書いています(おわりに、を除く)。また Beta などの他のサービスとの比較は入っていないので、internal app sharing または DeployGate の2択として記述しています。

20200427 in-app-updates が internal-app-sharing であればテスト出来ることを追記*1

TL;DR

  • 完全無料がいい && いっぱい固定リンク発行したい -> internal app sharing
  • in-app-updates, dynamic delivery をテストしたい -> internal app sharing
  • アップデートを配布したい -> DeployGate
  • テスターに一般人が含まれる -> DeployGate
  • こだわりはない -> なんでもいいと思います

というか、完全無料というポリシーがない限りは併用でいいと思いますし、あとは QA で何をチェックしたいかだと思います。

参考:

便宜上、internal app sharing の固定リンクとDeployGate の配布ページを同時に表現する場合、「配布リンク」としています。

○ : 対応済み
△ : 対応予定または一部対応
× : 対応していない(対応予定なし、不明)
機能等 internal app sharing DeployGate
無料利用が可能か *2
配布リンクの上限 ありません グループプラン以上では制限はありません
debuggable app の配布
無署名 app の配布 ×*3
aab upload
dynamic delivery ×
dynamic feature module を利用した app の配布 *4
Upload API
任意の署名キーの利用 ×
任意のpackage名の利用 *5 *6
FireOS などでの利用 ×
Production と完全に同じ app を配布できるか ×*7 ×*8
同一バージョンコードの配信
リビジョン情報の埋め込み *9
ダウンロード数の制限機能 *10 *11
Uploader の制限
配布リンクの削除 ×
配布リンクに対する配信 app version の更新 ×
配布リンクへのパスワード設定 ×
自然流入のテスターへのインストール許可 *12
特定のテスターへのインストール許可
↑に認証をつける *13
異なる app 間でのテスターの流用 *14
異なる app ごとにテスターを設定する ×
参加テスターの確認 *15
テスターへの連絡 × ×*16
in-app updates の利用 ×

また internal app sharing では機能を利用するために Play Store app の Developer モードを有効にするなどの操作が必要で、ガイドがなく、テスターに一定の説明やリテラシー要求を行う必要があります。DeployGateではアプリ内部からの導線で前設定が完結するようになっています。*17

おわりに

現段階で、internal app sharing にのみ「技術的に」可能であることは dynamic feature module のダウンロード程度です。他の機能についてはすでに DeployGate で実装している、上位互換なものも多く、またやろうと思えば技術的に実装可能なものが殆どです。

ということで、△について、一緒に実装してくれる人を募集しています。

*1:調査時点で出来なかったのか、やり方がまずかったのかは分かりません。恐らく後者。

*2:最初課金しないと配布ページが〜と書いてたんですが、個数制限有りで利用できました・・・

*3:無署名 app はインストール不可のため、署名し直す必要があります。aab対応後に実現可能になりますが、特に話に上がっていないので△ではありません

*4:DeployGate ではuniversal apkでの配信になります

*5:PlayStore に存在するもののみ配信可能です

*6:異なるパッケージ名の app をグルーピングして同一視する機能などはありません

*7:署名が選べません

*8:dynamic deliveryが使えません

*9:配信名を設定できますが、リリースノートと併記するといった用途には耐えきれません

*10:変更できません

*11:変更が可能です

*12:internal app sharing では自然流入を許可する/しないを一括管理するため、全てのリンクに対して設定が継承される。したがって一部のリンクでは認証有り、といった使い分けができない。

*13:プランごとに異なります。また配布ページにはパスワード機能がありますが、別途認証機能を実装することを検討しています。

*14:グループプラン以上が必要です

*15:ドキュメントによると出来るらしいんですが、UIがありません

*16:特に話に上がっていないので、実現可能性はあれど△としませんでした

*17:ただしリテラシー要求はいらないとは言ってません

CircleCI (Workflow) の tags filter でドキュメントを誤読してハマった

CircleCI (Workflow) の tags filter で document を読み違えてハマったのでメモ。

Configuring CircleCI - CircleCI

CircleCI では filters 機構を用いて、tag/branch ごとに job の実行を制御することが可能。*1

例えば master branch のみで実行したい場合は

filters:
  branches:
    only: master

とすればよく、反対に master branch では実行したくない場合、

filters:
  branches:
    ignore: master

と記述すればよい。これは filters 指定がなければ デフォルトで全ての branch がjob実行対象 であることを意味している。

tags についてはどうなっているかというと

CircleCI does not run workflows for tags unless you explicitly specify tag filters. Additionally, if a job requires any other jobs (directly or indirectly), you must specify tag filters for those jobs.

Tags can have the keys only and ignore keys. You may also use regular expressions to match against tags by enclosing them with ‘/s’, or map to a list of such strings. Regular expressions must match the entire string. Both lightweight and annotated tags are supported.

- Any tags that match only will run the job.
- Any tags that match ignore will not run the job.
- If neither only nor ignore are specified then the job is skipped for all tags.
- If both only and ignore are specified the only is considered before ignore.

明示的に tags の設定をしないとタグは job 実行対象にならないと言っている。ここで v1.0.0 タグでのみ実行させる場合を考える。意味を深く考えずに branches の例を取るなら

filters:
  tags:
    only: v1.0.0

のようになるが、この表記の意味は 任意の branch 及び v1.0.0 タグのとき job 実行対象とする となってしまう。

したがって、v1.0.0 タグでのみ実行させる場合は以下のように 全ての branch を実行対象としないように記述する 必要がある。

filters:
  tags:
    only: v1.0.0
  branches:
    ignore: /.*/

ちなみに以下でも any branch で job が実行されてしまう。

filters:
  tags:
    only: v1.0.0
  branches:
    only: []

久々に tags を使おうとしてハマりまくってしまった・・・ちゃんと読むとたしかにそう書いてあるんだけど。なんとなく「指定する == 絞り込み」みたいな感覚だった。

filters:
  tags:
    run: true
  branches:
    run: false

みたいな書き方ができると嬉しいなぁ!

そういえば tags を必要とする workflow の実行したい jobs には全てこの filters をつけないといけないのがつらいけど、placeholder job を作って、そいつに filters を設定し、その job を始点とするような job tree を書けばいいんじゃないんですかね(動くかは知らない)

*1:余談ではあるが、workflow-level filtering が欲しい

KotlinFest 2019 CfP に応募した

Deep Dive into Kotlin DSL

DSL の話から Kotlin DSL (Gradle Kotlin DSL じゃないよ)について触れ、自分たちでも DSL を構築する方法を知る・構築できるようになることを目的とした発表です。

日本語で出したんですが、英語発表でもいいよと書いておきました。(英語発表がどの程度欲しい・採択するつもりなのか分からなかったので)

この発表では DSL の基礎から Kotlin DSL における拡張表現や開発方法まで踏み込んで発表します。

また Kotlin DSL の実例として

- Gradle Kotlin DSL
- Ktor or DOM (like React)
- Android Jetpack Compose

あたりを取り上げて、どのように Kotlin DSL が作用し、Java/Groovy ではなぜ出来ないのかについても触れたいと思います。

Topics (予定)

- What's domain-specific language and Kotlin DSL?
- Let you know real examples of Kotlin DSL
- Getting started understanding and developing DSL
    - What's the *primitive* DSL
    - The power of *Lambda* in DSL world
    - Typesafe builder and Kotlin DSL
- Syntactic extension
    - Extension method e.g. Gradle Kotlin DSL
    - Operator overloading e.g. Ktor
    - Infix operator based Fluent style e.g. Assertion libraries
    - DSLMarker and implicit receiver resolution e.g. Jetpack Compose
- For DSL users
    - Adapt Kotlin DSL to existing libraries
- For DSL developers
    - How to keep both usabilities of Kotlin DSL and other JVM languages
    - Examples in Gradle Kotlin DSL

英語なら以下みたいな感じ。

In this presentation, I will talk about the basis and practical application of Kotlin DSL in achieving attendees will be able to develop Kotlin DSL.

I will pick up the following examples to explain how Kotlin DSL work and why Java/Groovy are not enough or not good at doing so.

- Gradle Kotlin DSL
- Ktor or DOM (like React)
- Android Jetpack Compose

Topics (just a plan)

- What's domain-specific language and Kotlin DSL?
- Let you know real examples of Kotlin DSL
- Getting started understanding and developing DSL
    - What's the *primitive* DSL
    - The power of *Lambda* in DSL world
    - Typesafe builder and Kotlin DSL
- Syntactic extension
    - Extension method e.g. Gradle Kotlin DSL
    - Operator overloading e.g. Ktor
    - Infix operator based Fluent style e.g. Assertion libraries
    - DSLMarker and implicit receiver resolution e.g. Jetpack Compose
- For DSL users
    - Adapt Kotlin DSL to existing libraries
- For DSL developers
    - How to keep both usabilities of Kotlin DSL and other JVM languages
    - Examples in Gradle Kotlin DSL

おまけ

昨日の勉強会で Nkzn さんに「Gradle Kotlin DSLの話聞きたい」と言われたけど、45分やれる気がしないのでサクッと5分LTに送るか悩む。

GitHubなどで Issue/PR ごとに情報を読み書きする

GitHub などを使っていると PR Review だのなんだのを自動化したくなりますよね。

ただ GitHub Issue などには Issue ごとの metadata attachment みたいなものがなく、何かしらの方法で情報を保存する必要があります。

CI のキャッシュを用いて、キーを PR URL などにすれば1対1のデータリレーションが作成できますが、ここにも CI ごとの特性や複数の情報保持などで少々面倒くさいことになります。

例えば CircleCI だと同じキャッシュキーへのデータ上書きはできない *1 ため、PR URL をキーにしてデータを保存するということが困難です。

そこで Issue/PR/comment Body が markdown (もとい HTML rendering) であることを利用して、Issue/PR ごとにデータを保存する方法を紹介します。

<!-- <metadata name>/<ここに json を埋め込む> -->

↑のように HTML コメントを利用するだけです。HTML コメントは見かけ上 rendering されないため、見た目を損ねることなくデータの保持が可能です。 *2

あとは以下の流れで json を取り出し、よしなにしましょう。

  • Issue/PR/comment body を DOM Tree に変換
  • Comment Node だけ filter
  • metadata name で filter
  • 該当 node の text content から <metadata name>/ を削除し、JSON にパースする
  • よしなにする

これの使い方は本当に多様で、利用方法の一例は別の記事で紹介します。

*1:expire した場合は新規書き込みと見なしており、上書きのときに限ります

*2:これは danger などで実際に利用されているテクニックです。