
Una base de codi, dues apps: com fer white-label sense fork
GeekBye i Pavleur són dues apps d'escriptori amb marques diferents construïdes des d'un únic repositori — sense fork, sense base de codi duplicada. Aquesta és la maquinària de build que fa que una sola base de codi compili en dos productes, i l'error d'una sola línia que va fer que la nostra segona app es presentés amb el nom equivocat.
GeekBye és un notetaker de reunions. Pavleur és una app de pràctica d'entrevistes amb IA. Semblen dos productes diferents perquè són dos productes diferents — però estan construïts des d'una sola base de codi, sense fork i sense cap branca que es vagi desviant. Aquest és el costat d'enginyeria del white-labeling: no "per què vendre una app amb la teva marca" (això és una altra entrada), sinó com compiles realment un únic repositori en dues apps — i l'error gens glamurós que s'amaga en tot sistema white-label fins que el vas a buscar.
Genera, no facis branques
La manera equivocada de fer funcionar dos productes des d'una base de codi és escampar if (product === 'pavleur') per tota l'app, o fer un fork i resar perquè les dues còpies no divergeixin mai. Totes dues opcions es podreixen de pressa.
L'enfocament de GeekBye és la generació de codi en temps de build. Cada producte té un fitxer de configuració — configs/geekbye/config.ts, configs/pavleur/config.ts — que en conté la identitat: nom, bundle id, protocol d'URL, el repositori de releases per a les actualitzacions automàtiques, i així successivament. Abans d'una build, s'executa un sol script:
node scripts/build-product.js pavleur
Aquest script llegeix la config del producte triat i fa tres coses concretes:
- Genera dos mòduls de configuració — un per al renderer (
src/config/product.ts), un per al procés principal (electron/config/product.ts) — cadascun estampat amb una capçalera "DO NOT EDIT MANUALLY — changes will be overwritten". La resta de l'app simplement importaproductName,applicationIdiprotocolde la config i no pensa mai en quina marca és. - Copia només els assets d'aquella marca — les icones, el logo petit, la icona de les notificacions toast. Una build de GeekBye conté físicament zero art de Pavleur i viceversa. Res per filtrar.
- Reescriu
package.json(a les builds de release) — l'app id, el nom del producte, el protocol d'URL, el repo de releases de GitHub on publicar i, com a detall agradable, el text de permís de micròfon amb la marca, perquè macOS pregunti "Pavleur voldria accedir al micròfon", no la marca equivocada.
Tota l'app és agnòstica de marca; la marca s'injecta en temps de build. No hi ha cap commutador de producte en temps d'execució que es pugui equivocar, perquè en temps d'execució només hi ha mai un producte incrustat.
Un backend, dues marques
Totes dues apps parlen amb el mateix backend. Llavors, com sap el servidor a quin producte — i a quins preus, prompts i límits — pertany una petició?
Dos senyals, tots dos fixats en temps de build. Primer, un application id (geekbye o pavleur) incrustat a la config i enviat amb les peticions, perquè el backend pugui encaminar cap al comportament del producte correcte. Segon, un User-Agent que el client adjunta a cada crida al backend, amb la forma Product/version (platform) — per exemple Pavleur/1.2.5 (win32) o GeekBye/1.7.3 (darwin). Aquesta única capçalera dona al backend atribució per producte i per sistema operatiu de franc, sense cap camp d'analítica a part. Les actualitzacions automàtiques, mentrestant, es mantenen aïllades: cada producte publica al seu propi repositori de releases, així que una actualització de GeekBye mai no es pot servir a una instal·lació de Pavleur.
L'onboarding també difereix per producte — l'assistent de GeekBye va de transcripció de reunions, el de Pavleur de pràctica d'entrevistes — i el correcte es tria en temps d'execució a partir de l'únic application id incrustat. La mateixa maquinària, dues portes d'entrada.
L'error: quan Pavleur es va presentar com a GeekBye
Aquí ve la batalleta, i és la que tot sistema white-label té en alguna versió.
La generació de config funcionava. Les icones s'intercanviaven. El prompt del micròfon duia la marca. Els protocols i els bundle ids eren correctes. Segons qualsevol mesura visible, una build de Pavleur era Pavleur. I tot i així, durant un temps, cada build de Pavleur trucava a casa al backend presentant-se com a GeekBye.
El culpable era una sola cadena hardcodejada, i s'amagava al lloc menys glamurós imaginable: el constructor del User-Agent, ficat dins d'un helper de reintents de xarxa. Tot el que era de cara a l'usuari s'havia encaminat a través de la config, però aquesta utilitat de fontaneria profunda encara tenia un literal `GeekBye/${version}` a dins. Ningú no pensa en una utilitat de reintents com a "branding", així que ningú no l'havia revisada. La solució eren quatre línies — importar el productName dinàmic de la config i interpolar-lo en lloc de la marca hardcodejada — però la lliçó és tot el sentit d'això:
El white-labeling no és "ha canviat el logo?". És "passa cada cadena d'identitat per la config?" I els últims reductes mai no viuen a la UI, on aniries a mirar. Viuen al User-Agent, al gestor del protocol de deep links, als textos dels prompts de permisos, als missatges d'error — la fontaneria que ningú no arxiva sota "marca". L'única defensa fiable és tractar el literal de la marca com una cosa que caces activament: fes grep del nom hardcodejat a tota la base de codi i fes fallar la build si apareix enlloc fora de la config que se suposa que el defineix.
Tres coses que ens va ensenyar el white-labeling
- Genera, no facis branques. La codegen en temps de build de dos petits mòduls de configuració més la còpia selectiva d'assets guanya tant al fork com a un matoll de comprovacions de producte en temps d'execució. L'app es manté agnòstica de marca; la marca és un input de build.
- Les cadenes d'identitat es filtren cap a la fontaneria. La UI és el 90% fàcil. El 10% difícil és el User-Agent, el protocol d'URL, la cadena del permís de micròfon, els textos d'error. Fes-los passar tots per un únic export
productName/applicationId— i fes grep del nom de marca cru com a porta de CI perquè un literal perdut no pugui arribar mai a producció. - Un backend, moltes marques, desambiguades per id i capçalera. Un application id de temps de build més un User-Agent
Product/version (platform)donen atribució per producte i per plataforma amb zero infraestructura duplicada — separant netament la identitat immutable de temps de build del comportament en temps d'execució.
Per al costat de producte i privadesa de fer funcionar un notetaker white-label sota la teva pròpia marca, vegeu el notetaker d'IA white-label. Aquest és el sisè capítol de la història de la v1 que es converteix en GeekBye v2 — per al capítol anterior, què és realment una release de 127 commits (v1.7.0); i per a tot l'arc, l'anatomia de publicar programari fins a la perfecció.