
Tek Kod Tabanı, İki Uygulama: Fork'lamadan White-Label Nasıl Yapılır
GeekBye ve Pavleur, tek bir depodan derlenen, farklı markalanmış iki masaüstü uygulaması — fork yok, kopyalanmış kod tabanı yok. İşte tek kod tabanını iki ürüne derleten build zamanı mekanizması ve ikinci uygulamamızın kendini yanlış isimle tanıtmasına yol açan tek satırlık hata.
GeekBye bir toplantı not alma uygulaması. Pavleur ise yapay zekâ destekli bir mülakat pratiği uygulaması. İki farklı ürün gibi görünüyorlar çünkü gerçekten iki farklı ürünler — ama tek bir kod tabanından derleniyorlar; fork yok, zamanla ayrışan bir dal yok. Bu, white-label işinin mühendislik tarafı: "markalı bir uygulama neden satılır" değil (o başka bir yazının konusu), tek bir depoyu gerçekten nasıl iki uygulamaya derlersiniz — ve siz gidip aramadıkça her white-label sisteminde saklanan o gösterişsiz hata.
Üretin, dallandırmayın
Tek kod tabanından iki ürün çalıştırmanın yanlış yolu, uygulamanın her yerine if (product === 'pavleur') serpiştirmek ya da fork'layıp iki kopyanın asla ayrışmaması için dua etmektir. İkisi de hızla çürür.
GeekBye'ın yaklaşımı build zamanı kod üretimi. Her ürünün kimliğini tutan bir config dosyası var — configs/geekbye/config.ts, configs/pavleur/config.ts — isim, bundle id, URL protokolü, otomatik güncellemeler için releases deposu ve benzerleri. Build'den önce tek bir script çalışır:
node scripts/build-product.js pavleur
Bu script seçilen ürünün config'ini okur ve üç somut şey yapar:
- İki config modülü üretir — biri renderer için (
src/config/product.ts), biri main process için (electron/config/product.ts) — her biri "DO NOT EDIT MANUALLY — changes will be overwritten" başlığıyla damgalanır. Uygulamanın geri kalanı config'den yalnızcaproductName,applicationIdveprotocolimport eder ve hangi marka olduğunu hiç düşünmez. - Yalnızca o markanın asset'lerini kopyalar — ikonlar, küçük logo, toast bildirim ikonu. Bir GeekBye build'i fiziksel olarak sıfır Pavleur görseli içerir, tersi de öyle. Sızacak bir şey yok.
package.json'ı yeniden yazar (release build'lerinde) — app id, ürün adı, URL protokolü, yayınlanacak GitHub releases deposu ve güzel bir dokunuşla markalı mikrofon izni metni; böylece macOS yanlış markayla değil, "Pavleur mikrofona erişmek istiyor" diye sorar.
Uygulamanın tamamı marka bağımsızdır; marka build sırasında enjekte edilir. Çalışma zamanında yanlış gidecek bir ürün anahtarı yoktur, çünkü çalışma zamanında her zaman yalnızca tek bir ürün gömülüdür.
Tek backend, iki marka
Her iki uygulama da aynı backend'le konuşur. Peki sunucu bir isteğin hangi ürüne — ve hangi fiyatlandırmaya, prompt'lara ve limitlere — ait olduğunu nereden bilir?
İki sinyal, ikisi de build sırasında ayarlanır. Birincisi, config'e gömülen ve isteklerle birlikte gönderilen bir application id (geekbye ya da pavleur); böylece backend isteği doğru ürünün davranışına yönlendirebilir. İkincisi, istemcinin her backend çağrısına eklediği, Product/version (platform) biçiminde bir User-Agent — örn. Pavleur/1.2.5 (win32) ya da GeekBye/1.7.3 (darwin). O tek header, ayrı bir analitik alanı olmadan backend'e ürün başına ve işletim sistemi başına atıfı bedavaya verir. Otomatik güncellemeler ise izole kalır: her ürün kendi releases deposuna yayınlar, bu yüzden bir GeekBye güncellemesi asla bir Pavleur kurulumuna sunulamaz.
Onboarding da ürüne göre farklıdır — GeekBye'ın sihirbazı toplantı transkripsiyonuyla, Pavleur'ünki mülakat pratiğiyle ilgilidir — ve doğru olan, gömülü tek application id'den çalışma zamanında seçilir. Aynı makine, iki ön kapı.
Hata: Pavleur kendine GeekBye dediğinde
İşte savaş hikâyesi — ve her white-label sisteminin bir versiyonuna sahip olduğu hikâye bu.
Config üretimi çalışıyordu. İkonlar değişiyordu. Mikrofon istemi markalıydı. Protokoller ve bundle id'ler doğruydu. Görünür her ölçüte göre bir Pavleur build'i Pavleur'dü. Yine de bir süre boyunca her Pavleur build'i backend'e kendine GeekBye diyerek bağlandı.
Suçlu tek bir hardcoded string'di ve hayal edilebilecek en gösterişsiz yerde saklanıyordu: bir ağ retry yardımcısının içine sıkışmış User-Agent üreticisinde. Kullanıcıya dönük her şey config üzerinden yönlendirilmişti, ama bu derin tesisat aracında hâlâ düz bir `GeekBye/${version}` vardı. Kimse bir retry aracını "markalama" olarak düşünmez, bu yüzden kimse kontrol etmemişti. Düzeltme dört satırdı — config'den dinamik productName import edip hardcoded marka yerine onu interpolate etmek — ama asıl mesele dersin kendisi:
White-label "logo değişti mi" demek değildir. "Her kimlik string'i config üzerinden geçiyor mu" demektir. Ve son kaçaklar asla bakacağınız yer olan UI'da yaşamaz. User-Agent'ta, deep-link protokol işleyicisinde, izin istemi metninde, hata mesajlarında yaşarlar — kimsenin "marka" başlığı altına koymadığı tesisatta. Tek güvenilir savunma, marka literal'ini aktif olarak avlanacak bir şey gibi görmektir: kod tabanında hardcoded ismi grep'leyin ve onu tanımlaması gereken config dışında herhangi bir yerde görünürse build'i düşürün.
White-label'ın bize öğrettiği üç şey
- Üretin, dallandırmayın. İki küçük config modülünün build zamanı codegen'i artı seçici asset kopyalama, hem fork'lamayı hem de çalışma zamanındaki ürün kontrolü çalılığını yener. Uygulama marka bağımsız kalır; marka bir build girdisidir.
- Kimlik string'leri tesisata sızar. UI kolay %90'dır. Zor %10; User-Agent, URL protokolü, mikrofon izni string'i ve hata metinleridir. Hepsini tek bir
productName/applicationIdexport'undan geçirin — ve başıboş bir literal asla yayınlanamasın diye ham marka adını CI kapısı olarak grep'leyin. - Tek backend, çok marka; id ve header ile ayırt edin. Build zamanı application id artı
Product/version (platform)User-Agent, sıfır kopyalanmış altyapıyla ürün başına ve platform başına atıf sağlar — değişmez build zamanı kimliğini çalışma zamanı davranışından temiz biçimde ayırır.
Kendi markanız altında white-label bir not alma uygulaması işletmenin ürün ve gizlilik tarafı için bkz. white-label yapay zekâ not alıcı. Bu, GeekBye v2'ye dönüşen v1 hikâyesinin altıncı bölümü — bir önceki bölüm için, 127 commit'lik bir sürüm aslında nedir (v1.7.0); ve tüm yay için, yazılımı kusursuzluğa kadar yayınlamanın anatomisi.