JavaScript, HTML, CSS e... !
0 commenti

Il template di Shivaji Bodkhe

CI/CD per Apps Script con GitHub Actions

Quante volte vi è capitato di aprire uno script in produzione e trovarlo diverso da come lo ricordavate? Qualcuno ci ha messo le mani dall'editor del browser, magari per "una modifica veloce", e adesso tocca capire cosa è cambiato. Lo storico delle versioni di Apps Script aiuta fino a un certo punto. Git è un'altra cosa.

Con gli agenti di coding (Antigravity, Claude Code, Gemini CLI, ormai ognuno ha il suo preferito) la faccenda si complica ancora. Il codice nasce in locale, cambia a blocchi di cento righe alla volta e spesso nessuno lo rilegge tutto. Senza un repository dietro diventa difficile anche solo sapere cosa stiamo mettendo in produzione.

Qualche giorno fa AppsScriptPulse ha segnalato il lavoro di Shivaji Bodkhe, che ha pubblicato su GitHub un template per fare CI/CD su Apps Script con GitHub Actions e clasp. A me però ha colpito soprattutto l'articolo che lo accompagna: Shivaji racconta il percorso passo per passo, inclusi gli errori in cui è inciampato (ci sono anche il classico "permission denied" e Git che non sa chi sei). Di solito nei tutorial questa parte sparisce, ed è un peccato perché è quella che serve di più. Il merito è tutto suo, io qui vi racconto come funziona e ci aggiungo un paio di riflessioni. Per i dettagli, l'articolo originale è linkato in fondo.

Un po' di contesto

A giugno (il 23, per la precisione) Google ha annunciato che Apps Script è diventato un core service di Workspace. Vuol dire che ricade sotto gli stessi termini, le stesse garanzie sui dati e lo stesso supporto di Gmail o Drive, e Google stessa suggerisce agli admin che lo avevano bloccato per motivi di compliance di ripensarci.

Per chi lavora in azienda non è un dettaglio da poco. Se Apps Script diventa una piattaforma ufficiale, prima o poi qualcuno chiederà le cose che si chiedono a qualsiasi piattaforma ufficiale: chi ha approvato questa modifica, come si torna indietro, dove stanno le credenziali. Shivaji lo fa notare nel suo post e sono d'accordo con lui. Fino a ieri una pipeline così poteva sembrare un vezzo da sviluppatori, adesso ha senso.

Com'è fatto il progetto

L'esempio è una dashboard vendite su un Google Sheet. Ci sono le funzioni per creare i fogli (Orders, Daily Summary, Settings), per riempirli di dati finti, per ricalcolare il riepilogo giornaliero e mandarlo via mail, più quella che crea il trigger. Poi c'è un doGet() che risponde con un JSON di health check, comodo quando pubblicate lo script come web app e volete sapere al volo se sta in piedi e a quale foglio è collegato.

Il codice è diviso in tre file (Code.js, Config.js, OrderService.js) oltre al manifest. Attorno ci sono un package.json, uno script di validazione in Node e il workflow di GitHub Actions, mentre il .claspignore si assicura che su Apps Script arrivino solo i sorgenti.

Mi è piaciuta la gestione del foglio di calcolo. Se manca la Script Property SPREADSHEET_ID, lo script ne crea uno nuovo, salva l'ID e vi scrive l'URL nei log. Chi ha passato un pomeriggio a spiegare a un collega "no, prima crei il foglio, poi copi l'ID, poi lo incolli nelle proprietà dello script" sa di cosa parlo.

La pipeline

Il workflow parte sulle pull request, sui push su main e a mano da GitHub. C'è un filtro sui percorsi, così se cambiate solo il README non succede niente, e un blocco concurrency che annulla le esecuzioni vecchie se fate due push uno dietro l'altro.

La scelta che conta davvero è la divisione in due job. Il primo, validate, gira sempre, anche sulle PR. Lancia npm run check, che passa ogni file a node --check e poi esegue uno script che controlla il manifest: runtime V8, eccezioni mandate su Cloud Logging (exceptionLogging impostato a STACKDRIVER), presenza degli scope OAuth e delle funzioni che il progetto si aspetta di trovare.

Il secondo, deploy, parte solo se la validazione è andata bene e solo quando non si tratta di una PR. Ricostruisce le credenziali di clasp, fa clasp push --force e, se gli avete dato un deployment ID, aggiorna il deployment esistente con una nuova versione. La PR viene controllata, il merge su main finisce in produzione. Come in qualsiasi progetto software fatto bene.

Le credenziali

clasp non ha un vero login non interattivo. Quando fate clasp login entrate con il vostro account Google e clasp si salva token e refresh token in ~/.clasprc.json. In CI quel file bisogna portarcelo in qualche modo.

Shivaji mette il contenuto del file in un secret di GitHub (CLASPRC_JSON) e l'ID dello script in un altro (APPS_SCRIPT_SCRIPT_ID). Al momento del deploy il workflow ricrea i due file:

- name: Write clasp credentials
  env:
    CLASPRC_JSON: ${{ secrets.CLASPRC_JSON }}
  run: |
    test -n "$CLASPRC_JSON"
    printf '%s' "$CLASPRC_JSON" > "$HOME/.clasprc.json"

- name: Write clasp project file
  env:
    APPS_SCRIPT_SCRIPT_ID: ${{ secrets.APPS_SCRIPT_SCRIPT_ID }}
  run: |
    test -n "$APPS_SCRIPT_SCRIPT_ID"
    cat > .clasp.json <<EOF
    {
      "scriptId": "${APPS_SCRIPT_SCRIPT_ID}",
      "rootDir": "."
    }
    EOF

Quel test -n all'inizio di ogni step lo trovo furbo: se vi siete dimenticati un secret il job si ferma lì e capite subito perché, invece di inseguire un errore strano di clasp tre step dopo. Ovviamente né .clasp.json né .clasprc.json finiscono nel repository.

Cosa mi ha lasciato qualche dubbio

Premessa doverosa: quello che segue l'ho scritto leggendo il codice, non l'ho testato sul template. Prendetelo come spunto e, se avete dubbi, fate riferimento alla fonte.

La condizione sul deployment ID

L'ultimo step del workflow ha questa condizione:

if: ${{ secrets.APPS_SCRIPT_DEPLOYMENT_ID != '' }}

Mi è venuto un dubbio perché la documentazione di GitHub Actions dice che i secret non si possono usare direttamente in un if. Se è così, quello step potrebbe non comportarsi come ci si aspetta. La strada che indica la documentazione è passare il secret in una variabile d'ambiente del job e controllare quella:

deploy:
  env:
    APPS_SCRIPT_DEPLOYMENT_ID: ${{ secrets.APPS_SCRIPT_DEPLOYMENT_ID }}
  steps:
    # ...
    - name: Create or update deployment
      if: ${{ env.APPS_SCRIPT_DEPLOYMENT_ID != '' }}
      run: |
        npx clasp create-deployment 
          --deploymentId "$APPS_SCRIPT_DEPLOYMENT_ID" 
          --description "GitHub Actions deploy ${GITHUB_SHA}"

Non l'ho provato su un'esecuzione vera, quindi va verificato. Se usate il template guardate nei log se lo step parte (o viene saltato) quando ve lo aspettate, e date un'occhiata al repository originale: magari nel frattempo è stato aggiornato.

La validazione

node --check controlla la sintassi e basta. Che SpreadsheetApp.getActiv() non esista, a lui non interessa. Lo script di validazione cerca i nomi delle funzioni con una regex, che va bene per non cancellare per sbaglio un entry point ma non va molto oltre.

Io qui aggiungerei ESLint, dichiarando le globali di Apps Script, e un controllo dei tipi con @types/google-apps-script. Non serve passare a TypeScript, dovrebbe bastare un jsconfig.json con checkJs attivo e un tsc --noEmit nel job di validazione. Con gli agenti AI che scrivono codice è il controllo che metterei per primo, perché il loro errore tipico è proprio inventarsi un metodo che suona bene ma non esiste.

Di chi è il token

Il refresh token in .clasprc.json è quello dell'account con cui avete fatto login, con tutti gli scope che clasp chiede (e non sono pochi). Se è il vostro account aziendale, la pipeline agisce a nome vostro.

Personalmente userei un account dedicato agli script, tipo il workspace-automations@dominio.com di cui parla AppsScriptPulse. Poi sposterei i secret dal livello del repository a quello dell'environment production, con dei revisori obbligatori, così nessun job tocca le credenziali senza che qualcuno abbia approvato. E metterei in conto che, se il token viene revocato, la pipeline si ferma. Meglio scoprirlo con calma che il giorno del rilascio urgente.

Il push sovrascrive

Questo non è un difetto del template, è clasp. Ogni push rimpiazza i file del progetto con quelli del repository, quindi chi modifica dall'editor web perde la modifica al deploy successivo, senza nessun avviso. Conviene dirlo al team prima che succeda, non dopo.

Staging

Se un push su main va dritto in produzione, per uno script vostro siete a posto. Per un add-on usato da tutto il dominio io non mi fiderei. Lo suggerisce lo stesso Shivaji nel README: due progetti Apps Script separati, uno di staging e uno di produzione, e deploy in produzione solo a partire da un tag.

Chi può fare deploy

C'è un'osservazione di AppsScriptPulse che secondo me vale più di tutto il resto. Con questo schema chi contribuisce, persona o agente AI che sia, non ha bisogno di poter modificare lo script né di conoscere le credenziali dell'account proprietario. Apre una PR, qualcuno la approva, il resto lo fa la pipeline.

Per le automazioni interne cambia parecchio. Non conta più chi ha accesso all'editor, conta chi può approvare una pull request. E ogni modifica che arriva in produzione ha un autore e qualcuno che l'ha rivista, che durante un audit è esattamente quello che vi chiedono.

Se fate ancora deploy copiando e incollando nell'editor, partite da qui. Leggete l'articolo di Shivaji, fate un fork del template e adattatelo al vostro progetto. Prima di usarlo sul serio verificherei la condizione sui secret e darei una sistemata alla validazione, il resto si aggiunge col tempo.

Link

Tags

Michele Pisani

Michele Pisani

Sviluppatore Javascript ed esperto in Digital Analytics

L'esperienza nel settore Digital Analytics unita ad anni di sviluppo in Javascript ha trovato la massima espressione in Google Apps Script che mi ha permesso, con estrema facilità e poche righe di codice, di realizzare potenti applicazioni interattive e processi automatizzati integrati con i prodotti della G Suite.

Come contattarmi
scrivi un commento

0 Commenti

Non ci sono commenti

Nessuno ha ancora commentato questo articolo, fallo tu per primo!

scrivi un commento

Scrivi un commento

Il tuo indirizzo email non sarà pubblicato.I campi contrassegnati da un * sono obbligatori
Puoi utilizzare i seguenti tag nei commenti:
[bold]testo[/bold] se vuoi evidenziare un testo con il grassetto[code]function helloworld() { }[/code] se vuoi pubblicare una porzione di codice[url]https://www.appsscript.it[/url] se devi riferirti ad un indirizzo web