以发布者的身份将扩展程序迁移到 Cloud Functions(续1)

); requiresRole("roles/bigquery.dataEditor"); requiresRole("roles/datastore.user"); requiresRole("roles/bigquery.user");


如果您的[扩展程序发布 Eventarc 事件](https://invertase.io/blog/introducing-extension-events),您*还*需要设置发布事件所需的相应角色和 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`](https://firebase.google.cn/docs/reference/functions/2nd-gen/node/firebase-functions.globaloptions?hl=zh-cn) 来设置全局选项(例如默认区域或 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`](https://www.npmjs.com/package/firebase-tools?activeTab=readme)
`>= 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
   ```

**与测试扩展程序的不同之处**:

- 触发器部署为 `fsexportbigquery`(部署为典型的第 2 代函数而非套件时没有前缀),而不是 `ext-<instanceId>-fsexportbigquery`。在 Cloud Functions 信息中心和日志中查找该名称。
- 您的代码现在可以在 Firebase Local Emulator Suite 中像正常函数一样运行了。
  您可以使用 `.env.local` 设置要在模拟器中使用的参数值。您还可以使用 `firebase-functions-test` SDK 对代码进行单元测试,如 [Cloud Functions 的单元测试](https://firebase.google.cn/docs/functions/unit-testing?hl=zh-cn)中所述。
- 配置不再由扩展程序运行时驱动。如果部署后缺少变更日志表,请手动重新运行设置任务:`firebase
  functions:lifecycle:run afterFirstDeploy CODEBASE_NAME`。该任务是幂等的,因此重新运行该任务可协调数据集、表和视图。
- 形参值来自 `.env`,而不是安装表单,因此一旦 `.env` 完成,`firebase deploy` 的重新运行就是非交互式的。

### 10. 在 npm 上发布函数套件

验证从扩展程序到第 2 代函数的转换后,您可以按照以下任一指南将发布候选版本发布到 npm,以进行端到端测试:

- [创建和发布无范围的公开软件包](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages)
- [创建和发布限定范围的公开软件包](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages)

当您的套件发布到 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 中提交并添加标记),请执行以下操作:

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`](https://www.npmjs.com/package/firebase-tools?activeTab=readme) 版本 `>= 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. 测试迁移替换

您可以设置一个可正常运行的扩展程序实例,然后使用[用户迁移指南](https://firebase.google.cn/docs/extensions/users/migrate?hl=zh-cn#choose-migration-path)(使用 [`firebase ext:migrate --package`](https://firebase.google.cn/docs/extensions/users/migrate?hl=zh-cn#migrate-with-ext-migrate) 或[函数套件 CLI 命令](https://firebase.google.cn/docs/extensions/users/migrate?hl=zh-cn#function-kits-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> package.


Google 会扫描已知的扩展程序 README,查找类似 `<!--
FIREBASE_EXTENSION_REPLACEMENT: extension=“firebase/firestore-bigquery-export”
package=“@firebase-function-kits/firestore-bigquery-export” -->` 的注释,并使用这些注释填充存储在 `firebase-tools` 代码库中的官方替换项注册表,如 [`replacements.json`](https://github.com/firebase/firebase-tools/blob/be0000e18311c2e843032c26782738fa893cc6c3/src/extensions/replacements.json) 中所示。
您还可以查看 `replacements.json`,了解系统将为您的扩展服务扫描哪些 `README.md`。官方替换列表每周更新一次。

**示例**:从 Cloud Firestore 到 BigQuery 的流

`firestore-bigquery-export` 扩展程序 [`README.md`](https://github.com/firebase/extensions/blob/next/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 package. ```