firebase-tools 14.3.1 → 14.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/lib/api.js +10 -2
  2. package/lib/apphosting/backend.js +72 -18
  3. package/lib/apphosting/rollout.js +2 -2
  4. package/lib/apphosting/secrets/dialogs.js +4 -4
  5. package/lib/apphosting/secrets/index.js +2 -2
  6. package/lib/auth.js +22 -2
  7. package/lib/bin/cli.js +1 -33
  8. package/lib/bin/mcp.js +12 -2
  9. package/lib/checkValidTargetFilters.js +1 -0
  10. package/lib/command.js +3 -2
  11. package/lib/commands/apphosting-secrets-grantaccess.js +1 -1
  12. package/lib/commands/deploy.js +1 -0
  13. package/lib/commands/init.js +1 -1
  14. package/lib/commands/login-use.js +2 -11
  15. package/lib/commands/use.js +9 -8
  16. package/lib/config.js +1 -0
  17. package/lib/crashlytics/listTopIssues.js +44 -0
  18. package/lib/dataconnect/cloudAICompanionClient.js +67 -0
  19. package/lib/deploy/apphosting/args.js +2 -0
  20. package/lib/deploy/apphosting/deploy.js +74 -0
  21. package/lib/deploy/apphosting/index.js +9 -0
  22. package/lib/deploy/apphosting/prepare.js +141 -0
  23. package/lib/deploy/apphosting/release.js +53 -0
  24. package/lib/deploy/apphosting/util.js +65 -0
  25. package/lib/deploy/extensions/v2FunctionHelper.js +2 -1
  26. package/lib/deploy/functions/checkIam.js +3 -3
  27. package/lib/deploy/functions/ensure.js +2 -1
  28. package/lib/deploy/functions/prepare.js +23 -16
  29. package/lib/deploy/functions/release/fabricator.js +4 -4
  30. package/lib/deploy/functions/release/index.js +1 -1
  31. package/lib/deploy/functions/runtimes/python/index.js +3 -0
  32. package/lib/deploy/functions/runtimes/supported/types.js +17 -11
  33. package/lib/deploy/index.js +2 -0
  34. package/lib/emulator/apphosting/index.js +1 -0
  35. package/lib/emulator/apphosting/serve.js +77 -3
  36. package/lib/emulator/auth/widget_ui.js +2 -1
  37. package/lib/emulator/controller.js +18 -4
  38. package/lib/emulator/dataconnectEmulator.js +9 -2
  39. package/lib/emulator/downloadableEmulatorInfo.json +60 -0
  40. package/lib/emulator/downloadableEmulators.js +25 -61
  41. package/lib/experiments.js +1 -1
  42. package/lib/extensions/manifest.js +2 -2
  43. package/lib/fsAsync.js +9 -2
  44. package/lib/gcp/auth.js +32 -2
  45. package/lib/gcp/cloudbilling.js +12 -1
  46. package/lib/gcp/cloudfunctions.js +1 -2
  47. package/lib/gcp/cloudfunctionsv2.js +1 -2
  48. package/lib/gcp/cloudscheduler.js +2 -2
  49. package/lib/gcp/computeEngine.js +19 -2
  50. package/lib/gcp/devConnect.js +6 -1
  51. package/lib/gcp/firestore.js +24 -1
  52. package/lib/gcp/iam.js +1 -5
  53. package/lib/gcp/storage.js +25 -1
  54. package/lib/index.js +1 -2
  55. package/lib/init/features/apphosting.js +84 -6
  56. package/lib/init/features/database.js +64 -45
  57. package/lib/init/features/dataconnect/index.js +51 -53
  58. package/lib/init/features/emulators.js +8 -4
  59. package/lib/init/features/firestore/index.js +54 -23
  60. package/lib/init/features/firestore/indexes.js +23 -24
  61. package/lib/init/features/firestore/rules.js +35 -41
  62. package/lib/init/features/functions/index.js +2 -0
  63. package/lib/init/features/functions/javascript.js +3 -2
  64. package/lib/init/features/functions/typescript.js +3 -2
  65. package/lib/init/features/genkit/index.js +2 -1
  66. package/lib/init/features/hosting/github.js +3 -2
  67. package/lib/init/features/index.js +8 -4
  68. package/lib/init/features/remoteconfig.js +3 -2
  69. package/lib/init/index.js +76 -24
  70. package/lib/logger.js +71 -7
  71. package/lib/management/projects.js +24 -1
  72. package/lib/mcp/errors.js +1 -1
  73. package/lib/mcp/index.js +135 -43
  74. package/lib/mcp/tools/auth/get_user.js +16 -7
  75. package/lib/mcp/tools/auth/index.js +8 -1
  76. package/lib/mcp/tools/auth/list_users.js +47 -0
  77. package/lib/mcp/tools/auth/set_claims.js +20 -11
  78. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  79. package/lib/mcp/tools/core/consult_assistant.js +1 -1
  80. package/lib/mcp/tools/core/create_android_sha.js +40 -0
  81. package/lib/mcp/tools/core/create_app.js +90 -0
  82. package/lib/mcp/tools/core/create_project.js +68 -0
  83. package/lib/mcp/tools/core/get_admin_sdk_config.js +26 -0
  84. package/lib/mcp/tools/core/get_environment.js +51 -0
  85. package/lib/mcp/tools/core/get_sdk_config.js +6 -3
  86. package/lib/mcp/tools/core/index.js +19 -2
  87. package/lib/mcp/tools/core/init.js +129 -0
  88. package/lib/mcp/tools/core/update_environment.js +55 -0
  89. package/lib/mcp/tools/crashlytics/index.js +5 -0
  90. package/lib/mcp/tools/crashlytics/list_top_issues.js +34 -0
  91. package/lib/mcp/tools/dataconnect/converter.js +13 -1
  92. package/lib/mcp/tools/dataconnect/emulator.js +32 -0
  93. package/lib/mcp/tools/dataconnect/execute_graphql.js +24 -8
  94. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +24 -8
  95. package/lib/mcp/tools/dataconnect/execute_mutation.js +27 -15
  96. package/lib/mcp/tools/dataconnect/execute_query.js +26 -14
  97. package/lib/mcp/tools/dataconnect/generate_operation.js +6 -6
  98. package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
  99. package/lib/mcp/tools/dataconnect/get_connector.js +7 -7
  100. package/lib/mcp/tools/dataconnect/get_schema.js +5 -5
  101. package/lib/mcp/tools/dataconnect/index.js +1 -5
  102. package/lib/mcp/tools/dataconnect/list_services.js +1 -1
  103. package/lib/mcp/tools/firestore/converter.js +47 -1
  104. package/lib/mcp/tools/firestore/delete_document.js +37 -0
  105. package/lib/mcp/tools/firestore/index.js +12 -2
  106. package/lib/mcp/tools/firestore/list_collections.js +1 -6
  107. package/lib/mcp/tools/firestore/query_collection.js +116 -0
  108. package/lib/mcp/tools/index.js +35 -16
  109. package/lib/mcp/tools/messaging/index.js +5 -0
  110. package/lib/mcp/tools/messaging/send_message.js +42 -0
  111. package/lib/mcp/tools/remoteconfig/get_template.js +27 -0
  112. package/lib/mcp/tools/remoteconfig/index.js +7 -0
  113. package/lib/mcp/tools/remoteconfig/publish_template.js +34 -0
  114. package/lib/mcp/tools/remoteconfig/rollback_template.js +29 -0
  115. package/lib/mcp/tools/rules/get_rules.js +29 -0
  116. package/lib/mcp/tools/rules/validate_rules.js +98 -0
  117. package/lib/mcp/tools/storage/get_download_url.js +8 -5
  118. package/lib/mcp/tools/storage/index.js +7 -2
  119. package/lib/mcp/types.js +9 -1
  120. package/lib/mcp/util.js +29 -2
  121. package/lib/messaging/interfaces.js +2 -0
  122. package/lib/messaging/sendMessage.js +48 -0
  123. package/lib/remoteconfig/publish.js +39 -0
  124. package/lib/requireAuth.js +2 -2
  125. package/lib/utils.js +2 -37
  126. package/package.json +2 -1
  127. package/schema/firebase-config.json +62 -10
  128. package/templates/init/functions/javascript/package.lint.json +1 -1
  129. package/templates/init/functions/javascript/package.nolint.json +1 -1
  130. package/templates/init/functions/typescript/package.lint.json +1 -1
  131. package/templates/init/functions/typescript/package.nolint.json +2 -2
  132. package/lib/mcp/tools/directory/get_project_directory.js +0 -20
  133. package/lib/mcp/tools/directory/index.js +0 -6
  134. package/lib/mcp/tools/directory/set_project_directory.js +0 -33
  135. package/lib/mcp/tools/firestore/get_rules.js +0 -26
package/lib/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.cloudCompanionOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.consoleOrigin = exports.authOrigin = exports.apphostingGitHubAppInstallationURL = exports.apphostingP4SADomain = exports.apphostingOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.developerConnectP4SADomain = exports.developerConnectOrigin = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
4
- exports.setScopes = exports.getScopes = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectP4SADomain = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = void 0;
3
+ exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.crashlyticsApiOrigin = exports.messagingApiOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.cloudCompanionOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.consoleOrigin = exports.authManagementOrigin = exports.authOrigin = exports.apphostingGitHubAppInstallationURL = exports.apphostingP4SADomain = exports.apphostingOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.developerConnectP4SADomain = exports.developerConnectOrigin = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
4
+ exports.setScopes = exports.getScopes = exports.cloudAiCompanionOrigin = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectP4SADomain = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = void 0;
5
5
  const constants_1 = require("./emulator/constants");
6
6
  const logger_1 = require("./logger");
7
7
  const scopes = require("./scopes");
@@ -37,6 +37,8 @@ const apphostingGitHubAppInstallationURL = () => utils.envOverride("FIREBASE_APP
37
37
  exports.apphostingGitHubAppInstallationURL = apphostingGitHubAppInstallationURL;
38
38
  const authOrigin = () => utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com");
39
39
  exports.authOrigin = authOrigin;
40
+ const authManagementOrigin = () => utils.envOverride("FIREBASE_AUTH_MANAGEMENT_URL", "https://identitytoolkit.googleapis.com");
41
+ exports.authManagementOrigin = authManagementOrigin;
40
42
  const consoleOrigin = () => utils.envOverride("FIREBASE_CONSOLE_URL", "https://console.firebase.google.com");
41
43
  exports.consoleOrigin = consoleOrigin;
42
44
  const dynamicLinksOrigin = () => utils.envOverride("FIREBASE_DYNAMIC_LINKS_URL", "https://firebasedynamiclinks.googleapis.com");
@@ -100,6 +102,10 @@ const rtdbMetadataOrigin = () => utils.envOverride("FIREBASE_RTDB_METADATA_URL",
100
102
  exports.rtdbMetadataOrigin = rtdbMetadataOrigin;
101
103
  const remoteConfigApiOrigin = () => utils.envOverride("FIREBASE_REMOTE_CONFIG_URL", "https://firebaseremoteconfig.googleapis.com");
102
104
  exports.remoteConfigApiOrigin = remoteConfigApiOrigin;
105
+ const messagingApiOrigin = () => utils.envOverride("FIREBASE_MESSAGING_CONFIG_URL", "https://fcm.googleapis.com");
106
+ exports.messagingApiOrigin = messagingApiOrigin;
107
+ const crashlyticsApiOrigin = () => utils.envOverride("FIREBASE_CRASHLYTICS_URL", "https://firebasecrashlytics.googleapis.com");
108
+ exports.crashlyticsApiOrigin = crashlyticsApiOrigin;
103
109
  const resourceManagerOrigin = () => utils.envOverride("FIREBASE_RESOURCEMANAGER_URL", "https://cloudresourcemanager.googleapis.com");
104
110
  exports.resourceManagerOrigin = resourceManagerOrigin;
105
111
  const rulesOrigin = () => utils.envOverride("FIREBASE_RULES_URL", "https://firebaserules.googleapis.com");
@@ -138,6 +144,8 @@ const cloudSQLAdminOrigin = () => utils.envOverride("CLOUD_SQL_URL", "https://sq
138
144
  exports.cloudSQLAdminOrigin = cloudSQLAdminOrigin;
139
145
  const vertexAIOrigin = () => utils.envOverride("VERTEX_AI_URL", "https://aiplatform.googleapis.com");
140
146
  exports.vertexAIOrigin = vertexAIOrigin;
147
+ const cloudAiCompanionOrigin = () => utils.envOverride("CLOUD_AI_COMPANION_URL", "https://cloudaicompanion.googleapis.com");
148
+ exports.cloudAiCompanionOrigin = cloudAiCompanionOrigin;
141
149
  function getScopes() {
142
150
  return Array.from(commandScopes);
143
151
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getBackend = exports.getBackendForAmbiguousLocation = exports.chooseBackends = exports.getBackendForLocation = exports.promptLocation = exports.deleteBackendAndPoll = exports.setDefaultTrafficPolicy = exports.createBackend = exports.ensureAppHostingComputeServiceAccount = exports.createGitRepoLink = exports.doSetup = void 0;
3
+ exports.getBackend = exports.getBackendForAmbiguousLocation = exports.chooseBackends = exports.promptExistingBackend = exports.getBackendForLocation = exports.promptLocation = exports.deleteBackendAndPoll = exports.setDefaultTrafficPolicy = exports.createBackend = exports.promptNewBackendId = exports.ensureAppHostingComputeServiceAccount = exports.createGitRepoLink = exports.ensureRequiredApisEnabled = exports.doSetupSourceDeploy = exports.doSetup = void 0;
4
4
  const clc = require("colorette");
5
5
  const poller = require("../operation-poller");
6
6
  const apphosting = require("../gcp/apphosting");
@@ -19,6 +19,7 @@ const app_1 = require("./app");
19
19
  const ora = require("ora");
20
20
  const node_fetch_1 = require("node-fetch");
21
21
  const rollout_1 = require("./rollout");
22
+ const fuzzy = require("fuzzy");
22
23
  const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
23
24
  const apphostingPollerOptions = {
24
25
  apiOrigin: (0, api_1.apphostingOrigin)(),
@@ -51,14 +52,7 @@ async function awaitTlsReady(url) {
51
52
  } while (!ready);
52
53
  }
53
54
  async function doSetup(projectId, webAppName, serviceAccount) {
54
- await Promise.all([
55
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.developerConnectOrigin)(), "apphosting", true),
56
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudbuildOrigin)(), "apphosting", true),
57
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.secretManagerOrigin)(), "apphosting", true),
58
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudRunApiOrigin)(), "apphosting", true),
59
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.artifactRegistryDomain)(), "apphosting", true),
60
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.iamOrigin)(), "apphosting", true),
61
- ]);
55
+ await ensureRequiredApisEnabled(projectId);
62
56
  await ensureAppHostingComputeServiceAccount(projectId, serviceAccount);
63
57
  const location = await promptLocation(projectId, "Select a primary region to host your backend:\n");
64
58
  const gitRepositoryLink = await githubConnections.linkGitHubRepository(projectId, location);
@@ -76,7 +70,7 @@ async function doSetup(projectId, webAppName, serviceAccount) {
76
70
  (0, utils_1.logWarning)(`Firebase web app not set`);
77
71
  }
78
72
  const createBackendSpinner = ora("Creating your new backend...").start();
79
- const backend = await createBackend(projectId, location, backendId, gitRepositoryLink, serviceAccount, webApp === null || webApp === void 0 ? void 0 : webApp.id, rootDir);
73
+ const backend = await createBackend(projectId, location, backendId, serviceAccount, gitRepositoryLink, webApp === null || webApp === void 0 ? void 0 : webApp.id, rootDir);
80
74
  createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
81
75
  await setDefaultTrafficPolicy(projectId, location, backendId, branch);
82
76
  const confirmRollout = await (0, prompt_1.confirm)({
@@ -112,6 +106,32 @@ async function doSetup(projectId, webAppName, serviceAccount) {
112
106
  (0, utils_1.logSuccess)(`Your backend is now deployed at:\n\thttps://${backend.uri}`);
113
107
  }
114
108
  exports.doSetup = doSetup;
109
+ async function doSetupSourceDeploy(projectId, backendId) {
110
+ const location = await promptLocation(projectId, "Select a primary region to host your backend:\n");
111
+ const webApp = await app_1.webApps.getOrCreateWebApp(projectId, null, backendId);
112
+ if (!webApp) {
113
+ (0, utils_1.logWarning)(`Firebase web app not set`);
114
+ }
115
+ const createBackendSpinner = ora("Creating your new backend...").start();
116
+ const backend = await createBackend(projectId, location, backendId, null, undefined, webApp === null || webApp === void 0 ? void 0 : webApp.id);
117
+ createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
118
+ return {
119
+ backend,
120
+ location,
121
+ };
122
+ }
123
+ exports.doSetupSourceDeploy = doSetupSourceDeploy;
124
+ async function ensureRequiredApisEnabled(projectId) {
125
+ await Promise.all([
126
+ (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.developerConnectOrigin)(), "apphosting", true),
127
+ (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudbuildOrigin)(), "apphosting", true),
128
+ (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.secretManagerOrigin)(), "apphosting", true),
129
+ (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudRunApiOrigin)(), "apphosting", true),
130
+ (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.artifactRegistryDomain)(), "apphosting", true),
131
+ (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.iamOrigin)(), "apphosting", true),
132
+ ]);
133
+ }
134
+ exports.ensureRequiredApisEnabled = ensureRequiredApisEnabled;
115
135
  async function createGitRepoLink(projectId, location, connectionId) {
116
136
  await Promise.all([
117
137
  (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.developerConnectOrigin)(), "apphosting", true),
@@ -130,7 +150,7 @@ async function createGitRepoLink(projectId, location, connectionId) {
130
150
  await githubConnections.linkGitHubRepository(projectId, location, connectionId);
131
151
  }
132
152
  exports.createGitRepoLink = createGitRepoLink;
133
- async function ensureAppHostingComputeServiceAccount(projectId, serviceAccount) {
153
+ async function ensureAppHostingComputeServiceAccount(projectId, serviceAccount, deployFromSource = false) {
134
154
  const sa = serviceAccount || defaultComputeServiceAccountEmail(projectId);
135
155
  const name = `projects/${projectId}/serviceAccounts/${sa}`;
136
156
  try {
@@ -147,6 +167,14 @@ async function ensureAppHostingComputeServiceAccount(projectId, serviceAccount)
147
167
  throw new error_1.FirebaseError(`Failed to create backend due to missing delegation permissions for ${sa}. Make sure you have the iam.serviceAccounts.actAs permission.`, { original: err });
148
168
  }
149
169
  }
170
+ if (deployFromSource) {
171
+ const policy = await (0, resourceManager_1.getIamPolicy)(projectId);
172
+ const objectViewerBinding = policy.bindings.find((binding) => binding.role === "roles/storage.objectViewer");
173
+ if (!objectViewerBinding ||
174
+ !objectViewerBinding.members.includes(`serviceAccount:${defaultComputeServiceAccountEmail(projectId)}`)) {
175
+ await (0, resourceManager_1.addServiceAccountToRoles)(projectId, defaultComputeServiceAccountEmail(projectId), ["roles/storage.objectViewer"], true);
176
+ }
177
+ }
150
178
  }
151
179
  exports.ensureAppHostingComputeServiceAccount = ensureAppHostingComputeServiceAccount;
152
180
  async function promptNewBackendId(projectId, location) {
@@ -168,17 +196,20 @@ async function promptNewBackendId(projectId, location) {
168
196
  (0, utils_1.logWarning)(`Backend with id ${backendId} already exists in ${location}`);
169
197
  }
170
198
  }
199
+ exports.promptNewBackendId = promptNewBackendId;
171
200
  function defaultComputeServiceAccountEmail(projectId) {
172
201
  return `${DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME}@${projectId}.iam.gserviceaccount.com`;
173
202
  }
174
- async function createBackend(projectId, location, backendId, repository, serviceAccount, webAppId, rootDir = "/") {
203
+ async function createBackend(projectId, location, backendId, serviceAccount, repository, webAppId, rootDir = "/") {
175
204
  const defaultServiceAccount = defaultComputeServiceAccountEmail(projectId);
176
205
  const backendReqBody = {
177
206
  servingLocality: "GLOBAL_ACCESS",
178
- codebase: {
179
- repository: `${repository.name}`,
180
- rootDirectory: rootDir,
181
- },
207
+ codebase: repository
208
+ ? {
209
+ repository: `${repository.name}`,
210
+ rootDirectory: rootDir,
211
+ }
212
+ : undefined,
182
213
  labels: deploymentTool.labels(),
183
214
  serviceAccount: serviceAccount || defaultServiceAccount,
184
215
  appId: webAppId,
@@ -203,6 +234,7 @@ async function provisionDefaultComputeServiceAccount(projectId) {
203
234
  "roles/firebaseapphosting.computeRunner",
204
235
  "roles/firebase.sdkAdminServiceAgent",
205
236
  "roles/developerconnect.readTokenAccessor",
237
+ "roles/storage.objectViewer",
206
238
  ], true);
207
239
  }
208
240
  async function setDefaultTrafficPolicy(projectId, location, backendId, codebaseBranch) {
@@ -225,11 +257,11 @@ async function promptLocation(projectId, prompt = "Please select a location:") {
225
257
  if (allowedLocations.length === 1) {
226
258
  return allowedLocations[0];
227
259
  }
228
- const location = (await (0, prompt_1.select)({
260
+ const location = await (0, prompt_1.select)({
229
261
  default: constants_1.DEFAULT_LOCATION,
230
262
  message: prompt,
231
263
  choices: allowedLocations,
232
- }));
264
+ });
233
265
  (0, utils_1.logSuccess)(`Location set to ${location}.\n`);
234
266
  return location;
235
267
  }
@@ -245,6 +277,28 @@ async function getBackendForLocation(projectId, location, backendId) {
245
277
  }
246
278
  }
247
279
  exports.getBackendForLocation = getBackendForLocation;
280
+ async function promptExistingBackend(projectId, promptMessage) {
281
+ const { backends } = await apphosting.listBackends(projectId, "-");
282
+ const backendId = await (0, prompt_1.search)({
283
+ message: promptMessage,
284
+ source: (input = "") => {
285
+ return new Promise((resolve) => resolve([
286
+ ...fuzzy
287
+ .filter(input, backends, {
288
+ extract: (backend) => apphosting.parseBackendName(backend.name).id,
289
+ })
290
+ .map((result) => {
291
+ return {
292
+ name: apphosting.parseBackendName(result.original.name).id,
293
+ value: apphosting.parseBackendName(result.original.name).id,
294
+ };
295
+ }),
296
+ ]));
297
+ },
298
+ });
299
+ return backendId;
300
+ }
301
+ exports.promptExistingBackend = promptExistingBackend;
248
302
  async function chooseBackends(projectId, backendId, chooseBackendPrompt, force) {
249
303
  let { unreachable, backends } = await apphosting.listBackends(projectId, "-");
250
304
  if (unreachable && unreachable.length !== 0) {
@@ -19,8 +19,8 @@ const apphostingPollerOptions = {
19
19
  const GIT_COMMIT_SHA_REGEX = /^(?:[0-9a-f]{40}|[0-9a-f]{7})$/;
20
20
  async function createRollout(backendId, projectId, branch, commit, force) {
21
21
  const backend = await (0, backend_1.getBackend)(projectId, backendId);
22
- if (!backend.codebase.repository) {
23
- throw new error_1.FirebaseError(`Backend ${backendId} is misconfigured due to missing a connected repository. You can delete and recreate your backend using 'firebase apphosting:backends:delete' and 'firebase apphosting:backends:create'.`);
22
+ if (!backend.codebase || !backend.codebase.repository) {
23
+ throw new error_1.FirebaseError(`Backend ${backendId} is missing a connected repository. If you would like to deploy from a branch or commit of a GitHub repository, you can connect one through the Firebase Console. If you would like to deploy from local source, run 'firebase deploy'.`);
24
24
  }
25
25
  const { location } = apphosting.parseBackendName(backend.name);
26
26
  const { repoLink, owner, repo, readToken } = await (0, devConnect_1.getRepoDetailsFromBackend)(projectId, location, backend.codebase.repository);
@@ -9,11 +9,11 @@ const prompt = require("../../prompt");
9
9
  const utils = require("../../utils");
10
10
  const logger_1 = require("../../logger");
11
11
  const env = require("../../functions/env");
12
- function toMetadata(projectNumber, backends) {
12
+ async function toMetadata(projectNumber, backends) {
13
13
  const metadata = [];
14
14
  for (const backend of backends) {
15
15
  const [, , , location, , id] = backend.name.split("/");
16
- metadata.push(Object.assign({ location, id }, (0, _1.serviceAccountsForBackend)(projectNumber, backend)));
16
+ metadata.push(Object.assign({ location, id }, (await (0, _1.serviceAccountsForBackend)(projectNumber, backend))));
17
17
  }
18
18
  return metadata.sort((left, right) => {
19
19
  const cmplocation = left.location.localeCompare(right.location);
@@ -86,12 +86,12 @@ async function selectBackendServiceAccounts(projectNumber, projectId, options) {
86
86
  message: "To use this secret, your backend's service account must be granted access. Would you like to grant access now?",
87
87
  });
88
88
  if (grant) {
89
- return (0, _1.toMulti)((0, _1.serviceAccountsForBackend)(projectNumber, listBackends.backends[0]));
89
+ return (0, _1.toMulti)(await (0, _1.serviceAccountsForBackend)(projectNumber, listBackends.backends[0]));
90
90
  }
91
91
  utils.logBullet(exports.GRANT_ACCESS_IN_FUTURE);
92
92
  return { buildServiceAccounts: [], runServiceAccounts: [] };
93
93
  }
94
- const metadata = toMetadata(projectNumber, listBackends.backends);
94
+ const metadata = await toMetadata(projectNumber, listBackends.backends);
95
95
  if (metadata.every(matchesServiceAccounts(metadata[0]))) {
96
96
  utils.logBullet("To use this secret, your backend's service account must be granted access.");
97
97
  utils.logBullet("All of your backends share the following " +
@@ -21,7 +21,7 @@ function toMulti(accounts) {
21
21
  return m;
22
22
  }
23
23
  exports.toMulti = toMulti;
24
- function serviceAccountsForBackend(projectNumber, backend) {
24
+ async function serviceAccountsForBackend(projectNumber, backend) {
25
25
  if (backend.serviceAccount) {
26
26
  return {
27
27
  buildServiceAccount: backend.serviceAccount,
@@ -30,7 +30,7 @@ function serviceAccountsForBackend(projectNumber, backend) {
30
30
  }
31
31
  return {
32
32
  buildServiceAccount: gcb.getDefaultServiceAccount(projectNumber),
33
- runServiceAccount: gce.getDefaultServiceAccount(projectNumber),
33
+ runServiceAccount: await gce.getDefaultServiceAccount(projectNumber),
34
34
  };
35
35
  }
36
36
  exports.serviceAccountsForBackend = serviceAccountsForBackend;
package/lib/auth.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addAdditionalAccount = exports.logout = exports.getAccessToken = exports.haveValidTokens = exports.isExpired = exports.loggedIn = exports.findAccountByEmail = exports.loginGithub = exports.loginGoogle = exports.setGlobalDefaultAccount = exports.setProjectAccount = exports.loginAdditionalAccount = exports.selectAccount = exports.setRefreshToken = exports.setActiveAccount = exports.getAllAccounts = exports.getAdditionalAccounts = exports.getProjectDefaultAccount = exports.getGlobalDefaultAccount = void 0;
3
+ exports.addAdditionalAccount = exports.logout = exports.getAccessToken = exports.haveValidTokens = exports.isExpired = exports.loggedIn = exports.findAccountByEmail = exports.loginGithub = exports.loginGoogle = exports.setGlobalDefaultAccount = exports.setProjectAccount = exports.loginAdditionalAccount = exports.selectAccount = exports.setRefreshToken = exports.setActiveAccount = exports.assertAccount = exports.getAllAccounts = exports.getAdditionalAccounts = exports.getProjectDefaultAccount = exports.getGlobalDefaultAccount = void 0;
4
4
  const clc = require("colorette");
5
5
  const FormData = require("form-data");
6
6
  const http = require("http");
@@ -62,6 +62,16 @@ function getAllAccounts() {
62
62
  return res;
63
63
  }
64
64
  exports.getAllAccounts = getAllAccounts;
65
+ function assertAccount(email, options) {
66
+ const allAccounts = getAllAccounts();
67
+ const accountExists = allAccounts.some((a) => a.user.email === email);
68
+ if (!accountExists) {
69
+ throw new error_1.FirebaseError(`Account ${email} does not exist, ${(options === null || options === void 0 ? void 0 : options.mcp)
70
+ ? `use the 'firebase_get_environment' tool to see available accounts or instruct the user to use the 'firebase login:add' terminal command to add a new account.`
71
+ : `run "${clc.bold("firebase login:list")} to see valid accounts`}`);
72
+ }
73
+ }
74
+ exports.assertAccount = assertAccount;
65
75
  function setActiveAccount(options, account) {
66
76
  if (account.tokens.refresh_token) {
67
77
  setRefreshToken(account.tokens.refresh_token);
@@ -121,7 +131,17 @@ function setProjectAccount(projectDir, email) {
121
131
  configstore_1.configstore.set("activeAccounts", activeAccounts);
122
132
  }
123
133
  exports.setProjectAccount = setProjectAccount;
124
- function setGlobalDefaultAccount(account) {
134
+ function setGlobalDefaultAccount(accountOrEmail) {
135
+ let account;
136
+ if (typeof accountOrEmail === "string") {
137
+ const accountFromEmail = getAllAccounts().find((acc) => acc.user.email === accountOrEmail);
138
+ if (!accountFromEmail)
139
+ throw new error_1.FirebaseError(`Account '${accountOrEmail}' is not a signed-in user on this device.`);
140
+ account = accountFromEmail;
141
+ }
142
+ else {
143
+ account = accountOrEmail;
144
+ }
125
145
  configstore_1.configstore.set("user", account.user);
126
146
  configstore_1.configstore.set("tokens", account.tokens);
127
147
  const additionalAccounts = getAdditionalAccounts();
package/lib/bin/cli.js CHANGED
@@ -6,9 +6,6 @@ const clc = require("colorette");
6
6
  const marked_terminal_1 = require("marked-terminal");
7
7
  const marked_1 = require("marked");
8
8
  marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
9
- const node_path_1 = require("node:path");
10
- const triple_beam_1 = require("triple-beam");
11
- const node_util_1 = require("node:util");
12
9
  const fs = require("node:fs");
13
10
  const configstore_1 = require("../configstore");
14
11
  const errorOut_1 = require("../errorOut");
@@ -17,46 +14,17 @@ const logger_1 = require("../logger");
17
14
  const client = require("..");
18
15
  const fsutils = require("../fsutils");
19
16
  const utils = require("../utils");
20
- const winston = require("winston");
21
17
  const experiments_1 = require("../experiments");
22
18
  const fetchMOTD_1 = require("../fetchMOTD");
23
19
  function cli(pkg) {
24
20
  const updateNotifier = updateNotifierPkg({ pkg });
25
21
  const args = process.argv.slice(2);
26
22
  let cmd;
27
- function findAvailableLogFile() {
28
- const candidates = ["firebase-debug.log"];
29
- for (let i = 1; i < 10; i++) {
30
- candidates.push(`firebase-debug.${i}.log`);
31
- }
32
- for (const c of candidates) {
33
- const logFilename = (0, node_path_1.join)(process.cwd(), c);
34
- try {
35
- const fd = fs.openSync(logFilename, "r+");
36
- fs.closeSync(fd);
37
- return logFilename;
38
- }
39
- catch (e) {
40
- if (e.code === "ENOENT") {
41
- return logFilename;
42
- }
43
- }
44
- }
45
- throw new Error("Unable to obtain permissions for firebase-debug.log");
46
- }
47
- const logFilename = findAvailableLogFile();
48
23
  if (!process.env.DEBUG && args.includes("--debug")) {
49
24
  process.env.DEBUG = "true";
50
25
  }
51
26
  process.env.IS_FIREBASE_CLI = "true";
52
- logger_1.logger.add(new winston.transports.File({
53
- level: "debug",
54
- filename: logFilename,
55
- format: winston.format.printf((info) => {
56
- const segments = [info.message, ...(info[triple_beam_1.SPLAT] || [])].map(utils.tryStringify);
57
- return `[${info.level}] ${(0, node_util_1.stripVTControlCharacters)(segments.join(" "))}`;
58
- }),
59
- }));
27
+ const logFilename = (0, logger_1.useFileLogger)();
60
28
  logger_1.logger.debug("-".repeat(70));
61
29
  logger_1.logger.debug("Command: ", process.argv.join(" "));
62
30
  logger_1.logger.debug("CLI Version: ", pkg.version);
package/lib/bin/mcp.js CHANGED
@@ -3,10 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.mcp = void 0;
5
5
  const logger_1 = require("../logger");
6
- (0, logger_1.silenceStdout)();
7
6
  const index_1 = require("../mcp/index");
8
7
  const util_1 = require("util");
9
8
  const types_1 = require("../mcp/types");
9
+ const index_js_1 = require("../mcp/tools/index.js");
10
+ const path_1 = require("path");
10
11
  const STARTUP_MESSAGE = `
11
12
  This is a running process of the Firebase MCP server. This command should only be executed by an MCP client. An example MCP client configuration might be:
12
13
 
@@ -24,13 +25,22 @@ async function mcp() {
24
25
  options: {
25
26
  only: { type: "string", default: "" },
26
27
  dir: { type: "string" },
28
+ "generate-tool-list": { type: "boolean", default: false },
27
29
  },
28
30
  allowPositionals: true,
29
31
  });
32
+ if (values["generate-tool-list"]) {
33
+ console.log((0, index_js_1.markdownDocsOfTools)());
34
+ return;
35
+ }
36
+ (0, logger_1.useFileLogger)();
30
37
  const activeFeatures = (values.only || "")
31
38
  .split(",")
32
39
  .filter((f) => types_1.SERVER_FEATURES.includes(f));
33
- const server = new index_1.FirebaseMcpServer({ activeFeatures, projectRoot: values.dir });
40
+ const server = new index_1.FirebaseMcpServer({
41
+ activeFeatures,
42
+ projectRoot: values.dir ? (0, path_1.resolve)(values.dir) : undefined,
43
+ });
34
44
  await server.start();
35
45
  if (process.stdin.isTTY)
36
46
  process.stderr.write(STARTUP_MESSAGE);
@@ -26,6 +26,7 @@ const FILTERABLE_TARGETS = new Set([
26
26
  "storage",
27
27
  "database",
28
28
  "dataconnect",
29
+ "apphosting",
29
30
  ]);
30
31
  async function checkValidTargetFilters(options) {
31
32
  const only = !options.only ? [] : options.only.split(",");
package/lib/command.js CHANGED
@@ -13,6 +13,7 @@ const track_1 = require("./track");
13
13
  const auth_1 = require("./auth");
14
14
  const projects_1 = require("./management/projects");
15
15
  const requireAuth_1 = require("./requireAuth");
16
+ const logger_1 = require("./logger");
16
17
  class Command {
17
18
  constructor(cmd) {
18
19
  this.cmd = cmd;
@@ -163,8 +164,8 @@ class Command {
163
164
  if ((0, utils_1.getInheritedOption)(options, "json")) {
164
165
  options.nonInteractive = true;
165
166
  }
166
- else {
167
- (0, utils_1.setupLoggers)();
167
+ else if (!options.isMCP) {
168
+ (0, logger_1.useConsoleLoggers)();
168
169
  }
169
170
  if ((0, utils_1.getInheritedOption)(options, "config")) {
170
171
  options.configPath = (0, utils_1.getInheritedOption)(options, "config");
@@ -54,6 +54,6 @@ exports.command = new command_1.Command("apphosting:secrets:grantaccess <secretN
54
54
  else {
55
55
  backend = await apphosting.getBackend(projectId, location, backendId);
56
56
  }
57
- const accounts = secrets.toMulti(secrets.serviceAccountsForBackend(projectNumber, backend));
57
+ const accounts = secrets.toMulti(await secrets.serviceAccountsForBackend(projectNumber, backend));
58
58
  await Promise.allSettled(secretList.map((secretName) => secrets.grantSecretAccess(projectId, projectNumber, secretName, accounts)));
59
59
  });
@@ -24,6 +24,7 @@ exports.VALID_DEPLOY_TARGETS = [
24
24
  "remoteconfig",
25
25
  "extensions",
26
26
  "dataconnect",
27
+ "apphosting",
27
28
  ];
28
29
  exports.TARGET_PERMISSIONS = {
29
30
  database: ["firebasedatabase.instances.update"],
@@ -40,7 +40,7 @@ let choices = [
40
40
  },
41
41
  {
42
42
  value: "apphosting",
43
- name: "App Hosting: Configure an apphosting.yaml file for App Hosting",
43
+ name: "App Hosting: Enable web app deployments with App Hosting",
44
44
  checked: false,
45
45
  hidden: false,
46
46
  },
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
- const clc = require("colorette");
5
4
  const command_1 = require("../command");
6
5
  const utils = require("../utils");
7
6
  const auth = require("../auth");
@@ -9,11 +8,7 @@ const error_1 = require("../error");
9
8
  exports.command = new command_1.Command("login:use <email>")
10
9
  .description("set the default account to use for this project directory or the global default account if not in a Firebase project directory")
11
10
  .action((email, options) => {
12
- const allAccounts = auth.getAllAccounts();
13
- const accountExists = allAccounts.some((a) => a.user.email === email);
14
- if (!accountExists) {
15
- throw new error_1.FirebaseError(`Account ${email} does not exist, run "${clc.bold("firebase login:list")}" to see valid accounts`);
16
- }
11
+ auth.assertAccount(email);
17
12
  const projectDir = options.projectRoot;
18
13
  if (projectDir) {
19
14
  if (options.user.email === email) {
@@ -27,15 +22,11 @@ exports.command = new command_1.Command("login:use <email>")
27
22
  if (options.user.email === email) {
28
23
  throw new error_1.FirebaseError(`Already using account ${email} for the global default account.`);
29
24
  }
30
- const newDefaultAccount = allAccounts.find((a) => a.user.email === email);
31
- if (!newDefaultAccount) {
32
- throw new error_1.FirebaseError(`Account ${email} does not exist, run "${clc.bold("firebase login:list")}" to see valid accounts`);
33
- }
34
25
  const oldDefaultAccount = auth.getGlobalDefaultAccount();
35
26
  if (!oldDefaultAccount) {
36
27
  throw new error_1.FirebaseError("Could not determine global default account");
37
28
  }
38
- auth.setGlobalDefaultAccount(newDefaultAccount);
29
+ auth.setGlobalDefaultAccount(email);
39
30
  auth.addAdditionalAccount(oldDefaultAccount);
40
31
  utils.logSuccess(`Set global default account to ${email}.`);
41
32
  return email;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.command = void 0;
3
+ exports.command = exports.setNewActive = void 0;
4
4
  const clc = require("colorette");
5
5
  const command_1 = require("../command");
6
6
  const projects_1 = require("../management/projects");
@@ -30,10 +30,10 @@ function listAliases(options) {
30
30
  function verifyMessage(name) {
31
31
  return "please verify project " + clc.bold(name) + " exists and you have access.";
32
32
  }
33
- async function setNewActive(projectOrAlias, aliasOpt, options) {
33
+ async function setNewActive(projectOrAlias, aliasOpt, rc, projectRoot) {
34
34
  let project;
35
- const hasAlias = options.rc.hasProjectAlias(projectOrAlias);
36
- const resolvedProject = options.rc.resolveAlias(projectOrAlias);
35
+ const hasAlias = rc.hasProjectAlias(projectOrAlias);
36
+ const resolvedProject = rc.resolveAlias(projectOrAlias);
37
37
  (0, command_2.validateProjectId)(resolvedProject);
38
38
  try {
39
39
  project = await (0, projects_1.getProject)(resolvedProject);
@@ -45,24 +45,25 @@ async function setNewActive(projectOrAlias, aliasOpt, options) {
45
45
  if (!project) {
46
46
  throw new error_1.FirebaseError(`Cannot create alias ${clc.bold(aliasOpt)}, ${verifyMessage(projectOrAlias)}`);
47
47
  }
48
- options.rc.addProjectAlias(aliasOpt, projectOrAlias);
48
+ rc.addProjectAlias(aliasOpt, projectOrAlias);
49
49
  logger_1.logger.info("Created alias", clc.bold(aliasOpt), "for", resolvedProject + ".");
50
50
  }
51
51
  if (hasAlias) {
52
52
  if (!project) {
53
53
  throw new error_1.FirebaseError(`Unable to use alias ${clc.bold(projectOrAlias)}, ${verifyMessage(resolvedProject)}`);
54
54
  }
55
- utils.makeActiveProject(options.projectRoot, projectOrAlias);
55
+ utils.makeActiveProject(projectRoot, projectOrAlias);
56
56
  logger_1.logger.info("Now using alias", clc.bold(projectOrAlias), "(" + resolvedProject + ")");
57
57
  }
58
58
  else if (project) {
59
- utils.makeActiveProject(options.projectRoot, projectOrAlias);
59
+ utils.makeActiveProject(projectRoot, projectOrAlias);
60
60
  logger_1.logger.info("Now using project", clc.bold(projectOrAlias));
61
61
  }
62
62
  else {
63
63
  throw new error_1.FirebaseError(`Invalid project selection, ${verifyMessage(projectOrAlias)}`);
64
64
  }
65
65
  }
66
+ exports.setNewActive = setNewActive;
66
67
  function unalias(alias, options) {
67
68
  if (options.rc.hasProjectAlias(alias)) {
68
69
  options.rc.removeProjectAlias(alias);
@@ -150,7 +151,7 @@ exports.command = new command_1.Command("use [alias_or_project_id]")
150
151
  " to start a project directory in the current folder.");
151
152
  }
152
153
  if (newActive) {
153
- return setNewActive(newActive, aliasOpt, options);
154
+ return setNewActive(newActive, aliasOpt, options.rc, options.projectRoot);
154
155
  }
155
156
  if (options.unalias) {
156
157
  return unalias(options.unalias, options);
package/lib/config.js CHANGED
@@ -239,4 +239,5 @@ Config.MATERIALIZE_TARGETS = [
239
239
  "storage",
240
240
  "remoteconfig",
241
241
  "dataconnect",
242
+ "apphosting",
242
243
  ];
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listTopIssues = void 0;
4
+ const apiv2_1 = require("../apiv2");
5
+ const logger_1 = require("../logger");
6
+ const error_1 = require("../error");
7
+ const api_1 = require("../api");
8
+ const TIMEOUT = 10000;
9
+ const apiClient = new apiv2_1.Client({
10
+ urlPrefix: (0, api_1.crashlyticsApiOrigin)(),
11
+ apiVersion: "v1alpha",
12
+ });
13
+ async function listTopIssues(projectId, appId, issueCount) {
14
+ try {
15
+ const queryParams = new URLSearchParams();
16
+ queryParams.set("page_size", `${issueCount}`);
17
+ const requestProjectId = parseProjectId(appId);
18
+ if (requestProjectId === undefined) {
19
+ throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
20
+ }
21
+ const response = await apiClient.request({
22
+ method: "GET",
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ },
26
+ path: `/projects/${requestProjectId}/apps/${appId}/reports/topIssues`,
27
+ queryParams: queryParams,
28
+ timeout: TIMEOUT,
29
+ });
30
+ return response.body;
31
+ }
32
+ catch (err) {
33
+ logger_1.logger.debug(err.message);
34
+ throw new error_1.FirebaseError(`Failed to fetch the top issues for the Firebase Project ${projectId}, AppId ${appId}. Error: ${err}.`, { original: err });
35
+ }
36
+ }
37
+ exports.listTopIssues = listTopIssues;
38
+ function parseProjectId(appId) {
39
+ const appIdParts = appId.split(":");
40
+ if (appIdParts.length > 1) {
41
+ return appIdParts[1];
42
+ }
43
+ return undefined;
44
+ }