Migrare le estensioni Firebase a Cloud Functions

Questa guida mostra come eseguire la migrazione delle estensioni dall'ambiente Firebase Extensions deprecato a una funzione che gli utenti installano ed eseguono il deployment nel proprio codebase Cloud Functions per Firebase (2ª gen.).

Questo è il percorso di migrazione consigliato. Firebase manterrà un elenco di estensioni con equivalenti npm ufficiali; questa guida ti illustra come creare la tua.

In questa guida, l'estensione Stream Cloud Firestore to BigQuery (firestore-bigquery-export) viene utilizzata come esempio. Ogni sezione termina con un Esempio pratico che mostra l'aspetto dell'estensione prima della migrazione e l'aspetto dopo, come pacchetto @firebase-function-kits/firestore-bigquery-export.

Registrati per ricevere ulteriori informazioni e assistenza sulla migrazione delle estensioni

Se hai domande su come eseguire la migrazione da Firebase Extensions, puoi contattarci all'indirizzo firebase-extensions-migrator-support-external@google.com. Invieremo un'email anche a questo gruppo quando aggiorneremo la guida con ulteriori informazioni su pacchettizzazione, test e distribuzione delle funzioni di 2ª generazione.

Per unirti a questo gruppo, invia un messaggio all'indirizzo firebase-extensions-migrator-support-external+subscribe@google.com, che risponderà con un'email di richiesta di iscrizione. Devi rispondere a questa email, non fare clic sul pulsante "Iscriviti a questo gruppo".

Prima di iniziare

Per completare questa migrazione secondo le istruzioni, utilizzerai le seguenti funzionalità di Cloud Functions:

  • Configurazione parametrizzata. Ogni parametro dichiarato in extension.yaml diventa un parametro definito nel codice del pacchetto.

  • Ruoli IAM dichiarativi e API richieste. Ogni ruolo dichiarato in extension.yaml diventa una chiamata requiresRole(...) e ogni API diventa una chiamata requiresAPI(...) nel codice del pacchetto. Al momento del deployment, l'interfaccia a riga di comando Firebase concede i ruoli dichiarati a un service account di runtime gestito e attiva le API dichiarate per tuo conto.

  • Eventi del ciclo di vita per codebase Cloud Functions. Cloud Functions codebase ora supportano gli eventi del ciclo di vita analoghi a Firebase Extensions. Dichiara la configurazione al momento dell'installazione e dell'aggiornamento con gli hook del ciclo di vita afterFirstDeploy(...) e afterRedeploy(...). Questi sostituiscono i lifecycleEvents che dichiari in extension.yaml.

Esegui la migrazione del codice sorgente Firebase Extensions a una funzione di 2ª gen.

(Facoltativo) Migrazione automatica con la skill dell'agente Firebase

Puoi automatizzare i passaggi da 1 a 8 (inventario delle risorse, attivazione degli upgrade, conversioni di parametri e secret, IAM dichiarativo, hook del ciclo di vita e generazione del README del pacchetto) utilizzando la skill ufficiale dell'agente AI extension-to-functions-codebase.

Installa la skill

Se tu o il tuo assistente di coding AI (Gemini in Firebase, Cursor, Claude Code, GitHub Copilot) non avete ancora installato la skill, eseguite il seguente comando utilizzando la CLI delle skill:

npx skills add firebase/agent-skills --skill extension-to-functions-codebase

Una volta installata la skill nel progetto, l'assistente di coding AI segue automaticamente le regole di migrazione e i passaggi di trasformazione. Puoi utilizzare il seguente prompt:

"Esegui la migrazione di questa Firebase Extension in un pacchetto di kit di funzioni di 2ª generazione pubblicabile seguendo le istruzioni nella competenza extension-to-functions-codebase."

1. Inventaria l'estensione

Inizia facendo un inventario della tua estensione: un elenco completo di tutto ciò che l'estensione dichiara, invia e documenta, in modo che ogni comportamento abbia una destinazione definita nella funzione di 2ª gen. e nulla vada perso nella migrazione.

Esamina ciascuno dei seguenti elementi e prendi nota di ciò che trovi:

  • extension.yaml, che dichiara parametri, funzioni, eventi, ruoli IAM, API richieste, secret e hook del ciclo di vita.

  • functions/, che contiene il codice della funzione, le dipendenze, la configurazione di compilazione, i trigger e le funzioni della coda di attività.

  • README.md, PREINSTALL.md e POSTINSTALL.md, che contengono i passaggi di configurazione, gli avvisi e le note di fatturazione.

  • scripts/, che contiene tutte le utilità di importazione, backfill, IAM, riparazione o migrazione e qualsiasi altro strumento che fornisci insieme all'estensione.

Poi, per ogni elemento in extension.yaml, decidi dove inserirlo nel pacchetto npm:

  • Converti la configurazione utente in parametri Cloud Functions (passaggio 4).

  • Converti i secret in secret Cloud Functions (Passaggio 4).

  • Converti i ruoli IAM in dichiarazioni requiresRole(...) (passaggio 6).

  • Converti le API di Google richieste in dichiarazioni requiresAPI(...), se appropriato (passaggio 6).

  • Converti gli hook di installazione e aggiornamento in dichiarazioni afterFirstDeploy(...) e afterRedeploy(...) (Passaggio 7).

  • Converti gli ID istanza da EXT_INSTANCE_ID a FIREBASE_KIT_INSTANCE_ID (passaggio 4).

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

La lettura di firestore-bigquery-export/extension.yaml e functions/ produce questo inventario:

In extension.yaml Conteggio/valore Dove va
params 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) Cloud Functions parametri (Passaggio 4)
apis bigquery.googleapis.com requiresAPI(...) (Passaggio 6)
roles bigquery.dataEditor, datastore.user, bigquery.user requiresRole(...) (Passaggio 6)
resources 1 trigger di evento (fsexportbigquery) + funzioni della coda di attività (initBigQuerySync, setupBigQuerySync) Funzioni del pacchetto esportato (passaggio 3)
lifecycleEvents onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy/afterRedeploy (passaggio 7)
ID istanza Non utilizzato (nessuna lettura di EXT_INSTANCE_ID) Nessun elemento di cui eseguire la migrazione
scripts/ import/ (backfill), gen-schema-view/ Mantenuti come script (non rientrano nell'ambito di questo articolo)

Analisi. L'estensione non dichiara parametri type: secret, quindi non c'è nulla da migrare per i secret nel Passaggio 4. Il trigger di eventi è già di 2ª gen.; solo le funzioni della coda di attività sono ancora di 1ª gen. (pertinente al passaggio 3).

2. Aggiorna package.json

Aggiorna il file package.json dell'estensione. Se esegui la migrazione di un'estensione, può essere la directory principale package.json. Se esegui la migrazione di molte estensioni in un unico repository, assegna a ogni estensione il proprio pacchetto.

Versioni minime dell'SDK: dichiara firebase-functions >= 7.4.0 e firebase-admin >= 14.2.0 come dipendenze. Dichiara anche la tua versione di firebase-functions come dipendenza peer, in modo che il progetto Cloud Functions degli utenti abbia la stessa versione dell'SDK con cui è stata scritta la tua libreria.

{
  "name": "<package-name>",
  "version": "1.0.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/index.d.ts",
      "default": "./lib/index.js"
    }
  },
  "engines": {
    "node": "22"
  },
  "peerDependencies": {
    "firebase-functions": "^7.4.0"
  },
  "dependencies": {
    "firebase-functions": "^7.4.0",
    "firebase-admin": "^14.2.0"
  }
}

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

Prima. Il file functions/package.json dell'estensione è privato, assegna un nome all'ID estensione e dichiara firebase-functions come dipendenza diretta:

{
  "name": "firestore-bigquery-export",
  "main": "lib/index.js",
  "private": true,
  "dependencies": {
    "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
    "firebase-admin": "^14.2.0",
    "firebase-functions": "^6.3.2"
  }
}

Dopo. Un pacchetto pubblicabile: nome con ambito, una mappa exports e firebase-functions spostato in peerDependencies:

{
  "name": "@firebase-function-kits/firestore-bigquery-export",
  "version": "0.1.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/index.d.ts",
      "default": "./lib/index.js"
    }
  },
  "engines": {
    "node": "22"
  },
  "peerDependencies": {
    "firebase-functions": "^7.4.0"
  },
  "dependencies": {
    "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
    "firebase-admin": "^14.2.0",
    "firebase-functions": "^7.4.0"
  }
}

3. Esegui l'upgrade delle funzioni dalla 1ª gen. alla 2ª gen.

Se l'estensione esporta ancora funzioni di 1ª gen., converti ogni trigger nel suo equivalente di 2ª gen. Importa dai moduli firebase-functions/... e passa le impostazioni di runtime nelle opzioni dell'attivatore.

Consulta la guida all'upgrade a Cloud Functions (2nd gen). In particolare, puoi ridurre al minimo gli sforzi di riscrittura utilizzando la destrutturazione degli eventi con patch di 2ª gen. ed evitare di riscrivere la logica della funzione perché l'SDK di 2ª gen. espone i parametri v1 come campi nell'oggetto evento, consentendoti di utilizzare parametri destrutturati/denominati e mantenere invariata la logica di business.

Prima. 1ª gen.:

import * as functions from "firebase-functions/v1";

export const sync = functions.firestore
  .document("{collectionId}/{documentId}")
  .onWrite(async (change, context) => {
    await handleWrite(change.before, change.after, context.params);
  });

Dopo. 2ª gen.:

import { onDocumentWritten } from "firebase-functions/firestore";

export const syncV2 = onDocumentWritten(
  { document: "{collectionId}/{documentId}" },
  async ({ change, context }) =>
    await handleWrite(change.before, change.after, context.params)
);

Consulta il confronto delle versioni di Cloud Functions per un elenco completo delle differenze tra le funzioni di 1ª gen. e 2ª gen.

Una differenza significativa tra le funzioni di 1ª e 2ª gen. è che le funzioni di 2ª gen. devono essere collocate insieme alle risorse trigger. Quando gli utenti eseguono la migrazione da un'estensione che utilizza funzioni di 1ª gen. a un kit che utilizza funzioni di 2ª gen., potrebbero dover modificare le località delle funzioni per soddisfare questo requisito.

Le posizioni di Cloud Functions selezionate dagli utenti potrebbero non funzionare durante la migrazione perché la posizione esistente viene conservata. Se le funzioni attivate dagli eventi dell'estensione si basavano sulla località delle funzioni fornita dall'utente, ti suggeriamo di aggiungere un nuovo parametro all'estensione per raccogliere la località dell'attivatore di eventi e mapparla alla nuova località della funzione.

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

defineString("DATABASE_REGION", {
  label: "Firestore Instance Location",
  description:
    "Where is the Firestore database located? You can check your current database location at https://console.cloud.google.com/firestore/databases. The functions in this kit deploy to the Cloud Run region closest to this location.",
  input: select({
    "Multi-region (Europe - Belgium and Netherlands)": "eur3",
    "Multi-region (United States)": "nam5",
    "Multi-region (Iowa, North Virginia, and Oklahoma)": "nam7",
    "Iowa (us-central1)": "us-central1",
    // More locations...
  })
});

// Firestore multi-region locations are not Cloud Run regions; deploying a
// function to one hard-fails, so they map to a region inside the multi-region.
const MULTI_REGION_TO_FUNCTION_REGION: Record<string, string> = {
  nam5: "us-central1",
  nam7: "us-central1",
  eur3: "europe-west1",
};

/**
 * Maps a Firestore database location to the Cloud Run region the functions
 * should deploy to. The lookup is case-insensitive and ignores surrounding
 * whitespace, as the CLI's own region handling is. Regional locations pass
 * through lowercased; an unset or blank location returns `undefined`, meaning
 * the functions declare no region.
 */
export function firestoreLocationToFunctionRegion(
  location: string | undefined
): string | undefined {
  const normalized = location?.trim().toLowerCase();
  if (!normalized) {
    return undefined;
  }
  return MULTI_REGION_TO_FUNCTION_REGION[normalized] ?? normalized;
}

const functionRegion = firestoreLocationToFunctionRegion(
  process.env.DATABASE_REGION
);

export const fsexportbigquery = onDocumentWritten(
  {
    region: functionRegion,
    // Other configuration
  },
  (event) => handleDocumentWrite(event, getHandlerContext())
);

Al primo deployment del kit, agli utenti verrà richiesto il loro DATABASE_REGION e verrà eseguito il deployment delle funzioni nel functionRegion corrispondente sopra, risolvendo eventuali problemi di località per le funzioni attivate dagli eventi di 2ª generazione.

4. Converti i parametri e i secret dell'estensione

Parametri

Ogni parametro dichiarato in extension.yaml diventa un parametro Cloud Functions.

Converti le letture dirette dell'ambiente:

const collectionPath = process.env.COLLECTION_PATH;

nei parametri Cloud Functions:

import { defineString } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";

const collectionPath = defineString("COLLECTION_PATH");

// Pass the param directly when used as a placeholder (e.g. trigger path)
export const sync = onDocumentWritten(
  { document: collectionPath },
  async (event) => {
    // Call .value() to read the string inside a handler
    const path = collectionPath.value();
    await handleWrite(path, event);
  }
);

Utilizza collectionPath.value() per leggere la stringa all'interno di un gestore; utilizza collectionPath direttamente dove è previsto un segnaposto, ad esempio un percorso di trigger di una funzione.

La CLI Firebase rileva i parametri e ne legge i valori da .env, .env.<projectId> o chiede agli utenti di inserirli durante il deployment. Mantieni gli stessi nomi dei parametri, in modo che i valori di un'installazione esistente vengano trasferiti.

È importante non modificare i nomi dei parametri dichiarati nel codice in alcun modo. La migrazione delle estensioni conserva automaticamente i valori dei parametri degli utenti finali esistenti, ma solo se i nomi rimangono invariati.

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

Prima. Un parametro dichiarato in extension.yaml, letto come variabile di ambiente non elaborata in config.ts:

# extension.yaml
- param: COLLECTION_PATH
  label: Collection path
  type: string
  required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,

Dopo. Un defineString; l'interfaccia a riga di comando lo rileva e legge da .env:

// src/config.ts
import { defineString } from "firebase-functions/params";

collectionPath: defineString("COLLECTION_PATH", {
  label: "Collection path",
  // We now support "nonEmpty: true" to ensure a value other than the empty string
  // is entered, analogous to "required: true" in extension.yaml
  input: { text: { nonEmpty: true } }
}),

Il nome del parametro rimane invariato, quindi un .env esistente continua a funzionare.

ID istanza

Le estensioni leggono il proprio ID istanza da EXT_INSTANCE_ID, inserito dal runtime di Extensions. I kit di funzioni leggono il proprio ID istanza da FIREBASE_KIT_INSTANCE_ID, che l'interfaccia a riga di comando Firebase imposta per ogni istanza del kit sulla chiave dell'istanza nella mappa instances in firebase.json. La CLI lo fornisce durante il rilevamento in fase di deployment, nell'emulatore e alle funzioni di cui è stato eseguito il deployment.

L'ID istanza non è un parametro, quindi non dichiararlo con defineString. In effetti, FIREBASE_... è un prefisso riservato nei file .env, quindi gli utenti non potranno impostarlo o eseguirne l'override. I valori inseriti dall'interfaccia a riga di comando non sono visibili al sistema dei parametri. Leggilo direttamente dall'ambiente:

// Before
const instanceId = process.env.EXT_INSTANCE_ID;

// After
const instanceId = process.env.FIREBASE_KIT_INSTANCE_ID;

La variabile verrà impostata solo quando il deployment del pacchetto viene eseguito come kit. Se il tuo codice viene distribuito anche come codebase autonoma (vedi il passaggio 9), trattalo come facoltativo o esegui un fail-fast con un messaggio chiaro quando manca. Se l'estensione esponeva l'ID istanza come parametro rivolto agli utenti, devi rimuoverlo, poiché ora il valore è di proprietà dell'interfaccia a riga di comando Firebase.

Esempio pratico: Elimina dati utente

(L'estensione Stream Cloud Firestore to BigQuery non legge l'ID istanza, quindi non è necessario eseguire la migrazione. L'estensione Delete User Data lo utilizza per assegnare un nome ai propri argomenti Pub/Sub.

Prima. Leggi come variabile di ambiente non elaborata in config.ts con il prefisso ext- utilizzato da Extensions per le sue risorse:

// functions/src/config.ts
discoveryTopic: `ext-${process.env.EXT_INSTANCE_ID}-discovery`,
deletionTopic: `ext-${process.env.EXT_INSTANCE_ID}-deletion`,

Dopo. Una semplice lettura process.env di FIREBASE_KIT_INSTANCE_ID utilizzata per due parametri ordinari in modo che gli utenti possano eseguire l'override dei nomi degli argomenti:

// src/config.ts
const instanceId = process.env.FIREBASE_KIT_INSTANCE_ID;

// Non-empty defaults so Pub/Sub trigger bindings resolve during deploy
// discovery without freezing an empty topic name into the manifest.
discoveryTopicName: defineString("DISCOVERY_TOPIC_NAME", {
  default: `kit-${instanceId}-discovery`,
}),
deletionTopicName: defineString("DELETION_TOPIC_NAME", {
  default: `kit-${instanceId}-deletion`,
}),

I valori predefiniti non devono essere vuoti perché le associazioni dei trigger vengono risolte al momento del discovery. Nel manifest di deployment verrà scritto un valore predefinito vuoto come nome dell'argomento. Il kit è anche protetto dall'esecuzione al di fuori del contesto di un kit. Se la variabile non è presente, il valore predefinito a livello di modulo viene valutato come kit-undefined-discovery, quindi il caricatore della configurazione non va a buon fine e restituisce un errore esplicativo:

// ...
const instanceId = process.env.FIREBASE_KIT_INSTANCE_ID;
if (!instanceId) {
  throw new Error(
    "FIREBASE_KIT_INSTANCE_ID is not set. It is provided automatically to " +
      "kit instances by firebase-tools >= 15.32.0; deploy or emulate this " +
      "kit with a supported CLI version."
  );
}
// ...

Questo controllo viene eseguito quando un gestore risolve per la prima volta la sua configurazione, quindi una variabile mancante produce un errore di runtime chiaro anziché funzioni associate silenziosamente agli argomenti kit-undefined-*. Per rifiutare il deployment, esegui il controllo nell'ambito del modulo in modo che venga eseguito durante il rilevamento. Poiché l'interfaccia a riga di comando ricava l'ID istanza da firebase.json, non esiste un INSTANCE_ID configurabile e non c'è nulla da sincronizzare tra più istanze.

Secret

In extension.yaml, dichiari i secret con type: secret. Il runtime di Extensions li archivia e li associa, in modo che il codice dell'estensione possa leggere direttamente process.env.PARAM_NAME. In un tipico codebase Cloud Functions, dichiari e associ ogni secret in modo esplicito:

import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";

const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);

Una volta eseguita la migrazione dell'estensione a un pacchetto/kit npm, i riferimenti ai secret vengono gestiti nel file .env dell'utente finale. È importante non modificare in alcun modo i nomi dei secret dichiarati nel codice. Durante la migrazione, i secret degli utenti finali vengono migrati di conseguenza.

Esempio elaborato: attiva l'email da Cloud Firestore

Prima. MAIL_COLLECTION e SMTP_PASSWORD vengono lette come variabili di ambiente non elaborate in config.ts:

# extension.yaml
- param: MAIL_COLLECTION
  label: Email documents collection
  type: string
  default: mail
  required: true

- param: SMTP_PASSWORD
  label: SMTP password
  type: secret
// functions/src/config.ts
mailCollection: process.env.MAIL_COLLECTION,
smtpPassword: process.env.SMTP_PASSWORD,

Dopo. Un defineString e un defineSecret; l'interfaccia a riga di comando li rileva entrambi e legge da .env:

import { defineString, defineSecret } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";

const mailCollection = defineString("MAIL_COLLECTION", {
  label: "Email documents collection",
  default: "mail"
});

const smtpPassword = defineSecret("SMTP_PASSWORD", { label: "SMTP password" });

export const processQueue = onDocumentWritten(
  { document: `${mailCollection}/{documentId}`, secrets: [smtpPassword] },
  async (event) => {
    const collection = mailCollection.value();
    const password = smtpPassword.value();
    // ...
  }
);

5. Esegui la migrazione delle chiamate interne alla coda di attività

Alcune estensioni mettono in coda il lavoro nelle proprie code di attività dall'interno del codice della funzione, utilizzando Firebase Admin SDK. Questo è diverso dalla ricezione di un'attività inviata (descritta nelle sezioni Esegui l'upgrade delle funzioni e Converti gli hook del ciclo di vita). In questo caso, il tuo codice è il producer che chiama queue.enqueue(...).

Le versioni precedenti di Admin SDK richiedevano alle estensioni di passare il proprio ID istanza dell'estensione come secondo parametro per scegliere come target una funzione di coda di attività nella stessa estensione. A partire dalla versione 14.2.0 di firebase-admin, questa operazione non è obbligatoria né consigliata. Per impostazione predefinita, l'API Task Queue ora ha come target le code di attività nello stesso contesto (ad esempio un'estensione o un kit). È sicuro e consigliato rimuovere questo parametro nel codice sia come estensione sia come funzioni autonome. La rimozione di questo parametro garantisce la portabilità e la compatibilità con le versioni successive.

Tutto il resto della chiamata di accodamento, ovvero il percorso della risorsa locations/<region>/functions/<name>, il payload dell'attività e la logica di ripetizione, rimane invariato.

Per maggiori dettagli sull'inserimento in coda delle funzioni con Cloud Tasks, consulta Inserisci in coda le funzioni con Cloud Tasks.

Prima. Estensione di 1ª gen.:

import { getFunctions } from "firebase-admin/functions";

const queue = getFunctions().taskQueue(
  `locations/${config.location}/functions/syncBigQuery`,
  process.env.EXT_INSTANCE_ID, // extension instance ID, injected by the runtime
);
await queue.enqueue(taskData);

Dopo. Estensione di 2ª gen.:

import { getFunctions } from "firebase-admin/functions";

const queue = getFunctions().taskQueue(
  `locations/${process.env.FUNCTION_REGION}/functions/syncBigQuery`
);
await queue.enqueue(taskData);

Se la chiamata di accodamento ha come target un codebase con prefisso, anche il nome della funzione rilevata ha un prefisso (ad esempio orders-syncBigQuery); consulta Esamina e installa l'istanza del kit di funzioni sostitutive e Esegui il test come kit di funzioni.

6. Dichiara le API e i ruoli IAM richiesti

Sposta i requisiti IAM e API dell'estensione da extension.yaml al codice:

import { requiresAPI, requiresRole } from "firebase-functions";

requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");

Con la sicurezza dichiarativa, l'interfaccia a riga di comando Firebase crea o aggiorna un service account di runtime gestito per il codebase e gli concede l'unione di tutti i ruoli dichiarati. Documenta per gli utenti che tutte le funzioni nel codebase vengono eseguite con questi ruoli, a meno che l'API finale non supporti un modello più ristretto.

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

Prima. Dichiarato in extension.yaml; il runtime di Extensions ha abilitato l'API e ha concesso i ruoli a un account gestito:

apis:
  - apiName: bigquery.googleapis.com
roles:
  - role: bigquery.dataEditor
  - role: datastore.user
  - role: bigquery.user

Dopo. Dichiarato nel codice con requiresAPI e requiresRole:

import { requiresAPI, requiresRole } from "firebase-functions";

requiresAPI(
  "bigquery.googleapis.com",
  "Needed to write changelog rows and views"
);
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");

Se l'estensione pubblica eventi Eventarc, devi anche impostare i ruoli e le API richiesti appropriati per la pubblicazione degli eventi. In precedenza, questa operazione veniva gestita dalle estensioni senza richiedere modifiche al tuo extensions.yaml. Puoi farlo in modo condizionale nel codice in modo che il kit richieda queste autorizzazioni solo quando viene utilizzato un canale Eventarc personalizzato.

if (!!process.env.EVENTARC_CHANNEL) {
  requiresRole("roles/eventarc.publisher");
  requiresAPI(
    "eventarcpublishing.googleapis.com",
    "Publishes the extension's custom events to its Eventarc channel."
  );
}

7. Converti gli hook del ciclo di vita

Se l'estensione chiama getExtensions().runtime() (ad esempio, setProcessingState o setFatalError), elimina queste chiamate, poiché generano un errore se vengono chiamate da una funzione di 2ª gen. di cui è stato eseguito il deployment normalmente. Lo stato del ciclo di vita è ora gestito da afterFirstDeploy e afterRedeploy, dove questo monitoraggio dello stato non viene utilizzato.

Firebase Extensions può eseguire la configurazione quando un utente installa, aggiorna o riconfigura un'estensione. Nel pacchetto npm, dichiara le azioni del ciclo di vita equivalenti nel codice.

Per la configurazione una tantum:

import { afterFirstDeploy } from "firebase-functions/lifecycle";
import { onTaskDispatched } from "firebase-functions/tasks";

export const runInitialSetup = onTaskDispatched(async (request) => {
  await initializeResources(request.data);
});

afterFirstDeploy({
  task: {
    function: "runInitialSetup",
    body: {}
  }
});

Per gli aggiornamenti di configurazione o codice:

import { afterRedeploy } from "firebase-functions/lifecycle";

afterRedeploy({
  task: {
    function: "runInitialSetup",
    body: { reconcile: true }
  }
});

Rendi idempotenti le azioni del ciclo di vita. Gli utenti potrebbero doverli eseguire nuovamente manualmente se l'invio o l'esecuzione non vanno a buon fine:

firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

Prima. lifecycleEvents in extension.yaml, generato dal runtime delle estensioni:

lifecycleEvents:
  onInstall:
    function: initBigQuerySync
    processingMessage: Configuring BigQuery Sync.
  onUpdate:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync
  onConfigure:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync

Dopo. Dichiarato nel codice; l'attività esegue il provisioning di BigQuery al primo deployment:

import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";

afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });

Il provisioning è idempotente, quindi una nuova esecuzione riconcilia il set di dati, la tabella e le viste. Gli utenti possono eseguire nuovamente il controllo manualmente con firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.

8. Configurazione dei documenti per gli utenti

Scrivi un README del pacchetto che spieghi, come minimo:

  • I valori .env richiesti dal pacchetto.
  • I secret richiesti dal pacchetto e come eseguire la migrazione dei valori dei secret esistenti.
  • I ruoli IAM dichiarati dal pacchetto con requiresRole(...).
  • Le API di Google che il pacchetto abilita o richiede.
  • Gli hook del ciclo di vita dichiarati dal pacchetto e come eseguirli nuovamente manualmente.
  • Note sulla fatturazione.
  • Cosa è cambiato rispetto all'estensione originale.
  • In che modo il pacchetto ottiene l'ID istanza (FIREBASE_KIT_INSTANCE_ID impostato dalla CLI) e che viene eseguito il deployment di tutte le funzioni dell'istanza con un prefisso kit-<instanceId>-.

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

Il pacchetto README include una tabella concreta "Cosa è cambiato":

Problema Come l'estensione In qualità di @firebase-function-kits/firestore-bigquery-export
Configurazione Parametri dell'estensione Cloud Functions parametri tramite .env
IAM Concessa dalle estensioni requiresRole(...), applicato al deployment
In fase di provisioning Attività del ciclo di vita per estensioni afterFirstDeploy/afterRedeploy attività
Nomi delle funzioni ext-<instanceId>-fsexportbigquery fsexportbigquery (con prefisso facoltativo)
ID istanza EXT_INSTANCE_ID inserito da Estensioni FIREBASE_KIT_INSTANCE_ID, impostato dalla CLI da firebase.json

9. Testa la funzione di 2ª gen.

Ora dovresti avere una funzione di 2ª generazione che, una volta eseguito il deployment, si comporta in modo identico a una nuova installazione dell'estensione. Il passaggio successivo consiste nel verificare e correggere eventuali problemi introdotti accidentalmente lungo il percorso.

Per chiamare setGlobalOptions per impostare opzioni globali come una regione o una CPU predefinita, devi farlo solo quando esegui il deployment del kit come funzione autonoma di 2ª generazione. Quando il kit viene installato come pacchetto npm, gli utenti chiamano setGlobalOptions nel codice di wrapping per configurare questi parametri e ricevono avvisi se ciò accade due volte. Puoi proteggere questa chiamata controllando la variabile di ambiente FIREBASE_KIT_INSTANCE_ID:

import { setGlobalOptions } from "firebase-functions";

if (!process.env.FIREBASE_KIT_INSTANCE_ID) {
  setGlobalOptions({
    region: "us-east1",
    maxInstances: 10,
  });
}

Assicurati di utilizzare firebase-tools >= 15.32.0 ed esegui il deployment della funzione di 2ª gen. convertita in un progetto di test con le risorse appropriate per testarne il comportamento. Se hai già configurato un progetto di test per testare l'estensione, esegui questo comando:

firebase deploy --only functions

Compila la procedura guidata risultante che ti chiede i valori dei parametri nello stesso modo in cui avresti compilato il modulo di installazione nella console Firebase per l'estensione.

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

Verifichiamo la sincronizzazione end-to-end da Cloud Firestore a BigQuery:

  1. Nella pagina Cloud Firestore della console Firebase, crea la raccolta che hai impostato come COLLECTION_PATH (users), se non esiste già.
  2. Crea un documento denominato bigquery-mirror-test contenente campi con qualsiasi valore.
  3. Nella pagina BigQuery della console Google Cloud, esegui una query sulla tabella del log delle modifiche non elaborato. Dovrebbe contenere una singola riga che registra la creazione del documento:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
    
  4. Esegui una query sulla vista più recente, che dovrebbe restituire l'evento di modifica più recente per l'unico documento presente (bigquery-mirror-test):

    SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
    
  5. Elimina il documento bigquery-mirror-test in Cloud Firestore. Scompare dalla visualizzazione più recente e un evento DELETE viene aggiunto alla tabella del log delle modifiche non elaborato.

    Puoi esaminare la cronologia completa di un singolo documento con:

    SELECT *
       FROM `PROJECT_ID.analytics.users_raw_changelog`
       WHERE document_name = "bigquery-mirror-test"
       ORDER BY timestamp ASC
    

Differenze rispetto al test dell'estensione:

  • Il deployment del trigger viene eseguito come fsexportbigquery (nessun prefisso quando il deployment viene eseguito come una tipica funzione di 2ª gen. e non come un kit), non come ext-<instanceId>-fsexportbigquery. Cerca questo nome nella dashboard e nei log di Cloud Functions.
  • Il tuo codice ora viene eseguito in Firebase Local Emulator Suite come funzioni normali. Puoi impostare il valore dei parametri da utilizzare nell'emulatore con .env.local. Puoi anche eseguire il test delle unità del tuo codice utilizzando l'SDK firebase-functions-test, come descritto in Test delle unità di Cloud Functions.
  • Il provisioning non è più gestito dal runtime delle estensioni. Se la tabella del log delle modifiche non è presente dopo il deployment, esegui nuovamente l'attività di configurazione manualmente: firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. L'attività è idempotente, quindi la sua nuova esecuzione riconcilia il set di dati, la tabella e le viste.
  • I valori dei parametri provengono da .env anziché dal modulo di installazione, pertanto le esecuzioni ripetute di firebase deploy non sono interattive una volta completato .env.

10. Pubblica il tuo kit di funzioni su npm

Una volta convalidata la conversione da estensione a funzione di 2ª gen., puoi pubblicare un candidato per la release su npm per i test end-to-end utilizzando una delle seguenti guide:

Una volta pubblicato su npm, il kit può essere installato con firebase functions:kits:install ed essere elencato come sostituto ufficiale della tua estensione.

Ti consigliamo vivamente di pubblicare prima un candidato per la release. I kit vengono installati in base al nome e alla versione del pacchetto, quindi una pre-release ti consente di testare il flusso di installazione reale rispetto al registry senza esporre un pacchetto non finito agli utenti che eseguono l'installazione con il tag latest predefinito.

Prima della pubblicazione:

  1. Scegli un nome di pacchetto. Funzionano sia i nomi con ambito sia quelli senza ambito (consulta le guide riportate sopra). Tieni presente che i pacchetti con ambito sono privati per impostazione predefinita, quindi passa --access public.
  2. Costruisci e controlla quali astronavi. main e types puntano all'output compilato (lib/ nel nostro esempio), quindi la directory deve essere inclusa nel file tar pubblicato. Utilizza .npmignore o una lista consentita files e controlla il risultato con npm pack --dry-run. Uno script prepublishOnly che esegue la build impedisce la pubblicazione di output obsoleti.

package.json aggiunte che rendono la pubblicazione sicura per impostazione predefinita:

{
  "files": ["lib", "README.md", "CHANGELOG.md"],
  "publishConfig": { "access": "public", "tag": "next" },
  "scripts": {
    "build": "tsc -b",
    "prepublishOnly": "npm run build && npm test"
  }
}

Il campo "publishConfig": { "tag": "next" } garantisce che un npm publish semplice non sovrascriva mai latest.

Creazione di un candidato per la release:

Ad esempio, per incrementare la versione localmente da 0.0.2-rc.3 a 0.0.2-rc.4 (questo esegue il commit e il tagging in Git se package.json si trova nella directory root del repository):

npm version prerelease --preid rc

Per pubblicare il candidato per la release e registrare @your-org/your-kit@0.0.2-rc.4 su npm con il tag next:

npm publish

Il sito web npm può richiedere alcuni minuti per mostrare la nuova versione; npm view legge direttamente il registry:

npm view @your-org/your-kit versions dist-tags

Una volta completati i passaggi 11 e 12 di questa guida, puoi promuovere il pacchetto a una versione stabile:

npm version 0.0.2
npm publish --tag latest
npm dist-tag add @your-org/your-kit@0.0.2 next

Nota su npm-shrinkwrap.json: ti consigliamo vivamente di includere un file npm-shrinkwrap.json nel pacchetto. In caso contrario, l'interfaccia a riga di comando avvisa gli utenti durante l'installazione. In questo modo, gli utenti utilizzano le dipendenze esatte con cui hai eseguito i test e contribuisce a proteggere dagli attacchi alla catena di fornitura. Tuttavia, uno shrinkwrap viene applicato letteralmente nei progetti degli utenti, anche durante la build Cloud Functions (npm ci), dove le voci solo per lo sviluppo possono non riuscire con EBADPLATFORM. Potresti dover rimuovere le voci "dev": true e devDependencies dalla copia shrinkwrap pubblicata.

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

package.json del kit al momento del quarto candidato per la release:

{
  "name": "@firebase-function-kits/firestore-bigquery-export",
  "version": "0.0.2-rc.4",
  "repository": {
    "type": "git",
    "url": "https://github.com/firebase/extensions.git",
    "directory": "kits/firestore-bigquery-export"
  },
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "engines": { "node": "22" },
  "scripts": { "build": "tsc -b" }
}

Tieni presente che in questo caso il kit si trova in un monorepository, quindi è importante includere repository.directory affinché il link del registry npm rimandi alla cartella corretta. La sua CHANGELOG.md contiene le note per la release in attesa.

11. Testa come kit di funzioni

Una volta pubblicato il kit, ti consigliamo di testarlo utilizzando npm.

Assicurati di utilizzare la versione >= 15.32.0 di firebase-tools e installa il kit:

firebase functions:kits:install --package <your-package-name>@<your-prerelease-version>

In questo modo, il pacchetto viene scaricato da npm, configurato all'interno di una nuova directory di origine per il kit e ti viene mostrata la procedura di configurazione della prima istanza, simile al flusso di installazione delle estensioni. Dopo aver installato e configurato il pacchetto localmente, esegui un deployment per creare le risorse nel tuo progetto Google Cloud:

firebase deploy --only functions:<your-kit-instance-id>

Dopo l'installazione, l'interfaccia a riga di comando Firebase stampa un comando di deployment simile con l'ID istanza esatto che hai scelto durante l'installazione.

Convalida di nuovo il kit seguendo le istruzioni riportate nel passaggio 9. Testa la funzione di 2ª gen. Ora che stai eseguendo il deployment utilizzando i kit, le tue funzioni hanno il prefisso e il nome kit-<instance-id>-<method-name>. In questo modo, i kit possono avere più istanze, eseguendo il deployment della stessa funzione più volte in un progetto, ciascuna con un nome univoco.

12. Testa la sostituzione della migrazione

Puoi configurare un'istanza di estensione funzionante e poi utilizzare la guida alla migrazione degli utenti (utilizzando firebase ext:migrate --package o i comandi dell'interfaccia a riga di comando dei kit di funzioni) per completare il test del kit di funzioni come sostituto della migrazione.

13. Informa gli utenti e Google della sostituzione ufficiale dell'estensione

Una volta che il kit di funzioni sostitutivo è pronto e disponibile come pacchetto npm verso cui gli utenti devono eseguire la migrazione, informa sia gli utenti sia Google di questa sostituzione ufficiale. Aggiorna il file README.md nel repository GitHub che ospita la tua estensione con le seguenti informazioni:

<!-- FIREBASE_EXTENSION_REPLACEMENT: extension="<your-extesion-id>" package="<your-npm-package-name>" -->
> [!WARNING]
> **Deprecation Notice:** The Firebase Extension `<your-extension>` is deprecated. Migrate to the [<your-npm-package-name>](<link-to-your-npm-package>) package.

Google analizza i file README delle estensioni note per trovare commenti come <!-- FIREBASE_EXTENSION_REPLACEMENT: extension="firebase/firestore-bigquery-export" package="@firebase-function-kits/firestore-bigquery-export" --> e li utilizza per compilare il nostro registro ufficiale delle sostituzioni archiviate nel repository firebase-tools come replacements.json. Puoi anche controllare replacements.json per vedere quali README.md verranno scansionati per la tua estensione. L'elenco ufficiale delle sostituzioni viene aggiornato settimanalmente.

Esempio elaborato: esegui lo streaming da Cloud Firestore a BigQuery

L'estensione firestore-bigquery-export README.md contiene:

<!-- FIREBASE_EXTENSION_REPLACEMENT: extension="firebase/firestore-bigquery-export" package="@firebase-function-kits/firestore-bigquery-export" -->
> [!WARNING]
> **Deprecation Notice:** The Firebase Extension `firebase/firestore-bigquery-export` is deprecated. Please migrate to the [`@firebase-function-kits/firestore-bigquery-export`](https://www.npmjs.com/package/@firebase-function-kits/firestore-bigquery-export) package.