Migrar as Extensões do Firebase para o Cloud Functions

Neste guia, mostramos como migrar suas extensões do ambiente Firebase Extensions descontinuado para uma função que seus usuários instalam e implantam no próprio Cloud Functions para a base de código Firebase (2ª geração).

Esse é o caminho de migração recomendado. O Firebase vai manter uma lista de extensões com equivalentes oficiais do npm. Este guia mostra como criar as suas.

Neste guia, a extensão Transmitir Cloud Firestore para BigQuery (firestore-bigquery-export) é usada como exemplo. Cada seção termina com um Exemplo prático que mostra como era a extensão antes e depois da migração, como o pacote @firebase-function-kits/firestore-bigquery-export.

Inscreva-se para receber mais informações e ajuda na migração de extensões

Se você tiver dúvidas sobre como migrar do Firebase Extensions, entre em contato pelo e-mail firebase-extensions-migrator-support-external@google.com. Também vamos enviar um e-mail para esse grupo quando atualizarmos o guia com mais informações sobre como empacotar, testar e distribuir suas funções de 2ª geração.

Para participar desse grupo, envie uma mensagem para firebase-extensions-migrator-support-external+subscribe@google.com, que vai responder com um e-mail de solicitação de participação. Você precisa responder a esse e-mail, não clicar no botão "Participar deste grupo".

Antes de começar

Para concluir essa migração conforme descrito, você vai usar os seguintes recursos do Cloud Functions:

  • Configuração parametrizada. Cada parâmetro declarado em extension.yaml se torna um parâmetro definido no código do pacote.

  • Papéis declarativos do IAM e APIs necessárias. Cada função declarada em extension.yaml se torna uma chamada requiresRole(...), e cada API se torna uma chamada requiresAPI(...) no código do pacote. No momento da implantação, a CLI Firebase concede os papéis declarados a uma conta de serviço de ambiente de execução gerenciado e ativa as APIs declaradas em seu nome.

  • Eventos de ciclo de vida para bases de código Cloud Functions. As bases de código do Cloud Functions agora são compatíveis com eventos de ciclo de vida análogos ao Firebase Extensions. Declare a configuração de instalação e atualização com os hooks de ciclo de vida afterFirstDeploy(...) e afterRedeploy(...). Eles substituem o lifecycleEvents declarado em extension.yaml.

Migrar a origem Firebase Extensions para uma função de 2ª geração

(Opcional) Migração automatizada com a habilidade do agente Firebase

É possível automatizar as etapas de 1 a 8 (inventário de recursos, acionamento de upgrades, conversões de parâmetros e secrets, IAM declarativo, hooks de ciclo de vida e geração do README do pacote) usando a habilidade oficial do agente de IA extension-to-functions-codebase.

Instalar a habilidade

Se você ou seu assistente de programação de IA (Gemini em Firebase, Cursor, Claude Code, GitHub Copilot) ainda não instalou a habilidade, execute o comando a seguir usando a CLI de habilidades:

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

Depois que a habilidade for instalada no seu projeto, o assistente de programação de IA vai seguir automaticamente as regras de migração e as etapas de transformação. Você pode usar o seguinte comando:

"Migre esta extensão do Firebase para um pacote publicável do Function Kit de 2ª geração seguindo as instruções na habilidade extension-to-functions-codebase."

1. Inventariar a extensão

Comece fazendo um inventário da extensão: uma lista completa de tudo que ela declara, envia e documenta. Assim, cada comportamento tem um destino definido na função de 2ª geração, e nada é perdido na migração.

Revise cada um dos itens a seguir e anote o que encontrar:

  • extension.yaml, que declara seus parâmetros, funções, eventos, papéis do IAM, APIs obrigatórias, secrets e hooks de ciclo de vida.

  • functions/, que contém o código da função, dependências, configuração de build, acionadores e funções de fila de tarefas.

  • README.md, PREINSTALL.md e POSTINSTALL.md, que contêm etapas de configuração, avisos e observações de faturamento.

  • scripts/, que contém utilitários de importação, backfill, IAM, reparo ou migração e outras ferramentas enviadas com a extensão.

Em seguida, para cada item em extension.yaml, decida onde ele fica no pacote npm:

  • Converta a configuração do usuário em parâmetros Cloud Functions (Etapa 4).

  • Converta os secrets em secrets do Cloud Functions (Etapa 4).

  • Converter papéis do IAM em declarações requiresRole(...) (Etapa 6).

  • Converta as APIs do Google necessárias em declarações requiresAPI(...) quando apropriado (Etapa 6).

  • Converta os hooks de instalação e atualização em declarações afterFirstDeploy(...) e afterRedeploy(...) (Etapa 7).

  • Converta os IDs de instância de EXT_INSTANCE_ID para FIREBASE_KIT_INSTANCE_ID (etapa 4).

Exemplo prático:transmissão de Cloud Firestore para BigQuery

A leitura de firestore-bigquery-export/extension.yaml e functions/ gera este inventário:

Em extension.yaml Contagem / valor Para onde ele vai
params 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) Cloud Functions params (Etapa 4)
apis bigquery.googleapis.com requiresAPI(...) (Etapa 6)
roles bigquery.dataEditor, datastore.user, bigquery.user requiresRole(...) (Etapa 6)
resources 1 gatilho de evento (fsexportbigquery) + funções de fila de tarefas (initBigQuerySync, setupBigQuerySync) Funções de pacote exportadas (etapa 3)
lifecycleEvents onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy / afterRedeploy (Etapa 7)
ID da instância Não usado (sem leituras de EXT_INSTANCE_ID) Nada para migrar
scripts/ import/ (preenchimento), gen-schema-view/ Mantidos como scripts (fora do escopo aqui)

Análise. A extensão não declara parâmetros type: secret. Portanto, não há nada para migrar para secrets na Etapa 4. O acionador de eventos já é de 2ª geração. Somente as funções de fila de tarefas ainda são de 1ª geração (relevante na Etapa 3).

2. Atualizar package.json

Atualize o arquivo package.json da extensão. Se você estiver migrando uma extensão, esse pode ser o package.json raiz. Se você estiver migrando muitas extensões em um repositório, dê a cada uma delas um pacote próprio.

Versões mínimas do SDK:declare firebase-functions >= 7.4.0 e firebase-admin >= 14.2.0 como dependências. Declare sua versão de firebase-functions como uma dependência de mesmo nível para que o projeto Cloud Functions dos usuários tenha a mesma versão do SDK com que sua biblioteca foi escrita.

{
  "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"
  }
}

Exemplo prático:transmissão de Cloud Firestore para BigQuery

Antes. O functions/package.json da extensão é particular, nomeia o ID da extensão e declara firebase-functions como uma dependência direta:

{
  "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"
  }
}

Depois. Um pacote publicável: nome com escopo, um mapa exports e firebase-functions movido para 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. Fazer upgrade das funções da 1ª para a 2ª geração

Se a extensão ainda exportar funções de 1ª geração, converta cada acionador para o equivalente de 2ª geração. Importe dos módulos firebase-functions/... e transmita configurações de tempo de execução nas opções de acionamento.

Consulte o guia de upgrade do Cloud Functions de 2ª geração. É importante ressaltar que você pode minimizar os esforços de reescrita usando a desestruturação de eventos corrigidos de 2ª geração e evitar reescrever a lógica da função, porque o SDK de 2ª geração expõe os parâmetros da v1 como campos no objeto de evento. Isso permite usar parâmetros desestruturados/nomeados e manter a lógica de negócios inalterada.

Antes. 1ª geração:

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);
  });

Depois. 2ª geração:

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

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

Consulte a comparação de versões do Cloud Functions para uma lista completa das diferenças entre as funções de 1ª e 2ª geração.

Uma diferença significativa entre as funções de 1ª e 2ª geração é que as funções de 2ª geração precisam estar localizadas no mesmo lugar que os recursos de acionamento. Quando os usuários migram de uma extensão que usa funções de 1ª geração para um kit que usa funções de 2ª geração, eles podem precisar mudar os locais das funções para atender a esse requisito.

Qualquer local do Cloud Functions selecionado pelos usuários pode ser interrompido durante a migração porque o local atual é preservado. Se as funções acionadas por eventos da sua extensão dependiam do local das funções fornecidas pelo usuário, sugerimos adicionar um novo parâmetro à extensão para coletar o local do acionador de eventos e mapear isso para o novo local da função.

Exemplo prático:transmissão de Cloud Firestore para 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())
);

Na primeira implantação do kit, os usuários vão receber uma solicitação para informar o DATABASE_REGION, e as funções serão implantadas no functionRegion correspondente acima, corrigindo problemas de local para as funções acionadas por eventos de segunda geração.

4. Converter parâmetros e secrets de extensão

Parâmetros

Cada parâmetro declarado em extension.yaml se torna um parâmetro Cloud Functions.

Converter leituras diretas do ambiente:

const collectionPath = process.env.COLLECTION_PATH;

em parâmetros 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);
  }
);

Use collectionPath.value() para ler a string dentro de um gerenciador; use collectionPath diretamente onde um marcador de posição é esperado, como um caminho de acionador de função.

A CLI Firebase descobre seus parâmetros e lê os valores deles em .env, .env.<projectId> ou solicita aos usuários durante a implantação. Mantenha os mesmos nomes de parâmetros para que os valores de uma instalação atual sejam transferidos.

É importante não mudar os nomes de parâmetros declarados no seu código de forma alguma. A migração de extensão preserva automaticamente os valores de parâmetro do usuário final, mas apenas quando os nomes permanecem inalterados.

Exemplo prático:transmissão de Cloud Firestore para BigQuery

Antes. Um parâmetro declarado em extension.yaml, lido como uma variável de ambiente bruta em config.ts:

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

Depois. Um defineString. A CLI o descobre e lê de .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 } }
}),

O nome do parâmetro não mudou, então um .env atual continua funcionando.

ID da instância

As extensões leem o ID da instância de EXT_INSTANCE_ID, injetado pelo ambiente de execução das extensões. Os kits de função leem o ID da instância de FIREBASE_KIT_INSTANCE_ID, que a CLI Firebase define para cada instância do kit como a chave da instância no mapa instances em firebase.json. A CLI fornece isso durante a descoberta no momento da implantação, no emulador e para as funções implantadas.

O ID da instância não é um parâmetro. Portanto, não o declare com defineString. Na verdade, FIREBASE_... é um prefixo reservado em arquivos .env, então os usuários não podem definir nem substituir esse prefixo. Os valores injetados pela CLI não ficam visíveis para o sistema de parâmetros. Leia diretamente no ambiente:

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

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

A variável só será definida quando o pacote for implantado como um kit. Se o código também for implantado como uma base de código independente (consulte a Etapa 9), trate-o como opcional ou falhe rapidamente com uma mensagem clara quando ele estiver faltando. Se a extensão expôs o ID da instância como um parâmetro voltado ao usuário, remova esse parâmetro, já que a CLI Firebase agora é proprietária do valor.

Exemplo prático:excluir dados do usuário

A extensão Stream Cloud Firestore para BigQuery não lê o ID da instância. Portanto, não há nada para migrar. A extensão "Excluir dados do usuário" usa esse recurso para nomear os tópicos do Pub/Sub.

Antes. Lido como uma variável de ambiente bruta em config.ts com o prefixo ext- que as extensões usavam para os recursos:

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

Depois. Uma leitura simples de process.env de FIREBASE_KIT_INSTANCE_ID usada para dois parâmetros comuns para que os usuários possam substituir os nomes dos 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`,
}),

Os padrões não podem estar vazios porque as vinculações de gatilho são resolvidas no momento da descoberta. Um padrão vazio seria gravado no manifesto de implantação como o nome do tópico. O kit também se defende contra a execução fora do contexto de um kit. Se a variável estiver faltando, o padrão no nível do módulo será avaliado como kit-undefined-discovery. Portanto, o carregador de configuração falha com um erro explicativo em vez disso:

// ...
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."
  );
}
// ...

Essa verificação é executada quando um gerenciador resolve a configuração pela primeira vez. Assim, uma variável ausente produz um erro de tempo de execução claro em vez de funções vinculadas silenciosamente a tópicos kit-undefined-*. Para rejeitar a implantação, faça a verificação no escopo do módulo para que ela seja executada durante a descoberta. Como a CLI deriva o ID da instância de firebase.json, não há um INSTANCE_ID configurável nem nada para manter sincronizado em várias instâncias.

Secrets

Em extension.yaml, você declara secrets com type: secret. O tempo de execução das extensões armazena e vincula esses dados para que o código da extensão possa ler process.env.PARAM_NAME diretamente. Em uma base de código Cloud Functions típica, você declara e vincula cada segredo explicitamente:

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

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

Depois que a extensão é migrada para um pacote/kit npm, as referências secretas são gerenciadas no arquivo .env do usuário final. É importante não mudar os nomes secretos declarados no código de forma alguma. Durante a migração, os segredos do usuário final são migrados de acordo.

Exemplo prático:acionar e-mail de Cloud Firestore

Antes. MAIL_COLLECTION e SMTP_PASSWORD são lidas como variáveis de ambiente brutas em 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,

Depois. Um defineString e um defineSecret. A CLI descobre os dois e lê de .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. Migrar chamadas internas da fila de tarefas

Algumas extensões enfileiram o trabalho nas próprias filas de tarefas de dentro do código da função, usando Firebase Admin SDK. Isso é diferente de receber uma tarefa despachada (abordada nas seções Fazer upgrade de funções e Converter hooks de ciclo de vida). Aqui, seu código é o produtor que chama queue.enqueue(...).

As versões anteriores do Admin SDK exigiam que as extensões transmitissem o próprio ID da instância da extensão como um segundo parâmetro para segmentar uma função da fila de tarefas na mesma extensão. A partir da versão 14.2.0 do firebase-admin, isso não é necessário nem recomendado. Por padrão, a API Task Queue agora tem como destino filas de tarefas no mesmo contexto (por exemplo, uma extensão ou um kit). É seguro e recomendável remover esse parâmetro do código, tanto como uma extensão quanto como funções independentes. A remoção desse parâmetro garante portabilidade e compatibilidade futura.

Todo o restante da chamada enqueue — o caminho do recurso locations/<region>/functions/<name>, o payload da tarefa e a lógica de nova tentativa — permanece igual.

Consulte Enfileirar funções com o Cloud Tasks para mais detalhes sobre como enfileirar funções com Cloud Tasks.

Antes. Extensão da 1ª geração:

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);

Depois. Extensão de 2ª geração:

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

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

Se a chamada de enfileiramento segmentar uma base de código com prefixo, o nome da função descoberta também terá um prefixo (por exemplo, orders-syncBigQuery). Consulte Analisar e instalar a instância do kit de função de substituição e Testar como um kit de função.

6. Declarar as APIs e os papéis do IAM necessários

Mova os requisitos de IAM e API da extensão de extension.yaml para o 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");

Com a segurança declarativa, a CLI Firebase cria ou atualiza uma conta de serviço de tempo de execução gerenciado para a base de código e concede a ela a união de todos os papéis declarados. Documente para seus usuários que todas as funções no codebase são executadas com essas funções, a menos que a API final seja compatível com um modelo mais restrito.

Exemplo prático:transmissão de Cloud Firestore para BigQuery

Antes. Declarado em extension.yaml. O ambiente de execução das extensões ativou a API e concedeu os papéis a uma conta gerenciada:

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

Depois. Declarado no código com 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 a extensão publicar eventos do Eventarc, você também precisará definir as APIs e os papéis obrigatórios adequados para publicar eventos. Antes, isso era processado pelas extensões sem que fosse necessário fazer mudanças no seu extensions.yaml. Você pode fazer isso de forma condicional no seu código para que o kit só solicite essas permissões quando um canal personalizado do Eventarc for usado.

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

7. Converter hooks de ciclo de vida

Se a extensão chamar getExtensions().runtime() (por exemplo, setProcessingState ou setFatalError), exclua essas chamadas, porque elas geram um erro se forem chamadas de uma função de 2ª geração implantada normalmente. O estado do ciclo de vida agora é controlado por afterFirstDeploy e afterRedeploy, em que esse rastreamento de estado não é usado.

O Firebase Extensions pode executar a configuração quando um usuário instala, atualiza ou reconfigura uma extensão. No pacote npm, declare ações equivalentes do ciclo de vida no código.

Para uma configuração ú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 atualizações de configuração ou código:

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

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

Torne as ações de ciclo de vida idempotentes. Seus usuários talvez precisem executar esses comandos manualmente se o envio ou a execução falhar:

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

Exemplo prático:transmissão de Cloud Firestore para BigQuery

Antes. lifecycleEvents em extension.yaml, causado pelo tempo de execução das extensões:

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

Depois. Declarado no código, a tarefa provisiona BigQuery na primeira implantação:

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

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

O provisionamento é idempotente. Portanto, uma nova execução reconcilia o conjunto de dados, a tabela e as visualizações. Os usuários podem executar novamente de forma manual com firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.

8. Documentar a configuração para os usuários

Escreva um pacote README que explique, no mínimo:

  • Os valores de .env que o pacote exige.
  • Os secrets que o pacote exige e como migrar valores de secret existentes.
  • Os papéis do IAM que o pacote declara com requiresRole(...).
  • As APIs do Google que o pacote ativa ou exige.
  • Os hooks de ciclo de vida declarados pelo pacote e como executá-los novamente manualmente.
  • Observações sobre o faturamento.
  • O que mudou em comparação com a extensão original.
  • Como o pacote recebe o ID da instância (FIREBASE_KIT_INSTANCE_ID definido pela CLI) e que todas as funções da instância são implantadas com um prefixo kit-<instanceId>-.

Exemplo prático:transmissão de Cloud Firestore para BigQuery

O pacote README envia uma tabela concreta de "o que mudou":

Problema Como a extensão Como @firebase-function-kits/firestore-bigquery-export
Configuração Parâmetros de extensão Parâmetros Cloud Functions via .env
IAM Concedida por extensões requiresRole(...), aplicada na implantação
Provisionamento Tarefa de ciclo de vida por extensões afterFirstDeploy / afterRedeploy tarefa
Nomes de funções ext-<instanceId>-fsexportbigquery fsexportbigquery (com prefixo opcional)
ID da instância EXT_INSTANCE_ID injetado por extensões FIREBASE_KIT_INSTANCE_ID, definido pela CLI de firebase.json

9. Testar a função de 2ª geração

Agora você tem uma função de 2ª geração que, quando implantada, se comporta de maneira idêntica a uma nova instalação da sua extensão. A próxima etapa é verificar e corrigir problemas introduzidos acidentalmente ao longo do processo.

Para chamar setGlobalOptions e definir opções globais, como uma região ou CPU padrão, faça isso apenas ao implantar seu kit como uma função independente de 2ª geração. Quando o kit é instalado como um pacote npm, os usuários chamam setGlobalOptions no código de encapsulamento para configurar esses parâmetros e recebem avisos se isso acontecer duas vezes. É possível proteger essa chamada verificando a variável de ambiente FIREBASE_KIT_INSTANCE_ID:

import { setGlobalOptions } from "firebase-functions";

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

Verifique se você está usando firebase-tools >= 15.32.0 e implante a função convertida de 2ª geração em um projeto de teste com os recursos adequados para testar o comportamento dela. Se você já tiver um projeto de teste configurado para testar sua extensão, execute o seguinte comando:

firebase deploy --only functions

Preencha o assistente resultante solicitando valores de parâmetro da mesma forma que você preencheria o formulário de instalação no console Firebase para a extensão.

Exemplo prático:transmissão de Cloud Firestore para BigQuery

Verificamos a sincronização de ponta a ponta de Cloud Firestore para BigQuery:

  1. Na página Cloud Firestore do console Firebase, crie a coleção que você definiu como COLLECTION_PATH (users) se ela ainda não existir.
  2. Crie um documento chamado bigquery-mirror-test com campos e valores.
  3. Na página BigQuery do console Google Cloud, consulte a tabela de mudanças bruta. Ele precisa conter uma única linha registrando a criação do documento:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
    
  4. Consulte a visualização mais recente, que vai retornar o evento de mudança mais recente do único documento presente (bigquery-mirror-test):

    SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
    
  5. Exclua o documento bigquery-mirror-test em Cloud Firestore. Ele desaparece da visualização mais recente, e um evento DELETE é anexado à tabela bruta do changelog.

    É possível inspecionar o histórico completo de um único documento com:

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

Diferenças em relação ao teste da extensão:

  • O gatilho é implantado como fsexportbigquery (sem prefixo ao implantar como uma função típica de 2ª geração e não um kit), não ext-<instanceId>-fsexportbigquery. Procure esse nome no painel e nos registros de Cloud Functions.
  • Agora seu código é executado no Firebase Local Emulator Suite como funções normais. É possível definir o valor dos parâmetros a serem usados no emulador com .env.local. Também é possível testar seu código usando o SDK firebase-functions-test, conforme descrito em Teste de unidade de Cloud Functions.
  • O provisionamento não é mais feito pelo tempo de execução das extensões. Se a tabela de changelog estiver faltando após a implantação, execute novamente a tarefa de configuração manualmente: firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. A tarefa é idempotente. Portanto, ao executá-la novamente, o conjunto de dados, a tabela e as visualizações são reconciliados.
  • Os valores de parâmetro vêm de .env em vez do formulário de instalação. Portanto, as novas execuções de firebase deploy não são interativas depois que .env é concluído.

10. Publicar o kit de funções no npm

Depois de validar a conversão da extensão para a função de 2ª geração, você pode publicar uma versão candidata a lançamento no npm para testes de ponta a ponta usando um dos seguintes guias:

Quando o kit é publicado no npm, ele pode ser instalado com firebase functions:kits:install e listado como o substituto oficial da sua extensão.

Recomendamos publicar primeiro uma versão candidata a lançamento. Os kits são instalados por nome e versão do pacote. Assim, uma prévia permite testar o fluxo de instalação real no registro sem expor um pacote inacabado aos usuários que instalam com a tag latest padrão.

Antes de publicar:

  1. Escolha um nome de pacote. Os nomes com e sem escopo funcionam. Consulte os guias acima. Os pacotes com escopo são particulares por padrão. Portanto, transmita --access public.
  2. Crie e verifique o que é enviado. main e types apontam para a saída compilada (lib/ no exemplo trabalhado). Portanto, esse diretório precisa ser incluído no arquivo tar publicado. Use .npmignore ou uma lista de permissões files e inspecione o resultado com npm pack --dry-run. Um script prepublishOnly que executa sua build impede a publicação de resultados desatualizados.

package.json adições que tornam a publicação segura por padrão:

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

O campo "publishConfig": { "tag": "next" } garante que um npm publish simples nunca substitua latest.

Criação de uma versão candidata a lançamento:

Por exemplo, para incrementar a versão localmente de 0.0.2-rc.3 para 0.0.2-rc.4 (isso confirma e marca no Git se package.json estiver na raiz do repositório):

npm version prerelease --preid rc

Para publicar a versão candidata a lançamento e registrar @your-org/your-kit@0.0.2-rc.4 no npm com a tag next:

npm publish

O site do npm pode levar alguns minutos para mostrar a nova versão. O npm view lê o registro diretamente:

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

Depois de concluir as etapas 11 e 12 deste guia, você poderá promover o pacote para uma versão estável:

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

Observação sobre npm-shrinkwrap.json:recomendamos incluir um arquivo npm-shrinkwrap.json com seu pacote. A CLI avisa os usuários na instalação se você não fizer isso. Ele garante que os usuários usem as dependências exatas que você testou e ajuda a proteger contra ataques à cadeia de suprimentos. No entanto, um shrinkwrap é aplicado de forma literal nos projetos dos usuários, inclusive durante o build do Cloud Functions (npm ci), em que entradas somente para desenvolvimento podem falhar com EBADPLATFORM. Talvez seja necessário remover entradas "dev": true e devDependencies da cópia publicada do shrinkwrap.

Exemplo prático:transmissão de Cloud Firestore para BigQuery

O package.json do kit no momento da quarta versão candidata a lançamento:

{
  "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" }
}

Nesse caso, o kit está em um monorepo. Por isso, é importante incluir repository.directory para que o link do registro npm aponte para a pasta correta. O CHANGELOG.md dele contém notas sobre o lançamento pendente.

11. Teste como um kit de funções

Depois de publicar o kit, recomendamos testá-lo usando o npm.

Verifique se você está usando a versão >= 15.32.0 do firebase-tools e instale o kit:

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

Isso faz o download do pacote do npm, configura-o em um novo diretório de origem para seu kit e orienta você na configuração da primeira instância, semelhante ao fluxo de instalação de extensões. Depois de instalar e configurar o pacote localmente, execute uma implantação para criar os recursos no projeto Google Cloud:

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

Após a instalação, a CLI Firebase vai imprimir um comando de implantação semelhante com o ID exato da instância escolhida durante a instalação.

Valide o kit novamente seguindo as instruções em Etapa 9. Teste a função de 2ª geração. Agora que você está implantando usando kits, suas funções são prefixadas e nomeadas kit-<instance-id>-<method-name>. Isso permite que os kits tenham várias instâncias, implantando a mesma função várias vezes em um projeto, cada uma com um nome exclusivo.

12. Teste de substituição de migração

É possível configurar uma instância de extensão funcional e usar o guia de migração para usuários (usando firebase ext:migrate --package ou os comandos da CLI dos kits de funções) para concluir o teste do kit de funções como uma substituição de migração.

13. Notificar usuários e o Google sobre a substituição oficial da extensão

Quando a substituição do kit de funções estiver pronta e disponível como um pacote npm para migração dos usuários, informe a eles e ao Google sobre essa substituição oficial. Atualize o arquivo README.md no repositório do GitHub que hospeda sua extensão com as seguintes informações:

<!-- 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.

O Google verifica os READMEs de extensões conhecidas em busca de comentários como <!-- FIREBASE_EXTENSION_REPLACEMENT: extension="firebase/firestore-bigquery-export" package="@firebase-function-kits/firestore-bigquery-export" --> e usa isso para preencher nosso registro oficial de substituições armazenado no repositório firebase-tools como replacements.json. Você também pode verificar replacements.json para saber quais README.md serão verificados na sua extensão. A lista oficial de substituições é atualizada semanalmente.

Exemplo prático:transmissão de Cloud Firestore para BigQuery

A extensão firestore-bigquery-export README.md contém:

<!-- 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.