Migracja rozszerzeń w Firebase do Cloud Functions

Z tego przewodnika dowiesz się, jak przenieść rozszerzenia z wycofanego środowiska Firebase Extensions do funkcji, którą użytkownicy instalują i wdrażają w swoim środowisku Cloud Functions w przypadku bazy kodu Firebase (2 generacji).

Jest to zalecana ścieżka migracji. Firebase będzie prowadzić listę rozszerzeń z oficjalnymi odpowiednikami w npm. Ten przewodnik zawiera instrukcje tworzenia własnych rozszerzeń.

W tym przewodniku jako przykładu używamy rozszerzenia Stream Cloud Firestore to BigQuery (firestore-bigquery-export). Każda sekcja kończy się przykładem, który pokazuje, jak rozszerzenie wyglądało przed migracją, a jak wygląda po niej w pakiecie @firebase-function-kits/firestore-bigquery-export.

Zarejestruj się, aby uzyskać więcej informacji i pomoc w przenoszeniu rozszerzeń

Jeśli masz pytania dotyczące migracji z Firebase Extensions, możesz się z nami skontaktować pod adresem firebase-extensions-migrator-support-external@google.com. Będziemy też wysyłać e-maile do tej grupy, gdy będziemy aktualizować przewodnik o dodatkowe informacje na temat przygotowywania pakietów, testowania i dystrybucji funkcji 2 generacji.

Aby dołączyć do tej grupy, wyślij wiadomość na adres firebase-extensions-migrator-support-external+subscribe@google.com. W odpowiedzi otrzymasz e-maila z prośbą o potwierdzenie członkostwa. Musisz odpowiedzieć na tego e-maila, a nie klikać przycisku „Dołącz do tej grupy”.

Zanim zaczniesz

Aby przeprowadzić tę migrację zgodnie z instrukcjami, użyj tych funkcji Cloud Functions:

  • Konfiguracja sparametryzowana. Każdy parametr zadeklarowany w extension.yaml staje się zdefiniowanym parametrem w kodzie pakietu.

  • Deklaratywne role uprawnień i wymagane interfejsy API. Każda rola zadeklarowana w extension.yaml staje się wywołaniem requiresRole(...), a każdy interfejs API staje się wywołaniem requiresAPI(...) w kodzie pakietu. Podczas wdrażania interfejs Firebase CLI przyznaje zadeklarowane role zarządzanemu wykonawczemu kontu usługi i włącza w Twoim imieniu zadeklarowane interfejsy API.

  • Zdarzenia cyklu życia w przypadku baz kodu Cloud Functions. Cloud Functions Bazy kodu obsługują teraz zdarzenia cyklu życia analogiczne do Firebase Extensions. Zadeklaruj konfigurację czasu instalacji i aktualizacji za pomocą funkcji cyklu życia afterFirstDeploy(...) i afterRedeploy(...). Zastępują one lifecycleEvents zadeklarowane w extension.yaml.

Przenoszenie źródła Firebase Extensions do funkcji 2 generacji

(Opcjonalnie) Automatyczna migracja za pomocą Firebase umiejętności agenta

Możesz zautomatyzować kroki 1–8 (inwentaryzacja zasobów, wyzwalanie uaktualnień, konwersje parametrów i kluczy tajnych, deklaratywne IAM, punkty zaczepienia cyklu życia i generowanie pliku README pakietu) za pomocą oficjalnej umiejętności agenta AI extension-to-functions-codebase.

Instalowanie umiejętności

Jeśli Ty lub Twój asystent kodowania AI (Gemini w Firebase, Cursor, Claude Code, GitHub Copilot) nie macie jeszcze zainstalowanej umiejętności, uruchom to polecenie za pomocą interfejsu wiersza poleceń umiejętności:

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

Po zainstalowaniu umiejętności w projekcie asystent kodowania AI automatycznie stosuje reguły migracji i kroki transformacji. Możesz użyć tego prompta:

„Przenieś to rozszerzenie Firebase do pakietu funkcji 2 generacji, który można opublikować, zgodnie z instrukcjami w extension-to-functions-codebase umiejętności”.

1. Sprawdź rozszerzenie

Zacznij od inwentaryzacji rozszerzenia: pełnej listy wszystkich elementów, które rozszerzenie deklaruje, dostarcza i dokumentuje, aby każde działanie miało zdefiniowane miejsce docelowe w funkcji 2 generacji i nic nie zostało utracone podczas migracji.

Sprawdź te kwestie i zanotuj swoje spostrzeżenia:

  • extension.yaml, który deklaruje parametry, funkcje, zdarzenia, role IAM, wymagane interfejsy API, tajne klucze i punkty zaczepienia cyklu życia.

  • functions/, który zawiera kod funkcji, zależności, konfigurację kompilacji, aktywatory i funkcje kolejki zadań.

  • README.md, PREINSTALL.md i POSTINSTALL.md, które zawierają instrukcje konfiguracji, ostrzeżenia i informacje o płatnościach.

  • scripts/, który zawiera narzędzia do importowania, uzupełniania, IAM, naprawy lub migracji, a także inne narzędzia dostarczane wraz z rozszerzeniem.

Następnie dla każdego elementu w extension.yaml zdecyduj, gdzie ma się on znaleźć w pakiecie npm:

  • Przekształć konfigurację użytkownika w parametry Cloud Functions (krok 4).

  • Przekształć obiekty tajne w Cloud Functions obiekty tajne (krok 4).

  • Przekształć role IAM w deklaracje requiresRole(...) (krok 6).

  • Przekształć wymagane interfejsy API Google w deklaracje requiresAPI(...), jeśli jest to odpowiednie (krok 6).

  • Przekształć wywołania instalacji i aktualizacji w deklaracje afterFirstDeploy(...) i afterRedeploy(...) (krok 7).

  • Przekonwertuj identyfikatory instancji z EXT_INSTANCE_ID na FIREBASE_KIT_INSTANCE_ID (krok 4).

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

Odczytanie produktów firestore-bigquery-export/extension.yaml i functions/ daje te zasoby reklamowe:

W extension.yaml Liczba / wartość Gdzie trafiają
params 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) Cloud Functions params (Krok 4)
apis bigquery.googleapis.com requiresAPI(...) (Krok 6)
roles bigquery.dataEditor, datastore.user, bigquery.user requiresRole(...) (Krok 6)
resources 1 wyzwalacz zdarzenia (fsexportbigquery) + funkcje kolejki zadań (initBigQuerySync, setupBigQuerySync) Funkcje wyeksportowanego pakietu (krok 3)
lifecycleEvents onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy / afterRedeploy (Krok 7)
Identyfikator instancji Nie używane (brak odczytów EXT_INSTANCE_ID) Brak elementów do przeniesienia
scripts/ import/ (reklamy zapasowe), gen-schema-view/ zachowane jako skrypty (nie są tu brane pod uwagę),

Analiza. Rozszerzenie nie deklaruje żadnych parametrów type: secret, więc w kroku 4 nie ma niczego do migracji w przypadku obiektów tajnych. Wywoływacz zdarzeń jest już funkcją 2 generacji, a tylko funkcje kolejki zadań są nadal funkcjami 1 generacji (ma to znaczenie w kroku 3).

2. Aktualizowanie pliku package.json

Zaktualizuj plik package.json rozszerzenia. Jeśli przenosisz jedno rozszerzenie, może to być katalog główny package.json. Jeśli przenosisz wiele rozszerzeń w jednym repozytorium, każde z nich powinno mieć własny pakiet.

Minimalne wersje pakietu SDK: zadeklaruj firebase-functions >= 7.4.0 i firebase-admin >= 14.2.0 jako zależności. Zadeklaruj swoją wersję firebase-functions jako zależność równorzędną, aby projekt Cloud Functions użytkowników zawierał tę samą wersję pakietu SDK, w której została napisana Twoja biblioteka.

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

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

Przed. functions/package.json rozszerzenia jest prywatny, zawiera identyfikator rozszerzenia i deklaruje firebase-functions jako bezpośrednią zależność:

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

Po Pakiet do opublikowania: nazwa z zakresem, mapa exports i firebase-functions przeniesione do 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. Uaktualnianie funkcji z 1 generacji do 2 generacji

Jeśli rozszerzenie nadal eksportuje funkcje 1 generacji, przekonwertuj każdy wyzwalacz na jego odpowiednik 2 generacji. Importuj z modułów firebase-functions/... i przekazuj ustawienia czasu działania w opcjach reguły.

Zapoznaj się z Cloud Functions przewodnikiem po uaktualnianiu do 2 generacji. W szczególności możesz zminimalizować wysiłek związany z przepisywaniem kodu, korzystając z destrukturyzacji zdarzeń 2 generacji, i uniknąć przepisywania logiki funkcji, ponieważ pakiet SDK 2 generacji udostępnia parametry v1 jako pola w obiekcie zdarzenia, co pozwala używać zdekonstruowanych lub nazwanych parametrów i nie zmieniać logiki biznesowej.

Przed. 1 generacja:

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

Po 2 generacja:

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

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

Pełną listę różnic między funkcjami 1 generacji i 2 generacji znajdziesz w Cloud Functionsporównaniu wersji.

Istotną różnicą między funkcjami 1 i 2 generacji jest to, że funkcje 2 generacji muszą znajdować się w tej samej lokalizacji co zasoby wywołujące. Gdy użytkownicy przechodzą z rozszerzenia korzystającego z funkcji 1 generacji na pakiet korzystający z funkcji 2 generacji, mogą musieć zmienić lokalizacje funkcji, aby spełnić to wymaganie.

Podczas migracji mogą wystąpić problemy z lokalizacjami Cloud Functions wybranymi przez użytkowników, ponieważ zachowana zostanie dotychczasowa lokalizacja. Jeśli funkcje wywoływane przez zdarzenia w rozszerzeniu korzystały z lokalizacji funkcji dostarczonych przez użytkownika, zalecamy dodanie do rozszerzenia nowego parametru, który będzie zbierać lokalizację wywołania zdarzenia i mapować ją na nową lokalizację funkcji.

Przykład: przesyłanie strumieniowe z Cloud Firestore do 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())
);

Podczas pierwszego wdrożenia pakietu użytkownicy zostaną poproszeni o podanie DATABASE_REGION, a funkcje zostaną wdrożone w odpowiednim functionRegion, co rozwiąże problemy z lokalizacją w przypadku funkcji wywoływanych przez zdarzenia 2 generacji.

4. Konwertowanie parametrów i obiektów tajnych rozszerzenia

Parametry

Każdy parametr zadeklarowany w extension.yaml staje się parametrem Cloud Functions.

Konwertowanie odczytów bezpośrednich z otoczenia:

const collectionPath = process.env.COLLECTION_PATH;

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

Użyj collectionPath.value(), aby odczytać ciąg w obsłudze, a collectionPath bezpośrednio w miejscu, w którym oczekiwany jest obiekt zastępczy, np. w ścieżce wyzwalacza funkcji.

Interfejs wiersza poleceń Firebase wykrywa parametry i odczytuje ich wartości z .env, .env.<projectId> lub wyświetla prośby do użytkowników podczas wdrażania. Zachowaj te same nazwy parametrów, aby wartości z istniejącej instalacji zostały przeniesione.

Ważne jest, aby nie zmieniać nazw parametrów zadeklarowanych w kodzie. Migracja rozszerzeń automatycznie zachowuje dotychczasowe wartości parametrów użytkownika, ale tylko wtedy, gdy nazwy pozostają bez zmian.

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

Przed. Parametr zadeklarowany w extension.yaml, odczytany jako zmienna środowiskowa w config.ts:

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

Po Jeden plik defineString. Interfejs wiersza poleceń go wykrywa i odczytuje z .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 } }
}),

Nazwa parametru nie uległa zmianie, więc istniejąca funkcja .env nadal działa.

Identyfikator instancji

Rozszerzenia odczytują identyfikator instancji z zmiennej EXT_INSTANCE_ID wstrzykiwanej przez środowisko wykonawcze rozszerzeń. Zestawy funkcji odczytują identyfikator instancji z FIREBASE_KIT_INSTANCE_ID, który interfejs Firebase CLI ustawia dla każdego zestawu instancji na klucz instancji na mapie instances w firebase.json. Interfejs CLI udostępnia go podczas wykrywania w czasie wdrażania, w emulatorze i wdrożonym funkcjom.

Identyfikator instancji nie jest parametrem, więc nie deklaruj go za pomocą znaku defineString. W rzeczywistości FIREBASE_... jest zarezerwowanym prefiksem w plikach .env, więc użytkownicy nie będą mogli go tam ustawić ani zastąpić. Wartości wstrzykiwane przez interfejs CLI nie są widoczne dla systemu parametrów. Odczytaj go bezpośrednio ze środowiska:

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

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

Zmienna zostanie ustawiona tylko wtedy, gdy pakiet zostanie wdrożony jako zestaw. Jeśli Twój kod jest też wdrażany jako samodzielna baza kodu (patrz krok 9), traktuj go jako opcjonalny lub w przypadku jego braku szybko wyświetlaj jasny komunikat o błędzie. Jeśli rozszerzenie udostępniało identyfikator instancji jako parametr widoczny dla użytkownika, musisz go usunąć, ponieważ teraz wartość jest własnością interfejsu wiersza poleceń Firebase CLI.

Przykład: usuwanie danych użytkownika

(Rozszerzenie Strumień Cloud Firestore do BigQuery nie odczytuje identyfikatora instancji, więc nie ma tam nic do migracji. Rozszerzenie Delete User Data używa go do nazywania swoich tematów Pub/Sub).

Przed. Odczytaj jako zmienną środowiskową w config.ts z prefiksem ext-, którego rozszerzenia używały w przypadku swoich zasobów:

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

Po Zwykłe process.env odczytanie FIREBASE_KIT_INSTANCE_ID użyte w przypadku 2 zwykłych parametrów, aby użytkownicy mogli zastępować nazwy tematów:

// 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`,
}),

Wartości domyślne nie mogą być puste, ponieważ powiązania wyzwalaczy są rozwiązywane w momencie wykrycia. W pliku manifestu wdrożenia zostanie zapisana pusta wartość domyślna jako nazwa tematu. Zestaw SDK chroni też przed uruchamianiem poza kontekstem zestawu SDK. Jeśli zmienna nie występuje, domyślna wartość na poziomie modułu będzie wynosić kit-undefined-discovery, więc moduł wczytywania konfiguracji zwróci błąd z wyjaśnieniem:

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

Sprawdzanie jest przeprowadzane, gdy moduł obsługi po raz pierwszy rozwiązuje swoją konfigurację, więc brakująca zmienna powoduje wyraźny błąd w czasie działania, a nie ciche powiązanie funkcji z tematami kit-undefined-*. Aby odrzucić wdrożenie, wykonaj sprawdzenie w zakresie modułu, aby było ono uruchamiane podczas wykrywania. Ponieważ interfejs CLI wyodrębnia identyfikator instancji z firebase.json, nie ma konfigurowalnego parametru INSTANCE_ID ani niczego, co trzeba by synchronizować w wielu instancjach.

Obiekty tajne

W języku extension.yaml deklarujesz wpisy tajne za pomocą funkcji type: secret. Środowisko wykonawcze rozszerzeń przechowuje i wiąże te dane, dzięki czemu kod rozszerzenia może je bezpośrednio odczytywać process.env.PARAM_NAME. W typowym kodzie Cloud Functions każdy klucz tajny jest deklarowany i powiązany w sposób jawny:

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

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

Po przeniesieniu rozszerzenia do pakietu lub zestawu npm odwołania do kluczy tajnych są zarządzane w pliku .env użytkownika końcowego. Ważne jest, aby nie zmieniać nazw tajnych zadeklarowanych w kodzie w ogóle. Podczas migracji odpowiednio przenoszone są tajne dane użytkowników.

Przykład: wyślij e-maila z Cloud Firestore

Przed. MAIL_COLLECTION i SMTP_PASSWORD są odczytywane jako surowe zmienne środowiskowe w 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,

Po Jeden plik defineString i jeden plik defineSecret; interfejs wiersza poleceń wykrywa oba pliki i odczytuje dane z pliku .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. Migracja wewnętrznych wywołań kolejki zadań

Niektóre rozszerzenia umieszczają zadania w własnych kolejkach zadań z poziomu kodu funkcji, używając Firebase Admin SDK. Różni się to od otrzymywania wysłanego zadania (omówionego w sekcjach Uaktualnianie funkcji i Konwertowanie funkcji cyklu życia). W tym przypadku kod jest producentem, który wywołuje queue.enqueue(...).

W poprzednich wersjach Admin SDK rozszerzenia musiały przekazywać własny identyfikator instancji rozszerzenia jako drugi parametr, aby kierować funkcję kolejki zadań w tym samym rozszerzeniu. Od wersji firebase-admin 14.2.0 nie jest to wymagane ani zalecane. Interfejs Task Queue API domyślnie kieruje teraz zadania do kolejek zadań w tym samym kontekście (np. rozszerzeniu lub zestawie). Zalecamy usunięcie tego parametru z kodu zarówno w przypadku rozszerzenia, jak i funkcji autonomicznych. Usunięcie tego parametru zapewnia przenośność i zgodność z przyszłymi wersjami.

Wszystkie pozostałe elementy wywołania enqueue – ścieżka zasobulocations/<region>/functions/<name>, ładunek zadania i logika ponawiania – pozostają bez zmian.

Więcej informacji o kolejkowaniu funkcji za pomocą Cloud Tasks znajdziesz w artykule Kolejkowanie funkcji za pomocą Cloud Tasks.

Przed. Rozszerzenie 1 generacji:

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

Po Rozszerzenie 2 generacji:

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

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

Jeśli wywołanie kolejki zadań jest kierowane do bazy kodu z prefiksem, wykryta nazwa funkcji również będzie miała prefiks (np. orders-syncBigQuery). Więcej informacji znajdziesz w sekcjach Sprawdzanie i instalowanie instancji zestawu funkcji zastępujących i Testowanie jako zestaw funkcji.

6. Deklarowanie wymaganych interfejsów API i ról uprawnień

Przenieś wymagania rozszerzenia dotyczące IAM i interfejsu API z extension.yaml do kodu:

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

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

W przypadku deklaratywnego zabezpieczenia interfejs FirebaseCLI tworzy lub aktualizuje zarządzane konto usługi środowiska wykonawczego dla bazy kodu i przyznaje mu sumę wszystkich zadeklarowanych ról. Poinformuj użytkowników, że wszystkie funkcje w bazie kodu działają z tymi rolami, chyba że końcowy interfejs API obsługuje węższy model.

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

Przed. Zadeklarowane w extension.yaml; środowisko wykonawcze rozszerzeń włączyło interfejs API i przyznało role kontu zarządzanemu:

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

Po Zadeklarowane w kodzie za pomocą requiresAPI i 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");

Jeśli Twoje rozszerzenie publikuje zdarzenia Eventarc, musisz również skonfigurować odpowiednie wymagane role i interfejsy API do publikowania zdarzeń. Wcześniej było to obsługiwane przez rozszerzenia bez konieczności wprowadzania zmian w extensions.yaml. Możesz to zrobić warunkowo w kodzie, aby pakiet wysyłał prośby o te uprawnienia tylko wtedy, gdy jest używany niestandardowy kanał Eventarc.

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

7. Konwertowanie ciekawostek dotyczących cyklu życia

Jeśli rozszerzenie wywołuje getExtensions().runtime() (np. setProcessingState lub setFatalError), usuń te wywołania, ponieważ w przypadku wywołania z normalnie wdrożonej funkcji 2 generacji powodują one błąd. Stan cyklu życia jest teraz określany przez afterFirstDeploy i afterRedeploy, a śledzenie tego stanu nie jest używane.

Firebase Extensions może uruchamiać konfigurację, gdy użytkownik instaluje, aktualizuje lub ponownie konfiguruje rozszerzenie. W pakiecie npm zadeklaruj w kodzie równoważne działania związane z cyklem życia.

Aby skonfigurować urządzenie po raz pierwszy:

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

W przypadku aktualizacji konfiguracji lub kodu:

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

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

Zadbaj o to, aby działania związane z cyklem życia były idempotentne. Jeśli wysyłanie lub wykonywanie się nie powiedzie, użytkownicy mogą ręcznie ponownie uruchomić te skrypty:

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

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

Przed. lifecycleEvents w extension.yaml, czynnik kosztowy: środowisko wykonawcze rozszerzeń:

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

Po Zadeklarowane w kodzie; zadanie zapewnia BigQuery przy pierwszym wdrożeniu:

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

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

Aprowizacja jest idempotentna, więc ponowne uruchomienie powoduje uzgodnienie zbioru danych, tabeli i widoków. Użytkownicy mogą ponownie uruchomić go ręcznie za pomocą ikony firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.

8. Konfigurowanie dokumentów dla użytkowników

Napisz README pakietu, który zawiera co najmniej:

  • Wartości .env wymagane przez pakiet.
  • Obiekty tajne wymagane przez pakiet i sposób migracji istniejących wartości obiektów tajnych.
  • Role uprawnień, które pakiet deklaruje za pomocą requiresRole(...).
  • Interfejsy API Google, które pakiet włącza lub których wymaga.
  • Punkty zaczepienia cyklu życia zadeklarowane przez pakiet i sposób ich ponownego uruchomienia ręcznie.
  • Uwagi dotyczące płatności.
  • Co się zmieniło w porównaniu z oryginalnym rozszerzeniem.
  • Jak pakiet uzyskuje identyfikator instancji (FIREBASE_KIT_INSTANCE_ID ustawiany przez interfejs wiersza poleceń) i że wszystkie funkcje instancji są wdrażane z prefiksem kit-<instanceId>-.

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

Pakiet README zawiera konkretną tabelę „co się zmieniło”:

Potencjalny problem Jako rozszerzenie Jako @firebase-function-kits/firestore-bigquery-export
Konfiguracja Parametry rozszerzenia Cloud Functions params przez .env
Uprawnienia Przyznane przez rozszerzenia requiresRole(...), zastosowano podczas wdrażania
Udostępniam Zadanie cyklu życia według rozszerzeń afterFirstDeploy / afterRedeploy zadanie
Nazwy funkcji ext-<instanceId>-fsexportbigquery fsexportbigquery (opcjonalnie z prefiksem)
Identyfikator instancji EXT_INSTANCE_ID wstrzykiwane przez rozszerzenia FIREBASE_KIT_INSTANCE_ID, ustawiony przez interfejs wiersza poleceń na firebase.json

9. Testowanie funkcji 2 generacji

Powinna się teraz pojawić funkcja 2 generacji, która po wdrożeniu będzie działać identycznie jak nowa instalacja rozszerzenia. Następnym krokiem jest sprawdzenie i naprawienie wszelkich problemów, które mogły się pojawić po drodze.

Aby wywołać funkcję setGlobalOptions w celu ustawienia opcji globalnych, takich jak domyślny region lub CPU, musisz to zrobić tylko wtedy, gdy wdrażasz pakiet jako samodzielną funkcję 2 generacji. Gdy zestaw zostanie zainstalowany jako pakiet npm, użytkownicy wywołują w kodzie opakowującym funkcję setGlobalOptions, aby skonfigurować te parametry. Jeśli zrobią to 2 razy, otrzymają ostrzeżenia. Możesz zabezpieczyć to wywołanie, sprawdzając zmienną środowiskową FIREBASE_KIT_INSTANCE_ID:

import { setGlobalOptions } from "firebase-functions";

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

Upewnij się, że używasz firebase-tools >= 15.32.0, i wdroż przekonwertowaną funkcję 2 generacji w projekcie testowym z odpowiednimi zasobami, aby przetestować jej działanie. Jeśli masz już projekt testowy skonfigurowany na potrzeby testowania rozszerzenia, uruchom to polecenie:

firebase deploy --only functions

Wypełnij kreator, podając wartości parametrów w taki sam sposób, jak w przypadku formularza instalacji w konsoli Firebase dla rozszerzenia.

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

Sprawdzamy synchronizację Cloud Firestore z BigQuery na całej długości:

  1. Na stronie Cloud Firestore w Firebase konsoli utwórz kolekcję, którą ustawisz jako COLLECTION_PATH (users), jeśli jeszcze nie istnieje.
  2. Utwórz dokument o nazwie bigquery-mirror-test zawierający dowolne pola z dowolnymi wartościami.
  3. Na stronie BigQuery w konsoli Google Cloud wyślij zapytanie do tabeli raw changelog. Powinien zawierać jeden wiersz rejestrujący utworzenie dokumentu:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
    
  4. Wyślij zapytanie o najnowszą wersję, która powinna zwrócić najnowsze zdarzenie zmiany w jedynym obecnym dokumencie (bigquery-mirror-test):

    SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
    
  5. Usuń dokument bigquery-mirror-test w Cloud Firestore. Znika z widoku najnowszych zmian, a do tabeli z surowym dziennikiem zmian dodawane jest zdarzenie DELETE.

    Pełną historię pojedynczego dokumentu możesz sprawdzić za pomocą tego polecenia:

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

Różnice w porównaniu z testowaniem rozszerzenia:

  • Aktywator jest wdrażany jako fsexportbigquery (bez prefiksu, gdy jest wdrażany jako typowa funkcja 2 generacji, a nie zestaw), a nie ext-<instanceId>-fsexportbigquery. Poszukaj tej nazwy w panelu Cloud Functions i dziennikach.
  • Kod będzie teraz działać w Firebase Local Emulator Suite jako zwykła funkcja. Wartości parametrów, które mają być używane w emulatorze, możesz ustawić za pomocą .env.local. Możesz też przeprowadzić testy jednostkowe kodu za pomocą pakietu SDK firebase-functions-test zgodnie z opisem w sekcji Testy jednostkoweCloud Functions.
  • Inicjowanie nie jest już oparte na środowisku wykonawczym rozszerzeń. Jeśli po wdrożeniu brakuje tabeli dziennika zmian, ponownie uruchom ręcznie zadanie konfiguracji: firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. Zadanie jest idempotentne, więc ponowne jego uruchomienie powoduje uzgodnienie zbioru danych, tabeli i widoków.
  • Wartości parametrów pochodzą z .env, a nie z formularza instalacji, więc ponowne uruchomienia firebase deploy są nieinteraktywne po zakończeniu .env.

10. Publikowanie zestawu funkcji w npm

Po zweryfikowaniu konwersji z rozszerzenia na funkcję 2. generacji możesz opublikować wersję kandydującą do publikacji w npm, aby przeprowadzić testowanie kompleksowe, korzystając z jednego z tych przewodników:

Gdy zestaw zostanie opublikowany w npm, można go zainstalować za pomocą polecenia firebase functions:kits:install i wymienić jako oficjalny zamiennik rozszerzenia.

Zdecydowanie zalecamy najpierw opublikowanie wersji kandydującej do publikacji. Pakiety są instalowane według nazwy pakietu i wersji, więc wersja przedpremierowa umożliwia przetestowanie rzeczywistego procesu instalacji w rejestrze bez udostępniania niedokończonego pakietu użytkownikom, którzy instalują go za pomocą domyślnego tagu latest.

Przed opublikowaniem:

  1. Wybierz nazwę pakietu. Działają zarówno nazwy z zakresem, jak i bez zakresu (patrz powyższe przewodniki). Pamiętaj, że pakiety z zakresem są domyślnie prywatne, więc przekaż --access public.
  2. Buduj i sprawdzaj, jakie statki. main i types wskazują skompilowane dane wyjściowe (lib/ w naszym przykładzie), więc ten katalog musi być uwzględniony w opublikowanym pliku tar. Użyj .npmignore lub files listy dozwolonych i sprawdź wynik za pomocą npm pack --dry-run. Skrypt prepublishOnly, który uruchamia kompilację, zapobiega publikowaniu nieaktualnych danych wyjściowych.

package.json dodatki, które zapewniają bezpieczeństwo publikowania w standardzie:

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

Pole "publishConfig": { "tag": "next" } zapewnia, że zwykły znak npm publish nigdy nie zastąpi znaku latest.

Tworzenie wersji kandydującej do publikacji:

Aby na przykład zwiększyć wersję lokalnie z 0.0.2-rc.3 na 0.0.2-rc.4 (zatwierdza i taguje w Git, jeśli package.json znajduje się w katalogu głównym repozytorium):

npm version prerelease --preid rc

Aby opublikować wersję kandydującą do publikacji i zarejestrować @your-org/your-kit@0.0.2-rc.4 w npm pod tagiem next:

npm publish

Wyświetlenie nowej wersji w witrynie npm może potrwać kilka minut. npm view odczytuje rejestr bezpośrednio:

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

Po wykonaniu kroku 11 i kroku 12 tego przewodnika możesz promować pakiet do wersji stabilnej:

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

Uwaga dotycząca pliku npm-shrinkwrap.json: zdecydowanie zalecamy dołączenie do pakietu pliku npm-shrinkwrap.json. Jeśli tego nie zrobisz, interfejs CLI wyświetli ostrzeżenie podczas instalacji. Dzięki temu użytkownicy korzystają z dokładnie tych zależności, które zostały przez Ciebie przetestowane, i pomaga chronić przed atakami na łańcuch dostaw. Jednak licencja shrinkwrap jest stosowana dosłownie w projektach użytkowników, w tym podczas Cloud Functionskompilacji (npm ci), gdzie wpisy przeznaczone tylko dla programistów mogą zakończyć się niepowodzeniem z błędem EBADPLATFORM. Konieczne może być usunięcie wpisów "dev": true i devDependencies z opublikowanej wersji shrinkwrap.

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

package.json zestawu w momencie jego czwartej wersji kandydującej do publikacji:

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

Pamiętaj, że w tym przypadku zestaw znajduje się w repozytorium monorepo, więc ważne jest, aby w linku do rejestru npm uwzględnić repository.directory, aby wskazywał on prawidłowy folder. W sekcji CHANGELOG.md znajdują się informacje o oczekującej wersji.

11. Zestaw do testowania funkcji

Po opublikowaniu zestawu zalecamy przetestowanie go za pomocą npm.

Sprawdź, czy używasz wersji firebase-tools>= 15.32.0, i zainstaluj zestaw:

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

Spowoduje to pobranie pakietu z npm, skonfigurowanie go w nowym katalogu źródłowym zestawu i przeprowadzenie Cię przez proces konfigurowania pierwszej instancji podobny do procesu instalacji rozszerzeń. Po zainstalowaniu i skonfigurowaniu pakietu lokalnie uruchom wdrożenie, aby utworzyć zasoby w projekcie Google Cloud:

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

Po instalacji interfejs Firebase CLI wyświetli podobne polecenie wdrażania z dokładnym identyfikatorem instancji wybranym podczas instalacji.

Ponownie zweryfikuj zestaw, postępując zgodnie z instrukcjami w kroku 9. Przetestuj funkcję 2 generacji. Teraz, gdy wdrażasz funkcje za pomocą pakietów, mają one prefiks i nazwę kit-<instance-id>-<method-name>. Dzięki temu zestawy mogą mieć wiele instancji, co umożliwia wielokrotne wdrażanie tej samej funkcji w projekcie, przy czym każda z nich ma unikalną nazwę.

12. Testowanie zamiennika migracji

Możesz skonfigurować działającą instancję rozszerzenia, a potem skorzystać z przewodnika migracji użytkowników (używając firebase ext:migrate --package lub poleceń interfejsu wiersza poleceń zestawów funkcji), aby dokończyć testowanie zestawu funkcji jako zamiennika migracji.

13. Powiadamianie użytkowników i Google o oficjalnym zastąpieniu rozszerzenia

Gdy zestaw funkcji zastępczych będzie gotowy i dostępny jako pakiet npm, do którego użytkownicy powinni przejść, poinformuj o tym zarówno użytkowników, jak i Google. Zaktualizuj plik README.md w repozytorium GitHub, w którym znajduje się Twoje rozszerzenie, podając te informacje:

<!-- 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 skanuje pliki README znanych rozszerzeń pod kątem komentarzy takich jak <!-- FIREBASE_EXTENSION_REPLACEMENT: extension="firebase/firestore-bigquery-export" package="@firebase-function-kits/firestore-bigquery-export" --> i wykorzystuje te informacje do wypełniania oficjalnego rejestru zamienników przechowywanego w repozytorium firebase-tools jako replacements.json. Możesz też kliknąć replacements.json, aby sprawdzić, które README.md będą skanowane w przypadku Twojego rozszerzenia. Oficjalna lista zamienników jest aktualizowana co tydzień.

Przykład: przesyłanie strumieniowe z Cloud Firestore do BigQuery

Rozszerzenie firestore-bigquery-exportREADME.md zawiera:

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