Steven
Steven6 min citire

O singură bază de cod, două aplicații: cum faci white-label fără fork

GeekBye și Pavleur sunt două aplicații desktop cu branding diferit, construite dintr-un singur repository — fără fork, fără bază de cod duplicată. Iată mecanismul de build-time care face ca o singură bază de cod să compileze în două produse și bug-ul de o linie care a făcut ca a doua noastră aplicație să se prezinte cu numele greșit.

Inginerie
Arhitectură
Build
Lansări GeekBye
O singură bază de cod, două aplicații: cum faci white-label fără fork

GeekBye este un notetaker de ședințe. Pavleur este o aplicație de exersare a interviurilor cu AI. Par două produse diferite pentru că sunt două produse diferite — dar sunt construite dintr-o singură bază de cod, fără fork și fără vreun branch care să se abată. Aceasta e partea de inginerie a white-label-ului: nu „de ce să vinzi o aplicație branded" (asta e altă postare), ci cum compilezi de fapt un singur repository în două aplicații — și bug-ul lipsit de glamour care se ascunde în orice sistem de white-label până te apuci să-l cauți.

Generează, nu ramifica

Modul greșit de a rula două produse dintr-o singură bază de cod este să presari if (product === 'pavleur') prin toată aplicația sau să faci fork și să te rogi ca cele două copii să nu diverge niciodată. Ambele se strică repede.

Abordarea GeekBye este generarea de cod la build-time. Fiecare produs are un fișier de config — configs/geekbye/config.ts, configs/pavleur/config.ts — care îi conține identitatea: nume, bundle id, protocol URL, repository-ul de lansări pentru auto-update și așa mai departe. Înainte de un build, rulează un singur script:

node scripts/build-product.js pavleur

Scriptul acela citește config-ul produsului ales și face trei lucruri concrete:

  1. Generează două module de config — unul pentru renderer (src/config/product.ts), unul pentru procesul principal (electron/config/product.ts) — fiecare marcat cu un antet „DO NOT EDIT MANUALLY — changes will be overwritten". Restul aplicației doar importă productName, applicationId și protocol din config și nu se gândește niciodată ce brand este.
  2. Copiază doar assets-urile acelui brand — iconițe, logo-ul mic, iconița pentru notificări toast. Un build de GeekBye conține fizic zero grafică Pavleur și viceversa. Nimic de scurs.
  3. Rescrie package.json (la build-urile de release) — app id-ul, numele produsului, protocolul URL, repo-ul de lansări GitHub în care să publice și, foarte plăcut, textul branded al permisiunii pentru microfon, astfel încât macOS să întrebe „Pavleur ar dori să acceseze microfonul", nu brandul greșit.

Întreaga aplicație e agnostică față de brand; brandul e injectat la build-time. Nu există niciun switch de produs la runtime care să poată fi greșit, pentru că la runtime există mereu doar un singur produs încorporat.

Un backend, două branduri

Ambele aplicații vorbesc cu același backend. Deci cum știe serverul cărui produs — și cărei scheme de prețuri, prompturi și limite — îi aparține o cerere?

Două semnale, ambele setate la build-time. Primul, un application id (geekbye sau pavleur) încorporat în config și trimis cu cererile, astfel încât backend-ul să poată direcționa către comportamentul produsului potrivit. Al doilea, un User-Agent pe care clientul îl atașează la fiecare apel către backend, de forma Product/version (platform) — de ex. Pavleur/1.2.5 (win32) sau GeekBye/1.7.3 (darwin). Acel singur header îi oferă backend-ului atribuire per produs și per sistem de operare gratuit, fără un câmp de analytics separat. Între timp, auto-update-urile rămân izolate: fiecare produs publică în propriul repository de lansări, așa că un update de GeekBye nu poate ajunge niciodată la o instalare de Pavleur.

Și onboarding-ul diferă de la produs la produs — wizard-ul GeekBye e despre transcrierea ședințelor, cel al lui Pavleur e despre exersarea interviurilor — iar cel potrivit e ales la runtime din singurul application id încorporat. Aceeași mașinărie, două uși de intrare.

Bug-ul: când Pavleur s-a numit pe sine GeekBye

Iată povestea de război, și e cea de care orice sistem de white-label are o variantă.

Generarea config-ului funcționa. Iconițele se schimbau. Prompt-ul pentru microfon era branded. Protocoalele și bundle id-urile erau corecte. După orice măsură vizibilă, un build de Pavleur era Pavleur. Și totuși, o vreme, fiecare build de Pavleur suna acasă la backend numindu-se GeekBye.

Vinovatul era un singur string hardcodat, și se ascundea în locul cel mai lipsit de glamour cu putință: builder-ul de User-Agent, îndesat într-un helper de retry de rețea. Tot ce era vizibil pentru utilizator fusese rutat prin config, dar acest singur utilitar de plumbing adânc mai avea încă un literal `GeekBye/${version}` în el. Nimeni nu se gândește la un utilitar de retry ca la „branding", așa că nimeni nu-l verificase. Reparația a însemnat patru linii — importă productName dinamic din config și interpolează-l în loc de brandul hardcodat — dar lecția e chiar esența:

White-label-ul nu înseamnă „s-a schimbat logo-ul". Înseamnă „trece fiecare string de identitate prin config". Iar ultimele rămășițe nu locuiesc niciodată în interfață, acolo unde te-ai uita. Locuiesc în User-Agent, în handler-ul de protocol pentru deep-link, în textul prompt-ului de permisiune, în mesajele de eroare — plumbing-ul pe care nimeni nu-l clasează la „brand". Singura apărare de încredere e să tratezi literalul de brand ca pe ceva ce vânezi activ: dă grep prin codebase după numele hardcodat și fă build-ul să eșueze dacă apare oriunde în afara config-ului care ar trebui să-l definească.

Trei lucruri pe care ni le-a predat white-label-ul

  1. Generează, nu ramifica. Generarea de cod la build-time a două module de config minuscule plus copierea selectivă a assets-urilor bate atât fork-ul, cât și un hățiș de verificări de produs la runtime. Aplicația rămâne agnostică față de brand; brandul e un input de build.
  2. Stringurile de identitate se scurg în plumbing. Interfața e cei 90% ușori. Cei 10% grei sunt User-Agent-ul, protocolul URL, stringul de permisiune pentru microfon, textul erorilor. Rutează-le pe toate printr-un singur export productName / applicationId — și dă grep după numele brut al brandului ca poartă de CI, astfel încât un literal rătăcit să nu poată ajunge niciodată în producție.
  3. Un backend, multe branduri, dezambiguizate prin id și header. Un application id de build-time plus un User-Agent Product/version (platform) oferă atribuire per produs și per platformă cu zero infrastructură duplicată — separând curat identitatea imutabilă de build-time de comportamentul de runtime.

Pentru partea de produs și de confidențialitate a rulării unui notetaker white-label sub propriul brand, vezi notetaker-ul AI white-label. Acesta e al șaselea capitol al poveștii v1 care devine GeekBye v2 — pentru capitolul anterior, ce înseamnă cu adevărat o lansare de 127 de commit-uri (v1.7.0); iar pentru întregul arc, anatomia livrării de software până la perfecțiune.

Articole Similare

O singură variabilă CSS, cinci runde de review și un toolchain Swift care a mintit
Steven
Steven7 min citire

O singură variabilă CSS, cinci runde de review și un toolchain Swift care a mintit

GeekBye v2.0.7 a făcut întregul overlay translucid reglabil — ceea ce sună ca o modificare CSS de o linie și absolut nu a fost. Adevărata poveste e ce a prins review-ul: două suprafețe care refuzau să pălească, o bară de înregistrare care arăta greșit la aceeași opacitate și un binar compilat care a oscilat 672 de octeți pentru că propriile noastre documente i-au spus unui reviewer versiunea greșită de Swift.

Inginerie
Design
Build
Am șters 5.000 de linii de cod audio — și transcrierile au început să apară de două ori
Steven
Steven6 min citire

Am șters 5.000 de linii de cod audio — și transcrierile au început să apară de două ori

GeekBye v1.6 a smuls din rădăcini două transcriere Swift care rulau pe dispozitiv și le-a înlocuit cu un singur pipeline unificat — o ștergere netă de peste 5.000 de linii, făcută în timp ce oamenii erau în plină ședință pe aplicație. Apoi transcrierile au început să apară de două ori, pentru că bug-ul s-a mutat pe singurul strat care încă credea că există două motoare.

Inginerie
Transcriere
Arhitectură
Ce înseamnă cu adevărat o lansare de 127 de commit-uri
Steven
Steven6 min citire

Ce înseamnă cu adevărat o lansare de 127 de commit-uri

GeekBye v1.7.0 a însemnat 127 de commit-uri în unsprezece zile. Din afară pare o sută de lucruri mărunte. Din interior au fost două funcționalități mari împletite una în alta — iar una dintre ele a fost construită în locul greșit, apoi smulsă de acolo și reconstruită în plină lansare. Iată anatomia unei lansări mari.

Inginerie
Lansare
Arhitectură