Przygotowywanie rozszerzeń w Firebase do migracji do Cloud Functions

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

Jest to zalecana ścieżka migracji. Firebase będzie prowadzić listę rozszerzeń z oficjalnymi odpowiednikami w npm. Z tego przewodnika dowiesz się, jak utworzyć własne rozszerzenie.

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

Zarejestruj się, aby uzyskać więcej informacji i pomocy dotyczącej migracji rozszerzeń

Jeśli masz pytania dotyczące migracji z Firebase Extensions, możesz skontaktować się z nami 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 pakowania, 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. Otrzymasz e-maila z prośbą o dołączenie. Musisz odpowiedzieć na tego e-maila, a nie kliknąć przycisku „Dołącz do tej grupy”.

Zanim zaczniesz

Aby przeprowadzić tę migrację, użyjesz tych funkcji Cloud Functions:

  • Konfiguracja z parametrami. 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 funkcji. Podczas wdrażania interfejs Firebase CLI przyznaje zadeklarowane role zarządzanemu kontu usługi środowiska wykonawczego i włącza zadeklarowane interfejsy API w Twoim imieniu.

  • 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ę podczas instalacji i aktualizacji za pomocą haków cyklu życia afterFirstDeploy(...) i afterRedeploy(...). Zastępują one lifecycleEvents zadeklarowane w extension.yaml.

Sprawdź rozszerzenie

Zacznij od sprawdzenia rozszerzenia: pełnej listy wszystkich elementów, które rozszerzenie deklaruje, dostarcza i dokumentuje. Dzięki temu każde działanie będzie miało zdefiniowane miejsce docelowe w funkcji 2 generacji i nic nie zostanie utracone podczas migracji.

Sprawdź każdy z tych elementów i zanotuj, co znajdziesz:

  • extension.yaml, który deklaruje parametry, funkcje, zdarzenia, role uprawnień, wymagane interfejsy API, obiekty tajne i haki cyklu życia.

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

  • README.md, PREINSTALL.md i POSTINSTALL.md, które zawierają instrukcje konfiguracji, ostrzeżenia i informacje o rozliczeniach.

  • scripts/, który zawiera narzędzia do importu, uzupełniania, uprawnień, naprawy lub migracji oraz inne narzędzia dostarczane razem z rozszerzeniem.

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

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

  • Przekształć obiekty tajne w obiekty tajne Cloud Functions (sekcja 6).

  • Przekształć role uprawnień w deklaracje requiresRole(...) (sekcja 8).

  • Przekształć wymagane interfejsy API Google w deklaracje requiresAPI(...) (sekcja 8).

  • Przekształć haki instalacji i aktualizacji w deklaracje afterFirstDeploy(...) i afterRedeploy(...) (sekcja 8).

Przykład: Stream Firestore to BigQuery

Odczytanie firestore-bigquery-export/extension.yaml i functions/ daje ten spis:

W extension.yaml Liczba / wartość Gdzie się znajduje
params 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) Cloud Functions parametry (sekcja 5)
apis bigquery.googleapis.com requiresAPI(...) (sekcja 7)
roles bigquery.dataEditor, datastore.user, bigquery.user requiresRole(...) (sekcja 7)
resources 1 wyzwalacz zdarzeń (fsexportbigquery) + funkcje kolejki zadań (initBigQuerySync, setupBigQuerySync) Wyeksportowane funkcje pakietu (sekcja 3)
lifecycleEvents onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy / afterRedeploy (sekcja 9)
scripts/ import/ (backfill), gen-schema-view/ Zachowane jako skrypty (poza zakresem)

Rozszerzenie nie deklaruje żadnych parametrów typu secret, więc w sekcji 6 tego przewodnika nie ma nic do przeniesienia. Wyzwalacz zdarzeń jest już 2 generacji, a tylko funkcje kolejki zadań są nadal 1 generacji (istotne w sekcji 3).

Aktualizowanie pliku package.json

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

Minimalne wersje pakietu SDK. Zadeklaruj firebase-functions >= 7.3 i firebase-admin >= 14.2.0 jako zależności. Zadeklaruj też swoją wersję firebase-functions jako zależność równorzędną.

{
  "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.3.0"
  },
  "dependencies": {
    "firebase-functions": "^7.3.0",
    "firebase-admin": "^14.2.0"
  }
}

Oprócz zwykłej zależności zadeklaruj firebase-functions jako zależność równorzędną, aby projekt Cloud Functions użytkowników miał tę samą wersję pakietu SDK, w której napisano Twoją bibliotekę.

Przykład: Stream Firestore to BigQuery

Przed. Plik functions/package.json rozszerzenia jest prywatny, zawiera nazwę identyfikatora 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 w zakresie, mapa eksportów i firebase-functions przeniesione do peerDependencies:

{
  "name": "@firebase/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.3.0" },
  "dependencies": {
      "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
      "firebase-admin": "^14.2.0",
      "firebase-functions": "^7.3.0"
    }
}

Uaktualnianie funkcji z 1 generacji do 2 generacji

Jeśli Twoje rozszerzenie nadal eksportuje funkcje 1 generacji, przekonwertuj każdą funkcję na jej odpowiednik 2 generacji. Importuj z modułów firebase-functions/... i przekazuj ustawienia środowiska wykonawczego w opcjach funkcji.

Możesz zminimalizować nakład pracy związany z przepisywaniem kodu dzięki destrukcji zdarzeń z poprawkami w 2 generacji i uniknąć przepisywania logiki funkcji ponieważ pakiet SDK 2 generacji udostępnia teraz parametry V1 jako pola w obiekcie zdarzenia. Dzięki temu możesz używać parametrów zdestrukturyzowanych lub nazwanych i zachować logikę biznesową bez zmian.

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 Functions porównaniu wersji Cloud Functions.

Konwertowanie parametrów i obiektów tajnych rozszerzenia

Konwertowanie parametrów

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

Konwertuj bezpośrednie odczyty środowiska:

const collectionPath = process.env.COLLECTION_PATH;

na Cloud Functions parametry:

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

Aby odczytać ciąg znaków wewnątrz obsługi, użyj collectionPath.value(). Użyj collectionPath bezpośrednio tam, gdzie oczekiwany jest obiekt zastępczy, np. ścieżka wyzwalacza funkcji .

Interfejs Firebase CLI wykrywa parametry i odczytuje ich wartości z .env, .env.projectId, lub wyświetla użytkownikom prośbę o podanie wartości 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 w ogóle. Migracja rozszerzenia automatycznie zachowa istniejącą wartość parametru użytkownika, ale tylko wtedy, gdy nazwy pozostaną bez zmian.

Przykład: Stream Firestore to BigQuery Przed. Parametr zadeklarowany w extension.yaml, odczytywany jako surowa 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 defineString; interfejs CLI wykrywa go 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, analagous to "required: true" in extensions.yaml
  input: { text: { nonEmpty: true} }
}),

Nazwa parametru nie została zmieniona, więc istniejący plik .env nadal działa.

Konwertowanie obiektów tajnych

W extension.yaml deklarujesz obiekty tajne za pomocą type: secret. Środowisko wykonawcze rozszerzeń przechowuje je i wiąże, dzięki czemu kod rozszerzenia może bezpośrednio odczytywać process.env.PARAM_NAME. W typowej bazie kodu Cloud Functions deklarujesz i wiżążesz każdy obiekt tajny osobno:

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

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

Gdy rozszerzenie zostanie przeniesione do pakietu/zestawu npm, odwołania do obiektów tajnych będą zarządzane w pliku .env użytkownika. Ważne jest, aby nie zmieniać nazw obiektów tajnych zadeklarowanych w kodzie w ogóle. Podczas migracji obiekty tajne użytkownika zostaną odpowiednio przeniesione.

Przykład: Trigger Email From Cloud Firestore

Przed. MAIL_COLLECTION i SMTP_PASSWORD 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 defineString i jeden defineSecret; interfejs CLI wykrywa oba i odczytuje z .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();
    // ...
  }
);

Migrowanie wewnętrznych wywołań kolejki zadań

Niektóre rozszerzenia dodają zadania do własnych kolejek zadań z poziomu kodu funkcji za pomocą Firebase Admin SDK. Różni się to od odbierania wysłanego zadania (omówionego w sekcjach Uaktualnianie funkcji i Konwertowanie haków cyklu życia). W tym przypadku Twój kod jest producentem wywołującym queue.enqueue(...).

W poprzednich wersjach Admin SDK rozszerzenia musiały przekazywać własny identyfikator instancji rozszerzenia jako drugi parametr, aby kierować do funkcji kolejki zadań w tym samym rozszerzeniu. Od wersji `firebase-admin` 14.2.0 nie jest to wymagane ani zalecane. Interfejs Task Queue API będzie teraz domyślnie kierować do kolejek zadań w tym samym kontekście (np. rozszerzeniu). 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.

Wszystko inne dotyczące wywołania enqueue – ścieżka zasobu locations/region/functions/name, ładunek zadania i logika ponawiania – pozostaje bez zmian.

Więcej informacji o dodawaniu funkcji do kolejki za pomocą Cloud Tasks znajdziesz w artykule /docs/functions/task-functions.

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";
import { region } from "firebase-functions/params";

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

Jeśli wywołanie enqueue kieruje do bazy kodu z prefiksem, wykryta nazwa funkcji też ma prefiks (np. orders-syncBigQuery).

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

Przenieś wymagania rozszerzenia dotyczące uprawnień i interfejsów 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");

Dzięki deklaratywnemu zabezpieczeniu interfejs Firebase CLI 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 ostateczny interfejs API obsługuje węższy model.

Przykład: Stream Firestore to BigQuery

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

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/biguqery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");

Konwertowanie haków cyklu życia

Jeśli Twoje 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 spowodują one błąd. Stan cyklu życia jest teraz określany przez afterFirstDeploy i afterRedeploy, gdzie to śledzenie 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 cyklu życia.

W przypadku konfiguracji jednorazowej:

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

Spraw, aby działania cyklu życia były idempotentne. Użytkownicy mogą ręcznie uruchomić je ponownie, jeśli wysyłka lub wykonanie się nie powiedzie:

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

Przykład: Stream Firestore to BigQuery

Przed. lifecycleEvents w extension.yaml, sterowane przez ś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 udostępnia BigQuery podczas pierwszego wdrożenia:

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

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

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

Dokumentowanie konfiguracji dla użytkowników

  • Napisz plik README pakietu, który zawiera co najmniej te informacje:

  • Wartości .env wymagane przez pakiet.

  • Obiekty tajne wymagane przez pakiet oraz sposób przenoszenia istniejących wartości obiektów tajnych.

  • Role uprawnień zadeklarowane przez pakiet za pomocą requiresRole(...).

  • Interfejsy API Google, które pakiet włącza lub wymaga.

  • Haki cyklu życia zadeklarowane przez pakiet oraz sposób ich ręcznego ponownego uruchamiania.

  • Informacje o rozliczeniach.

  • Co się zmieniło w porównaniu z oryginalnym rozszerzeniem.

Przykład: Stream Firestore to BigQuery

Plik README pakietu zawiera konkretną tabelę „Co się zmieniło”:

Potencjalny problem Jako rozszerzenie Jako @firebase/firestore-bigquery-export
Konfiguracja Parametry rozszerzenia Parametry funkcji za pomocą .env
Uprawnienia Przyznane przez rozszerzenia requiresRole(...), zastosowane podczas wdrażania
Udostępniam Zadanie cyklu życia przez rozszerzenia Zadanie afterFirstDeploy / afterRedeploy
Nazwy funkcji ext-instanceId-fsexportbigquery fsexportbigquery (opcjonalnie z prefiksem)

Testowanie funkcji 2 generacji

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

Upewnij się, że używasz firebase-tools >= 15.24.0, i wdróż przekonwertowaną funkcję 2 generacji w projekcie testowym z odpowiednimi zasobami, aby przetestować jej działanie. Jeśli masz już skonfigurowany projekt testowy do testowania rozszerzenia, użyj tego polecenia:

firebase deploy --only functions

Po wpisaniu tego polecenia wypełnij wyświetlony kreator, podając wartości parametrów w taki sam sposób, jak w przypadku wypełniania formularza instalacji w konsoli Firebase.

Przykład: Stream Firestore to BigQuery

Sprawdzamy synchronizację Cloud Firestore z BigQuery od początku do końca:

  1. W konsoli Cloud Firestore utwórz zbiór, który został ustawiony jako COLLECTION_PATH (użytkownicy), jeśli jeszcze nie istnieje.
  2. Utwórz dokument o nazwie bigquery-mirror-test zawierający dowolne pola z dowolnymi wartościami.
  3. W konsoli BigQuery utwórz zapytanie do tabeli surowego dziennika zmian. Powinien on zawierać jeden wiersz rejestrujący utworzenie dokumentu:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
  1. Utwórz zapytanie do najnowszego widoku, który powinien zwrócić najnowsze zdarzenie zmiany dla jedynego obecnego dokumentu: bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
  1. Usuń dokument bigquery-mirror-test w Cloud Firestore. Zniknie on z najnowszego widoku, a do tabeli surowego dziennika zmian zostanie dodane 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:

  • Wyzwalacz wdraża się jako fsexportbigquery (opcjonalnie z prefiksem bazy kodu), a nie ext-&lt;instanceId&gt;-fsexportbigquery. Poszukaj tej nazwy w Cloud Functions panelu i logach.
  • Twój kod będzie teraz działać w Firebase Local Emulator Suite jako zwykłe funkcje. Wartości parametrów do użycia w emulatorze możesz ustawić za pomocą .env.local. Możesz też testować jednostkowo kod za pomocą pakietu firebase-functions-test SDK zgodnie z opisem w artykule Testowanie jednostkowe Cloud Functions
  • Udostępnianie nie jest już sterowane przez środowisko wykonawcze rozszerzeń. Jeśli po wdrożeniu brakuje tabeli dziennika zmian, ręcznie uruchom ponownie zadanie konfiguracji: firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. Zadanie jest idempotentne, więc ponowne uruchomienie powoduje uzgodnienie zbioru danych, tabeli i widoków.
  • Wartości parametrów pochodzą z .env, a nie z formularza instalacji, więc ponowne uruchomienie firebase deploy jest nieinteraktywne, gdy .env jest gotowy.