Aramayı 271 markaya güvenle kuran komut satırı aracı

Önce değişikliği gösteren, sonra yedekleyip uygulayan kurulum aracı: 271 markada arama yayını, bağımsız doğrulama ve geri alma.

Hazır, kapalı veya boş arama olarak işaretlenmiş markaların durum panosunun yanında discover, plan, apply ve verify çalıştıran bir terminal

Search V2 hazırdı. Elle denediğim markalarda çalışıyordu. Sıradaki soru şuydu: bunu uygun olan her markaya, kimsenin canlı mağazasını bozmadan nasıl kuracağız?

Elle yapmak, her marka için paneli açmak, CSS (sayfanın renklerini, boyutlarını ve yerleşimini belirleyen stil kuralları) yapıştırmak, widget (sayfaya eklenen küçük işlevsel bileşen) oluşturmak, kaydetmek ve siteyi kontrol etmek demekti. Birkaç yüz kez. Her biri, bir ekip arkadaşının o marka için kurduğu bir şeyi ezme ihtimali.

Ben de bir CLI (komut yazarak kullanılan arayüz) yazdım. Sonunda aramayı Revotas'ta 272 markanın 271'ine kurdu ve hiçbir markanın kendi sitesine dokunmadı.

CLI'ın gördüğü haliyle yayılım: markalar gruplar halinde canlıya geçiyor, 272 markanın 271'i kuruluyor ve hiçbir markanın kendi sitesi düzenlenmiyor.

İşi zorlaştıran ne

Kurulum yolu zaten vardı. Her mağaza, widget listesini panelden çeken bir widget loader çalıştırıyor. Bir widget kaydedip arama config'ini (uygulamanın davranışını belirleyen ayarlar) yazdığınızda loader onu bir sonraki sayfa görüntülemesinde alıyor.

Sorun bu yolun etrafındaki her şeydi:

  • Her marka farklıydı. Bazı config'ler kapalıydı. Bazı arama indeksleri sıfır ürün döndürüyordu. Bazı input (girdi) seçicileri canlı sitede artık hiçbir şeyle eşleşmiyordu.
  • Eski arama ile yenisi aynı status bayrağını okuyor.
  • Backend (uygulamanın sunucuda çalışan bölümü) benim değildi. Araç yalnızca panelde zaten var olan endpoint'leri (bir serviste istek gönderilen belirli adres) kullanabiliyordu.

Kod düz TypeScript; .ts dosyalarını doğrudan çalıştıran Node 22.6+ üzerinde koşuyor. Build (kaynak kodu çalıştırılabilir veya yayımlanabilir çıktıya dönüştürme) adımı yok, runtime (kodun çalıştığı ortam) bağımlılığı yok.

Yedi komut, varsayılan davranış "hiçbir şey yapma"

Akış discover, plan, apply, verify. Kenar durumlar için deconflict, report ve rollback.

discover her müşteri id'sini geziyor, markanın arama config'ini okuyor, bir test sorgusu atıyor ve kurulu widget'ları listeliyor. plan bir diff'i (iki sürüm arasındaki değişiklikleri gösteren karşılaştırma) JSON (veriyi alan ve değer çiftleriyle taşıyan metin biçimi) dosyasına yazıyor ve prod'a (gerçek kullanıcıların kullandığı canlı ortam) tek istek göndermiyor.

apply bayraksız çalıştırılınca da dry-run (değişikliği uygulamadan ne yapılacağını gösteren deneme). Ne yapacağını yazdırıp duruyor. Gerçek yazma --apply istiyor ve --limit=5 ya da --ids=... ile daraltılabiliyor. Pilot da böyle koştu: önce bir avuç marka.

Güvenli yolu bilerek tembel yol yaptım. Yorgunken iki kez yukarı ok tuşuna basarsam en kötü sonuç bir çıktı olmalı.

Evet/hayır yerine durum sınıfları

Kurulamayan bir markanın yanında bir sebep olmalı. "Hazır değil" kimseye bir sonraki adımı söylemiyor. Bunu ben mi düzeltirim, yoksa müşterinin sitesinde mi bir şey değişmeli?

Bu yüzden discover her markayı bir sınıfa koyuyor. Başlıcaları:

Durum Anlamı
ready Kuruluma hazır
stale-selector Arama input seçicisi canlı sitede eşleşmiyor
loader-unconfirmed Kayıtlı widget var ama loader sunucu HTML'inde (web sayfasının yapısını tanımlayan işaretleme dili) yok (tag manager olabilir)
search-empty Arama endpoint'i ürün döndürmüyor
disabled Config kapalı

Varsayılan olarak plana yalnızca ready giriyor. Diğerleri için --include= ya da açık id listesi gerekiyor.

İki detayı oturtmak zaman aldı. Boş bir widget listesi genelde sadece henüz kimsenin widget kurmadığını gösteriyor, loader hakkında pek bir şey söylemiyor. Bu yüzden --probe-sites seçeneği markanın ana sayfasını çekip loader'ı arıyor. Yalnızca sunucudan gelen HTML'i görebiliyor, yani loader'ı tag manager ile yükleyen marka loader-unconfirmed kalıyor. Tahmin etmektense bunu tercih ederim.

İkincisi: sınıflandırma, bu aracın kendi kurduğu widget'ları saymıyor. O kural olmasa rollout'tan (yeni sürümü kullanıcılara aşamalı olarak açma) sonra her marka erişilebilir görünürdü, çünkü aracın kendi widget'ı kanıt sayılırdı.

Markanın kendi emeğini asla ezme

Kaydetme endpoint'leri bütün objeyi değiştiriyor. Bu yüzden her yazma önce okuma, sonra birleştirme, sonra tam objeyi geri gönderme. Sonra, inisiyatife bırakmak istemediğim birkaç kural var:

  • Yeni aramanın CSS alanına varsayılan preset (önceden hazırlanmış ayar veya görünüm şablonu) yalnızca alan boşsa yazılıyor. Markanın mevcut CSS'i asla ezilmiyor.
  • Eski aramanın CSS'ine hiç dokunulmuyor. Eskisi gibi çalışmaya devam ediyor.
  • "Bu elementleri kaldır" listesindeki her kayıt korunuyor; araç eski aramanın kapsayıcısını listenin sonuna ekliyor.
  • Aracın widget'ı isimle upsert ediliyor. apply iki kez çalışırsa aynı widget güncelleniyor.

Her yazmadan önce apply, markanın ham config'ini ve widget listesini zaman damgalı bir yedek klasörüne kaydediyor. rollback --from=<zaman> --confirm config'i oradan birebir geri yüklüyor. Dürüst bir sınır var: aracın kullandığı API (yazılımların birbirine istek göndermesini sağlayan arayüz) üzerinden widget silinemiyor. Widget çalıştırmadan önce yoksa rollback onu kapatıyor. Listede kalıyor, ama kapalı.

Tek bayrak, iki ürün

Yeni arama ancak markanın config status'ü açıksa mount oluyor. Kapalı bir marka için akla gelen ilk çözüm o status'ü açmak. Ama eski arama da aynı bayrağa bakıyor. Eski loader'ın her sayfada koşulsuz çalıştığı markalarda, bayrağı yeni arama için açmak eski aramayı da bütün ziyaretçiler için geri açardı.

Bu yüzden apply bu markaları varsayılan olarak atlıyor ve id'lerini yazdırıyor. Bunu iki ayrı bayrak değiştiriyor: config'i açmak için --enable-disabled, eski aramayı alışveriş yapanlara gösterecek markaları dahil etmek için --allow-v1-exposure. Eski loader'ı yalnızca bir URL parametresiyle tetiklenen markada risk yok, o marka geçiyor.

Kapalı bir marka hiçbir zaman sessizce açılmıyor. Biri onu bir sebeple kapattı ve çoğu zaman o sebebi bilmiyordum.

Read-back yazmayı kanıtlar, verify siteyi

Her apply config'i ve widget'ı üç hedefe kaydediyor, bekliyor, sonra ikisini de geri okuyor: CSS var mı, widget doğru URL parametresiyle açık mı, config status'ü değişmedi mi. Gerçeğin kaynağı bu read-back. Bazı hedefler diğerlerinin kabul ettiği bir yazmayı reddedebiliyor ve bunu HTTP (tarayıcı ile sunucu arasındaki istek ve yanıt protokolü) status'ünden anlayamıyorsunuz.

Ama read-back sadece verinin yerine ulaştığını kanıtlar. Ziyaretçinin aramayı görüp görmediği hakkında bir şey söylemez. Bu yüzden verify istediğiniz zaman tekrar çalıştırabileceğiniz ayrı bir komut. Config'i ve widget'ı yeniden kontrol ediyor, arama script'ini (belirli bir işi yapan kod dosyası) servis eden host'a HEAD isteği atıyor ve ürünlerin hâlâ geldiğini görmek için gerçek bir sorgu çalıştırıyor. Host kontrolü erkenden işe yaradı. Araç her markanın script subdomain'ini mevcut widget'larından çıkarıyor ve eski bir widget'tan kopyalanan bir subdomain DNS'te (alan adını sunucu adresine çeviren sistem) hiç yoktu. Plan o marka için zaten ortak host'a düşmüştü, ama bunu verify sayesinde biliyordum.

report hepsini filtreli, CSV export'lu ve her marka için test linki olan tek bir HTML dosyasına çeviriyor.

Pilottaki tuzak

Bir pilot marka ilk denemede boş döndü. Config doğru, widget açık, script servis ediliyor. Sayfada hiçbir şey yok.

Loader widget listesini oturum boyunca sessionStorage'da tutuyor. Test linki o sitede zaten oturumu olan bir sekmede açılmıştı, yani loader kurulumdan önceki listeyi kullandı. Storage temizlendi, sayfa yenilendi, arama geldi.

Rollout'ta sorun yoktu. Ama test linki gönderdiğim herkes aynı şeye takılacaktı. README artık test linklerini yeni sekmede açmayı söylüyor, sebebiyle birlikte.

Yenisiyle kavga eden eski widget'lar

Bazı markalar eski arama widget'ının kendi kopyasını her sayfada çalıştırıyordu. Demo linkinde o widget eski paneli yüklüyor, yeni arama da yükleniyor ve ikisi aynı input'a bağlanıyordu. Ziyaretçi eski paneli görüyordu.

deconflict bu widget'lara tek bir koşul ekliyor: sayfa URL'i demo parametresini içermiyorsa çalış. Normal ziyaretçinin URL'inde o parametre (modelin eğitimde öğrendiği sayısal değer) hiç olmuyor, yani onlar için hiçbir şey değişmiyor. 17 markada çalıştı.

Bu kural düz bir substring karşılaştırması. O yüzden hiçbir varyant parametresi diğerinin substring'i olamaz, yoksa bir varyantın linki diğerini tetikler. Yeni bir varyant, bir JSON dosyasında bir satır ve bir CSS preset'i demek.

Farklı ne yapardım

Oturum cache (yeniden kullanmak için saklanan geçici veri, yani önbellek) uyarısını rapordaki her test linkinin yanına koyardım, insanların gerçekten baktığı yere. README kimsenin en son okuduğu yer.

Widget silme endpoint'ini de en başta isterdim. Rollback'te kapatmak işe yarıyor, ama markanın widget listesinde birinin sonradan açıklaması gereken kalabalık bırakıyor.

271 marka canlıda, biri bilerek dışarıda, hiçbir marka sitesi elle düzenlenmedi. İşin çoğu, aracın neyi yapmayı reddetmesi gerektiğine karar vermekti.

Etkileşimli demoyu deneyin. Aynı komutları uydurma bir marka filosu üzerinde çalıştırıyor. Diğer işlerim Revotas sayfasında.

Bu yazı İngilizce aslından Türkçeye çevrilmiştir.

Çözmeye değer bir problemin mi var?Birlikte çözelim

Konuşmaya başla

Full-stack ve uygulamalı AI rollerine açığım, uzaktan veya hibrit.

Rolü, ekibi ve problemi anlat.