firebase-tools 15.3.1 → 15.5.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 (38) hide show
  1. package/lib/apiv2.js +13 -3
  2. package/lib/appdistribution/client.js +2 -1
  3. package/lib/apphosting/rollout.js +1 -1
  4. package/lib/apptesting/parseTestFiles.js +91 -43
  5. package/lib/bin/cli.js +2 -2
  6. package/lib/command.js +3 -0
  7. package/lib/commands/apptesting.js +75 -0
  8. package/lib/commands/dataconnect-compile.js +46 -0
  9. package/lib/commands/dataconnect-execute.js +14 -12
  10. package/lib/commands/firestore-databases-create.js +69 -12
  11. package/lib/commands/firestore-databases-update.js +6 -14
  12. package/lib/commands/index.js +2 -0
  13. package/lib/dataconnect/client.js +2 -1
  14. package/lib/dataconnect/types.js +8 -1
  15. package/lib/deploy/functions/prepare.js +1 -0
  16. package/lib/deploy/functions/validate.js +25 -0
  17. package/lib/emulator/auth/operations.js +2 -2
  18. package/lib/emulator/dataconnect/pgliteServer.js +26 -33
  19. package/lib/emulator/dataconnectEmulator.js +0 -1
  20. package/lib/emulator/downloadableEmulatorInfo.json +24 -24
  21. package/lib/ensureApiEnabled.js +2 -2
  22. package/lib/env.js +18 -0
  23. package/lib/firestore/api-types.js +26 -11
  24. package/lib/firestore/api.js +3 -0
  25. package/lib/gcp/iam.js +1 -1
  26. package/lib/gcp/rules.js +5 -2
  27. package/lib/gcp/serviceusage.js +2 -2
  28. package/lib/init/features/firestore/indexes.js +1 -1
  29. package/lib/init/features/{storage.js → storage/index.js} +12 -4
  30. package/lib/init/features/storage/rules.js +20 -0
  31. package/lib/mcp/tools/core/get_security_rules.js +1 -1
  32. package/lib/mcp/tools/crashlytics/reports.js +1 -1
  33. package/lib/mcp/tools/firestore/delete_document.js +1 -1
  34. package/lib/mcp/tools/firestore/query_collection.js +1 -1
  35. package/lib/track.js +1 -16
  36. package/lib/tsconfig.publish.tsbuildinfo +1 -1
  37. package/package.json +3 -2
  38. package/templates/init/apptesting/smoke_test.yaml +1 -1
@@ -4,6 +4,7 @@ exports.isGraphQLResponseError = exports.isGraphQLResponse = exports.MAIN_SCHEMA
4
4
  exports.requiresVector = requiresVector;
5
5
  exports.toDatasource = toDatasource;
6
6
  exports.mainSchemaYaml = mainSchemaYaml;
7
+ exports.secondarySchemaYamls = secondarySchemaYamls;
7
8
  exports.mainSchema = mainSchema;
8
9
  exports.isMainSchema = isMainSchema;
9
10
  exports.MAIN_SCHEMA_ID = "main";
@@ -42,6 +43,12 @@ function mainSchemaYaml(dataconnectYaml) {
42
43
  }
43
44
  return mainSch;
44
45
  }
46
+ function secondarySchemaYamls(dataconnectYaml) {
47
+ if (dataconnectYaml.schema) {
48
+ return [];
49
+ }
50
+ return (dataconnectYaml.schemas || []).filter((s) => s.id && s.id !== exports.MAIN_SCHEMA_ID);
51
+ }
45
52
  function mainSchema(schemas) {
46
53
  const mainSch = schemas.find((s) => isMainSchema(s));
47
54
  if (!mainSch) {
@@ -54,5 +61,5 @@ function isMainSchema(schema) {
54
61
  }
55
62
  const isGraphQLResponse = (g) => !!g.data || !!g.errors;
56
63
  exports.isGraphQLResponse = isGraphQLResponse;
57
- const isGraphQLResponseError = (g) => !!g.error;
64
+ const isGraphQLResponseError = (g) => !!g.error?.message || !!g.message;
58
65
  exports.isGraphQLResponseError = isGraphQLResponseError;
@@ -205,6 +205,7 @@ async function prepare(context, options, payload) {
205
205
  await (0, checkIam_1.ensureGenkitMonitoringRoles)(projectId, projectNumber, matchingBackend, haveBackend, options.dryRun);
206
206
  await ensure.secretAccess(projectId, matchingBackend, haveBackend, options.dryRun);
207
207
  updateEndpointTargetedStatus(wantBackends, context.filters || []);
208
+ validate.checkFiltersIntegrity(wantBackends, context.filters);
208
209
  (0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
209
210
  }
210
211
  function inferDetailsFromExisting(want, have, usedDotenv) {
@@ -8,6 +8,7 @@ exports.endpointsAreUnique = endpointsAreUnique;
8
8
  exports.functionsDirectoryExists = functionsDirectoryExists;
9
9
  exports.functionIdsAreValid = functionIdsAreValid;
10
10
  exports.secretsAreValid = secretsAreValid;
11
+ exports.checkFiltersIntegrity = checkFiltersIntegrity;
11
12
  const path = require("path");
12
13
  const clc = require("colorette");
13
14
  const error_1 = require("../../error");
@@ -257,3 +258,27 @@ async function validateSecretVersions(projectId, endpoints) {
257
258
  }
258
259
  }
259
260
  }
261
+ function checkFiltersIntegrity(wantBackends, filters) {
262
+ if (!filters) {
263
+ return;
264
+ }
265
+ for (const filter of filters) {
266
+ let matched = false;
267
+ for (const b of Object.values(wantBackends)) {
268
+ if (backend.someEndpoint(b, (e) => (0, functionsDeployHelper_1.endpointMatchesFilter)(e, filter))) {
269
+ matched = true;
270
+ break;
271
+ }
272
+ }
273
+ if (!matched) {
274
+ const parts = [];
275
+ if (filter.codebase) {
276
+ parts.push(filter.codebase);
277
+ }
278
+ if (filter.idChunks) {
279
+ parts.push(filter.idChunks.join("-"));
280
+ }
281
+ throw new error_1.FirebaseError(`No function matches the filter: ${parts.join(":")}`);
282
+ }
283
+ }
284
+ }
@@ -2243,8 +2243,8 @@ function generateBlockingFunctionJwt(state, event, url, timeoutMs, user, options
2243
2243
  }
2244
2244
  if (user.lastLoginAt || user.createdAt) {
2245
2245
  jwt.user_record.metadata = {
2246
- last_sign_in_time: user.lastLoginAt,
2247
- creation_time: user.createdAt,
2246
+ last_sign_in_time: user.lastLoginAt ? parseInt(user.lastLoginAt) : undefined,
2247
+ creation_time: user.createdAt ? parseInt(user.createdAt) : undefined,
2248
2248
  };
2249
2249
  }
2250
2250
  if (state.shouldForwardCredentialToBlockingFunction("accessToken")) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PGliteExtendedQueryPatch = exports.PostgresServer = exports.TRUNCATE_TABLES_SQL = void 0;
3
+ exports.PGliteLoggerPatch = exports.PostgresServer = exports.TRUNCATE_TABLES_SQL = void 0;
4
4
  exports.fromNodeSocket = fromNodeSocket;
5
5
  const pglite_1 = require("@electric-sql/pglite");
6
6
  const { dynamicImport } = require(true && "../../dynamicImport");
@@ -42,12 +42,12 @@ class PostgresServer {
42
42
  await db.query("DEALLOCATE ALL");
43
43
  }
44
44
  const response = await db.execProtocolRaw(data);
45
- for await (const message of extendedQueryPatch.filterResponse(data, response)) {
45
+ for await (const message of extendedQueryPatch.getMessages(data, response)) {
46
46
  yield message;
47
47
  }
48
48
  },
49
49
  });
50
- const extendedQueryPatch = new PGliteExtendedQueryPatch(connection);
50
+ const extendedQueryPatch = new PGliteLoggerPatch(connection);
51
51
  socket.on("end", () => {
52
52
  logger_1.logger.debug("Postgres client disconnected");
53
53
  });
@@ -202,42 +202,35 @@ async function fromNodeSocket(socket, options) {
202
202
  : undefined;
203
203
  return new pg_gateway_1.PostgresConnection({ readable: rs, writable: ws }, opts);
204
204
  }
205
- class PGliteExtendedQueryPatch {
205
+ const CODE_TO_FRONTEND_MESSAGE = {};
206
+ for (const key in pg_gateway_1.FrontendMessageCode) {
207
+ if (Object.prototype.hasOwnProperty.call(pg_gateway_1.FrontendMessageCode, key)) {
208
+ CODE_TO_FRONTEND_MESSAGE[pg_gateway_1.FrontendMessageCode[key]] = key;
209
+ }
210
+ }
211
+ const CODE_TO_BACKEND_MESSAGE = {};
212
+ for (const key in pg_gateway_1.BackendMessageCode) {
213
+ if (Object.prototype.hasOwnProperty.call(pg_gateway_1.BackendMessageCode, key)) {
214
+ CODE_TO_BACKEND_MESSAGE[pg_gateway_1.BackendMessageCode[key]] = key;
215
+ }
216
+ }
217
+ function codeToFrontendMessageName(code) {
218
+ return CODE_TO_FRONTEND_MESSAGE[code] || `UNKNOWN_FRONTEND_CODE_${code}`;
219
+ }
220
+ function codeTogBackendMessageName(code) {
221
+ return CODE_TO_BACKEND_MESSAGE[code] || `UNKNOWN_BACKEND_CODE_${code}`;
222
+ }
223
+ class PGliteLoggerPatch {
206
224
  constructor(connection) {
207
225
  this.connection = connection;
208
- this.isExtendedQuery = false;
209
- this.eqpErrored = false;
210
226
  this.pgliteDebugLog = fs.createWriteStream("pglite-debug.log");
211
227
  }
212
- async *filterResponse(message, response) {
213
- const pipelineStartMessages = [
214
- pg_gateway_1.FrontendMessageCode.Parse,
215
- pg_gateway_1.FrontendMessageCode.Bind,
216
- pg_gateway_1.FrontendMessageCode.Close,
217
- ];
218
- const decoded = decoder.write(message);
219
- this.pgliteDebugLog.write("Front: " + decoded);
220
- if (pipelineStartMessages.includes(message[0])) {
221
- this.isExtendedQuery = true;
222
- }
223
- if (message[0] === pg_gateway_1.FrontendMessageCode.Sync) {
224
- this.isExtendedQuery = false;
225
- this.eqpErrored = false;
226
- }
228
+ async *getMessages(request, response) {
229
+ this.pgliteDebugLog.write(`\n[-> ${codeToFrontendMessageName(request[0])}] ` + decoder.write(request));
227
230
  for await (const bm of (0, pg_gateway_1.getMessages)(response)) {
228
- if (this.eqpErrored) {
229
- continue;
230
- }
231
- if (this.isExtendedQuery && bm[0] === pg_gateway_1.BackendMessageCode.ErrorMessage) {
232
- this.eqpErrored = true;
233
- }
234
- if (this.isExtendedQuery && bm[0] === pg_gateway_1.BackendMessageCode.ReadyForQuery) {
235
- this.pgliteDebugLog.write("Filtered: " + decoder.write(bm));
236
- continue;
237
- }
238
- this.pgliteDebugLog.write("Sent: " + decoder.write(bm));
231
+ this.pgliteDebugLog.write(`\n[<- ${codeTogBackendMessageName(bm[0])}] ${decoder.write(bm)}`);
239
232
  yield bm;
240
233
  }
241
234
  }
242
235
  }
243
- exports.PGliteExtendedQueryPatch = PGliteExtendedQueryPatch;
236
+ exports.PGliteLoggerPatch = PGliteLoggerPatch;
@@ -231,7 +231,6 @@ class DataConnectEmulator {
231
231
  connectionString: connectionString.toString(),
232
232
  database,
233
233
  serviceId,
234
- maxOpenConnections: 1,
235
234
  });
236
235
  this.logger.logLabeled("DEBUG", "Data Connect", `Successfully connected to ${connectionString}}`);
237
236
  return true;
@@ -54,36 +54,36 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "3.0.2",
58
- "expectedSize": 30298976,
59
- "expectedChecksum": "73ea9380be2f18c2ade56c35c75069ce",
60
- "expectedChecksumSHA256": "e0e3bbfc0c9daaa3b29a5775c5a2f02b74868d5c526e059054447649b40d5555",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.0.2",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.0.2"
57
+ "version": "3.1.1",
58
+ "expectedSize": 30450528,
59
+ "expectedChecksum": "06e49453e3c9f876d445972a3588965f",
60
+ "expectedChecksumSHA256": "07dbbb5d1356597acc478d3c58dbf48e716b3795f4da0e7e174b0fd472b004d2",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.1.1",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.1.1"
63
63
  },
64
64
  "darwin_arm64": {
65
- "version": "3.0.2",
66
- "expectedSize": 29763362,
67
- "expectedChecksum": "33ff59564b3c6b36f894736143d77a8c",
68
- "expectedChecksumSHA256": "90fd7e1c7f6a2fceb6862c474f01ed900dce24194cce7e1206962871eab45918",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.0.2",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.0.2"
65
+ "version": "3.1.1",
66
+ "expectedSize": 29916818,
67
+ "expectedChecksum": "477dd3da175dba6d7545ade62a8274f6",
68
+ "expectedChecksumSHA256": "472fe448f25fa8546aa383406db78eaf4eec4d8b700bb325dbc65899aba1bd20",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.1.1",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.1.1"
71
71
  },
72
72
  "win32": {
73
- "version": "3.0.2",
74
- "expectedSize": 30797824,
75
- "expectedChecksum": "3ca37c9bc16105b93313e335c5158ae7",
76
- "expectedChecksumSHA256": "8c383c0add384ffacb0b373fb7b19f382ad4768dc8005fed739838b724a7eaa1",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.0.2",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.0.2.exe"
73
+ "version": "3.1.1",
74
+ "expectedSize": 30951424,
75
+ "expectedChecksum": "c51ec99bffd2f37fa2bb793f7cc74ea7",
76
+ "expectedChecksumSHA256": "e03d0dae5f040b03d211d6741540ed11206b03548b230eb8e36338b5b57545ee",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.1.1",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.1.1.exe"
79
79
  },
80
80
  "linux": {
81
- "version": "3.0.2",
82
- "expectedSize": 30220472,
83
- "expectedChecksum": "30c9f580530cd653fd047cec93936e96",
84
- "expectedChecksumSHA256": "dd228bea045b12232c871aa1b0dde876e8afcd2c3b1a21d72afbe5dffef38eb8",
85
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.0.2",
86
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.0.2"
81
+ "version": "3.1.1",
82
+ "expectedSize": 30372024,
83
+ "expectedChecksum": "8a3977f07facaacaa9be3f9ec6bd90b7",
84
+ "expectedChecksumSHA256": "5c36d16d4ded51dd887c939a82e8cdc7e84cb40a2c5d00cb02bff90ca65c2472",
85
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.1.1",
86
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.1.1"
87
87
  }
88
88
  }
89
89
  }
@@ -27,7 +27,7 @@ async function check(projectId, apiUri, prefix, silent = false) {
27
27
  return true;
28
28
  }
29
29
  const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`, {
30
- headers: { "x-goog-quota-user": `projects/${projectId}` },
30
+ headers: { "x-goog-user-project": `projects/${projectId}` },
31
31
  skipLog: { resBody: true },
32
32
  });
33
33
  const isEnabled = res.body.state === "ENABLED";
@@ -45,7 +45,7 @@ function isPermissionError(e) {
45
45
  async function enable(projectId, apiName) {
46
46
  try {
47
47
  await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, undefined, {
48
- headers: { "x-goog-quota-user": `projects/${projectId}` },
48
+ headers: { "x-goog-user-project": `projects/${projectId}` },
49
49
  skipLog: { resBody: true },
50
50
  });
51
51
  cacheEnabledAPI(projectId, apiName);
package/lib/env.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isFirebaseStudio = isFirebaseStudio;
4
4
  exports.isFirebaseMcp = isFirebaseMcp;
5
+ exports.detectAIAgent = detectAIAgent;
5
6
  const fsutils_1 = require("./fsutils");
6
7
  let googleIdxFolderExists;
7
8
  function isFirebaseStudio() {
@@ -15,3 +16,20 @@ function isFirebaseStudio() {
15
16
  function isFirebaseMcp() {
16
17
  return !!process.env.IS_FIREBASE_MCP;
17
18
  }
19
+ function detectAIAgent() {
20
+ if (process.env.ANTIGRAVITY_CLI_ALIAS)
21
+ return "antigravity";
22
+ if (process.env.CLAUDECODE)
23
+ return "claude_code";
24
+ if (process.env.CLINE_ACTIVE)
25
+ return "cline";
26
+ if (process.env.CODEX_SANDBOX)
27
+ return "codex_cli";
28
+ if (process.env.CURSOR_AGENT)
29
+ return "cursor";
30
+ if (process.env.GEMINI_CLI)
31
+ return "gemini_cli";
32
+ if (process.env.OPENCODE)
33
+ return "open_code";
34
+ return "unknown";
35
+ }
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RecurrenceType = exports.DatabaseEdition = exports.PointInTimeRecoveryEnablement = exports.PointInTimeRecoveryEnablementOption = exports.DatabaseDeleteProtectionState = exports.DatabaseDeleteProtectionStateOption = 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.ArrayConfig = exports.Order = exports.Density = exports.ApiScope = exports.QueryScope = exports.Mode = void 0;
4
+ exports.validateEnablementOption = validateEnablementOption;
5
+ const error_1 = require("../error");
4
6
  var Mode;
5
7
  (function (Mode) {
6
8
  Mode["ASCENDING"] = "ASCENDING";
@@ -51,26 +53,32 @@ var DatabaseType;
51
53
  DatabaseType["DATASTORE_MODE"] = "DATASTORE_MODE";
52
54
  DatabaseType["FIRESTORE_NATIVE"] = "FIRESTORE_NATIVE";
53
55
  })(DatabaseType || (exports.DatabaseType = DatabaseType = {}));
54
- var DatabaseDeleteProtectionStateOption;
55
- (function (DatabaseDeleteProtectionStateOption) {
56
- DatabaseDeleteProtectionStateOption["ENABLED"] = "ENABLED";
57
- DatabaseDeleteProtectionStateOption["DISABLED"] = "DISABLED";
58
- })(DatabaseDeleteProtectionStateOption || (exports.DatabaseDeleteProtectionStateOption = DatabaseDeleteProtectionStateOption = {}));
56
+ var EnablementOption;
57
+ (function (EnablementOption) {
58
+ EnablementOption["ENABLED"] = "ENABLED";
59
+ EnablementOption["DISABLED"] = "DISABLED";
60
+ })(EnablementOption || (exports.EnablementOption = EnablementOption = {}));
59
61
  var DatabaseDeleteProtectionState;
60
62
  (function (DatabaseDeleteProtectionState) {
61
63
  DatabaseDeleteProtectionState["ENABLED"] = "DELETE_PROTECTION_ENABLED";
62
64
  DatabaseDeleteProtectionState["DISABLED"] = "DELETE_PROTECTION_DISABLED";
63
65
  })(DatabaseDeleteProtectionState || (exports.DatabaseDeleteProtectionState = DatabaseDeleteProtectionState = {}));
64
- var PointInTimeRecoveryEnablementOption;
65
- (function (PointInTimeRecoveryEnablementOption) {
66
- PointInTimeRecoveryEnablementOption["ENABLED"] = "ENABLED";
67
- PointInTimeRecoveryEnablementOption["DISABLED"] = "DISABLED";
68
- })(PointInTimeRecoveryEnablementOption || (exports.PointInTimeRecoveryEnablementOption = PointInTimeRecoveryEnablementOption = {}));
69
66
  var PointInTimeRecoveryEnablement;
70
67
  (function (PointInTimeRecoveryEnablement) {
71
68
  PointInTimeRecoveryEnablement["ENABLED"] = "POINT_IN_TIME_RECOVERY_ENABLED";
72
69
  PointInTimeRecoveryEnablement["DISABLED"] = "POINT_IN_TIME_RECOVERY_DISABLED";
73
70
  })(PointInTimeRecoveryEnablement || (exports.PointInTimeRecoveryEnablement = PointInTimeRecoveryEnablement = {}));
71
+ var RealtimeUpdatesMode;
72
+ (function (RealtimeUpdatesMode) {
73
+ RealtimeUpdatesMode["ENABLED"] = "REALTIME_UPDATES_MODE_ENABLED";
74
+ RealtimeUpdatesMode["DISABLED"] = "REALTIME_UPDATES_MODE_DISABLED";
75
+ })(RealtimeUpdatesMode || (exports.RealtimeUpdatesMode = RealtimeUpdatesMode = {}));
76
+ var DataAccessMode;
77
+ (function (DataAccessMode) {
78
+ DataAccessMode["UNSPECIFIED"] = "DATA_ACCESS_MODE_UNSPECIFIED";
79
+ DataAccessMode["ENABLED"] = "DATA_ACCESS_MODE_ENABLED";
80
+ DataAccessMode["DISABLED"] = "DATA_ACCESS_MODE_DISABLED";
81
+ })(DataAccessMode || (exports.DataAccessMode = DataAccessMode = {}));
74
82
  var DatabaseEdition;
75
83
  (function (DatabaseEdition) {
76
84
  DatabaseEdition["DATABASE_EDITION_UNSPECIFIED"] = "DATABASE_EDITION_UNSPECIFIED";
@@ -82,3 +90,10 @@ var RecurrenceType;
82
90
  RecurrenceType["DAILY"] = "DAILY";
83
91
  RecurrenceType["WEEKLY"] = "WEEKLY";
84
92
  })(RecurrenceType || (exports.RecurrenceType = RecurrenceType = {}));
93
+ function validateEnablementOption(optionValue, flagName, helpCommandText) {
94
+ if (optionValue &&
95
+ optionValue !== EnablementOption.ENABLED &&
96
+ optionValue !== EnablementOption.DISABLED) {
97
+ throw new error_1.FirebaseError(`Invalid value for flag --${flagName}. ${helpCommandText}`);
98
+ }
99
+ }
@@ -536,6 +536,9 @@ class FirestoreApi {
536
536
  databaseEdition: req.databaseEdition,
537
537
  deleteProtectionState: req.deleteProtectionState,
538
538
  pointInTimeRecoveryEnablement: req.pointInTimeRecoveryEnablement,
539
+ realtimeUpdatesMode: req.realtimeUpdatesMode,
540
+ firestoreDataAccessMode: req.firestoreDataAccessMode,
541
+ mongodbCompatibleDataAccessMode: req.mongodbCompatibleDataAccessMode,
539
542
  cmekConfig: req.cmekConfig,
540
543
  };
541
544
  const options = { queryParams: { databaseId: req.databaseId } };
package/lib/gcp/iam.js CHANGED
@@ -63,7 +63,7 @@ async function testResourceIamPermissions(origin, apiVersion, resourceName, perm
63
63
  }
64
64
  const headers = {};
65
65
  if (quotaUser) {
66
- headers["x-goog-quota-user"] = quotaUser;
66
+ headers["x-goog-user-project"] = quotaUser;
67
67
  }
68
68
  const response = await localClient.post(`/${resourceName}:testIamPermissions`, { permissions }, { headers });
69
69
  const allowed = new Set(response.body.permissions || []);
package/lib/gcp/rules.js CHANGED
@@ -28,9 +28,12 @@ function _handleErrorResponse(response) {
28
28
  code: 2,
29
29
  });
30
30
  }
31
- async function getLatestRulesetName(projectId, service) {
31
+ async function getLatestRulesetName(projectId, service, resourceName) {
32
32
  const releases = await listAllReleases(projectId);
33
- const prefix = `projects/${projectId}/releases/${service}`;
33
+ let prefix = `projects/${projectId}/releases/${service}`;
34
+ if (resourceName) {
35
+ prefix += `/${resourceName}`;
36
+ }
34
37
  const release = releases.find((r) => r.name.startsWith(prefix));
35
38
  if (!release) {
36
39
  return null;
@@ -22,7 +22,7 @@ const serviceUsagePollerOptions = {
22
22
  async function generateServiceIdentity(projectNumber, service, prefix) {
23
23
  utils.logLabeledBullet(prefix, `generating the service identity for ${(0, colorette_1.bold)(service)}...`);
24
24
  try {
25
- const res = await exports.apiClient.post(`projects/${projectNumber}/services/${service}:generateServiceIdentity`, {}, { headers: { "x-goog-quota-user": `projects/${projectNumber}` } });
25
+ const res = await exports.apiClient.post(`projects/${projectNumber}/services/${service}:generateServiceIdentity`, {}, { headers: { "x-goog-user-project": `projects/${projectNumber}` } });
26
26
  return res.body;
27
27
  }
28
28
  catch (err) {
@@ -39,6 +39,6 @@ async function generateServiceIdentityAndPoll(projectNumber, service, prefix) {
39
39
  await poller.pollOperation({
40
40
  ...serviceUsagePollerOptions,
41
41
  operationResourceName: op.name,
42
- headers: { "x-goog-quota-user": `projects/${projectNumber}` },
42
+ headers: { "x-goog-user-project": `projects/${projectNumber}` },
43
43
  });
44
44
  }
@@ -33,7 +33,7 @@ async function initIndexes(setup, config, info) {
33
33
  utils.logBullet(`Downloaded the existing Firestore indexes from the Firebase console`);
34
34
  }
35
35
  }
36
- info.writeRules = await config.confirmWriteProjectFile(info.indexesFilename, info.indexes);
36
+ info.writeIndexes = await config.confirmWriteProjectFile(info.indexesFilename, info.indexes);
37
37
  }
38
38
  async function getIndexesFromConsole(projectId, databaseId) {
39
39
  const indexesPromise = indexes.listIndexes(projectId, databaseId);
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.askQuestions = askQuestions;
4
4
  exports.actuate = actuate;
5
5
  const clc = require("colorette");
6
- const logger_1 = require("../../logger");
7
- const prompt_1 = require("../../prompt");
8
- const templates_1 = require("../../templates");
9
- const error_1 = require("../../error");
6
+ const logger_1 = require("../../../logger");
7
+ const prompt_1 = require("../../../prompt");
8
+ const templates_1 = require("../../../templates");
9
+ const error_1 = require("../../../error");
10
+ const rules_1 = require("./rules");
10
11
  const RULES_TEMPLATE = (0, templates_1.readTemplateSync)("init/storage/storage.rules");
11
12
  const DEFAULT_RULES_FILE = "storage.rules";
12
13
  async function askQuestions(setup, config) {
@@ -20,6 +21,13 @@ async function askQuestions(setup, config) {
20
21
  rules: RULES_TEMPLATE,
21
22
  writeRules: true,
22
23
  };
24
+ if (setup.projectId) {
25
+ const downloadedRules = await (0, rules_1.getRulesFromConsole)(setup.projectId);
26
+ if (downloadedRules) {
27
+ info.rules = downloadedRules;
28
+ logger_1.logger.info(`Downloaded the existing Storage Security Rules from the Firebase console`);
29
+ }
30
+ }
23
31
  info.rulesFilename = await (0, prompt_1.input)({
24
32
  message: "What file should be used for Storage Rules?",
25
33
  default: DEFAULT_RULES_FILE,
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRulesFromConsole = getRulesFromConsole;
4
+ const gcp = require("../../../gcp");
5
+ const utils = require("../../../utils");
6
+ async function getRulesFromConsole(projectId) {
7
+ const defaultBucket = await gcp.storage.getDefaultBucket(projectId);
8
+ const name = await gcp.rules.getLatestRulesetName(projectId, "firebase.storage", defaultBucket);
9
+ if (!name) {
10
+ return null;
11
+ }
12
+ const rules = await gcp.rules.getRulesetContent(name);
13
+ if (rules.length <= 0) {
14
+ return utils.reject("Ruleset has no files", { exit: 1 });
15
+ }
16
+ if (rules.length > 1) {
17
+ return utils.reject("Ruleset has too many files: " + rules.length, { exit: 1 });
18
+ }
19
+ return rules[0].content;
20
+ }
@@ -10,7 +10,7 @@ const getDefaultDatabaseInstance_1 = require("../../../getDefaultDatabaseInstanc
10
10
  exports.get_security_rules = (0, tool_1.tool)("core", {
11
11
  name: "get_security_rules",
12
12
  description: "Use this to retrieve the security rules for a specified Firebase service. " +
13
- "If there are multiple instances of that service in the product, the rules for the defualt instance are returned.",
13
+ "If there are multiple instances of that service in the product, the rules for the default instance are returned.",
14
14
  inputSchema: zod_1.z.object({
15
15
  type: zod_1.z.enum(["firestore", "rtdb", "storage"]).describe("The service to get rules for."),
16
16
  }),
@@ -31,7 +31,7 @@ function toText(response, filters) {
31
31
  }
32
32
  exports.get_report = (0, tool_1.tool)("crashlytics", {
33
33
  name: "get_report",
34
- description: `Use this to request numerical reports from Crashlytics. The result aggregates the sum of events and impacted users, grouped by a dimension appropriate for that report. Agents must read the [Firebase Crashlytics Reports Guide](firebase://guides/crashlytics/reports) using the \`firebase_read_resources\` tool before calling to understand criticial prerequisites for requesting reports and how to interpret the results.
34
+ description: `Use this to request numerical reports from Crashlytics. The result aggregates the sum of events and impacted users, grouped by a dimension appropriate for that report. Agents must read the [Firebase Crashlytics Reports Guide](firebase://guides/crashlytics/reports) using the \`firebase_read_resources\` tool before calling to understand critical prerequisites for requesting reports and how to interpret the results.
35
35
  `.trim(),
36
36
  inputSchema: reports_1.ReportInputSchema,
37
37
  annotations: {
@@ -9,7 +9,7 @@ const delete_1 = require("../../../firestore/delete");
9
9
  const types_1 = require("../../../emulator/types");
10
10
  exports.delete_document = (0, tool_1.tool)("firestore", {
11
11
  name: "delete_document",
12
- description: "Use this to delete a Firestore documents from a database in the current project by full document paths. Use this if you know the exact path of a document.",
12
+ description: "Use this to delete Firestore documents from a database in the current project by full document paths. Use this if you know the exact path of a document.",
13
13
  inputSchema: zod_1.z.object({
14
14
  database: zod_1.z
15
15
  .string()
@@ -9,7 +9,7 @@ const converter_1 = require("./converter");
9
9
  const types_1 = require("../../../emulator/types");
10
10
  exports.query_collection = (0, tool_1.tool)("firestore", {
11
11
  name: "query_collection",
12
- description: "Use this to retrieve one or more Firestore documents from a collection is a database in the current project by a collection with a full document path. Use this if you know the exact path of a collection and the filtering clause you would like for the document.",
12
+ description: "Use this to retrieve one or more Firestore documents from a collection in a database in the current project by a collection with a full document path. Use this if you know the exact path of a collection and the filtering clause you would like for the document.",
13
13
  inputSchema: zod_1.z.object({
14
14
  database: zod_1.z
15
15
  .string()
package/lib/track.js CHANGED
@@ -15,21 +15,6 @@ const configstore_1 = require("./configstore");
15
15
  const logger_1 = require("./logger");
16
16
  const env_1 = require("./env");
17
17
  const pkg = require("../package.json");
18
- function detectAIAgent() {
19
- if (process.env.CLAUDECODE)
20
- return "claude_code";
21
- if (process.env.CLINE_ACTIVE)
22
- return "cline";
23
- if (process.env.CODEX_SANDBOX)
24
- return "codex_cli";
25
- if (process.env.CURSOR_AGENT)
26
- return "cursor";
27
- if (process.env.GEMINI_CLI)
28
- return "gemini_cli";
29
- if (process.env.OPENCODE)
30
- return "open_code";
31
- return "unknown";
32
- }
33
18
  exports.GA4_PROPERTIES = {
34
19
  cli: {
35
20
  measurementId: process.env.FIREBASE_CLI_GA4_MEASUREMENT_ID || "G-PDN0QWHQJR",
@@ -67,7 +52,7 @@ const GA4_USER_PROPS = {
67
52
  value: (0, env_1.isFirebaseStudio)().toString(),
68
53
  },
69
54
  ai_agent: {
70
- value: detectAIAgent(),
55
+ value: (0, env_1.detectAIAgent)(),
71
56
  },
72
57
  };
73
58
  async function trackGA4(eventName, params, duration = 1) {