firebase-tools 14.12.0 → 14.12.1

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 (35) hide show
  1. package/lib/commands/firestore-databases-create.js +11 -0
  2. package/lib/crashlytics/buildToolsJarHelper.js +1 -2
  3. package/lib/deploy/dataconnect/prepare.js +2 -2
  4. package/lib/deploy/dataconnect/release.js +2 -2
  5. package/lib/deploy/firestore/deploy.js +10 -0
  6. package/lib/deploy/functions/prepareFunctionsUpload.js +3 -1
  7. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  8. package/lib/firestore/api-sort.js +96 -3
  9. package/lib/firestore/api-types.js +14 -1
  10. package/lib/firestore/api.js +68 -1
  11. package/lib/firestore/validator.js +1 -1
  12. package/lib/functions/deprecationWarnings.js +4 -4
  13. package/lib/gcp/cloudsql/connect.js +1 -1
  14. package/lib/init/features/dataconnect/sdk.js +1 -2
  15. package/lib/mcp/index.js +75 -2
  16. package/lib/mcp/prompt.js +10 -0
  17. package/lib/mcp/prompts/core/deploy.js +58 -0
  18. package/lib/mcp/prompts/core/index.js +5 -0
  19. package/lib/mcp/prompts/index.js +45 -0
  20. package/lib/mcp/tools/core/get_sdk_config.js +10 -0
  21. package/lib/mcp/tools/database/get_data.js +49 -0
  22. package/lib/mcp/tools/database/get_rules.js +39 -0
  23. package/lib/mcp/tools/database/index.js +8 -0
  24. package/lib/mcp/tools/database/set_data.js +57 -0
  25. package/lib/mcp/tools/database/set_rules.js +41 -0
  26. package/lib/mcp/tools/database/validate_rules.js +41 -0
  27. package/lib/mcp/tools/index.js +4 -1
  28. package/lib/mcp/tools/rules/get_rules.js +1 -1
  29. package/lib/mcp/types.js +2 -0
  30. package/lib/mcp/util.js +2 -0
  31. package/lib/rtdb.js +10 -6
  32. package/lib/utils.js +24 -1
  33. package/package.json +1 -1
  34. package/schema/firebase-config.json +3 -0
  35. package/templates/init/firestore/firestore.indexes.json +26 -1
@@ -14,6 +14,7 @@ const error_1 = require("../error");
14
14
  exports.command = new command_1.Command("firestore:databases:create <database>")
15
15
  .description("create a database in your Firebase project")
16
16
  .option("--location <locationId>", "region to create database, for example 'nam5'. Run 'firebase firestore:locations' to get a list of eligible locations (required)")
17
+ .option("--edition <edition>", "the edition of the database to create, for example 'standard' or 'enterprise'. If not provided, 'standard' is used as a default.")
17
18
  .option("--delete-protection <deleteProtectionState>", "whether or not to prevent deletion of database, for example 'ENABLED' or 'DISABLED'. Default is 'DISABLED'")
18
19
  .option("--point-in-time-recovery <enablement>", "whether to enable the PITR feature on this database, for example 'ENABLED' or 'DISABLED'. Default is 'DISABLED'")
19
20
  .option("-k, --kms-key-name <kmsKeyName>", "the resource ID of a Cloud KMS key. If set, the database created will be a " +
@@ -29,6 +30,15 @@ exports.command = new command_1.Command("firestore:databases:create <database>")
29
30
  throw new error_1.FirebaseError(`Missing required flag --location. ${helpCommandText}`);
30
31
  }
31
32
  const type = types.DatabaseType.FIRESTORE_NATIVE;
33
+ let databaseEdition = types.DatabaseEdition.STANDARD;
34
+ if (options.edition) {
35
+ const edition = options.edition.toUpperCase();
36
+ if (edition !== types.DatabaseEdition.STANDARD &&
37
+ edition !== types.DatabaseEdition.ENTERPRISE) {
38
+ throw new error_1.FirebaseError(`Invalid value for flag --edition. ${helpCommandText}`);
39
+ }
40
+ databaseEdition = edition;
41
+ }
32
42
  if (options.deleteProtection &&
33
43
  options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.ENABLED &&
34
44
  options.deleteProtection !== types.DatabaseDeleteProtectionStateOption.DISABLED) {
@@ -56,6 +66,7 @@ exports.command = new command_1.Command("firestore:databases:create <database>")
56
66
  databaseId: database,
57
67
  locationId: options.location,
58
68
  type,
69
+ databaseEdition,
59
70
  deleteProtectionState,
60
71
  pointInTimeRecoveryEnablement,
61
72
  cmekConfig,
@@ -8,7 +8,6 @@ const spawn = require("cross-spawn");
8
8
  const downloadUtils = require("../downloadUtils");
9
9
  const error_1 = require("../error");
10
10
  const logger_1 = require("../logger");
11
- const node_fs_1 = require("node:fs");
12
11
  const utils = require("../utils");
13
12
  const JAR_CACHE_DIR = process.env.FIREBASE_CRASHLYTICS_BUILDTOOLS_PATH ||
14
13
  path.join(os.homedir(), ".cache", "firebase", "crashlytics", "buildtools");
@@ -26,7 +25,7 @@ async function fetchBuildtoolsJar() {
26
25
  }
27
26
  if (fs.existsSync(JAR_CACHE_DIR)) {
28
27
  logger_1.logger.debug(`Deleting Jar cache at ${JAR_CACHE_DIR} because the CLI was run with a newer Jar version`);
29
- (0, node_fs_1.rmSync)(JAR_CACHE_DIR, { recursive: true });
28
+ fs.rmSync(JAR_CACHE_DIR, { recursive: true, force: true });
30
29
  }
31
30
  utils.logBullet("Downloading crashlytics-buildtools.jar to " + jarPath);
32
31
  utils.logBullet("For open source licenses used by this command, look in the META-INF directory in the buildtools.jar file");
@@ -19,7 +19,7 @@ const types_1 = require("../../dataconnect/types");
19
19
  const schemaMigration_1 = require("../../dataconnect/schemaMigration");
20
20
  const freeTrial_1 = require("../../dataconnect/freeTrial");
21
21
  async function default_1(context, options) {
22
- var _a;
22
+ var _a, _b, _c;
23
23
  const projectId = (0, projectUtils_1.needProjectId)(options);
24
24
  if (!(await (0, cloudbilling_1.checkBillingEnabled)(projectId))) {
25
25
  throw new error_1.FirebaseError((0, freeTrial_1.upgradeInstructions)(projectId));
@@ -53,7 +53,7 @@ async function default_1(context, options) {
53
53
  utils.logLabeledBullet("dataconnect", `Successfully prepared schema and connectors`);
54
54
  if (options.dryRun) {
55
55
  for (const si of serviceInfos) {
56
- await (0, schemaMigration_1.diffSchema)(options, si.schema, (_a = si.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation);
56
+ await (0, schemaMigration_1.diffSchema)(options, si.schema, (_c = (_b = (_a = si.dataConnectYaml.schema) === null || _a === void 0 ? void 0 : _a.datasource) === null || _b === void 0 ? void 0 : _b.postgresql) === null || _c === void 0 ? void 0 : _c.schemaValidation);
57
57
  }
58
58
  utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
59
59
  await Promise.all(serviceInfos
@@ -17,10 +17,10 @@ async function default_1(context, options) {
17
17
  }));
18
18
  })
19
19
  .map((s) => {
20
- var _a;
20
+ var _a, _b, _c, _d;
21
21
  return ({
22
22
  schema: s.schema,
23
- validationMode: (_a = s.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation,
23
+ validationMode: (_d = (_c = (_b = (_a = s.dataConnectYaml) === null || _a === void 0 ? void 0 : _a.schema) === null || _b === void 0 ? void 0 : _b.datasource) === null || _c === void 0 ? void 0 : _c.postgresql) === null || _d === void 0 ? void 0 : _d.schemaValidation,
24
24
  });
25
25
  });
26
26
  if (wantSchemas.length) {
@@ -22,6 +22,15 @@ async function createDatabase(context, options) {
22
22
  if (!firestoreCfg.database) {
23
23
  firestoreCfg.database = "(default)";
24
24
  }
25
+ let edition = types.DatabaseEdition.STANDARD;
26
+ if (firestoreCfg.edition) {
27
+ const upperEdition = firestoreCfg.edition.toUpperCase();
28
+ if (upperEdition !== types.DatabaseEdition.STANDARD &&
29
+ upperEdition !== types.DatabaseEdition.ENTERPRISE) {
30
+ throw new error_1.FirebaseError(`Invalid edition specified for database in firebase.json: ${firestoreCfg.edition}`);
31
+ }
32
+ edition = upperEdition;
33
+ }
25
34
  const api = new api_1.FirestoreApi();
26
35
  try {
27
36
  await api.getDatabase(options.projectId, firestoreCfg.database);
@@ -34,6 +43,7 @@ async function createDatabase(context, options) {
34
43
  databaseId: firestoreCfg.database,
35
44
  locationId: firestoreCfg.location || "nam5",
36
45
  type: types.DatabaseType.FIRESTORE_NATIVE,
46
+ databaseEdition: edition,
37
47
  deleteProtectionState: types.DatabaseDeleteProtectionState.DISABLED,
38
48
  pointInTimeRecoveryEnablement: types.PointInTimeRecoveryEnablement.DISABLED,
39
49
  };
@@ -74,7 +74,9 @@ async function packageSource(sourceDir, config, runtimeConfig) {
74
74
  name: CONFIG_DEST_FILE,
75
75
  mode: 420,
76
76
  });
77
- (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
77
+ if (Object.keys(runtimeConfig).some((k) => k !== "firebase")) {
78
+ (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
79
+ }
78
80
  }
79
81
  await pipeAsync(archive, fileStream);
80
82
  }
@@ -54,28 +54,28 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.11.0",
58
- "expectedSize": 29234016,
59
- "expectedChecksum": "96da48708b8210f0d3d97099d777b322",
60
- "expectedChecksumSHA256": "10fe334f2c4145e4c9d27bc442bdc92b1252164d3daac47bb7555da42b6d7050",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.0",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.0"
57
+ "version": "2.11.1",
58
+ "expectedSize": 29352800,
59
+ "expectedChecksum": "52d86a5546bbb9e2fcd67faa90b9f07e",
60
+ "expectedChecksumSHA256": "217b66589c32d4378201100fa968e69f7c94537044b892be1aaa14d7f6ce6b12",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.1",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1"
63
63
  },
64
64
  "win32": {
65
- "version": "2.11.0",
66
- "expectedSize": 29719040,
67
- "expectedChecksum": "df6b221af204a4a21163bcc73367f99e",
68
- "expectedChecksumSHA256": "af17c0d873b2b8f1919652c1ad9fd4aed417cd82bc35bbc5cc4a302fc90f9a03",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.0",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.0.exe"
65
+ "version": "2.11.1",
66
+ "expectedSize": 29841920,
67
+ "expectedChecksum": "704cee75ad2d384cf28ac1683c8d1179",
68
+ "expectedChecksumSHA256": "fadb5a1d0f03160c133389df9d452cc620cfdface041971a5356ac5a69e937ff",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.1",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.11.0",
74
- "expectedSize": 29159608,
75
- "expectedChecksum": "38d6161f7e8f06ee89e5dc4094f1a9b6",
76
- "expectedChecksumSHA256": "3391efac3570164141b5d7f3e3d48b6426ec9c8cf9eafeabee3fa31678120218",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.0",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.0"
73
+ "version": "2.11.1",
74
+ "expectedSize": 29282488,
75
+ "expectedChecksum": "435656eab760033228bf7b7e77d56dde",
76
+ "expectedChecksumSHA256": "0abb33c0bba0ed8ef1394cc1cab5a333ec81b3dddf48aa625abe8ca28f38840e",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.1",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1"
79
79
  }
80
80
  }
81
81
  }
@@ -8,6 +8,19 @@ const QUERY_SCOPE_SEQUENCE = [
8
8
  API.QueryScope.COLLECTION,
9
9
  undefined,
10
10
  ];
11
+ const API_SCOPE_SEQUENCE = [
12
+ API.ApiScope.ANY_API,
13
+ API.ApiScope.DATASTORE_MODE_API,
14
+ API.ApiScope.MONGODB_COMPATIBLE_API,
15
+ undefined,
16
+ ];
17
+ const DENSITY_SEQUENCE = [
18
+ API.Density.DENSITY_UNSPECIFIED,
19
+ API.Density.SPARSE_ALL,
20
+ API.Density.SPARSE_ANY,
21
+ API.Density.DENSE,
22
+ undefined,
23
+ ];
11
24
  const ORDER_SEQUENCE = [API.Order.ASCENDING, API.Order.DESCENDING, undefined];
12
25
  const ARRAY_CONFIG_SEQUENCE = [API.ArrayConfig.CONTAINS, undefined];
13
26
  function compareSpecIndex(a, b) {
@@ -17,7 +30,23 @@ function compareSpecIndex(a, b) {
17
30
  if (a.queryScope !== b.queryScope) {
18
31
  return compareQueryScope(a.queryScope, b.queryScope);
19
32
  }
20
- return compareArrays(a.fields, b.fields, compareIndexField);
33
+ let cmp = compareArrays(a.fields, b.fields, compareIndexField);
34
+ if (cmp !== 0) {
35
+ return cmp;
36
+ }
37
+ cmp = compareApiScope(a.apiScope, b.apiScope);
38
+ if (cmp !== 0) {
39
+ return cmp;
40
+ }
41
+ cmp = compareDensity(a.density, b.density);
42
+ if (cmp !== 0) {
43
+ return cmp;
44
+ }
45
+ cmp = compareBoolean(a.multikey, b.multikey);
46
+ if (cmp !== 0) {
47
+ return cmp;
48
+ }
49
+ return compareBoolean(a.unique, b.unique);
21
50
  }
22
51
  exports.compareSpecIndex = compareSpecIndex;
23
52
  function compareApiIndex(a, b) {
@@ -31,7 +60,23 @@ function compareApiIndex(a, b) {
31
60
  if (a.queryScope !== b.queryScope) {
32
61
  return compareQueryScope(a.queryScope, b.queryScope);
33
62
  }
34
- return compareArrays(a.fields, b.fields, compareIndexField);
63
+ let cmp = compareArrays(a.fields, b.fields, compareIndexField);
64
+ if (cmp !== 0) {
65
+ return cmp;
66
+ }
67
+ cmp = compareApiScope(a.apiScope, b.apiScope);
68
+ if (cmp !== 0) {
69
+ return cmp;
70
+ }
71
+ cmp = compareDensity(a.density, b.density);
72
+ if (cmp !== 0) {
73
+ return cmp;
74
+ }
75
+ cmp = compareBoolean(a.multikey, b.multikey);
76
+ if (cmp !== 0) {
77
+ return cmp;
78
+ }
79
+ return compareBoolean(a.unique, b.unique);
35
80
  }
36
81
  exports.compareApiIndex = compareApiIndex;
37
82
  function compareApiDatabase(a, b) {
@@ -115,14 +160,62 @@ function compareFieldIndex(a, b) {
115
160
  if (a.arrayConfig !== b.arrayConfig) {
116
161
  return compareArrayConfig(a.arrayConfig, b.arrayConfig);
117
162
  }
118
- return 0;
163
+ let cmp = compareApiScope(a.apiScope, b.apiScope);
164
+ if (cmp !== 0) {
165
+ return cmp;
166
+ }
167
+ cmp = compareDensity(a.density, b.density);
168
+ if (cmp !== 0) {
169
+ return cmp;
170
+ }
171
+ cmp = compareBoolean(a.multikey, b.multikey);
172
+ if (cmp !== 0) {
173
+ return cmp;
174
+ }
175
+ return compareBoolean(a.unique, b.unique);
119
176
  }
120
177
  function compareQueryScope(a, b) {
121
178
  return QUERY_SCOPE_SEQUENCE.indexOf(a) - QUERY_SCOPE_SEQUENCE.indexOf(b);
122
179
  }
180
+ function compareApiScope(a, b) {
181
+ if (a === b) {
182
+ return 0;
183
+ }
184
+ if (a === undefined) {
185
+ return -1;
186
+ }
187
+ if (b === undefined) {
188
+ return 1;
189
+ }
190
+ return API_SCOPE_SEQUENCE.indexOf(a) - API_SCOPE_SEQUENCE.indexOf(b);
191
+ }
192
+ function compareDensity(a, b) {
193
+ if (a === b) {
194
+ return 0;
195
+ }
196
+ if (a === undefined) {
197
+ return -1;
198
+ }
199
+ if (b === undefined) {
200
+ return 1;
201
+ }
202
+ return DENSITY_SEQUENCE.indexOf(a) - DENSITY_SEQUENCE.indexOf(b);
203
+ }
123
204
  function compareOrder(a, b) {
124
205
  return ORDER_SEQUENCE.indexOf(a) - ORDER_SEQUENCE.indexOf(b);
125
206
  }
207
+ function compareBoolean(a, b) {
208
+ if (a === b) {
209
+ return 0;
210
+ }
211
+ if (a === undefined) {
212
+ return -1;
213
+ }
214
+ if (b === undefined) {
215
+ return 1;
216
+ }
217
+ return Number(a) - Number(b);
218
+ }
126
219
  function compareArrayConfig(a, b) {
127
220
  return ARRAY_CONFIG_SEQUENCE.indexOf(a) - ARRAY_CONFIG_SEQUENCE.indexOf(b);
128
221
  }
@@ -1,6 +1,6 @@
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.QueryScope = exports.Mode = void 0;
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;
4
4
  var Mode;
5
5
  (function (Mode) {
6
6
  Mode["ASCENDING"] = "ASCENDING";
@@ -12,6 +12,19 @@ var QueryScope;
12
12
  QueryScope["COLLECTION"] = "COLLECTION";
13
13
  QueryScope["COLLECTION_GROUP"] = "COLLECTION_GROUP";
14
14
  })(QueryScope = exports.QueryScope || (exports.QueryScope = {}));
15
+ var ApiScope;
16
+ (function (ApiScope) {
17
+ ApiScope["ANY_API"] = "ANY_API";
18
+ ApiScope["DATASTORE_MODE_API"] = "DATASTORE_MODE_API";
19
+ ApiScope["MONGODB_COMPATIBLE_API"] = "MONGODB_COMPATIBLE_API";
20
+ })(ApiScope = exports.ApiScope || (exports.ApiScope = {}));
21
+ var Density;
22
+ (function (Density) {
23
+ Density["DENSITY_UNSPECIFIED"] = "DENSITY_UNSPECIFIED";
24
+ Density["SPARSE_ALL"] = "SPARSE_ALL";
25
+ Density["SPARSE_ANY"] = "SPARSE_ANY";
26
+ Density["DENSE"] = "DENSE";
27
+ })(Density = exports.Density || (exports.Density = {}));
15
28
  var Order;
16
29
  (function (Order) {
17
30
  Order["ASCENDING"] = "ASCENDING";
@@ -160,6 +160,10 @@ class FirestoreApi {
160
160
  collectionGroup: util.parseIndexName(index.name).collectionGroupId,
161
161
  queryScope: index.queryScope,
162
162
  fields: index.fields,
163
+ apiScope: index.apiScope,
164
+ density: index.density,
165
+ multikey: index.multikey,
166
+ unique: index.unique,
163
167
  };
164
168
  });
165
169
  if (!fields) {
@@ -179,6 +183,10 @@ class FirestoreApi {
179
183
  order: firstField.order,
180
184
  arrayConfig: firstField.arrayConfig,
181
185
  queryScope: index.queryScope,
186
+ apiScope: index.apiScope,
187
+ density: index.density,
188
+ multikey: index.multikey,
189
+ unique: index.unique,
182
190
  };
183
191
  }),
184
192
  };
@@ -205,6 +213,18 @@ class FirestoreApi {
205
213
  validator.assertHas(index, "collectionGroup");
206
214
  validator.assertHas(index, "queryScope");
207
215
  validator.assertEnum(index, "queryScope", Object.keys(types.QueryScope));
216
+ if (index.apiScope) {
217
+ validator.assertEnum(index, "apiScope", Object.keys(types.ApiScope));
218
+ }
219
+ if (index.density) {
220
+ validator.assertEnum(index, "density", Object.keys(types.Density));
221
+ }
222
+ if (index.multikey) {
223
+ validator.assertType("multikey", index.multikey, "boolean");
224
+ }
225
+ if (index.unique) {
226
+ validator.assertType("unique", index.unique, "boolean");
227
+ }
208
228
  validator.assertHas(index, "fields");
209
229
  index.fields.forEach((field) => {
210
230
  validator.assertHas(field, "fieldPath");
@@ -239,6 +259,18 @@ class FirestoreApi {
239
259
  if (index.queryScope) {
240
260
  validator.assertEnum(index, "queryScope", Object.keys(types.QueryScope));
241
261
  }
262
+ if (index.apiScope) {
263
+ validator.assertEnum(index, "apiScope", Object.keys(types.ApiScope));
264
+ }
265
+ if (index.density) {
266
+ validator.assertEnum(index, "density", Object.keys(types.Density));
267
+ }
268
+ if (index.multikey) {
269
+ validator.assertType("multikey", index.multikey, "boolean");
270
+ }
271
+ if (index.unique) {
272
+ validator.assertType("unique", index.unique, "boolean");
273
+ }
242
274
  });
243
275
  }
244
276
  async patchField(project, spec, databaseId = "(default)") {
@@ -246,6 +278,10 @@ class FirestoreApi {
246
278
  const indexes = spec.indexes.map((index) => {
247
279
  return {
248
280
  queryScope: index.queryScope,
281
+ apiScope: index.apiScope,
282
+ density: index.density,
283
+ multikey: index.multikey,
284
+ unique: index.unique,
249
285
  fields: [
250
286
  {
251
287
  fieldPath: spec.fieldPath,
@@ -282,6 +318,10 @@ class FirestoreApi {
282
318
  return this.apiClient.post(url, {
283
319
  fields: index.fields,
284
320
  queryScope: index.queryScope,
321
+ apiScope: index.apiScope,
322
+ density: index.density,
323
+ multikey: index.multikey,
324
+ unique: index.unique,
285
325
  });
286
326
  }
287
327
  deleteIndex(index) {
@@ -296,6 +336,18 @@ class FirestoreApi {
296
336
  if (index.queryScope !== spec.queryScope) {
297
337
  return false;
298
338
  }
339
+ if (index.apiScope !== spec.apiScope) {
340
+ return false;
341
+ }
342
+ if (index.density !== spec.density) {
343
+ return false;
344
+ }
345
+ if (index.multikey !== spec.multikey) {
346
+ return false;
347
+ }
348
+ if (index.unique !== spec.unique) {
349
+ return false;
350
+ }
299
351
  if (index.fields.length !== spec.fields.length) {
300
352
  return false;
301
353
  }
@@ -312,6 +364,9 @@ class FirestoreApi {
312
364
  if (iField.arrayConfig !== sField.arrayConfig) {
313
365
  return false;
314
366
  }
367
+ if (iField.vectorConfig !== sField.vectorConfig) {
368
+ return false;
369
+ }
315
370
  i++;
316
371
  }
317
372
  return true;
@@ -368,8 +423,19 @@ class FirestoreApi {
368
423
  const i = {
369
424
  collectionGroup: index.collectionGroup || index.collectionId,
370
425
  queryScope: index.queryScope || types.QueryScope.COLLECTION,
371
- fields: [],
372
426
  };
427
+ if (index.apiScope) {
428
+ i.apiScope = index.apiScope;
429
+ }
430
+ if (index.density) {
431
+ i.density = index.density;
432
+ }
433
+ if (index.multikey !== undefined) {
434
+ i.multikey = index.multikey;
435
+ }
436
+ if (index.unique !== undefined) {
437
+ i.unique = index.unique;
438
+ }
373
439
  if (index.fields) {
374
440
  i.fields = index.fields.map((field) => {
375
441
  const f = {
@@ -429,6 +495,7 @@ class FirestoreApi {
429
495
  const payload = {
430
496
  locationId: req.locationId,
431
497
  type: req.type,
498
+ databaseEdition: req.databaseEdition,
432
499
  deleteProtectionState: req.deleteProtectionState,
433
500
  pointInTimeRecoveryEnablement: req.pointInTimeRecoveryEnablement,
434
501
  cmekConfig: req.cmekConfig,
@@ -26,7 +26,7 @@ exports.assertHasOneOf = assertHasOneOf;
26
26
  function assertEnum(obj, prop, valid) {
27
27
  const objString = clc.cyan(JSON.stringify(obj));
28
28
  if (valid.indexOf(obj[prop]) < 0) {
29
- throw new error_1.FirebaseError(`Field "${prop}" must be one of ${valid.join(", ")}: ${objString}`);
29
+ throw new error_1.FirebaseError(`Field "${prop}" must be one of ${valid.join(", ")}: ${objString}`);
30
30
  }
31
31
  }
32
32
  exports.assertEnum = assertEnum;
@@ -2,15 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.logFunctionsConfigDeprecationWarning = void 0;
4
4
  const utils_1 = require("../utils");
5
- const FUNCTIONS_CONFIG_DEPRECATION_MESSAGE = `DEPRECATION NOTICE: Action required to deploy after Dec 31, 2025
5
+ const FUNCTIONS_CONFIG_DEPRECATION_MESSAGE = `DEPRECATION NOTICE: Action required to deploy after March 2026
6
6
 
7
7
  functions.config() API is deprecated.
8
- Cloud Runtime Configuration API, the Google Cloud service used to store function configuration data, will be shut down on December 31, 2025. As a result, you must migrate away from using functions.config() to continue deploying your functions after December 31, 2025.
8
+ Cloud Runtime Configuration API, the Google Cloud service used to store function configuration data, will be shut down in March 2026. As a result, you must migrate away from using functions.config() to continue deploying your functions after March 2026.
9
9
 
10
10
  What this means for you:
11
11
 
12
- - The Firebase CLI commands for managing this configuration (functions:config:set, get, unset, clone, and export) are deprecated. These commands no longer work after December 31, 2025.
13
- - firebase deploy command will fail for functions that use the legacy functions.config() API after December 31, 2025.
12
+ - The Firebase CLI commands for managing this configuration (functions:config:set, get, unset, clone, and export) are deprecated. These commands will no longer work after March 2026.
13
+ - firebase deploy command will fail for functions that use the legacy functions.config() API after March 2026.
14
14
 
15
15
  Existing deployments will continue to work with their current configuration.
16
16
 
@@ -103,7 +103,7 @@ exports.executeSqlCmdsAsIamUser = executeSqlCmdsAsIamUser;
103
103
  async function executeSqlCmdsAsSuperUser(options, instanceId, databaseId, cmds, silent = false, transaction = false) {
104
104
  const projectId = (0, projectUtils_1.needProjectId)(options);
105
105
  const superuser = "firebasesuperuser";
106
- const temporaryPassword = utils.generateId(20);
106
+ const temporaryPassword = utils.generatePassword(20);
107
107
  await cloudSqlAdminClient.createUser(projectId, instanceId, "BUILT_IN", superuser, temporaryPassword);
108
108
  return await execute([`SET ROLE = '${superuser}'`, ...cmds], {
109
109
  projectId,
@@ -75,8 +75,7 @@ async function askQuestions(setup, config) {
75
75
  const unusedFrameworks = fileUtils_1.SUPPORTED_FRAMEWORKS.filter((framework) => { var _a; return !((_a = newConnectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk[framework]); });
76
76
  if (unusedFrameworks.length > 0) {
77
77
  const additionalFrameworks = await (0, prompt_1.checkbox)({
78
- message: "Which frameworks would you like to generate SDKs for? " +
79
- "Press Space to select features, then Enter to confirm your choices.",
78
+ message: "Which frameworks would you like to generate SDKs for in addition to the TypeScript SDK? Press Enter to skip.\n",
80
79
  choices: fileUtils_1.SUPPORTED_FRAMEWORKS.map((frameworkStr) => {
81
80
  var _a, _b;
82
81
  return ({
package/lib/mcp/index.js CHANGED
@@ -7,6 +7,7 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
7
  const util_1 = require("./util");
8
8
  const types_1 = require("./types");
9
9
  const index_1 = require("./tools/index");
10
+ const index_2 = require("./prompts/index");
10
11
  const configstore_1 = require("../configstore");
11
12
  const command_1 = require("../command");
12
13
  const requireAuth_1 = require("../requireAuth");
@@ -22,7 +23,7 @@ const api = require("../api");
22
23
  const logging_transport_1 = require("./logging-transport");
23
24
  const env_1 = require("../env");
24
25
  const timeout_1 = require("../timeout");
25
- const SERVER_VERSION = "0.2.0";
26
+ const SERVER_VERSION = "0.3.0";
26
27
  const cmd = new command_1.Command("experimental:mcp");
27
28
  const orderedLogLevels = [
28
29
  "debug",
@@ -56,9 +57,15 @@ class FirebaseMcpServer {
56
57
  this.activeFeatures = options.activeFeatures;
57
58
  this.startupRoot = options.projectRoot || process.env.PROJECT_ROOT;
58
59
  this.server = new index_js_1.Server({ name: "firebase", version: SERVER_VERSION });
59
- this.server.registerCapabilities({ tools: { listChanged: true }, logging: {} });
60
+ this.server.registerCapabilities({
61
+ tools: { listChanged: true },
62
+ logging: {},
63
+ prompts: { listChanged: true },
64
+ });
60
65
  this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, this.mcpListTools.bind(this));
61
66
  this.server.setRequestHandler(types_js_1.CallToolRequestSchema, this.mcpCallTool.bind(this));
67
+ this.server.setRequestHandler(types_js_1.ListPromptsRequestSchema, this.mcpListPrompts.bind(this));
68
+ this.server.setRequestHandler(types_js_1.GetPromptRequestSchema, this.mcpGetPrompt.bind(this));
62
69
  this.server.oninitialized = async () => {
63
70
  var _a, _b;
64
71
  const clientInfo = this.server.getClientVersion();
@@ -159,11 +166,19 @@ class FirebaseMcpServer {
159
166
  getTool(name) {
160
167
  return this.availableTools.find((t) => t.mcp.name === name) || null;
161
168
  }
169
+ get availablePrompts() {
170
+ var _a;
171
+ return (0, index_2.availablePrompts)(((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures);
172
+ }
173
+ getPrompt(name) {
174
+ return this.availablePrompts.find((p) => p.mcp.name === name) || null;
175
+ }
162
176
  setProjectRoot(newRoot) {
163
177
  this.updateStoredClientConfig({ projectRoot: newRoot });
164
178
  this.cachedProjectRoot = newRoot || undefined;
165
179
  this.detectedFeatures = undefined;
166
180
  void this.server.sendToolListChanged();
181
+ void this.server.sendPromptListChanged();
167
182
  }
168
183
  async resolveOptions() {
169
184
  const options = { cwd: this.cachedProjectRoot, isMCP: true };
@@ -258,6 +273,64 @@ class FirebaseMcpServer {
258
273
  return (0, util_1.mcpError)(err);
259
274
  }
260
275
  }
276
+ async mcpListPrompts() {
277
+ await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]);
278
+ const hasActiveProject = !!(await this.getProjectId());
279
+ await this.trackGA4("mcp_list_prompts");
280
+ const skipAutoAuthForStudio = (0, env_1.isFirebaseStudio)();
281
+ return {
282
+ prompts: this.availablePrompts.map((p) => ({
283
+ name: p.mcp.name,
284
+ description: p.mcp.description,
285
+ annotations: p.mcp.annotations,
286
+ arguments: p.mcp.arguments,
287
+ })),
288
+ _meta: {
289
+ projectRoot: this.cachedProjectRoot,
290
+ projectDetected: hasActiveProject,
291
+ authenticatedUser: await this.getAuthenticatedUser(skipAutoAuthForStudio),
292
+ activeFeatures: this.activeFeatures,
293
+ detectedFeatures: this.detectedFeatures,
294
+ },
295
+ };
296
+ }
297
+ async mcpGetPrompt(req) {
298
+ await this.detectProjectRoot();
299
+ const promptName = req.params.name;
300
+ const promptArgs = req.params.arguments || {};
301
+ const prompt = this.getPrompt(promptName);
302
+ if (!prompt) {
303
+ throw new Error(`Prompt '${promptName}' could not be found.`);
304
+ }
305
+ let projectId = await this.getProjectId();
306
+ projectId = projectId || "";
307
+ const skipAutoAuthForStudio = (0, env_1.isFirebaseStudio)();
308
+ const accountEmail = await this.getAuthenticatedUser(skipAutoAuthForStudio);
309
+ const options = { projectDir: this.cachedProjectRoot, cwd: this.cachedProjectRoot };
310
+ const promptsCtx = {
311
+ projectId: projectId,
312
+ host: this,
313
+ config: config_1.Config.load(options, true) || new config_1.Config({}, options),
314
+ rc: (0, rc_1.loadRC)(options),
315
+ accountEmail,
316
+ };
317
+ try {
318
+ const messages = await prompt.fn(promptArgs, promptsCtx);
319
+ await this.trackGA4("mcp_get_prompt", {
320
+ tool_name: promptName,
321
+ });
322
+ return {
323
+ messages,
324
+ };
325
+ }
326
+ catch (err) {
327
+ await this.trackGA4("mcp_get_prompt", {
328
+ tool_name: promptName,
329
+ error: 1,
330
+ });
331
+ throw err;
332
+ }
333
+ }
261
334
  async start() {
262
335
  const transport = process.env.FIREBASE_MCP_DEBUG_LOG
263
336
  ? new logging_transport_1.LoggingStdioServerTransport(process.env.FIREBASE_MCP_DEBUG_LOG)
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prompt = void 0;
4
+ function prompt(options, fn) {
5
+ return {
6
+ mcp: options,
7
+ fn,
8
+ };
9
+ }
10
+ exports.prompt = prompt;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deploy = void 0;
4
+ const prompt_1 = require("../../prompt");
5
+ exports.deploy = (0, prompt_1.prompt)({
6
+ name: "deploy",
7
+ omitPrefix: true,
8
+ description: "Use this command to deploy resources to Firebase.",
9
+ arguments: [
10
+ {
11
+ name: "prompt",
12
+ description: "any specific instructions you wish to provide about deploying",
13
+ required: false,
14
+ },
15
+ ],
16
+ annotations: {
17
+ title: "Deploy to Firebase",
18
+ },
19
+ }, async ({ prompt }, { config, projectId, accountEmail }) => {
20
+ return [
21
+ {
22
+ role: "user",
23
+ content: {
24
+ type: "text",
25
+ text: `
26
+ Your goal is to deploy resources from the current project to Firebase.
27
+
28
+ Active user: ${accountEmail || "<NONE>"}
29
+ Active project: ${projectId || "<NONE>"}
30
+
31
+ Contents of \`firebase.json\` config file:
32
+
33
+ \`\`\`json
34
+ ${config.readProjectFile("firebase.json", { fallback: "<FILE DOES NOT EXIST>" })}
35
+ \`\`\`
36
+
37
+ ## User Instructions
38
+
39
+ ${prompt || "<the user didn't supply specific instructions>"}
40
+
41
+ ## Steps
42
+
43
+ Follow the steps below taking note of any user instructions provided above.
44
+
45
+ 1. If there is no active user, prompt the user to run \`firebase login\` in an interactive terminal before continuing.
46
+ 2. If there is no \`firebase.json\` file and the current workspace is a static web application, manually create a \`firebase.json\` with \`"hosting"\` configuration based on the current directory's web app configuration. Add a \`{"hosting": {"predeploy": "<build_script>"}}\` config to build before deploying.
47
+ 3. If there is no active project, ask the user if they want to use an existing project or create a new one.
48
+ 3a. If create a new one, use the \`firebase_create_project\` tool.
49
+ 3b. If they want to use an existing one, ask them for a project id (the \`firebase_list_projects\` tool may be helpful).
50
+ 4. Only after making sure Firebase has been initialized, run the \`firebase deploy\` shell command to perform the deploy. This may take a few minutes.
51
+ 5. If the deploy has errors, attempt to fix them and ask the user clarifying questions as needed.
52
+ 6. If the deploy needs \`--force\` to run successfully, ALWAYS prompt the user before running \`firebase deploy --force\`.
53
+ 7. If only one specific feature is failing, use command \`firebase deploy --only <feature>\` as you debug.
54
+ `.trim(),
55
+ },
56
+ },
57
+ ];
58
+ });
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.corePrompts = void 0;
4
+ const deploy_1 = require("./deploy");
5
+ exports.corePrompts = [deploy_1.deploy];
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.availablePrompts = void 0;
4
+ const core_1 = require("./core");
5
+ const prompts = {
6
+ core: core_1.corePrompts,
7
+ firestore: [],
8
+ storage: [],
9
+ dataconnect: [],
10
+ auth: [],
11
+ messaging: [],
12
+ remoteconfig: [],
13
+ crashlytics: [],
14
+ apphosting: [],
15
+ database: [],
16
+ };
17
+ function namespacePrompts(promptsToNamespace, feature) {
18
+ return promptsToNamespace.map((p) => {
19
+ const newPrompt = Object.assign({}, p);
20
+ newPrompt.mcp = Object.assign({}, p.mcp);
21
+ if (newPrompt.mcp.omitPrefix) {
22
+ }
23
+ else if (feature === "core") {
24
+ newPrompt.mcp.name = `firebase:${p.mcp.name}`;
25
+ }
26
+ else {
27
+ newPrompt.mcp.name = `firebase:${feature}:${p.mcp.name}`;
28
+ }
29
+ newPrompt.mcp._meta = Object.assign(Object.assign({}, p.mcp._meta), { feature });
30
+ return newPrompt;
31
+ });
32
+ }
33
+ function availablePrompts(features) {
34
+ const allPrompts = namespacePrompts(prompts["core"], "core");
35
+ if (!features) {
36
+ features = Object.keys(prompts).filter((f) => f !== "core");
37
+ }
38
+ for (const feature of features) {
39
+ if (prompts[feature] && feature !== "core") {
40
+ allPrompts.push(...namespacePrompts(prompts[feature], feature));
41
+ }
42
+ }
43
+ return allPrompts;
44
+ }
45
+ exports.availablePrompts = availablePrompts;
@@ -37,5 +37,15 @@ exports.get_sdk_config = (0, tool_1.tool)({
37
37
  if (!appId)
38
38
  return (0, util_1.mcpError)(`Could not find an app for platform '${inputPlatform}' in project '${projectId}'`);
39
39
  const sdkConfig = await (0, apps_1.getAppConfig)(appId, platform);
40
+ if ("configFilename" in sdkConfig) {
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text",
45
+ text: `SDK config content for \`${sdkConfig.configFilename}\`:\n\n\`\`\`\n${Buffer.from(sdkConfig.configFileContents, "base64").toString("utf-8")}\n\`\`\``,
46
+ },
47
+ ],
48
+ };
49
+ }
40
50
  return (0, util_1.toContent)(sdkConfig, { format: "json" });
41
51
  });
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.get_data = void 0;
4
+ const zod_1 = require("zod");
5
+ const tool_1 = require("../../tool");
6
+ const util_1 = require("../../util");
7
+ const url = require("node:url");
8
+ const apiv2_1 = require("../../../apiv2");
9
+ const consumers_1 = require("node:stream/consumers");
10
+ const node_path_1 = require("node:path");
11
+ exports.get_data = (0, tool_1.tool)({
12
+ name: "get_data",
13
+ description: "Returns RTDB data from the specified location",
14
+ inputSchema: zod_1.z.object({
15
+ databaseUrl: zod_1.z
16
+ .string()
17
+ .optional()
18
+ .describe("connect to the database at url. If omitted, use default database instance <project>-default-rtdb.firebasedatabase.app. Can point to emulator URL (e.g. localhost:6000/<instance>)"),
19
+ path: zod_1.z.string().describe("The path to the data to read. (ex: /my/cool/path)"),
20
+ }),
21
+ annotations: {
22
+ title: "Get Realtime Database data",
23
+ readOnlyHint: true,
24
+ },
25
+ _meta: {
26
+ requiresAuth: false,
27
+ requiresProject: false,
28
+ },
29
+ }, async ({ path: getPath, databaseUrl }, { projectId, host }) => {
30
+ if (!getPath.startsWith("/")) {
31
+ return (0, util_1.mcpError)(`paths must start with '/' (you passed ''${getPath}')`);
32
+ }
33
+ const dbUrl = new url.URL(databaseUrl
34
+ ? `${databaseUrl}/${getPath}.json`
35
+ : node_path_1.default.join(`https://${projectId}-default-rtdb.us-central1.firebasedatabase.app`, `${getPath}.json`));
36
+ const client = new apiv2_1.Client({
37
+ urlPrefix: dbUrl.origin,
38
+ auth: true,
39
+ });
40
+ host.logger.debug(`sending read request to path '${getPath}' for url '${dbUrl.toString()}'`);
41
+ const res = await client.request({
42
+ method: "GET",
43
+ path: dbUrl.pathname,
44
+ responseType: "stream",
45
+ resolveOnHTTPError: true,
46
+ });
47
+ const content = await (0, consumers_1.text)(res.body);
48
+ return (0, util_1.toContent)(content);
49
+ });
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.get_rules = void 0;
4
+ const zod_1 = require("zod");
5
+ const apiv2_1 = require("../../../apiv2");
6
+ const tool_1 = require("../../tool");
7
+ const util_1 = require("../../util");
8
+ exports.get_rules = (0, tool_1.tool)({
9
+ name: "get_rules",
10
+ description: "Get an RTDB database's rules",
11
+ inputSchema: zod_1.z.object({
12
+ databaseUrl: zod_1.z
13
+ .string()
14
+ .optional()
15
+ .describe("connect to the database at url. If omitted, use default database instance <project>-default-rtdb.firebaseio.com. Can point to emulator URL (e.g. localhost:6000/<instance>)"),
16
+ }),
17
+ annotations: {
18
+ title: "Get Realtime Database rules",
19
+ readOnlyHint: true,
20
+ },
21
+ _meta: {
22
+ requiresAuth: false,
23
+ requiresProject: false,
24
+ },
25
+ }, async ({ databaseUrl }, { projectId }) => {
26
+ const dbUrl = databaseUrl !== null && databaseUrl !== void 0 ? databaseUrl : `https://${projectId}-default-rtdb.us-central1.firebasedatabase.app`;
27
+ const client = new apiv2_1.Client({ urlPrefix: dbUrl });
28
+ const response = await client.request({
29
+ method: "GET",
30
+ path: "/.settings/rules.json",
31
+ responseType: "stream",
32
+ resolveOnHTTPError: true,
33
+ });
34
+ if (response.status !== 200) {
35
+ return (0, util_1.mcpError)(`Failed to fetch current rules. Code: ${response.status}`);
36
+ }
37
+ const rules = await response.response.text();
38
+ return (0, util_1.toContent)(rules);
39
+ });
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.realtimeDatabaseTools = void 0;
4
+ const get_rules_1 = require("./get_rules");
5
+ const get_data_1 = require("./get_data");
6
+ const set_data_1 = require("./set_data");
7
+ const validate_rules_1 = require("./validate_rules");
8
+ exports.realtimeDatabaseTools = [get_data_1.get_data, set_data_1.set_data, get_rules_1.get_rules, validate_rules_1.validate_rules];
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.set_data = void 0;
4
+ const zod_1 = require("zod");
5
+ const tool_1 = require("../../tool");
6
+ const util_1 = require("../../util");
7
+ const url = require("node:url");
8
+ const utils_1 = require("../../../utils");
9
+ const apiv2_1 = require("../../../apiv2");
10
+ const error_1 = require("../../../error");
11
+ const node_path_1 = require("node:path");
12
+ exports.set_data = (0, tool_1.tool)({
13
+ name: "set_data",
14
+ description: "Writes RTDB data to the specified location",
15
+ inputSchema: zod_1.z.object({
16
+ databaseUrl: zod_1.z
17
+ .string()
18
+ .optional()
19
+ .describe("connect to the database at url. If omitted, use default database instance <project>-default-rtdb.us-central1.firebasedatabase.app. Can point to emulator URL (e.g. localhost:6000/<instance>)"),
20
+ path: zod_1.z.string().describe("The path to the data to read. (ex: /my/cool/path)"),
21
+ data: zod_1.z.string().describe('The JSON to write. (ex: {"alphabet": ["a", "b", "c"]})'),
22
+ }),
23
+ annotations: {
24
+ title: "Set Realtime Database data",
25
+ readOnlyHint: false,
26
+ idempotentHint: true,
27
+ },
28
+ _meta: {
29
+ requiresAuth: false,
30
+ requiresProject: false,
31
+ },
32
+ }, async ({ path: setPath, databaseUrl, data }, { projectId, host }) => {
33
+ if (!setPath.startsWith("/")) {
34
+ return (0, util_1.mcpError)(`paths must start with '/' (you passed ''${setPath}')`);
35
+ }
36
+ const dbUrl = new url.URL(databaseUrl
37
+ ? `${databaseUrl}/${setPath}.json`
38
+ : node_path_1.default.join(`https://${projectId}-default-rtdb.us-central1.firebasedatabase.app`, `${setPath}.json`));
39
+ const client = new apiv2_1.Client({
40
+ urlPrefix: dbUrl.origin,
41
+ auth: true,
42
+ });
43
+ const inStream = (0, utils_1.stringToStream)(data);
44
+ host.logger.debug(`sending write request to path '${setPath}' for url '${dbUrl.toString()}'`);
45
+ try {
46
+ await client.request({
47
+ method: "PUT",
48
+ path: dbUrl.pathname,
49
+ body: inStream,
50
+ });
51
+ }
52
+ catch (err) {
53
+ host.logger.debug((0, error_1.getErrMsg)(err));
54
+ return (0, util_1.mcpError)(`Unexpected error while setting data: ${(0, error_1.getErrMsg)(err)}`);
55
+ }
56
+ return (0, util_1.toContent)("write successful!");
57
+ });
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validate_rules = void 0;
4
+ const zod_1 = require("zod");
5
+ const apiv2_1 = require("../../../apiv2");
6
+ const tool_1 = require("../../tool");
7
+ const util_1 = require("../../util");
8
+ const rtdb_1 = require("../../../rtdb");
9
+ const error_1 = require("../../../error");
10
+ exports.validate_rules = (0, tool_1.tool)({
11
+ name: "validate_rules",
12
+ description: "Validates an RTDB database's rules",
13
+ inputSchema: zod_1.z.object({
14
+ databaseUrl: zod_1.z
15
+ .string()
16
+ .optional()
17
+ .describe("connect to the database at url. If omitted, use default database instance <project>-default-rtdb.firebaseio.com. Can point to emulator URL (e.g. localhost:6000/<instance>)"),
18
+ rules: zod_1.z
19
+ .string()
20
+ .describe('The rules object, as a string (ex: {".read": false, ".write": false})'),
21
+ }),
22
+ annotations: {
23
+ title: "Validate Realtime Database rules",
24
+ idempotentHint: true,
25
+ },
26
+ _meta: {
27
+ requiresAuth: true,
28
+ requiresProject: false,
29
+ },
30
+ }, async ({ databaseUrl, rules }, { projectId, host }) => {
31
+ const dbUrl = databaseUrl !== null && databaseUrl !== void 0 ? databaseUrl : `https://${projectId}-default-rtdb.us-central1.firebasedatabase.app`;
32
+ const client = new apiv2_1.Client({ urlPrefix: dbUrl });
33
+ try {
34
+ await (0, rtdb_1.updateRulesWithClient)(client, rules, { dryRun: true });
35
+ }
36
+ catch (e) {
37
+ host.logger.debug(`failed to update rules at url ${dbUrl}`);
38
+ return (0, util_1.mcpError)((0, error_1.getErrMsg)(e));
39
+ }
40
+ return (0, util_1.toContent)("the inputted rules are valid!");
41
+ });
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validate_rules = void 0;
4
+ const zod_1 = require("zod");
5
+ const apiv2_1 = require("../../../apiv2");
6
+ const tool_1 = require("../../tool");
7
+ const util_1 = require("../../util");
8
+ const rtdb_1 = require("../../../rtdb");
9
+ const error_1 = require("../../../error");
10
+ exports.validate_rules = (0, tool_1.tool)({
11
+ name: "validate_rules",
12
+ description: "Validates an RTDB database's rules",
13
+ inputSchema: zod_1.z.object({
14
+ databaseUrl: zod_1.z
15
+ .string()
16
+ .optional()
17
+ .describe("connect to the database at url. If omitted, use default database instance <project>-default-rtdb.firebaseio.com. Can point to emulator URL (e.g. localhost:6000/<instance>)"),
18
+ rules: zod_1.z
19
+ .string()
20
+ .describe('The rules object, as a string (ex: {"rules": {".read": false, ".write": false}})'),
21
+ }),
22
+ annotations: {
23
+ title: "Validate Realtime Database rules",
24
+ idempotentHint: true,
25
+ },
26
+ _meta: {
27
+ requiresAuth: true,
28
+ requiresProject: false,
29
+ },
30
+ }, async ({ databaseUrl, rules }, { projectId, host }) => {
31
+ const dbUrl = databaseUrl !== null && databaseUrl !== void 0 ? databaseUrl : `https://${projectId}-default-rtdb.us-central1.firebasedatabase.app`;
32
+ const client = new apiv2_1.Client({ urlPrefix: dbUrl });
33
+ try {
34
+ await (0, rtdb_1.updateRulesWithClient)(client, rules, { dryRun: true });
35
+ }
36
+ catch (e) {
37
+ host.logger.debug(`failed to validate rules at url ${dbUrl}`);
38
+ return (0, util_1.mcpError)((0, error_1.getErrMsg)(e));
39
+ }
40
+ return (0, util_1.toContent)("the inputted rules are valid!");
41
+ });
@@ -10,8 +10,9 @@ const index_6 = require("./messaging/index");
10
10
  const index_7 = require("./remoteconfig/index");
11
11
  const index_8 = require("./crashlytics/index");
12
12
  const index_9 = require("./apphosting/index");
13
+ const index_10 = require("./database/index");
13
14
  function availableTools(activeFeatures) {
14
- const toolDefs = addFeaturePrefix("firebase", index_4.coreTools);
15
+ const toolDefs = [];
15
16
  if (!(activeFeatures === null || activeFeatures === void 0 ? void 0 : activeFeatures.length)) {
16
17
  activeFeatures = Object.keys(tools);
17
18
  }
@@ -22,6 +23,7 @@ function availableTools(activeFeatures) {
22
23
  }
23
24
  exports.availableTools = availableTools;
24
25
  const tools = {
26
+ core: addFeaturePrefix("firebase", index_4.coreTools),
25
27
  firestore: addFeaturePrefix("firestore", index_3.firestoreTools),
26
28
  auth: addFeaturePrefix("auth", index_1.authTools),
27
29
  dataconnect: addFeaturePrefix("dataconnect", index_2.dataconnectTools),
@@ -30,6 +32,7 @@ const tools = {
30
32
  remoteconfig: addFeaturePrefix("remoteconfig", index_7.remoteConfigTools),
31
33
  crashlytics: addFeaturePrefix("crashlytics", index_8.crashlyticsTools),
32
34
  apphosting: addFeaturePrefix("apphosting", index_9.appHostingTools),
35
+ database: addFeaturePrefix("database", index_10.realtimeDatabaseTools),
33
36
  };
34
37
  function addFeaturePrefix(feature, tools) {
35
38
  return tools.map((tool) => (Object.assign(Object.assign({}, tool), { mcp: Object.assign(Object.assign({}, tool.mcp), { name: `${feature}_${tool.mcp.name}`, _meta: Object.assign(Object.assign({}, tool.mcp._meta), { feature }) }) })));
@@ -21,7 +21,7 @@ function getRulesTool(productName, releaseName) {
21
21
  }, async (_, { projectId }) => {
22
22
  const rulesetName = await (0, rules_1.getLatestRulesetName)(projectId, releaseName);
23
23
  if (!rulesetName)
24
- return (0, util_1.mcpError)(`No active Firestore rules were found in project '${projectId}'`);
24
+ return (0, util_1.mcpError)(`No active ${productName} rules were found in project '${projectId}'`);
25
25
  const rules = await (0, rules_1.getRulesetContent)(rulesetName);
26
26
  return (0, util_1.toContent)(rules[0].content);
27
27
  });
package/lib/mcp/types.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SERVER_FEATURES = void 0;
4
4
  exports.SERVER_FEATURES = [
5
+ "core",
5
6
  "firestore",
6
7
  "storage",
7
8
  "dataconnect",
@@ -10,4 +11,5 @@ exports.SERVER_FEATURES = [
10
11
  "remoteconfig",
11
12
  "crashlytics",
12
13
  "apphosting",
14
+ "database",
13
15
  ];
package/lib/mcp/util.js CHANGED
@@ -56,6 +56,7 @@ function commandExistsSync(command) {
56
56
  }
57
57
  exports.commandExistsSync = commandExistsSync;
58
58
  const SERVER_FEATURE_APIS = {
59
+ core: "",
59
60
  firestore: (0, api_1.firestoreOrigin)(),
60
61
  storage: (0, api_1.storageOrigin)(),
61
62
  dataconnect: (0, api_1.dataconnectOrigin)(),
@@ -64,6 +65,7 @@ const SERVER_FEATURE_APIS = {
64
65
  remoteconfig: (0, api_1.remoteConfigApiOrigin)(),
65
66
  crashlytics: (0, api_1.crashlyticsApiOrigin)(),
66
67
  apphosting: (0, api_1.apphostingOrigin)(),
68
+ database: (0, api_1.realtimeOrigin)(),
67
69
  };
68
70
  async function checkFeatureActive(feature, projectId, options) {
69
71
  var _a;
package/lib/rtdb.js CHANGED
@@ -1,16 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.updateRules = void 0;
3
+ exports.updateRulesWithClient = exports.updateRules = void 0;
4
4
  const apiv2_1 = require("./apiv2");
5
5
  const database_1 = require("./management/database");
6
6
  const error_1 = require("./error");
7
7
  const api_1 = require("./database/api");
8
8
  const utils = require("./utils");
9
9
  async function updateRules(projectId, instance, src, options = {}) {
10
- const queryParams = {};
11
- if (options.dryRun) {
12
- queryParams.dryRun = "true";
13
- }
14
10
  const downstreamOptions = { instance: instance, project: projectId };
15
11
  await (0, database_1.populateInstanceDetails)(downstreamOptions);
16
12
  if (!downstreamOptions.instanceDetails) {
@@ -18,6 +14,14 @@ async function updateRules(projectId, instance, src, options = {}) {
18
14
  }
19
15
  const origin = utils.getDatabaseUrl((0, api_1.realtimeOriginOrCustomUrl)(downstreamOptions.instanceDetails.databaseUrl), instance, "");
20
16
  const client = new apiv2_1.Client({ urlPrefix: origin });
17
+ return updateRulesWithClient(client, options);
18
+ }
19
+ exports.updateRules = updateRules;
20
+ async function updateRulesWithClient(client, src, options = {}) {
21
+ const queryParams = {};
22
+ if (options.dryRun) {
23
+ queryParams.dryRun = "true";
24
+ }
21
25
  const response = await client.request({
22
26
  method: "PUT",
23
27
  path: ".settings/rules.json",
@@ -32,4 +36,4 @@ async function updateRules(projectId, instance, src, options = {}) {
32
36
  throw new error_1.FirebaseError("Unexpected error while deploying database rules.", { exit: 2 });
33
37
  }
34
38
  }
35
- exports.updateRules = updateRules;
39
+ exports.updateRulesWithClient = updateRulesWithClient;
package/lib/utils.js CHANGED
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.openInBrowserPopup = exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.tryParse = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarningToStderr = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
4
- exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = void 0;
4
+ exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = void 0;
5
5
  const fs = require("fs-extra");
6
6
  const tty = require("tty");
7
7
  const path = require("node:path");
8
8
  const yaml = require("yaml");
9
+ const crypto = require("node:crypto");
9
10
  const _ = require("lodash");
10
11
  const url = require("url");
11
12
  const http = require("http");
@@ -546,6 +547,28 @@ function generateId(n = 6) {
546
547
  return id;
547
548
  }
548
549
  exports.generateId = generateId;
550
+ function generatePassword(n = 20) {
551
+ const lower = "abcdefghijklmnopqrstuvwxyz";
552
+ const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
553
+ const numbers = "0123456789";
554
+ const special = "!@#$%^&*()_+~`|}{[]:;?><,./-=";
555
+ const all = lower + upper + numbers + special;
556
+ let pw = "";
557
+ pw += lower[crypto.randomInt(lower.length)];
558
+ pw += upper[crypto.randomInt(upper.length)];
559
+ pw += numbers[crypto.randomInt(numbers.length)];
560
+ pw += special[crypto.randomInt(special.length)];
561
+ for (let i = 4; i < n; i++) {
562
+ pw += all[crypto.randomInt(all.length)];
563
+ }
564
+ const pwArray = pw.split("");
565
+ for (let i = pwArray.length - 1; i > 0; i--) {
566
+ const j = crypto.randomInt(i);
567
+ [pwArray[i], pwArray[j]] = [pwArray[j], pwArray[i]];
568
+ }
569
+ return pwArray.join("");
570
+ }
571
+ exports.generatePassword = generatePassword;
549
572
  function readSecretValue(prompt, dataFile) {
550
573
  if ((!dataFile || dataFile === "-") && tty.isatty(0)) {
551
574
  return (0, prompt_1.password)({ message: prompt });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "14.12.0",
3
+ "version": "14.12.1",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -88,6 +88,9 @@
88
88
  "database": {
89
89
  "type": "string"
90
90
  },
91
+ "edition": {
92
+ "type": "string"
93
+ },
91
94
  "indexes": {
92
95
  "type": "string"
93
96
  },
@@ -1,5 +1,5 @@
1
1
  {
2
- // Example:
2
+ // Example (Standard Edition):
3
3
  //
4
4
  // "indexes": [
5
5
  // {
@@ -21,6 +21,31 @@
21
21
  // },
22
22
  // ]
23
23
  // ]
24
+ //
25
+ // Example (Enterprise Edition):
26
+ //
27
+ // "indexes": [
28
+ // {
29
+ // "collectionGroup": "reviews",
30
+ // "queryScope": "COLLECTION_GROUP",
31
+ // "apiScope": "MONGODB_COMPATIBLE_API",
32
+ // "density": "DENSE",
33
+ // "multikey": false,
34
+ // "fields": [
35
+ // { "fieldPath": "baz", "mode": "ASCENDING" }
36
+ // ]
37
+ // },
38
+ // {
39
+ // "collectionGroup": "items",
40
+ // "queryScope": "COLLECTION_GROUP",
41
+ // "apiScope": "MONGODB_COMPATIBLE_API",
42
+ // "density": "SPARSE_ANY",
43
+ // "multikey": true,
44
+ // "fields": [
45
+ // { "fieldPath": "baz", "mode": "ASCENDING" }
46
+ // ]
47
+ // },
48
+ // ]
24
49
  "indexes": [],
25
50
  "fieldOverrides": []
26
51
  }