firebase-tools 14.14.0 → 14.15.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 (67) hide show
  1. package/lib/apphosting/backend.js +40 -15
  2. package/lib/auth.js +37 -2
  3. package/lib/commands/apphosting-backends-create.js +8 -5
  4. package/lib/commands/dataconnect-sdk-generate.js +3 -5
  5. package/lib/commands/dataconnect-sql-diff.js +2 -2
  6. package/lib/commands/dataconnect-sql-grant.js +2 -2
  7. package/lib/commands/dataconnect-sql-migrate.js +2 -2
  8. package/lib/commands/dataconnect-sql-setup.js +2 -2
  9. package/lib/commands/dataconnect-sql-shell.js +2 -2
  10. package/lib/commands/init.js +3 -0
  11. package/lib/commands/login.js +12 -7
  12. package/lib/crashlytics/addNote.js +27 -0
  13. package/lib/crashlytics/deleteNote.js +23 -0
  14. package/lib/crashlytics/getIssueDetails.js +5 -20
  15. package/lib/crashlytics/getSampleCrash.js +6 -20
  16. package/lib/crashlytics/listNotes.js +29 -0
  17. package/lib/crashlytics/listTopDevices.js +33 -0
  18. package/lib/crashlytics/listTopIssues.js +8 -23
  19. package/lib/crashlytics/listTopOperatingSystems.js +32 -0
  20. package/lib/crashlytics/listTopVersions.js +32 -0
  21. package/lib/crashlytics/updateIssue.js +35 -0
  22. package/lib/crashlytics/utils.js +38 -0
  23. package/lib/dataconnect/appFinder.js +103 -0
  24. package/lib/dataconnect/load.js +105 -6
  25. package/lib/deploy/dataconnect/prepare.js +1 -3
  26. package/lib/emulator/controller.js +2 -2
  27. package/lib/emulator/downloadableEmulatorInfo.json +17 -17
  28. package/lib/init/features/dataconnect/create_app.js +48 -0
  29. package/lib/init/features/dataconnect/index.js +19 -30
  30. package/lib/init/features/dataconnect/sdk.js +218 -161
  31. package/lib/init/features/index.js +3 -2
  32. package/lib/init/index.js +5 -1
  33. package/lib/management/apps.js +3 -3
  34. package/lib/mcp/prompts/core/deploy.js +51 -8
  35. package/lib/mcp/prompts/crashlytics/common.js +10 -0
  36. package/lib/mcp/prompts/crashlytics/fix_issue.js +89 -0
  37. package/lib/mcp/prompts/crashlytics/index.js +6 -0
  38. package/lib/mcp/prompts/crashlytics/prioritize_issues.js +79 -0
  39. package/lib/mcp/prompts/index.js +3 -2
  40. package/lib/mcp/tools/core/init.js +5 -1
  41. package/lib/mcp/tools/crashlytics/add_note.js +32 -0
  42. package/lib/mcp/tools/crashlytics/constants.js +11 -0
  43. package/lib/mcp/tools/crashlytics/delete_note.js +35 -0
  44. package/lib/mcp/tools/crashlytics/get_issue_details.js +2 -4
  45. package/lib/mcp/tools/crashlytics/get_sample_crash.js +4 -4
  46. package/lib/mcp/tools/crashlytics/index.js +16 -2
  47. package/lib/mcp/tools/crashlytics/list_notes.js +37 -0
  48. package/lib/mcp/tools/crashlytics/list_top_devices.js +33 -0
  49. package/lib/mcp/tools/crashlytics/list_top_issues.js +5 -7
  50. package/lib/mcp/tools/crashlytics/list_top_operating_systems.js +33 -0
  51. package/lib/mcp/tools/crashlytics/list_top_versions.js +33 -0
  52. package/lib/mcp/tools/crashlytics/update_issue.js +37 -0
  53. package/lib/mcp/tools/dataconnect/execute_graphql.js +2 -2
  54. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +2 -2
  55. package/lib/mcp/tools/dataconnect/execute_mutation.js +2 -2
  56. package/lib/mcp/tools/dataconnect/execute_query.js +2 -2
  57. package/lib/mcp/tools/dataconnect/generate_operation.js +2 -2
  58. package/lib/mcp/tools/dataconnect/get_connector.js +2 -2
  59. package/lib/mcp/tools/dataconnect/get_schema.js +2 -2
  60. package/lib/utils.js +11 -1
  61. package/package.json +1 -1
  62. package/templates/init/dataconnect/connector.yaml +0 -16
  63. package/templates/init/dataconnect/dataconnect.yaml +2 -1
  64. package/templates/init/dataconnect/mutations.gql +29 -29
  65. package/templates/init/dataconnect/queries.gql +73 -73
  66. package/templates/init/dataconnect/schema.gql +48 -48
  67. package/lib/dataconnect/fileUtils.js +0 -168
@@ -51,27 +51,52 @@ async function awaitTlsReady(url) {
51
51
  }
52
52
  } while (!ready);
53
53
  }
54
- async function doSetup(projectId, webAppName, serviceAccount) {
54
+ async function doSetup(projectId, nonInteractive, webAppName, backendId, serviceAccount, primaryRegion, rootDir) {
55
55
  await ensureRequiredApisEnabled(projectId);
56
- await ensureAppHostingComputeServiceAccount(projectId, serviceAccount);
57
- const location = await promptLocation(projectId, "Select a primary region to host your backend:\n");
58
- const gitRepositoryLink = await githubConnections.linkGitHubRepository(projectId, location);
59
- const rootDir = await (0, prompt_1.input)({
60
- default: "/",
61
- message: "Specify your app's root directory relative to your repository",
62
- });
63
- const branch = await githubConnections.promptGitHubBranch(gitRepositoryLink);
64
- (0, utils_1.logSuccess)(`Repo linked successfully!\n`);
65
- (0, utils_1.logBullet)(`${clc.yellow("===")} Set up your backend`);
66
- const backendId = await promptNewBackendId(projectId, location);
67
- (0, utils_1.logSuccess)(`Name set to ${backendId}\n`);
68
- const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName, backendId);
56
+ await ensureAppHostingComputeServiceAccount(projectId, serviceAccount ? serviceAccount : null);
57
+ let location = primaryRegion;
58
+ let gitRepositoryLink;
59
+ let branch;
60
+ if (nonInteractive) {
61
+ if (!backendId || !primaryRegion) {
62
+ throw new error_1.FirebaseError("nonInteractive mode requires a backendId and primaryRegion");
63
+ }
64
+ }
65
+ else {
66
+ if (!location) {
67
+ location = await promptLocation(projectId, "Select a primary region to host your backend:\n");
68
+ }
69
+ if (!backendId) {
70
+ (0, utils_1.logBullet)(`${clc.yellow("===")} Set up your backend`);
71
+ backendId = await promptNewBackendId(projectId, location);
72
+ (0, utils_1.logSuccess)(`Name set to ${backendId}\n`);
73
+ }
74
+ if (!rootDir) {
75
+ rootDir = await (0, prompt_1.input)({
76
+ default: "/",
77
+ message: "Specify your app's root directory relative to your repository",
78
+ });
79
+ }
80
+ gitRepositoryLink = await githubConnections.linkGitHubRepository(projectId, location);
81
+ branch = await githubConnections.promptGitHubBranch(gitRepositoryLink);
82
+ (0, utils_1.logSuccess)(`Repo linked successfully!\n`);
83
+ }
84
+ if (!location || !backendId) {
85
+ throw new error_1.FirebaseError("Internal error: location or backendId is not defined.");
86
+ }
87
+ const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName ? webAppName : null, backendId);
69
88
  if (!webApp) {
70
89
  (0, utils_1.logWarning)(`Firebase web app not set`);
71
90
  }
72
91
  const createBackendSpinner = ora("Creating your new backend...").start();
73
- const backend = await createBackend(projectId, location, backendId, serviceAccount, gitRepositoryLink, webApp === null || webApp === void 0 ? void 0 : webApp.id, rootDir);
92
+ const backend = await createBackend(projectId, location, backendId, serviceAccount ? serviceAccount : null, gitRepositoryLink, webApp === null || webApp === void 0 ? void 0 : webApp.id, rootDir);
74
93
  createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
94
+ if (nonInteractive) {
95
+ return;
96
+ }
97
+ if (!branch) {
98
+ throw new error_1.FirebaseError("Branch was not set while connecting to a github repo.");
99
+ }
75
100
  await setDefaultTrafficPolicy(projectId, location, backendId, branch);
76
101
  const confirmRollout = await (0, prompt_1.confirm)({
77
102
  default: true,
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.assertAccount = 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.recordCredentials = exports.loginPrototyper = 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");
@@ -293,7 +293,7 @@ function respondHtml(req, res, statusCode, html) {
293
293
  function urlsafeBase64(base64string) {
294
294
  return base64string.replace(/\+/g, "-").replace(/=+$/, "").replace(/\//g, "_");
295
295
  }
296
- async function loginRemotely() {
296
+ async function loginPrototyper() {
297
297
  var _a;
298
298
  const authProxyClient = new apiv2.Client({
299
299
  urlPrefix: (0, api_1.authProxyOrigin)(),
@@ -305,6 +305,41 @@ async function loginRemotely() {
305
305
  const attestToken = (_a = (await authProxyClient.post("/attest", {
306
306
  session_id: sessionId,
307
307
  })).body) === null || _a === void 0 ? void 0 : _a.token;
308
+ const loginUrl = `${(0, api_1.authProxyOrigin)()}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}&studio_prototyper=true}`;
309
+ return {
310
+ uri: loginUrl,
311
+ sessionId: sessionId.substring(0, 5).toUpperCase(),
312
+ authorize: async (code) => {
313
+ const tokens = await getTokensFromAuthorizationCode(code, `${(0, api_1.authProxyOrigin)()}/complete`, codeVerifier);
314
+ const creds = {
315
+ user: jwt.decode(tokens.id_token, { json: true }),
316
+ tokens: tokens,
317
+ scopes: SCOPES,
318
+ };
319
+ recordCredentials(creds);
320
+ return creds;
321
+ },
322
+ };
323
+ }
324
+ exports.loginPrototyper = loginPrototyper;
325
+ function recordCredentials(creds) {
326
+ configstore_1.configstore.set("user", creds.user);
327
+ configstore_1.configstore.set("tokens", creds.tokens);
328
+ configstore_1.configstore.set("loginScopes", creds.scopes);
329
+ configstore_1.configstore.delete("session");
330
+ }
331
+ exports.recordCredentials = recordCredentials;
332
+ async function loginRemotely() {
333
+ const authProxyClient = new apiv2.Client({
334
+ urlPrefix: (0, api_1.authProxyOrigin)(),
335
+ auth: false,
336
+ });
337
+ const sessionId = (0, uuid_1.v4)();
338
+ const codeVerifier = (0, crypto_1.randomBytes)(32).toString("hex");
339
+ const codeChallenge = urlsafeBase64((0, crypto_1.createHash)("sha256").update(codeVerifier).digest("base64"));
340
+ const attestToken = (await authProxyClient.post("/attest", {
341
+ session_id: sessionId,
342
+ })).body.token;
308
343
  const loginUrl = `${(0, api_1.authProxyOrigin)()}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`;
309
344
  logger_1.logger.info();
310
345
  logger_1.logger.info("To sign in to the Firebase CLI:");
@@ -2,9 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const command_1 = require("../command");
5
+ const error_1 = require("../error");
5
6
  const projectUtils_1 = require("../projectUtils");
6
7
  const requireAuth_1 = require("../requireAuth");
7
- const requireInteractive_1 = require("../requireInteractive");
8
8
  const backend_1 = require("../apphosting/backend");
9
9
  const apphosting_1 = require("../gcp/apphosting");
10
10
  const firedata_1 = require("../gcp/firedata");
@@ -12,14 +12,17 @@ const requireTosAcceptance_1 = require("../requireTosAcceptance");
12
12
  exports.command = new command_1.Command("apphosting:backends:create")
13
13
  .description("create a Firebase App Hosting backend")
14
14
  .option("-a, --app <webAppId>", "specify an existing Firebase web app's ID to associate your App Hosting backend with")
15
+ .option("--backend <backend>", "specify the name of the new backend. Required with --non-interactive.")
15
16
  .option("-s, --service-account <serviceAccount>", "specify the service account used to run the server", "")
17
+ .option("--primary-region <primaryRegion>", "specify the primary region for the backend. Required with --non-interactive.")
18
+ .option("--root-dir <rootDir>", "specify the root directory for the backend.")
16
19
  .before(requireAuth_1.requireAuth)
17
20
  .before(apphosting_1.ensureApiEnabled)
18
- .before(requireInteractive_1.default)
19
21
  .before((0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.APPHOSTING_TOS_ID))
20
22
  .action(async (options) => {
21
23
  const projectId = (0, projectUtils_1.needProjectId)(options);
22
- const webAppId = options.app;
23
- const serviceAccount = options.serviceAccount;
24
- await (0, backend_1.doSetup)(projectId, webAppId, serviceAccount);
24
+ if (options.nonInteractive && (options.backend == null || options.primaryRegion == null)) {
25
+ throw new error_1.FirebaseError(`--non-interactive option requires --backend and --primary-region`);
26
+ }
27
+ await (0, backend_1.doSetup)(projectId, options.nonInteractive, options.app, options.backend, options.serviceAccount, options.primaryRegion, options.rootDir);
25
28
  });
@@ -6,7 +6,6 @@ const command_1 = require("../command");
6
6
  const dataconnectEmulator_1 = require("../emulator/dataconnectEmulator");
7
7
  const projectUtils_1 = require("../projectUtils");
8
8
  const load_1 = require("../dataconnect/load");
9
- const fileUtils_1 = require("../dataconnect/fileUtils");
10
9
  const logger_1 = require("../logger");
11
10
  const auth_1 = require("../auth");
12
11
  exports.command = new command_1.Command("dataconnect:sdk:generate")
@@ -14,10 +13,9 @@ exports.command = new command_1.Command("dataconnect:sdk:generate")
14
13
  .option("--watch", "watch for changes to your connector GQL files and regenerate your SDKs when updates occur")
15
14
  .action(async (options) => {
16
15
  const projectId = (0, projectUtils_1.needProjectId)(options);
17
- const services = (0, fileUtils_1.readFirebaseJson)(options.config);
18
- for (const service of services) {
19
- const configDir = service.source;
20
- const serviceInfo = await (0, load_1.load)(projectId, options.config, configDir);
16
+ const serviceInfos = await (0, load_1.loadAll)(projectId, options.config);
17
+ for (const serviceInfo of serviceInfos) {
18
+ const configDir = serviceInfo.sourceDirectory;
21
19
  const hasGeneratables = serviceInfo.connectorInfo.some((c) => {
22
20
  var _a, _b, _c, _d;
23
21
  return (((_a = c.connectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk) ||
@@ -5,7 +5,7 @@ const command_1 = require("../command");
5
5
  const projectUtils_1 = require("../projectUtils");
6
6
  const ensureApis_1 = require("../dataconnect/ensureApis");
7
7
  const requirePermissions_1 = require("../requirePermissions");
8
- const fileUtils_1 = require("../dataconnect/fileUtils");
8
+ const load_1 = require("../dataconnect/load");
9
9
  const schemaMigration_1 = require("../dataconnect/schemaMigration");
10
10
  const requireAuth_1 = require("../requireAuth");
11
11
  exports.command = new command_1.Command("dataconnect:sql:diff [serviceId]")
@@ -20,7 +20,7 @@ exports.command = new command_1.Command("dataconnect:sql:diff [serviceId]")
20
20
  var _a;
21
21
  const projectId = (0, projectUtils_1.needProjectId)(options);
22
22
  await (0, ensureApis_1.ensureApis)(projectId);
23
- const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
23
+ const serviceInfo = await (0, load_1.pickService)(projectId, options.config, serviceId);
24
24
  const diffs = await (0, schemaMigration_1.diffSchema)(options, serviceInfo.schema, (_a = serviceInfo.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation);
25
25
  return { projectId, serviceId, diffs };
26
26
  });
@@ -5,7 +5,7 @@ const command_1 = require("../command");
5
5
  const projectUtils_1 = require("../projectUtils");
6
6
  const ensureApis_1 = require("../dataconnect/ensureApis");
7
7
  const requirePermissions_1 = require("../requirePermissions");
8
- const fileUtils_1 = require("../dataconnect/fileUtils");
8
+ const load_1 = require("../dataconnect/load");
9
9
  const schemaMigration_1 = require("../dataconnect/schemaMigration");
10
10
  const requireAuth_1 = require("../requireAuth");
11
11
  const error_1 = require("../error");
@@ -36,7 +36,7 @@ exports.command = new command_1.Command("dataconnect:sql:grant [serviceId]")
36
36
  }
37
37
  const projectId = (0, projectUtils_1.needProjectId)(options);
38
38
  await (0, ensureApis_1.ensureApis)(projectId);
39
- const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
39
+ const serviceInfo = await (0, load_1.pickService)(projectId, options.config, serviceId);
40
40
  await (0, schemaMigration_1.grantRoleToUserInSchema)(options, serviceInfo.schema);
41
41
  return { projectId, serviceId };
42
42
  });
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const command_1 = require("../command");
5
5
  const projectUtils_1 = require("../projectUtils");
6
- const fileUtils_1 = require("../dataconnect/fileUtils");
6
+ const load_1 = require("../dataconnect/load");
7
7
  const error_1 = require("../error");
8
8
  const schemaMigration_1 = require("../dataconnect/schemaMigration");
9
9
  const requireAuth_1 = require("../requireAuth");
@@ -24,7 +24,7 @@ exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
24
24
  var _a, _b;
25
25
  const projectId = (0, projectUtils_1.needProjectId)(options);
26
26
  await (0, ensureApis_1.ensureApis)(projectId);
27
- const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
27
+ const serviceInfo = await (0, load_1.pickService)(projectId, options.config, serviceId);
28
28
  const instanceId = (_a = serviceInfo.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instanceId;
29
29
  if (!instanceId) {
30
30
  throw new error_1.FirebaseError("dataconnect.yaml is missing field schema.datasource.postgresql.cloudsql.instanceId");
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const command_1 = require("../command");
5
5
  const projectUtils_1 = require("../projectUtils");
6
- const fileUtils_1 = require("../dataconnect/fileUtils");
6
+ const load_1 = require("../dataconnect/load");
7
7
  const error_1 = require("../error");
8
8
  const requireAuth_1 = require("../requireAuth");
9
9
  const requirePermissions_1 = require("../requirePermissions");
@@ -25,7 +25,7 @@ exports.command = new command_1.Command("dataconnect:sql:setup [serviceId]")
25
25
  var _a;
26
26
  const projectId = (0, projectUtils_1.needProjectId)(options);
27
27
  await (0, ensureApis_1.ensureApis)(projectId);
28
- const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
28
+ const serviceInfo = await (0, load_1.pickService)(projectId, options.config, serviceId);
29
29
  const instanceId = (_a = serviceInfo.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instanceId;
30
30
  if (!instanceId) {
31
31
  throw new error_1.FirebaseError("dataconnect.yaml is missing field schema.datasource.postgresql.cloudsql.instanceId");
@@ -8,7 +8,7 @@ const command_1 = require("../command");
8
8
  const projectUtils_1 = require("../projectUtils");
9
9
  const ensureApis_1 = require("../dataconnect/ensureApis");
10
10
  const requirePermissions_1 = require("../requirePermissions");
11
- const fileUtils_1 = require("../dataconnect/fileUtils");
11
+ const load_1 = require("../dataconnect/load");
12
12
  const schemaMigration_1 = require("../dataconnect/schemaMigration");
13
13
  const requireAuth_1 = require("../requireAuth");
14
14
  const connect_1 = require("../gcp/cloudsql/connect");
@@ -79,7 +79,7 @@ exports.command = new command_1.Command("dataconnect:sql:shell [serviceId]")
79
79
  .action(async (serviceId, options) => {
80
80
  const projectId = (0, projectUtils_1.needProjectId)(options);
81
81
  await (0, ensureApis_1.ensureApis)(projectId);
82
- const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
82
+ const serviceInfo = await (0, load_1.pickService)(projectId, options.config, serviceId);
83
83
  const { instanceId, databaseId } = (0, schemaMigration_1.getIdentifiers)(serviceInfo.schema);
84
84
  const { user: username } = await (0, connect_1.getIAMUser)(options);
85
85
  const instance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
@@ -206,6 +206,9 @@ async function initAction(feature, options) {
206
206
  if (setup.features.includes("hosting") && setup.features.includes("hosting:github")) {
207
207
  setup.features = setup.features.filter((f) => f !== "hosting:github");
208
208
  }
209
+ if (setup.features.includes("dataconnect") && setup.features.includes("dataconnect:sdk")) {
210
+ setup.features = setup.features.filter((f) => f !== "dataconnect:sdk");
211
+ }
209
212
  await (0, init_1.init)(setup, config, options);
210
213
  logger_1.logger.info();
211
214
  config.writeProjectFile("firebase.json", setup.config);
@@ -15,7 +15,8 @@ exports.command = new command_1.Command("login")
15
15
  .option("--no-localhost", "login from a device without an accessible localhost")
16
16
  .option("--reauth", "force reauthentication even if already logged in")
17
17
  .action(async (options) => {
18
- if (options.nonInteractive) {
18
+ var _a, _b, _c, _d;
19
+ if (options.nonInteractive && !options.prototyperLogin) {
19
20
  throw new error_1.FirebaseError("Cannot run login in non-interactive mode. See " +
20
21
  clc.bold("login:ci") +
21
22
  " to generate a token for use in non-interactive environments.", { exit: 1 });
@@ -26,7 +27,11 @@ exports.command = new command_1.Command("login")
26
27
  logger_1.logger.info("Already logged in as", clc.bold(user.email));
27
28
  return user;
28
29
  }
29
- if (!options.reauth) {
30
+ if (options.consent) {
31
+ (_b = (_a = options.consent) === null || _a === void 0 ? void 0 : _a.metrics) !== null && _b !== void 0 ? _b : configstore_1.configstore.set("usage", options.consent.metrics);
32
+ (_d = (_c = options.consent) === null || _c === void 0 ? void 0 : _c.gemini) !== null && _d !== void 0 ? _d : configstore_1.configstore.set("gemini", options.consent.gemini);
33
+ }
34
+ else if (!options.reauth && !options.prototyperLogin) {
30
35
  utils.logBullet("The Firebase CLI’s MCP server feature can optionally make use of Gemini in Firebase. " +
31
36
  "Learn more about Gemini in Firebase and how it uses your data: https://firebase.google.com/docs/gemini-in-firebase#how-gemini-in-firebase-uses-your-data");
32
37
  const geminiUsage = await (0, prompt_1.confirm)("Enable Gemini in Firebase features?");
@@ -40,12 +45,12 @@ exports.command = new command_1.Command("login")
40
45
  utils.logBullet("To change your preferences at any time, run `firebase logout` and `firebase login` again.");
41
46
  }
42
47
  }
43
- const useLocalhost = (0, utils_1.isCloudEnvironment)() ? false : options.localhost;
48
+ if (options.prototyperLogin) {
49
+ return auth.loginPrototyper();
50
+ }
51
+ const useLocalhost = !(0, utils_1.isCloudEnvironment)() && !!options.localhost;
44
52
  const result = await auth.loginGoogle(useLocalhost, user === null || user === void 0 ? void 0 : user.email);
45
- configstore_1.configstore.set("user", result.user);
46
- configstore_1.configstore.set("tokens", result.tokens);
47
- configstore_1.configstore.set("loginScopes", result.scopes);
48
- configstore_1.configstore.delete("session");
53
+ auth.recordCredentials(result);
49
54
  logger_1.logger.info();
50
55
  if (typeof result.user !== "string") {
51
56
  utils.logSuccess("Success! Logged in as " + clc.bold(result.user.email));
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addNote = void 0;
4
+ const logger_1 = require("../logger");
5
+ const error_1 = require("../error");
6
+ const utils_1 = require("./utils");
7
+ async function addNote(appId, issueId, note) {
8
+ const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
9
+ logger_1.logger.debug(`[mcp][crashlytics] addNote called with appId: ${appId}, issueId: ${issueId}, note: ${note}`);
10
+ try {
11
+ const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
12
+ method: "POST",
13
+ headers: {
14
+ "Content-Type": "application/json",
15
+ },
16
+ path: `/projects/${requestProjectNumber}/apps/${appId}/issues/${issueId}/notes`,
17
+ body: { body: note },
18
+ timeout: utils_1.TIMEOUT,
19
+ });
20
+ return response.body;
21
+ }
22
+ catch (err) {
23
+ logger_1.logger.debug(err.message);
24
+ throw new error_1.FirebaseError(`Failed to add note to issue ${issueId} for app ${appId}. Error: ${err}.`, { original: err });
25
+ }
26
+ }
27
+ exports.addNote = addNote;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteNote = void 0;
4
+ const logger_1 = require("../logger");
5
+ const error_1 = require("../error");
6
+ const utils_1 = require("./utils");
7
+ async function deleteNote(appId, issueId, noteId) {
8
+ const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
9
+ logger_1.logger.debug(`[mcp][crashlytics] deleteNote called with appId: ${appId}, issueId: ${issueId}, noteId: ${noteId}`);
10
+ try {
11
+ await utils_1.CRASHLYTICS_API_CLIENT.request({
12
+ method: "DELETE",
13
+ path: `/projects/${requestProjectNumber}/apps/${appId}/issues/${issueId}/notes/${noteId}`,
14
+ timeout: utils_1.TIMEOUT,
15
+ });
16
+ return `Successfully deleted note ${noteId} from issue ${issueId}.`;
17
+ }
18
+ catch (err) {
19
+ logger_1.logger.debug(err.message);
20
+ throw new error_1.FirebaseError(`Failed to delete note ${noteId} from issue ${issueId} for app ${appId}. Error: ${err}.`, { original: err });
21
+ }
22
+ }
23
+ exports.deleteNote = deleteNote;
@@ -1,28 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getIssueDetails = void 0;
4
- const apiv2_1 = require("../apiv2");
5
4
  const logger_1 = require("../logger");
6
5
  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
- });
6
+ const utils_1 = require("./utils");
13
7
  async function getIssueDetails(appId, issueId) {
8
+ const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
9
+ logger_1.logger.debug(`[mcp][crashlytics] getIssueDetails called with appId: ${appId}, issueId: ${issueId}`);
14
10
  try {
15
- const requestProjectNumber = parseProjectNumber(appId);
16
- if (requestProjectNumber === undefined) {
17
- throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
18
- }
19
- const response = await apiClient.request({
11
+ const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
20
12
  method: "GET",
21
13
  headers: {
22
14
  "Content-Type": "application/json",
23
15
  },
24
16
  path: `/projects/${requestProjectNumber}/apps/${appId}/issues/${issueId}`,
25
- timeout: TIMEOUT,
17
+ timeout: utils_1.TIMEOUT,
26
18
  });
27
19
  return response.body;
28
20
  }
@@ -32,10 +24,3 @@ async function getIssueDetails(appId, issueId) {
32
24
  }
33
25
  }
34
26
  exports.getIssueDetails = getIssueDetails;
35
- function parseProjectNumber(appId) {
36
- const appIdParts = appId.split(":");
37
- if (appIdParts.length > 1) {
38
- return appIdParts[1];
39
- }
40
- return undefined;
41
- }
@@ -1,16 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getSampleCrash = void 0;
4
- const apiv2_1 = require("../apiv2");
5
4
  const logger_1 = require("../logger");
6
5
  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
- });
6
+ const utils_1 = require("./utils");
13
7
  async function getSampleCrash(appId, issueId, sampleCount, variantId) {
8
+ const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
9
+ logger_1.logger.debug(`[mcp][crashlytics] getSampleCrash called with appId: ${appId}, issueId: ${issueId}, sampleCount: ${sampleCount}, variantId: ${variantId}`);
14
10
  try {
15
11
  const queryParams = new URLSearchParams();
16
12
  queryParams.set("filter.issue.id", issueId);
@@ -18,18 +14,15 @@ async function getSampleCrash(appId, issueId, sampleCount, variantId) {
18
14
  if (variantId) {
19
15
  queryParams.set("filter.issue.variant_id", variantId);
20
16
  }
21
- const requestProjectNumber = parseProjectNumber(appId);
22
- if (requestProjectNumber === undefined) {
23
- throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
24
- }
25
- const response = await apiClient.request({
17
+ logger_1.logger.debug(`[mcp][crashlytics] getSampleCrash query paramaters: ${queryParams}`);
18
+ const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
26
19
  method: "GET",
27
20
  headers: {
28
21
  "Content-Type": "application/json",
29
22
  },
30
23
  path: `/projects/${requestProjectNumber}/apps/${appId}/events`,
31
24
  queryParams: queryParams,
32
- timeout: TIMEOUT,
25
+ timeout: utils_1.TIMEOUT,
33
26
  });
34
27
  return response.body;
35
28
  }
@@ -39,10 +32,3 @@ async function getSampleCrash(appId, issueId, sampleCount, variantId) {
39
32
  }
40
33
  }
41
34
  exports.getSampleCrash = getSampleCrash;
42
- function parseProjectNumber(appId) {
43
- const appIdParts = appId.split(":");
44
- if (appIdParts.length > 1) {
45
- return appIdParts[1];
46
- }
47
- return undefined;
48
- }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listNotes = void 0;
4
+ const logger_1 = require("../logger");
5
+ const error_1 = require("../error");
6
+ const utils_1 = require("./utils");
7
+ async function listNotes(appId, issueId, noteCount) {
8
+ const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
9
+ try {
10
+ const queryParams = new URLSearchParams();
11
+ queryParams.set("page_size", `${noteCount}`);
12
+ logger_1.logger.debug(`[mcp][crashlytics] listNotes called with appId: ${appId}, issueId: ${issueId}, noteCount: ${noteCount}`);
13
+ const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
14
+ method: "GET",
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ },
18
+ path: `/projects/${requestProjectNumber}/apps/${appId}/issues/${issueId}/notes`,
19
+ queryParams: queryParams,
20
+ timeout: utils_1.TIMEOUT,
21
+ });
22
+ return response.body;
23
+ }
24
+ catch (err) {
25
+ logger_1.logger.debug(err.message);
26
+ throw new error_1.FirebaseError(`Failed to fetch notes for issue ${issueId} for app ${appId}. Error: ${err}.`, { original: err });
27
+ }
28
+ }
29
+ exports.listNotes = listNotes;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listTopDevices = void 0;
4
+ const logger_1 = require("../logger");
5
+ const error_1 = require("../error");
6
+ const utils_1 = require("./utils");
7
+ async function listTopDevices(appId, deviceCount, issueId) {
8
+ const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
9
+ const platformPath = (0, utils_1.parsePlatform)(appId);
10
+ try {
11
+ const queryParams = new URLSearchParams();
12
+ queryParams.set("page_size", `${deviceCount}`);
13
+ if (issueId) {
14
+ queryParams.set("filter.issue.id", issueId);
15
+ }
16
+ logger_1.logger.debug(`[mcp][crashlytics] listTopDevices called with appId: ${appId}, deviceCount: ${deviceCount}, issueId: ${issueId}`);
17
+ const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
18
+ method: "GET",
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ },
22
+ path: `/projects/${requestProjectNumber}/apps/${appId}/reports/${platformPath}`,
23
+ queryParams: queryParams,
24
+ timeout: utils_1.TIMEOUT,
25
+ });
26
+ return response.body;
27
+ }
28
+ catch (err) {
29
+ logger_1.logger.debug(err.message);
30
+ throw new error_1.FirebaseError(`Failed to fetch the top devices for the Firebase app id: ${appId}. Error: ${err}.`, { original: err });
31
+ }
32
+ }
33
+ exports.listTopDevices = listTopDevices;
@@ -1,45 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listTopIssues = void 0;
4
- const apiv2_1 = require("../apiv2");
5
4
  const logger_1 = require("../logger");
6
5
  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, issueType, issueCount) {
6
+ const utils_1 = require("./utils");
7
+ async function listTopIssues(appId, issueType, issueCount) {
8
+ const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
14
9
  try {
15
10
  const queryParams = new URLSearchParams();
16
11
  queryParams.set("page_size", `${issueCount}`);
17
12
  queryParams.set("filter.issue.error_types", `${issueType}`);
18
- const requestProjectId = parseProjectId(appId);
19
- if (requestProjectId === undefined) {
20
- throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
21
- }
22
- const response = await apiClient.request({
13
+ logger_1.logger.debug(`[mcp][crashlytics] listTopIssues called with appId: ${appId}, issueType: ${issueType}, issueCount: ${issueCount}`);
14
+ const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
23
15
  method: "GET",
24
16
  headers: {
25
17
  "Content-Type": "application/json",
26
18
  },
27
- path: `/projects/${requestProjectId}/apps/${appId}/reports/topIssues`,
19
+ path: `/projects/${requestProjectNumber}/apps/${appId}/reports/topIssues`,
28
20
  queryParams: queryParams,
29
- timeout: TIMEOUT,
21
+ timeout: utils_1.TIMEOUT,
30
22
  });
31
23
  return response.body;
32
24
  }
33
25
  catch (err) {
34
26
  logger_1.logger.debug(err.message);
35
- throw new error_1.FirebaseError(`Failed to fetch the top issues for the Firebase Project ${projectId}, AppId ${appId}. Error: ${err}.`, { original: err });
27
+ throw new error_1.FirebaseError(`Failed to fetch the top issues for the Firebase app id: ${appId}. Error: ${err}.`, { original: err });
36
28
  }
37
29
  }
38
30
  exports.listTopIssues = listTopIssues;
39
- function parseProjectId(appId) {
40
- const appIdParts = appId.split(":");
41
- if (appIdParts.length > 1) {
42
- return appIdParts[1];
43
- }
44
- return undefined;
45
- }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listTopOperatingSystems = void 0;
4
+ const logger_1 = require("../logger");
5
+ const error_1 = require("../error");
6
+ const utils_1 = require("./utils");
7
+ async function listTopOperatingSystems(appId, osCount, issueId) {
8
+ const requestProjectNumber = (0, utils_1.parseProjectNumber)(appId);
9
+ try {
10
+ const queryParams = new URLSearchParams();
11
+ queryParams.set("page_size", `${osCount}`);
12
+ if (issueId) {
13
+ queryParams.set("filter.issue.id", issueId);
14
+ }
15
+ logger_1.logger.debug(`[mcp][crashlytics] listTopOperatingSystems called with appId: ${appId}, osCount: ${osCount}, issueId: ${issueId}`);
16
+ const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
17
+ method: "GET",
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ },
21
+ path: `/projects/${requestProjectNumber}/apps/${appId}/reports/topOperatingSystems`,
22
+ queryParams: queryParams,
23
+ timeout: utils_1.TIMEOUT,
24
+ });
25
+ return response.body;
26
+ }
27
+ catch (err) {
28
+ logger_1.logger.debug(err.message);
29
+ throw new error_1.FirebaseError(`Failed to fetch the top operating systems for the Firebase app id: ${appId}. Error: ${err}.`, { original: err });
30
+ }
31
+ }
32
+ exports.listTopOperatingSystems = listTopOperatingSystems;