Firebase Extensions를 Cloud Functions로 이전

이 가이드에서는 지원 중단된 Firebase Extensions 환경에서 사용자가 Firebase (2세대) 코드베이스를 위해 자체 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으로 문의해 주세요. 또한 2세대 함수 패키징, 테스트, 배포에 관한 자세한 정보를 가이드에 업데이트할 때 이 그룹에 이메일을 보내드릴 예정입니다.

이 그룹에 가입하려면 firebase-extensions-migrator-support-external+subscribe@google.com으로 메시지를 보내세요. 그러면 회원 가입 요청 이메일이 전송됩니다. '이 그룹에 가입' 버튼을 클릭하지 말고 이메일에 답장해야 합니다.

시작하기 전에

이 마이그레이션을 서면으로 완료하려면 Cloud Functions의 다음 기능을 사용합니다.

  • 매개변수화된 구성. extension.yaml에서 선언하는 각 매개변수는 패키지 코드에서 정의된 매개변수가 됩니다.

  • 선언적 IAM 역할 및 필수 API extension.yaml에 선언된 각 역할은 requiresRole(...) 호출이 되고 각 API는 패키지 코드에서 requiresAPI(...) 호출이 됩니다. 배포 시 Firebase CLI는 선언된 역할을 관리형 런타임 서비스 계정에 부여하고 선언된 API를 대신 사용 설정합니다.

  • Cloud Functions 코드베이스의 수명 주기 이벤트 이제 Cloud Functions 코드베이스에서 Firebase Extensions와 유사한 수명 주기 이벤트를 지원합니다. 수명 주기 후크 afterFirstDeploy(...) 및 afterRedeploy(...)를 사용하여 설치 시간 및 업데이트 시간 설정을 선언합니다. 이는 extension.yaml에서 선언하는 lifecycleEvents를 대체합니다.

Firebase Extensions 소스를 2세대 함수로 마이그레이션

(선택사항) Firebase 에이전트 스킬을 사용한 자동화된 이전

공식 extension-to-functions-codebase AI 에이전트 기술을 사용하여 1~8단계 (리소스 인벤토리 작성, 업그레이드 트리거, 매개변수 및 비밀 변환, 선언적 IAM, 수명 주기 후크, 패키지 README 생성)를 자동화할 수 있습니다.

스킬 설치

나 또는 AI 코딩 어시스턴트 (Firebase, Cursor, Claude Code, GitHub Copilot의 Gemini)가 아직 스킬을 설치하지 않은 경우 스킬 CLI를 사용하여 다음 명령어를 실행합니다.

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

스킬이 프로젝트에 설치되면 AI 코딩 어시스턴트가 마이그레이션 규칙과 변환 단계를 자동으로 따릅니다. 다음 프롬프트를 사용할 수 있습니다.

'extension-to-functions-codebase 스킬의 안내에 따라 이 Firebase 확장 프로그램을 게시 가능한 2세대 함수 키트 패키지로 이전해 줘.'

1. 확장 프로그램 인벤토리

먼저 확장 프로그램의 인벤토리를 작성합니다. 확장 프로그램이 선언하고, 제공하고, 문서화하는 모든 항목의 전체 목록으로, 모든 동작이 2세대 함수에서 정의된 대상을 갖도록 하고 이전 중에 손실되는 항목이 없도록 합니다.

다음을 검토하고 확인한 내용을 기록합니다.

  • extension.yaml: 매개변수, 함수, 이벤트, IAM 역할, 필수 API, 보안 비밀, 수명 주기 후크를 선언합니다.

  • 함수 코드, 종속 항목, 빌드 구성, 트리거, 태스크 큐 함수가 포함된 functions/

  • 설정 단계, 경고, 결제 메모가 포함된 README.md, PREINSTALL.md, POSTINSTALL.md

  • scripts/: 가져오기, 백필, IAM, 복구 또는 이전 유틸리티와 확장 프로그램과 함께 제공되는 기타 도구가 포함됩니다.

그런 다음 extension.yaml의 각 항목에 대해 npm 패키지에서 항목이 어디로 이동하는지 결정합니다.

  • 사용자 구성을 Cloud Functions 매개변수로 변환합니다 (4단계).

  • 보안 비밀을 Cloud Functions 보안 비밀로 변환합니다(4단계).

  • IAM 역할을 requiresRole(...) 선언으로 변환합니다 (6단계).

  • 필요한 Google API를 적절한 requiresAPI(...) 선언으로 변환합니다 (6단계).

  • 설치 및 업데이트 후크를 afterFirstDeploy(...) 및 afterRedeploy(...) 선언으로 변환합니다 (7단계).

  • 인스턴스 ID를 EXT_INSTANCE_ID에서 FIREBASE_KIT_INSTANCE_ID로 변환합니다(4단계).

작업 예: Cloud Firestore를 BigQuery로 스트리밍

firestore-bigquery-export/extension.yaml 및 functions/을 읽으면 다음 인벤토리가 생성됩니다.

extension.yaml 개수 / 값 데이터가 전송되는 위치
params 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) Cloud Functions params (4단계)
apis bigquery.googleapis.com requiresAPI(...) (6단계)
roles bigquery.dataEditor, datastore.user, bigquery.user requiresRole(...) (6단계)
resources 이벤트 트리거 1개 (fsexportbigquery) + 태스크 큐 함수 (initBigQuerySync, setupBigQuerySync) 내보낸 패키지 함수 (3단계)
lifecycleEvents onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy / afterRedeploy (7단계)
인스턴스 ID 사용되지 않음 (EXT_INSTANCE_ID 읽기 없음) 마이그레이션할 항목 없음
scripts/ import/ (백필), gen-schema-view/ 스크립트로 유지됨 (여기서는 범위 외)

분석. 확장 프로그램이 type: secret 매개변수를 선언하지 않으므로 4단계에서 보안 비밀을 이전할 필요가 없습니다. 이벤트 트리거는 이미 2세대입니다. 태스크 큐 함수만 아직 1세대입니다 (3단계와 관련됨).

2. package.json 업데이트

확장 프로그램의 package.json 파일을 업데이트합니다. 확장 프로그램을 하나만 이전하는 경우 루트 package.json일 수 있습니다. 하나의 저장소에서 여러 확장 프로그램을 이전하는 경우 각 확장 프로그램에 자체 패키지를 부여하세요.

최소 SDK 버전: firebase-functions >= 7.4.0 및 firebase-admin >= 14.2.0을 종속 항목으로 선언합니다. 사용자의 Cloud Functions 프로젝트에 라이브러리가 작성된 SDK와 동일한 버전이 있도록 firebase-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는 비공개이며 확장 프로그램 ID를 지정하고 firebase-functions를 직접 종속 항목으로 선언합니다.

{
  "name": "firestore-bigquery-export",
  "main": "lib/index.js",
  "private": true,
  "dependencies": {
    "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
    "firebase-admin": "^14.2.0",
    "firebase-functions": "^6.3.2"
  }
}

After. 게시 가능한 패키지: 범위가 지정된 이름, exports 맵, firebase-functions이 peerDependencies로 이동됨:

{
  "name": "@firebase-function-kits/firestore-bigquery-export",
  "version": "0.1.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/index.d.ts",
      "default": "./lib/index.js"
    }
  },
  "engines": {
    "node": "22"
  },
  "peerDependencies": {
    "firebase-functions": "^7.4.0"
  },
  "dependencies": {
    "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
    "firebase-admin": "^14.2.0",
    "firebase-functions": "^7.4.0"
  }
}

3. 1세대에서 2세대로 함수 업그레이드

확장에서 여전히 1세대 함수를 내보내는 경우 각 트리거를 2세대 트리거로 변환합니다. firebase-functions/... 모듈에서 가져오고 트리거 옵션에서 런타임 설정을 전달합니다.

Cloud Functions 2세대 업그레이드 가이드를 참고하세요. 특히 2세대 패치 이벤트 구조 분해를 사용하여 다시 작성하는 노력을 최소화할 수 있으며, 2세대 SDK는 v1 매개변수를 이벤트 객체의 필드로 노출하므로 구조 분해된/명명된 매개변수를 사용하여 비즈니스 로직을 변경하지 않아도 됩니다.

이전 1세대:

import * as functions from "firebase-functions/v1";

export const sync = functions.firestore
  .document("{collectionId}/{documentId}")
  .onWrite(async (change, context) => {
    await handleWrite(change.before, change.after, context.params);
  });

After. 2세대:

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

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

1세대 함수와 2세대 함수의 차이점 전체 목록은 Cloud Functions 버전 비교를 참고하세요.

1세대 함수와 2세대 함수의 중요한 차이점은 2세대 함수가 트리거 리소스와 동일한 위치에 있어야 한다는 것입니다. 사용자가 1세대 함수를 사용하는 확장 프로그램에서 2세대 함수를 사용하는 키트로 이전하는 경우 이 요구사항을 충족하기 위해 함수 위치를 변경해야 할 수 있습니다.

기존 위치가 유지되므로 사용자가 선택한 Cloud Functions 위치가 이전 중에 중단될 수 있습니다. 확장 프로그램의 이벤트 트리거 함수가 사용자가 제공한 함수 위치에 의존하는 경우 확장 프로그램에 새 매개변수를 추가하여 이벤트 트리거 위치를 수집하고 이를 새 함수 위치에 매핑하는 것이 좋습니다.

작업 예: Cloud Firestore를 BigQuery로 스트리밍

defineString("DATABASE_REGION", {
  label: "Firestore Instance Location",
  description:
    "Where is the Firestore database located? You can check your current database location at https://console.cloud.google.com/firestore/databases. The functions in this kit deploy to the Cloud Run region closest to this location.",
  input: select({
    "Multi-region (Europe - Belgium and Netherlands)": "eur3",
    "Multi-region (United States)": "nam5",
    "Multi-region (Iowa, North Virginia, and Oklahoma)": "nam7",
    "Iowa (us-central1)": "us-central1",
    // More locations...
  })
});

// Firestore multi-region locations are not Cloud Run regions; deploying a
// function to one hard-fails, so they map to a region inside the multi-region.
const MULTI_REGION_TO_FUNCTION_REGION: Record<string, string> = {
  nam5: "us-central1",
  nam7: "us-central1",
  eur3: "europe-west1",
};

/**
 * Maps a Firestore database location to the Cloud Run region the functions
 * should deploy to. The lookup is case-insensitive and ignores surrounding
 * whitespace, as the CLI's own region handling is. Regional locations pass
 * through lowercased; an unset or blank location returns `undefined`, meaning
 * the functions declare no region.
 */
export function firestoreLocationToFunctionRegion(
  location: string | undefined
): string | undefined {
  const normalized = location?.trim().toLowerCase();
  if (!normalized) {
    return undefined;
  }
  return MULTI_REGION_TO_FUNCTION_REGION[normalized] ?? normalized;
}

const functionRegion = firestoreLocationToFunctionRegion(
  process.env.DATABASE_REGION
);

export const fsexportbigquery = onDocumentWritten(
  {
    region: functionRegion,
    // Other configuration
  },
  (event) => handleDocumentWrite(event, getHandlerContext())
);

키트를 처음 배포할 때 사용자에게 DATABASE_REGION가 표시되고 함수가 위의 해당 functionRegion에 배포되어 2세대 이벤트 트리거 함수의 위치 문제가 해결됩니다.

4. 확장 프로그램 매개변수 및 보안 비밀 변환

Params

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,

After. defineString 하나. CLI가 이를 검색하고 .env에서 읽습니다.

// src/config.ts
import { defineString } from "firebase-functions/params";

collectionPath: defineString("COLLECTION_PATH", {
  label: "Collection path",
  // We now support "nonEmpty: true" to ensure a value other than the empty string
  // is entered, analogous to "required: true" in extension.yaml
  input: { text: { nonEmpty: true } }
}),

매개변수 이름은 변경되지 않으므로 기존 .env는 계속 작동합니다.

인스턴스 ID

확장 프로그램은 확장 프로그램 런타임에 의해 삽입된 EXT_INSTANCE_ID에서 인스턴스 ID를 읽습니다. 함수 키트는 FIREBASE_KIT_INSTANCE_ID에서 인스턴스 ID를 읽습니다. 는 Firebase CLI가 각 키트 인스턴스에 대해 firebase.json의 instances 맵에 있는 인스턴스의 키로 설정합니다. CLI는 배포 시간 검색 중에, 에뮬레이터에서, 배포된 함수에 이를 제공합니다.

인스턴스 ID는 매개변수가 아니므로 defineString로 선언하지 마세요. 실제로 FIREBASE_...는 .env 파일에서 예약된 접두사이므로 사용자는 이를 설정하거나 재정의할 수 없습니다. CLI가 삽입하는 값은 params 시스템에 표시되지 않습니다. 환경에서 직접 읽습니다.

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

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

이 변수는 패키지가 키트로 배포된 경우에만 설정됩니다. 코드도 독립형 코드베이스로 배포되는 경우 (9단계 참고) 선택사항으로 취급하거나 누락된 경우 명확한 메시지와 함께 빠르게 실패합니다. 확장 프로그램이 인스턴스 ID를 사용자 대상 매개변수로 노출한 경우 이제 Firebase CLI가 값을 소유하므로 해당 매개변수를 삭제해야 합니다.

작동 예시: 사용자 데이터 삭제

(스트림 Cloud Firestore~BigQuery 확장 프로그램은 인스턴스 ID를 읽지 않으므로 마이그레이션할 항목이 없습니다. 삭제 사용자 데이터 확장 프로그램은 이를 사용하여 Pub/Sub 주제의 이름을 지정합니다.)

이전 확장 프로그램이 리소스에 사용하는 ext- 접두사가 있는 config.ts에서 원시 환경 변수로 읽습니다.

// functions/src/config.ts
discoveryTopic: `ext-${process.env.EXT_INSTANCE_ID}-discovery`,
deletionTopic: `ext-${process.env.EXT_INSTANCE_ID}-deletion`,

After. 사용자가 주제 이름을 재정의할 수 있도록 두 개의 일반 매개변수에 사용되는 일반 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."
  );
}
// ...

이 검사는 핸들러가 처음으로 구성을 확인하면 실행되므로 누락된 변수는 kit-undefined-* 주제에 자동으로 바인딩된 함수가 아닌 명확한 런타임 오류를 생성합니다. 배포 자체를 거부하려면 검색 중에 실행되도록 모듈 범위에서 검사를 실행하세요. CLI는 firebase.json에서 인스턴스 ID를 파생하므로 구성 가능한 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,

After. defineString 하나와 defineSecret 하나가 있습니다. CLI는 둘 다 검색하고 .env에서 읽습니다.

import { defineString, defineSecret } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";

const mailCollection = defineString("MAIL_COLLECTION", {
  label: "Email documents collection",
  default: "mail"
});

const smtpPassword = defineSecret("SMTP_PASSWORD", { label: "SMTP password" });

export const processQueue = onDocumentWritten(
  { document: `${mailCollection}/{documentId}`, secrets: [smtpPassword] },
  async (event) => {
    const collection = mailCollection.value();
    const password = smtpPassword.value();
    // ...
  }
);

5. 내부 작업 대기열 호출 마이그레이션

일부 확장 프로그램은 Firebase Admin SDK를 사용하여 함수 코드 내부에서 자체 작업 대기열에 작업을 대기열에 추가합니다. 이는 디스패치된 작업을 수신하는 것과는 다릅니다 (함수 업그레이드 및 수명 주기 후크 변환 섹션 참고). 여기서 코드는 queue.enqueue(...)를 호출하는 생산자입니다.

이전 버전의 Admin SDK에서는 확장 프로그램이 동일한 확장 프로그램의 태스크 큐 함수를 타겟팅하기 위해 자체 확장 프로그램 인스턴스 ID를 두 번째 매개변수로 전달해야 했습니다. firebase-admin 14.2.0부터는 필수가 아니며 권장되지도 않습니다. 이제 Task Queue API는 기본적으로 동일한 컨텍스트 (예: 확장 프로그램 또는 키트)의 태스크 큐를 타겟팅합니다. 확장 프로그램과 독립형 함수 모두에서 코드의 이 매개변수를 삭제해도 안전하며 삭제하는 것이 좋습니다. 이 매개변수를 삭제하면 이식성과 향후 호환성이 보장됩니다.

대기열에 추가 호출에 관한 다른 모든 항목(locations/<region>/functions/<name> 리소스 경로, 작업 페이로드, 재시도 로직)은 동일하게 유지됩니다.

Cloud Tasks로 함수를 큐에 추가하는 방법에 대한 자세한 내용은 Cloud Tasks로 큐에 함수 추가를 참고하세요.

이전 1세대 확장 프로그램:

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

const queue = getFunctions().taskQueue(
  `locations/${config.location}/functions/syncBigQuery`,
  process.env.EXT_INSTANCE_ID, // extension instance ID, injected by the runtime
);
await queue.enqueue(taskData);

After. 2세대 확장 프로그램:

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

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

대기열에 추가 호출이 접두사가 지정된 코드베이스를 타겟팅하는 경우 검색된 함수 이름에도 접두사가 지정됩니다 (예: orders-syncBigQuery). 대체 함수 키트 인스턴스 검토 및 설치 및 함수 키트로 테스트를 참고하세요.

6. 필수 API 및 IAM 역할 선언

확장 프로그램의 IAM 및 API 요구사항을 extension.yaml에서 코드에 이동합니다.

import { requiresAPI, requiresRole } from "firebase-functions";

requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");

선언적 보안을 사용하면 Firebase CLI가 코드베이스의 관리형 런타임 서비스 계정을 만들거나 업데이트하고 선언된 모든 역할의 합집합을 부여합니다. 최종 API가 더 좁은 모델을 지원하지 않는 한 코드베이스의 모든 함수가 이러한 역할로 실행된다고 사용자에게 설명합니다.

작업 예: Cloud Firestore를 BigQuery로 스트리밍

이전 extension.yaml에 선언됩니다. 확장 프로그램 런타임에서 API를 사용 설정하고 관리 계정에 역할을 부여했습니다.

apis:
  - apiName: bigquery.googleapis.com
roles:
  - role: bigquery.dataEditor
  - role: datastore.user
  - role: bigquery.user

After. requiresAPI 및 requiresRole로 코드에 선언됩니다.

import { requiresAPI, requiresRole } from "firebase-functions";

requiresAPI(
  "bigquery.googleapis.com",
  "Needed to write changelog rows and views"
);
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");

확장 프로그램이 Eventarc 이벤트를 게시하는 경우 이벤트를 게시하는 데 필요한 적절한 역할과 API도 설정해야 합니다. 이전에는 extensions.yaml을 변경하지 않아도 확장 프로그램에서 처리했습니다. 커스텀 Eventarc 채널이 사용될 때만 키트가 이러한 권한을 요청하도록 코드에서 조건부로 이를 실행할 수 있습니다.

if (!!process.env.EVENTARC_CHANNEL) {
  requiresRole("roles/eventarc.publisher");
  requiresAPI(
    "eventarcpublishing.googleapis.com",
    "Publishes the extension's custom events to its Eventarc channel."
  );
}

7. 수명 주기 후크 변환

확장 프로그램이 getExtensions().runtime() (예: setProcessingState 또는 setFatalError)을 호출하는 경우 이러한 호출을 삭제하세요. 정상적으로 배포된 2세대 함수에서 호출되면 오류가 발생하기 때문입니다. 이제 수명 주기 상태는 afterFirstDeploy 및 afterRedeploy에 의해 관리되며 이 상태 추적은 사용되지 않습니다.

Firebase Extensions는 사용자가 확장 프로그램을 설치, 업데이트 또는 재구성할 때 설정을 실행할 수 있습니다. npm 패키지에서 코드에 상응하는 수명 주기 작업을 선언합니다.

일회성 설정의 경우:

import { afterFirstDeploy } from "firebase-functions/lifecycle";
import { onTaskDispatched } from "firebase-functions/tasks";

export const runInitialSetup = onTaskDispatched(async (request) => {
  await initializeResources(request.data);
});

afterFirstDeploy({
  task: {
    function: "runInitialSetup",
    body: {}
  }
});

구성 또는 코드 업데이트의 경우:

import { afterRedeploy } from "firebase-functions/lifecycle";

afterRedeploy({
  task: {
    function: "runInitialSetup",
    body: { reconcile: true }
  }
});

수명 주기 작업을 멱등성으로 만드세요. 디스패치 또는 실행이 실패하면 사용자가 수동으로 다시 실행해야 할 수 있습니다.

firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME

작업 예: Cloud Firestore를 BigQuery로 스트리밍

이전 확장 프로그램 런타임에 의해 extension.yaml에서 lifecycleEvents이(가) 다음과 같이 실행됩니다.

lifecycleEvents:
  onInstall:
    function: initBigQuerySync
    processingMessage: Configuring BigQuery Sync.
  onUpdate:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync
  onConfigure:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync

After. 코드에 선언됩니다. 작업은 첫 번째 배포 시 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 패키지는 구체적인 '변경사항' 테이블을 제공합니다.

문제 확장 프로그램으로 @firebase-function-kits/firestore-bigquery-export 형태
구성 확장 프로그램 매개변수 .env을 통한 Cloud Functions 매개변수
IAM 확장 프로그램에서 부여 requiresRole(...), 배포 시 적용됨
프로비저닝 확장 프로그램별 수명 주기 작업 afterFirstDeploy / afterRedeploy 작업
함수 이름 ext-<instanceId>-fsexportbigquery fsexportbigquery (선택적으로 접두사 지정)
인스턴스 ID EXT_INSTANCE_ID 확장 프로그램에 의해 삽입됨 FIREBASE_KIT_INSTANCE_ID: firebase.json에서 CLI에 의해 설정됨

9. 2세대 함수 테스트

이제 배포 시 확장 프로그램의 새 설치와 동일하게 동작하는 2세대 함수가 있습니다. 다음 단계는 그 과정에서 실수로 발생한 문제를 확인하고 수정하는 것입니다.

setGlobalOptions을 호출하여 기본 리전이나 CPU와 같은 전역 옵션을 설정하려면 키트를 독립형 2세대 함수로 배포할 때만 그렇게 해야 합니다. 키트가 npm 패키지로 설치되면 사용자는 래핑 코드에서 setGlobalOptions를 호출하여 이러한 매개변수를 구성하며, 이 작업이 두 번 발생하면 경고가 표시됩니다. FIREBASE_KIT_INSTANCE_ID 환경 변수를 확인하여 이 호출을 보호할 수 있습니다.

import { setGlobalOptions } from "firebase-functions";

if (!process.env.FIREBASE_KIT_INSTANCE_ID) {
  setGlobalOptions({
    region: "us-east1",
    maxInstances: 10,
  });
}

firebase-tools >= 15.32.0를 사용하고 있는지 확인하고, 변환된 2세대 함수를 동작을 테스트할 적절한 리소스가 있는 테스트 프로젝트에 배포합니다. 확장 프로그램 테스트에서 이미 테스트 프로젝트를 설정한 경우 다음 명령어를 실행합니다.

firebase deploy --only functions

확장 프로그램의 Firebase 콘솔에서 설치 양식을 작성하는 것과 동일한 방식으로 매개변수 값을 묻는 결과 마법사를 작성합니다.

작업 예: Cloud Firestore를 BigQuery로 스트리밍

Cloud Firestore~BigQuery 동기화가 엔드 투 엔드로 확인됩니다.

  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
    

확장 프로그램 테스트와의 차이점:

  • 트리거는 ext-<instanceId>-fsexportbigquery이 아닌 fsexportbigquery로 배포됩니다 (일반적인 2세대 함수로 배포되고 키트가 아닌 경우 접두사 없음). Cloud Functions 대시보드와 로그에서 해당 이름을 찾습니다.
  • 이제 코드가 Firebase Local Emulator Suite에서 일반 함수로 실행됩니다. .env.local를 사용하여 에뮬레이터에서 사용할 파라미터 값을 설정할 수 있습니다. Cloud Functions 단위 테스트에 설명된 대로 firebase-functions-test SDK를 사용하여 코드를 단위 테스트할 수도 있습니다.
  • 프로비저닝이 더 이상 확장 프로그램 런타임에 의해 실행되지 않습니다. 배포 후 변경사항 로그 표가 누락된 경우 설정 작업을 수동으로 다시 실행합니다(firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME). 이 작업은 멱등성이 있으므로 다시 실행하면 데이터 세트, 테이블, 뷰가 조정됩니다.
  • 매개변수 값은 설치 양식이 아닌 .env에서 가져오므로 .env가 완료되면 firebase deploy의 재실행은 비대화형입니다.

10. npm에 함수 키트 게시

확장 프로그램에서 2세대 함수로의 변환을 검증한 후 다음 가이드 중 하나를 사용하여 엔드 투 엔드 테스트를 위해 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로 증분하려면 다음을 실행합니다(package.json이 저장소 루트에 있는 경우 Git에서 커밋 및 태그 지정).

npm version prerelease --preid rc

출시 후보를 게시하고 next 태그 아래 npm에 @your-org/your-kit@0.0.2-rc.4를 등록하려면 다음을 실행하세요.

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로 실패할 수 있습니다. 게시된 shrinkwrap 사본에서 "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" }
}

이 경우 키트는 모노레포에 있으므로 npm 레지스트리 링크가 올바른 폴더를 가리키도록 repository.directory를 포함하는 것이 중요합니다. CHANGELOG.md에는 대기 중인 출시의 메모가 포함됩니다.

11. 함수 키트로 테스트

키트를 게시한 후에는 npm을 사용하여 키트를 테스트하는 것이 좋습니다.

firebase-tools 버전 >= 15.32.0을 사용하고 키트를 설치해야 합니다.

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

이렇게 하면 npm에서 패키지가 다운로드되고, 키트의 새 소스 디렉터리 내에 설정되며, 확장 프로그램 설치 흐름과 유사하게 첫 번째 인스턴스를 구성하는 과정을 안내합니다. 패키지를 로컬로 설치하고 설정한 후 배포를 실행하여 Google Cloud 프로젝트에 리소스를 만듭니다.

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

설치 후 Firebase CLI는 설치 중에 선택한 정확한 인스턴스 ID와 유사한 배포 명령어를 출력합니다.

9단계. 2세대 함수를 테스트합니다. 이제 키트를 사용하여 배포하므로 함수에 kit-<instance-id>-<method-name>이라는 접두사가 붙고 이름이 지정됩니다. 이를 통해 키트가 여러 인스턴스를 가질 수 있으며, 프로젝트에서 동일한 함수를 여러 번 배포할 수 있습니다. 각 인스턴스에는 고유한 이름이 있습니다.

12. 이전 대체 테스트

작동하는 확장 프로그램 인스턴스를 설정한 다음 사용자 이전 가이드(firebase ext:migrate --package 또는 함수 키트 CLI 명령어 사용)를 사용하여 이전 대체 항목으로 함수 키트 테스트를 완료할 수 있습니다.

13. 사용자와 Google에 공식 확장 프로그램 교체 알림

함수 키트 대체가 준비되고 사용자가 마이그레이션해야 하는 npm 패키지로 제공되면 사용자에게 이 공식 대체에 관해 알리고 Google에도 알려주세요. 확장 프로그램을 호스팅하는 GitHub 저장소에서 README.md 파일을 다음 정보로 업데이트합니다.

<!-- FIREBASE_EXTENSION_REPLACEMENT: extension="<your-extesion-id>" package="<your-npm-package-name>" -->
> [!WARNING]
> **Deprecation Notice:** The Firebase Extension `<your-extension>` is deprecated. Migrate to the [<your-npm-package-name>](<link-to-your-npm-package>) package.

Google은 알려진 확장 프로그램 리드미에서 <!-- 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.