firebase-tools 14.15.2 → 14.17.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 (83) hide show
  1. package/lib/commands/firestore-bulkdelete.js +73 -0
  2. package/lib/commands/firestore-operations-cancel.js +44 -0
  3. package/lib/commands/firestore-operations-describe.js +29 -0
  4. package/lib/commands/firestore-operations-list.js +29 -0
  5. package/lib/commands/firestore-utils.js +15 -0
  6. package/lib/commands/functions-config-export.js +5 -2
  7. package/lib/commands/index.js +5 -0
  8. package/lib/config.js +16 -4
  9. package/lib/crashlytics/{listNotes.js → events.js} +11 -9
  10. package/lib/crashlytics/filters.js +77 -0
  11. package/lib/crashlytics/issues.js +50 -0
  12. package/lib/crashlytics/notes.js +67 -0
  13. package/lib/crashlytics/reports.js +47 -0
  14. package/lib/crashlytics/types.js +60 -0
  15. package/lib/dataconnect/ensureApis.js +3 -3
  16. package/lib/deploy/apphosting/deploy.js +2 -1
  17. package/lib/deploy/apphosting/util.js +5 -8
  18. package/lib/deploy/functions/deploy.js +4 -3
  19. package/lib/deploy/functions/prepare.js +8 -6
  20. package/lib/emulator/apphosting/developmentServer.js +3 -3
  21. package/lib/emulator/apphosting/serve.js +29 -29
  22. package/lib/emulator/commandUtils.js +7 -1
  23. package/lib/emulator/controller.js +15 -31
  24. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  25. package/lib/emulator/hub.js +7 -1
  26. package/lib/emulator/initEmulators.js +1 -1
  27. package/lib/extensions/runtimes/common.js +3 -2
  28. package/lib/firestore/api.js +45 -0
  29. package/lib/firestore/pretty-print.js +23 -0
  30. package/lib/functions/projectConfig.js +69 -9
  31. package/lib/gcp/cloudfunctions.js +1 -6
  32. package/lib/gcp/cloudfunctionsv2.js +1 -9
  33. package/lib/gcp/cloudsql/cloudsqladmin.js +2 -2
  34. package/lib/init/features/dataconnect/create_app.js +7 -2
  35. package/lib/init/features/dataconnect/index.js +101 -60
  36. package/lib/init/features/dataconnect/sdk.js +35 -17
  37. package/lib/mcp/errors.js +2 -10
  38. package/lib/mcp/index.js +0 -3
  39. package/lib/mcp/prompts/crashlytics/connect.js +114 -0
  40. package/lib/mcp/prompts/crashlytics/index.js +2 -3
  41. package/lib/mcp/tools/auth/disable_user.js +1 -1
  42. package/lib/mcp/tools/auth/get_user.js +9 -2
  43. package/lib/mcp/tools/core/index.js +4 -0
  44. package/lib/mcp/tools/core/init.js +11 -2
  45. package/lib/mcp/tools/core/login.js +46 -0
  46. package/lib/mcp/tools/core/logout.js +62 -0
  47. package/lib/mcp/tools/crashlytics/events.js +42 -0
  48. package/lib/mcp/tools/crashlytics/index.js +16 -20
  49. package/lib/mcp/tools/crashlytics/issues.js +56 -0
  50. package/lib/mcp/tools/crashlytics/notes.js +78 -0
  51. package/lib/mcp/tools/crashlytics/reports.js +100 -0
  52. package/lib/mcp/tools/dataconnect/index.js +2 -2
  53. package/lib/mcp/tools/dataconnect/{info.js → list_services.js} +5 -5
  54. package/lib/mcp/util.js +1 -17
  55. package/lib/serve/functions.js +4 -3
  56. package/lib/unzip.js +13 -0
  57. package/lib/utils.js +17 -1
  58. package/package.json +1 -1
  59. package/schema/firebase-config.json +160 -59
  60. package/lib/crashlytics/addNote.js +0 -27
  61. package/lib/crashlytics/deleteNote.js +0 -23
  62. package/lib/crashlytics/getIssueDetails.js +0 -26
  63. package/lib/crashlytics/getSampleCrash.js +0 -34
  64. package/lib/crashlytics/listTopDevices.js +0 -33
  65. package/lib/crashlytics/listTopIssues.js +0 -30
  66. package/lib/crashlytics/listTopOperatingSystems.js +0 -32
  67. package/lib/crashlytics/listTopVersions.js +0 -32
  68. package/lib/crashlytics/updateIssue.js +0 -35
  69. package/lib/mcp/prompts/crashlytics/common.js +0 -10
  70. package/lib/mcp/prompts/crashlytics/fix_issue.js +0 -89
  71. package/lib/mcp/prompts/crashlytics/prioritize_issues.js +0 -79
  72. package/lib/mcp/tools/crashlytics/add_note.js +0 -32
  73. package/lib/mcp/tools/crashlytics/constants.js +0 -11
  74. package/lib/mcp/tools/crashlytics/delete_note.js +0 -35
  75. package/lib/mcp/tools/crashlytics/get_issue_details.js +0 -31
  76. package/lib/mcp/tools/crashlytics/get_sample_crash.js +0 -43
  77. package/lib/mcp/tools/crashlytics/list_notes.js +0 -37
  78. package/lib/mcp/tools/crashlytics/list_top_devices.js +0 -33
  79. package/lib/mcp/tools/crashlytics/list_top_issues.js +0 -38
  80. package/lib/mcp/tools/crashlytics/list_top_operating_systems.js +0 -33
  81. package/lib/mcp/tools/crashlytics/list_top_versions.js +0 -33
  82. package/lib/mcp/tools/crashlytics/update_issue.js +0 -37
  83. package/lib/mcp/tools/database/set_rules.js +0 -41
@@ -1,6 +1,17 @@
1
1
  "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
14
+ exports.resolveConfigDir = exports.requireLocal = exports.isRemoteConfig = exports.isLocalConfig = exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
4
15
  const error_1 = require("../error");
5
16
  exports.DEFAULT_CODEBASE = "default";
6
17
  function normalize(config) {
@@ -33,17 +44,33 @@ function validatePrefix(prefix) {
33
44
  }
34
45
  exports.validatePrefix = validatePrefix;
35
46
  function validateSingle(config) {
36
- if (!config.source) {
37
- throw new error_1.FirebaseError("codebase source must be specified");
47
+ const { source, remoteSource, runtime, codebase: providedCodebase } = config, rest = __rest(config, ["source", "remoteSource", "runtime", "codebase"]);
48
+ if (source && remoteSource) {
49
+ throw new error_1.FirebaseError("Cannot specify both 'source' and 'remoteSource' in a single functions config. Please choose one.");
38
50
  }
39
- if (!config.codebase) {
40
- config.codebase = exports.DEFAULT_CODEBASE;
51
+ if (!source && !remoteSource) {
52
+ throw new error_1.FirebaseError("codebase source must be specified. Must specify either 'source' or 'remoteSource' in a functions config.");
41
53
  }
42
- validateCodebase(config.codebase);
54
+ const codebase = providedCodebase !== null && providedCodebase !== void 0 ? providedCodebase : exports.DEFAULT_CODEBASE;
55
+ validateCodebase(codebase);
43
56
  if (config.prefix) {
44
57
  validatePrefix(config.prefix);
45
58
  }
46
- return Object.assign(Object.assign({}, config), { source: config.source, codebase: config.codebase });
59
+ const commonConfig = Object.assign({ codebase }, rest);
60
+ if (source) {
61
+ return Object.assign(Object.assign(Object.assign({}, commonConfig), { source }), (runtime ? { runtime } : {}));
62
+ }
63
+ else if (remoteSource) {
64
+ if (!remoteSource.repository || !remoteSource.ref) {
65
+ throw new error_1.FirebaseError("remoteSource requires 'repository' and 'ref' to be specified.");
66
+ }
67
+ if (!runtime) {
68
+ throw new error_1.FirebaseError("functions.runtime is required when using remoteSource in firebase.json.");
69
+ }
70
+ return Object.assign(Object.assign({}, commonConfig), { remoteSource,
71
+ runtime });
72
+ }
73
+ throw new error_1.FirebaseError("Invalid functions config.");
47
74
  }
48
75
  function assertUnique(config, property, propval) {
49
76
  const values = new Set();
@@ -63,9 +90,22 @@ function assertUniqueSourcePrefixPair(config) {
63
90
  var _a;
64
91
  const sourcePrefixPairs = new Set();
65
92
  for (const c of config) {
66
- const key = JSON.stringify({ source: c.source, prefix: c.prefix || "" });
93
+ let sourceIdentifier;
94
+ let sourceDescription;
95
+ if (c.source) {
96
+ sourceIdentifier = c.source;
97
+ sourceDescription = `source directory ('${c.source}')`;
98
+ }
99
+ else if (c.remoteSource) {
100
+ sourceIdentifier = `remote:${c.remoteSource.repository}#${c.remoteSource.ref}@dir:${c.remoteSource.dir || "."}`;
101
+ sourceDescription = `remote source ('${c.remoteSource.repository}')`;
102
+ }
103
+ else {
104
+ continue;
105
+ }
106
+ const key = JSON.stringify({ source: sourceIdentifier, prefix: c.prefix || "" });
67
107
  if (sourcePrefixPairs.has(key)) {
68
- throw new error_1.FirebaseError(`More than one functions config specifies the same source directory ('${c.source}') and prefix ('${(_a = c.prefix) !== null && _a !== void 0 ? _a : ""}'). Please add a unique 'prefix' to each function configuration that shares this source to resolve the conflict.`);
108
+ throw new error_1.FirebaseError(`More than one functions config specifies the same ${sourceDescription} and prefix ('${(_a = c.prefix) !== null && _a !== void 0 ? _a : ""}'). Please add a unique 'prefix' to each function configuration that shares this source to resolve the conflict.`);
69
109
  }
70
110
  sourcePrefixPairs.add(key);
71
111
  }
@@ -89,3 +129,23 @@ function configForCodebase(config, codebase) {
89
129
  return codebaseCfg;
90
130
  }
91
131
  exports.configForCodebase = configForCodebase;
132
+ function isLocalConfig(c) {
133
+ return c.source !== undefined;
134
+ }
135
+ exports.isLocalConfig = isLocalConfig;
136
+ function isRemoteConfig(c) {
137
+ return c.remoteSource !== undefined;
138
+ }
139
+ exports.isRemoteConfig = isRemoteConfig;
140
+ function requireLocal(c, purpose) {
141
+ if (!isLocalConfig(c)) {
142
+ const msg = purpose !== null && purpose !== void 0 ? purpose : "This operation requires a local functions source directory, but the codebase is configured with a remote source.";
143
+ throw new error_1.FirebaseError(msg);
144
+ }
145
+ return c;
146
+ }
147
+ exports.requireLocal = requireLocal;
148
+ function resolveConfigDir(c) {
149
+ return c.configDir || c.source;
150
+ }
151
+ exports.resolveConfigDir = resolveConfigDir;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.captureRuntimeValidationError = exports.API_VERSION = void 0;
3
+ exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.captureRuntimeValidationError = exports.API_VERSION = void 0;
4
4
  const clc = require("colorette");
5
5
  const error_1 = require("../error");
6
6
  const logger_1 = require("../logger");
@@ -200,11 +200,6 @@ async function list(projectId, region) {
200
200
  });
201
201
  }
202
202
  }
203
- async function listFunctions(projectId, region) {
204
- const res = await list(projectId, region);
205
- return res.functions;
206
- }
207
- exports.listFunctions = listFunctions;
208
203
  async function listAllFunctions(projectId) {
209
204
  return list(projectId, "-");
210
205
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
3
+ exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
4
4
  const apiv2_1 = require("../apiv2");
5
5
  const error_1 = require("../error");
6
6
  const api_1 = require("../api");
@@ -84,14 +84,6 @@ async function getFunction(projectId, location, functionId) {
84
84
  return res.body;
85
85
  }
86
86
  exports.getFunction = getFunction;
87
- async function listFunctions(projectId, region) {
88
- const res = await listFunctionsInternal(projectId, region);
89
- if (res.unreachable.includes(region)) {
90
- throw new error_1.FirebaseError(`Cloud Functions region ${region} is unavailable`);
91
- }
92
- return res.functions;
93
- }
94
- exports.listFunctions = listFunctions;
95
87
  async function listAllFunctions(projectId) {
96
88
  return await listFunctionsInternal(projectId, "-");
97
89
  }
@@ -151,7 +151,7 @@ async function deleteDatabase(projectId, instanceId, databaseId) {
151
151
  return res.body;
152
152
  }
153
153
  exports.deleteDatabase = deleteDatabase;
154
- async function createUser(projectId, instanceId, type, username, password) {
154
+ async function createUser(projectId, instanceId, type, username, password, retryTimeout) {
155
155
  const maxRetries = 3;
156
156
  let retries = 0;
157
157
  while (true) {
@@ -180,7 +180,7 @@ async function createUser(projectId, instanceId, type, username, password) {
180
180
  if (builtinRoleNotReady(err.message) && retries < maxRetries) {
181
181
  retries++;
182
182
  await new Promise((resolve) => {
183
- setTimeout(resolve, 1000 * retries);
183
+ setTimeout(resolve, retryTimeout !== null && retryTimeout !== void 0 ? retryTimeout : 1000 * retries);
184
184
  });
185
185
  }
186
186
  else {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createNextApp = exports.createReactApp = void 0;
3
+ exports.createFlutterApp = exports.createNextApp = exports.createReactApp = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const clc = require("colorette");
6
6
  const utils_1 = require("../../../utils");
@@ -26,8 +26,13 @@ async function createNextApp(webAppId) {
26
26
  await executeCommand("npx", args);
27
27
  }
28
28
  exports.createNextApp = createNextApp;
29
+ async function createFlutterApp(webAppId) {
30
+ const args = ["create", webAppId];
31
+ await executeCommand("flutter", args);
32
+ }
33
+ exports.createFlutterApp = createFlutterApp;
29
34
  async function executeCommand(command, args) {
30
- (0, utils_1.logLabeledBullet)("dataconnect", `Running ${clc.bold(`${command} ${args.join(" ")}`)}`);
35
+ (0, utils_1.logLabeledBullet)("dataconnect", `> ${clc.bold(`${command} ${args.join(" ")}`)}`);
31
36
  return new Promise((resolve, reject) => {
32
37
  const childProcess = (0, child_process_1.spawn)(command, args, {
33
38
  stdio: "inherit",
@@ -26,26 +26,25 @@ const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconn
26
26
  const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
27
27
  const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
28
28
  const MUTATIONS_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/mutations.gql");
29
- const emptyConnector = {
30
- id: "example",
31
- path: "./example",
32
- files: [],
33
- };
34
- const defaultConnector = {
35
- id: "example",
36
- path: "./example",
37
- files: [
38
- {
39
- path: "queries.gql",
40
- content: QUERIES_TEMPLATE,
41
- },
29
+ const templateServiceInfo = {
30
+ schemaGql: [{ path: "schema.gql", content: SCHEMA_TEMPLATE }],
31
+ connectors: [
42
32
  {
43
- path: "mutations.gql",
44
- content: MUTATIONS_TEMPLATE,
33
+ id: "example",
34
+ path: "./example",
35
+ files: [
36
+ {
37
+ path: "queries.gql",
38
+ content: QUERIES_TEMPLATE,
39
+ },
40
+ {
41
+ path: "mutations.gql",
42
+ content: MUTATIONS_TEMPLATE,
43
+ },
44
+ ],
45
45
  },
46
46
  ],
47
47
  };
48
- const defaultSchema = { path: "schema.gql", content: SCHEMA_TEMPLATE };
49
48
  async function askQuestions(setup) {
50
49
  const info = {
51
50
  analyticsFlow: "cli",
@@ -54,6 +53,7 @@ async function askQuestions(setup) {
54
53
  locationId: "",
55
54
  cloudSqlInstanceId: "",
56
55
  cloudSqlDatabase: "",
56
+ shouldProvisionCSQL: false,
57
57
  };
58
58
  if (setup.projectId) {
59
59
  const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
@@ -101,6 +101,7 @@ async function actuate(setup, config, options) {
101
101
  void (0, track_1.trackGA4)("dataconnect_init", {
102
102
  project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
103
103
  flow: info.analyticsFlow,
104
+ provision_cloud_sql: String(info.shouldProvisionCSQL),
104
105
  });
105
106
  }
106
107
  if (info.appDescription) {
@@ -118,10 +119,11 @@ async function actuateWithInfo(setup, config, info, options) {
118
119
  const projectId = setup.projectId;
119
120
  if (!projectId) {
120
121
  info.analyticsFlow += "_save_template";
121
- return await writeFiles(config, info, { schemaGql: [defaultSchema], connectors: [defaultConnector] }, options);
122
+ return await writeFiles(config, info, templateServiceInfo, options);
122
123
  }
123
- const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
124
- if (hasBilling) {
124
+ await (0, ensureApis_1.ensureApis)(projectId, true);
125
+ const provisionCSQL = info.shouldProvisionCSQL && (await (0, cloudbilling_1.isBillingEnabled)(setup));
126
+ if (provisionCSQL) {
125
127
  await (0, provisionCloudSql_1.setupCloudSql)({
126
128
  projectId: projectId,
127
129
  location: info.locationId,
@@ -130,15 +132,18 @@ async function actuateWithInfo(setup, config, info, options) {
130
132
  requireGoogleMlIntegration: false,
131
133
  });
132
134
  }
135
+ const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
133
136
  if (!info.appDescription) {
137
+ if (!info.serviceGql) {
138
+ await downloadService(info, serviceName);
139
+ }
134
140
  if (info.serviceGql) {
135
141
  info.analyticsFlow += "_save_downloaded";
136
142
  return await writeFiles(config, info, info.serviceGql, options);
137
143
  }
138
144
  info.analyticsFlow += "_save_template";
139
- return await writeFiles(config, info, { schemaGql: [defaultSchema], connectors: [defaultConnector] }, options);
145
+ return await writeFiles(config, info, templateServiceInfo, options);
140
146
  }
141
- const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
142
147
  const serviceAlreadyExists = !(await (0, client_1.createService)(projectId, info.locationId, info.serviceId));
143
148
  const schemaGql = await (0, utils_1.promiseWithSpinner)(() => (0, fdcExperience_1.generateSchema)(info.appDescription, projectId), "Generating the Data Connect Schema...");
144
149
  const schemaFiles = [{ path: "schema.gql", content: schemaGql }];
@@ -148,7 +153,7 @@ async function actuateWithInfo(setup, config, info, options) {
148
153
  return await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
149
154
  }
150
155
  await (0, utils_1.promiseWithSpinner)(async () => {
151
- const [saveSchemaGql, waitForCloudSQLProvision] = schemasDeploySequence(projectId, info, schemaFiles, hasBilling);
156
+ const [saveSchemaGql, waitForCloudSQLProvision] = schemasDeploySequence(projectId, info, schemaFiles, provisionCSQL);
152
157
  await (0, client_1.upsertSchema)(saveSchemaGql);
153
158
  if (waitForCloudSQLProvision) {
154
159
  void (0, client_1.upsertSchema)(waitForCloudSQLProvision);
@@ -284,7 +289,6 @@ function subConnectorYamlValues(replacementValues) {
284
289
  return replaced;
285
290
  }
286
291
  async function promptForExistingServices(setup, info) {
287
- var _a, _b, _c, _d, _e;
288
292
  if (!setup.projectId) {
289
293
  return;
290
294
  }
@@ -292,10 +296,7 @@ async function promptForExistingServices(setup, info) {
292
296
  if (!existingServices.length) {
293
297
  return;
294
298
  }
295
- const existingServicesAndSchemas = await Promise.all(existingServices.map(async (s) => {
296
- return { service: s, schema: await (0, client_1.getSchema)(s.name) };
297
- }));
298
- const choice = await chooseExistingService(existingServicesAndSchemas);
299
+ const choice = await chooseExistingService(existingServices);
299
300
  if (!choice) {
300
301
  const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
301
302
  info.serviceId = (0, utils_1.newUniqueId)(defaultServiceId(), existingServiceIds);
@@ -303,39 +304,50 @@ async function promptForExistingServices(setup, info) {
303
304
  return;
304
305
  }
305
306
  info.analyticsFlow += "_pick_existing_service";
306
- const serviceName = (0, names_1.parseServiceName)(choice.service.name);
307
+ const serviceName = (0, names_1.parseServiceName)(choice.name);
307
308
  info.serviceId = serviceName.serviceId;
308
309
  info.locationId = serviceName.location;
310
+ await downloadService(info, choice.name);
311
+ }
312
+ async function downloadService(info, serviceName) {
313
+ var _a, _b, _c, _d, _e;
314
+ const schema = await (0, client_1.getSchema)(serviceName);
315
+ if (!schema) {
316
+ return;
317
+ }
309
318
  info.serviceGql = {
310
319
  schemaGql: [],
311
- connectors: [emptyConnector],
320
+ connectors: [
321
+ {
322
+ id: "example",
323
+ path: "./example",
324
+ files: [],
325
+ },
326
+ ],
312
327
  };
313
- if (choice.schema) {
314
- const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
315
- if ((_b = (_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance) {
316
- const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
317
- info.cloudSqlInstanceId = instanceName.instanceId;
318
- }
319
- if ((_c = choice.schema.source.files) === null || _c === void 0 ? void 0 : _c.length) {
320
- info.serviceGql.schemaGql = choice.schema.source.files;
321
- }
322
- info.cloudSqlDatabase = (_e = (_d = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _d === void 0 ? void 0 : _d.database) !== null && _e !== void 0 ? _e : "";
323
- const connectors = await (0, client_1.listConnectors)(choice.service.name, [
324
- "connectors.name",
325
- "connectors.source.files",
326
- ]);
327
- if (connectors.length) {
328
- info.serviceGql.connectors = connectors.map((c) => {
329
- const id = c.name.split("/").pop();
330
- return {
331
- id,
332
- path: connectors.length === 1 ? "./example" : `./${id}`,
333
- files: c.source.files || [],
334
- };
335
- });
336
- }
328
+ const primaryDatasource = schema.datasources.find((d) => d.postgresql);
329
+ if ((_b = (_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance) {
330
+ const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
331
+ info.cloudSqlInstanceId = instanceName.instanceId;
332
+ }
333
+ if ((_c = schema.source.files) === null || _c === void 0 ? void 0 : _c.length) {
334
+ info.serviceGql.schemaGql = schema.source.files;
335
+ }
336
+ info.cloudSqlDatabase = (_e = (_d = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _d === void 0 ? void 0 : _d.database) !== null && _e !== void 0 ? _e : "";
337
+ const connectors = await (0, client_1.listConnectors)(serviceName, [
338
+ "connectors.name",
339
+ "connectors.source.files",
340
+ ]);
341
+ if (connectors.length) {
342
+ info.serviceGql.connectors = connectors.map((c) => {
343
+ const id = c.name.split("/").pop();
344
+ return {
345
+ id,
346
+ path: connectors.length === 1 ? "./example" : `./${id}`,
347
+ files: c.source.files || [],
348
+ };
349
+ });
337
350
  }
338
- return;
339
351
  }
340
352
  async function chooseExistingService(existing) {
341
353
  const fdcConnector = (0, utils_1.envOverride)("FDC_CONNECTOR", "");
@@ -344,7 +356,7 @@ async function chooseExistingService(existing) {
344
356
  if (serviceEnvVar) {
345
357
  const [serviceLocationFromEnvVar, serviceIdFromEnvVar] = serviceEnvVar.split("/");
346
358
  const serviceFromEnvVar = existing.find((s) => {
347
- const serviceName = (0, names_1.parseServiceName)(s.service.name);
359
+ const serviceName = (0, names_1.parseServiceName)(s.name);
348
360
  return (serviceName.serviceId === serviceIdFromEnvVar &&
349
361
  serviceName.location === serviceLocationFromEnvVar);
350
362
  });
@@ -356,7 +368,7 @@ async function chooseExistingService(existing) {
356
368
  (0, utils_1.logWarning)(`Unable to pick up an existing service based on ${envVarName}=${serviceEnvVar}.`);
357
369
  }
358
370
  const choices = existing.map((s) => {
359
- const serviceName = (0, names_1.parseServiceName)(s.service.name);
371
+ const serviceName = (0, names_1.parseServiceName)(s.name);
360
372
  return {
361
373
  name: `${serviceName.location}/${serviceName.serviceId}`,
362
374
  value: s,
@@ -412,7 +424,11 @@ async function promptForCloudSQL(setup, info) {
412
424
  info.locationId = await (0, prompt_1.select)({
413
425
  message: "What location would like to use?",
414
426
  choices,
415
- default: "us-central1",
427
+ default: "us-east4",
428
+ });
429
+ info.shouldProvisionCSQL = await (0, prompt_1.confirm)({
430
+ message: `Would you like to provision your Cloud SQL instance and database now?`,
431
+ default: true,
416
432
  });
417
433
  }
418
434
  if (info.cloudSqlInstanceId !== "" && info.cloudSqlDatabase === "") {
@@ -436,14 +452,39 @@ async function locationChoices(setup) {
436
452
  }
437
453
  else {
438
454
  return [
439
- { name: "us-central1", value: "us-central1" },
440
- { name: "europe-north1", value: "europe-north1" },
455
+ { name: "asia-east1", value: "asia-east1" },
456
+ { name: "asia-east2", value: "asia-east2" },
457
+ { name: "asia-northeast1", value: "asia-northeast1" },
458
+ { name: "asia-northeast2", value: "asia-northeast2" },
459
+ { name: "asia-northeast3", value: "asia-northeast3" },
460
+ { name: "asia-south1", value: "asia-south1" },
461
+ { name: "asia-southeast1", value: "asia-southeast1" },
462
+ { name: "asia-southeast2", value: "asia-southeast2" },
463
+ { name: "australia-southeast1", value: "australia-southeast1" },
464
+ { name: "australia-southeast2", value: "australia-southeast2" },
441
465
  { name: "europe-central2", value: "europe-central2" },
466
+ { name: "europe-north1", value: "europe-north1" },
467
+ { name: "europe-southwest1", value: "europe-southwest1" },
442
468
  { name: "europe-west1", value: "europe-west1" },
469
+ { name: "europe-west2", value: "europe-west2" },
470
+ { name: "europe-west3", value: "europe-west3" },
471
+ { name: "europe-west4", value: "europe-west4" },
472
+ { name: "europe-west6", value: "europe-west6" },
473
+ { name: "europe-west8", value: "europe-west8" },
474
+ { name: "europe-west9", value: "europe-west9" },
475
+ { name: "me-west1", value: "me-west1" },
476
+ { name: "northamerica-northeast1", value: "northamerica-northeast1" },
477
+ { name: "northamerica-northeast2", value: "northamerica-northeast2" },
478
+ { name: "southamerica-east1", value: "southamerica-east1" },
443
479
  { name: "southamerica-west1", value: "southamerica-west1" },
480
+ { name: "us-central1", value: "us-central1" },
481
+ { name: "us-east1", value: "us-east1" },
444
482
  { name: "us-east4", value: "us-east4" },
483
+ { name: "us-south1", value: "us-south1" },
445
484
  { name: "us-west1", value: "us-west1" },
446
- { name: "asia-southeast1", value: "asia-southeast1" },
485
+ { name: "us-west2", value: "us-west2" },
486
+ { name: "us-west3", value: "us-west3" },
487
+ { name: "us-west4", value: "us-west4" },
447
488
  ];
448
489
  }
449
490
  }
@@ -26,25 +26,38 @@ async function askQuestions(setup) {
26
26
  };
27
27
  info.apps = await chooseApp();
28
28
  if (!info.apps.length) {
29
- const existingFilesAndDirs = (0, fsutils_1.listFiles)(cwd);
30
- const webAppId = (0, utils_1.newUniqueId)("web-app", existingFilesAndDirs);
29
+ const npxMissingWarning = (0, utils_1.commandExistsSync)("npx")
30
+ ? ""
31
+ : clc.yellow(" (you need to install Node.js first)");
32
+ const flutterMissingWarning = (0, utils_1.commandExistsSync)("flutter")
33
+ ? ""
34
+ : clc.yellow(" (you need to install Flutter first)");
31
35
  const choice = await (0, prompt_1.select)({
32
36
  message: `Do you want to create an app template?`,
33
37
  choices: [
34
- { name: "React", value: "react" },
35
- { name: "Next.JS", value: "next" },
38
+ { name: `React${npxMissingWarning}`, value: "react" },
39
+ { name: `Next.JS${npxMissingWarning}`, value: "next" },
40
+ { name: `Flutter${flutterMissingWarning}`, value: "flutter" },
36
41
  { name: "no", value: "no" },
37
42
  ],
38
43
  });
39
- switch (choice) {
40
- case "react":
41
- await (0, create_app_1.createReactApp)(webAppId);
42
- break;
43
- case "next":
44
- await (0, create_app_1.createNextApp)(webAppId);
45
- break;
46
- case "no":
47
- break;
44
+ try {
45
+ switch (choice) {
46
+ case "react":
47
+ await (0, create_app_1.createReactApp)((0, utils_1.newUniqueId)("web-app", (0, fsutils_1.listFiles)(cwd)));
48
+ break;
49
+ case "next":
50
+ await (0, create_app_1.createNextApp)((0, utils_1.newUniqueId)("web-app", (0, fsutils_1.listFiles)(cwd)));
51
+ break;
52
+ case "flutter":
53
+ await (0, create_app_1.createFlutterApp)((0, utils_1.newUniqueId)("flutter_app", (0, fsutils_1.listFiles)(cwd)));
54
+ break;
55
+ case "no":
56
+ break;
57
+ }
58
+ }
59
+ catch (err) {
60
+ (0, utils_1.logLabeledError)("dataconnect", `Failed to create a ${choice} app template`);
48
61
  }
49
62
  }
50
63
  setup.featureInfo = setup.featureInfo || {};
@@ -151,10 +164,15 @@ async function actuateWithInfo(setup, config, info) {
151
164
  config.writeProjectFile(path.relative(config.projectDir, connectorYamlPath), connectorYamlContents);
152
165
  (0, utils_1.logLabeledBullet)("dataconnect", `Installing the generated SDKs ...`);
153
166
  const account = (0, auth_1.getGlobalDefaultAccount)();
154
- await dataconnectEmulator_1.DataConnectEmulator.generate({
155
- configDir: connectorInfo.directory,
156
- account,
157
- });
167
+ try {
168
+ await dataconnectEmulator_1.DataConnectEmulator.generate({
169
+ configDir: connectorInfo.directory,
170
+ account,
171
+ });
172
+ }
173
+ catch (e) {
174
+ (0, utils_1.logLabeledError)("dataconnect", `Failed to generate Data Connect SDKs\n${e === null || e === void 0 ? void 0 : e.message}`);
175
+ }
158
176
  (0, utils_1.logLabeledSuccess)("dataconnect", `Installed generated SDKs for ${clc.bold(apps.map((a) => (0, appFinder_1.appDescription)(a)).join(", "))}`);
159
177
  if (apps.some((a) => a.platform === types_1.Platform.IOS)) {
160
178
  (0, utils_1.logBullet)(clc.bold("Please follow the instructions here to add your generated sdk to your XCode project:\n\thttps://firebase.google.com/docs/data-connect/ios-sdk#set-client"));
package/lib/mcp/errors.js CHANGED
@@ -4,18 +4,10 @@ exports.mcpGeminiError = exports.mcpAuthError = exports.NO_PROJECT_ERROR = void
4
4
  const util_1 = require("./util");
5
5
  exports.NO_PROJECT_ERROR = (0, util_1.mcpError)('No active project was found. Use the `firebase_update_environment` tool to set the project directory to an absolute folder location containing a firebase.json config file. Alternatively, change the MCP server config to add [...,"--dir","/absolute/path/to/project/directory"] in its command-line arguments.', "PRECONDITION_FAILED");
6
6
  function mcpAuthError(skipADC) {
7
- const cmd = (0, util_1.commandExistsSync)("firebase") ? "firebase" : "npx -y firebase-tools";
8
7
  if (skipADC) {
9
- return (0, util_1.mcpError)(`The user is not currently logged into the Firebase CLI, which is required to use this tool. Please instruct the user to execute this shell command to sign in.
10
- \`\`\`sh
11
- ${cmd} login
12
- \`\`\``);
8
+ return (0, util_1.mcpError)(`The user is not currently logged into the Firebase CLI, which is required to use this tool. Please run the 'firebase_login' tool to log in.`);
13
9
  }
14
- return (0, util_1.mcpError)(`The user is not currently logged into the Firebase CLI, which is required to use this tool. Please instruct the user to execute this shell command to sign in or to configure [Application Default Credentials][ADC] on their machine.
15
- \`\`\`sh
16
- ${cmd} login
17
- \`\`\`
18
-
10
+ return (0, util_1.mcpError)(`The user is not currently logged into the Firebase CLI, which is required to use this tool. Please run the 'firebase_login' tool to log in, or instruct the user to configure [Application Default Credentials][ADC] on their machine.
19
11
  [ADC]: https://cloud.google.com/docs/authentication/application-default-credentials`);
20
12
  }
21
13
  exports.mcpAuthError = mcpAuthError;
package/lib/mcp/index.js CHANGED
@@ -140,9 +140,6 @@ class FirebaseMcpServer {
140
140
  return this.emulatorHubClient;
141
141
  }
142
142
  const projectId = await this.getProjectId();
143
- if (!projectId) {
144
- return;
145
- }
146
143
  this.emulatorHubClient = new hubClient_1.EmulatorHubClient(projectId);
147
144
  return this.emulatorHubClient;
148
145
  }