Steven
Steven1 分で読める

一つのコードベース、二つのアプリ: フォークせずにホワイトラベル化する方法

GeekByeとPavleurは、単一のリポジトリから作られる、別々にブランド付けされた二つのデスクトップアプリです — フォークもなく、コードベースの複製もありません。これが、一つのコードベースを二つのプロダクトへコンパイルさせるビルド時の仕組みであり、そして私たちの二つ目のアプリが間違った名前で自己紹介してしまった一行のバグの話です。

エンジニアリング
アーキテクチャ
ビルド
GeekByeリリース
一つのコードベース、二つのアプリ: フォークせずにホワイトラベル化する方法

GeekByeはミーティングのノートテイカーです。PavleurはAIによる面接練習アプリです。両者は二つの異なるプロダクトのように見えます — なぜなら両者は本当に二つの異なるプロダクトだからです — しかし両者は一つのコードベースから作られており、フォークもなければ、ずれていくブランチもありません。これがホワイトラベル化のエンジニアリング側です: 「なぜブランド付きアプリを売るのか」ではなく(それは別の記事です)、実際にどうやって一つのリポジトリを二つのアプリにコンパイルするのか — そして、探しに行くまであらゆるホワイトラベルシステムに潜んでいる、地味なバグの話です。

分岐するな、生成せよ

一つのコードベースから二つのプロダクトを走らせる間違ったやり方は、アプリのあちこちにif (product === 'pavleur')をばらまくか、フォークして二つのコピーが決してずれないよう祈ることです。どちらも速く腐ります。

GeekByeのアプローチはビルド時のコード生成です。各プロダクトはconfigファイルを持ちます — configs/geekbye/config.tsconfigs/pavleur/config.ts — その識別情報を保持しています: 名前、バンドルid、URLプロトコル、自動更新用のリリースリポジトリ、などなど。ビルドの前に、一つのスクリプトが走ります:

node scripts/build-product.js pavleur

そのスクリプトは選ばれたプロダクトのconfigを読み、三つの具体的なことをします:

  1. 二つのconfigモジュールを生成する — 一つはレンダラー用(src/config/product.ts)、一つはメインプロセス用(electron/config/product.ts) — それぞれに*「手動で編集するな — 変更は上書きされる」*というヘッダーが刻印されます。アプリの残りはただconfigからproductNameapplicationIdprotocolをインポートするだけで、自分がどのブランドなのかを一切考えません。
  2. そのブランドのアセットだけをコピーする — アイコン、小さなロゴ、トースト通知アイコン。GeekByeビルドは物理的にPavleurの絵素材をゼロしか含まず、逆もまた然り。漏れるものは何もありません。
  3. package.jsonを書き換える(リリースビルドで) — アプリid、プロダクト名、URLプロトコル、公開先のGitHubリリースリポジトリ、そして嬉しいことに、ブランド付けされたマイク許可テキストまで — macOSが「Pavleurがマイクへのアクセスを求めています」と尋ね、間違ったブランドを出さないように。

アプリ全体はブランド非依存であり、ブランドはビルド時に注入されます。ランタイムのプロダクト切り替えを間違える余地はありません。なぜならランタイムには、焼き込まれた一つのプロダクトしか存在しないからです。

一つのbackend、二つのブランド

両アプリは同じbackendと話します。では、あるリクエストがどのプロダクト — そしてどの料金、プロンプト、制限 — に属するのかを、サーバーはどうやって知るのでしょうか?

二つのシグナルがあり、どちらもビルド時に設定されます。第一に、configに焼き込まれてリクエストとともに送られるapplication id(geekbyeまたはpavleur)で、backendが正しいプロダクトの挙動へルーティングできます。第二に、クライアントがあらゆるbackend呼び出しに付けるUser-Agentで、Product/version (platform)の形をしています — 例えばPavleur/1.2.5 (win32)GeekBye/1.7.3 (darwin)です。このヘッダー一つで、backendは別のアナリティクスフィールドなしに、プロダクトごとかつOSごとの区別を無料で手に入れます。一方で自動更新は分離されたままです: 各プロダクトは自分自身のリリースリポジトリに公開するので、GeekByeの更新がPavleurのインストールに配信されることは決してありません。

オンボーディングもプロダクトごとに異なります — GeekByeのウィザードはミーティングの文字起こしについて、Pavleurのウィザードは面接練習について — そして正しい方が、焼き込まれた単一のapplication idからランタイムに選ばれます。同じ仕組み、二つの玄関です。

バグ: Pavleurが自分をGeekByeと名乗ったとき

これが戦記であり、あらゆるホワイトラベルシステムがそのバリエーションを持つものです。

config生成は動いていました。アイコンは入れ替わりました。マイクのプロンプトはブランド付けされていました。プロトコルとバンドルidは正しかった。目に見えるあらゆる尺度で、PavleurビルドはPavleurでした。それなのにしばらくの間、あらゆるPavleurビルドは自分をGeekByeと名乗ってbackendに電話をかけていたのです。

犯人はハードコードされたたった一つの文字列で、想像しうる最も地味な場所に潜んでいました: ネットワークのリトライヘルパーの中に押し込まれた、User-Agentビルダーです。ユーザーの目に触れるものはすべてconfig経由になっていましたが、この一つの深い配管ユーティリティだけは、まだ`GeekBye/${version}`というリテラルを抱えていました。誰もリトライユーティリティを「ブランディング」だとは思わないので、誰もそれを確認していなかったのです。修正は四行 — configから動的なproductNameをインポートし、ハードコードされたブランドの代わりにそれを埋め込む — でしたが、教訓こそが要点です:

ホワイトラベル化とは「ロゴが変わったか」ではない。「すべての識別文字列がconfig経由になっているか」だ。 そして最後の生き残りは、あなたが探すであろうUIには決していない。それはUser-Agent、ディープリンクのプロトコルハンドラ、許可プロンプトの文言、エラーメッセージ — 誰も「ブランド」に分類しない配管に住んでいる。唯一の信頼できる防御は、ブランドリテラルを能動的に狩る対象として扱うことだ: ハードコードされた名前をコードベースでgrepし、それを定義するはずのconfigの外側のどこかにそれが現れたらビルドを失敗させよ。

ホワイトラベル化が教えた三つのこと

  1. 分岐するな、生成せよ。 二つの小さなconfigモジュールのビルド時コード生成に加えて選択的なアセットコピーは、フォークにもランタイムのプロダクトチェックの茂みにも勝る。アプリはブランド非依存のまま; ブランドはビルドの入力だ。
  2. 識別文字列は配管に漏れ出す。 UIは簡単な90%だ。難しい10%はUser-Agent、URLプロトコル、マイク許可の文字列、エラーの文言。それらをすべて一つのproductName/applicationIdのエクスポート経由にせよ — そして生のブランド名をCIゲートとしてgrepし、はぐれリテラルが決して出荷されないようにせよ。
  3. 一つのbackend、多数のブランド、idとヘッダーで区別せよ。 ビルド時のapplication idにProduct/version (platform)のUser-Agentを加えれば、重複したインフラゼロで、プロダクトごと・プラットフォームごとの区別が手に入る — 不変のビルド時アイデンティティを、ランタイムの挙動からきれいに切り離しながら。

自分のブランドでホワイトラベルのノートテイカーを走らせることの、プロダクトとプライバシーの側面については、ホワイトラベルAIノートテイカーをご覧ください。これはGeekBye v2になるv1の物語の第六章です — 前の章については、127コミットのリリースとは実際に何なのか(v1.7.0)を; そして弧の全体については、ソフトウェアを完璧に出荷することの解剖学をご覧ください。

関連記事

たった一つのCSS変数、五回のレビュー、そして嘘をついたSwiftツールチェーン
Steven
Steven1 分で読める

たった一つのCSS変数、五回のレビュー、そして嘘をついたSwiftツールチェーン

GeekBye v2.0.7は、オーバーレイ全体を調整可能な半透明にしました — 一行のCSS変更のように聞こえますが、まったくそうではありませんでした。本当の物語は、コードレビューが何を捕まえたかにあります: 薄れることを拒んだ二つの面、同じ不透明度なのに間違って見えた録音バー、そして自分たちのドキュメントがレビュアーに間違ったSwiftのバージョンを伝えたせいで672バイトを行ったり来たりしたコンパイル済みバイナリです。

エンジニアリング
デザイン
ビルド
音声コードを5,000行削除したら — 文字起こしが二重に出始めた
Steven
Steven1 分で読める

音声コードを5,000行削除したら — 文字起こしが二重に出始めた

GeekBye v1.6は、オンデバイスの二つのSwift文字起こしエンジンを引き剥がし、一つの統一パイプラインに置き換えました — 差し引き5,000行超の削除を、人々がアプリで会議の真っ最中に、です。すると文字起こしが二重に現れ始めました。バグが、まだエンジンは二つあると思い込んでいた唯一の層へ移動したからです。

エンジニアリング
文字起こし
アーキテクチャ
127コミットのリリースとは実際に何なのか
Steven
Steven1 分で読める

127コミットのリリースとは実際に何なのか

GeekBye v1.7.0は11日間で127コミットでした。外から見れば、それは百個の小さな作業の集まりに見えます。しかし内側から見れば、それは編み合わされた二つの大きな機能でした — そしてそのうちの一つは間違った場所に作られ、リリースの途中で引き剥がされて作り直されたのです。これが大きなリリースの解剖学です。

エンジニアリング
リリース
アーキテクチャ