انتقال «افزونه‌های Firebase» به Cloud Functions

این راهنما به شما نشان می‌دهد که چگونه افزونه‌های خود را از محیط منسوخ‌شده Firebase Extensions به تابعی منتقل کنید که کاربران شما در پایگاه کد Firebase (نسل دوم) خودشان در Cloud Functions نصب و مستقر می‌کنند.

این مسیر انتقال توصیه‌شده است. Firebase فهرستی از افزونه‌ها را با معادل‌های رسمی npm نگهداری خواهد کرد؛ این راهنما شما را در ایجاد افزونه خودتان راهنمایی می‌کند.

در سراسر این راهنما، از افزونه جاری‌سازی Cloud Firestore به BigQuery (firestore-bigquery-export) به‌عنوان نمونه اجرایی استفاده می‌شود. هر بخش با یک نمونه کارشده به پایان می‌رسد که نشان می‌دهد آن افزونه قبل‌از انتقال چگونه بوده است و بعداز انتقال، به‌عنوان بسته @firebase-function-kits/firestore-bigquery-export، چگونه خواهد بود.

برای دریافت اطلاعات و راهنمایی بیشتر درباره انتقال افزونه‌ها ثبت‌نام کنید

اگر درباره نحوه انتقال از Firebase Extensions سؤالی دارید، می‌توانید ازطریق firebase-extensions-migrator-support-external@google.com با ما تماس بگیرید. همچنین وقتی راهنما را با اطلاعات بیشتر درباره بسته‌بندی، آزمایش، و توزیع توابع نسل دوم به‌روزرسانی کنیم، به این گروه ایمیل خواهیم زد.

برای پیوستن به این گروه، پیامی به firebase-extensions-migrator-support-external+subscribe@google.com ارسال کنید، که با ایمیل درخواست عضویت پاسخ خواهد داد. باید به آن ایمیل پاسخ دهید، نه اینکه روی دکمه «پیوستن به این گروه» کلیک کنید.

قبل از شروع

برای تکمیل این انتقال همان‌طور که نوشته شده است، از ویژگی‌های زیر در Cloud Functions استفاده خواهید کرد:

  • پیکربندی پارامتری‌شده. هر پارامتری که در extension.yaml اعلام می‌کنید به پارامتر تعریف‌شده‌ای در کد بسته‌تان تبدیل می‌شود.

  • نقش‌های IAM اعلانی و میاناهای برنامه‌سازی کاربردی لازم. هر نقشی که در extension.yaml اعلام می‌کنید به فراخوانی requiresRole(...) تبدیل می‌شود و هر میانای برنامه‌سازی کاربردی به فراخوانی requiresAPI(...) در کد بسته‌تان تبدیل می‌شود. در زمان استقرار، Firebase CLI نقش‌های اعلام‌شده را به حساب سرویس زمان اجرای مدیریت‌شده اعطا می‌کند و میاناهای برنامه‌سازی کاربردی اعلام‌شده را ازطرف شما فعال می‌کند.

  • رویدادهای چرخه حیات برای پایگاه‌های کد Cloud Functions. پایگاه‌های کد Cloud Functions اکنون از رویدادهای چرخه حیات مشابه Firebase Extensions پشتیبانی می‌کنند. راه‌اندازی زمان نصب و زمان به‌روزرسانی را با قلاب‌های چرخه حیات afterFirstDeploy(...) و afterRedeploy(...) اعلام کنید. این‌ها جایگزین lifecycleEvents می‌شوند که در extension.yaml اعلام می‌کنید.

انتقال منبع Firebase Extensions به تابع نسل دوم

(اختیاری) انتقال خودکار با Firebase Agent Skill

می‌توانید مراحل ۱ تا ۸ (فهرست‌بندی منابع، راه‌اندازی ارتقاها، تبدیل‌های پارامتر و راز، IAM اعلانی، قلاب‌های چرخه حیات، و تولید README بسته) را بااستفاده از مهارت رسمی عامل هوش مصنوعی extension-to-functions-codebase خودکار کنید.

نصب مهارت

اگر شما یا دستیار کدنویسی هوش مصنوعی شما (Gemini در Firebase،‏ Cursor،‏ Claude Code،‏ GitHub Copilot) هنوز مهارت را نصب نکرده‌اید، فرمان زیر را بااستفاده از skills CLI اجرا کنید:

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

وقتی مهارت در پروژه شما نصب شد، دستیار کدنویسی هوش مصنوعی شما به‌طور خودکار از قوانین انتقال و مراحل تبدیل آن پیروی می‌کند. می‌توانید از پیام‌واره زیر استفاده کنید:

«لطفاً این «افزونه Firebase» را طبق دستورالعمل‌های موجود در extension-to-functions-codebase مهارت به یک بسته «کیت تابع نسل دوم» قابل‌انتشار انتقال دهید.»

۱. فهرست کردن افزونه

با تهیه فهرست موجودی از افزونه‌تان شروع کنید: فهرست کاملی از هر چیزی که افزونه اعلام، ارسال، و مستند می‌کند، تا هر رفتاری مقصد تعریف‌شده‌ای در تابع نسل دوم داشته باشد و چیزی در انتقال ازدست نرود.

هریک از موارد زیر را بررسی کنید و آنچه را پیدا می‌کنید یادداشت کنید:

  • extension.yaml که پارامترها، توابع، رویدادها، نقش‌های IAM، میاناهای برنامه‌سازی کاربردی موردنیاز، اسرار، و قلاب‌های چرخه حیات را اعلام می‌کند.

  • functions/، که حاوی کد تابع، وابستگی‌ها، پیکربندی ساخت، راه‌اندازها، و توابع صف تکلیف شما است.

  • README.md، PREINSTALL.md، و POSTINSTALL.md که حاوی مراحل راه‌اندازی، هشدارها، و یادداشت‌های صورت‌حساب است.

  • scripts/، که حاوی هرگونه ابزار کمکی وارد کردن، پر کردن، IAM، تعمیر، یا انتقال، و هر ابزار دیگری است که همراه با افزونه ارسال می‌کنید.

سپس، برای هر مورد در extension.yaml، تصمیم بگیرید که در بسته npm کجا قرار گیرد:

  • پیکربندی کاربر را به پارامترهای Cloud Functions تبدیل کنید (مرحله ۴).

  • رمزها را به رمزهای Cloud Functions تبدیل کنید (مرحله ۴).

  • نقش‌های IAM را به اظهارنامه‌های requiresRole(...) تبدیل کنید (مرحله ۶).

  • میاناهای برنامه‌سازی کاربردی Google موردنیاز را درصورت لزوم به اظهارنامه‌های requiresAPI(...) تبدیل کنید (مرحله ۶).

  • تبدیل قلاب‌های نصب و به‌روزرسانی به اظهارنامه‌های afterFirstDeploy(...) و afterRedeploy(...) (مرحله ۷).

  • شناسه‌های نمونه را از EXT_INSTANCE_ID به FIREBASE_KIT_INSTANCE_ID تبدیل کنید (مرحله ۴).

نمونه کارشده: جاری‌سازی Cloud Firestore به BigQuery

خواندن firestore-bigquery-export/extension.yaml و functions/ این موجودی را تولید می‌کند:

در extension.yaml تعداد / مقدار کجا می‌رود
params ‫۲۵ (COLLECTION_PATH،‏ DATASET_ID،‏ TABLE_ID،‏ DATASET_LOCATION،‏ VIEW_TYPE،‏ …) Cloud Functions پارامتر (مرحله ۴)
apis bigquery.googleapis.com requiresAPI(...) (مرحله ۶)
roles ‫bigquery.dataEditor،‏ datastore.user،‏ bigquery.user requiresRole(...) (مرحله ۶)
resources ‫۱ راه‌انداز رویداد (fsexportbigquery) + توابع صف وظیفه (initBigQuerySync، setupBigQuerySync) توابع بسته صادرشده (مرحله ۳)
lifecycleEvents onInstall ← initBigQuerySync؛ onUpdate / onConfigure ← setupBigQuerySync ‫afterFirstDeploy / afterRedeploy (مرحله ۷)
شناسه نمونه استفاده‌نشده (EXT_INSTANCE_ID خوانده نشده است) چیزی برای انتقال وجود ندارد
scripts/ ‫import/ (پر کردن)، gen-schema-view/ به‌عنوان دستورگان نگهداری می‌شود (خارج از محدوده اینجا)

تجزیه‌وتحلیل. افزونه هیچ پارامتر type: secret اعلام نمی‌کند، بنابراین چیزی برای انتقال برای اسرار در مرحله ۴ وجود ندارد. راه‌انداز رویداد ازقبل نسل دوم است؛ فقط توابع صف تکلیف هنوز نسل اول هستند (مربوط به مرحله ۳).

۲. به‌روزرسانی package.json

فایل package.json افزونه‌تان را به‌روز کنید. اگر یک افزونه را منتقل می‌کنید، این می‌تواند ریشه package.json باشد. اگر افزونه‌های زیادی را در یک مخزن منتقل می‌کنید، به هر افزونه بسته خودش را بدهید.

حداقل نسخه‌های کیت توسعه نرم‌افزار: firebase-functions >= 7.4.0 و firebase-admin >= 14.2.0 را به‌عنوان وابستگی اعلام کنید. نسخه firebase-functions خود را به‌عنوان وابستگی همتا نیز اعلام کنید تا پروژه Cloud Functions کاربران شما همان نسخه کیت توسعه نرم‌افزاری را داشته باشد که کتابخانه شما با آن نوشته شده است.

{
  "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 افزونه خصوصی است، شناسه افزونه را نام می‌برد، و 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"
  }
}

۳. ارتقا دادن توابع از نسل اول به نسل دوم

اگر افزونه‌تان هنوز توابع نسل اول را صادر می‌کند، هر راه‌انداز را به معادل نسل دوم آن تبدیل کنید. از ماژول‌های firebase-functions/... وارد کنید و تنظیمات زمان اجرا را در گزینه‌های راه‌انداز ارسال کنید.

راهنمای ارتقا دادن Cloud Functions نسل دوم را ببینید. به‌ویژه، می‌توانید بااستفاده از ساختارشکنی رویداد وصله‌شده نسل دوم، تلاش‌های بازنویسی را به‌حداقل برسانید و از بازنویسی منطق تابع خود اجتناب کنید زیرا کیت توسعه نرم‌افزار نسل دوم پارامترهای نسخه ۱ را به‌عنوان فیلدهایی در شیء رویداد نشان می‌دهد، که به شما امکان می‌دهد از پارامترهای ساختارشکنی‌شده/نام‌گذاری‌شده استفاده کنید و منطق کسب‌وکار خود را بدون تغییر نگه دارید.

قبل. نسل اول:

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

بعد. نسل دوم:

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

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

برای مشاهده فهرست جامعی از تفاوت‌های بین توابع نسل اول و نسل دوم، مقایسه نسخه Cloud Functions را ببینید.

تفاوت مهم بین توابع نسل اول و دوم این است که توابع نسل دوم باید با منابع راه‌اندازشان در یک مکان باشند. وقتی کاربران از افزونه‌ای که از توابع نسل اول استفاده می‌کند به کیتی که از توابع نسل دوم استفاده می‌کند مهاجرت می‌کنند، ممکن است لازم باشد مکان توابع را تغییر دهند تا این الزام را برآورده کنند.

هر مکان 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 مربوطه مستقر خواهند شد تا مشکلات مکان برای توابع راه‌اندازی‌شده با رویداد gen2 آن‌ها برطرف شود.

۴. تبدیل پارامترها و اطلاعات محرمانه افزونه

پارامترها

هر پارامتری که در 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 موجود همچنان کار می‌کند.

شناسه نمونه

افزونه‌ها شناسه نمونه‌شان را از EXT_INSTANCE_ID می‌خوانند که توسط زمان اجرای افزونه‌ها تزریق شده است. کیت‌های تابع شناسه نمونه خود را از FIREBASE_KIT_INSTANCE_ID می‌خوانند، که Firebase CLI برای هر نمونه کیت روی کلید نمونه در نقشه instances در firebase.json تنظیم می‌کند. رابط خط فرمان آن را درطول اکتشاف زمان استقرار، در شبیه‌ساز، و برای توابع مستقرشده ارائه می‌دهد.

شناسه نمونه پارامتر نیست، بنابراین آن را با defineString اعلام نکنید. درواقع، FIREBASE_... پیشوندی رزروشده در فایل‌های .env است، بنابراین کاربران نمی‌توانند آن را در آنجا تنظیم یا لغو کنند. مقدارهایی که CLI تزریق می‌کند برای سیستم پارامترها قابل‌مشاهده نیست. آن را مستقیماً از محیط بخوانید:

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

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

این متغیر فقط زمانی تنظیم می‌شود که بسته‌تان به‌عنوان کیت توسعه داده شود. اگر کد شما به‌عنوان پایگاه کد مستقل نیز مستقر شده است (به مرحله ۹ مراجعه کنید)، آن را اختیاری درنظر بگیرید یا درصورت نبودن آن، با پیامی واضح به‌سرعت شکست را اعلام کنید. اگر افزونه شما شناسه نمونه را به‌عنوان پارامتر سَمت کاربر نشان می‌دهد، باید آن پارامتر را بردارید، زیرا اکنون Firebase CLI مالک این مقدار است.

نمونه کارشده: حذف داده‌های کاربر

(افزونه Stream Cloud Firestore to BigQuery شناسه نمونه خود را نمی‌خواند، بنابراین چیزی برای انتقال در آنجا وجود ندارد. افزونه «حذف داده‌های کاربر» از آن برای نام‌گذاری موضوعات 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`,

بعد. A plain process.env read of FIREBASE_KIT_INSTANCE_ID used for two ordinary params so that users can override the topic names:

// 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 می‌گیرد، 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,

بعد. یک 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();
    // ...
  }
);

۵. انتقال تماس‌های صف وظیفه داخلی

برخی‌از افزونه‌ها بااستفاده از Firebase Admin SDK، کارها را از داخل کد تابع خود در صف‌های وظیفه خودشان قرار می‌دهند. این با دریافت تکلیف ارسال‌شده (که در بخش‌های توابع ارتقا و تبدیل قلاب‌های چرخه حیات پوشش داده شده است) متفاوت است. در اینجا کد شما تولیدکننده‌ای است که queue.enqueue(...) را فراخوانی می‌کند.

نسخه‌های قبلی Admin SDK به افزونه‌ها نیاز داشتند تا شناسه نمونه افزونه خود را به‌عنوان پارامتر دوم برای هدف‌یابی تابع «صف وظیفه» در همان افزونه ارسال کنند. از نسخه 14.2.0 firebase-admin، این کار نه الزامی است و نه توصیه می‌شود. اکنون Task Queue API به‌طور پیش‌فرض صف‌های تکلیف را در همان بافت (برای مثال، افزونه یا کیت) هدف قرار می‌دهد. برداشتن این پارامتر در کدتان هم به‌عنوان افزونه و هم به‌عنوان توابع مستقل ایمن است و توصیه می‌شود. برداشتن این پارامتر قابلیت حمل و سازگاری پیش‌رو را تضمین می‌کند.

همه چیزهای دیگر درباره فراخوانی صف‌بندی — مسیر منبع locations/<region>/functions/<name>، بار مفید تکلیف، و منطق تکرار مجدد شما — یکسان باقی می‌ماند.

برای جزئیات بیشتر درباره توابع صف‌بندی با Cloud Tasks، توابع صف‌بندی با Cloud Tasks را ببینید.

قبل. افزونه نسل اول:

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

بعد. افزونه نسل دوم:

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

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

اگر فراخوانی صف‌بندی شما پایگاه کد پیشوندی را هدف قرار دهد، نام تابع کشف‌شده نیز پیشوند دارد (برای مثال، orders-syncBigQuery)؛ نمونه کیت تابع جایگزین را مرور و نصب کنید و به‌عنوان کیت تابع آزمایش کنید.

‫۶. اعلام کردن میاناهای برنامه‌سازی کاربردی و نقش‌های IAM موردنیاز

الزامات IAM و میانای برنامه‌سازی کاربردی افزونه‌تان را از 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 یک حساب سرویس زمان اجرای مدیریت‌شده برای پایگاه کد ایجاد یا به‌روزرسانی می‌کند و اتحاد همه نقش‌های اعلام‌شده را به آن اعطا می‌کند. برای کاربرانتان مستند کنید که همه عملکردهای پایگاه کد با آن نقش‌ها اجرا می‌شود، مگراینکه میانای برنامه‌سازی کاربردی نهایی از مدل محدودتری پشتیبانی کند.

نمونه کارشده: جاری‌سازی Cloud Firestore به BigQuery

قبل. در extension.yaml اعلام شده است؛ زمان اجرای «افزونه‌ها» میانای برنامه‌سازی کاربردی را فعال کرد و نقش‌ها را به حساب مدیریت‌شده اعطا کرد:

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 را منتشر می‌کند، همچنین باید نقش‌ها و میاناهای برنامه‌سازی کاربردی لازم را برای انتشار رویدادها تنظیم کنید. این مورد قبلاً توسط «افزونه‌ها» بدون نیاز به هیچ تغییری در 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."
  );
}

‫۷. تبدیل کردن قلاب‌های چرخه حیات

اگر افزونه شما getExtensions().runtime() را فراخوانی می‌کند (برای مثال، setProcessingState یا setFatalError)، آن فراخوانی‌ها را حذف کنید، زیرا اگر از تابع نسل دوم که به‌طور معمول مستقر شده است فراخوانی شوند، خطا ایجاد می‌کنند. وضعیت چرخه حیات اکنون توسط 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 به‌صورت دستی دوباره اجرا کنند.

‫۸. راه‌اندازی سند برای کاربران شما

بسته README را بنویسید که حداقل موارد زیر را توضیح دهد:

  • مقادیر .env که بسته نیاز دارد.
  • اطلاعات محرمانه‌ای که بسته نیاز دارد و نحوه انتقال مقادیر محرمانه موجود.
  • نقش‌های IAM که بسته با requiresRole(...) اعلام می‌کند.
  • میاناهای برنامه‌سازی کاربردی Google که بسته فعال می‌کند یا به آن‌ها نیاز دارد.
  • The lifecycle hooks that the package declares, and how to rerun them manually.
  • یادداشت‌های صورت‌حساب.
  • چه چیزی در مقایسه با افزونه اصلی تغییر کرده است.
  • نحوه دریافت شناسه نمونه توسط بسته (FIREBASE_KIT_INSTANCE_ID تنظیم‌شده توسط رابط خط فرمان) و اینکه همه توابع نمونه با پیشوند kit-<instanceId>- مستقر می‌شوند.

نمونه کارشده: جاری‌سازی Cloud Firestore به BigQuery

بسته README جدول واقعی «چه چیزی تغییر کرده است» را ارسال می‌کند:

نگرانی به‌عنوان افزونه به‌عنوان @firebase-function-kits/firestore-bigquery-export
پیکربندی پارامترهای افزونه Cloud Functions params via .env
مدیریت هویت و دسترسی اعطاشده توسط «افزونه‌ها» ‫requiresRole(...)، اعمال‌شده در زمان استقرار
درحال آماده‌سازی تکلیف چرخه حیات توسط «افزونه‌ها» ‫afterFirstDeploy / afterRedeploy تکلیف
نام‌های تابع ext-<instanceId>-fsexportbigquery fsexportbigquery (اختیاراً پیشونددار)
شناسه نمونه ‫EXT_INSTANCE_ID تزریق‌شده توسط افزونه‌ها ‫FIREBASE_KIT_INSTANCE_ID، تنظیم‌شده توسط CLI از firebase.json

‫۹. آزمایش کردن تابع نسل دوم

اکنون باید تابع نسل دومی داشته باشید که وقتی مستقر می‌شود، رفتاری مشابه با نصب جدید افزونه شما داشته باشد. مرحله بعدی درستی‌سنجی و رفع هرگونه مشکلی است که به‌طور تصادفی درطول مسیر ایجاد شده است.

برای فراخوانی setGlobalOptions برای تنظیم گزینه‌های سراسری مانند منطقه پیش‌فرض یا CPU، باید این کار را فقط هنگام استقرار کیت خود به‌عنوان یک تابع مستقل نسل دوم انجام دهید. وقتی کیت شما به‌عنوان بسته 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 استفاده می‌کنید و تابع نسل دوم تبدیل‌شده را در پروژه آزمایشی با منابع مناسب برای آزمایش رفتار آن مستقر کنید. اگر قبلاً پروژه آزمایشی راه‌اندازی‌شده از آزمایش افزونه‌تان دارید، فرمان زیر را اجرا کنید:

firebase deploy --only functions

جادوگر حاصل را که از شما مقادیر پارامتر را درخواست می‌کند، به همان روشی که فرم نصب را در کنسول Firebase برای افزونه پر می‌کردید، تکمیل کنید.

نمونه کارشده: جاری‌سازی Cloud Firestore به BigQuery

همگام‌سازی سرتاسر Cloud Firestore به BigQuery را درستی‌سنجی می‌کنیم:

  1. در صفحه Cloud Firestore کنسول Firebase، مجموعه‌ای را که به‌عنوان COLLECTION_PATH (users) تنظیم کرده‌اید ایجاد کنید (اگر ازقبل وجود ندارد).
  2. سندی به‌نام bigquery-mirror-test ایجاد کنید که حاوی فیلدهایی با مقادیر مختلف باشد.
  3. در صفحه BigQuery کنسول Google Cloud، جدول گزارش تغییرات خام را پُرسمان کنید. این فایل باید حاوی یک ردیف باشد که ایجاد سند را ثبت کند:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
    
  4. آخرین نمای را پُرسمان کنید که باید آخرین رویداد تغییر را برای تنها سند موجود (bigquery-mirror-test) برگرداند:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
    
  5. سند bigquery-mirror-test را در Cloud Firestore حذف کنید. از نمای جدیدترین ناپدید می‌شود و رویداد DELETE به جدول گزارش تغییرات خام اضافه می‌شود.

    می‌توانید سابقه کامل یک سند را با موارد زیر بررسی کنید:

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

تفاوت‌های آزمایش کردن افزونه:

  • راه‌انداز به‌صورت fsexportbigquery (بدون پیشوند هنگام استقرار به‌عنوان تابع نسل دوم معمولی و نه کیت) مستقر می‌شود، نه ext-<instanceId>-fsexportbigquery. آن نام را در داشبورد و گزارش‌های Cloud Functions جستجو کنید.
  • اکنون کد شما در Firebase Local Emulator Suite به‌عنوان توابع عادی اجرا می‌شود. می‌توانید مقدار پارامترها را برای استفاده در شبیه‌ساز با .env.local تنظیم کنید. همچنین می‌توانید کدتان را بااستفاده از کیت توسعه نرم‌افزار firebase-functions-test همان‌طور که در آزمایش واحد Cloud Functions شرح داده شده است، آزمایش واحد کنید.
  • تأمین دیگر توسط زمان اجرای «افزونه‌ها» انجام نمی‌شود. اگر جدول گزارش تغییرات پس‌از استقرار وجود ندارد، تکلیف راه‌اندازی را به‌صورت دستی دوباره اجرا کنید: firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. این تکلیف هم‌توان است، بنابراین اجرای مجدد آن مجموعه داده، جدول، و نماها را تطبیق می‌دهد.
  • مقادیر پارامتر از .env می‌آید نه از فرم نصب، بنابراین اجرای مجدد firebase deploy پس‌از تکمیل .env غیرتعاملی است.

‫۱۰. کیت تابع خود را در npm منتشر کنید

پس‌از اعتبارسنجی تبدیل از افزونه به تابع نسل دوم، می‌توانید بااستفاده از یکی از راهنماهای زیر، نامزد انتشار را برای آزمایش سرتاسر در 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 (این کار در Git ثبت و برچسب‌گذاری می‌شود اگر package.json در ریشه مخزن باشد):

npm version prerelease --preid rc

برای انتشار نامزد انتشار و ثبت @your-org/your-kit@0.0.2-rc.4 در npm تحت برچسب next:

npm publish

ممکن است چند دقیقه طول بکشد تا وب‌سایت npm نسخه جدید را نشان دهد؛ npm view مستقیماً رجیستری را می‌خواند:

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

وقتی مرحله ۱۱ و مرحله ۱۲ این راهنما تکمیل شد، می‌توانید بسته را به نسخه پایدار ارتقا دهید:

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 را در بسته‌تان بگنجانید؛ اگر این کار را نکنید، رابط خط فرمان هنگام نصب به کاربران هشدار می‌دهد. این کار تضمین می‌کند که کاربران دقیقاً از همان وابستگی‌هایی استفاده کنند که شما آزمایش کرده‌اید و به محافظت دربرابر حملات زنجیره تأمین کمک می‌کند. بااین‌حال، 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 را برای پیوند دفتر ثبت npm اضافه کنید تا به پوشه صحیح اشاره کند. CHANGELOG.md آن حاوی یادداشت‌هایی برای نسخهٔ پخش معلق است.

‫۱۱. آزمایش به‌عنوان کیت تابع

پس‌از انتشار کیت، توصیه می‌کنیم کیتتان را بااستفاده از 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 فرمان استقرار مشابهی را با شناسه دقیق نمونه‌ای که درطول نصب انتخاب کرده‌اید چاپ می‌کند.

کیت را دوباره بااستفاده از دستورالعمل‌های مرحله ۹ اعتبارسنجی کنید. تابع نسل دوم را آزمایش کنید. اکنون که بااستفاده از کیت‌ها استقرار می‌کنید، توابع شما پیشوند دارند و نام‌گذاری شده‌اند kit-<instance-id>-<method-name>. این کار به کیت‌ها اجازه می‌دهد چندین نمونه داشته باشند، و یک تابع را چندین بار در یک پروژه مستقر کنند، هرکدام با یک نام منحصربه‌فرد.

‫۱۲. آزمایش جایگزینی انتقال

می‌توانید یک نمونه افزونه فعال راه‌اندازی کنید و سپس از راهنمای انتقال کاربر (بااستفاده از firebase ext:migrate --package یا فرمان‌های CLI کیت‌های تابع) استفاده کنید تا آزمایش کیت تابع خود را به‌عنوان جایگزین انتقال تکمیل کنید.

‫۱۳. به کاربران و Google درباره جایگزین رسمی افزونه‌تان اطلاع دهید

وقتی کیت تابع جایگزین شما آماده شد و به‌عنوان بسته npm دردسترس قرار گرفت، کاربران باید به آن منتقل شوند، این جایگزین رسمی را به کاربران و Google اطلاع دهید. فایل README.md را در مخزن GitHub که میزبان افزونه شما است با اطلاعات زیر به‌روز کنید:

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