firebase-tools 15.16.0 → 15.17.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.
Files changed (43) hide show
  1. package/lib/api.js +1 -1
  2. package/lib/deploy/apphosting/release.js +1 -1
  3. package/lib/deploy/functions/prepare.js +113 -3
  4. package/lib/deploy/functions/services/ailogic.js +7 -0
  5. package/lib/deploy/functions/services/database.js +16 -0
  6. package/lib/deploy/functions/services/firestore.js +1 -0
  7. package/lib/deploy/functions/services/storage.js +15 -1
  8. package/lib/deploy/functions/triggerRegionHelper.js +111 -2
  9. package/lib/emulator/downloadableEmulatorInfo.json +23 -23
  10. package/lib/emulator/functionsEmulatorShared.js +2 -1
  11. package/lib/env.js +5 -1
  12. package/lib/firestore/api-sort.js +22 -0
  13. package/lib/firestore/api-types.js +11 -1
  14. package/lib/firestore/api.js +21 -1
  15. package/lib/firestore/fsConfig.js +8 -0
  16. package/lib/firestore/pretty-print.js +26 -8
  17. package/lib/frameworks/next/index.js +1 -1
  18. package/lib/mcp/apps/deploy/mcp-app.js +120 -0
  19. package/lib/mcp/apps/deploy/vite.config.js +16 -0
  20. package/lib/mcp/apps/init/mcp-app.js +230 -0
  21. package/lib/mcp/apps/init/vite.config.js +16 -0
  22. package/lib/mcp/apps/update_environment/mcp-app.js +38 -36
  23. package/lib/mcp/apps/update_environment/vite.config.js +16 -0
  24. package/lib/mcp/index.js +16 -5
  25. package/lib/mcp/resources/deploy_ui.js +31 -0
  26. package/lib/mcp/resources/index.js +4 -0
  27. package/lib/mcp/resources/init_ui.js +31 -0
  28. package/lib/mcp/resources/update_environment_ui.js +3 -3
  29. package/lib/mcp/tools/auth/get_users.js +1 -1
  30. package/lib/mcp/tools/core/deploy.js +87 -0
  31. package/lib/mcp/tools/core/deploy_status.js +32 -0
  32. package/lib/mcp/tools/core/index.js +4 -0
  33. package/lib/mcp/tools/core/init.js +3 -0
  34. package/lib/mcp/tools/core/update_environment.js +3 -0
  35. package/lib/mcp/tools/firestore/query_collection.js +1 -1
  36. package/lib/mcp/tools/functions/list_functions.js +2 -2
  37. package/lib/mcp/util/jobs.js +31 -0
  38. package/lib/mcp/util.js +5 -4
  39. package/lib/tsconfig.compile.tsbuildinfo +1 -1
  40. package/lib/tsconfig.publish.tsbuildinfo +1 -1
  41. package/package.json +1 -1
  42. package/templates/init/functions/dart/pubspec.yaml +1 -1
  43. package/templates/init/functions/dart/server.dart +2 -2
package/lib/api.js CHANGED
@@ -66,7 +66,7 @@ const functionsV2Origin = () => utils.envOverride("FIREBASE_FUNCTIONS_V2_URL", "
66
66
  exports.functionsV2Origin = functionsV2Origin;
67
67
  const runOrigin = () => utils.envOverride("CLOUD_RUN_URL", "https://run.googleapis.com");
68
68
  exports.runOrigin = runOrigin;
69
- const functionsDefaultRegion = () => utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "us-central1");
69
+ const functionsDefaultRegion = () => utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "REGION_TBD");
70
70
  exports.functionsDefaultRegion = functionsDefaultRegion;
71
71
  const cloudbuildOrigin = () => utils.envOverride("FIREBASE_CLOUDBUILD_URL", "https://cloudbuild.googleapis.com");
72
72
  exports.cloudbuildOrigin = cloudbuildOrigin;
@@ -37,6 +37,7 @@ async function default_1(context, options) {
37
37
  (0, utils_1.logLabeledBullet)("apphosting", `You may also track the rollout(s) at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
38
38
  const rolloutsSpinner = ora(`Starting rollout(s) for backend(s) ${backendIds.join(", ")}; this may take a few minutes. It's safe to exit now.\n`).start();
39
39
  const results = await Promise.allSettled(rollouts);
40
+ rolloutsSpinner.stop();
40
41
  let failed = false;
41
42
  for (let i = 0; i < results.length; i++) {
42
43
  const res = results[i];
@@ -51,7 +52,6 @@ async function default_1(context, options) {
51
52
  (0, utils_1.logLabeledError)("apphosting", `${res.reason}`);
52
53
  }
53
54
  }
54
- rolloutsSpinner.stop();
55
55
  if (failed) {
56
56
  throw new error_1.FirebaseError("One or more rollouts failed. Please review the errors above and try again.");
57
57
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EVENTARC_SOURCE_ENV = void 0;
3
+ exports.DEFAULT_FUNCTION_REGION = exports.EVENTARC_SOURCE_ENV = void 0;
4
4
  exports.prepare = prepare;
5
5
  exports.matchRegionsForExisting = matchRegionsForExisting;
6
6
  exports.resolveDefaultRegions = resolveDefaultRegions;
@@ -23,6 +23,12 @@ 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
32
  const api_1 = require("../../api");
27
33
  const functionsDeployHelper_1 = require("./functionsDeployHelper");
28
34
  const utils_1 = require("../../utils");
@@ -43,6 +49,7 @@ const functional_1 = require("../../functional");
43
49
  const prepare_1 = require("../extensions/prepare");
44
50
  const prompt = require("../../prompt");
45
51
  exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
52
+ exports.DEFAULT_FUNCTION_REGION = "us-central1";
46
53
  async function prepare(context, options, payload) {
47
54
  const projectId = (0, projectUtils_1.needProjectId)(options);
48
55
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
@@ -256,9 +263,112 @@ function matchRegionsForExisting(want, have) {
256
263
  }
257
264
  async function resolveDefaultRegions(want, have) {
258
265
  matchRegionsForExisting(want, have);
259
- for (const endpoint of Object.values(want.endpoints[build.REGION_TBD] || {})) {
260
- moveEndpointToRegion(want, endpoint, "us-central1");
266
+ const endpoints = Object.values(want.endpoints[build.REGION_TBD] || {});
267
+ for (const endpoint of endpoints) {
268
+ let resolvedRegion = "us-central1";
269
+ try {
270
+ if (backend.isBlockingTriggered(endpoint)) {
271
+ resolvedRegion = resolveRegionForBlockingTrigger(endpoint);
272
+ }
273
+ else if (backend.isEventTriggered(endpoint)) {
274
+ resolvedRegion = await resolveRegionForEventTrigger(endpoint);
275
+ }
276
+ }
277
+ catch (err) {
278
+ logger_1.logger.debug(`Failed to resolve region for endpoint ${endpoint.id}. Defaulting to us-central1.`, (0, error_1.getErrStack)(err));
279
+ }
280
+ moveEndpointToRegion(want, endpoint, resolvedRegion);
281
+ }
282
+ }
283
+ function resolveRegionForBlockingTrigger(endpoint) {
284
+ const eventType = endpoint.blockingTrigger.eventType;
285
+ if (events.AUTH_BLOCKING_EVENTS.includes(eventType)) {
286
+ return "us-east1";
287
+ }
288
+ if ((0, ailogic_1.isGlobalAILogicEndpoint)(endpoint)) {
289
+ return "us-east1";
290
+ }
291
+ return exports.DEFAULT_FUNCTION_REGION;
292
+ }
293
+ async function resolveRegionForEventTrigger(endpoint) {
294
+ const eventTrigger = endpoint.eventTrigger;
295
+ const eventType = eventTrigger.eventType;
296
+ if (eventType.startsWith("google.cloud.pubsub.") ||
297
+ eventType.startsWith("providers/cloud.auth/eventTypes/") ||
298
+ eventType.startsWith("providers/firebase.auth/eventTypes/") ||
299
+ eventType.startsWith("google.firebase.testlab.") ||
300
+ eventType.startsWith("google.firebase.remoteconfig.") ||
301
+ eventType.startsWith("google.firebase.firebasealerts.")) {
302
+ return "us-east1";
303
+ }
304
+ if (eventType.startsWith("google.cloud.firestore.")) {
305
+ try {
306
+ const databaseId = eventTrigger.eventFilters?.database || "(default)";
307
+ const db = await (0, firestore_1.getDatabase)(endpoint.project, databaseId);
308
+ const locationId = db.locationId.toLowerCase();
309
+ if (locationId === "nam5" || locationId === "nam7")
310
+ return "us-central1";
311
+ if (locationId === "eur3")
312
+ return "europe-west1";
313
+ return locationId;
314
+ }
315
+ catch (err) {
316
+ logger_1.logger.debug("Failed to resolve Firestore database location", (0, error_1.getErrStack)(err));
317
+ }
318
+ }
319
+ if (eventType.startsWith("google.cloud.storage.")) {
320
+ try {
321
+ const bucketName = eventTrigger.eventFilters?.bucket;
322
+ if (bucketName) {
323
+ const bucket = await (0, storage_1.getBucket)(bucketName);
324
+ const locationId = bucket.location.toLowerCase();
325
+ if (locationId === "us")
326
+ return "us-east1";
327
+ if (locationId === "eu")
328
+ return "europe-west1";
329
+ if (locationId === "asia")
330
+ return "asia-east1";
331
+ return locationId;
332
+ }
333
+ }
334
+ catch (err) {
335
+ logger_1.logger.debug("Failed to resolve Cloud Storage bucket location", (0, error_1.getErrStack)(err));
336
+ }
337
+ }
338
+ if (eventType.startsWith("google.firebase.database.")) {
339
+ if (eventTrigger.region)
340
+ return eventTrigger.region;
341
+ try {
342
+ const instanceName = eventTrigger.eventFilters?.instance;
343
+ if (instanceName) {
344
+ const details = await (0, database_1.getDatabaseInstanceDetails)(endpoint.project, instanceName);
345
+ if (details.location && details.location !== "-") {
346
+ return details.location.toLowerCase();
347
+ }
348
+ }
349
+ }
350
+ catch (err) {
351
+ logger_1.logger.debug("Failed to resolve Realtime Database instance location", (0, error_1.getErrStack)(err));
352
+ }
353
+ }
354
+ if (eventType.startsWith("google.firebase.dataconnect.")) {
355
+ if (eventTrigger.region)
356
+ return eventTrigger.region;
357
+ try {
358
+ const service = eventTrigger.eventFilters?.service;
359
+ if (service) {
360
+ return (0, names_1.parseServiceName)(service).location;
361
+ }
362
+ const connector = eventTrigger.eventFilters?.connector;
363
+ if (connector) {
364
+ return (0, names_1.parseConnectorName)(connector).location;
365
+ }
366
+ }
367
+ catch (err) {
368
+ logger_1.logger.debug("Failed to resolve DataConnect location", (0, error_1.getErrStack)(err));
369
+ }
261
370
  }
371
+ return exports.DEFAULT_FUNCTION_REGION;
262
372
  }
263
373
  function inferDetailsFromExisting(want, have, usedDotenv) {
264
374
  for (const wantE of backend.allEndpoints(want)) {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AILogicService = exports.AI_LOGIC_EVENTS = exports.AI_LOGIC_AFTER_GENERATE_CONTENT = exports.AI_LOGIC_BEFORE_GENERATE_CONTENT = void 0;
4
4
  exports.isAILogicEvent = isAILogicEvent;
5
+ exports.isGlobalAILogicEndpoint = isGlobalAILogicEndpoint;
5
6
  const backend = require("../backend");
6
7
  const error_1 = require("../../../error");
7
8
  const ailogicApi = require("../../../gcp/ailogic");
@@ -19,6 +20,12 @@ function isAILogicEvent(endpoint) {
19
20
  }
20
21
  return exports.AI_LOGIC_EVENTS.includes(endpoint.blockingTrigger.eventType);
21
22
  }
23
+ function isGlobalAILogicEndpoint(endpoint) {
24
+ if (!isAILogicEvent(endpoint)) {
25
+ return false;
26
+ }
27
+ return !endpoint.blockingTrigger.options?.regionalWebhook;
28
+ }
22
29
  class AILogicService {
23
30
  constructor() {
24
31
  this.ensureTriggerRegion = () => Promise.resolve();
@@ -1,7 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearCache = clearCache;
4
+ exports.getDatabaseInstanceDetails = getDatabaseInstanceDetails;
3
5
  exports.ensureDatabaseTriggerRegion = ensureDatabaseTriggerRegion;
4
6
  const error_1 = require("../../../error");
7
+ const database_1 = require("../../../management/database");
8
+ const instanceCache = new Map();
9
+ function clearCache() {
10
+ instanceCache.clear();
11
+ }
12
+ async function getDatabaseInstanceDetails(projectId, instanceName) {
13
+ const key = `${projectId}/${instanceName}`;
14
+ if (instanceCache.has(key)) {
15
+ return instanceCache.get(key);
16
+ }
17
+ const details = await (0, database_1.getDatabaseInstanceDetails)(projectId, instanceName);
18
+ instanceCache.set(key, details);
19
+ return details;
20
+ }
5
21
  function ensureDatabaseTriggerRegion(endpoint) {
6
22
  if (!endpoint.eventTrigger.region) {
7
23
  endpoint.eventTrigger.region = endpoint.region;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.clearCache = clearCache;
4
+ exports.getDatabase = getDatabase;
4
5
  exports.ensureFirestoreTriggerRegion = ensureFirestoreTriggerRegion;
5
6
  const firestore = require("../../../gcp/firestore");
6
7
  const error_1 = require("../../../error");
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearCache = clearCache;
4
+ exports.getBucket = getBucket;
3
5
  exports.obtainStorageBindings = obtainStorageBindings;
4
6
  exports.ensureStorageTriggerRegion = ensureStorageTriggerRegion;
5
7
  const storage = require("../../../gcp/storage");
@@ -7,6 +9,18 @@ const logger_1 = require("../../../logger");
7
9
  const error_1 = require("../../../error");
8
10
  const location_1 = require("../../../gcp/location");
9
11
  const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
12
+ const bucketCache = new Map();
13
+ function clearCache() {
14
+ bucketCache.clear();
15
+ }
16
+ async function getBucket(bucketName) {
17
+ if (bucketCache.has(bucketName)) {
18
+ return bucketCache.get(bucketName);
19
+ }
20
+ const b = await storage.getBucket(bucketName);
21
+ bucketCache.set(bucketName, b);
22
+ return b;
23
+ }
10
24
  async function obtainStorageBindings(projectNumber) {
11
25
  const storageResponse = await storage.getServiceAccount(projectNumber);
12
26
  const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
@@ -26,7 +40,7 @@ async function ensureStorageTriggerRegion(endpoint) {
26
40
  }
27
41
  logger_1.logger.debug(`Looking up bucket region for the storage event trigger on bucket ${eventTrigger.eventFilters.bucket}`);
28
42
  try {
29
- const bucket = await storage.getBucket(eventTrigger.eventFilters.bucket);
43
+ const bucket = await getBucket(eventTrigger.eventFilters.bucket);
30
44
  eventTrigger.region = bucket.location.toLowerCase();
31
45
  logger_1.logger.debug("Setting the event trigger region to", eventTrigger.region, ".");
32
46
  }
@@ -3,13 +3,122 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureTriggerRegions = ensureTriggerRegions;
4
4
  const backend = require("./backend");
5
5
  const services_1 = require("./services");
6
+ const logger_1 = require("../../logger");
7
+ const utils = require("../../utils");
8
+ const storage_1 = require("./services/storage");
9
+ const firestore_1 = require("./services/firestore");
10
+ const database_1 = require("./services/database");
6
11
  async function ensureTriggerRegions(want) {
7
12
  const regionLookups = [];
13
+ const triggerRegionMap = new Map();
8
14
  for (const ep of backend.allEndpoints(want)) {
9
- if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)) {
15
+ if (!backend.isEventTriggered(ep)) {
10
16
  continue;
11
17
  }
12
- regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep));
18
+ if (ep.platform === "gcfv1") {
19
+ const eventType = ep.eventTrigger.eventType || "";
20
+ const resource = ep.eventTrigger.eventFilters?.resource;
21
+ if (eventType.includes("storage")) {
22
+ const bucketName = extractBucketName(resource);
23
+ if (bucketName) {
24
+ regionLookups.push((0, storage_1.getBucket)(bucketName)
25
+ .then((bucket) => {
26
+ triggerRegionMap.set(backend.functionName(ep), bucket.location.toLowerCase());
27
+ })
28
+ .catch((err) => {
29
+ logger_1.logger.debug(`Failed to resolve trigger region for V1 storage function ${ep.id}:`, err);
30
+ }));
31
+ }
32
+ }
33
+ else if (eventType.includes("firestore")) {
34
+ const dbId = extractDatabaseId(resource);
35
+ regionLookups.push((0, firestore_1.getDatabase)(ep.project, dbId)
36
+ .then((db) => {
37
+ triggerRegionMap.set(backend.functionName(ep), db.locationId.toLowerCase());
38
+ })
39
+ .catch((err) => {
40
+ logger_1.logger.debug(`Failed to resolve trigger region for V1 firestore function ${ep.id}:`, err);
41
+ }));
42
+ }
43
+ else if (eventType.includes("database")) {
44
+ const instanceName = extractInstanceName(resource);
45
+ if (instanceName) {
46
+ regionLookups.push((0, database_1.getDatabaseInstanceDetails)(ep.project, instanceName)
47
+ .then((details) => {
48
+ if (details.location && details.location !== "-") {
49
+ triggerRegionMap.set(backend.functionName(ep), details.location.toLowerCase());
50
+ }
51
+ })
52
+ .catch((err) => {
53
+ logger_1.logger.debug(`Failed to resolve trigger region for V1 database function ${ep.id}:`, err);
54
+ }));
55
+ }
56
+ }
57
+ }
58
+ else {
59
+ regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep));
60
+ }
13
61
  }
14
62
  await Promise.all(regionLookups);
63
+ if (process.env.FIREBASE_SUPPRESS_REGION_WARNING === "true") {
64
+ return;
65
+ }
66
+ const offendingFunctions = [];
67
+ for (const ep of backend.allEndpoints(want)) {
68
+ if (!backend.isEventTriggered(ep)) {
69
+ continue;
70
+ }
71
+ let triggerRegion;
72
+ if (ep.platform === "gcfv1") {
73
+ triggerRegion = triggerRegionMap.get(backend.functionName(ep));
74
+ }
75
+ else {
76
+ triggerRegion = ep.eventTrigger.region;
77
+ }
78
+ if (ep.region !== "us-central1" || !triggerRegion || triggerRegion === "global") {
79
+ continue;
80
+ }
81
+ if (!isUSRegion(triggerRegion)) {
82
+ offendingFunctions.push(`- ${ep.id} (us-central1, Trigger: ${triggerRegion})`);
83
+ }
84
+ }
85
+ if (offendingFunctions.length > 0) {
86
+ utils.logLabeledWarning("functions", `The following functions have triggers in different regions than they are located:\n` +
87
+ offendingFunctions.join("\n") +
88
+ `\nTo avoid unnecessary cross-region network hops, consider assigning these functions to their trigger regions or collocating them. ` +
89
+ `To suppress this warning, set FIREBASE_SUPPRESS_REGION_WARNING=true in your environment variables.`);
90
+ }
91
+ }
92
+ function extractBucketName(resource) {
93
+ if (!resource)
94
+ return null;
95
+ const match = /buckets\/([^/]+)/.exec(resource);
96
+ if (match)
97
+ return match[1];
98
+ if (!resource.includes("/"))
99
+ return resource;
100
+ return null;
101
+ }
102
+ function extractDatabaseId(resource) {
103
+ if (!resource)
104
+ return "(default)";
105
+ const match = /databases\/([^/]+)/.exec(resource);
106
+ if (match)
107
+ return match[1];
108
+ if (!resource.includes("/"))
109
+ return resource;
110
+ return "(default)";
111
+ }
112
+ function extractInstanceName(resource) {
113
+ if (!resource)
114
+ return null;
115
+ const match = /instances\/([^/]+)/.exec(resource);
116
+ if (match)
117
+ return match[1];
118
+ if (!resource.includes("/"))
119
+ return resource;
120
+ return null;
121
+ }
122
+ function isUSRegion(region) {
123
+ return region === "us" || region.startsWith("nam") || region.startsWith("us-");
15
124
  }
@@ -54,36 +54,36 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "3.4.6",
58
- "expectedSize": 32361376,
59
- "expectedChecksum": "85cf160307a0ff39b8e625676b89789e",
60
- "expectedChecksumSHA256": "4f66d53776975c32df3c86870602965d2a5a495a14d86906cdbea84c8e40156b",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.4.6",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.6"
57
+ "version": "3.4.7",
58
+ "expectedSize": 32361408,
59
+ "expectedChecksum": "b756c557573420a54da92e9d66934355",
60
+ "expectedChecksumSHA256": "d97bbc39e6c9960d233db975c3f2e66d43c2a34436f8e39fae3cbe2a815e2c6b",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.4.7",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.7"
63
63
  },
64
64
  "darwin_arm64": {
65
- "version": "3.4.6",
66
- "expectedSize": 30504594,
67
- "expectedChecksum": "c1e5c18caa1fadcaf813c98bc05a8d77",
68
- "expectedChecksumSHA256": "1934ec8f6e8788a5521801072df278950b9525b0294c97cc009e65a7446d5567",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.4.6",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.6"
65
+ "version": "3.4.7",
66
+ "expectedSize": 30504610,
67
+ "expectedChecksum": "f3015d7cfd03bd4fcb7d76d6aac29c84",
68
+ "expectedChecksumSHA256": "3b0d4d273dabd04dc01ef57001180ed6daae2f1781d25f1f01eb6da5d25d5ae5",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.4.7",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.7"
71
71
  },
72
72
  "win32": {
73
- "version": "3.4.6",
74
- "expectedSize": 32401408,
75
- "expectedChecksum": "c57c0ca3c50f358122ac741e501c9fde",
76
- "expectedChecksumSHA256": "ed4bde0ebf9499ffd8915091f9ec3886a1c5ba145f54261b1c8c701a38268984",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.4.6",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.6.exe"
73
+ "version": "3.4.7",
74
+ "expectedSize": 32403456,
75
+ "expectedChecksum": "7fac83eee96f4f638d84a9c3a6950040",
76
+ "expectedChecksumSHA256": "42a88d0377565a94b966336d45b45a8ec24c6a81b0206654327d7722107569de",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.4.7",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.7.exe"
79
79
  },
80
80
  "linux": {
81
- "version": "3.4.6",
81
+ "version": "3.4.7",
82
82
  "expectedSize": 31518904,
83
- "expectedChecksum": "b05791a643618dd8fbdb5e56b474c45b",
84
- "expectedChecksumSHA256": "716ca10ddb2341873fc0febcffbc84420ac1f20262c3d08f9422cee025d1f60e",
85
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.4.6",
86
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.6"
83
+ "expectedChecksum": "2aa615b65f12a9efd768e13ab92bf457",
84
+ "expectedChecksumSHA256": "7ce36de105950172ee6f41100df93c92ba2cd2b2f89cb157740fc1ae808fcebb",
85
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.4.7",
86
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.7"
87
87
  }
88
88
  }
89
89
  }
@@ -21,6 +21,7 @@ const fs = require("fs");
21
21
  const crypto_1 = require("crypto");
22
22
  const _ = require("lodash");
23
23
  const backend = require("../deploy/functions/backend");
24
+ const build = require("../deploy/functions/build");
24
25
  const constants_1 = require("./constants");
25
26
  const manifest_1 = require("../extensions/manifest");
26
27
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
@@ -70,7 +71,7 @@ function prepareEndpoints(endpoints) {
70
71
  function emulatedFunctionsFromEndpoints(endpoints) {
71
72
  const regionDefinitions = [];
72
73
  for (const endpoint of endpoints) {
73
- if (!endpoint.region) {
74
+ if (!endpoint.region || endpoint.region === build.REGION_TBD) {
74
75
  endpoint.region = "us-central1";
75
76
  }
76
77
  const def = {
package/lib/env.js CHANGED
@@ -22,7 +22,7 @@ function setFirebaseMcp(value) {
22
22
  isFirebaseMcpFlag = value;
23
23
  }
24
24
  function detectAIAgent() {
25
- if (process.env.ANTIGRAVITY_CLI_ALIAS)
25
+ if (process.env.ANTIGRAVITY_AGENT)
26
26
  return "antigravity";
27
27
  if (process.env.CLAUDECODE)
28
28
  return "claude_code";
@@ -36,5 +36,9 @@ function detectAIAgent() {
36
36
  return "gemini_cli";
37
37
  if (process.env.OPENCODE)
38
38
  return "open_code";
39
+ if (process.env.ANDROID_STUDIO_AGENT)
40
+ return "android_studio_agent";
41
+ if (process.env.KIRO_AGENT_PATH)
42
+ return "kiro";
39
43
  return "unknown";
40
44
  }
@@ -147,6 +147,9 @@ function compareIndexField(a, b) {
147
147
  if (a.vectorConfig !== b.vectorConfig) {
148
148
  return compareVectorConfig(a.vectorConfig, b.vectorConfig);
149
149
  }
150
+ if (a.searchConfig !== b.searchConfig) {
151
+ return compareSearchConfig(a.searchConfig, b.searchConfig);
152
+ }
150
153
  return 0;
151
154
  }
152
155
  function compareFieldIndex(a, b) {
@@ -232,6 +235,25 @@ function compareVectorConfig(a, b) {
232
235
  }
233
236
  return a.dimension - b.dimension;
234
237
  }
238
+ function compareSearchConfig(a, b) {
239
+ if (!a) {
240
+ if (!b) {
241
+ return 0;
242
+ }
243
+ else {
244
+ return 1;
245
+ }
246
+ }
247
+ else if (!b) {
248
+ return -1;
249
+ }
250
+ const aTextSpecs = a?.textSpec?.indexSpecs?.length || 0;
251
+ const bTextSpecs = b?.textSpec?.indexSpecs?.length || 0;
252
+ if (aTextSpecs !== bTextSpecs) {
253
+ return aTextSpecs - bTextSpecs;
254
+ }
255
+ return 0;
256
+ }
235
257
  function compareArrays(a, b, fn) {
236
258
  const minFields = Math.min(a.length, b.length);
237
259
  for (let i = 0; i < minFields; i++) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RecurrenceType = exports.DatabaseEdition = exports.DataAccessMode = exports.RealtimeUpdatesMode = exports.PointInTimeRecoveryEnablement = exports.DatabaseDeleteProtectionState = exports.EnablementOption = exports.DatabaseType = exports.StateTtl = exports.State = exports.ArrayConfig = exports.Order = exports.Density = exports.ApiScope = exports.QueryScope = exports.Mode = void 0;
3
+ exports.RecurrenceType = exports.DatabaseEdition = exports.DataAccessMode = exports.RealtimeUpdatesMode = exports.PointInTimeRecoveryEnablement = exports.DatabaseDeleteProtectionState = exports.EnablementOption = exports.DatabaseType = exports.StateTtl = exports.State = exports.TextMatchType = exports.TextIndexType = exports.ArrayConfig = exports.Order = exports.Density = exports.ApiScope = exports.QueryScope = exports.Mode = void 0;
4
4
  exports.validateEnablementOption = validateEnablementOption;
5
5
  const error_1 = require("../error");
6
6
  var Mode;
@@ -36,6 +36,16 @@ var ArrayConfig;
36
36
  (function (ArrayConfig) {
37
37
  ArrayConfig["CONTAINS"] = "CONTAINS";
38
38
  })(ArrayConfig || (exports.ArrayConfig = ArrayConfig = {}));
39
+ var TextIndexType;
40
+ (function (TextIndexType) {
41
+ TextIndexType["TEXT_INDEX_TYPE_UNSPECIFIED"] = "TEXT_INDEX_TYPE_UNSPECIFIED";
42
+ TextIndexType["TOKENIZED"] = "TOKENIZED";
43
+ })(TextIndexType || (exports.TextIndexType = TextIndexType = {}));
44
+ var TextMatchType;
45
+ (function (TextMatchType) {
46
+ TextMatchType["TEXT_MATCH_TYPE_UNSPECIFIED"] = "TEXT_MATCH_TYPE_UNSPECIFIED";
47
+ TextMatchType["MATCH_GLOBALLY"] = "MATCH_GLOBALLY";
48
+ })(TextMatchType || (exports.TextMatchType = TextMatchType = {}));
39
49
  var State;
40
50
  (function (State) {
41
51
  State["CREATING"] = "CREATING";
@@ -254,7 +254,7 @@ class FirestoreApi {
254
254
  validator.assertHas(index, "fields");
255
255
  index.fields.forEach((field) => {
256
256
  validator.assertHas(field, "fieldPath");
257
- validator.assertHasOneOf(field, ["order", "arrayConfig", "vectorConfig"]);
257
+ validator.assertHasOneOf(field, ["order", "arrayConfig", "searchConfig", "vectorConfig"]);
258
258
  if (field.order) {
259
259
  validator.assertEnum(field, "order", Object.keys(types.Order));
260
260
  }
@@ -265,6 +265,20 @@ class FirestoreApi {
265
265
  validator.assertType("vectorConfig.dimension", field.vectorConfig.dimension, "number");
266
266
  validator.assertHas(field.vectorConfig, "flat");
267
267
  }
268
+ if (field.searchConfig) {
269
+ if (field.searchConfig.textSpec) {
270
+ validator.assertHas(field.searchConfig.textSpec, "indexSpecs");
271
+ for (const spec of field.searchConfig.textSpec.indexSpecs) {
272
+ validator.assertHas(spec, "indexType");
273
+ validator.assertEnum(spec, "indexType", Object.keys(types.TextIndexType));
274
+ validator.assertHas(spec, "matchType");
275
+ validator.assertEnum(spec, "matchType", Object.keys(types.TextMatchType));
276
+ }
277
+ }
278
+ if (field.searchConfig.geoSpec?.geoJsonIndexingDisabled !== undefined) {
279
+ validator.assertType("searchConfig.geoSpec.geoJsonIndexingDisabled", field.searchConfig.geoSpec.geoJsonIndexingDisabled, "boolean");
280
+ }
281
+ }
268
282
  });
269
283
  }
270
284
  validateField(field) {
@@ -405,6 +419,9 @@ class FirestoreApi {
405
419
  if (!utils.deepEqual(iField.vectorConfig, sField.vectorConfig)) {
406
420
  return false;
407
421
  }
422
+ if (!utils.deepEqual(iField.searchConfig, sField.searchConfig)) {
423
+ return false;
424
+ }
408
425
  i++;
409
426
  }
410
427
  return true;
@@ -488,6 +505,9 @@ class FirestoreApi {
488
505
  else if (field.vectorConfig) {
489
506
  f.vectorConfig = field.vectorConfig;
490
507
  }
508
+ else if (field.searchConfig) {
509
+ f.searchConfig = field.searchConfig;
510
+ }
491
511
  else if (field.mode === types.Mode.ARRAY_CONTAINS) {
492
512
  f.arrayConfig = types.ArrayConfig.CONTAINS;
493
513
  }
@@ -35,6 +35,14 @@ function getFirestoreConfig(projectId, options) {
35
35
  return [];
36
36
  }
37
37
  }
38
+ if (onlyDatabases.has("rules")) {
39
+ onlyDatabases.delete("rules");
40
+ allDatabases = true;
41
+ }
42
+ if (onlyDatabases.has("indexes")) {
43
+ onlyDatabases.delete("indexes");
44
+ allDatabases = true;
45
+ }
38
46
  const results = [];
39
47
  for (const c of fsConfig) {
40
48
  const { database, target } = c;