En esta guía, se muestra cómo migrar tus extensiones del entorno Firebase Extensions en desuso a una función que tus usuarios instalan e implementan en su propio Cloud Functions para la base de código de Firebase (2ª gen.).
Esta es la ruta de migración recomendada. Firebase mantendrá una lista de extensiones con equivalentes oficiales de npm. En esta guía, se explica cómo crear la tuya.
A lo largo de esta guía, se usa la extensión Stream Cloud Firestore to
BigQuery (firestore-bigquery-export) como ejemplo en ejecución. Cada sección finaliza con un Ejemplo práctico que muestra cómo se veía esa extensión antes de la migración y cómo se ve después, como el paquete @firebase-function-kits/firestore-bigquery-export.
Regístrate para obtener más información y ayuda sobre la migración de extensiones
Si tienes preguntas sobre cómo migrar desde Firebase Extensions, puedes comunicarte con nosotros a firebase-extensions-migrator-support-external@google.com. También enviaremos un correo electrónico a este grupo a medida que actualicemos la guía con más información sobre el empaquetado, las pruebas y la distribución de tus funciones de 2ª gen.
Para unirte a este grupo, envía un mensaje a firebase-extensions-migrator-support-external+subscribe@google.com, que responderá con un correo electrónico de solicitud de membresía. Debes responder ese correo electrónico, no hacer clic en el botón "Unirse a este grupo".
Antes de comenzar
Para completar esta migración tal como se describe, usarás las siguientes funciones de Cloud Functions:
Configuración con parámetros. Cada parámetro que declares en
extension.yamlse convertirá en un parámetro definido en el código de tu paquete.Roles de IAM declarativos y APIs obligatorias. Cada rol que declares en
extension.yamlse convierte en una llamada arequiresRole(...), y cada API se convierte en una llamada arequiresAPI(...)en el código de tu paquete. En el momento de la implementación, la CLI de Firebase otorga los roles declarados a una cuenta de servicio de tiempo de ejecución administrada y habilita las APIs declaradas en tu nombre.Eventos de ciclo de vida para las bases de código de Cloud Functions. Las bases de código de Cloud Functions ahora admiten eventos de ciclo de vida análogos a Firebase Extensions. Declara la configuración en el momento de la instalación y de la actualización con los hooks de ciclo de vida
afterFirstDeploy(...)yafterRedeploy(...). Estos reemplazan ellifecycleEventsque declaras enextension.yaml.
Migra la fuente de Firebase Extensions a una función de 2ª gen.
Migración automatizada (opcional) con la habilidad del agente de Firebase
Puedes automatizar los pasos del 1 al 8 (inventario de recursos, activadores de actualizaciones, conversiones de parámetros y secretos, IAM declarativo, hooks de ciclo de vida y generación del README del paquete) con la habilidad oficial del agente de IA de extension-to-functions-codebase.
Instala la habilidad
Si tú o tu asistente de programación con IA (Gemini en Firebase, Cursor, Claude Code, GitHub Copilot) aún no instalaron la habilidad, ejecuta el siguiente comando con la CLI de habilidades:
npx skills add firebase/agent-skills --skill extension-to-functions-codebase
Una vez que la habilidad se instala en tu proyecto, tu asistente de programación con IA sigue automáticamente sus reglas de migración y pasos de transformación. Puedes usar la siguiente instrucción:
"Migra esta extensión de Firebase a un paquete publicable de Function Kit de 2ª gen. siguiendo las instrucciones de la habilidad de extension-to-functions-codebase".
1. Haz un inventario de la extensión
Comienza por hacer un inventario de tu extensión: una lista completa de todo lo que la extensión declara, envía y documenta, de modo que cada comportamiento tenga un destino definido en la función de 2ª gen. y no se pierda nada en la migración.
Revisa cada uno de los siguientes elementos y anota lo que encuentres:
extension.yaml, que declara tus parámetros, funciones, eventos, roles de IAM, APIs requeridas, secretos y hooks de ciclo de vida.functions/, que contiene el código de tu función, las dependencias, la configuración de compilación, los activadores y las funciones de la lista de tareas en cola.README.md,PREINSTALL.mdyPOSTINSTALL.md, que contienen pasos de configuración, advertencias y notas de facturaciónscripts/, que contiene cualquier utilidad de importación, relleno, IAM, reparación o migración, y cualquier otra herramienta que envíes junto con la extensión.
Luego, para cada elemento de extension.yaml, decide dónde se ubicará en el paquete de npm:
Convierte la configuración del usuario en parámetros de Cloud Functions (paso 4).
Convierte los secretos en Cloud Functions secretos (paso 4).
Convierte los roles de IAM en declaraciones de
requiresRole(...)(paso 6).Convierte las APIs de Google requeridas en declaraciones de
requiresAPI(...)cuando corresponda (Paso 6).Convierte los hooks de instalación y actualización en declaraciones de
afterFirstDeploy(...)yafterRedeploy(...)(paso 7).Convierte los IDs de instancia de
EXT_INSTANCE_IDaFIREBASE_KIT_INSTANCE_ID(paso 4).
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
La lectura de firestore-bigquery-export/extension.yaml y functions/ produce este inventario:
En extension.yaml |
Recuento / valor | A dónde va |
|---|---|---|
params |
25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) |
Parámetros de Cloud Functions (paso 4) |
apis |
bigquery.googleapis.com |
requiresAPI(...) (Paso 6) |
roles |
bigquery.dataEditor, datastore.user, bigquery.user |
requiresRole(...) (Paso 6) |
resources |
1 activador de eventos (fsexportbigquery) + funciones de la lista de tareas en cola (initBigQuerySync, setupBigQuerySync) |
Funciones del paquete exportado (Paso 3) |
lifecycleEvents |
onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync |
afterFirstDeploy / afterRedeploy (Paso 7) |
| ID de instancia | No se usa (no hay lecturas de EXT_INSTANCE_ID) |
No hay nada para migrar |
scripts/ |
import/ (relleno), gen-schema-view/ |
Se mantienen como secuencias de comandos (fuera del alcance aquí) |
Análisis: La extensión no declara ningún parámetro de type: secret, por lo que no hay nada que migrar para los secretos en el paso 4. El activador de eventos ya es de 2ª gen.; solo las funciones de la lista de tareas en cola siguen siendo de 1ª gen. (relevante en el paso 3).
2. Actualiza package.json
Actualiza el archivo package.json de tu extensión. Si migras una extensión, este puede ser el package.json raíz. Si migras muchas extensiones en un solo repositorio, asigna un paquete propio a cada extensión.
Versiones mínimas del SDK: Declara firebase-functions >=
7.4.0 y firebase-admin >=
14.2.0 como dependencias. Declara tu versión de firebase-functions también como una dependencia del mismo nivel, de modo que el proyecto Cloud Functions de tus usuarios tenga la misma versión del SDK con la que se escribió tu biblioteca.
{
"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"
}
}
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
Antes. El functions/package.json de la extensión es privado, nombra el ID de la extensión y declara firebase-functions como una dependencia directa:
{
"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"
}
}
Después. Un paquete publicable: nombre con alcance, un mapa de exports y firebase-functions movido a 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. Actualiza funciones de la 1ª a la 2ª gen.
Si tu extensión aún exporta funciones de 1ª gen., convierte cada activador a su equivalente de 2ª gen. Importa desde los módulos firebase-functions/... y pasa la configuración del tiempo de ejecución en las opciones del activador.
Consulta la guía de actualización de Cloud Functions de 2ª gen. En particular, puedes minimizar los esfuerzos de reescritura con la desestructuración de eventos parcheados de 2ª gen. y evitar reescribir la lógica de tu función, ya que el SDK de 2ª gen. expone los parámetros de la versión 1 como campos en el objeto de evento, lo que te permite usar parámetros desestructurados o con nombre y mantener tu lógica empresarial sin cambios.
Antes. 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);
});
Después. 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 la comparación de versiones de Cloud Functions para obtener una lista completa de las diferencias entre las funciones de 1ª gen. y 2ª gen.
Una diferencia significativa entre las funciones de 1ª y 2ª gen. es que las funciones de 2ª gen. deben estar ubicadas en el mismo lugar que sus recursos de activador. Cuando los usuarios migran de una extensión que usa funciones de 1ª gen. a un kit que usa funciones de 2ª gen., es posible que deban cambiar las ubicaciones de las funciones para cumplir con este requisito.
Es posible que las ubicaciones de Cloud Functions que seleccionaron tus usuarios dejen de funcionar durante la migración porque se conserva la ubicación existente. Si las funciones activadas por eventos de tu extensión dependían de la ubicación de las funciones proporcionadas por el usuario, te sugerimos que agregues un parámetro nuevo a tu extensión para recopilar la ubicación del activador de eventos y asignarla a la nueva ubicación de la función.
Ejemplo sobre el que se trabajó: Transmisión de 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())
);
En la primera implementación del kit, se les solicitará a los usuarios su
DATABASE_REGION y las funciones se implementarán en el
functionRegion correspondiente anterior, lo que solucionará cualquier problema de ubicación de sus funciones activadas por eventos de gen2.
4. Cómo convertir parámetros y secretos de la extensión
Params
Cada parámetro que declares en extension.yaml se convierte en un parámetro Cloud Functions.
Convierte las lecturas directas del entorno:
const collectionPath = process.env.COLLECTION_PATH;
en los parámetros de 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);
}
);
Usa collectionPath.value() para leer la cadena dentro de un controlador; usa collectionPath directamente donde se espera un marcador de posición, como una ruta de activación de funciones.
La CLI de Firebase descubre tus parámetros y lee sus valores de .env, .env.<projectId> o solicita a tus usuarios durante la implementación. Mantén los mismos nombres de parámetros para que se transfieran los valores de una instalación existente.
Es importante que no cambies los nombres de los parámetros declarados en tu código en absoluto. La migración de extensiones conserva automáticamente los valores de los parámetros existentes del usuario final, pero solo cuando los nombres no cambian.
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
Antes. Un parámetro declarado en extension.yaml, que se lee como una variable de entorno sin procesar en config.ts:
# extension.yaml
- param: COLLECTION_PATH
label: Collection path
type: string
required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,
Después. Un defineString; la CLI lo descubre y lee desde .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 } }
}),
El nombre del parámetro no cambió, por lo que un .env existente sigue funcionando.
ID de instancia
Las extensiones leen su ID de instancia de EXT_INSTANCE_ID, que se inserta en el entorno de ejecución de Extensions. Los kits de funciones leen su ID de instancia desde FIREBASE_KIT_INSTANCE_ID, que la CLI de Firebase establece para cada instancia del kit en la clave de la instancia en el mapa instances en firebase.json. La CLI lo proporciona durante el descubrimiento en el momento de la implementación, en el emulador y a las funciones implementadas.
El ID de instancia no es un parámetro, por lo que no debes declararlo con defineString. De hecho, FIREBASE_... es un prefijo reservado en los archivos .env, por lo que los usuarios no podrán establecerlo ni anularlo allí. Los valores que inyecta la CLI no son visibles para el sistema de parámetros. Léelo directamente desde el entorno:
// Before
const instanceId = process.env.EXT_INSTANCE_ID;
// After
const instanceId = process.env.FIREBASE_KIT_INSTANCE_ID;
La variable solo se establecerá cuando tu paquete se implemente como un kit. Si tu código también se implementa como una base de código independiente (consulta el paso 9), trátalo como opcional o falla rápidamente con un mensaje claro cuando falte. Si tu extensión expuso el ID de instancia como un parámetro visible para el usuario, debes quitar ese parámetro, ya que la CLI de Firebase ahora posee el valor.
Ejemplo práctico: Borra los datos del usuario
(La extensión Stream Cloud Firestore a BigQuery no lee su ID de instancia, por lo que no hay nada que migrar allí. La extensión Delete User Data la usa para nombrar sus temas de Pub/Sub.
Antes. Se lee como una variable de entorno sin procesar en config.ts con el prefijo ext- que usa Extensiones para sus recursos:
// functions/src/config.ts
discoveryTopic: `ext-${process.env.EXT_INSTANCE_ID}-discovery`,
deletionTopic: `ext-${process.env.EXT_INSTANCE_ID}-deletion`,
Después. Una lectura process.env simple de FIREBASE_KIT_INSTANCE_ID que se usa para dos parámetros comunes, de modo que los usuarios puedan anular los nombres de los temas:
// 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`,
}),
Los valores predeterminados no deben estar vacíos porque las vinculaciones de activadores se resuelven en el momento del descubrimiento. Se escribiría un valor predeterminado vacío en el manifiesto de implementación como el nombre del tema. El kit también se defiende contra la ejecución fuera del contexto de un kit. Si falta la variable, el valor predeterminado a nivel del módulo se evaluaría como kit-undefined-discovery, por lo que el cargador de configuración fallaría con un error explicativo:
// ...
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."
);
}
// ...
Esta verificación se ejecuta cuando un controlador resuelve su configuración por primera vez, por lo que una variable faltante produce un error de tiempo de ejecución claro en lugar de funciones vinculadas de forma silenciosa a temas de kit-undefined-*. Para rechazar la implementación, realiza la verificación en el alcance del módulo para que se ejecute durante el descubrimiento. Dado que la CLI deriva el ID de la instancia de firebase.json, no hay ningún INSTANCE_ID configurable ni nada que deba mantenerse sincronizado en varias instancias.
Secrets
En extension.yaml, declaras secretos con type: secret. El tiempo de ejecución de Extensiones los almacena y los vincula, de modo que el código de tu extensión pueda leer process.env.PARAM_NAME directamente. En una base de código de Cloud Functions típica, declaras y vinculas cada secreto de forma explícita:
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 vez que tu extensión se migra a un paquete o kit de npm, las referencias a secretos se administran en el archivo .env del usuario final. Es importante que no cambies los nombres de los secretos declarados en tu código en absoluto. Durante la migración, los secretos del usuario final se migran según corresponda.
Ejemplo sobre el que se trabajó: Envía un correo electrónico desde Cloud Firestore
Antes. MAIL_COLLECTION y SMTP_PASSWORD se leen como variables de entorno sin procesar en 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,
Después. Un defineString y un defineSecret; la CLI descubre ambos y lee desde .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. Migra las llamadas internas a la lista de tareas en cola
Algunas extensiones ponen en cola el trabajo en sus propias colas de tareas desde el interior de su código de función, usando Firebase Admin SDK. Esto es diferente de recibir una tarea enviada (que se aborda en las secciones Actualiza funciones y Convierte hooks de ciclo de vida). Aquí, tu código es el productor que llama a queue.enqueue(...).
Las versiones anteriores de Admin SDK requerían que las extensiones pasaran su propio ID de instancia de extensión como un segundo parámetro para dirigirse a una función de Task Queue en la misma extensión. A partir de firebase-admin 14.2.0, esto no es obligatorio ni recomendado. De forma predeterminada, la API de Task Queue ahora apunta a las colas de tareas en el mismo contexto (por ejemplo, una extensión o un kit). Es seguro y recomendable quitar este parámetro del código, tanto como extensión como funciones independientes.
Quitar este parámetro garantiza la portabilidad y la compatibilidad con versiones posteriores.
Todo lo demás sobre la llamada enqueue (la ruta de recursos locations/<region>/functions/<name>, la carga útil de la tarea y la lógica de reintentos) sigue siendo igual.
Consulta Pon funciones en cola con Cloud Tasks para obtener más detalles sobre cómo poner funciones en cola con Cloud Tasks.
Antes. Extensión de 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);
Después. Extensión de 2ª gen.:
import { getFunctions } from "firebase-admin/functions";
const queue = getFunctions().taskQueue(
`locations/${process.env.FUNCTION_REGION}/functions/syncBigQuery`
);
await queue.enqueue(taskData);
Si tu llamada a enqueue tiene como objetivo una base de código con prefijo, el nombre de la función descubierta también tendrá un prefijo (por ejemplo, orders-syncBigQuery). Consulta Revisa e instala la instancia del kit de funciones de reemplazo y Prueba como un kit de funciones.
6. Declara las APIs y los roles de IAM obligatorios
Mueve los requisitos de IAM y de API de tu extensión fuera de extension.yaml y al código:
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 seguridad declarativa, la CLI de Firebase crea o actualiza una cuenta de servicio de tiempo de ejecución administrada para la base de código y le otorga la unión de todos los roles declarados. Documenta para tus usuarios que todas las funciones de la base de código se ejecutan con esos roles, a menos que la API final admita un modelo más limitado.
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
Antes. Se declara en extension.yaml; el entorno de ejecución de Extensions habilitó la API y otorgó los roles a una cuenta administrada:
apis:
- apiName: bigquery.googleapis.com
roles:
- role: bigquery.dataEditor
- role: datastore.user
- role: bigquery.user
Después. Se declara en el código con requiresAPI y 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");
Si tu extensión publica eventos de Eventarc, también debes establecer los roles y las APIs requeridos adecuados para publicar eventos. Anteriormente, las extensiones se encargaban de esto sin que se requirieran cambios en tu extensions.yaml. Puedes hacerlo de forma condicional en tu código para que el kit solo solicite estos permisos cuando se use un canal de Eventarc personalizado.
if (!!process.env.EVENTARC_CHANNEL) {
requiresRole("roles/eventarc.publisher");
requiresAPI(
"eventarcpublishing.googleapis.com",
"Publishes the extension's custom events to its Eventarc channel."
);
}
7. Cómo convertir hooks de ciclo de vida
Si tu extensión llama a getExtensions().runtime() (por ejemplo, setProcessingState o setFatalError), borra esas llamadas, ya que arrojan un error si se las llama desde una función de 2ª gen. implementada normalmente. El estado del ciclo de vida ahora se controla con afterFirstDeploy y afterRedeploy, en los que no se usa este seguimiento del estado.
Firebase Extensions puede ejecutar la configuración cuando un usuario instala, actualiza o reconfigura una extensión. En tu paquete npm, declara acciones de ciclo de vida equivalentes en el código.
Para la configuración única:
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: {}
}
});
Para actualizaciones de configuración o código, haz lo siguiente:
import { afterRedeploy } from "firebase-functions/lifecycle";
afterRedeploy({
task: {
function: "runInitialSetup",
body: { reconcile: true }
}
});
Haz que tus acciones del ciclo de vida sean idempotentes. Es posible que tus usuarios deban volver a ejecutarlos manualmente si falla el envío o la ejecución:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
Antes. lifecycleEvents en extension.yaml, impulsado por el tiempo de ejecución de Extensiones:
lifecycleEvents:
onInstall:
function: initBigQuerySync
processingMessage: Configuring BigQuery Sync.
onUpdate:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
onConfigure:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
Después. Se declara en el código. La tarea aprovisiona BigQuery en la primera implementación:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";
afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });
El aprovisionamiento es idempotente, por lo que una nueva ejecución reconcilia el conjunto de datos, la tabla y las vistas.
Los usuarios pueden volver a ejecutarlo de forma manual con firebase functions:lifecycle:run afterFirstDeploy
CODEBASE_NAME.
8. Configuración de documentos para tus usuarios
Escribe un paquete README que explique, como mínimo, lo siguiente:
- Son los valores de
.envque requiere el paquete. - Los secretos que requiere el paquete y cómo migrar los valores de secretos existentes
- Roles de IAM que declara el paquete con
requiresRole(...). - Son las APIs de Google que el paquete habilita o requiere.
- Los hooks de ciclo de vida que declara el paquete y cómo volver a ejecutarlos de forma manual
- Notas de facturación.
- Qué cambió en comparación con la extensión original
- Cómo el paquete obtiene su ID de instancia (
FIREBASE_KIT_INSTANCE_IDestablecido por la CLI) y que todas las funciones de la instancia se implementan con un prefijokit-<instanceId>-
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
El paquete README incluye una tabla concreta de "qué cambió":
| Problema | Como extensión | Como @firebase-function-kits/firestore-bigquery-export |
|---|---|---|
| Configuración | Parámetros de extensión | Cloud Functions parámetros a través de .env |
| IAM | Otorgado por extensiones | requiresRole(...), aplicado en la implementación |
| Aprovisionamiento | Tarea de ciclo de vida por extensiones | Tarea afterFirstDeploy / afterRedeploy |
| Nombres de las funciones | ext-<instanceId>-fsexportbigquery |
fsexportbigquery (con prefijo opcional) |
| ID de instancia | EXT_INSTANCE_ID insertado por extensiones |
FIREBASE_KIT_INSTANCE_ID, que establece la CLI a partir de firebase.json |
9. Prueba tu función de 2ª gen.
Ahora deberías tener una función de 2ª gen. que, cuando se implemente, se comportará de forma idéntica a una nueva instalación de tu extensión. El siguiente paso es verificar y corregir cualquier problema que se haya introducido accidentalmente en el proceso.
Para llamar a setGlobalOptions y establecer opciones globales, como una región o una CPU predeterminadas, debes hacerlo solo cuando implementes tu kit como una función de 2ª gen. independiente. Cuando tu kit se instala como un paquete npm, los usuarios llaman a setGlobalOptions en su código de wrapper para configurar estos parámetros, y recibirán advertencias si esto sucede dos veces.
Puedes proteger esta llamada verificando la variable de entorno FIREBASE_KIT_INSTANCE_ID:
import { setGlobalOptions } from "firebase-functions";
if (!process.env.FIREBASE_KIT_INSTANCE_ID) {
setGlobalOptions({
region: "us-east1",
maxInstances: 10,
});
}
Asegúrate de usar firebase-tools
>= 15.32.0 y, luego, implementa tu función de 2ª gen. convertida en un proyecto de prueba con los recursos adecuados para probar su comportamiento. Si ya tienes un proyecto de prueba configurado para probar tu extensión, ejecuta el siguiente comando:
firebase deploy --only functions
Completa el asistente resultante que te solicita valores de parámetros de la misma manera en que habrías completado el formulario de instalación en la consola de Firebase para la extensión.
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
Verificamos la sincronización de Cloud Firestore a BigQuery de extremo a extremo:
- En la página Cloud Firestore de la consola de Firebase, crea la colección que configuraste como
COLLECTION_PATH(users) si aún no existe. - Crea un documento llamado
bigquery-mirror-testque contenga cualquier campo con cualquier valor. En la página BigQuery de la consola de Google Cloud, consulta la tabla sin procesar del registro de cambios. Debe contener un solo registro de la creación del documento:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`Consulta la vista más reciente, que debería devolver el evento de cambio más reciente para el único documento presente (
bigquery-mirror-test):SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`Borra el documento
bigquery-mirror-testen Cloud Firestore. Desaparece de la vista más reciente y se agrega un eventoDELETEa la tabla del registro de cambios sin procesar.Puedes inspeccionar el historial completo de un solo documento con el siguiente comando:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog` WHERE document_name = "bigquery-mirror-test" ORDER BY timestamp ASC
Diferencias con respecto a las pruebas de la extensión:
- El activador se implementa como
fsexportbigquery(sin prefijo cuando se implementa como una función típica de 2ª gen. y no como un kit), no comoext-<instanceId>-fsexportbigquery. Busca ese nombre en el panel y los registros de Cloud Functions. - Ahora tu código se ejecuta en Firebase Local Emulator Suite como funciones normales.
Puedes establecer el valor de los parámetros que se usarán en el emulador con
.env.local. También puedes realizar pruebas de unidades en tu código con el SDK defirebase-functions-test, como se describe en Pruebas de unidades de Cloud Functions. - El aprovisionamiento ya no se basa en el tiempo de ejecución de las extensiones. Si falta la tabla de registro de cambios después de la implementación, vuelve a ejecutar la tarea de configuración de forma manual:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. La tarea es idempotente, por lo que volver a ejecutarla reconcilia el conjunto de datos, la tabla y las vistas. - Los valores de los parámetros provienen de
.enven lugar del formulario de instalación, por lo que las repeticiones defirebase deployno son interactivas una vez que se completa.env.
10. Publica tu kit de funciones en npm
Una vez que hayas validado la conversión de la extensión a la función de 2ª gen., puedes publicar una versión candidata en npm para realizar pruebas de extremo a extremo con una de las siguientes guías:
- Cómo crear y publicar paquetes públicos sin alcance
- Cómo crear y publicar paquetes públicos con alcance
Cuando tu kit se publique en npm, se podrá instalar con firebase functions:kits:install y se mostrará como el reemplazo oficial de tu extensión.
Te recomendamos que primero publiques una versión candidata para el lanzamiento. Los kits se instalan por nombre de paquete y versión, por lo que una versión preliminar te permite probar el flujo de instalación real en el registro sin exponer un paquete sin terminar a los usuarios que instalan con la etiqueta latest predeterminada.
Antes de publicar:
- Elige un nombre de paquete. Funcionan tanto los nombres con alcance como los que no lo tienen (consulta las guías anteriores). Ten en cuenta que los paquetes con alcance son privados de forma predeterminada, por lo que debes pasar
--access public. - Construir y revisar qué naves.
mainytypesapuntan a tu salida compilada (lib/en nuestro ejemplo), por lo que ese directorio debe incluirse en el archivo tar publicado. Usa.npmignoreo una lista de entidades permitidas defiles, y, luego, inspecciona el resultado connpm pack --dry-run. Un script deprepublishOnlyque ejecuta tu compilación evita que se publique un resultado desactualizado.
Adiciones de package.json que hacen que la publicación sea segura de forma predeterminada:
{
"files": ["lib", "README.md", "CHANGELOG.md"],
"publishConfig": { "access": "public", "tag": "next" },
"scripts": {
"build": "tsc -b",
"prepublishOnly": "npm run build && npm test"
}
}
El campo "publishConfig": { "tag": "next" } garantiza que un npm
publish simple nunca reemplace a latest.
Cómo cortar una versión candidata para lanzamiento:
Por ejemplo, para incrementar la versión de forma local de 0.0.2-rc.3 a 0.0.2-rc.4 (esto confirma y etiqueta en Git si package.json está en la raíz del repositorio), haz lo siguiente:
npm version prerelease --preid rc
Para publicar la versión candidata para lanzamiento y registrar @your-org/your-kit@0.0.2-rc.4 en npm con la etiqueta next, haz lo siguiente:
npm publish
El sitio web de npm puede tardar unos minutos en mostrar la versión nueva. npm view lee el registro directamente:
npm view @your-org/your-kit versions dist-tags
Una vez que completes los pasos 11 y 12 de esta guía, podrás promover el paquete a una versión estable:
npm version 0.0.2
npm publish --tag latest
npm dist-tag add @your-org/your-kit@0.0.2 next
Nota sobre npm-shrinkwrap.json: Te recomendamos que incluyas un archivo npm-shrinkwrap.json con tu paquete. La CLI advierte a los usuarios durante la instalación si no lo haces. Garantiza que los usuarios utilicen las dependencias exactas con las que realizaste las pruebas y ayuda a proteger contra los ataques a la cadena de suministro. Sin embargo, se aplica un ajuste de envoltura de forma literal en los proyectos de los usuarios, incluso durante la compilación de Cloud Functions (npm ci), en la que las entradas solo para desarrolladores pueden fallar con EBADPLATFORM. Es posible que debas quitar las entradas "dev": true y devDependencies de la copia publicada del envoltorio.
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
El package.json del kit en el momento de su cuarta versión candidata para lanzamiento:
{
"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" }
}
Ten en cuenta que, en este caso, el kit se encuentra en un monorepo, por lo que es importante incluir repository.directory para que el vínculo del registro de npm apunte a la carpeta correcta. Su CHANGELOG.md contiene notas para la versión pendiente.
11. Prueba como kit de funciones
Una vez que hayas publicado tu kit, te recomendamos que lo pruebes con npm.
Asegúrate de usar la versión >= 15.32.0 de firebase-tools y, luego, instala el kit:
firebase functions:kits:install --package <your-package-name>@<your-prerelease-version>
Esto descarga tu paquete de npm, lo configura dentro de un nuevo directorio del código fuente para tu kit y te guía para configurar la primera instancia de manera similar al flujo de instalación de extensiones. Una vez que hayas instalado y configurado tu paquete de forma local, ejecuta una implementación para crear los recursos en tu proyecto de Google Cloud:
firebase deploy --only functions:<your-kit-instance-id>
Después de la instalación, la CLI de Firebase imprime un comando de implementación similar con el ID de instancia exacto que elegiste durante la instalación.
Vuelve a validar el kit siguiendo las instrucciones del paso 9. Prueba tu función de 2ª gen. Ahora que implementas con kits, tus funciones tienen el prefijo y el nombre kit-<instance-id>-<method-name>. Esto permite que los kits tengan varias instancias y que se implemente la misma función varias veces en un proyecto, cada una con un nombre único.
12. Prueba de reemplazo de migración
Puedes configurar una instancia de extensión en funcionamiento y, luego, usar la guía de migración para usuarios (con firebase ext:migrate --package o los comandos de la CLI de los kits de funciones) para terminar de probar tu kit de funciones como reemplazo de migración.
13. Notifica a los usuarios y a Google sobre el reemplazo oficial de tu extensión
Una vez que el reemplazo del kit de funciones esté listo y disponible como un paquete npm al que los usuarios deban migrar, infórmales a tus usuarios y a Google sobre este reemplazo oficial. Actualiza el archivo README.md en el repositorio de GitHub que aloja tu extensión con la siguiente información:
<!-- 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 analiza los archivos README de extensiones conocidos para detectar comentarios como <!--
FIREBASE_EXTENSION_REPLACEMENT: extension="firebase/firestore-bigquery-export"
package="@firebase-function-kits/firestore-bigquery-export" --> y los usa para completar nuestro registro oficial de reemplazos almacenado en el repositorio de firebase-tools como replacements.json.
También puedes consultar replacements.json para ver qué README.md se analizarán para tu extensión. La lista oficial de reemplazos se actualiza semanalmente.
Ejemplo sobre el que se trabajó: Transmisión de Cloud Firestore a BigQuery
La extensión firestore-bigquery-export
README.md
contiene lo siguiente:
<!-- 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.