extension-to-functions-codebase
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExtension to Functions Codebase & npm Package Migration
扩展到Functions代码库与npm包迁移
Overview
概述
This skill guides the agent in migrating a Firebase Extension repository or
instance into either:
- A standalone Cloud Functions for Firebase codebase (, for end-user app integration), or
firebase-functions - A publishable npm package / shareable open source package (for extension publishers distributing reusable V2 functions).
It leverages native Cloud Functions for Firebase GA capabilities to handle
permissions, dependencies, and lifecycle hooks natively in code, and provides
instructions for modernizing legacy V1 triggers to V2 using the Destructuring
Compatibility Shim.
本技能指导Agent将Firebase扩展仓库或实例迁移至以下两种目标之一:
- 独立的Cloud Functions for Firebase代码库(基于,用于终端用户应用集成),或
firebase-functions - 可发布的npm包/可共享开源包(供扩展发布者分发可复用的V2函数)。
它利用Cloud Functions for Firebase的正式版(GA)功能,在代码中原生处理权限、依赖项和生命周期钩子,并提供使用解构兼容垫片(Destructuring Compatibility Shim)将旧版V1触发器现代化升级到V2的说明。
Triggers
触发条件
Activate this skill when a user asks to:
- Migrate or convert an installed Firebase Extension into a standalone functions codebase.
- Convert an extension repository into a publishable npm package (shareable open source package).
- Upgrade extension triggers from V1 to V2.
当用户提出以下需求时激活本技能:
- 将已安装的Firebase Extension迁移或转换为独立的Functions代码库。
- 将扩展仓库转换为可发布的npm包(可共享开源包)。
- 将扩展触发器从V1升级到V2。
Target Migration Workflows
目标迁移流程
Before starting, determine the target destination with the developer:
-
Target A: Local Functions Codebase (End-User App Integration)
- Output: Source code placed in the project's folder.
functions/src/ - Configuration: Parameters defined via ,
defineString, etc. indefineSecret..env - Deployment: Deployed directly via .
firebase deploy --only functions
- Output: Source code placed in the project's
-
Target B: Publishable npm Package / Shareable Open Source Package
- Output: Reusable npm package containing exported V2 functions.
- Configuration: with
package.jsonmap andexportsdeclared infirebase-functions(ordependencies).peerDependencies - Usage: End users install the package () and re-export functions in their
npm i <package-name>.index.ts
开始前,与开发者确认目标迁移方向:
-
目标A:本地Functions代码库(终端用户应用集成)
- 输出:源代码放置在项目的目录下。
functions/src/ - 配置:通过中的
.env、defineString等定义参数。defineSecret - 部署:直接通过命令部署。
firebase deploy --only functions
- 输出:源代码放置在项目的
-
目标B:可发布的npm包/可共享开源包
- 输出:包含导出V2函数的可复用npm包。
- 配置:中包含
package.json映射,且exports声明在firebase-functions(或dependencies)中。peerDependencies - 使用方式:终端用户安装包()并在其
npm i <package-name>中重新导出函数。index.ts
Getting Started & Git Safety
入门指南与Git安全规范
- Git Status: Verify the workspace has a clean git status before starting.
- In-Place Copying: If copying code to a new subdirectory within the same
repository:
- Use (or copy files and commit) to copy the extension's source directory to the target directory.
git cp - Commit immediately:
"Copying [extension-name] extension to [directory] in preparation for rewrite"
- Use
- Git状态检查:开始前确认工作区的Git状态为干净状态。
- 原地复制:如果要将代码复制到同一仓库内的新子目录:
- 使用(或复制文件后提交)将扩展的源码目录复制到目标目录。
git cp - 立即提交:
"Copying [extension-name] extension to [directory] in preparation for rewrite"
- 使用
Rules and Constraints
规则与约束
1. Zero-Local-Overhead (Cloud Functions Integration)
1. 零本地开销(Cloud Functions集成)
Assume Cloud Functions for Firebase Workload Identities, Declarative Security,
and SDK Lifecycle Hooks are fully GA.
- Do NOT output instructions or scripts telling users to run manual IAM commands or create service accounts.
gcloud - Do NOT write code comments instructing users to manually enable Google APIs in the cloud console.
- Instead, use declarative and
requiresAPIimports from the SDK.requiresRole
假设Cloud Functions for Firebase的工作负载身份、声明式安全和SDK生命周期钩子均已正式发布(GA)。
- 请勿输出指导用户手动执行IAM命令或创建服务账号的说明或脚本。
gcloud - 请勿编写代码注释指导用户在云控制台中手动启用Google API。
- 取而代之,使用SDK中的和
requiresAPI导入声明。requiresRole
2. Global Parameter Access Restriction
2. 全局参数访问限制
- Never call on any parameter at global scope.
.value() - If a global variable or class instance is initialized using a parameter value,
declare the variable globally and initialize it inside the callback:
onInit()typescriptimport { defineString } from "firebase-functions/params"; import { onInit } from "firebase-functions/v2"; const bqDataset = defineString("DATASET_ID"); let bqClient: BigQuery; onInit(() => { bqClient = new BigQuery({ datasetId: bqDataset.value() }); });
- 绝对不要在全局作用域调用参数的方法。
.value() - 如果全局变量或类实例需要使用参数值初始化,请在全局声明变量并在回调中完成初始化:
onInit()typescriptimport { defineString } from "firebase-functions/params"; import { onInit } from "firebase-functions/v2"; const bqDataset = defineString("DATASET_ID"); let bqClient: BigQuery; onInit(() => { bqClient = new BigQuery({ datasetId: bqDataset.value() }); });
3. Concurrency & Cost Parity for V2
3. V2触发器的并发与成本一致性
When upgrading triggers to V2:
- By default, V2 functions enable concurrency (up to 80 requests per instance).
- If you want to maintain V1 fractional CPU pricing (and disable concurrency),
set in the function's options object.
cpu: "gcf_gen1"
升级触发器到V2时:
- 默认情况下,V2函数启用并发(每个实例最多处理80个请求)。
- 如果希望保持V1的 fractional CPU定价(并禁用并发),请在函数的配置对象中设置。
cpu: "gcf_gen1"
Step-by-Step Migration Execution
分步迁移执行流程
Step 1: Inventory the Extension
步骤1:盘点扩展内容
Conduct a complete inventory of everything the extension declares, ships, and
documents so that nothing is lost during migration:
- Inventory :
extension.yaml- : Convert to Functions params (
params,defineString, etc.).defineSecret - : Convert to
apisdeclarations.requiresAPI(...) - : Convert to
rolesdeclarations.requiresRole(...) - (
lifecycleEvents,onInstall,onUpdate): Convert toonConfigureandafterFirstDeployhooks.afterRedeploy - : Note all function triggers to convert from 1st gen (
resources) to 2nd gen (firebase-functions/v1), including standard event triggers, HTTP handlers, and task queues (firebase-functions/v2).onTaskDispatched
- Inventory Files & Tooling:
- : Source code, triggers, helpers, and task queue handlers.
functions/ - Documentation: ,
README.md, andPREINSTALL.md.POSTINSTALL.md - : Note any backfill, import, or helper scripts shipped with the extension.
scripts/
全面盘点扩展声明、交付和文档的所有内容,确保迁移过程中无内容丢失:
- 盘点:
extension.yaml- :转换为Functions参数(
params、defineString等)。defineSecret - :转换为
apis声明。requiresAPI(...) - :转换为
roles声明。requiresRole(...) - (
lifecycleEvents、onInstall、onUpdate):转换为onConfigure和afterFirstDeploy钩子。afterRedeploy - :记录所有需要从第一代(
resources)转换为第二代(firebase-functions/v1)的函数触发器,包括标准事件触发器、HTTP处理器和任务队列(firebase-functions/v2)。onTaskDispatched
- 盘点文件与工具:
- :源码、触发器、辅助工具和任务队列处理器。
functions/ - 文档:、
README.md和PREINSTALL.md。POSTINSTALL.md - :记录扩展附带的任何回填、导入或辅助脚本。
scripts/
Step 2: Create / Update package.json
package.json步骤2:创建/更新package.json
package.jsonCreate an npm package for the migrated extension code (either at project root or
in a dedicated workspace directory):
- Set a publishable package name ().
name: "<package-name>" - Preserve Dev & Test Dependencies: Preserve all existing , test runners (
devDependencies,jest,ts-jest,@types/jest,mocha), and test scripts (@types/mocha) from the legacy extension ("test": "..."or rootfunctions/package.json). Do not drop test frameworks or type definitions.package.json - Declare (e.g.
firebase-functions) in^7.0.0(ordependenciesif creating a lightweight middleware package where the root consumer manages the runtime version):peerDependenciesjson{ "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" }, "dependencies": { "firebase-admin": "^12.0.0", "firebase-functions": "^7.0.0" } }
为迁移后的扩展代码创建npm包(位于项目根目录或专用工作区目录):
- 设置可发布的包名()。
name: "<package-name>" - 保留开发与测试依赖:保留旧版扩展(或根目录
functions/package.json)中所有现有的package.json、测试运行器(devDependencies、jest、ts-jest、@types/jest、mocha)和测试脚本(@types/mocha)。请勿丢弃测试框架或类型定义。"test": "..." - 在(如果创建轻量级中间件包且由根消费者管理运行时版本,则声明在
dependencies)中声明peerDependencies(例如firebase-functions):^7.0.0json{ "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" }, "dependencies": { "firebase-admin": "^12.0.0", "firebase-functions": "^7.0.0" } }
Step 3: Move Function Code & Expose Deployable Functions
步骤3:迁移函数代码并暴露可部署函数
Move the extension's function source into the package's folder:
src/- Keep normal Firebase trigger exports. The package must expose deployable
functions that end users can re-export from their entrypoint ().
index.ts - Document that consumers must deploy packaged functions by re-exporting them:
Note: A bare import (typescript
export * from "<package-name>"; // Or named exports: // export { syncV2, initBigQuerySync } from "<package-name>";) is not enough. The Firebase CLI only deploys functions exported from the user's root entry file.import "<package-name>"
将扩展的函数源码迁移到包的目录:
src/- 保留常规Firebase触发器导出。包必须暴露可部署的函数,以便终端用户可以从其入口文件()重新导出。
index.ts - 文档说明消费者必须通过重新导出的方式部署打包后的函数:
注意:仅导入(typescript
export * from "<package-name>"; // 或命名导出: // export { syncV2, initBigQuerySync } from "<package-name>";)是不够的。Firebase CLI仅部署从用户根入口文件导出的函数。import "<package-name>"
Step 4: Upgrade Functions from 1st Gen to 2nd Gen
步骤4:将函数从第一代升级到第二代
Convert each exported 1st gen function trigger (, ,
) to its 2nd gen equivalent (,
, from ):
onWriteonRequesttasks.taskQueue().onDispatchonDocumentWrittenonRequestonTaskDispatchedfirebase-functions/v2/...- Signatures & Destructuring Shim: Use the Destructuring Compatibility Shim
() to preserve V1 business logic without rewriting function bodies. See signature-mapping.md and destructuring-shim.md.
{ shimmedKey, context } - Authentication Triggers Exception: If your extension uses v1
Authentication Triggers (,
auth.user().onCreate()), instruct the agent to check live whether a 2nd gen alternative exists in the installedauth.user().onDelete()package or live documentation. If 2nd gen Auth triggers are not yet supported for those events, warn the user clearly and halt or refuse the migration for those specific triggers until a V2 alternative becomes available.firebase-functions
将每个导出的第一代函数触发器(、、)转换为对应的第二代触发器(来自的、、):
onWriteonRequesttasks.taskQueue().onDispatchfirebase-functions/v2/...onDocumentWrittenonRequestonTaskDispatched- 签名与解构垫片:使用解构兼容垫片()保留V1业务逻辑,无需重写函数体。请参阅signature-mapping.md和destructuring-shim.md。
{ shimmedKey, context } - 身份验证触发器例外:如果扩展使用V1身份验证触发器(、
auth.user().onCreate()),请指导Agent检查已安装的auth.user().onDelete()包或在线文档中是否存在第二代替代方案。如果这些事件的第二代Auth触发器尚未支持,请明确警告用户,并暂停或拒绝这些特定触发器的迁移,直到V2替代方案可用。firebase-functions
Step 5: Replace Extension Params with Functions Params
步骤5:将扩展参数替换为Functions参数
Each parameter in becomes a Parameterized Configuration call:
extension.yaml- Map parameter primitives and attributes:
- Type ->
stringdefineString("PARAM_NAME", { label: "...", description: "...", default: "..." }) - Type ->
secretdefineSecret("PARAM_NAME") - Type ->
intdefineInt("PARAM_NAME", { label: "...", default: 123 }) - Type ->
booleandefineBoolean("PARAM_NAME", { default: true }) - Type /
select-> mapmultiSelectintooptions:inputdefineString("PARAM_NAME", { input: { select: { options: [{ value: "val", label: "Val" }] } } }) - -> map into text input options:
validationRegexdefineString("PARAM_NAME", { input: { text: { validationRegex: "^[a-z]+$" } } }) - / Non-empty validation -> map
required: trueinto input options:nonEmpty: truedefineString("PARAM_NAME", { input: { text: { nonEmpty: true } } })
- Type
- Read parameter values inside handlers using .
paramName.value() - Keep Exact Parameter Names: Never change parameter names
(,
COLLECTION_PATH, etc.) so that existing values carry over seamlessly inDATASET_ID..env
extension.yaml- 映射参数类型和属性:
- 类型->
stringdefineString("PARAM_NAME", { label: "...", description: "...", default: "..." }) - 类型->
secretdefineSecret("PARAM_NAME") - 类型->
intdefineInt("PARAM_NAME", { label: "...", default: 123 }) - 类型->
booleandefineBoolean("PARAM_NAME", { default: true }) - 类型/
select-> 将multiSelect映射到options:inputdefineString("PARAM_NAME", { input: { select: { options: [{ value: "val", label: "Val" }] } } }) - -> 映射到文本输入选项:
validationRegexdefineString("PARAM_NAME", { input: { text: { validationRegex: "^[a-z]+$" } } }) - / 非空验证 -> 将
required: true映射到输入选项:nonEmpty: truedefineString("PARAM_NAME", { input: { text: { nonEmpty: true } } })
- 类型
- 在处理器内部使用读取参数值。
paramName.value() - 保留确切参数名称:永远不要更改参数名称(如、
COLLECTION_PATH等),以便现有值可以在DATASET_ID中无缝迁移。.env
Step 6: Migrate Internal Task Queue Calls (queue.enqueue(...)
)
queue.enqueue(...)步骤6:迁移内部任务队列调用(queue.enqueue(...)
)
queue.enqueue(...)If your extension code enqueues tasks onto its own queue using the Admin SDK
():
getFunctions().taskQueue(...)- Under the Extensions runtime, the Admin SDK resolved queues by function name
plus .
process.env.EXT_INSTANCE_ID - In an npm package / regular codebase: Remove the second argument
() entirely. The Admin SDK automatically targets the current codebase:
EXT_INSTANCE_IDtypescript// Before (extension runtime): // const queue = getFunctions().taskQueue(`locations/${region}/functions/syncBigQuery`, process.env.EXT_INSTANCE_ID); // After (npm package): const queue = getFunctions().taskQueue(`locations/${region}/functions/syncBigQuery`); await queue.enqueue(taskData);
如果扩展代码使用Admin SDK()将任务加入自身队列:
getFunctions().taskQueue(...)- 在Extensions运行时中,Admin SDK通过函数名称加上解析队列。
process.env.EXT_INSTANCE_ID - 在npm包/常规代码库中:完全移除第二个参数()。Admin SDK会自动定位当前代码库:
EXT_INSTANCE_IDtypescript// 之前(扩展运行时): // const queue = getFunctions().taskQueue(`locations/${region}/functions/syncBigQuery`, process.env.EXT_INSTANCE_ID); // 之后(npm包): const queue = getFunctions().taskQueue(`locations/${region}/functions/syncBigQuery`); await queue.enqueue(taskData);
Step 7: Migrate Secrets
步骤7:迁移密钥
For parameters declared with :
type: secret- Declare the secret explicitly:
typescript
import { defineSecret } from "firebase-functions/params"; const apiKey = defineSecret("API_KEY"); - Bind the secret in the trigger options:
typescript
export const fn = onRequest({ secrets: [apiKey] }, handler); - Keep exact secret names unchanged so existing Secret Manager bindings work.
对于声明为的参数:
type: secret- 显式声明密钥:
typescript
import { defineSecret } from "firebase-functions/params"; const apiKey = defineSecret("API_KEY"); - 在触发器配置中绑定密钥:
typescript
export const fn = onRequest({ secrets: [apiKey] }, handler); - 保留确切的密钥名称不变,以便现有的Secret Manager绑定继续生效。
Step 8: Declare Required APIs and IAM Roles
步骤8:声明所需API和IAM角色
Replace and from with declarative code in your
entry file:
apisrolesextension.yamltypescript
import { requiresAPI, requiresRole } from "firebase-functions";
requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");At deploy time, the Firebase CLI automatically grants these roles to the managed
runtime service account and enables the required APIs.
将中的和替换为入口文件中的声明式代码:
extension.yamlapisrolestypescript
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。
Step 9: Convert Lifecycle Hooks (afterFirstDeploy
& afterRedeploy
)
afterFirstDeployafterRedeploy步骤9:转换生命周期钩子(afterFirstDeploy
& afterRedeploy
)
afterFirstDeployafterRedeployReplace (, , ) with SDK
lifecycle hooks:
lifecycleEventsonInstallonUpdateonConfigure- Convert task queue handlers (,
initBigQuerySync) to V2setupBigQuerySyncfromonTaskDispatched(removing legacyfirebase-functions/v2/taskscalls).getExtensions().runtime().setProcessingState(...) - Register lifecycle hooks in code:
typescript
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/v2"; // Replaces onInstall: afterFirstDeploy({ task: { function: "runInitialSetup", body: {} } }); // Replaces onUpdate & onConfigure: afterRedeploy({ task: { function: "runInitialSetup", body: { reconcile: true } } }); - Ensure handlers are idempotent and document manual rerun commands:
bash
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
将(、、)替换为SDK生命周期钩子:
lifecycleEventsonInstallonUpdateonConfigure- 将任务队列处理器(、
initBigQuerySync)转换为V2的setupBigQuerySync(来自onTaskDispatched),移除旧版的firebase-functions/v2/tasks调用。getExtensions().runtime().setProcessingState(...) - 在代码中注册生命周期钩子:
typescript
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/v2"; // 替代onInstall: afterFirstDeploy({ task: { function: "runInitialSetup", body: {} } }); // 替代onUpdate & onConfigure: afterRedeploy({ task: { function: "runInitialSetup", body: { reconcile: true } } }); - 确保处理器是幂等的,并记录手动重新运行的命令:
bash
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
Step 10: Document Setup for Your Users (README.md
)
README.md步骤10:为用户编写设置文档(README.md
)
README.mdPreserve whatever documentation, secrets, and setup instructions the original
extension already documented in (and /
), updating them as needed:
README.mdPREINSTALL.mdPOSTINSTALL.md- Update Configuration References: Replace instructions referencing legacy
installation prompts or
extension.yamlfiles with standardext-*.envParameterized Configuration setup matching the.envparameters.defineString - Include Re-export Snippet: Ensure the basic root re-export snippet is
shown () so users know how to expose the functions in their root
export * from "<package-name>";.index.ts - Avoid Unnecessary Boilerplate: Do not generate redundant comparison tables or generic installation steps if the setup is self-evident in the code or already covered by existing README sections.
保留原始扩展已在(以及/)中记录的所有文档、密钥和设置说明,并按需更新:
README.mdPREINSTALL.mdPOSTINSTALL.md- 更新配置引用:将引用旧版安装提示或
extension.yaml文件的说明替换为与ext-*.env参数匹配的标准defineString参数化配置设置。.env - 包含重新导出代码片段:确保展示基本的根重新导出代码片段(),以便用户知道如何在其根
export * from "<package-name>";中暴露函数。index.ts - 避免不必要的模板内容:如果设置在代码中自明或已在现有README章节中涵盖,请不要生成冗余的对比表格或通用安装步骤。
Step 11: Build & Test Verification
步骤11:构建与测试验证
- Verify Source Compilation: Run (
npm run build) to ensure no TypeScript compilation errors intsc.src/ - Verify Unit Test Suite & Type Definitions:
- If existing unit tests (,
__tests__/) are present in the extension:test/- Ensure (or the original test framework types) are present in
@types/jestso test files type-check cleanly.devDependencies - Update test invocations for upgraded V2 triggers to pass a single
destructured event object
(instead of positional arguments
({ change: mockChange, context: mockContext })).(mockChange, mockContext) - Run or type-check test files (
npm test) to verify zero regressions.npx tsc --noEmit
- Ensure
- If existing unit tests (
- 验证源码编译:运行(
npm run build)确保tsc目录中无TypeScript编译错误。src/ - 验证单元测试套件与类型定义:
- 如果扩展中存在现有单元测试(、
__tests__/):test/- 确保(或原始测试框架类型)存在于
@types/jest中,以便测试文件可以干净地进行类型检查。devDependencies - 更新升级后的V2触发器的测试调用,传递单个解构的事件对象(而非位置参数
({ change: mockChange, context: mockContext }))。(mockChange, mockContext) - 运行或对测试文件进行类型检查(
npm test)以验证无回归问题。npx tsc --noEmit
- 确保
- 如果扩展中存在现有单元测试(
References & Official Resources
参考资料与官方资源
- Official Google Migration Guide: Prepare Firebase Extensions for migration to Cloud Functions
- Publisher Support Contacts:
- Support Email:
firebase-extensions-migrator-support-external@google.com - Support Group Subscription:
firebase-extensions-migrator-support-external+subscribe@google.com
- Support Email:
- Destructuring Shim: See destructuring-shim.md for details on event property translation.
- Trigger Mapping: See signature-mapping.md for V1 vs V2 trigger definitions and shim keys.
- Configuration & Parameters: See
configuration-migration.md for
options, params, and secret bindings.
runWith
- 官方Google迁移指南: Prepare Firebase Extensions for migration to Cloud Functions
- 发布者支持联系方式:
- 支持邮箱:
firebase-extensions-migrator-support-external@google.com - 支持组订阅:
firebase-extensions-migrator-support-external+subscribe@google.com
- 支持邮箱:
- 解构垫片: 详情请参阅 destructuring-shim.md中的事件属性转换说明。
- 触发器映射: 详情请参阅 signature-mapping.md中的V1与V2触发器定义及垫片键。
- 配置与参数: 详情请参阅
configuration-migration.md中的选项、参数和密钥绑定说明。
runWith