本指南介绍了如何将扩展程序从已弃用的 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,系统会回复一封会员资格申请电子邮件。您必须回复该电子邮件,而不是点击“加入此群组”按钮。
准备工作
如需按本文所述完成迁移,您将使用 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 代函数套件软件包中。”
1. 清点扩展程序
首先,请清点扩展程序:列出扩展程序声明、随附和记录的所有内容,以便每个行为在第 2 代函数中都有明确的目标,并且在迁移过程中不会丢失任何内容。
请查看以下各项,并记下您发现的内容:
extension.yaml,用于声明参数、函数、事件、IAM 角色、必需的 API、密钥和生命周期钩子。functions/,其中包含函数代码、依赖项、构建配置、触发器和任务队列函数。README.md、PREINSTALL.md和POSTINSTALL.md,其中包含设置步骤、警告和结算备注。scripts/,其中包含任何导入、回填、IAM、修复或迁移实用程序,以及您随扩展程序一起提供的任何其他工具。
然后,对于 extension.yaml 中的每个项,确定其在 npm 软件包中的位置:
将用户配置转换为 Cloud Functions 参数(第 4 步)。
将密钥转换为 Cloud Functions 密钥(第 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 文件。如果您要迁移一个扩展程序,则可以是根 package.json。如果您要在一个代码库中迁移多个扩展程序,请为每个扩展程序提供自己的软件包。
最低 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. 转换扩展程序参数和 Secret
参数
您在 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,
之后。一个 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。函数套件从
FIREBASE_KIT_INSTANCE_ID 读取其实例 ID,Firebase CLI 会将每个套件实例的
设置为 firebase.json 中 instances 映射中的实例键。CLI 在部署时发现期间、在模拟器中以及向已部署的函数提供该变量。
实例 ID 不是参数,因此请勿使用 defineString 进行声明。事实上,FIREBASE_... 是 .env 文件中的预留前缀,因此用户无法在此处设置或替换它。CLI 注入的值对参数系统不可见。直接从环境中读取:
// 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,因此无需迁移任何内容。“删除用户数据”扩展程序使用它来命名其 Pub/Sub 主题。)
之前。在 config.ts 中读取为原始环境变量,带有扩展程序用于其资源的 ext- 前缀:
// functions/src/config.ts
discoveryTopic: `ext-${process.env.EXT_INSTANCE_ID}-discovery`,
deletionTopic: `ext-${process.env.EXT_INSTANCE_ID}-deletion`,
之后。对 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 声明 Secret。扩展程序运行时会存储并绑定这些变量,因此您的扩展程序代码可以直接读取 process.env.PARAM_NAME。在典型的 Cloud Functions 代码库中,您需要明确声明和绑定每个 Secret:
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 文件中进行管理。请务必不要更改代码中声明的 Secret 名称。在迁移期间,最终用户密钥会相应地迁移。
实际操作示例:通过 Cloud Firestore 触发电子邮件
之前。在 config.ts 中,MAIL_COLLECTION 和 SMTP_PASSWORD 读取为原始环境变量:
# 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,
之后。一个 defineString 和一个 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 作为第二个参数传递,以定位同一扩展程序中的任务队列函数。自 firebase-admin 14.2.0 起,此设置既不是必需的,也不建议使用。任务队列 API 现在默认定位到同一上下文(例如扩展程序或套件)中的任务队列。无论是作为扩展程序还是作为独立函数,您都可以放心地在代码中移除此参数,我们甚至鼓励您这样做。移除此参数可确保可移植性和向前兼容性。
enqueue 调用的其他所有方面(包括 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 中声明;扩展程序运行时已启用该 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 软件包中,以代码形式声明等效的生命周期操作。
对于一次性设置:
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 的流
之前。lifecycleEvents(在 extension.yaml 中),由扩展程序运行时驱动:
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 附带一个具体的“更改内容”表:
| 问题 | 作为扩展程序 | As @firebase-function-kits/firestore-bigquery-export |
|---|---|---|
| 配置 | 扩展程序参数 | Cloud Functions通过 .env 传递的参数 |
| 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 来配置这些参数,如果发生两次,他们会收到警告。
您可以通过检查 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 的端到端同步:
- 在 Firebase 控制台的 Cloud Firestore 页面中,创建您设置为
COLLECTION_PATH(users) 的集合(如果该集合尚不存在)。 - 创建一个名为
bigquery-mirror-test的文档,其中包含任意字段和任意值。 在 Google Cloud 控制台的 BigQuery 页面中,查询原始更改日志表。它应包含记录文档创建的单行:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`查询最新视图,该视图应返回唯一存在的文档 (
bigquery-mirror-test) 的最新更改事件:SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`删除 Cloud Firestore 中的
bigquery-mirror-test文档。它会从最新视图中消失,并且DELETE事件会附加到原始更改日志表中。您可以使用以下命令检查单个文档的完整历史记录:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog` WHERE document_name = "bigquery-mirror-test" ORDER BY timestamp ASC
与测试扩展程序的不同之处:
- 触发器部署为
fsexportbigquery(部署为典型的第 2 代函数而非套件时没有前缀),而不是ext-<instanceId>-fsexportbigquery。在 Cloud Functions 信息中心和日志中查找该名称。 - 您的代码现在可以在 Firebase Local Emulator Suite 中像正常函数一样运行了。
您可以使用
.env.local设置要在模拟器中使用的参数值。您还可以使用firebase-functions-testSDK 对代码进行单元测试,如 Cloud Functions 的单元测试中所述。 - 配置不再由扩展程序运行时驱动。如果部署后缺少变更日志表,请手动重新运行设置任务:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME。该任务是幂等的,因此重新运行该任务可协调数据集、表和视图。 - 形参值来自
.env,而不是安装表单,因此一旦.env完成,firebase deploy的重新运行就是非交互式的。
10. 在 npm 上发布函数套件
验证从扩展程序到第 2 代函数的转换后,您可以按照以下任一指南将发布候选版本发布到 npm,以进行端到端测试:
当您的套件发布到 npm 后,可以使用 firebase functions:kits:install 进行安装,并列为扩展程序的官方替代项。
我们强烈建议您先发布候选版本。套件按软件包名称和版本进行安装,因此预发布版本可让您针对注册表测试实际安装流程,而不会向使用默认 latest 标记安装的用户公开未完成的软件包。
发布前:
- 选择软件包名称。无论是限定范围的名称还是未限定范围的名称,都可以使用(请参阅上文中的指南)。请注意,作用域软件包默认是私有的,因此请传递
--access public。 - 构建并检查哪些飞船。
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 中提交并添加标记),请执行以下操作:
npm version prerelease --preid rc
如需发布候选版本并在 npm 上注册 @your-org/your-kit@0.0.2-rc.4(使用 next 标记),请执行以下操作:
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 会在安装时向用户发出警告。它可确保用户使用您测试过的确切依赖项,并有助于防范供应链攻击。不过,shrinkwrap 会在用户的项目中逐字应用,包括在 Cloud Functions 构建 (npm ci) 期间,其中仅限开发者的条目可能会失败并显示 EBADPLATFORM。您可能需要从已发布的收缩包装副本中剥离 "dev": true 条目和 devDependencies。
示例:从 Cloud Firestore 到 BigQuery 的流
在第四个候选版本发布时,该套件的 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" }
}
请注意,在这种情况下,该软件包位于单代码库中,因此务必添加 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.