Migrare le estensioni Firebase a Cloud Functions

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

Questo è il percorso di migrazione consigliato. Firebasegestirà 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 pratico. Ogni sezione termina con un Esempio pratico che mostra l'aspetto dell'estensione prima della migrazione e dopo, come pacchetto @firebase-function-kits/firestore-bigquery-export.

Registrati per ricevere maggiori 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 a questo gruppo anche quando aggiorneremo la guida con ulteriori informazioni su pacchettizzazione, test e distribuzione delle funzioni di 2ª gen.

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 "Unisciti a questo gruppo".

Prima di iniziare

Per completare questa migrazione come descritto, 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 che dichiari in extension.yaml diventa una chiamata requiresRole(...) e ogni API diventa una chiamata requiresAPI(...) nel codice del pacchetto. Al momento del deployment, la CLI 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 i codebase Cloud Functions. Le codebase Cloud Functions ora supportano gli eventi del ciclo di vita analoghi a Firebase Extensions. Dichiara la configurazione in fase di installazione e aggiornamento con gli hook del ciclo di vita afterFirstDeploy(...) e afterRedeploy(...). Questi sostituiscono lifecycleEvents che dichiari in extension.yaml.

Eseguire la migrazione dell'origine Firebase Extensions a una funzione di 2ª gen.

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

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

Installare la skill

Se tu o il tuo assistente di programmazione AI (Gemini in Firebase, Cursor, Claude Code, GitHub Copilot) non avete ancora installato la skill, esegui il seguente comando utilizzando l'interfaccia a riga di comando delle skill:

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

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

"Esegui la migrazione di questa estensione Firebase in un pacchetto di kit di funzioni di seconda generazione pubblicabile seguendo le istruzioni nella skill extension-to-functions-codebase."

1. Inventariare l'estensione

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

Esamina ciascuno dei seguenti elementi e annota 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 della build, i trigger e le funzioni della coda di attività.

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

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

Poi, per ogni elemento in extension.yaml, decidi dove deve essere inserito nel pacchetto npm:

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

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

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

  • Converti le API di Google richieste in dichiarazioni requiresAPI(...), se opportuno (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: riproduci in streaming Cloud Firestore su BigQuery

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

In extension.yaml Conteggio / valore Dove vengono inviati
params 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) Cloud Functions params (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 di 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 EXT_INSTANCE_ID) Nessun elemento da migrare
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 di code di attività sono ancora di 1ª gen. (pertinente nel passaggio 3).

2. Aggiorna package.json

Aggiorna il file package.json dell'estensione. Se esegui la migrazione di un'estensione, puoi utilizzare la package.json principale. 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 la tua versione di firebase-functions anche come dipendenza peer, in modo che il progetto Cloud Functions dei tuoi 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: riproduci in streaming Cloud Firestore su 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ª alla 2ª gen.

Se la tua 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 di attivazione.

Consulta la guida all'upgrade di Cloud Functions di 2ª gen.. In particolare, puoi ridurre al minimo gli sforzi di riscrittura utilizzando la destrutturazione degli eventi 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 tra le versioni di Cloud Functions per un elenco completo delle differenze tra le funzioni di 1ª e 2ª gen.

Una differenza significativa tra le funzioni di 1ª e 2ª gen. è che le funzioni di 2ª gen. devono trovarsi nella stessa posizione delle 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 posizioni 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 mantenuta. Se le funzioni attivate dagli eventi della tua estensione si basavano sulla posizione delle funzioni fornite dall'utente, ti consigliamo di aggiungere un nuovo parametro all'estensione per raccogliere la posizione di attivazione dell'evento e mapparla alla nuova posizione della funzione.

Esempio elaborato: riproduci in streaming Cloud Firestore su 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à chiesto il loro DATABASE_REGION e le funzioni verranno implementate nel corrispondente functionRegion sopra, risolvendo eventuali problemi di posizione per le funzioni attivate dagli eventi gen2.

4. Convertire i parametri e i secret dell'estensione

Parametri

Ogni parametro che dichiari 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 attivazione della funzione.

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

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

Esempio elaborato: riproduci in streaming Cloud Firestore su 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; la CLI 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 è invariato, quindi un .env esistente continua a funzionare.

ID istanza

Le estensioni leggono il proprio ID istanza da EXT_INSTANCE_ID, inserito dal runtime delle estensioni. I kit di funzioni leggono il proprio ID istanza da FIREBASE_KIT_INSTANCE_ID, che la CLI Firebase imposta per ogni istanza del kit sulla chiave dell'istanza nella mappa instances in firebase.json. La CLI lo fornisce durante l'individuazione 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. Infatti, FIREBASE_... è un prefisso riservato nei file .env, quindi gli utenti non potranno impostarlo o sostituirlo. I valori inseriti dalla CLI non sono visibili al sistema di 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 pacchetto viene implementato come kit. Se il tuo codice viene implementato anche come codebase autonoma (vedi Passaggio 9), consideralo facoltativo o genera un errore rapido con un messaggio chiaro quando non è presente. Se la tua estensione ha esposto l'ID istanza come parametro rivolto agli utenti, devi rimuoverlo, in quanto ora il valore è di proprietà della CLI Firebase.

Esempio pratico: Elimina dati utente

L'estensione Stream Cloud Firestore to BigQuery non legge il suo ID istanza, quindi non c'è nulla da migrare. L'estensione Elimina dati utente lo utilizza per denominare i propri argomenti Pub/Sub.)

Prima. Leggi come variabile di ambiente non elaborata in config.ts con il prefisso ext- utilizzato dalle estensioni per le relative 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 sostituire i 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é i binding dei trigger vengono risolti al momento del rilevamento. Un valore predefinito vuoto verrà scritto nel manifest di deployment come nome dell'argomento. Il kit è anche difensivo contro l'esecuzione al di fuori del contesto di un kit. Se la variabile non è presente, il valore predefinito a livello di modulo verrà valutato come kit-undefined-discovery, quindi il caricatore della configurazione non riesce a caricare la configurazione e restituisce un errore esplicativo invece:

// ...
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 la distribuzione stessa, esegui il controllo nell'ambito del modulo in modo che venga eseguito durante il rilevamento. Poiché la CLI deriva l'ID istanza da firebase.json, non esiste un INSTANCE_ID configurabile e non è necessario mantenere la sincronizzazione tra più istanze.

Secret

In extension.yaml, dichiari i secret con type: secret. L'ambiente di runtime delle estensioni le archivia e le associa, in modo che il codice dell'estensione possa leggere process.env.PARAM_NAME direttamente. In una codebase Cloud Functions tipica, 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 dell'utente finale vengono migrati di conseguenza.

Esempio elaborato: Trigger Email From 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; la CLI 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. Eseguire la migrazione delle chiamate alla coda di attività interna

Alcune estensioni mettono in coda il lavoro nelle proprie code di attività dall'interno del codice della funzione, utilizzando Firebase Admin SDK. Questa operazione è diversa dalla ricezione di un'attività inviata (descritta nelle sezioni Funzioni di upgrade e Convertire gli hook del ciclo di vita). Qui il tuo codice è il producer che chiama queue.enqueue(...).

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

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 saperne di più sull'accodamento delle funzioni con Cloud Tasks, consulta Accodare 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 una codebase con prefisso, anche il nome della funzione rilevato ha un prefisso (ad esempio, orders-syncBigQuery); vedi Esamina e installa l'istanza del kit di funzioni di sostituzione e Test come kit di funzioni.

6. Dichiarare le API e i ruoli IAM richiesti

Sposta i requisiti IAM e API della tua 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, la CLI 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 i tuoi 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: riproduci in streaming Cloud Firestore su BigQuery

Prima. Dichiarato in extension.yaml; il runtime delle estensioni ha attivato l'API e 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 la tua 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. Convertire gli hook del ciclo di vita

Se la tua estensione chiama getExtensions().runtime() (ad esempio, setProcessingState o setFatalError), elimina queste chiamate, in quanto generano un errore se chiamate da una funzione di 2ª gen. normalmente implementata. Lo stato del ciclo di vita è ora determinato 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 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 del codice:

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

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

Rendi idempotenti le azioni del ciclo di vita. Se l'invio o l'esecuzione non va a buon fine, gli utenti potrebbero doverli eseguire di nuovo manualmente:

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

Esempio elaborato: riproduci in streaming Cloud Firestore su 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; le disposizioni del task 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 test 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 di nuovo manualmente.
  • Note di fatturazione.
  • Che cosa è cambiato rispetto all'estensione originale.
  • Come il pacchetto ottiene l'ID istanza (FIREBASE_KIT_INSTANCE_ID impostato dalla CLI) e che tutte le funzioni dell'istanza vengono implementate con un prefisso kit-<instanceId>-.

Esempio elaborato: riproduci in streaming Cloud Firestore su BigQuery

Il pacchetto README include una tabella concreta "Che cosa è cambiato":

Problema Come estensione Come @firebase-function-kits/firestore-bigquery-export
Configurazione Parametri di estensione Cloud Functions params via .env
IAM Concessa dalle estensioni requiresRole(...), applicato al momento del 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 dalle estensioni FIREBASE_KIT_INSTANCE_ID, impostato dalla CLI da firebase.json

9. Testare la funzione di 2ª gen.

Ora dovresti avere una funzione di 2ª gen. che, una volta eseguito il deployment, si comporta in modo identico a una nuova installazione della tua estensione. Il passaggio successivo consiste nel verificare e correggere eventuali problemi introdotti accidentalmente durante il processo.

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 di 2ª gen. autonoma. Quando il kit viene installato come pacchetto npm, gli utenti chiamano setGlobalOptions nel codice di wrapping per configurare questi parametri e riceveranno avvisi se ciò si verifica 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 e di eseguire 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: riproduci in streaming Cloud Firestore su BigQuery

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

  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 valori.
  3. Nella pagina BigQuery della console Google Cloud, esegui una query sulla tabella raw_changelog. Deve contenere una singola riga che registra la creazione del documento:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
    
  4. Esegui una query sulla visualizzazione più recente, che dovrebbe restituire l'ultimo evento di modifica 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 alla tabella del changelog non elaborato viene aggiunto un evento DELETE.

    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 trigger viene eseguito il deployment come fsexportbigquery (nessun prefisso quando viene eseguito il deployment come una tipica funzione di 2ª gen. e non come un kit), non come ext-<instanceId>-fsexportbigquery. Cerca questo nome nel dashboard e nei log Cloud Functions.
  • Il 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 testare le 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 di nuovo manualmente l'attività di configurazione: firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. L'attività è idempotente, quindi la sua 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 nuove esecuzioni di firebase deploy non sono interattive una volta completata l'esecuzione di .env.

10. Pubblica il tuo kit di funzioni su npm

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

Quando il kit viene pubblicato su npm, può essere installato con firebase functions:kits:install ed elencato come sostituzione 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 registro 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 (vedi le guide sopra). Tieni presente che i pacchetti con ambito sono privati per impostazione predefinita, quindi passa --access public.
  2. Costruisci e controlla le navi. main e types puntano all'output compilato (lib/ nel nostro esempio pratico), quindi questa directory deve essere inclusa nel file tar pubblicato. Utilizza .npmignore o una lista consentita files e ispeziona il risultato con npm pack --dry-run. Uno script prepublishOnly che esegue la build impedisce la pubblicazione di output obsoleti.

Aggiunte a package.json 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" } assicura che un npm publish semplice non sovrascriva mai latest.

Taglio 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 radice 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

Potrebbero essere necessari alcuni minuti prima che sul sito web npm venga visualizzata la nuova versione; npm view legge il registro direttamente:

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

Una volta completati il passaggio 11 e il passaggio 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. Se non lo fai, la CLI avvisa gli utenti durante l'installazione. Garantisce che gli utenti utilizzino le dipendenze esatte che hai testato e contribuisce a proteggere dagli attacchi alla catena di fornitura. Tuttavia, il wrapping viene applicato alla lettera nei progetti degli utenti, anche durante la build Cloud Functions (npm ci), dove le voci solo per sviluppatori possono non riuscire con EBADPLATFORM. Potresti dover rimuovere le voci "dev": true e devDependencies dalla copia shrinkwrap pubblicata.

Esempio elaborato: riproduci in streaming Cloud Firestore su 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 monorepo, quindi è importante includere repository.directory per il link al registro npm in modo che punti alla cartella corretta. Il suo CHANGELOG.md contiene le note per la release in attesa.

11. Testare come kit di funzioni

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

Assicurati di utilizzare firebase-tools versione >= 15.32.0 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 in una nuova directory di origine per il kit e ti viene chiesto di configurare la prima istanza in modo simile al flusso di installazione delle estensioni. Una volta installato e configurato il pacchetto in locale, esegui un deployment per creare le risorse nel progetto Google Cloud:

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

Dopo l'installazione, la CLI 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 esegui il deployment utilizzando i kit, le tue funzioni hanno il prefisso e il nome kit-<instance-id>-<method-name>. Ciò consente ai kit di avere più istanze, di eseguire il deployment della stessa funzione più volte in un progetto, ognuna con un nome univoco.

12. Test migration replacement

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

13. Notificare agli utenti e a Google la sostituzione ufficiale dell'estensione

Una volta che la sostituzione del kit di funzioni è pronta e disponibile come pacchetto npm a cui gli utenti devono eseguire la migrazione, informa sia gli utenti che 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 esamina i file README delle estensioni noti per 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 archiviato 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: riproduci in streaming Cloud Firestore su 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.