// caso-studio
wp-bootstrap
CLI in Bash che mette in locale un sito WordPress, nuovo, già su git o solo online, con un solo comando e nessuna domanda. Usa un vhost Apache wildcard, sincronizza il database via SSH con backup e controlli, e ha test in puro Bash.
Cos'è
Un progetto WordPress in agenzia va messo sulla mia macchina in tre casi: quando nasce da zero, quando esiste già online e ci devo lavorare, e quando è online ma non ha mai avuto un repo. I passaggi sono sempre gli stessi: repo, core, database, wp-config.php, tema, plugin, virtual host, primo pull del database. Ogni volta uguali, e ogni volta un'occasione per sbagliare qualcosa.
wpb riduce tutto a un comando:
wpb new git@…/nuovo-sito.git # progetto nuovo: dalla starter al primo push
wpb get git@…/sito-online.git # progetto esistente: clone, DB e plugin dallo staging
wpb adopt git@…/repo-vuoto.git # sito online senza git: codice e DB dal server, primo push
Dopo circa un minuto il sito risponde su http://<nome>.stage. Nel caso comune wpb non fa nessuna domanda.
Da script a strumento
La prima versione era uno script da 16 step numerati. Funzionava, ma in pratica restava lento. Chiedeva una decina di input, creava un virtual host per ogni sito con sudo e riavvio di Apache, installava i plugin gratuiti uno per uno mentre i progetti veri usano versioni premium versionate nel repo, e copriva solo i progetti nuovi.
Il problema non era il terminale, ma quante cose chiedeva. Riprogettandolo sono partito da una domanda: quanti di quei dati si possono ricavare dagli altri? Quasi tutti. Dall'URL del repo si ricavano il nome della cartella, il dominio locale, il database e il suo utente. I pochi dati che restano sono quelli del server remoto: vengono chiesti una volta sola e salvati in un .env del progetto, mai versionato.
Da questa scelta seguono le altre:
- Un solo virtual host wildcard.
wpb setuplo aggiunge una volta al file vhost di Apache, dopo un backup e il controllo conhttpd -t, e ripristina il file se il controllo fallisce. Da quel momento ogni cartella in~/Sitesè raggiungibile come<cartella>.stage, senzasudoe senza riavvii. I blocchi scritti a mano restano validi e hanno la precedenza.wpbrifiuta di creare un progetto il cui dominio sia già occupato da uno di quei blocchi. - Motore pubblico, dati privati. Questo repo contiene solo la logica. La starter con il tema child, i plugin premium e i tool frontend vive in un repo privato separato, che il motore legge ma da cui non dipende. Nessun dato di clienti, credenziale o file sotto licenza finisce qui.
- Una starter che si aggiorna da sola.
wpb starter:refreshconfronta la versione di ogni plugin della starter con quelle dei progetti già presenti sulla macchina e copia la più recente. Non servono API né chiavi di licenza. - Sottocomandi al posto degli script generati. Prima ogni progetto riceveva un
sync-operation.shcon i valori scritti dentro. Orawpb db:pull,db:push,plugins:pull,assets:*esshleggono il.envdel progetto in cui ti trovi. I progetti creati col vecchio flusso vengono riconosciuti e i loro valori importati automaticamente.
Ogni step registra il proprio completamento su file. Se qualcosa fallisce basta rilanciare lo stesso comando: wpb salta gli step già fatti e, in caso di errore, stampa il comando esatto da rilanciare. --dry-run mostra cosa succederebbe senza toccare nulla.
Un sito che non ha un repo
Capita di dover testare o prendere in carico un sito che gira sul server ma non è mai stato versionato. new non va bene, perché parte dalla starter. Nemmeno get, perché prende il tema dal repo. Per questo caso c'è wpb adopt.
Riusa quasi tutti gli step che esistevano già. Ne aggiunge uno solo, code_pull, che copia via rsync temi, plugin e mu-plugin dal server, senza gli upload. Poi seguono core, database, wp-config.php e il primo db:pull. Con l'URL di un repo vuoto fa anche il primo commit e il push, con lo stesso controllo di wpb new. Senza URL (--name) resta tutto in locale e niente esce dalla macchina.
I siti presi così hanno fatto emergere un limite di db:pull. Il search-replace cercava solo l'URL salvato nel .env, ma i database reali ne contengono di più: http e https mescolati, e URL in formato JSON (https:\/\/…) dove li salvano Elementor e i blocchi. Ora db:pull sostituisce l'URL remoto e i home/siteurl letti dal dump, in entrambi gli schemi e in entrambi i formati. Se il .env ha lo schema sbagliato lo corregge e lo segnala, e alla fine rigenera il CSS di Elementor.
Quando i dati sono veri
Un tool che sposta database tra la mia macchina e lo staging deve partire dall'idea che prima o poi qualcosa andrà storto. La revisione del codice ha fatto emergere problemi reali, anche nel flusso che usavo da anni.
- Il vecchio script scriveva il dump SQL nella cartella pubblica del server e, se un passaggio falliva, ce lo lasciava. Ora il dump passa direttamente attraverso la connessione SSH (
wp db export -ewp db import -) e non diventa mai un file sul server. db:pullsalva prima una copia del database locale.db:pushcontrolla che i prefissi delle tabelle coincidano: senza questo controllo avrebbe dichiarato "fatto" senza aggiornare niente. Poi fa un backup del database remoto fuori dalla cartella pubblica e chiede conferma mostrando esattamente cosa sta per sovrascrivere.- Apache ascolta su tutte le interfacce, quindi dalla rete locale chiunque poteva scaricare la cartella
.gitdi ogni progetto. Il blocco wildcard ora nega.git/, i file.env*e la cartella dei log per tutti i siti, compresi quelli configurati prima. - Nessun database viene mai eliminato.
wpb newewpb adoptsi rifiutano di pusharewp-config.phpo.env, e pushano solo in un repo vuoto.
I test (sì, ancora in Bash)
Come nella prima versione, la suite è in puro Bash, senza framework: 16 file e quasi 400 asserzioni. Non richiedono rete, un database vero, Apache o un server. All'inizio di ogni file i comandi pericolosi vengono sostituiti da versioni finte che interrompono il test. Così nessun errore nei test può raggiungere un server reale.
# fail-fast stubs: accidental real calls are impossible unless a test redefines them
for _c in ssh scp rsync wp mysql; do
eval "$_c() { echo \"unexpected \$FUNCNAME \$*\" >&2; exit 99; }"
done
Sopra questa base, ogni test ridefinisce solo quello che gli serve. Uno simula un server remoto che stampa un avviso PHP prima del prefisso delle tabelle, e verifica che nel wp-config.php finisca il prefisso giusto e non l'avviso. Un altro simula un errore a metà della creazione del database e verifica che il comando successivo riparta invece di bloccarsi. La configurazione del vhost wildcard l'ho verificata anche su un'istanza di Apache separata, lanciata apposta.
Limiti consapevoli
È nato per uno stack preciso: macOS, Homebrew, Apache, WP-CLI e uno staging organizzato come RunCloud (~/webapps/<app>). Il percorso remoto è definito in un solo punto, quindi adattarlo a un altro hosting è una modifica di una riga, ma non pretende di essere universale. È il suo pregio: fa una cosa sola, quella che serviva, e la fa sempre allo stesso modo.