Firebase Extensions を Cloud Functions に移行する

このガイドでは、非推奨の Firebase Extensions 環境から、ユーザーが独自の Cloud Functions でインストールしてデプロイする Firebase(第 2 世代)コードベースの関数に拡張機能を移行する方法について説明します。

これは推奨される移行パスです。Firebase は、公式の npm 相当の拡張機能のリストを保持します。このガイドでは、そのリストを作成する手順について説明します。

このガイド全体を通して、実行例として Stream Cloud Firestore to BigQuery 拡張機能(firestore-bigquery-export)を使用します。各セクションの最後には、移行前の拡張機能と、移行後の @firebase-function-kits/firestore-bigquery-export パッケージとしての拡張機能の例を示す例が記載されています。

登録して、拡張機能の移行に関する詳細情報とサポートを入手する

Firebase Extensions からの移行方法についてご不明な点がある場合は、firebase-extensions-migrator-support-external@google.com までお問い合わせください。また、第 2 世代関数のパッケージ化、テスト、配布に関する詳細情報をガイドに追加した場合は、このグループにメールでお知らせします。

このグループに参加するには、firebase-extensions-migrator-support-external+subscribe@google.com にメッセージを送信します。メンバーシップ リクエストのメールが返信されます。そのメールに返信する必要があります。[Join This Group] ボタンをクリックしないでください。

始める前に

この移行を手順どおりに進めるには、Cloud Functions の次の機能を使用します。

  • パラメータ化された構成。extension.yaml で宣言した各パラメータは、パッケージ コードで定義されたパラメータになります。

  • 宣言型 IAM ロールと必要な API。extension.yaml で宣言した各ロールは requiresRole(...) 呼び出しになり、各 API はパッケージ コードの requiresAPI(...) 呼び出しになります。デプロイ時に、Firebase CLI は宣言されたロールをマネージド ランタイム サービス アカウントに付与し、宣言された API をユーザーに代わって有効にします。

  • Cloud Functions コードベースのライフサイクル イベント。Cloud Functions コードベースで、Firebase Extensions と同様のライフサイクル イベントがサポートされるようになりました。ライフサイクル フック afterFirstDeploy(...) と afterRedeploy(...) を使用して、インストール時と更新時の設定を宣言します。これらは、extension.yaml で宣言する lifecycleEvents に代わるものです。

Firebase Extensions ソースを第 2 世代の関数に移行する

(省略可)Firebase エージェント スキルを使用した自動移行

公式の extension-to-functions-codebase AI エージェント スキルを使用すると、手順 1 ~ 8(リソースのインベントリ作成、アップグレードのトリガー、パラメータとシークレットの変換、宣言型 IAM、ライフサイクル フック、パッケージ README の生成)を自動化できます。

スキルをインストールする

自分または AI コーディング アシスタント(Firebase の Gemini、Cursor、Claude Code、GitHub Copilot)がスキルをまだインストールしていない場合は、スキル CLI を使用して次のコマンドを実行します。

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

スキルがプロジェクトにインストールされると、AI コーディング アシスタントは移行ルールと変換手順に自動的に従います。次のプロンプトを使用できます。

「extension-to-functions-codebase スキルの手順に沿って、この Firebase 拡張機能を公開可能な第 2 世代の Function Kit パッケージに移行してください。」

1. 拡張機能をインベントリに登録する

まず、拡張機能のインベントリを作成します。これは、拡張機能が宣言、出荷、ドキュメント化するすべてのものの完全なリストです。これにより、すべての動作が第 2 世代の関数で定義された宛先を持ち、移行で何も失われないようにします。

次の各項目を確認し、見つかった内容をメモします。

  • extension.yaml: パラメータ、関数、イベント、IAM ロール、必要な API、シークレット、ライフサイクル フックを宣言します。

  • functions/: 関数コード、依存関係、ビルド構成、トリガー、タスクキュー関数が含まれています。

  • README.md、PREINSTALL.md、POSTINSTALL.md。これらには、設定手順、警告、請求に関する注意事項が含まれています。

  • scripts/。インポート、バックフィル、IAM、修復、移行ユーティリティ、および拡張機能とともに提供するその他のツールが含まれます。

次に、extension.yaml の各アイテムについて、npm パッケージのどこに配置するかを決定します。

  • ユーザー構成を Cloud Functions パラメータに変換します(ステップ 4)。

  • Secret を Cloud Functions Secret に変換します(ステップ 4)。

  • IAM ロールを requiresRole(...) 宣言に変換します(ステップ 6)。

  • 必要に応じて、必要な Google API を requiresAPI(...) 宣言に変換します(ステップ 6)。

  • インストール フックと更新フックを afterFirstDeploy(...) 宣言と afterRedeploy(...) 宣言に変換します(ステップ 7)。

  • インスタンス ID を EXT_INSTANCE_ID から FIREBASE_KIT_INSTANCE_ID に変換します(ステップ 4)。

実施例: Cloud Firestore から BigQuery へのストリーミング

firestore-bigquery-export/extension.yaml と functions/ を読み取ると、次の在庫が生成されます。

extension.yaml カウント / 値 データの送信先
params 25(COLLECTION_PATH、DATASET_ID、TABLE_ID、DATASET_LOCATION、VIEW_TYPE、…) Cloud Functions パラメータ(ステップ 4)
apis bigquery.googleapis.com requiresAPI(...)(ステップ 6)
roles bigquery.dataEditor、datastore.user、bigquery.user requiresRole(...)(ステップ 6)
resources 1 つのイベント トリガー(fsexportbigquery)+ タスクキュー関数(initBigQuerySync、setupBigQuerySync) エクスポートされたパッケージ関数(ステップ 3)
lifecycleEvents onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy / afterRedeploy(ステップ 7)
インスタンス ID 使用されていない(EXT_INSTANCE_ID の読み取りがない) 移行するデータがありません
scripts/ import/(バックフィル)、gen-schema-view/ スクリプトとして保持(ここでは対象外)

分析。拡張機能は type: secret パラメータを宣言していないため、ステップ 4 のシークレットを移行する必要はありません。イベント トリガーはすでに第 2 世代です。タスクキュー関数のみが第 1 世代のままです(ステップ 3 で関連します)。

2. package.json を更新する

拡張機能の package.json ファイルを更新します。1 つの拡張機能を移行する場合は、ルート package.json にできます。1 つのリポジトリで多くの拡張機能を移行する場合は、各拡張機能に独自のパッケージを指定します。

最小 SDK バージョン: firebase-functions >= 7.4.0 と firebase-admin >= 14.2.0 を依存関係として宣言します。firebase-functions のバージョンをピア依存関係として宣言して、ユーザーの Cloud Functions プロジェクトに、ライブラリの作成に使用した SDK と同じバージョンが設定されるようにします。

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

実施例: Cloud Firestore から BigQuery へのストリーミング

変更前。拡張機能の functions/package.json は非公開で、拡張機能 ID を指定し、firebase-functions を直接依存関係として宣言します。

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

変更後。公開可能なパッケージ: スコープ名、exports マップ、firebase-functions が 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. 関数を第 1 世代から第 2 世代にアップグレードする

拡張機能が第 1 世代の関数をエクスポートしている場合は、各トリガーを第 2 世代の同等のトリガーに変換します。firebase-functions/... モジュールからインポートし、トリガー オプションでランタイム設定を渡します。

Cloud Functions 第 2 世代のアップグレード ガイドをご覧ください。特に、第 2 世代のパッチ適用済みイベントの分割代入を使用することで、書き換えの労力を最小限に抑えることができます。また、第 2 世代の SDK は v1 パラメータをイベント オブジェクトのフィールドとして公開するため、関数ロジックを書き換える必要はありません。分割代入された名前付きパラメータを使用して、ビジネスロジックを変更せずに維持できます。

変更前。第 1 世代:

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

変更後。第 2 世代:

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

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

第 1 世代と第 2 世代の関数の違いの一覧については、Cloud Functions バージョンの比較をご覧ください。

第 1 世代の関数と第 2 世代の関数の大きな違いは、第 2 世代の関数はトリガー リソースと同じ場所に配置する必要があることです。第 1 世代の関数を使用する拡張機能から第 2 世代の関数を使用するキットに移行する場合、この要件を満たすために関数のロケーションを変更する必要がある場合があります。

移行中に、ユーザーが選択した Cloud Functions のロケーションが破損する可能性があります。これは、既存のロケーションが保持されるためです。拡張機能のイベント トリガー関数がユーザー指定の関数ロケーションに依存している場合は、イベント トリガー ロケーションを収集して新しい関数ロケーションにマッピングする新しいパラメータを拡張機能に追加することをおすすめします。

実施例: Cloud Firestore から 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())
);

キットの初回デプロイ時に、ユーザーに DATABASE_REGION の入力を求めるメッセージが表示され、関数が対応する functionRegion にデプロイされます。これにより、第 2 世代のイベント トリガー関数で発生するロケーションの問題が修正されます。

4. 拡張機能のパラメータとシークレットを変換する

パラメータ

extension.yaml で宣言した各パラメータは Cloud Functions パラメータになります。

環境の直接読み取りを変換します。

const collectionPath = process.env.COLLECTION_PATH;

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

ハンドラ内の文字列を読み取るには collectionPath.value() を使用します。プレースホルダが想定される場所(関数トリガー パスなど)では、collectionPath を直接使用します。

Firebase CLI はパラメータを検出し、.env、.env.<projectId> から値を読み取ります。また、デプロイ中にユーザーにプロンプトを表示します。既存のインストールから値が引き継がれるように、パラメータ名は同じにします。

コードで宣言したパラメータ名は一切変更しないでください。拡張機能の移行では、既存のエンドユーザー パラメータ値が自動的に保持されますが、名前が変更されていない場合に限ります。

実施例: Cloud Firestore から BigQuery へのストリーミング

変更前。extension.yaml で宣言されたパラメータが、config.ts で未加工の環境変数として読み取られます。

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

変更後。1 つの defineString。CLI がそれを検出し、.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 } }
}),

パラメータ名は変更されていないため、既存の .env は引き続き機能します。

インスタンス ID

拡張機能は、拡張機能ランタイムによって挿入された EXT_INSTANCE_ID からインスタンス ID を読み取ります。関数キットは からインスタンス ID を読み取ります。Firebase CLI は、各キット インスタンスの を firebase.json の instances マップ内のインスタンスのキーに設定します。FIREBASE_KIT_INSTANCE_IDCLI は、デプロイ時の検出、エミュレータ、デプロイされた関数でこれを提供します。

インスタンス ID はパラメータではないため、defineString で宣言しないでください。実際、FIREBASE_... は .env ファイルで予約済みの接頭辞であるため、ユーザーはそこで設定したりオーバーライドしたりすることはできません。CLI が挿入する値は、params システムには表示されません。環境から直接読み取ります。

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

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

この変数は、パッケージがキットとしてデプロイされた場合にのみ設定されます。コードがスタンドアロンのコードベースとしてもデプロイされている場合(ステップ 9 を参照)、コードを省略可能として扱うか、コードがない場合は明確なメッセージでフェイル ファストします。拡張機能でインスタンス ID をユーザー向けのパラメータとして公開していた場合は、そのパラメータを削除する必要があります。これは、Firebase CLI がその値を所有するようになったためです。

動作例: ユーザーデータを削除する

(Stream Cloud Firestore から BigQuery への拡張機能はインスタンス ID を読み取らないため、移行するものはありません。Delete User Data 拡張機能は、Pub/Sub トピックの名前付けに使用します)。

変更前。ext- 接頭辞が付いた config.ts の未加工の環境変数として読み取ります。この接頭辞は、拡張機能がリソースに使用したものです。

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

変更後。ユーザーがトピック名をオーバーライドできるように、2 つの通常のパラメータに使用される FIREBASE_KIT_INSTANCE_ID のプレーンな process.env 読み取り:

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

トリガー バインディングは検出時に解決されるため、デフォルトは空でない必要があります。空のデフォルトは、トピック名としてデプロイ マニフェストに書き込まれます。また、キットのコンテキスト外で実行されることに対する防御機能も備えています。変数が欠落している場合、モジュール レベルのデフォルトは kit-undefined-discovery に評価されるため、構成ローダーは説明付きのエラーで失敗します。

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

このチェックは、ハンドラが最初に構成を解決するときに実行されるため、変数が欠落していると、kit-undefined-* トピックにサイレントにバインドされた関数ではなく、明確なランタイム エラーが生成されます。デプロイ自体を拒否するには、モジュール スコープでチェックを実行して、検出中に実行されるようにします。CLI は firebase.json からインスタンス ID を取得するため、構成可能な INSTANCE_ID はなく、複数のインスタンス間で同期を維持するものもありません。

シークレット

extension.yaml では、type: secret を使用してシークレットを宣言します。拡張機能のランタイムはこれらを保存してバインドするため、拡張機能のコードは process.env.PARAM_NAME を直接読み取ることができます。一般的な Cloud Functions コードベースでは、各シークレットを明示的に宣言してバインドします。

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

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

拡張機能が npm パッケージ/キットに移行されると、シークレット参照はエンドユーザーの .env ファイルで管理されます。コードで宣言されたシークレット名を一切変更しないことが重要です。移行中、エンドユーザー シークレットは適宜移行されます。

実施例: Cloud Firestore からメールをトリガーする

変更前。MAIL_COLLECTION と SMTP_PASSWORD は、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,

変更後。1 つの defineString と 1 つの defineSecret。CLI は両方を検出し、.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. 内部タスクキュー呼び出しを移行する

一部の拡張機能は、Firebase Admin SDK を使用して、関数コード内から独自のタスクキューに作業をキューに追加します。これは、ディスパッチされたタスクを受信することとは異なります(関数をアップグレードするとライフサイクル フックを変換するのセクションで説明しています)。ここで、コードは queue.enqueue(...) を呼び出すプロデューサーです。

以前のバージョンの Admin SDK では、同じ拡張機能内のタスクキュー関数をターゲットにするために、拡張機能が独自の拡張機能インスタンス ID を 2 番目のパラメータとして渡す必要がありました。firebase-admin 14.2.0 以降では、これは必須でも推奨でもありません。Task Queue API は、デフォルトで同じコンテキスト(拡張機能やキットなど)のタスクキューをターゲットにするようになりました。このパラメータは、拡張機能としてもスタンドアロン関数としても、コードから削除しても安全であり、削除することが推奨されます。このパラメータを削除すると、ポータビリティと上位互換性が確保されます。

エンキュー呼び出しに関するその他のすべて(locations/<region>/functions/<name> リソースパス、タスク ペイロード、再試行ロジックなど)は変更されません。

Cloud Tasks を使用して関数をキューに追加する方法の詳細については、Cloud Tasks で関数をキューに追加するをご覧ください。

変更前。第 1 世代の延長:

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

変更後。第 2 世代の拡張機能:

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

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

エンキュー呼び出しが接頭辞付きのコードベースを対象としている場合、検出された関数名にも接頭辞が付きます(例: orders-syncBigQuery)。置換関数キット インスタンスを確認してインストールすると関数キットとしてテストするをご覧ください。

6. 必要な API と IAM ロールを宣言する

拡張機能の IAM と API の要件を extension.yaml からコードに移動します。

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

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

宣言型セキュリティを使用すると、Firebase CLI はコードベースのマネージド ランタイム サービス アカウントを作成または更新し、宣言されたすべてのロールの和集合を付与します。最終的な API がより狭いモデルをサポートしていない限り、コードベース内のすべての関数がこれらのロールで実行されることをユーザー向けに文書化します。

実施例: Cloud Firestore から BigQuery へのストリーミング

変更前。extension.yaml で宣言されています。Extensions ランタイムが API を有効にして、マネージド アカウントにロールを付与しました。

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

変更後。コードで requiresAPI と 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");

拡張機能が Eventarc イベントを公開する場合は、イベントの公開に必要な適切なロールと API も設定する必要があります。これは以前、extensions.yaml を変更することなく拡張機能によって処理されていました。コードで条件付きでこれを行うことで、カスタム 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. ライフサイクル フックを変換する

拡張機能が getExtensions().runtime()(setProcessingState や setFatalError など)を呼び出している場合は、それらの呼び出しを削除します。通常デプロイされた第 2 世代関数から呼び出されると、エラーがスローされるためです。ライフサイクル状態は afterFirstDeploy と afterRedeploy によって制御されるようになり、この状態のトラッキングは使用されなくなりました。

Firebase Extensions は、ユーザーが拡張機能をインストール、更新、再構成するときにセットアップを実行できます。npm パッケージで、同等のライフサイクル アクションをコードで宣言します。

1 回限りの設定の場合:

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

構成またはコードの更新の場合:

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

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

ライフサイクル アクションをべき等にします。ディスパッチまたは実行に失敗した場合は、ユーザーが手動で再実行する必要があることがあります。

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

実施例: Cloud Firestore から BigQuery へのストリーミング

変更前。extension.yaml の lifecycleEvents(拡張機能のランタイムによるもの):

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

変更後。コードで宣言されます。タスクは最初のデプロイで BigQuery をプロビジョニングします。

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

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

プロビジョニングはべき等であるため、再実行するとデータセット、テーブル、ビューが調整されます。ユーザーは firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME で手動で再実行できます。

8. ユーザー向けのドキュメント設定

少なくとも次の内容を説明するパッケージ README を記述します。

  • パッケージに必要な .env 値。
  • パッケージに必要なシークレットと、既存のシークレット値を移行する方法。
  • パッケージが requiresRole(...) で宣言する IAM ロール。
  • パッケージが有効にするか、必要とする Google API。
  • パッケージが宣言するライフサイクル フックと、それらを手動で再実行する方法。
  • 請求に関するメモ。
  • 元の拡張機能と比較して変更された点。
  • パッケージがインスタンス ID(CLI によって設定された FIREBASE_KIT_INSTANCE_ID)を取得する方法と、インスタンスのすべての関数が kit-<instanceId>- プレフィックスでデプロイされる方法。

実施例: Cloud Firestore から BigQuery へのストリーミング

パッケージ README には、具体的な「変更内容」テーブルが付属しています。

懸念事項 拡張機能として @firebase-function-kits/firestore-bigquery-export の場合
Config 拡張機能パラメータ .env 経由の Cloud Functions パラメータ
IAM 拡張機能によって付与された権限 requiresRole(...)(デプロイ時に適用)
プロビジョニング 拡張機能別のライフサイクル タスク afterFirstDeploy / afterRedeploy 件のタスク
関数名 ext-<instanceId>-fsexportbigquery fsexportbigquery(接頭辞を付けることも可能)
インスタンス ID EXT_INSTANCE_ID(拡張機能によって挿入されたもの) FIREBASE_KIT_INSTANCE_ID(CLI によって firebase.json から設定)

9. 第 2 世代の関数をテストする

これで、デプロイ時に拡張機能の新規インストールと同じ動作をする第 2 世代の関数が作成されました。次のステップは、途中で誤って導入された問題を検証して修正することです。

setGlobalOptions を呼び出して、デフォルトのリージョンや CPU などのグローバル オプションを設定する場合は、キットをスタンドアロンの第 2 世代関数としてデプロイするときにのみ行う必要があります。キットが npm パッケージとしてインストールされると、ユーザーはラッピング コードで setGlobalOptions を呼び出してこれらのパラメータを構成します。この処理が 2 回行われると、警告が表示されます。この呼び出しは、FIREBASE_KIT_INSTANCE_ID 環境変数を確認することで保護できます。

import { setGlobalOptions } from "firebase-functions";

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

firebase-tools >= 15.32.0 を使用し、変換された第 2 世代の関数を適切なリソースを含むテスト プロジェクトにデプロイして、その動作をテストします。拡張機能のテスト用にテスト プロジェクトがすでに設定されている場合は、次のコマンドを実行します。

firebase deploy --only functions

拡張機能の Firebase コンソールでインストール フォームに入力したときと同じように、パラメータ値を求めるウィザードに入力します。

実施例: Cloud Firestore から BigQuery へのストリーミング

Cloud Firestore から BigQuery への同期をエンドツーエンドで検証します。

  1. Firebase コンソールの Cloud Firestore ページで、COLLECTION_PATH(users)として設定したコレクションを作成します(まだ存在しない場合)。
  2. 任意のフィールドと値を含む bigquery-mirror-test という名前のドキュメントを作成します。
  3. Google Cloud コンソールの BigQuery ページで、未加工の変更ログ テーブルをクエリします。ドキュメントの作成を記録する単一行が含まれている必要があります。

    SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
    
  4. 最新のビューをクエリします。これにより、存在する唯一のドキュメント(bigquery-mirror-test)の最新の変更イベントが返されます。

    SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
    
  5. Cloud Firestore の bigquery-mirror-test ドキュメントを削除します。最新のビューから消え、DELETE イベントが未加工の変更ログ テーブルに追加されます。

    単一のドキュメントの履歴全体を調べるには、次のコマンドを使用します。

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

拡張機能のテストとの違い:

  • トリガーは ext-<instanceId>-fsexportbigquery ではなく fsexportbigquery としてデプロイされます(一般的な第 2 世代の関数としてデプロイされ、キットとしてデプロイされない場合は接頭辞なし)。Cloud Functions ダッシュボードとログでその名前を探します。
  • コードは Firebase Local Emulator Suite で通常の関数として実行されるようになりました。エミュレータで使用するパラメータの値を .env.local で設定できます。Cloud Functions の単体テストで説明されているように、firebase-functions-test SDK を使用してコードの単体テストを行うこともできます。
  • プロビジョニングは Extensions ランタイムによって行われなくなりました。デプロイ後に変更ログ テーブルが見つからない場合は、設定タスク firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME を手動で再実行します。このタスクはべき等であるため、再実行するとデータセット、テーブル、ビューが調整されます。
  • パラメータ値はインストール フォームではなく .env から取得されるため、.env が完了すると firebase deploy の再実行はインタラクティブではなくなります。

10. npm で関数キットを公開する

拡張機能から第 2 世代関数への変換を検証したら、次のいずれかのガイドを使用して、エンドツーエンド テスト用のリリース候補版を npm に公開できます。

キットを npm に公開すると、firebase functions:kits:install でインストールできるようになり、拡張機能の公式な代替としてリストに表示されます。

まずリリース候補版を公開することを強くおすすめします。キットはパッケージ名とバージョンでインストールされるため、プレリリースでは、未完成のパッケージをデフォルトの latest タグでインストールするユーザーに公開することなく、レジストリに対して実際のインストール フローをテストできます。

公開する前に:

  1. パッケージ名を選択します。スコープ付きの名前とスコープなしの名前の両方が機能します(上記のガイドを参照)。スコープ付きパッケージはデフォルトで非公開であるため、--access public を渡します。
  2. ビルドして、どの船を建造するかを確認します。main と types はコンパイルされた出力(この例では lib/)を指しているため、そのディレクトリは公開された tar ファイルに含まれている必要があります。.npmignore または files 許可リストを使用し、npm pack --dry-run で結果を検査します。ビルドを実行する prepublishOnly スクリプトにより、古い出力の公開が防止されます。

デフォルトで安全な公開を実現する package.json の追加:

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

"publishConfig": { "tag": "next" } フィールドにより、プレーンな npm publish が latest を上書きしないようにします。

リリース候補版のカット:

たとえば、ローカルでバージョンを 0.0.2-rc.3 から 0.0.2-rc.4 に増分するには(package.json がリポジトリのルートにある場合、これは Git で commit とタグ付けを行います)。

npm version prerelease --preid rc

リリース候補版を公開し、next タグで @your-org/your-kit@0.0.2-rc.4 を npm に登録するには:

npm publish

npm ウェブサイトで新しいバージョンが表示されるまでに数分かかることがあります。npm view はレジストリを直接読み取ります。

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

このガイドのステップ 11 とステップ 12 が完了したら、パッケージを安定版に昇格できます。

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

npm-shrinkwrap.json に関する注: パッケージに npm-shrinkwrap.json ファイルを含めることを強くおすすめします。含めないと、インストール時に CLI がユーザーに警告します。これにより、ユーザーがテストした依存関係を正確に使用し、サプライ チェーン攻撃から保護できます。ただし、シュリンクラップはユーザーのプロジェクトにそのまま適用されます。Cloud Functions ビルド(npm ci)中も同様です。この場合、開発専用のエントリは EBADPLATFORM で失敗する可能性があります。公開されたシュリンク ラップ コピーから "dev": true エントリと devDependencies を削除する必要がある場合があります。

実施例: Cloud Firestore から BigQuery へのストリーミング

4 番目のリリース候補版の時点でのキットの package.json:

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

この場合、キットはモノレポに存在するため、正しいフォルダを指すように npm レジストリ リンクに repository.directory を含めることが重要です。CHANGELOG.md には、保留中のリリースのメモが保持されます。

11. 関数キットとしてテストする

キットを公開したら、npm を使用してキットをテストすることをおすすめします。

firebase-tools バージョン >= 15.32.0 を使用していることを確認し、キットをインストールします。

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

これにより、npm からパッケージがダウンロードされ、キットの新しいソース ディレクトリ内に設定されます。また、拡張機能のインストール フローと同様に、最初のインスタンスの構成手順が説明されます。パッケージをローカルにインストールして設定したら、デプロイを実行して Google Cloud プロジェクトにリソースを作成します。

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

インストール後、Firebase CLI は、インストール時に選択したインスタンス ID を含む同様のデプロイ コマンドを出力します。

手順 9. 第 2 世代の関数をテストします。キットを使用してデプロイするようになったため、関数には kit-<instance-id>-<method-name> という接頭辞が付いて名前が付けられます。これにより、キットに複数のインスタンスを設定し、プロジェクト内で同じ関数を複数回デプロイできます。各インスタンスには一意の名前が付けられます。

12. 移行のテストの置換

動作する拡張機能インスタンスを設定し、ユーザー移行ガイド(firebase ext:migrate --package または関数キットの CLI コマンドを使用)を使用して、移行の代替として関数キットのテストを完了できます。

13. 公式拡張機能の代替機能についてユーザーと Google に通知する

関数キットの代替が準備でき、ユーザーが移行すべき npm パッケージとして利用可能になったら、ユーザーと Google の両方にこの公式の代替について通知します。拡張機能をホストする GitHub リポジトリの README.md ファイルを次の情報で更新します。

<!-- 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 は、既知の拡張機能の README で <!-- FIREBASE_EXTENSION_REPLACEMENT: extension="firebase/firestore-bigquery-export" package="@firebase-function-kits/firestore-bigquery-export" --> などのコメントをスキャンし、これを使用して firebase-tools リポジトリに replacements.json として保存されている置換の公式レジストリを設定します。replacements.json を確認して、拡張機能でスキャンされる README.md を確認することもできます。交換用の公式リストは毎週更新されます。

実施例: Cloud Firestore から BigQuery へのストリーミング

firestore-bigquery-export 拡張機能 README.md には、次のものが含まれています。

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