העברת תוספים ל-Firebase אל Cloud Functions

במדריך הזה מוסבר איך להעביר את התוספים שלכם מהסביבה Firebase Extensions שיצאה משימוש לפונקציה שהמשתמשים שלכם מתקינים ופורסים ב-Cloud Functions שלהם עבור בסיס הקוד של Firebase (דור שני).

זהו נתיב ההעברה המומלץ. ‫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. נשלח אימייל לקבוצה הזו גם כשנעדכן את המדריך עם מידע נוסף על אריזה, בדיקה והפצה של פונקציות מהדור השני.

כדי להצטרף לקבוצה הזו, צריך לשלוח הודעה לכתובת firebase-extensions-migrator-support-external+subscribe@google.com. בתגובה, יישלח אימייל עם בקשה להצטרפות. צריך להשיב לאימייל הזה, ולא ללחוץ על הלחצן 'הצטרפות לקבוצה'.

לפני שמתחילים

כדי להשלים את ההעברה הזו כמו שהיא כתובה, תשתמשו בתכונות הבאות של Cloud Functions:

  • הגדרה עם פרמטרים. כל פרמטר שמצהירים עליו ב-extension.yaml הופך לפרמטר מוגדר בקוד החבילה.

  • תפקידי IAM דקלרטיביים וממשקי API נדרשים. כל תפקיד שמצהירים עליו ב-extension.yaml הופך לקריאה של requiresRole(...), וכל API הופך לקריאה של requiresAPI(...) בקוד החבילה. בזמן הפריסה, ה-CLI של Firebase מקצה את התפקידים שהוגדרו לחשבון שירות של סביבת ריצה מנוהלת, ומפעיל את ממשקי ה-API שהוגדרו בשמכם.

  • אירועים במחזור החיים של בסיסי קוד Cloud Functions. בבסיסי קוד Cloud Functions יש עכשיו תמיכה באירועים של מחזור החיים, בדומה ל-Firebase Extensions. מגדירים את ההתקנה ואת העדכון באמצעות ה-lifecycle hooks‏ afterFirstDeploy(...) ו-afterRedeploy(...). ההגדרות האלה מחליפות את ההגדרות של lifecycleEvents שמוצהרות ב-extension.yaml.

העברת מקור Firebase Extensions לפונקציה מדור שני

(אופציונלי) העברה אוטומטית באמצעות Firebase Agent Skill

אתם יכולים להשתמש במיומנות הרשמית של סוכן ה-AI ‏extension-to-functions-codebase כדי לבצע אוטומטית את שלבים 1 עד 8 (ניהול מלאי המשאבים, הפעלת שדרוגים, המרות של פרמטרים וסודות, IAM הצהרתי, ווים של מחזור החיים ויצירת קובץ ה-README של החבילה).

התקנת הסקיל

אם אתם או העוזר האישי מבוסס-AI שלכם לתכנות (Gemini ב-Firebase, ‏ Cursor, ‏ Claude Code, ‏ GitHub Copilot) עדיין לא התקנתם את היכולת, מריצים את הפקודה הבאה באמצעות ה-CLI של היכולות:

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

אחרי שמתקינים את הכלי בפרויקט, עוזר ה-AI לתכנות פועל אוטומטית לפי כללי ההעברה ושלבי השינוי שלו. אפשר להשתמש בהנחיה הבאה:

"עליך להעביר את התוסף הזה של Firebase לחבילת פונקציות מהדור השני שניתן לפרסם, בהתאם להוראות שבextension-to-functions-codebase מיומנות".

1. מלאי התוסף

מתחילים במלאי של התוסף: רשימה מלאה של כל מה שהתוסף מצהיר, שולח ומתעד, כך שלכל התנהגות תהיה יעד מוגדר בפונקציה מהדור השני, ושום דבר לא יאבד במהלך ההעברה.

בודקים את כל אחד מהדברים הבאים ורושמים את מה שמצאתם:

  • ‫extension.yaml, שבו מצהירים על הפרמטרים, הפונקציות, האירועים, תפקידי ה-IAM, ממשקי ה-API הנדרשים, הסודות וה-lifecycle hooks.

  • ‫functions/, שמכיל את קוד הפונקציה, יחסי התלות, הגדרות הבנייה, הטריגרים והפונקציות של תור המשימות.

  • ‫README.md,‏ PREINSTALL.md ו-POSTINSTALL.md, שכוללים שלבי הגדרה, אזהרות והערות בנושא חיוב.

  • ‫scripts/, שמכיל כלי ייבוא, מילוי חוסרים, IAM, תיקון או העברה, וכל כלי אחר שאתם שולחים יחד עם התוסף.

לאחר מכן, לכל פריט ב-extension.yaml, מחליטים איפה הוא יופיע בחבילת npm:

  • המרת הגדרות משתמש לפרמטרים של Cloud Functions (שלב 4).

  • המרת סודות לCloud Functions סודות (שלב 4).

  • המרת תפקידי IAM להצהרות requiresRole(...) (שלב 6).

  • המרת Google APIs נדרשים להצהרות requiresAPI(...) במקומות המתאימים (שלב 6).

  • המרת ווים של התקנה ועדכון להצהרות afterFirstDeploy(...) ו-afterRedeploy(...) (שלב 7).

  • המרת מזהי מופעים מ-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 params (שלב 4)
apis bigquery.googleapis.com requiresAPI(...) (שלב 6)
roles ‫bigquery.dataEditor,‏ datastore.user,‏ bigquery.user requiresRole(...) (שלב 6)
resources טריגר אחד לאירוע (fsexportbigquery) + פונקציות של תור משימות (initBigQuerySync, setupBigQuerySync) פונקציות של חבילה שיוצאו (שלב 3)
lifecycleEvents onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync ‫afterFirstDeploy / afterRedeploy (שלב 7)
מזהה מופע לא בשימוש (אין קריאות של EXT_INSTANCE_ID) אין מה להעביר
scripts/ import/ (מילוי חוסרים), gen-schema-view/ נשמרים כסקריפטים (לא רלוונטיים כאן)

ניתוח. התוסף לא מגדיר פרמטרים של type: secret, ולכן אין מה להעביר בשביל סודות בשלב 4. הטריגר של האירוע כבר הוא דור שני, רק הפונקציות של תור המשימות הן עדיין דור ראשון (רלוונטי בשלב 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 של התוסף הוא פרטי, מצוין בו מזהה התוסף, ומוצהר בו ש-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. שדרוג פונקציות מדור ראשון לדור שני

אם התוסף עדיין מייצא פונקציות מהדור הראשון, צריך להמיר כל טריגר למקבילה שלו מהדור השני. ייבוא מהמודולים firebase-functions/... והעברת הגדרות זמן ריצה באפשרויות הטריגר.

Cloud Functionsמדריך לשדרוג לדור השני חשוב לציין שאפשר לצמצם את המאמצים שנדרשים לשכתוב באמצעות פירוק מובנה של אירועים עם תיקון מהדור השני, ולהימנע משכתוב של הלוגיקה של הפונקציה, כי SDK מהדור השני חושף את הפרמטרים של גרסה 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);
  });

אחרי. דור שני:

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 כאן אפשר לראות רשימה מקיפה של ההבדלים בין פונקציות מהדור הראשון לבין פונקציות מהדור השני.

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() כדי לקרוא את המחרוזת בתוך handler, ומשתמשים ב-collectionPath ישירות במקום שבו צפוי placeholder, כמו נתיב להפעלת פונקציה.

ה-CLI של Firebase מאתר את הפרמטרים וקורא את הערכים שלהם מ-.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, שה-CLI של Firebase מגדיר לכל מופע של ערכת כלים את המפתח של המופע במפת instances ב-firebase.json. ממשק ה-CLI מספק אותו במהלך הגילוי בזמן הפריסה, באמולטור ובפונקציות שנפרסו.

מזהה המכונה הוא לא פרמטר, לכן לא צריך להצהיר עליו באמצעות defineString. למעשה, FIREBASE_... היא תחילית שמורה בקובצי .env, ולכן המשתמשים לא יוכלו להגדיר אותה או לבטל את ההגדרה שלה שם. הערכים שמוזרקים על ידי ה-CLI לא גלויים למערכת הפרמטרים. אפשר לקרוא אותו ישירות מהסביבה:

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

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

המשתנה יוגדר רק כשחבילת האפליקציה תיפרס כערכה. אם הקוד שלכם גם נפרס כבסיס קוד עצמאי (ראו שלב 9), אפשר להתייחס אליו כאל קוד אופציונלי או להציג הודעה ברורה אם הוא חסר. אם התוסף שלכם חשף את מזהה המופע כפרמטר שגלוי למשתמשים, אתם צריכים להסיר את הפרמטר הזה, כי עכשיו הערך שייך ל-Firebase CLI.

דוגמה: מחיקת נתוני משתמשים

(התוסף Stream Cloud Firestore to BigQuery לא קורא את מזהה המופע שלו, ולכן אין מה להעביר שם. היא משמשת את התוסף Delete User Data (מחיקת נתוני משתמש) כדי לתת שמות לנושאים ב-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`,

אחרי. קריאה פשוטה של process.env FIREBASE_KIT_INSTANCE_ID שמשמשת לשני פרמטרים רגילים כדי שהמשתמשים יוכלו לשנות את שמות הנושאים:

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

הבדיקה הזו מופעלת כשפונקציית handler פותרת את ההגדרה שלה בפעם הראשונה, ולכן משתנה חסר יוצר שגיאת זמן ריצה ברורה במקום פונקציות שקשורות ללא הודעה מוקדמת לנושאים של 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();
    // ...
  }
);

5. העברת קריאות פנימיות לתור משימות

חלק מהתוספים מוסיפים עבודה לתורי המשימות שלהם מתוך קוד הפונקציה שלהם, באמצעות Firebase Admin SDK. הפעולה הזו שונה מקבלת משימה שהוקצתה (הסבר מופיע בקטעים פונקציות שדרוג והמרת וובינר לשידור חי). במקרה הזה, הקוד הוא היצרן שקורא ל-queue.enqueue(...).

בגרסאות קודמות של Admin SDK, תוספים נדרשו להעביר מזהה משלהם של מופע התוסף כפרמטר שני כדי לטרגט פונקציה של תור משימות באותו תוסף. החל מגרסה firebase-admin 14.2.0, אין צורך בכך ולא מומלץ לעשות זאת. ה-API של תור המשימות מכוון עכשיו כברירת מחדל לתורי משימות באותו הקשר (לדוגמה, תוסף או ערכת כלים). מומלץ להסיר את הפרמטר הזה מהקוד, גם כתוסף וגם כפונקציות עצמאיות. הסרת הפרמטר הזה מבטיחה ניידות ותאימות קדימה.

כל שאר הפרטים לגבי הקריאה ל-enqueue – נתיב המשאב locations/<region>/functions/<name>, מטען הייעודי (payload) של המשימה והלוגיקה של הניסיון החוזר – נשארים ללא שינוי.

במאמר הוספת פונקציות לתור באמצעות 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). מידע נוסף זמין במאמרים בדיקה והתקנה של מופע של ערכת פונקציות להחלפה ובדיקה כערכת פונקציות.

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

באמצעות אבטחה הצהרתית, ה-CLI של Firebase יוצר או מעדכן חשבון שירות מנוהל של זמן ריצה בשביל בסיס הקוד, ומעניק לו את כל התפקידים שהוגדרו. צריך לציין למשתמשים שכל הפונקציות בבסיס הקוד פועלות עם התפקידים האלה, אלא אם ה-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");

7. המרת הוקים (hooks) של מחזור חיים

אם התוסף קורא ל-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.

8. הגדרת מסמכים למשתמשים

כותבים README לחבילה שכולל לפחות את הפרטים הבאים:

  • הערכים של .env שהחבילה דורשת.
  • הסודות שנדרשים לחבילה, ואיך מעבירים ערכי סודות קיימים.
  • תפקידי ה-IAM שהחבילה מצהירה עליהם באמצעות requiresRole(...).
  • ממשקי Google API שהחבילה מפעילה או שנדרשים להפעלה.
  • ה-lifecycle hooks שהחבילה מצהירה עליהם, ואיך להפעיל אותם מחדש באופן ידני.
  • הערות בנוגע לחיובים.
  • מה השתנה בהשוואה לתוסף המקורי.
  • איך החבילה מקבלת את מזהה המופע שלה (FIREBASE_KIT_INSTANCE_ID מוגדר על ידי ה-CLI) ואיך כל הפונקציות של המופע נפרסות עם התחילית kit-<instanceId>-.

דוגמה: סטרימינג של Cloud Firestore אל BigQuery

החבילה README שולחת טבלה קונקרטית עם פירוט השינויים:

בעיה כתוסף בתור @firebase-function-kits/firestore-bigquery-export
הגדרות פרמטרים של תוסף Cloud Functions params via .env
IAM ההרשאה ניתנה על ידי Extensions ‫requiresRole(...), הוחל במהלך הפריסה
ניהול הקצאות (Provisioning) משימה במחזור החיים באמצעות תוספים afterFirstDeploy מתוך afterRedeploy משימות
שמות הפונקציות ext-<instanceId>-fsexportbigquery ‫fsexportbigquery (עם קידומת אופציונלית)
מזהה מופע EXT_INSTANCE_ID הוחדר על ידי תוספים ‫FIREBASE_KIT_INSTANCE_ID, מוגדר על ידי ה-CLI מ-firebase.json

9. בדיקת פונקציה מהדור השני

עכשיו אמורה להיות לכם פונקציה מדור שני, שכשפורסים אותה, היא מתנהגת בדיוק כמו התקנה חדשה של התוסף. השלב הבא הוא לאמת ולתקן בעיות שנוצרו בטעות במהלך התהליך.

כדי להתקשר אל 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 SDK, כמו שמתואר במאמר בדיקות יחידה של Cloud Functions.
  • הקצאת ההרשאות כבר לא מתבצעת על ידי סביבת זמן הריצה של התוספים. אם טבלת יומן השינויים חסרה אחרי הפריסה, מריצים מחדש את משימת ההגדרה באופן ידני: firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. המשימה היא אידמפוטנטית, ולכן הפעלה חוזרת שלה תתאים את מערך הנתונים, הטבלה והתצוגות.
  • ערכי הפרמטרים מגיעים מ-.env ולא מטופס ההתקנה, ולכן הפעלות חוזרות של firebase deploy הן לא אינטראקטיביות אחרי ש-.env מסתיים.

10. פרסום ערכת הפונקציות ב-npm

אחרי שמוודאים שההמרה מהתוסף לפונקציה מהדור השני בוצעה, אפשר לפרסם גרסה מועמדת להפצה ב-npm לבדיקה מקצה לקצה באמצעות אחד מהמדריכים הבאים:

אחרי שמפרסמים את ערכת הכלים ב-npm, אפשר להתקין אותה באמצעות firebase functions:kits:install והיא מופיעה כחלופה הרשמית לתוסף.

מומלץ מאוד לפרסם קודם גרסה מועמדת להפצה. ההתקנה של ערכות מתבצעת לפי שם החבילה והגרסה, ולכן גרסת טרום-הפצה מאפשרת לכם לבדוק את תהליך ההתקנה האמיתי מול המאגר, בלי לחשוף חבילה לא גמורה למשתמשים שמתקינים באמצעות תג ברירת המחדל latest.

לפני הפרסום:

  1. בוחרים שם חבילה. אפשר להשתמש בשמות עם היקף ובשמות ללא היקף (ראו את המדריכים שלמעלה). שימו לב: חבילות עם היקף מוגדר הן פרטיות כברירת מחדל, ולכן צריך להעביר את --access public.
  2. לבנות ולבדוק מה נשלח. הנתיבים main ו-types מצביעים על הפלט המהודר (lib/ בדוגמה שלנו), ולכן צריך לכלול את הספרייה הזו בקובץ ה-tar שפורסם. אפשר להשתמש ב-.npmignore או ברשימת היתרים files, ולבדוק את התוצאה באמצעות npm pack --dry-run. סקריפט prepublishOnly שמריץ את ה-build מונע פרסום של פלט לא עדכני.

תוספות של 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

אחרי שתסיימו את שלב 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 build ‏(npm ci), שבו יכול להיות שערכים שרלוונטיים רק לפיתוח ייכשלו עם EBADPLATFORM. יכול להיות שתצטרכו להסיר את הערכים "dev": true ואת devDependencies מהעותק שפורסם של קובץ ה-shrinkwrap.

דוגמה: סטרימינג של 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" }
}

שימו לב שבמקרה הזה, ערכת הכלים נמצאת במאגר monorepo, ולכן חשוב לכלול את repository.directory כדי שהקישור למאגר npm יצביע על התיקייה הנכונה. בCHANGELOG.md יש הערות לגבי הגרסה בהמתנה.

11. בדיקה כערכת פונקציות

אחרי שמפרסמים את ערכת הכלים, מומלץ לבדוק אותה באמצעות npm.

מוודאים שאתם משתמשים בגרסה >= 15.32.0 של firebase-tools ומתקינים את ערכת הכלים:

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

הפקודה הזו מורידה את החבילה מ-npm, מגדירה אותה בספריית קובצי המקור חדשה עבור ערכת הכלים ומסבירה איך להגדיר את המופע הראשון, בדומה לתהליך ההתקנה של התוספים. אחרי שמתקינים ומגדירים את החבילה באופן מקומי, מריצים פריסה כדי ליצור את המשאבים בפרויקט Google Cloud:

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

אחרי ההתקנה, ה-CLI של Firebase מדפיס פקודת פריסה דומה עם מזהה המופע המדויק שבחרתם במהלך ההתקנה.

צריך לאמת שוב את ערכת הכלים באמצעות ההוראות שמפורטות בשלב 9. בודקים את הפונקציה מהדור השני. עכשיו, כשאתם מבצעים פריסה באמצעות ערכות, הפונקציות שלכם מקבלות קידומת ושם kit-<instance-id>-<method-name>. כך אפשר ליצור כמה מופעים של ערכות, לפרוס את אותה פונקציה כמה פעמים בפרויקט, וכל פעם לתת לה שם ייחודי.

12. בדיקת החלפת ההעברה

אפשר להגדיר מופע תוסף פעיל ואז להשתמש במדריך להעברת משתמשים (באמצעות firebase ext:migrate --package או פקודות CLI של ערכות פונקציות) כדי לסיים את הבדיקה של ערכת הפונקציות כחלופה להעברה.

13. הודעה למשתמשים ול-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-exportREADME.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.