firebase-tools 15.19.1 → 15.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -225,17 +225,18 @@ class AppDistributionClient {
225
225
  }
226
226
  utils.logSuccess(`Testers removed from group successfully`);
227
227
  }
228
- async createReleaseTest(releaseName, devices, aiInstructions, loginCredential, testCaseName, displayName) {
228
+ async createReleaseTest(releaseName, devices, options = {}) {
229
229
  try {
230
230
  const response = await this.appDistroV1AlphaClient.request({
231
231
  method: "POST",
232
232
  path: `${releaseName}/tests`,
233
233
  body: {
234
234
  deviceExecutions: devices.map((device) => ({ device })),
235
- loginCredential,
236
- testCase: testCaseName,
237
- aiInstructions: aiInstructions,
238
- displayName: displayName,
235
+ loginCredential: options.loginCredential,
236
+ testCase: options.testCaseName,
237
+ aiInstructions: options.aiInstructions,
238
+ displayName: options.displayName,
239
+ resultsBucket: options.resultsBucket,
239
240
  },
240
241
  });
241
242
  return response.body;
@@ -8,6 +8,7 @@ exports.getAppName = getAppName;
8
8
  exports.toAppName = toAppName;
9
9
  exports.parseTestDevices = parseTestDevices;
10
10
  exports.getLoginCredential = getLoginCredential;
11
+ exports.getResultsBucket = getResultsBucket;
11
12
  const fs = require("fs-extra");
12
13
  const error_1 = require("../error");
13
14
  const projectUtils_1 = require("../projectUtils");
@@ -137,3 +138,23 @@ function getLoginCredential(args) {
137
138
  function isPresenceMismatched(value1, value2) {
138
139
  return (value1 && !value2) || (!value1 && value2);
139
140
  }
141
+ const APP_NAME_REGEX = /^projects\/(?<projectNumber>[^\/]+)\/apps\/(?<appId>[^\/]+)$/;
142
+ const BUCKET_NAME_FORMAT_REGEX = /^[a-z0-9_.-]+$/;
143
+ function getResultsBucket(bucket, appName) {
144
+ if (!bucket) {
145
+ return undefined;
146
+ }
147
+ let bucketName = bucket;
148
+ if (bucketName.startsWith("gs://")) {
149
+ bucketName = bucketName.substring(5);
150
+ }
151
+ if (!BUCKET_NAME_FORMAT_REGEX.test(bucketName)) {
152
+ throw new error_1.FirebaseError(`Invalid results bucket format: ${bucket}`);
153
+ }
154
+ const match = APP_NAME_REGEX.exec(appName);
155
+ if (!match || typeof match.groups === "undefined") {
156
+ throw new error_1.FirebaseError(`Invalid app name: ${appName}`);
157
+ }
158
+ const { projectNumber } = match.groups;
159
+ return `projects/${projectNumber}/buckets/${bucketName}`;
160
+ }
@@ -39,9 +39,11 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
39
39
  .option("--test-non-blocking", "run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
40
40
  .option("--test-case-ids <string>", "a comma-separated list of test case IDs.")
41
41
  .option("--test-case-ids-file <file>", "path to file with a comma- or newline-separated list of test case IDs.")
42
+ .option("--results-bucket <bucket>", "The name of a Google Cloud Storage bucket where raw results of any automated tests will be stored. If this flag is not set, Firebase creates a bucket for you. Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.")
42
43
  .before(requireAuth_1.requireAuth)
43
44
  .action(async (file, options) => {
44
45
  const appName = (0, options_parser_util_1.getAppName)(options);
46
+ const resultsBucket = (0, options_parser_util_1.getResultsBucket)(options.resultsBucket, appName);
45
47
  const distribution = new distribution_1.Distribution(file);
46
48
  const releaseNotes = getReleaseNotes(options.releaseNotes, options.releaseNotesFile);
47
49
  const testers = (0, options_parser_util_1.parseIntoStringArray)(options.testers, options.testersFile);
@@ -58,9 +60,9 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
58
60
  usernameResourceName: options.testUsernameResource,
59
61
  passwordResourceName: options.testPasswordResource,
60
62
  });
61
- await distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, options.testNonBlocking, loginCredential);
63
+ await distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, options.testNonBlocking, loginCredential, resultsBucket);
62
64
  });
63
- async function distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, testNonBlocking, loginCredential) {
65
+ async function distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, testNonBlocking, loginCredential, resultsBucket) {
64
66
  const requests = new client_1.AppDistributionClient();
65
67
  let aabInfo;
66
68
  if (distribution.distributionFileType() === distribution_1.DistributionFileType.AAB) {
@@ -116,11 +118,18 @@ async function distribute(appName, distribution, testCases, testDevices, release
116
118
  utils.logBullet("starting automated test (note: this feature is in beta)");
117
119
  const releaseTestPromises = [];
118
120
  if (!testCases.length) {
119
- releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices, undefined, loginCredential));
121
+ releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices, {
122
+ loginCredential,
123
+ resultsBucket,
124
+ }));
120
125
  }
121
126
  else {
122
127
  for (const testCaseId of testCases) {
123
- releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices, undefined, loginCredential, `${appName}/testCases/${testCaseId}`));
128
+ releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices, {
129
+ loginCredential,
130
+ testCaseName: `${appName}/testCases/${testCaseId}`,
131
+ resultsBucket,
132
+ }));
124
133
  }
125
134
  }
126
135
  const releaseTests = await Promise.all(releaseTestPromises);
@@ -29,9 +29,11 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
29
29
  .option("--test-devices <string>", "Semicolon-separated list of devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
30
30
  .option("--test-devices-file <string>", "Path to file containing a list of semicolon- or newline-separated devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
31
31
  .option("--test-non-blocking", "Run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
32
+ .option("--results-bucket <bucket>", "The name of a Google Cloud Storage bucket where raw test results will be stored. If this flag is not set, Firebase creates a default bucket for you. Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.")
32
33
  .before(requireAuth_1.requireAuth)
33
34
  .action(async (target, options) => {
34
35
  const appName = (0, options_parser_util_1.getAppName)(options);
36
+ const resultsBucket = (0, options_parser_util_1.getResultsBucket)(options.resultsBucket, appName);
35
37
  const testDir = path.resolve(options.testDir || "tests");
36
38
  if (!(0, fsutils_1.dirExistsSync)(testDir)) {
37
39
  throw new error_1.FirebaseError(`Tests directory not found: ${testDir}. Use the --test-dir flag to choose a different directory.`);
@@ -60,7 +62,7 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
60
62
  utils.logBullet(`Using release ${release.displayVersion} created at ${release.createTime}`);
61
63
  }
62
64
  invokeSpinner.start();
63
- releaseTests = await invokeTests(client, release.name, tests, !testDevices.length ? defaultDevices : testDevices);
65
+ releaseTests = await invokeTests(client, release.name, tests, !testDevices.length ? defaultDevices : testDevices, resultsBucket);
64
66
  invokeSpinner.text = `${(0, parseTestFiles_1.pluralizeTests)(releaseTests.length)} started successfully!`;
65
67
  invokeSpinner.succeed();
66
68
  }
@@ -76,14 +78,18 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
76
78
  utils.logBullet(`View detailed results in the Firebase Console:\n${release.firebaseConsoleUri}`);
77
79
  }
78
80
  });
79
- async function invokeTests(client, releaseName, testDefs, devices) {
81
+ async function invokeTests(client, releaseName, testDefs, devices, resultsBucket) {
80
82
  try {
81
83
  const releaseTests = [];
82
84
  for (const testDef of testDefs) {
83
85
  const aiInstructions = {
84
86
  steps: testDef.testCase.steps,
85
87
  };
86
- releaseTests.push(await client.createReleaseTest(releaseName, devices, aiInstructions, undefined, undefined, testDef.testCase.displayName));
88
+ releaseTests.push(await client.createReleaseTest(releaseName, devices, {
89
+ aiInstructions,
90
+ displayName: testDef.testCase.displayName,
91
+ resultsBucket,
92
+ }));
87
93
  }
88
94
  return releaseTests;
89
95
  }
@@ -253,7 +253,7 @@ async function registerSourceMap(sourceMap) {
253
253
  const client = new apiv2_1.Client({
254
254
  urlPrefix: "https://firebasetelemetryadmin.googleapis.com",
255
255
  auth: true,
256
- apiVersion: "v1",
256
+ apiVersion: "v1alpha",
257
257
  });
258
258
  try {
259
259
  await client.patch(sourceMap.name, sourceMap, { queryParams: { allowMissing: "true" } });
@@ -7,7 +7,6 @@ function initDeployStats() {
7
7
  numBuildErrors: 0,
8
8
  numBuildWarnings: new Map(),
9
9
  numServiceCreated: 0,
10
- numServiceDeleted: 0,
11
10
  numSchemaMigrated: 0,
12
11
  numConnectorUpdatedBeforeSchema: 0,
13
12
  numConnectorUpdatedAfterSchema: 0,
@@ -24,7 +23,6 @@ function deployStatsParams(stats) {
24
23
  return {
25
24
  missing_billing: (!!stats.missingBilling).toString(),
26
25
  num_service_created: stats.numServiceCreated,
27
- num_service_deleted: stats.numServiceDeleted,
28
26
  num_schema_migrated: stats.numSchemaMigrated,
29
27
  num_connector_updated_before_schema: stats.numConnectorUpdatedBeforeSchema,
30
28
  num_connector_updated_after_schema: stats.numConnectorUpdatedAfterSchema,
@@ -9,7 +9,6 @@ const provisionCloudSql_1 = require("../../dataconnect/provisionCloudSql");
9
9
  const names_1 = require("../../dataconnect/names");
10
10
  const api_1 = require("../../api");
11
11
  const ensureApiEnabled = require("../../ensureApiEnabled");
12
- const prompt_1 = require("../../prompt");
13
12
  async function default_1(context, options) {
14
13
  const dataconnect = context.dataconnect;
15
14
  if (!dataconnect) {
@@ -31,29 +30,11 @@ async function default_1(context, options) {
31
30
  filters?.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
32
31
  });
33
32
  dataconnect.deployStats.numServiceCreated = servicesToCreate.length;
34
- const servicesToDelete = filters
35
- ? []
36
- : services.filter((s) => !serviceInfos.some((si) => matches(si, s)));
37
- dataconnect.deployStats.numServiceDeleted = servicesToDelete.length;
38
33
  await Promise.all(servicesToCreate.map(async (s) => {
39
34
  const { projectId, locationId, serviceId } = splitName(s.serviceName);
40
35
  await client.createService(projectId, locationId, serviceId);
41
36
  utils.logLabeledSuccess("dataconnect", `Created service ${s.serviceName}`);
42
37
  }));
43
- if (servicesToDelete.length) {
44
- const serviceToDeleteList = servicesToDelete.map((s) => " - " + s.name).join("\n");
45
- if (await (0, prompt_1.confirm)({
46
- force: false,
47
- nonInteractive: options.nonInteractive,
48
- message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${serviceToDeleteList}\nWould you like to delete these services?`,
49
- default: false,
50
- })) {
51
- await Promise.all(servicesToDelete.map(async (s) => {
52
- await client.deleteService(s.name);
53
- utils.logLabeledSuccess("dataconnect", `Deleted service ${s.name}`);
54
- }));
55
- }
56
- }
57
38
  utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
58
39
  await Promise.all(serviceInfos
59
40
  .filter((si) => {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_FUNCTION_REGION = exports.EVENTARC_SOURCE_ENV = void 0;
3
+ exports.EVENTARC_SOURCE_ENV = void 0;
4
4
  exports.prepare = prepare;
5
5
  exports.resolveDefaultRegionsForBuild = resolveDefaultRegionsForBuild;
6
6
  exports.inferDetailsFromExisting = inferDetailsFromExisting;
@@ -23,12 +23,7 @@ const runtimes = require("./runtimes");
23
23
  const supported = require("./runtimes/supported");
24
24
  const validate = require("./validate");
25
25
  const ensure = require("./ensure");
26
- const events = require("../../functions/events/v1");
27
- const firestore_1 = require("./services/firestore");
28
- const storage_1 = require("./services/storage");
29
- const database_1 = require("./services/database");
30
- const ailogic_1 = require("./services/ailogic");
31
- const names_1 = require("../../dataconnect/names");
26
+ const services_1 = require("./services");
32
27
  const api_1 = require("../../api");
33
28
  const functionsDeployHelper_1 = require("./functionsDeployHelper");
34
29
  const utils_1 = require("../../utils");
@@ -49,7 +44,6 @@ const functional_1 = require("../../functional");
49
44
  const prepare_1 = require("../extensions/prepare");
50
45
  const prompt = require("../../prompt");
51
46
  exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
52
- exports.DEFAULT_FUNCTION_REGION = "us-central1";
53
47
  async function prepare(context, options, payload) {
54
48
  const projectId = (0, projectUtils_1.needProjectId)(options);
55
49
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
@@ -234,7 +228,7 @@ async function prepare(context, options, payload) {
234
228
  async function resolveDefaultRegionsForBuild(buildObj, have) {
235
229
  for (const [id, endpoint] of Object.entries(buildObj.endpoints)) {
236
230
  if (!endpoint.region?.length || endpoint.region.includes(build.REGION_TBD)) {
237
- let resolvedRegion = exports.DEFAULT_FUNCTION_REGION;
231
+ let resolvedRegion = services_1.FALLBACK_DEPLOYMENT_REGION;
238
232
  let matching;
239
233
  for (const region of Object.keys(have.endpoints)) {
240
234
  if (have.endpoints[region][id]) {
@@ -249,109 +243,19 @@ async function resolveDefaultRegionsForBuild(buildObj, have) {
249
243
  }
250
244
  else {
251
245
  try {
252
- if (build.isBlockingTriggered(endpoint)) {
253
- resolvedRegion = resolveRegionForBlockingTrigger(endpoint.blockingTrigger);
254
- }
255
- else if (build.isEventTriggered(endpoint)) {
256
- resolvedRegion = await resolveRegionForEventTrigger(endpoint.project, endpoint.eventTrigger);
257
- }
246
+ resolvedRegion = await resolveRegionForTrigger(endpoint);
258
247
  }
259
248
  catch (err) {
260
- logger_1.logger.debug(`Failed to resolve region for endpoint ${id}. Defaulting to ${exports.DEFAULT_FUNCTION_REGION}.`, (0, error_1.getErrStack)(err));
249
+ logger_1.logger.debug(`Failed to resolve region for endpoint ${id}. Defaulting to ${services_1.FALLBACK_DEPLOYMENT_REGION}.`, (0, error_1.getErrStack)(err));
261
250
  }
262
251
  }
263
252
  endpoint.region = [resolvedRegion];
264
253
  }
265
254
  }
266
255
  }
267
- function resolveRegionForBlockingTrigger(blockingTrigger) {
268
- const eventType = blockingTrigger.eventType;
269
- if (events.AUTH_BLOCKING_EVENTS.includes(eventType)) {
270
- return "us-east1";
271
- }
272
- if ((0, ailogic_1.isGlobalAILogicTrigger)(blockingTrigger)) {
273
- return "us-east1";
274
- }
275
- return exports.DEFAULT_FUNCTION_REGION;
276
- }
277
- async function resolveRegionForEventTrigger(project, eventTrigger) {
278
- const eventType = eventTrigger.eventType;
279
- if (eventType.startsWith("google.cloud.pubsub.") ||
280
- eventType.startsWith("providers/cloud.auth/eventTypes/") ||
281
- eventType.startsWith("providers/firebase.auth/eventTypes/") ||
282
- eventType.startsWith("google.firebase.testlab.") ||
283
- eventType.startsWith("google.firebase.remoteconfig.") ||
284
- eventType.startsWith("google.firebase.firebasealerts.")) {
285
- return "us-east1";
286
- }
287
- if (eventType.startsWith("google.cloud.firestore.")) {
288
- try {
289
- const databaseId = eventTrigger.eventFilters?.database || "(default)";
290
- const db = await (0, firestore_1.getDatabase)(project, databaseId);
291
- const locationId = db.locationId.toLowerCase();
292
- if (locationId === "nam5" || locationId === "nam7")
293
- return "us-central1";
294
- if (locationId === "eur3")
295
- return "europe-west1";
296
- return locationId;
297
- }
298
- catch (err) {
299
- logger_1.logger.debug("Failed to resolve Firestore database location", (0, error_1.getErrStack)(err));
300
- }
301
- }
302
- if (eventType.startsWith("google.cloud.storage.")) {
303
- try {
304
- const bucketName = eventTrigger.eventFilters?.bucket;
305
- if (bucketName) {
306
- const bucket = await (0, storage_1.getBucket)(bucketName);
307
- const locationId = bucket.location.toLowerCase();
308
- if (locationId === "us")
309
- return "us-east1";
310
- if (locationId === "eu")
311
- return "europe-west1";
312
- if (locationId === "asia")
313
- return "asia-east1";
314
- return locationId;
315
- }
316
- }
317
- catch (err) {
318
- logger_1.logger.debug("Failed to resolve Cloud Storage bucket location", (0, error_1.getErrStack)(err));
319
- }
320
- }
321
- if (eventType.startsWith("google.firebase.database.")) {
322
- if (eventTrigger.region)
323
- return eventTrigger.region;
324
- try {
325
- const instanceName = eventTrigger.eventFilters?.instance;
326
- if (instanceName) {
327
- const details = await (0, database_1.getDatabaseInstanceDetails)(project, instanceName);
328
- if (details.location && details.location !== "-") {
329
- return details.location.toLowerCase();
330
- }
331
- }
332
- }
333
- catch (err) {
334
- logger_1.logger.debug("Failed to resolve Realtime Database instance location", (0, error_1.getErrStack)(err));
335
- }
336
- }
337
- if (eventType.startsWith("google.firebase.dataconnect.")) {
338
- if (eventTrigger.region)
339
- return eventTrigger.region;
340
- try {
341
- const service = eventTrigger.eventFilters?.service;
342
- if (service) {
343
- return (0, names_1.parseServiceName)(service).location;
344
- }
345
- const connector = eventTrigger.eventFilters?.connector;
346
- if (connector) {
347
- return (0, names_1.parseConnectorName)(connector).location;
348
- }
349
- }
350
- catch (err) {
351
- logger_1.logger.debug("Failed to resolve DataConnect location", (0, error_1.getErrStack)(err));
352
- }
353
- }
354
- return exports.DEFAULT_FUNCTION_REGION;
256
+ async function resolveRegionForTrigger(endpoint) {
257
+ const service = (0, services_1.serviceForEndpoint)(endpoint);
258
+ return await service.getDefaultRegion(endpoint);
355
259
  }
356
260
  function inferDetailsFromExisting(want, have, usedDotenv) {
357
261
  for (const wantE of backend.allEndpoints(want)) {
@@ -4,6 +4,7 @@ exports.AILogicService = exports.AI_LOGIC_EVENTS = exports.AI_LOGIC_AFTER_GENERA
4
4
  exports.isAILogicEvent = isAILogicEvent;
5
5
  exports.isGlobalAILogicTrigger = isGlobalAILogicTrigger;
6
6
  const backend = require("../backend");
7
+ const build = require("../build");
7
8
  const error_1 = require("../../../error");
8
9
  const ailogicApi = require("../../../gcp/ailogic");
9
10
  const ailogic_1 = require("../../../gcp/ailogic");
@@ -27,19 +28,19 @@ function isGlobalAILogicTrigger(blockingTrigger) {
27
28
  class AILogicService {
28
29
  constructor() {
29
30
  this.ensureTriggerRegion = () => Promise.resolve();
31
+ this.requiredProjectBindings = async (projectNumber) => {
32
+ return [
33
+ {
34
+ role: "roles/run.invoker",
35
+ members: [
36
+ `serviceAccount:service-${projectNumber}@gcp-sa-firebasevertexai.iam.gserviceaccount.com`,
37
+ ],
38
+ },
39
+ ];
40
+ };
30
41
  this.name = "ailogic";
31
42
  this.api = "firebasevertexai.googleapis.com";
32
43
  }
33
- async requiredProjectBindings(projectNumber) {
34
- return [
35
- {
36
- role: "roles/run.invoker",
37
- members: [
38
- `serviceAccount:service-${projectNumber}@gcp-sa-firebasevertexai.iam.gserviceaccount.com`,
39
- ],
40
- },
41
- ];
42
- }
43
44
  validateTrigger(endpoint, wantBackend) {
44
45
  if (!isAILogicEvent(endpoint)) {
45
46
  return;
@@ -91,5 +92,11 @@ class AILogicService {
91
92
  }
92
93
  }
93
94
  }
95
+ async getDefaultRegion(endpoint) {
96
+ if (build.isBlockingTriggered(endpoint) && isGlobalAILogicTrigger(endpoint.blockingTrigger)) {
97
+ return "us-east1";
98
+ }
99
+ return "us-central1";
100
+ }
94
101
  }
95
102
  exports.AILogicService = AILogicService;
@@ -129,5 +129,8 @@ class AuthBlockingService {
129
129
  this.triggerQueue = this.triggerQueue.then(() => this.unregisterTriggerLocked(ep));
130
130
  return this.triggerQueue;
131
131
  }
132
+ async getDefaultRegion() {
133
+ return "us-east1";
134
+ }
132
135
  }
133
136
  exports.AuthBlockingService = AuthBlockingService;
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.clearCache = clearCache;
4
4
  exports.getDatabaseInstanceDetails = getDatabaseInstanceDetails;
5
5
  exports.ensureDatabaseTriggerRegion = ensureDatabaseTriggerRegion;
6
+ exports.getDefaultRegion = getDefaultRegion;
6
7
  const error_1 = require("../../../error");
8
+ const build = require("../build");
7
9
  const database_1 = require("../../../management/database");
8
10
  const instanceCache = new Map();
9
11
  function clearCache() {
@@ -27,3 +29,19 @@ function ensureDatabaseTriggerRegion(endpoint) {
27
29
  }
28
30
  return Promise.resolve();
29
31
  }
32
+ async function getDefaultRegion(endpoint) {
33
+ if (!build.isEventTriggered(endpoint)) {
34
+ throw new error_1.FirebaseError("Database getDefaultRegion requires an event-triggered endpoint");
35
+ }
36
+ if (endpoint.eventTrigger.region) {
37
+ return endpoint.eventTrigger.region;
38
+ }
39
+ const instanceName = endpoint.eventTrigger.eventFilters?.instance;
40
+ if (instanceName) {
41
+ const details = await getDatabaseInstanceDetails(endpoint.project, instanceName);
42
+ if (details.location && details.location !== "-") {
43
+ return details.location.toLowerCase();
44
+ }
45
+ }
46
+ throw new error_1.FirebaseError("Could not resolve database instance location");
47
+ }
@@ -2,8 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureDataConnectTriggerRegion = ensureDataConnectTriggerRegion;
4
4
  exports.getDataConnectP4SA = getDataConnectP4SA;
5
+ exports.getDefaultRegion = getDefaultRegion;
5
6
  const api_1 = require("../../../api");
6
7
  const error_1 = require("../../../error");
8
+ const build = require("../build");
9
+ const names_1 = require("../../../dataconnect/names");
7
10
  const AUTOPUSH_DATACONNECT_SA_DOMAIN = "gcp-sa-autopush-dataconnect.iam.gserviceaccount.com";
8
11
  const STAGING_DATACONNECT_SA_DOMAIN = "gcp-sa-staging-dataconnect.iam.gserviceaccount.com";
9
12
  const PROD_DATACONNECT_SA_DOMAIN = "gcp-sa-firebasedataconnect.iam.gserviceaccount.com";
@@ -26,3 +29,20 @@ function getDataConnectP4SA(projectNumber) {
26
29
  }
27
30
  return `service-${projectNumber}@${PROD_DATACONNECT_SA_DOMAIN}`;
28
31
  }
32
+ async function getDefaultRegion(endpoint) {
33
+ if (!build.isEventTriggered(endpoint)) {
34
+ throw new error_1.FirebaseError("DataConnect getDefaultRegion requires an event-triggered endpoint");
35
+ }
36
+ if (endpoint.eventTrigger.region) {
37
+ return endpoint.eventTrigger.region;
38
+ }
39
+ const service = endpoint.eventTrigger.eventFilters?.service;
40
+ if (service) {
41
+ return (0, names_1.parseServiceName)(service).location;
42
+ }
43
+ const connector = endpoint.eventTrigger.eventFilters?.connector;
44
+ if (connector) {
45
+ return (0, names_1.parseConnectorName)(connector).location;
46
+ }
47
+ throw new error_1.FirebaseError("Could not resolve DataConnect location");
48
+ }
@@ -3,8 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.clearCache = clearCache;
4
4
  exports.getDatabase = getDatabase;
5
5
  exports.ensureFirestoreTriggerRegion = ensureFirestoreTriggerRegion;
6
+ exports.getDefaultRegion = getDefaultRegion;
6
7
  const firestore = require("../../../gcp/firestore");
7
8
  const error_1 = require("../../../error");
9
+ const build = require("../build");
10
+ const location_1 = require("../../../gcp/location");
8
11
  const dbCache = new Map();
9
12
  const dbPromiseCache = new Map();
10
13
  function clearCache() {
@@ -43,3 +46,12 @@ async function ensureFirestoreTriggerRegion(endpoint) {
43
46
  throw new error_1.FirebaseError("A firestore trigger location must match the firestore database region.");
44
47
  }
45
48
  }
49
+ async function getDefaultRegion(endpoint) {
50
+ if (!build.isEventTriggered(endpoint)) {
51
+ throw new error_1.FirebaseError("Firestore getDefaultRegion requires an event-triggered endpoint");
52
+ }
53
+ const databaseId = endpoint.eventTrigger.eventFilters?.database || "(default)";
54
+ const db = await getDatabase(endpoint.project, databaseId);
55
+ const locationId = db.locationId.toLowerCase();
56
+ return location_1.FIRESTORE_DUAL_REGION_TO_REGION_MAPPING[locationId] || locationId;
57
+ }
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noopProjectBindings = exports.noop = void 0;
3
+ exports.FALLBACK_DEPLOYMENT_REGION = exports.DEFAULT_GLOBAL_TRIGGER_REGION = exports.noopProjectBindings = exports.noop = void 0;
4
4
  exports.serviceForEndpoint = serviceForEndpoint;
5
- const backend = require("../backend");
6
5
  const auth_1 = require("./auth");
7
6
  const storage_1 = require("./storage");
8
7
  const firebaseAlerts_1 = require("./firebaseAlerts");
@@ -16,6 +15,8 @@ const noop = () => Promise.resolve();
16
15
  exports.noop = noop;
17
16
  const noopProjectBindings = () => Promise.resolve([]);
18
17
  exports.noopProjectBindings = noopProjectBindings;
18
+ exports.DEFAULT_GLOBAL_TRIGGER_REGION = "us-east1";
19
+ exports.FALLBACK_DEPLOYMENT_REGION = "us-central1";
19
20
  const noOpService = {
20
21
  name: "noop",
21
22
  api: "",
@@ -23,6 +24,7 @@ const noOpService = {
23
24
  validateTrigger: exports.noop,
24
25
  registerTrigger: exports.noop,
25
26
  unregisterTrigger: exports.noop,
27
+ getDefaultRegion: () => Promise.resolve(exports.FALLBACK_DEPLOYMENT_REGION),
26
28
  };
27
29
  const pubSubService = {
28
30
  name: "pubsub",
@@ -32,6 +34,7 @@ const pubSubService = {
32
34
  validateTrigger: exports.noop,
33
35
  registerTrigger: exports.noop,
34
36
  unregisterTrigger: exports.noop,
37
+ getDefaultRegion: () => Promise.resolve(exports.DEFAULT_GLOBAL_TRIGGER_REGION),
35
38
  };
36
39
  const storageService = {
37
40
  name: "storage",
@@ -41,6 +44,7 @@ const storageService = {
41
44
  validateTrigger: exports.noop,
42
45
  registerTrigger: exports.noop,
43
46
  unregisterTrigger: exports.noop,
47
+ getDefaultRegion: storage_1.getDefaultRegion,
44
48
  };
45
49
  const firebaseAlertsService = {
46
50
  name: "firebasealerts",
@@ -50,6 +54,7 @@ const firebaseAlertsService = {
50
54
  validateTrigger: exports.noop,
51
55
  registerTrigger: exports.noop,
52
56
  unregisterTrigger: exports.noop,
57
+ getDefaultRegion: () => Promise.resolve(exports.DEFAULT_GLOBAL_TRIGGER_REGION),
53
58
  };
54
59
  const authBlockingService = new auth_1.AuthBlockingService();
55
60
  const aiLogicService = new ailogic_1.AILogicService();
@@ -61,6 +66,7 @@ const databaseService = {
61
66
  validateTrigger: exports.noop,
62
67
  registerTrigger: exports.noop,
63
68
  unregisterTrigger: exports.noop,
69
+ getDefaultRegion: database_1.getDefaultRegion,
64
70
  };
65
71
  const remoteConfigService = {
66
72
  name: "remoteconfig",
@@ -70,6 +76,7 @@ const remoteConfigService = {
70
76
  validateTrigger: exports.noop,
71
77
  registerTrigger: exports.noop,
72
78
  unregisterTrigger: exports.noop,
79
+ getDefaultRegion: () => Promise.resolve(exports.DEFAULT_GLOBAL_TRIGGER_REGION),
73
80
  };
74
81
  const testLabService = {
75
82
  name: "testlab",
@@ -79,6 +86,7 @@ const testLabService = {
79
86
  validateTrigger: exports.noop,
80
87
  registerTrigger: exports.noop,
81
88
  unregisterTrigger: exports.noop,
89
+ getDefaultRegion: () => Promise.resolve(exports.DEFAULT_GLOBAL_TRIGGER_REGION),
82
90
  };
83
91
  const firestoreService = {
84
92
  name: "firestore",
@@ -88,6 +96,7 @@ const firestoreService = {
88
96
  validateTrigger: exports.noop,
89
97
  registerTrigger: exports.noop,
90
98
  unregisterTrigger: exports.noop,
99
+ getDefaultRegion: firestore_1.getDefaultRegion,
91
100
  };
92
101
  const dataconnectService = {
93
102
  name: "dataconnect",
@@ -97,6 +106,7 @@ const dataconnectService = {
97
106
  validateTrigger: exports.noop,
98
107
  registerTrigger: exports.noop,
99
108
  unregisterTrigger: exports.noop,
109
+ getDefaultRegion: dataconnect_1.getDefaultRegion,
100
110
  };
101
111
  const EVENT_SERVICE_MAPPING = {
102
112
  "google.cloud.pubsub.topic.v1.messagePublished": pubSubService,
@@ -128,11 +138,12 @@ const EVENT_SERVICE_MAPPING = {
128
138
  "google.firebase.ailogic.v1.afterGenerate": aiLogicService,
129
139
  };
130
140
  function serviceForEndpoint(endpoint) {
131
- if (backend.isEventTriggered(endpoint)) {
132
- return EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType] || noOpService;
141
+ let eventType;
142
+ if ("eventTrigger" in endpoint && endpoint.eventTrigger?.eventType) {
143
+ eventType = endpoint.eventTrigger.eventType;
133
144
  }
134
- if (backend.isBlockingTriggered(endpoint)) {
135
- return EVENT_SERVICE_MAPPING[endpoint.blockingTrigger.eventType] || noOpService;
145
+ else if ("blockingTrigger" in endpoint && endpoint.blockingTrigger?.eventType) {
146
+ eventType = endpoint.blockingTrigger.eventType;
136
147
  }
137
- return noOpService;
148
+ return eventType ? EVENT_SERVICE_MAPPING[eventType] || noOpService : noOpService;
138
149
  }
@@ -4,10 +4,12 @@ exports.clearCache = clearCache;
4
4
  exports.getBucket = getBucket;
5
5
  exports.obtainStorageBindings = obtainStorageBindings;
6
6
  exports.ensureStorageTriggerRegion = ensureStorageTriggerRegion;
7
+ exports.getDefaultRegion = getDefaultRegion;
7
8
  const storage = require("../../../gcp/storage");
8
9
  const logger_1 = require("../../../logger");
9
10
  const error_1 = require("../../../error");
10
11
  const location_1 = require("../../../gcp/location");
12
+ const build = require("../build");
11
13
  const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
12
14
  const bucketCache = new Map();
13
15
  function clearCache() {
@@ -54,3 +56,15 @@ async function ensureStorageTriggerRegion(endpoint) {
54
56
  throw new error_1.FirebaseError(`A function in region ${endpoint.region} cannot listen to a bucket in region ${eventTrigger.region}`);
55
57
  }
56
58
  }
59
+ async function getDefaultRegion(endpoint) {
60
+ if (!build.isEventTriggered(endpoint)) {
61
+ throw new error_1.FirebaseError("Storage getDefaultRegion requires an event-triggered endpoint");
62
+ }
63
+ const bucketName = endpoint.eventTrigger.eventFilters?.bucket;
64
+ if (!bucketName) {
65
+ throw new error_1.FirebaseError("Could not find bucket name in event trigger filters");
66
+ }
67
+ const bucket = await getBucket(bucketName);
68
+ const locationId = bucket.location.toLowerCase();
69
+ return location_1.STORAGE_MULTI_REGION_TO_REGION_MAPPING[locationId] || locationId;
70
+ }