firebase-tools 14.11.0 → 14.11.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.
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureProjectConfigured = void 0;
4
+ const resourceManager_1 = require("../gcp/resourceManager");
5
+ const ensureApiEnabled_1 = require("../ensureApiEnabled");
6
+ const api_1 = require("../api");
7
+ const utils_1 = require("../utils");
8
+ const error_1 = require("../error");
9
+ const iam = require("../gcp/iam");
10
+ const prompt_1 = require("../prompt");
11
+ const TEST_RUNNER_ROLE = "roles/firebaseapptesting.testRunner";
12
+ const TEST_RUNNER_SERVICE_ACCOUNT_NAME = "firebaseapptesting-test-runner";
13
+ async function ensureProjectConfigured(projectId) {
14
+ await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.appTestingOrigin)(), "storage", false);
15
+ await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.appTestingOrigin)(), "run", false);
16
+ await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.appTestingOrigin)(), "artifactregistry", false);
17
+ const serviceAccount = runnerServiceAccount(projectId);
18
+ const serviceAccountExistsAndIsRunner = await (0, resourceManager_1.serviceAccountHasRoles)(projectId, serviceAccount, [TEST_RUNNER_ROLE], true);
19
+ if (!serviceAccountExistsAndIsRunner) {
20
+ const grant = await (0, prompt_1.confirm)(`Firebase App Testing runs tests in Cloud Run using a service account, provision an account, "${serviceAccount}", with the role "${TEST_RUNNER_ROLE}"?`);
21
+ if (!grant) {
22
+ (0, utils_1.logBullet)("You, or your project administrator, should run the following command to grant the required role:\n\n" +
23
+ `\tgcloud projects add-iam-policy-binding ${projectId} \\\n` +
24
+ `\t --member="serviceAccount:${serviceAccount}" \\\n` +
25
+ `\t --role="${TEST_RUNNER_ROLE}"\n`);
26
+ throw new error_1.FirebaseError(`Firebase App Testing requires a service account named "${serviceAccount}" with the "${TEST_RUNNER_ROLE}" role to execute tests using Cloud Run`);
27
+ }
28
+ await provisionServiceAccount(projectId, serviceAccount);
29
+ }
30
+ }
31
+ exports.ensureProjectConfigured = ensureProjectConfigured;
32
+ async function provisionServiceAccount(projectId, serviceAccount) {
33
+ try {
34
+ await iam.createServiceAccount(projectId, TEST_RUNNER_SERVICE_ACCOUNT_NAME, "Service Account used in Cloud Run, responsible for running tests", "Firebase App Testing Test Runner");
35
+ }
36
+ catch (err) {
37
+ if ((0, error_1.getErrStatus)(err) !== 409) {
38
+ throw err;
39
+ }
40
+ }
41
+ try {
42
+ await (0, resourceManager_1.addServiceAccountToRoles)(projectId, serviceAccount, [TEST_RUNNER_ROLE], true);
43
+ }
44
+ catch (err) {
45
+ if ((0, error_1.getErrStatus)(err) === 400) {
46
+ (0, utils_1.logWarning)(`Your App Testing runner service account, "${serviceAccount}", is still being provisioned in the background. If you encounter an error, please try again after a few moments.`);
47
+ }
48
+ else {
49
+ throw err;
50
+ }
51
+ }
52
+ }
53
+ function runnerServiceAccount(projectId) {
54
+ return `${TEST_RUNNER_SERVICE_ACCOUNT_NAME}@${projectId}.iam.gserviceaccount.com`;
55
+ }
@@ -10,6 +10,7 @@ const requirePermissions_1 = require("../requirePermissions");
10
10
  const functionsConfig = require("../functionsConfig");
11
11
  const functionsConfigClone_1 = require("../functionsConfigClone");
12
12
  const utils = require("../utils");
13
+ const deprecationWarnings_1 = require("../functions/deprecationWarnings");
13
14
  exports.command = new command_1.Command("functions:config:clone")
14
15
  .description("clone environment config from another project")
15
16
  .option("--from <projectId>", "the project from which to clone configuration")
@@ -50,4 +51,5 @@ exports.command = new command_1.Command("functions:config:clone")
50
51
  await (0, functionsConfigClone_1.functionsConfigClone)(options.from, projectId, only, except);
51
52
  utils.logSuccess(`Cloned functions config from ${clc.bold(options.from)} into ${clc.bold(projectId)}`);
52
53
  logger_1.logger.info(`\nPlease deploy your functions for the change to take effect by running ${clc.bold("firebase deploy --only functions")}\n`);
54
+ (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
53
55
  });
@@ -8,6 +8,7 @@ const logger_1 = require("../logger");
8
8
  const projectUtils_1 = require("../projectUtils");
9
9
  const requirePermissions_1 = require("../requirePermissions");
10
10
  const functionsConfig = require("../functionsConfig");
11
+ const deprecationWarnings_1 = require("../functions/deprecationWarnings");
11
12
  async function materialize(projectId, path) {
12
13
  if (path === undefined) {
13
14
  return functionsConfig.materializeAll(projectId);
@@ -31,5 +32,6 @@ exports.command = new command_1.Command("functions:config:get [path]")
31
32
  .action(async (path, options) => {
32
33
  const result = await materialize((0, projectUtils_1.needProjectId)(options), path);
33
34
  logger_1.logger.info(JSON.stringify(result, null, 2));
35
+ (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
34
36
  return result;
35
37
  });
@@ -9,6 +9,7 @@ const projectUtils_1 = require("../projectUtils");
9
9
  const requirePermissions_1 = require("../requirePermissions");
10
10
  const functionsConfig = require("../functionsConfig");
11
11
  const utils = require("../utils");
12
+ const deprecationWarnings_1 = require("../functions/deprecationWarnings");
12
13
  exports.command = new command_1.Command("functions:config:set [values...]")
13
14
  .description("set environment config with key=value syntax")
14
15
  .before(requirePermissions_1.requirePermissions, [
@@ -40,4 +41,5 @@ exports.command = new command_1.Command("functions:config:set [values...]")
40
41
  await Promise.all(promises);
41
42
  utils.logSuccess("Functions config updated.");
42
43
  logger_1.logger.info(`\nPlease deploy your functions for the change to take effect by running ${clc.bold("firebase deploy --only functions")}\n`);
44
+ (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
43
45
  });
@@ -10,6 +10,7 @@ const functionsConfig = require("../functionsConfig");
10
10
  const runtimeconfig = require("../gcp/runtimeconfig");
11
11
  const utils = require("../utils");
12
12
  const error_1 = require("../error");
13
+ const deprecationWarnings_1 = require("../functions/deprecationWarnings");
13
14
  exports.command = new command_1.Command("functions:config:unset [keys...]")
14
15
  .description("unset environment config at the specified path(s)")
15
16
  .before(requirePermissions_1.requirePermissions, [
@@ -39,4 +40,5 @@ exports.command = new command_1.Command("functions:config:unset [keys...]")
39
40
  }));
40
41
  utils.logSuccess("Environment updated.");
41
42
  logger_1.logger.info(`\nPlease deploy your functions for the change to take effect by running ${clc.bold("firebase deploy --only functions")}\n`);
43
+ (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
42
44
  });
@@ -102,7 +102,7 @@ if ((0, experiments_1.isEnabled)("genkit")) {
102
102
  if ((0, experiments_1.isEnabled)("apptesting")) {
103
103
  choices.push({
104
104
  value: "apptesting",
105
- name: "App Testing: create a smoke test",
105
+ name: "App Testing: create a smoke test, enable Cloud APIs (storage, run, & artifactregistry), and add a service account.",
106
106
  checked: false,
107
107
  });
108
108
  }
@@ -53,7 +53,9 @@ async function prepare(context, options, payload) {
53
53
  context.firebaseConfig = firebaseConfig;
54
54
  let runtimeConfig = { firebase: firebaseConfig };
55
55
  if (checkAPIsEnabled[1]) {
56
- runtimeConfig = Object.assign(Object.assign({}, runtimeConfig), (await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId)));
56
+ const config = await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId);
57
+ runtimeConfig = Object.assign(Object.assign({}, runtimeConfig), config);
58
+ context.hasRuntimeConfig = Object.keys(config).length > 0;
57
59
  }
58
60
  context.codebaseDeployEvents = {};
59
61
  const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
@@ -13,6 +13,7 @@ const hash_1 = require("./cache/hash");
13
13
  const functionsConfig = require("../../functionsConfig");
14
14
  const utils = require("../../utils");
15
15
  const fsAsync = require("../../fsAsync");
16
+ const deprecationWarnings_1 = require("../../functions/deprecationWarnings");
16
17
  const CONFIG_DEST_FILE = ".runtimeconfig.json";
17
18
  async function getFunctionsConfig(projectId) {
18
19
  var _a, _b;
@@ -73,6 +74,7 @@ async function packageSource(sourceDir, config, runtimeConfig) {
73
74
  name: CONFIG_DEST_FILE,
74
75
  mode: 420,
75
76
  });
77
+ (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
76
78
  }
77
79
  await pipeAsync(archive, fileStream);
78
80
  }
@@ -81,6 +81,7 @@ async function logAndTrackDeployStats(summary, context) {
81
81
  fn_deploy_num_successes: totalSuccesses,
82
82
  fn_deploy_num_canceled: totalAborts,
83
83
  fn_deploy_num_failures: totalErrors,
84
+ has_runtime_config: String(!!(context === null || context === void 0 ? void 0 : context.hasRuntimeConfig)),
84
85
  };
85
86
  reports.push((0, track_1.trackGA4)("function_deploy_group", fnDeployGroupEvent));
86
87
  const avgTime = totalTime / (totalSuccesses + totalErrors);
@@ -54,28 +54,28 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.10.0",
58
- "expectedSize": 29332320,
59
- "expectedChecksum": "8e1f4303580e91017d449a091f987caa",
60
- "expectedChecksumSHA256": "000356e1a93a8eb55ff6ba5e268b5dd4c0638636708e95874a74b859aa977cb3",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.10.0",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.0"
57
+ "version": "2.10.1",
58
+ "expectedSize": 29336416,
59
+ "expectedChecksum": "328ae0a354505430f1c162edf37da776",
60
+ "expectedChecksumSHA256": "ede5dc299045151f228b2adc346f47ad5d3ee10c3c9d7528df5fc0f0c6d70808",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.10.1",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.1"
63
63
  },
64
64
  "win32": {
65
- "version": "2.10.0",
66
- "expectedSize": 29824000,
67
- "expectedChecksum": "ea880276edee4908c4a6def6b1c7e2c1",
68
- "expectedChecksumSHA256": "99ab61eba3de0cf14ef4ed5597f54e81ea8383b6782beba4b9b3f884acae4629",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.10.0",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.0.exe"
65
+ "version": "2.10.1",
66
+ "expectedSize": 29829632,
67
+ "expectedChecksum": "82e82e35c1728a214db37967dba751e7",
68
+ "expectedChecksumSHA256": "ba509e921c692ac60c1138b5b9c83a23b4e11feb339f08df5f1cfced44cffc91",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.10.1",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.1.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.10.0",
74
- "expectedSize": 29257912,
75
- "expectedChecksum": "fa64e70507e70b8d8063ca0993a3b07a",
76
- "expectedChecksumSHA256": "76880201ff7204c32cbbaa2c0cbf6a30528486eb1f2948064dcd218bceaab65e",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.10.0",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.0"
73
+ "version": "2.10.1",
74
+ "expectedSize": 29266104,
75
+ "expectedChecksum": "d183c2335e16f8c568bdc9e782dcd240",
76
+ "expectedChecksumSHA256": "338e0cce561d29993737b91abfe7fc12c7d82d014ef114348ccdafeb3b7be3ae",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.10.1",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.1"
79
79
  }
80
80
  }
81
81
  }
package/lib/env.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isFirebaseMcp = exports.isFirebaseStudio = void 0;
4
+ const fsutils_1 = require("./fsutils");
5
+ let googleIdxFolderExists;
6
+ function isFirebaseStudio() {
7
+ if (googleIdxFolderExists === true || process.env.MONOSPACE_ENV)
8
+ return true;
9
+ if (googleIdxFolderExists === false)
10
+ return false;
11
+ googleIdxFolderExists = (0, fsutils_1.dirExistsSync)("/google/idx");
12
+ return googleIdxFolderExists;
13
+ }
14
+ exports.isFirebaseStudio = isFirebaseStudio;
15
+ function isFirebaseMcp() {
16
+ return !!process.env.IS_FIREBASE_MCP;
17
+ }
18
+ exports.isFirebaseMcp = isFirebaseMcp;
@@ -41,6 +41,13 @@ exports.ALL_EXPERIMENTS = experiments({
41
41
  "of how that image was created.",
42
42
  public: true,
43
43
  },
44
+ dangerouslyAllowFunctionsConfig: {
45
+ shortDescription: "Allows the use of deprecated functions.config() API",
46
+ fullDescription: "The functions.config() API is deprecated and will be removed on December 31, 2025. " +
47
+ "This experiment allows continued use of the API during the migration period.",
48
+ default: true,
49
+ public: true,
50
+ },
44
51
  emulatoruisnapshot: {
45
52
  shortDescription: "Load pre-release versions of the emulator UI",
46
53
  },
@@ -18,6 +18,20 @@ class FirestoreApi {
18
18
  this.apiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.firestoreOrigin)(), apiVersion: "v1" });
19
19
  this.printer = new pretty_print_1.PrettyPrint();
20
20
  }
21
+ static processIndexes(indexes) {
22
+ return indexes.map((index) => {
23
+ var _a, _b, _c;
24
+ let fields = index.fields;
25
+ const lastField = (_a = index.fields) === null || _a === void 0 ? void 0 : _a[index.fields.length - 1];
26
+ if ((lastField === null || lastField === void 0 ? void 0 : lastField.fieldPath) === "__name__") {
27
+ const defaultDirection = (_c = (_b = index.fields) === null || _b === void 0 ? void 0 : _b[index.fields.length - 2]) === null || _c === void 0 ? void 0 : _c.order;
28
+ if ((lastField === null || lastField === void 0 ? void 0 : lastField.order) === defaultDirection) {
29
+ fields = fields.slice(0, -1);
30
+ }
31
+ }
32
+ return Object.assign(Object.assign({}, index), { fields });
33
+ });
34
+ }
21
35
  async deploy(options, indexes, fieldOverrides, databaseId = "(default)") {
22
36
  const spec = this.upgradeOldSpec({
23
37
  indexes,
@@ -126,7 +140,7 @@ class FirestoreApi {
126
140
  if (!indexes) {
127
141
  return [];
128
142
  }
129
- return indexes;
143
+ return FirestoreApi.processIndexes(indexes);
130
144
  }
131
145
  async listFieldOverrides(project, databaseId = "(default)") {
132
146
  const parent = `projects/${project}/databases/${databaseId}/collectionGroups/-`;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logFunctionsConfigDeprecationWarning = void 0;
4
+ const utils_1 = require("../utils");
5
+ const FUNCTIONS_CONFIG_DEPRECATION_MESSAGE = `DEPRECATION NOTICE: Action required to deploy after Dec 31, 2025
6
+
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.
9
+
10
+ What this means for you:
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.
14
+
15
+ Existing deployments will continue to work with their current configuration.
16
+
17
+ See your migration options at: https://firebase.google.com/docs/functions/config-env#migrate-to-dotenv`;
18
+ function logFunctionsConfigDeprecationWarning() {
19
+ (0, utils_1.logWarningToStderr)(FUNCTIONS_CONFIG_DEPRECATION_MESSAGE);
20
+ }
21
+ exports.logFunctionsConfigDeprecationWarning = logFunctionsConfigDeprecationWarning;
@@ -7,7 +7,9 @@ const path = require("path");
7
7
  const prompt_1 = require("../../../prompt");
8
8
  const utils = require("../../../utils");
9
9
  const logger_1 = require("../../../logger");
10
- const PROMPTS_DIR = path.join(__dirname, "../../../../prompts");
10
+ const vsCodeUtils_1 = require("../../../vsCodeUtils");
11
+ const CLI_PROMPTS_DIR = path.join(__dirname, "../../../../prompts");
12
+ const VSCODE_PROMPTS_DIR = path.join(__dirname, "./prompts");
11
13
  const FIREBASE_TAG_REGEX = /<firebase_prompts(?:\s+hash="([^"]+)")?>([\s\S]*?)<\/firebase_prompts>/;
12
14
  const PROMPT_FILES = {
13
15
  base: "FIREBASE.md",
@@ -92,6 +94,7 @@ function getFeatureContent(feature) {
92
94
  const filename = PROMPT_FILES[feature];
93
95
  if (!filename)
94
96
  return "";
97
+ const PROMPTS_DIR = (0, vsCodeUtils_1.isVSCodeExtension)() ? VSCODE_PROMPTS_DIR : CLI_PROMPTS_DIR;
95
98
  const content = fs.readFileSync(path.join(PROMPTS_DIR, filename), "utf8");
96
99
  return content;
97
100
  }
@@ -4,6 +4,7 @@ exports.actuate = exports.askQuestions = void 0;
4
4
  const path_1 = require("path");
5
5
  const prompt_1 = require("../../../prompt");
6
6
  const templates_1 = require("../../../templates");
7
+ const ensureProjectConfigured_1 = require("../../../apptesting/ensureProjectConfigured");
7
8
  const SMOKE_TEST_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/apptesting/smoke_test.yaml");
8
9
  async function askQuestions(setup) {
9
10
  var _a, _b;
@@ -14,6 +15,9 @@ async function askQuestions(setup) {
14
15
  default: "tests",
15
16
  })),
16
17
  } });
18
+ if (setup.projectId) {
19
+ await (0, ensureProjectConfigured_1.ensureProjectConfigured)(setup.projectId);
20
+ }
17
21
  }
18
22
  exports.askQuestions = askQuestions;
19
23
  async function actuate(setup, config) {
package/lib/mcp/index.js CHANGED
@@ -19,8 +19,11 @@ const hubClient_js_1 = require("../emulator/hubClient.js");
19
19
  const node_fs_1 = require("node:fs");
20
20
  const ensureApiEnabled_js_1 = require("../ensureApiEnabled.js");
21
21
  const api = require("../api.js");
22
- const SERVER_VERSION = "0.1.0";
23
- const cmd = new command_js_1.Command("experimental:mcp").before(requireAuth_js_1.requireAuth);
22
+ const logging_transport_js_1 = require("./logging-transport.js");
23
+ const env_js_1 = require("../env.js");
24
+ const timeout_js_1 = require("../timeout.js");
25
+ const SERVER_VERSION = "0.2.0";
26
+ const cmd = new command_js_1.Command("experimental:mcp");
24
27
  const orderedLogLevels = [
25
28
  "debug",
26
29
  "info",
@@ -32,9 +35,20 @@ const orderedLogLevels = [
32
35
  "emergency",
33
36
  ];
34
37
  class FirebaseMcpServer {
38
+ async trackGA4(event, params = {}) {
39
+ var _a, _b;
40
+ if (!this.clientInfo)
41
+ await (0, timeout_js_1.timeoutFallback)(this.ready(), null, 2000);
42
+ const clientInfoParams = {
43
+ mcp_client_name: ((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name) || "<unknown-client>",
44
+ mcp_client_version: ((_b = this.clientInfo) === null || _b === void 0 ? void 0 : _b.version) || "<unknown-version>",
45
+ };
46
+ (0, track_js_1.trackGA4)(event, Object.assign(Object.assign({}, params), clientInfoParams));
47
+ }
35
48
  constructor(options) {
36
49
  this._ready = false;
37
50
  this._readyPromises = [];
51
+ this.currentLogLevel = process.env.FIREBASE_MCP_DEBUG_LOG ? "debug" : undefined;
38
52
  this.logger = Object.fromEntries(orderedLogLevels.map((logLevel) => [
39
53
  logLevel,
40
54
  (message) => this.log(logLevel, message),
@@ -50,10 +64,7 @@ class FirebaseMcpServer {
50
64
  const clientInfo = this.server.getClientVersion();
51
65
  this.clientInfo = clientInfo;
52
66
  if (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.name) {
53
- (0, track_js_1.trackGA4)("mcp_client_connected", {
54
- mcp_client_name: clientInfo.name,
55
- mcp_client_version: clientInfo.version,
56
- });
67
+ this.trackGA4("mcp_client_connected");
57
68
  }
58
69
  if (!((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name))
59
70
  this.clientInfo = { name: "<unknown-client>" };
@@ -76,9 +87,12 @@ class FirebaseMcpServer {
76
87
  this._readyPromises.push({ resolve: resolve, reject });
77
88
  });
78
89
  }
90
+ get clientName() {
91
+ var _a, _b;
92
+ return (_b = (_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : ((0, env_js_1.isFirebaseStudio)() ? "Firebase Studio" : "<unknown-client>");
93
+ }
79
94
  get clientConfigKey() {
80
- var _a;
81
- return `mcp.clientConfigs.${((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name) || "<unknown-client>"}:${this.startupRoot || process.cwd()}`;
95
+ return `mcp.clientConfigs.${this.clientName}:${this.startupRoot || process.cwd()}`;
82
96
  }
83
97
  getStoredClientConfig() {
84
98
  return configstore_js_1.configstore.get(this.clientConfigKey) || {};
@@ -90,17 +104,19 @@ class FirebaseMcpServer {
90
104
  return newConfig;
91
105
  }
92
106
  async detectProjectRoot() {
93
- await this.ready();
107
+ await (0, timeout_js_1.timeoutFallback)(this.ready(), null, 2000);
94
108
  if (this.cachedProjectRoot)
95
109
  return this.cachedProjectRoot;
96
110
  const storedRoot = this.getStoredClientConfig().projectRoot;
97
111
  this.cachedProjectRoot = storedRoot || this.startupRoot || process.cwd();
112
+ this.log("debug", "detected and cached project root: " + this.cachedProjectRoot);
98
113
  return this.cachedProjectRoot;
99
114
  }
100
115
  async detectActiveFeatures() {
101
116
  var _a;
102
117
  if ((_a = this.detectedFeatures) === null || _a === void 0 ? void 0 : _a.length)
103
118
  return this.detectedFeatures;
119
+ this.log("debug", "detecting active features of Firebase MCP server...");
104
120
  const options = await this.resolveOptions();
105
121
  const projectId = await this.getProjectId();
106
122
  const detected = await Promise.all(types_js_2.SERVER_FEATURES.map(async (f) => {
@@ -109,6 +125,7 @@ class FirebaseMcpServer {
109
125
  return null;
110
126
  }));
111
127
  this.detectedFeatures = detected.filter((f) => !!f);
128
+ this.log("debug", "detected features of Firebase MCP server: " + (this.detectedFeatures.join(", ") || "<none>"));
112
129
  return this.detectedFeatures;
113
130
  }
114
131
  async getEmulatorHubClient() {
@@ -156,36 +173,37 @@ class FirebaseMcpServer {
156
173
  async getProjectId() {
157
174
  return (0, projectUtils_js_1.getProjectId)(await this.resolveOptions());
158
175
  }
159
- async getAuthenticatedUser() {
176
+ async getAuthenticatedUser(skipAutoAuth = false) {
160
177
  try {
161
- const email = await (0, requireAuth_js_1.requireAuth)(await this.resolveOptions());
162
- return email !== null && email !== void 0 ? email : "Application Default Credentials";
178
+ this.log("debug", `calling requireAuth`);
179
+ const email = await (0, requireAuth_js_1.requireAuth)(await this.resolveOptions(), skipAutoAuth);
180
+ this.log("debug", `detected authenticated account: ${email || "<none>"}`);
181
+ return (email !== null && email !== void 0 ? email : skipAutoAuth) ? null : "Application Default Credentials";
163
182
  }
164
183
  catch (e) {
184
+ this.log("debug", `error in requireAuth: ${e}`);
165
185
  return null;
166
186
  }
167
187
  }
168
188
  async mcpListTools() {
169
- var _a, _b;
170
189
  await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]);
171
190
  const hasActiveProject = !!(await this.getProjectId());
172
- await (0, track_js_1.trackGA4)("mcp_list_tools", {
173
- mcp_client_name: (_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name,
174
- mcp_client_version: (_b = this.clientInfo) === null || _b === void 0 ? void 0 : _b.version,
175
- });
191
+ await this.trackGA4("mcp_list_tools");
192
+ const skipAutoAuthForStudio = (0, env_js_1.isFirebaseStudio)();
193
+ this.log("debug", `skip auto-auth in studio environment: ${skipAutoAuthForStudio}`);
176
194
  return {
177
195
  tools: this.availableTools.map((t) => t.mcp),
178
196
  _meta: {
179
197
  projectRoot: this.cachedProjectRoot,
180
198
  projectDetected: hasActiveProject,
181
- authenticatedUser: await this.getAuthenticatedUser(),
199
+ authenticatedUser: await this.getAuthenticatedUser(skipAutoAuthForStudio),
182
200
  activeFeatures: this.activeFeatures,
183
201
  detectedFeatures: this.detectedFeatures,
184
202
  },
185
203
  };
186
204
  }
187
205
  async mcpCallTool(request) {
188
- var _a, _b, _c, _d, _e, _f, _g;
206
+ var _a, _b, _c;
189
207
  await this.detectProjectRoot();
190
208
  const toolName = request.params.name;
191
209
  const toolArgs = request.params.arguments;
@@ -225,26 +243,24 @@ class FirebaseMcpServer {
225
243
  };
226
244
  try {
227
245
  const res = await tool.fn(toolArgs, toolsCtx);
228
- await (0, track_js_1.trackGA4)("mcp_tool_call", {
246
+ await this.trackGA4("mcp_tool_call", {
229
247
  tool_name: toolName,
230
248
  error: res.isError ? 1 : 0,
231
- mcp_client_name: (_d = this.clientInfo) === null || _d === void 0 ? void 0 : _d.name,
232
- mcp_client_version: (_e = this.clientInfo) === null || _e === void 0 ? void 0 : _e.version,
233
249
  });
234
250
  return res;
235
251
  }
236
252
  catch (err) {
237
- await (0, track_js_1.trackGA4)("mcp_tool_call", {
253
+ await this.trackGA4("mcp_tool_call", {
238
254
  tool_name: toolName,
239
255
  error: 1,
240
- mcp_client_name: (_f = this.clientInfo) === null || _f === void 0 ? void 0 : _f.name,
241
- mcp_client_version: (_g = this.clientInfo) === null || _g === void 0 ? void 0 : _g.version,
242
256
  });
243
257
  return (0, util_js_1.mcpError)(err);
244
258
  }
245
259
  }
246
260
  async start() {
247
- const transport = new stdio_js_1.StdioServerTransport();
261
+ const transport = process.env.FIREBASE_MCP_DEBUG_LOG
262
+ ? new logging_transport_js_1.LoggingStdioServerTransport(process.env.FIREBASE_MCP_DEBUG_LOG)
263
+ : new stdio_js_1.StdioServerTransport();
248
264
  await this.server.connect(transport);
249
265
  }
250
266
  async log(level, message) {
@@ -258,7 +274,8 @@ class FirebaseMcpServer {
258
274
  if (orderedLogLevels.indexOf(this.currentLogLevel) > orderedLogLevels.indexOf(level)) {
259
275
  return;
260
276
  }
261
- await this.server.sendLoggingMessage({ level, data });
277
+ if (this._ready)
278
+ await this.server.sendLoggingMessage({ level, data });
262
279
  }
263
280
  }
264
281
  exports.FirebaseMcpServer = FirebaseMcpServer;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoggingStdioServerTransport = void 0;
4
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
5
+ const fs_1 = require("fs");
6
+ const promises_1 = require("fs/promises");
7
+ class LoggingStdioServerTransport extends stdio_js_1.StdioServerTransport {
8
+ constructor(path) {
9
+ super();
10
+ this.path = path;
11
+ (0, fs_1.appendFileSync)(path, "--- new process start ---\n");
12
+ const origOnData = this._ondata;
13
+ this._ondata = (chunk) => {
14
+ origOnData(chunk);
15
+ (0, fs_1.appendFileSync)(path, chunk.toString(), { encoding: "utf8" });
16
+ };
17
+ }
18
+ async send(message) {
19
+ await super.send(message);
20
+ await (0, promises_1.appendFile)(this.path, JSON.stringify(message) + "\n");
21
+ }
22
+ }
23
+ exports.LoggingStdioServerTransport = LoggingStdioServerTransport;
package/lib/mcp/util.js CHANGED
@@ -6,6 +6,7 @@ const js_yaml_1 = require("js-yaml");
6
6
  const os_1 = require("os");
7
7
  const api_1 = require("../api");
8
8
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
9
+ const timeout_1 = require("../timeout");
9
10
  function toContent(data, options) {
10
11
  if (typeof data === "string")
11
12
  return { content: [{ type: "text", text: data }] };
@@ -70,7 +71,7 @@ async function checkFeatureActive(feature, projectId, options) {
70
71
  return true;
71
72
  try {
72
73
  if (projectId)
73
- return await (0, ensureApiEnabled_1.check)(projectId, SERVER_FEATURE_APIS[feature], "", true);
74
+ return await (0, timeout_1.timeoutFallback)((0, ensureApiEnabled_1.check)(projectId, SERVER_FEATURE_APIS[feature], "", true), true, 3000);
74
75
  }
75
76
  catch (e) {
76
77
  return true;
@@ -10,6 +10,8 @@ const logger_1 = require("./logger");
10
10
  const utils = require("./utils");
11
11
  const scopes = require("./scopes");
12
12
  const auth_1 = require("./auth");
13
+ const env_1 = require("./env");
14
+ const timeout_1 = require("./timeout");
13
15
  const AUTH_ERROR_MESSAGE = `Command requires authentication, please run ${clc.bold("firebase login")}`;
14
16
  let authClient;
15
17
  let lastOptions;
@@ -28,13 +30,14 @@ async function autoAuth(options, authScopes) {
28
30
  logger_1.logger.debug(`Running auto auth`);
29
31
  let clientEmail;
30
32
  try {
31
- const credentials = await client.getCredentials();
33
+ const timeoutMillis = (0, env_1.isFirebaseMcp)() ? 5000 : 15000;
34
+ const credentials = await (0, timeout_1.timeoutError)(client.getCredentials(), new error_1.FirebaseError(`Authenticating with default credentials timed out after ${timeoutMillis / 1000} seconds. Please try running \`firebase login\` instead.`), timeoutMillis);
32
35
  clientEmail = credentials.client_email;
33
36
  }
34
37
  catch (e) {
35
38
  logger_1.logger.debug(`Error getting account credentials.`);
36
39
  }
37
- if (process.env.MONOSPACE_ENV && token && clientEmail) {
40
+ if ((0, env_1.isFirebaseStudio)() && token && clientEmail) {
38
41
  const activeAccount = {
39
42
  user: { email: clientEmail },
40
43
  tokens: {
@@ -56,7 +59,7 @@ async function refreshAuth() {
56
59
  return lastOptions.tokens;
57
60
  }
58
61
  exports.refreshAuth = refreshAuth;
59
- async function requireAuth(options) {
62
+ async function requireAuth(options, skipAutoAuth = false) {
60
63
  lastOptions = options;
61
64
  api.setScopes([scopes.CLOUD_PLATFORM, scopes.FIREBASE_PLATFORM]);
62
65
  options.authScopes = api.getScopes();
@@ -76,6 +79,9 @@ async function requireAuth(options) {
76
79
  else if (user && (!(0, auth_1.isExpired)(tokens) || (tokens === null || tokens === void 0 ? void 0 : tokens.refresh_token))) {
77
80
  logger_1.logger.debug(`> authorizing via signed-in user (${user.email})`);
78
81
  }
82
+ else if (skipAutoAuth) {
83
+ return null;
84
+ }
79
85
  else {
80
86
  try {
81
87
  return await autoAuth(options, options.authScopes);
package/lib/timeout.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.timeoutError = exports.timeoutFallback = void 0;
4
+ async function timeoutFallback(promise, value, timeoutMillis = 2000) {
5
+ return Promise.race([
6
+ promise,
7
+ new Promise((resolve) => setTimeout(() => resolve(value), timeoutMillis)),
8
+ ]);
9
+ }
10
+ exports.timeoutFallback = timeoutFallback;
11
+ async function timeoutError(promise, error, timeoutMillis = 5000) {
12
+ if (typeof error === "string")
13
+ error = new Error(error);
14
+ return Promise.race([
15
+ promise,
16
+ new Promise((resolve, reject) => {
17
+ setTimeout(() => reject(error || new Error("Operation timed out.")), timeoutMillis);
18
+ }),
19
+ ]);
20
+ }
21
+ exports.timeoutError = timeoutError;
package/lib/track.js CHANGED
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- var _a;
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
4
3
  exports.cliSession = exports.vscodeSession = exports.emulatorSession = exports.trackVSCode = exports.trackEmulator = exports.trackGA4 = exports.usageEnabled = exports.GA4_PROPERTIES = void 0;
5
4
  const node_fetch_1 = require("node-fetch");
@@ -7,6 +6,7 @@ const uuid_1 = require("uuid");
7
6
  const auth_1 = require("./auth");
8
7
  const configstore_1 = require("./configstore");
9
8
  const logger_1 = require("./logger");
9
+ const env_1 = require("./env");
10
10
  const pkg = require("../package.json");
11
11
  exports.GA4_PROPERTIES = {
12
12
  cli: {
@@ -43,7 +43,7 @@ const GA4_USER_PROPS = {
43
43
  value: process.env.FIREPIT_VERSION || "none",
44
44
  },
45
45
  is_firebase_studio: {
46
- value: (_a = process.env.MONOSPACE_ENV) !== null && _a !== void 0 ? _a : "false",
46
+ value: (0, env_1.isFirebaseStudio)().toString(),
47
47
  },
48
48
  };
49
49
  async function trackGA4(eventName, params, duration = 1) {
package/lib/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getHostnameFromUrl = 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.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 = void 0;
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;
5
5
  const fs = require("fs-extra");
6
6
  const tty = require("tty");
7
7
  const path = require("node:path");
@@ -115,6 +115,11 @@ function logWarning(message, type = "warn", data = undefined) {
115
115
  logger_1.logger[type](clc.yellow(clc.bold(`${WARNING_CHAR} `)), message, data);
116
116
  }
117
117
  exports.logWarning = logWarning;
118
+ function logWarningToStderr(message) {
119
+ const prefix = clc.bold(`${WARNING_CHAR} `);
120
+ process.stderr.write(clc.yellow(prefix + message) + "\n");
121
+ }
122
+ exports.logWarningToStderr = logWarningToStderr;
118
123
  function logLabeledWarning(label, message, type = "warn", data = undefined) {
119
124
  logger_1.logger[type](clc.yellow(clc.bold(`${WARNING_CHAR} ${label}:`)), message, data);
120
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "14.11.0",
3
+ "version": "14.11.1",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -1,16 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const chai_1 = require("chai");
4
- const adminSdkConfig_1 = require("./adminSdkConfig");
5
- describe("adminSdkConfig", () => {
6
- describe("getProjectAdminSdkConfigOrCached", () => {
7
- it("should return a fake config for a demo project id", async () => {
8
- const projectId = "demo-project-1234";
9
- await (0, chai_1.expect)((0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId)).to.eventually.deep.equal({
10
- projectId: "demo-project-1234",
11
- databaseURL: "https://demo-project-1234.firebaseio.com",
12
- storageBucket: "demo-project-1234.appspot.com",
13
- });
14
- });
15
- });
16
- });
@@ -1,74 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const chai_1 = require("chai");
4
- const firestore_1 = require("../gcp/firestore");
5
- const proto_1 = require("../gcp/proto");
6
- const sort = require("./api-sort");
7
- describe("compareApiBackup", () => {
8
- it("should compare backups by location", () => {
9
- const nam5Backup = {
10
- name: "projects/example/locations/nam5/backups/backupid",
11
- };
12
- const usWest1Backup = {
13
- name: "projects/example/locations/us-west1/backups/backupid",
14
- };
15
- (0, chai_1.expect)(sort.compareApiBackup(usWest1Backup, nam5Backup)).to.greaterThanOrEqual(1);
16
- (0, chai_1.expect)(sort.compareApiBackup(nam5Backup, usWest1Backup)).to.lessThanOrEqual(-1);
17
- });
18
- it("should compare backups by snapshotTime (descending) if location is the same", () => {
19
- const earlierBackup = {
20
- name: "projects/example/locations/nam5/backups/backupid",
21
- snapshotTime: "2024-01-01T00:00:00.000000Z",
22
- };
23
- const laterBackup = {
24
- name: "projects/example/locations/nam5/backups/backupid",
25
- snapshotTime: "2024-02-02T00:00:00.000000Z",
26
- };
27
- (0, chai_1.expect)(sort.compareApiBackup(earlierBackup, laterBackup)).to.greaterThanOrEqual(1);
28
- (0, chai_1.expect)(sort.compareApiBackup(laterBackup, earlierBackup)).to.lessThanOrEqual(-1);
29
- });
30
- it("should compare backups by full name if location and snapshotTime are the same", () => {
31
- const nam5Backup1 = {
32
- name: "projects/example/locations/nam5/backups/earlier-backupid",
33
- snapshotTime: "2024-01-01T00:00:00.000000Z",
34
- };
35
- const nam5Backup2 = {
36
- name: "projects/example/locations/nam5/backups/later-backupid",
37
- snapshotTime: "2024-01-01T00:00:00.000000Z",
38
- };
39
- (0, chai_1.expect)(sort.compareApiBackup(nam5Backup2, nam5Backup1)).to.greaterThanOrEqual(1);
40
- (0, chai_1.expect)(sort.compareApiBackup(nam5Backup1, nam5Backup2)).to.lessThanOrEqual(-1);
41
- });
42
- });
43
- describe("compareApiBackupSchedule", () => {
44
- it("daily schedules should precede weekly ones", () => {
45
- const dailySchedule = {
46
- name: "projects/example/databases/mydatabase/backupSchedules/schedule",
47
- dailyRecurrence: {},
48
- retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
49
- };
50
- const weeklySchedule = {
51
- name: "projects/example/databases/mydatabase/backupSchedules/schedule",
52
- weeklyRecurrence: {
53
- day: firestore_1.DayOfWeek.FRIDAY,
54
- },
55
- retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24 * 7),
56
- };
57
- (0, chai_1.expect)(sort.compareApiBackupSchedule(weeklySchedule, dailySchedule)).to.greaterThanOrEqual(1);
58
- (0, chai_1.expect)(sort.compareApiBackup(dailySchedule, weeklySchedule)).to.lessThanOrEqual(-1);
59
- });
60
- it("should compare schedules with the same recurrence by name", () => {
61
- const dailySchedule1 = {
62
- name: "projects/example/databases/mydatabase/backupSchedules/schedule1",
63
- dailyRecurrence: {},
64
- retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
65
- };
66
- const dailySchedule2 = {
67
- name: "projects/example/databases/mydatabase/backupSchedules/schedule2",
68
- dailyRecurrence: {},
69
- retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
70
- };
71
- (0, chai_1.expect)(sort.compareApiBackupSchedule(dailySchedule1, dailySchedule2)).to.lessThanOrEqual(-1);
72
- (0, chai_1.expect)(sort.compareApiBackup(dailySchedule2, dailySchedule1)).to.greaterThanOrEqual(1);
73
- });
74
- });
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const chai_1 = require("chai");
4
- const backupUtils_1 = require("./backupUtils");
5
- describe("calculateRetention", () => {
6
- it("should accept minutes", () => {
7
- (0, chai_1.expect)((0, backupUtils_1.calculateRetention)("5m")).to.eq(300);
8
- });
9
- it("should accept hours", () => {
10
- (0, chai_1.expect)((0, backupUtils_1.calculateRetention)("3h")).to.eq(10800);
11
- });
12
- it("should accept days", () => {
13
- (0, chai_1.expect)((0, backupUtils_1.calculateRetention)("2d")).to.eq(172800);
14
- });
15
- it("should accept weeks", () => {
16
- (0, chai_1.expect)((0, backupUtils_1.calculateRetention)("3w")).to.eq(1814400);
17
- });
18
- });
@@ -1,61 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const chai_1 = require("chai");
4
- const API = require("./api-types");
5
- const pretty_print_1 = require("./pretty-print");
6
- const printer = new pretty_print_1.PrettyPrint();
7
- describe("prettyIndexString", () => {
8
- it("should correctly print an order type Index", () => {
9
- (0, chai_1.expect)(printer.prettyIndexString({
10
- name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
11
- queryScope: API.QueryScope.COLLECTION,
12
- fields: [
13
- { fieldPath: "foo", order: API.Order.ASCENDING },
14
- { fieldPath: "bar", order: API.Order.DESCENDING },
15
- ],
16
- }, false)).to.contain("(foo,ASCENDING) (bar,DESCENDING) ");
17
- });
18
- it("should correctly print a contains type Index", () => {
19
- (0, chai_1.expect)(printer.prettyIndexString({
20
- name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
21
- queryScope: API.QueryScope.COLLECTION,
22
- fields: [
23
- { fieldPath: "foo", order: API.Order.ASCENDING },
24
- { fieldPath: "baz", arrayConfig: API.ArrayConfig.CONTAINS },
25
- ],
26
- }, false)).to.contain("(foo,ASCENDING) (baz,CONTAINS) ");
27
- });
28
- it("should correctly print a vector type Index", () => {
29
- (0, chai_1.expect)(printer.prettyIndexString({
30
- name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
31
- queryScope: API.QueryScope.COLLECTION,
32
- fields: [{ fieldPath: "foo", vectorConfig: { dimension: 100, flat: {} } }],
33
- }, false)).to.contain("(foo,VECTOR<100>) ");
34
- });
35
- it("should correctly print a vector type Index with other fields", () => {
36
- (0, chai_1.expect)(printer.prettyIndexString({
37
- name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
38
- queryScope: API.QueryScope.COLLECTION,
39
- fields: [
40
- { fieldPath: "foo", order: API.Order.ASCENDING },
41
- { fieldPath: "bar", vectorConfig: { dimension: 200, flat: {} } },
42
- ],
43
- }, false)).to.contain("(foo,ASCENDING) (bar,VECTOR<200>) ");
44
- });
45
- });
46
- describe("firebaseConsoleDatabaseUrl", () => {
47
- it("should provide a console link", () => {
48
- (0, chai_1.expect)(printer.firebaseConsoleDatabaseUrl("example-project", "example-db")).to.equal("https://console.firebase.google.com/project/example-project/firestore/databases/example-db/data");
49
- });
50
- it("should convert (default) to -default-", () => {
51
- (0, chai_1.expect)(printer.firebaseConsoleDatabaseUrl("example-project", "(default)")).to.equal("https://console.firebase.google.com/project/example-project/firestore/databases/-default-/data");
52
- });
53
- });
54
- describe("prettyStringArray", () => {
55
- it("should correctly print an array of strings", () => {
56
- (0, chai_1.expect)(printer.prettyStringArray(["kms-key-1", "kms-key-2"])).to.equal("kms-key-1\nkms-key-2\n");
57
- });
58
- it("should print nothing if the array is empty", () => {
59
- (0, chai_1.expect)(printer.prettyStringArray([])).to.equal("");
60
- });
61
- });
@@ -1,42 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const chai_1 = require("chai");
4
- const util = require("./util");
5
- describe("IndexNameParsing", () => {
6
- it("should parse an index name correctly", () => {
7
- const name = "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123/";
8
- (0, chai_1.expect)(util.parseIndexName(name)).to.eql({
9
- projectId: "myproject",
10
- databaseId: "(default)",
11
- collectionGroupId: "collection",
12
- indexId: "abc123",
13
- });
14
- });
15
- it("should parse a field name correctly", () => {
16
- const name = "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123/";
17
- (0, chai_1.expect)(util.parseFieldName(name)).to.eql({
18
- projectId: "myproject",
19
- databaseId: "(default)",
20
- collectionGroupId: "collection",
21
- fieldPath: "abc123",
22
- });
23
- });
24
- it("should parse an index name from a named database correctly", () => {
25
- const name = "/projects/myproject/databases/named-db/collectionGroups/collection/indexes/abc123/";
26
- (0, chai_1.expect)(util.parseIndexName(name)).to.eql({
27
- projectId: "myproject",
28
- databaseId: "named-db",
29
- collectionGroupId: "collection",
30
- indexId: "abc123",
31
- });
32
- });
33
- it("should parse a field name from a named database correctly", () => {
34
- const name = "/projects/myproject/databases/named-db/collectionGroups/collection/fields/abc123/";
35
- (0, chai_1.expect)(util.parseFieldName(name)).to.eql({
36
- projectId: "myproject",
37
- databaseId: "named-db",
38
- collectionGroupId: "collection",
39
- fieldPath: "abc123",
40
- });
41
- });
42
- });