Steven
Steven6 λεπτά ανάγνωσης

Ένα Codebase, Δύο Εφαρμογές: Πώς να Κάνεις White-Label Χωρίς Fork

Το GeekBye και το Pavleur είναι δύο desktop εφαρμογές με διαφορετικό branding, χτισμένες από ένα και μόνο repository — χωρίς fork, χωρίς διπλασιασμένο codebase. Ιδού ο μηχανισμός χρόνου build που κάνει ένα codebase να μεταγλωττίζεται σε δύο προϊόντα, και το σφάλμα μίας γραμμής που έκανε τη δεύτερη εφαρμογή μας να συστήνεται με λάθος όνομα.

Μηχανική
Αρχιτεκτονική
Build
Εκδόσεις GeekBye
Ένα Codebase, Δύο Εφαρμογές: Πώς να Κάνεις White-Label Χωρίς Fork

Το GeekBye είναι μια εφαρμογή σημειώσεων συναντήσεων. Το Pavleur είναι μια εφαρμογή εξάσκησης συνεντεύξεων με AI. Μοιάζουν με δύο διαφορετικά προϊόντα επειδή είναι δύο διαφορετικά προϊόντα — αλλά χτίζονται από ένα codebase, χωρίς fork και χωρίς branch που ξεστρατίζει. Αυτή είναι η μηχανική πλευρά του white-labeling: όχι το «γιατί να πουλήσεις μια branded εφαρμογή» (αυτό είναι μια άλλη ανάρτηση), αλλά το πώς πραγματικά μεταγλωττίζεις ένα repository σε δύο εφαρμογές — και το άχαρο σφάλμα που κρύβεται σε κάθε white-label σύστημα μέχρι να πας να το ψάξεις.

Παράγε, μην κάνεις branch

Ο λάθος τρόπος να τρέχεις δύο προϊόντα από ένα codebase είναι να πασπαλίσεις if (product === 'pavleur') σε όλη την εφαρμογή, ή να κάνεις fork και να προσεύχεσαι τα δύο αντίγραφα να μην αποκλίνουν ποτέ. Και τα δύο σαπίζουν γρήγορα.

Η προσέγγιση του GeekBye είναι η παραγωγή κώδικα σε χρόνο build. Κάθε προϊόν έχει ένα αρχείο ρυθμίσεων — configs/geekbye/config.ts, configs/pavleur/config.ts — που κρατά την ταυτότητά του: όνομα, bundle id, URL protocol, το repository εκδόσεων για τις αυτόματες ενημερώσεις, και ούτω καθεξής. Πριν από ένα build, τρέχει ένα script:

node scripts/build-product.js pavleur

Αυτό το script διαβάζει τη ρύθμιση του επιλεγμένου προϊόντος και κάνει τρία συγκεκριμένα πράγματα:

  1. Παράγει δύο config modules — ένα για τον renderer (src/config/product.ts), ένα για το main process (electron/config/product.ts) — το καθένα σφραγισμένο με μια κεφαλίδα «ΜΗΝ ΕΠΕΞΕΡΓΑΖΕΣΤΕ ΧΕΙΡΟΚΙΝΗΤΑ — οι αλλαγές θα αντικατασταθούν». Η υπόλοιπη εφαρμογή απλώς κάνει import τα productName, applicationId και protocol από τη ρύθμιση και δεν σκέφτεται ποτέ ποιο brand είναι.
  2. Αντιγράφει μόνο τα assets του συγκεκριμένου brand — τα εικονίδια, το μικρό λογότυπο, το εικονίδιο των toast ειδοποιήσεων. Ένα build του GeekBye δεν περιέχει φυσικά ούτε ένα εικαστικό του Pavleur, και το αντίστροφο. Δεν υπάρχει τίποτα να διαρρεύσει.
  3. Ξαναγράφει το package.json (στα release builds) — το app id, το όνομα προϊόντος, το URL protocol, το GitHub repository εκδόσεων στο οποίο δημοσιεύει, και, το ωραίο, το branded κείμενο άδειας μικροφώνου, ώστε το macOS να ρωτά «Το Pavleur θα ήθελε πρόσβαση στο μικρόφωνο», όχι το λάθος brand.

Ολόκληρη η εφαρμογή είναι αδιάφορη ως προς το brand· το brand εγχέεται σε χρόνο build. Δεν υπάρχει runtime διακόπτης προϊόντος για να πάει στραβά, γιατί σε χρόνο εκτέλεσης υπάρχει πάντα μόνο ένα προϊόν ενσωματωμένο.

Ένα backend, δύο brands

Και οι δύο εφαρμογές μιλούν στο ίδιο backend. Πώς ξέρει λοιπόν ο server σε ποιο προϊόν — και σε ποια τιμολόγηση, prompts και όρια — ανήκει ένα αίτημα;

Δύο σήματα, και τα δύο ορισμένα σε χρόνο build. Πρώτον, ένα application id (geekbye ή pavleur) ενσωματωμένο στη ρύθμιση και σταλμένο με τα αιτήματα, ώστε το backend να δρομολογεί στη συμπεριφορά του σωστού προϊόντος. Δεύτερον, ένας User-Agent που ο client επισυνάπτει σε κάθε κλήση προς το backend, της μορφής Product/version (platform) — π.χ. Pavleur/1.2.5 (win32) ή GeekBye/1.7.3 (darwin). Αυτή η μία κεφαλίδα δίνει στο backend απόδοση ανά προϊόν και ανά λειτουργικό σύστημα δωρεάν, χωρίς ξεχωριστό πεδίο analytics. Οι αυτόματες ενημερώσεις, στο μεταξύ, μένουν απομονωμένες: κάθε προϊόν δημοσιεύει στο δικό του repository εκδόσεων, οπότε μια ενημέρωση του GeekBye δεν μπορεί ποτέ να σερβιριστεί σε μια εγκατάσταση του Pavleur.

Το onboarding διαφέρει επίσης ανά προϊόν — ο οδηγός του GeekBye αφορά την απομαγνητοφώνηση συναντήσεων, του Pavleur την εξάσκηση συνεντεύξεων — και ο σωστός επιλέγεται σε χρόνο εκτέλεσης από το ένα και μοναδικό ενσωματωμένο application id. Ίδιος μηχανισμός, δύο εξώπορτες.

Το σφάλμα: όταν το Pavleur αυτοαποκαλέστηκε GeekBye

Ιδού η ιστορία από το πεδίο, και είναι αυτή που κάθε white-label σύστημα έχει σε κάποια εκδοχή.

Η παραγωγή ρυθμίσεων δούλευε. Τα εικονίδια εναλλάσσονταν. Το μήνυμα για το μικρόφωνο ήταν branded. Τα protocols και τα bundle ids ήταν σωστά. Με κάθε ορατό μέτρο, ένα build του Pavleur ήταν Pavleur. Κι όμως, για ένα διάστημα, κάθε build του Pavleur τηλεφωνούσε στο backend αυτοαποκαλούμενο GeekBye.

Ο ένοχος ήταν ένα και μόνο hardcoded string, και κρυβόταν στο λιγότερο λαμπερό μέρος που μπορείς να φανταστείς: στον κατασκευαστή του User-Agent, χωμένο μέσα σε έναν retry βοηθό δικτύου. Οτιδήποτε έβλεπε ο χρήστης είχε περάσει από τη ρύθμιση, αλλά αυτό το ένα βαθιά θαμμένο βοηθητικό εργαλείο είχε ακόμη ένα literal `GeekBye/${version}` μέσα του. Κανείς δεν σκέφτεται ένα retry utility ως «branding», οπότε κανείς δεν το είχε ελέγξει. Η διόρθωση ήταν τέσσερις γραμμές — import το δυναμικό productName από τη ρύθμιση και παρεμβολή του αντί για το hardcoded brand — αλλά το μάθημα είναι όλη η ουσία:

Το white-labeling δεν είναι «άλλαξε το λογότυπο;». Είναι «περνά κάθε string ταυτότητας μέσα από τη ρύθμιση;» Και τα τελευταία υπολείμματα δεν ζουν ποτέ στο UI, εκεί που θα κοίταζες. Ζουν στον User-Agent, στον χειριστή του deep-link protocol, στο κείμενο των αιτημάτων άδειας, στα μηνύματα λάθους — στα υδραυλικά που κανείς δεν αρχειοθετεί ως «brand». Η μόνη αξιόπιστη άμυνα είναι να αντιμετωπίζεις το brand literal ως κάτι που κυνηγάς ενεργά: κάνε grep το codebase για το hardcoded όνομα και ρίξε το build αν εμφανιστεί οπουδήποτε εκτός της ρύθμισης που υποτίθεται ότι το ορίζει.

Τρία πράγματα που μας δίδαξε το white-labeling

  1. Παράγε, μην κάνεις branch. Το codegen χρόνου build δύο μικροσκοπικών config modules συν η επιλεκτική αντιγραφή assets κερδίζει και το fork και το runtime δάσος από ελέγχους προϊόντος. Η εφαρμογή μένει αδιάφορη ως προς το brand· το brand είναι μια είσοδος του build.
  2. Τα strings ταυτότητας διαρρέουν στα υδραυλικά. Το UI είναι το εύκολο 90%. Το δύσκολο 10% είναι ο User-Agent, το URL protocol, το string της άδειας μικροφώνου, τα κείμενα λαθών. Πέρασέ τα όλα από ένα export productName / applicationId — και κάνε grep για το γυμνό όνομα του brand ως πύλη CI, ώστε ένα αδέσποτο literal να μην μπορεί ποτέ να φτάσει σε παραγωγή.
  3. Ένα backend, πολλά brands, διάκριση με id και κεφαλίδα. Ένα application id χρόνου build συν ένας User-Agent της μορφής Product/version (platform) δίνει απόδοση ανά προϊόν και ανά πλατφόρμα με μηδέν διπλασιασμένη υποδομή — διαχωρίζοντας καθαρά την αμετάβλητη ταυτότητα χρόνου build από τη συμπεριφορά χρόνου εκτέλεσης.

Για την πλευρά προϊόντος-και-απορρήτου του να τρέχεις ένα white-label εργαλείο σημειώσεων κάτω από το δικό σου brand, δες το white-label AI εργαλείο σημειώσεων. Αυτό είναι το έκτο κεφάλαιο της ιστορίας του v1 που γίνεται GeekBye v2 — για το προηγούμενο κεφάλαιο, τι είναι πραγματικά μια έκδοση 127 commit (v1.7.0)· και για ολόκληρη την πορεία, η ανατομία της παράδοσης λογισμικού στην τελειότητα.

Σχετικά Άρθρα

Βγάζοντας το backend από τη διαδρομή του upload
Steven
Steven9 λεπτά ανάγνωσης

Βγάζοντας το backend από τη διαδρομή του upload

Το GeekBye καταγράφει την οθόνη σου και αποθηκεύει το βίντεο στο Google Drive σου. Η πρώτη έκδοση έστελνε κάθε καταγραφή μέσα από τους ίδιους τους διακομιστές του GeekBye στη διαδρομή προς τα εκεί· μια έκδοση αργότερα, το αρχείο πήγαινε κατευθείαν από το μηχάνημά σου στο Drive, και το backend υποβιβάστηκε στο να κρατά έναν μοναδικό δείκτη. Το ενδιαφέρον κομμάτι είναι πόσο λίγο κώδικα περιέχει στην πραγματικότητα η «άμεση, resumable» έκδοση — επειδή η resumability προήλθε από τη διαγραφή ενός proxy, όχι από τη συγγραφή ενός.

Μηχανική
Αρχιτεκτονική
Desktop
Τι Είναι Πραγματικά μια Έκδοση 127 Commit
Steven
Steven6 λεπτά ανάγνωσης

Τι Είναι Πραγματικά μια Έκδοση 127 Commit

Το GeekBye v1.7.0 ήταν 127 commit σε έντεκα μέρες. Απ' έξω μοιάζει με εκατό μικρά πράγματα. Από μέσα ήταν δύο μεγάλες λειτουργίες πλεγμένες μαζί — και η μία χτίστηκε σε λάθος σημείο, μετά ξηλώθηκε και ξαναχτίστηκε στη μέση της έκδοσης. Ιδού η ανατομία μιας μεγάλης έκδοσης.

Μηχανική
Έκδοση
Αρχιτεκτονική
Η σιωπή ήταν φέρουσα
Steven
Steven8 λεπτά ανάγνωσης

Η σιωπή ήταν φέρουσα

Οι δύο τελευταίες εκδόσεις του GeekBye v1 αφορούν την ίδια άβολη αλήθεια: η μεταγραφή σε πραγματικό χρόνο πάνω από ένα πραγματικό δίκτυο δεν είναι χωρίς απώλειες, και η έντιμη κίνηση είναι να σταματήσεις να προσποιείσαι ότι είναι. Η v1.8.20 κρατούσε ένα αντίγραφο κάθε κομματιού ήχου στον δίσκο πριν το πετάξει κατά τη διάρκεια μιας επανασύνδεσης, και άρχισε να σημειώνει φωναχτά τα κενά στη μεταγραφή. Η v1.9.0 σταμάτησε να στέλνει σιωπή για να εξοικονομήσει εύρος ζώνης — και ανακάλυψε ότι η σιωπή ήταν ακριβώς το σήμα που χρησιμοποιούσε το σύστημα μεταγραφής για να ξέρει ότι μια πρόταση είχε τελειώσει. Δύο εκδόσεις για το κόστος του να πετάς πράγματα.

Μηχανική
Audio
Αξιοπιστία