firebase-tools 14.22.0 → 14.23.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Firebase CLI [![Actions Status][gh-actions-badge]][gh-actions] [![Node Version][node-badge]][npm] [![NPM version][npm-badge]][npm] [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=firebase&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImZpcmViYXNlLXRvb2xzIiwiZXhwZXJpbWVudGFsOm1jcCIsIi0tZGlyIiwiLiJdfQ==)
1
+ # Firebase CLI and MCP Server [![Actions Status][gh-actions-badge]][gh-actions] [![Node Version][node-badge]][npm] [![NPM version][npm-badge]][npm] [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=firebase&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImZpcmViYXNlLXRvb2xzIiwiZXhwZXBpbWVudGFsOm1jcCIsIi0tZGlyIiwiLiJdfQ==)
2
2
 
3
- The Firebase Command Line Interface (CLI) Tools can be used to test, manage, and deploy your Firebase project from the command line.
3
+ The Firebase Command Line Interface (CLI) Tools can be used to test, manage, and deploy your Firebase project from the command line. This repository is also the home of the official Firebase MCP Server. For more information, please see the [Firebase MCP Server documentation](./src/mcp).
4
4
 
5
5
  - Deploy code and assets to your Firebase projects
6
6
  - Run a local web server for your Firebase Hosting site
@@ -87,9 +87,17 @@ These commands let you deploy and interact with your Firebase services.
87
87
 
88
88
  ### App Distribution Commands
89
89
 
90
- | Command | Description |
91
- | ------------------------------ | ---------------------- |
92
- | **appdistribution:distribute** | Upload a distribution. |
90
+ | Command | Description |
91
+ | ------------------------------------ | ---------------------------------------------------------------------------------------- |
92
+ | **appdistribution:distribute** | Upload a release binary and optionally distribute it to testers and run automated tests. |
93
+ | **appdistribution:testers:list** | List testers in project. |
94
+ | **appdistribution:testers:add** | Add testers to project (and group, if specified via flag). |
95
+ | **appdistribution:testers:remove** | Remove testers from a project (or group, if specified via flag). |
96
+ | **appdistribution:groups:list** | List groups (of testers). |
97
+ | **appdistribution:groups:create** | Create a group (of testers). |
98
+ | **appdistribution:groups:delete** | Delete a group (of testers). |
99
+ | **appdistribution:testcases:export** | Export test cases as a YAML file. |
100
+ | **appdistribution:testcases:import** | Import test cases from YAML file. |
93
101
 
94
102
  ### Auth Commands
95
103
 
package/lib/apiv2.js CHANGED
@@ -205,7 +205,12 @@ class Client {
205
205
  fetchOptions.agent = new proxy_agent_1.ProxyAgent();
206
206
  }
207
207
  if (options.signal) {
208
- fetchOptions.signal = options.signal;
208
+ const signal = options.signal;
209
+ signal.reason = "";
210
+ signal.throwIfAborted = () => {
211
+ throw new error_1.FirebaseError("Aborted");
212
+ };
213
+ fetchOptions.signal = signal;
209
214
  }
210
215
  let reqTimeout;
211
216
  if (options.timeout) {
@@ -213,7 +218,12 @@ class Client {
213
218
  reqTimeout = setTimeout(() => {
214
219
  controller.abort();
215
220
  }, options.timeout);
216
- fetchOptions.signal = controller.signal;
221
+ const signal = controller.signal;
222
+ signal.reason = "";
223
+ signal.throwIfAborted = () => {
224
+ throw new error_1.FirebaseError("Aborted");
225
+ };
226
+ fetchOptions.signal = signal;
217
227
  }
218
228
  if (typeof options.body === "string" || isStream(options.body)) {
219
229
  fetchOptions.body = options.body;
@@ -6,7 +6,6 @@ const operationPoller = require("../operation-poller");
6
6
  const error_1 = require("../error");
7
7
  const apiv2_1 = require("../apiv2");
8
8
  const api_1 = require("../api");
9
- const types_1 = require("./types");
10
9
  class AppDistributionClient {
11
10
  constructor() {
12
11
  this.appDistroV1Client = new apiv2_1.Client({
@@ -84,7 +83,7 @@ class AppDistributionClient {
84
83
  await this.appDistroV1Client.post(`/${releaseName}:distribute`, data);
85
84
  }
86
85
  catch (err) {
87
- let errorMessage = err.message;
86
+ let errorMessage = (0, error_1.getErrMsg)(err);
88
87
  const errorStatus = (_c = (_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.status;
89
88
  if (errorStatus === "FAILED_PRECONDITION") {
90
89
  errorMessage = "invalid testers";
@@ -100,12 +99,10 @@ class AppDistributionClient {
100
99
  }
101
100
  async listTesters(projectName, groupName) {
102
101
  var _a;
103
- const listTestersResponse = {
104
- testers: [],
105
- };
102
+ const testers = [];
106
103
  const client = this.appDistroV1Client;
107
- let pageToken;
108
104
  const filter = groupName ? `groups=${projectName}/groups/${groupName}` : null;
105
+ let pageToken;
109
106
  do {
110
107
  const queryParams = pageToken ? { pageToken } : {};
111
108
  if (filter != null) {
@@ -118,10 +115,10 @@ class AppDistributionClient {
118
115
  });
119
116
  }
120
117
  catch (err) {
121
- throw new error_1.FirebaseError(`Client request failed to list testers ${err}`);
118
+ throw new error_1.FirebaseError(`Client request failed to list testers ${(0, error_1.getErrMsg)(err)}`);
122
119
  }
123
120
  for (const t of (_a = apiResponse.body.testers) !== null && _a !== void 0 ? _a : []) {
124
- listTestersResponse.testers.push({
121
+ testers.push({
125
122
  name: t.name,
126
123
  displayName: t.displayName,
127
124
  groups: t.groups,
@@ -130,7 +127,7 @@ class AppDistributionClient {
130
127
  }
131
128
  pageToken = apiResponse.body.nextPageToken;
132
129
  } while (pageToken);
133
- return listTestersResponse;
130
+ return testers;
134
131
  }
135
132
  async addTesters(projectName, emails) {
136
133
  try {
@@ -161,9 +158,7 @@ class AppDistributionClient {
161
158
  }
162
159
  async listGroups(projectName) {
163
160
  var _a;
164
- const listGroupsResponse = {
165
- groups: [],
166
- };
161
+ const groups = [];
167
162
  const client = this.appDistroV1Client;
168
163
  let pageToken;
169
164
  do {
@@ -172,14 +167,14 @@ class AppDistributionClient {
172
167
  const apiResponse = await client.get(`${projectName}/groups`, {
173
168
  queryParams,
174
169
  });
175
- listGroupsResponse.groups.push(...((_a = apiResponse.body.groups) !== null && _a !== void 0 ? _a : []));
170
+ groups.push(...((_a = apiResponse.body.groups) !== null && _a !== void 0 ? _a : []));
176
171
  pageToken = apiResponse.body.nextPageToken;
177
172
  }
178
173
  catch (err) {
179
- throw new error_1.FirebaseError(`Client failed to list groups ${err}`);
174
+ throw new error_1.FirebaseError(`Client failed to list groups ${(0, error_1.getErrMsg)(err)}`);
180
175
  }
181
176
  } while (pageToken);
182
- return listGroupsResponse;
177
+ return groups;
183
178
  }
184
179
  async createGroup(projectName, displayName, alias) {
185
180
  let apiResponse;
@@ -239,7 +234,7 @@ class AppDistributionClient {
239
234
  method: "POST",
240
235
  path: `${releaseName}/tests`,
241
236
  body: {
242
- deviceExecutions: devices.map(types_1.mapDeviceToExecution),
237
+ deviceExecutions: devices.map((device) => ({ device })),
243
238
  loginCredential,
244
239
  testCase: testCaseName,
245
240
  },
@@ -254,5 +249,56 @@ class AppDistributionClient {
254
249
  const response = await this.appDistroV1AlphaClient.get(releaseTestName);
255
250
  return response.body;
256
251
  }
252
+ async listTestCases(appName) {
253
+ var _a;
254
+ const testCases = [];
255
+ const client = this.appDistroV1AlphaClient;
256
+ let pageToken;
257
+ do {
258
+ const queryParams = pageToken ? { pageToken } : {};
259
+ try {
260
+ const apiResponse = await client.get(`${appName}/testCases`, {
261
+ queryParams,
262
+ });
263
+ testCases.push(...((_a = apiResponse.body.testCases) !== null && _a !== void 0 ? _a : []));
264
+ pageToken = apiResponse.body.nextPageToken;
265
+ }
266
+ catch (err) {
267
+ throw new error_1.FirebaseError(`Client failed to list test cases ${(0, error_1.getErrMsg)(err)}`);
268
+ }
269
+ } while (pageToken);
270
+ return testCases;
271
+ }
272
+ async createTestCase(appName, testCase) {
273
+ try {
274
+ const response = await this.appDistroV1AlphaClient.request({
275
+ method: "POST",
276
+ path: `${appName}/testCases`,
277
+ body: testCase,
278
+ });
279
+ return response.body;
280
+ }
281
+ catch (err) {
282
+ throw new error_1.FirebaseError(`Failed to create test case ${(0, error_1.getErrMsg)(err)}`);
283
+ }
284
+ }
285
+ async batchUpsertTestCases(appName, testCases) {
286
+ try {
287
+ const response = await this.appDistroV1AlphaClient.request({
288
+ method: "POST",
289
+ path: `${appName}/testCases:batchUpdate`,
290
+ body: {
291
+ requests: testCases.map((tc) => ({
292
+ testCase: tc,
293
+ allowMissing: true,
294
+ })),
295
+ },
296
+ });
297
+ return response.body.testCases;
298
+ }
299
+ catch (err) {
300
+ throw new error_1.FirebaseError(`Failed to upsert test cases ${(0, error_1.getErrMsg)(err)}`);
301
+ }
302
+ }
257
303
  }
258
304
  exports.AppDistributionClient = AppDistributionClient;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mapDeviceToExecution = exports.UploadReleaseResult = exports.IntegrationState = void 0;
3
+ exports.UploadReleaseResult = exports.IntegrationState = void 0;
4
4
  var IntegrationState;
5
5
  (function (IntegrationState) {
6
6
  IntegrationState["AAB_INTEGRATION_STATE_UNSPECIFIED"] = "AAB_INTEGRATION_STATE_UNSPECIFIED";
@@ -18,14 +18,3 @@ var UploadReleaseResult;
18
18
  UploadReleaseResult["RELEASE_UPDATED"] = "RELEASE_UPDATED";
19
19
  UploadReleaseResult["RELEASE_UNMODIFIED"] = "RELEASE_UNMODIFIED";
20
20
  })(UploadReleaseResult = exports.UploadReleaseResult || (exports.UploadReleaseResult = {}));
21
- function mapDeviceToExecution(device) {
22
- return {
23
- device: {
24
- model: device.model,
25
- version: device.version,
26
- orientation: device.orientation,
27
- locale: device.locale,
28
- },
29
- };
30
- }
31
- exports.mapDeviceToExecution = mapDeviceToExecution;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fromYaml = exports.toYaml = void 0;
4
+ const jsYaml = require("js-yaml");
5
+ const error_1 = require("../error");
6
+ const ALLOWED_YAML_STEP_KEYS = new Set(["goal", "hint", "successCriteria"]);
7
+ const ALLOWED_YAML_TEST_CASE_KEYS = new Set([
8
+ "displayName",
9
+ "id",
10
+ "prerequisiteTestCaseId",
11
+ "steps",
12
+ ]);
13
+ function extractIdFromResourceName(name) {
14
+ var _a;
15
+ return (_a = name.split("/").pop()) !== null && _a !== void 0 ? _a : "";
16
+ }
17
+ function toYamlTestCases(testCases) {
18
+ return testCases.map((testCase) => (Object.assign(Object.assign({ displayName: testCase.displayName, id: extractIdFromResourceName(testCase.name) }, (testCase.prerequisiteTestCase && {
19
+ prerequisiteTestCaseId: extractIdFromResourceName(testCase.prerequisiteTestCase),
20
+ })), { steps: testCase.aiInstructions.steps.map((step) => (Object.assign(Object.assign({ goal: step.goal }, (step.hint && { hint: step.hint })), (step.successCriteria && { successCriteria: step.successCriteria })))) })));
21
+ }
22
+ function toYaml(testCases) {
23
+ return jsYaml.safeDump(toYamlTestCases(testCases));
24
+ }
25
+ exports.toYaml = toYaml;
26
+ function castExists(it, thing) {
27
+ if (it == null) {
28
+ throw new error_1.FirebaseError(`"${thing}" is required`);
29
+ }
30
+ return it;
31
+ }
32
+ function checkAllowedKeys(allowedKeys, o) {
33
+ for (const key of Object.keys(o)) {
34
+ if (!allowedKeys.has(key)) {
35
+ throw new error_1.FirebaseError(`unexpected property "${key}"`);
36
+ }
37
+ }
38
+ }
39
+ function fromYamlTestCases(appName, yamlTestCases) {
40
+ return yamlTestCases.map((yamlTestCase) => {
41
+ checkAllowedKeys(ALLOWED_YAML_TEST_CASE_KEYS, yamlTestCase);
42
+ return Object.assign(Object.assign({ displayName: castExists(yamlTestCase.displayName, "displayName"), aiInstructions: {
43
+ steps: castExists(yamlTestCase.steps, "steps").map((yamlStep) => {
44
+ checkAllowedKeys(ALLOWED_YAML_STEP_KEYS, yamlStep);
45
+ return Object.assign(Object.assign({ goal: castExists(yamlStep.goal, "goal") }, (yamlStep.hint && { hint: yamlStep.hint })), (yamlStep.successCriteria && {
46
+ successCriteria: yamlStep.successCriteria,
47
+ }));
48
+ }),
49
+ } }, (yamlTestCase.id && {
50
+ name: `${appName}/testCases/${yamlTestCase.id}`,
51
+ })), (yamlTestCase.prerequisiteTestCaseId && {
52
+ prerequisiteTestCase: `${appName}/testCases/${yamlTestCase.prerequisiteTestCaseId}`,
53
+ }));
54
+ });
55
+ }
56
+ function fromYaml(appName, yaml) {
57
+ let parsedYaml;
58
+ try {
59
+ parsedYaml = jsYaml.safeLoad(yaml);
60
+ }
61
+ catch (err) {
62
+ throw new error_1.FirebaseError(`Failed to parse YAML: ${(0, error_1.getErrMsg)(err)}`);
63
+ }
64
+ if (!Array.isArray(parsedYaml)) {
65
+ throw new error_1.FirebaseError("YAML file must contain a list of test cases.");
66
+ }
67
+ return fromYamlTestCases(appName, parsedYaml);
68
+ }
69
+ exports.fromYaml = fromYaml;
@@ -15,13 +15,12 @@ exports.command = new command_1.Command("appdistribution:groups:list")
15
15
  .alias("appdistribution:group:list")
16
16
  .before(requireAuth_1.requireAuth)
17
17
  .action(async (options) => {
18
- var _a;
19
18
  const projectName = await (0, options_parser_util_1.getProjectName)(options);
20
19
  const appDistroClient = new client_1.AppDistributionClient();
21
- let groupsResponse;
20
+ let groups;
22
21
  const spinner = ora("Preparing the list of your App Distribution Groups").start();
23
22
  try {
24
- groupsResponse = await appDistroClient.listGroups(projectName);
23
+ groups = await appDistroClient.listGroups(projectName);
25
24
  }
26
25
  catch (err) {
27
26
  spinner.fail();
@@ -31,10 +30,9 @@ exports.command = new command_1.Command("appdistribution:groups:list")
31
30
  });
32
31
  }
33
32
  spinner.succeed();
34
- const groups = (_a = groupsResponse.groups) !== null && _a !== void 0 ? _a : [];
35
33
  printGroupsTable(groups);
36
34
  utils.logSuccess(`Groups listed successfully`);
37
- return groupsResponse;
35
+ return { groups };
38
36
  });
39
37
  function printGroupsTable(groups) {
40
38
  const tableHead = ["Group", "Display Name", "Tester Count", "Release Count", "Invite Link Count"];
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const fs = require("fs-extra");
5
+ const command_1 = require("../command");
6
+ const yaml_helper_1 = require("../appdistribution/yaml_helper");
7
+ const requireAuth_1 = require("../requireAuth");
8
+ const client_1 = require("../appdistribution/client");
9
+ const options_parser_util_1 = require("../appdistribution/options-parser-util");
10
+ const error_1 = require("../error");
11
+ const utils = require("../utils");
12
+ exports.command = new command_1.Command("appdistribution:testcases:export <test-cases-yaml-file>")
13
+ .description("export test cases as a YAML file")
14
+ .option("--app <app_id>", "the app id of your Firebase app")
15
+ .before(requireAuth_1.requireAuth)
16
+ .action(async (yamlFile, options) => {
17
+ const appName = (0, options_parser_util_1.getAppName)(options);
18
+ const appDistroClient = new client_1.AppDistributionClient();
19
+ let testCases;
20
+ try {
21
+ testCases = await appDistroClient.listTestCases(appName);
22
+ }
23
+ catch (err) {
24
+ throw new error_1.FirebaseError("Failed to list test cases.", {
25
+ exit: 1,
26
+ original: err,
27
+ });
28
+ }
29
+ fs.writeFileSync(yamlFile, (0, yaml_helper_1.toYaml)(testCases), "utf8");
30
+ utils.logSuccess(`Exported ${testCases.length} test cases to ${yamlFile}`);
31
+ return { testCases };
32
+ });
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const fs = require("fs-extra");
5
+ const command_1 = require("../command");
6
+ const yaml_helper_1 = require("../appdistribution/yaml_helper");
7
+ const requireAuth_1 = require("../requireAuth");
8
+ const client_1 = require("../appdistribution/client");
9
+ const options_parser_util_1 = require("../appdistribution/options-parser-util");
10
+ const utils = require("../utils");
11
+ const error_1 = require("../error");
12
+ exports.command = new command_1.Command("appdistribution:testcases:import <test-cases-yaml-file>")
13
+ .description("import test cases from YAML file")
14
+ .option("--app <app_id>", "the app id of your Firebase app")
15
+ .before(requireAuth_1.requireAuth)
16
+ .action(async (yamlFile, options) => {
17
+ const appName = (0, options_parser_util_1.getAppName)(options);
18
+ const appDistroClient = new client_1.AppDistributionClient();
19
+ (0, options_parser_util_1.ensureFileExists)(yamlFile);
20
+ const testCases = (0, yaml_helper_1.fromYaml)(appName, fs.readFileSync(yamlFile, "utf8"));
21
+ const testCasesWithoutName = testCases.filter((tc) => !tc.name);
22
+ const creationResults = await Promise.allSettled(testCasesWithoutName.map((tc) => appDistroClient.createTestCase(appName, tc)));
23
+ const failed = creationResults.filter((r) => r.status === "rejected");
24
+ if (failed.length > 0) {
25
+ for (const f of failed) {
26
+ utils.logWarning(f.reason);
27
+ }
28
+ const succeeded = creationResults.length - failed.length;
29
+ throw new error_1.FirebaseError(`Created ${succeeded} test case(s), but failed to create ${failed.length}.`);
30
+ }
31
+ const testCasesWithName = testCases.filter((tc) => !!tc.name);
32
+ await appDistroClient.batchUpsertTestCases(appName, testCasesWithName);
33
+ utils.logSuccess(`Imported ${testCases.length} test cases from ${yamlFile}`);
34
+ });
@@ -14,13 +14,12 @@ exports.command = new command_1.Command("appdistribution:testers:list [group]")
14
14
  .description("list testers in project")
15
15
  .before(requireAuth_1.requireAuth)
16
16
  .action(async (group, options) => {
17
- var _a;
18
17
  const projectName = await (0, options_parser_util_1.getProjectName)(options);
19
18
  const appDistroClient = new client_1.AppDistributionClient();
20
- let testersResponse;
19
+ let testers;
21
20
  const spinner = ora("Preparing the list of your App Distribution testers").start();
22
21
  try {
23
- testersResponse = await appDistroClient.listTesters(projectName, group);
22
+ testers = await appDistroClient.listTesters(projectName, group);
24
23
  }
25
24
  catch (err) {
26
25
  spinner.fail();
@@ -30,10 +29,9 @@ exports.command = new command_1.Command("appdistribution:testers:list [group]")
30
29
  });
31
30
  }
32
31
  spinner.succeed();
33
- const testers = (_a = testersResponse.testers) !== null && _a !== void 0 ? _a : [];
34
32
  printTestersTable(testers);
35
33
  utils.logSuccess(`Testers listed successfully`);
36
- return testersResponse;
34
+ return { testers };
37
35
  });
38
36
  function printTestersTable(testers) {
39
37
  var _a, _b;
@@ -20,11 +20,14 @@ function load(client) {
20
20
  client.appdistribution.testers.list = loadCommand("appdistribution-testers-list");
21
21
  client.appdistribution.testers.add = loadCommand("appdistribution-testers-add");
22
22
  client.appdistribution.testers.delete = loadCommand("appdistribution-testers-remove");
23
- client.appdistribution.group = {};
24
- client.appdistribution.group.list = loadCommand("appdistribution-groups-list");
25
- client.appdistribution.group.create = loadCommand("appdistribution-groups-create");
26
- client.appdistribution.group.delete = loadCommand("appdistribution-groups-delete");
27
- client.appdistribution.groups = client.appdistribution.group;
23
+ client.appdistribution.groups = {};
24
+ client.appdistribution.groups.list = loadCommand("appdistribution-groups-list");
25
+ client.appdistribution.groups.create = loadCommand("appdistribution-groups-create");
26
+ client.appdistribution.groups.delete = loadCommand("appdistribution-groups-delete");
27
+ client.appdistribution.group = client.appdistribution.groups;
28
+ client.appdistribution.testCases = {};
29
+ client.appdistribution.testCases.export = loadCommand("appdistribution-testcases-export");
30
+ client.appdistribution.testCases.import = loadCommand("appdistribution-testcases-import");
28
31
  client.apps = {};
29
32
  client.apps.create = loadCommand("apps-create");
30
33
  client.apps.list = loadCommand("apps-list");
@@ -105,13 +105,11 @@ if ((0, experiments_1.isEnabled)("apptesting")) {
105
105
  checked: false,
106
106
  });
107
107
  }
108
- if ((0, experiments_1.isEnabled)("ailogic")) {
109
- choices.push({
110
- value: "ailogic",
111
- name: "AI Logic: Set up Firebase AI Logic with app provisioning",
112
- checked: false,
113
- });
114
- }
108
+ choices.push({
109
+ value: "ailogic",
110
+ name: "AI Logic: Set up Firebase AI Logic with app provisioning",
111
+ checked: false,
112
+ });
115
113
  choices.push({
116
114
  value: "aitools",
117
115
  name: "AI Tools: Configure AI coding assistants to work with your Firebase project",
@@ -10,6 +10,7 @@ const errors_1 = require("./errors");
10
10
  const types_1 = require("../types");
11
11
  const emulatorLogger_1 = require("../emulatorLogger");
12
12
  const state_1 = require("./state");
13
+ const error_1 = require("../../error");
13
14
  exports.authOperations = {
14
15
  identitytoolkit: {
15
16
  getProjects,
@@ -2054,11 +2055,16 @@ async function fetchBlockingFunction(state, event, user, options = {}, oauthToke
2054
2055
  let status;
2055
2056
  let text;
2056
2057
  try {
2058
+ const signal = controller.signal;
2059
+ signal.reason = "";
2060
+ signal.throwIfAborted = () => {
2061
+ throw new error_1.FirebaseError("Aborted");
2062
+ };
2057
2063
  const res = await (0, node_fetch_1.default)(url, {
2058
2064
  method: "POST",
2059
2065
  headers: { "Content-Type": "application/json" },
2060
2066
  body: JSON.stringify(reqBody),
2061
- signal: controller.signal,
2067
+ signal,
2062
2068
  });
2063
2069
  ok = res.ok;
2064
2070
  status = res.status;
@@ -44,38 +44,38 @@
44
44
  }
45
45
  },
46
46
  "pubsub": {
47
- "version": "0.8.14",
48
- "expectedSize": 66786933,
49
- "expectedChecksum": "a9025b3e53fdeafd2969ccb3ba1e1d38",
50
- "expectedChecksumSHA256": "4fbeefd8ecb32b10e5ab522e5d8748e0c2b13891c471c0c327c04632dcc75a8d",
51
- "remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.14.zip",
52
- "downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.14.zip",
53
- "binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.14/pubsub-emulator/bin/cloud-pubsub-emulator"
47
+ "version": "0.8.17",
48
+ "expectedSize": 65162324,
49
+ "expectedChecksum": "a88ec6424e49af459b5c8a3657d69c06",
50
+ "expectedChecksumSHA256": "72a49d14ee6cd4c1a0fee4a46f520fe4d7465396a155c61abc0103905047edd3",
51
+ "remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.17.zip",
52
+ "downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.17.zip",
53
+ "binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.17/pubsub-emulator/bin/cloud-pubsub-emulator"
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.15.1",
58
- "expectedSize": 29946720,
59
- "expectedChecksum": "cc8d5dd053cc71adad0f640ac2627018",
60
- "expectedChecksumSHA256": "a3749396507678bc546987ef047a9d0c25145064503a9adc777229f1bc9b8c6f",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.15.1",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1"
57
+ "version": "2.16.0",
58
+ "expectedSize": 29963104,
59
+ "expectedChecksum": "ee4d81abfba3576c7c6c8082313acfd0",
60
+ "expectedChecksumSHA256": "8aed1dc6cc8c35ab23fdf10e519e9cf3d93c8a0ccb0ff7d5af3ddc41a4847e3e",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v2.16.0",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.16.0"
63
63
  },
64
64
  "win32": {
65
- "version": "2.15.1",
66
- "expectedSize": 30440960,
67
- "expectedChecksum": "57390c392101a13952ff2aea74b62787",
68
- "expectedChecksumSHA256": "905a9ab2200189dc31f23b5454d21803b0c672442f0ee4bb7db8286d159a86e0",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.15.1",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1.exe"
65
+ "version": "2.16.0",
66
+ "expectedSize": 30456320,
67
+ "expectedChecksum": "546019a713be28620a3b43d49fc9d4b0",
68
+ "expectedChecksumSHA256": "ec1de720e173f0a613fbbb51767c0288121aa2f80e45e6bad0f3d2aff12689ec",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v2.16.0",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.16.0.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.15.1",
74
- "expectedSize": 29868216,
75
- "expectedChecksum": "cc6ef676d8c5c3a3f74c0bf037083cf0",
76
- "expectedChecksumSHA256": "1dfe085c467637ebf6a71cc6c8e8bc11fac85202056b960633607627bd647a6d",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.15.1",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.1"
73
+ "version": "2.16.0",
74
+ "expectedSize": 29888696,
75
+ "expectedChecksum": "5ab8da956043a48b43199f07b94ac48c",
76
+ "expectedChecksumSHA256": "1a420f3d2672627eae2d9b0b6747b833517cfc08f7e80ae3a79389788691b67a",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v2.16.0",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.16.0"
79
79
  }
80
80
  }
81
81
  }
@@ -240,6 +240,10 @@ function initializeNetworkFiltering() {
240
240
  }
241
241
  async function initializeFirebaseFunctionsStubs() {
242
242
  const firebaseFunctionsResolution = await assertResolveDeveloperNodeModule("firebase-functions");
243
+ if ((0, functionsEmulatorUtils_1.compareVersionStrings)(firebaseFunctionsResolution.version, "7.0.0") >= 0) {
244
+ logDebug("Detected firebase-functions v7+, skipping legacy stubs.");
245
+ return;
246
+ }
243
247
  const firebaseFunctionsRoot = (0, functionsEmulatorShared_1.findModuleRoot)("firebase-functions", firebaseFunctionsResolution.resolution);
244
248
  const httpsProviderResolution = path.join(firebaseFunctionsRoot, "lib/providers/https");
245
249
  const httpsProviderV1Resolution = path.join(firebaseFunctionsRoot, "lib/v1/providers/https");
@@ -451,6 +455,10 @@ function warnAboutStorageProd() {
451
455
  }
452
456
  async function initializeFunctionsConfigHelper() {
453
457
  const functionsResolution = await assertResolveDeveloperNodeModule("firebase-functions");
458
+ if ((0, functionsEmulatorUtils_1.compareVersionStrings)(functionsResolution.version, "7.0.0") >= 0) {
459
+ logDebug("Detected firebase-functions v7+, skipping config helper.");
460
+ return;
461
+ }
454
462
  const localFunctionsModule = require(functionsResolution.resolution);
455
463
  logDebug("Checked functions.config()", {
456
464
  config: localFunctionsModule.config(),
@@ -4,6 +4,7 @@ exports.TaskQueue = exports.TaskStatus = exports.Queue = void 0;
4
4
  const abort_controller_1 = require("abort-controller");
5
5
  const emulatorLogger_1 = require("./emulatorLogger");
6
6
  const types_1 = require("./types");
7
+ const error_1 = require("../error");
7
8
  const node_fetch_1 = require("node-fetch");
8
9
  class Node {
9
10
  constructor(data) {
@@ -198,6 +199,10 @@ class TaskQueue {
198
199
  }
199
200
  const controller = new abort_controller_1.default();
200
201
  const signal = controller.signal;
202
+ signal.reason = "";
203
+ signal.throwIfAborted = () => {
204
+ throw new error_1.FirebaseError("Aborted");
205
+ };
201
206
  const request = (0, node_fetch_1.default)(emulatedTask.task.httpRequest.url, {
202
207
  method: "POST",
203
208
  headers: headers,
@@ -118,12 +118,6 @@ exports.ALL_EXPERIMENTS = experiments({
118
118
  shortDescription: "Adds experimental App Testing feature",
119
119
  public: true,
120
120
  },
121
- ailogic: {
122
- shortDescription: "Enable Firebase AI Logic feature for existing apps",
123
- fullDescription: "Enables the AI Logic initialization feature that provisions AI Logic for existing Firebase apps.",
124
- public: true,
125
- default: false,
126
- },
127
121
  });
128
122
  function isValidExperiment(name) {
129
123
  return Object.keys(exports.ALL_EXPERIMENTS).includes(name);
@@ -22,16 +22,34 @@ class FirestoreApi {
22
22
  this.printer = new pretty_print_1.PrettyPrint();
23
23
  }
24
24
  static processIndex(index) {
25
- var _a, _b, _c;
26
- const fields = index.fields;
25
+ var _a;
26
+ let fields = index.fields;
27
+ const suffixOrder = FirestoreApi.lastIndexFieldOrder(fields);
28
+ const nameSuffix = { fieldPath: "__name__", order: suffixOrder };
27
29
  const lastField = (_a = index.fields) === null || _a === void 0 ? void 0 : _a[index.fields.length - 1];
30
+ if (lastField.vectorConfig) {
31
+ const vectorField = lastField;
32
+ fields = fields.slice(0, -1);
33
+ if (fields.length === 0 || (fields === null || fields === void 0 ? void 0 : fields[fields.length - 1].fieldPath) !== "__name__") {
34
+ fields.push(nameSuffix);
35
+ }
36
+ fields.push(vectorField);
37
+ return Object.assign(Object.assign({}, index), { fields });
38
+ }
28
39
  if ((lastField === null || lastField === void 0 ? void 0 : lastField.fieldPath) !== "__name__") {
29
- const defaultDirection = (_c = (_b = index.fields) === null || _b === void 0 ? void 0 : _b[index.fields.length - 1]) === null || _c === void 0 ? void 0 : _c.order;
30
- const nameSuffix = { fieldPath: "__name__", order: defaultDirection };
31
40
  fields.push(nameSuffix);
32
41
  }
33
42
  return Object.assign(Object.assign({}, index), { fields });
34
43
  }
44
+ static lastIndexFieldOrder(fields) {
45
+ let lastIndexFieldOrder = types.Order.ASCENDING;
46
+ for (const field of fields) {
47
+ if (field.order) {
48
+ lastIndexFieldOrder = field.order;
49
+ }
50
+ }
51
+ return lastIndexFieldOrder;
52
+ }
35
53
  async deploy(options, indexes, fieldOverrides, databaseId = "(default)") {
36
54
  var _a;
37
55
  const spec = this.upgradeOldSpec({
@@ -29,11 +29,11 @@ const HOSTING_GITHUB_ACTION_NAME = "FirebaseExtended/action-hosting-deploy@v0";
29
29
  const SERVICE_ACCOUNT_MAX_KEY_NUMBER = 10;
30
30
  const githubApiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.githubApiOrigin)(), auth: false });
31
31
  async function initGitHub(setup) {
32
- var _a, _b, _c, _d, _e;
32
+ var _a, _b, _c, _d, _e, _f, _g;
33
33
  if (!setup.projectId) {
34
34
  return (0, utils_1.reject)("Could not determine Project ID, can't set up GitHub workflow.", { exit: 1 });
35
35
  }
36
- if (!setup.config.hosting) {
36
+ if (!setup.config.hosting && !((_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.hosting)) {
37
37
  return (0, utils_1.reject)(`Didn't find a Hosting config in firebase.json. Run ${(0, colorette_1.bold)("firebase init hosting")} instead.`);
38
38
  }
39
39
  logger_1.logger.info();
@@ -67,10 +67,10 @@ async function initGitHub(setup) {
67
67
  (0, utils_1.logSuccess)(`Uploaded service account JSON to GitHub as secret ${(0, colorette_1.bold)(githubSecretName)}.`);
68
68
  (0, utils_1.logBullet)(`You can manage your secrets at https://github.com/${repo}/settings/secrets.`);
69
69
  logger_1.logger.info();
70
- if (!Array.isArray(setup.config.hosting) && setup.config.hosting.predeploy) {
70
+ if (!Array.isArray(setup.config.hosting) && ((_b = setup.config.hosting) === null || _b === void 0 ? void 0 : _b.predeploy)) {
71
71
  (0, utils_1.logBullet)(`You have a predeploy script configured in firebase.json.`);
72
72
  }
73
- const { script } = await promptForBuildScript((_a = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _a === void 0 ? void 0 : _a.useWebFrameworks);
73
+ const { script } = await promptForBuildScript((_c = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _c === void 0 ? void 0 : _c.useWebFrameworks);
74
74
  const ymlDeployDoc = loadYMLDeploy();
75
75
  let shouldWriteYMLHostingFile = true;
76
76
  let shouldWriteYMLDeployFile = false;
@@ -81,7 +81,7 @@ async function initGitHub(setup) {
81
81
  shouldWriteYMLHostingFile = overwrite;
82
82
  }
83
83
  if (shouldWriteYMLHostingFile) {
84
- writeChannelActionYMLFile(YML_FULL_PATH_PULL_REQUEST, githubSecretName, setup.projectId, script, (_b = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _b === void 0 ? void 0 : _b.useWebFrameworks, (_c = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _c === void 0 ? void 0 : _c.source);
84
+ writeChannelActionYMLFile(YML_FULL_PATH_PULL_REQUEST, githubSecretName, setup.projectId, script, (_d = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _d === void 0 ? void 0 : _d.useWebFrameworks, (_e = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _e === void 0 ? void 0 : _e.source);
85
85
  logger_1.logger.info();
86
86
  (0, utils_1.logSuccess)(`Created workflow file ${(0, colorette_1.bold)(YML_FULL_PATH_PULL_REQUEST)}`);
87
87
  }
@@ -101,7 +101,7 @@ async function initGitHub(setup) {
101
101
  shouldWriteYMLDeployFile = true;
102
102
  }
103
103
  if (shouldWriteYMLDeployFile) {
104
- writeDeployToProdActionYMLFile(YML_FULL_PATH_MERGE, branch, githubSecretName, setup.projectId, script, (_d = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _d === void 0 ? void 0 : _d.useWebFrameworks, (_e = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _e === void 0 ? void 0 : _e.source);
104
+ writeDeployToProdActionYMLFile(YML_FULL_PATH_MERGE, branch, githubSecretName, setup.projectId, script, (_f = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _f === void 0 ? void 0 : _f.useWebFrameworks, (_g = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _g === void 0 ? void 0 : _g.source);
105
105
  logger_1.logger.info();
106
106
  (0, utils_1.logSuccess)(`Created workflow file ${(0, colorette_1.bold)(YML_FULL_PATH_MERGE)}`);
107
107
  }
@@ -48,10 +48,10 @@ async function askQuestions(setup, config, options) {
48
48
  setup.featureInfo.hosting.newSiteId = await (0, interactive_1.pickHostingSiteName)("", createOptions);
49
49
  }
50
50
  }
51
+ let discoveredFramework = experiments.isEnabled("webframeworks")
52
+ ? await (0, frameworks_1.discover)(config.projectDir, false)
53
+ : undefined;
51
54
  if (experiments.isEnabled("webframeworks")) {
52
- let discoveredFramework = experiments.isEnabled("webframeworks")
53
- ? await (0, frameworks_1.discover)(config.projectDir, false)
54
- : undefined;
55
55
  if (discoveredFramework &&
56
56
  (await (0, prompt_1.confirm)({
57
57
  message: `Detected an existing ${frameworks_1.WebFrameworks[discoveredFramework.framework].name} codebase in the current directory, do you want to use this?`,
@@ -65,43 +65,43 @@ async function askQuestions(setup, config, options) {
65
65
  else {
66
66
  setup.featureInfo.hosting.useWebFrameworks = await (0, prompt_1.confirm)(`Do you want to use a web framework? (${clc.bold("experimental")})`);
67
67
  }
68
- if (setup.featureInfo.hosting.useWebFrameworks) {
69
- (_a = (_g = setup.featureInfo.hosting).source) !== null && _a !== void 0 ? _a : (_g.source = await (0, prompt_1.input)({
70
- message: "What folder would you like to use for your web application's root directory?",
71
- default: "hosting",
68
+ }
69
+ if (setup.featureInfo.hosting.useWebFrameworks) {
70
+ (_a = (_g = setup.featureInfo.hosting).source) !== null && _a !== void 0 ? _a : (_g.source = await (0, prompt_1.input)({
71
+ message: "What folder would you like to use for your web application's root directory?",
72
+ default: "hosting",
73
+ }));
74
+ discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source));
75
+ if (discoveredFramework) {
76
+ const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
77
+ (_b = (_h = setup.featureInfo.hosting).useDiscoveredFramework) !== null && _b !== void 0 ? _b : (_h.useDiscoveredFramework = await (0, prompt_1.confirm)({
78
+ message: `Detected an existing ${name} codebase in ${setup.featureInfo.hosting.source}, should we use this?`,
79
+ default: true,
72
80
  }));
73
- discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source));
74
- if (discoveredFramework) {
75
- const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
76
- (_b = (_h = setup.featureInfo.hosting).useDiscoveredFramework) !== null && _b !== void 0 ? _b : (_h.useDiscoveredFramework = await (0, prompt_1.confirm)({
77
- message: `Detected an existing ${name} codebase in ${setup.featureInfo.hosting.source}, should we use this?`,
78
- default: true,
79
- }));
80
- if (setup.featureInfo.hosting.useDiscoveredFramework)
81
- setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
82
- }
83
- const choices = [];
84
- for (const value in frameworks_1.WebFrameworks) {
85
- if (frameworks_1.WebFrameworks[value]) {
86
- const { name, init } = frameworks_1.WebFrameworks[value];
87
- if (init)
88
- choices.push({ name, value });
89
- }
81
+ if (setup.featureInfo.hosting.useDiscoveredFramework)
82
+ setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
83
+ }
84
+ const choices = [];
85
+ for (const value in frameworks_1.WebFrameworks) {
86
+ if (frameworks_1.WebFrameworks[value]) {
87
+ const { name, init } = frameworks_1.WebFrameworks[value];
88
+ if (init)
89
+ choices.push({ name, value });
90
90
  }
91
- const defaultChoice = (_c = choices.find(({ value }) => value === (discoveredFramework === null || discoveredFramework === void 0 ? void 0 : discoveredFramework.framework))) === null || _c === void 0 ? void 0 : _c.value;
92
- (_d = (_j = setup.featureInfo.hosting).webFramework) !== null && _d !== void 0 ? _d : (_j.webFramework = await (0, prompt_1.select)({
93
- message: "Please choose the framework:",
94
- default: defaultChoice,
95
- choices,
96
- }));
97
- setup.featureInfo.hosting.region =
98
- setup.featureInfo.hosting.region ||
99
- (await (0, prompt_1.select)({
100
- message: "In which region would you like to host server-side content, if applicable?",
101
- default: constants_1.DEFAULT_REGION,
102
- choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
103
- }));
104
91
  }
92
+ const defaultChoice = (_c = choices.find(({ value }) => value === (discoveredFramework === null || discoveredFramework === void 0 ? void 0 : discoveredFramework.framework))) === null || _c === void 0 ? void 0 : _c.value;
93
+ (_d = (_j = setup.featureInfo.hosting).webFramework) !== null && _d !== void 0 ? _d : (_j.webFramework = await (0, prompt_1.select)({
94
+ message: "Please choose the framework:",
95
+ default: defaultChoice,
96
+ choices,
97
+ }));
98
+ setup.featureInfo.hosting.region =
99
+ setup.featureInfo.hosting.region ||
100
+ (await (0, prompt_1.select)({
101
+ message: "In which region would you like to host server-side content, if applicable?",
102
+ default: constants_1.DEFAULT_REGION,
103
+ choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
104
+ }));
105
105
  }
106
106
  else {
107
107
  logger_1.logger.info();
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enhanceProvisioningError = void 0;
4
+ const error_1 = require("../../error");
5
+ function isErrorInfo(detail) {
6
+ return detail["@type"] === "type.googleapis.com/google.rpc.ErrorInfo";
7
+ }
8
+ function isHelpLinks(detail) {
9
+ return detail["@type"] === "type.googleapis.com/google.rpc.Help";
10
+ }
11
+ function extractErrorDetails(err) {
12
+ var _a;
13
+ if (!(err instanceof Error)) {
14
+ return "";
15
+ }
16
+ if (err instanceof error_1.FirebaseError && err.context) {
17
+ const context = err.context;
18
+ const errorBody = (_a = context.body) === null || _a === void 0 ? void 0 : _a.error;
19
+ if ((errorBody === null || errorBody === void 0 ? void 0 : errorBody.details) && Array.isArray(errorBody.details)) {
20
+ const parts = [];
21
+ for (const detail of errorBody.details) {
22
+ if (isErrorInfo(detail)) {
23
+ parts.push(`Error details:`);
24
+ parts.push(` Reason: ${detail.reason}`);
25
+ parts.push(` Domain: ${detail.domain}`);
26
+ if (detail.metadata) {
27
+ parts.push(` Additional Info: ${JSON.stringify(detail.metadata)}`);
28
+ }
29
+ }
30
+ else if (isHelpLinks(detail)) {
31
+ parts.push(`\nFor help resolving this issue:`);
32
+ for (const link of detail.links) {
33
+ parts.push(` - ${link.description}`);
34
+ parts.push(` ${link.url}`);
35
+ }
36
+ }
37
+ }
38
+ return parts.length > 0 ? `\n\n${parts.join("\n")}` : "";
39
+ }
40
+ }
41
+ return "";
42
+ }
43
+ function enhanceProvisioningError(err, contextMessage) {
44
+ const originalError = (0, error_1.getError)(err);
45
+ const errorDetails = extractErrorDetails(err);
46
+ const fullMessage = errorDetails
47
+ ? `${contextMessage}: ${originalError.message}${errorDetails}`
48
+ : `${contextMessage}: ${originalError.message}`;
49
+ return new error_1.FirebaseError(fullMessage, {
50
+ exit: 2,
51
+ original: originalError,
52
+ });
53
+ }
54
+ exports.enhanceProvisioningError = enhanceProvisioningError;
@@ -7,6 +7,7 @@ const error_1 = require("../../error");
7
7
  const logger_1 = require("../../logger");
8
8
  const operation_poller_1 = require("../../operation-poller");
9
9
  const apps_1 = require("../apps");
10
+ const errorHandler_1 = require("./errorHandler");
10
11
  const apiClient = new apiv2_1.Client({
11
12
  urlPrefix: (0, api_1.firebaseApiOrigin)(),
12
13
  apiVersion: "v1alpha",
@@ -100,11 +101,7 @@ async function provisionFirebaseApp(options) {
100
101
  return result;
101
102
  }
102
103
  catch (err) {
103
- const errorMessage = err instanceof Error ? err.message : String(err);
104
- throw new error_1.FirebaseError(`Failed to provision Firebase app: ${errorMessage}`, {
105
- exit: 2,
106
- original: err instanceof Error ? err : new Error(String(err)),
107
- });
104
+ throw (0, errorHandler_1.enhanceProvisioningError)(err, "Failed to provision Firebase app");
108
105
  }
109
106
  }
110
107
  exports.provisionFirebaseApp = provisionFirebaseApp;
@@ -2,4 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.functionsTools = void 0;
4
4
  const get_logs_1 = require("./get_logs");
5
- exports.functionsTools = [get_logs_1.get_logs];
5
+ const list_functions_1 = require("./list_functions");
6
+ exports.functionsTools = [get_logs_1.get_logs, list_functions_1.list_functions];
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.list_functions = void 0;
4
+ const zod_1 = require("zod");
5
+ const tool_1 = require("../../tool");
6
+ const util_1 = require("../../util");
7
+ const backend = require("../../../deploy/functions/backend");
8
+ const error_1 = require("../../../error");
9
+ exports.list_functions = (0, tool_1.tool)("functions", {
10
+ name: "list_functions",
11
+ description: "List all deployed functions in your Firebase project.",
12
+ inputSchema: zod_1.z.object({}),
13
+ annotations: {
14
+ title: "List Deployed Functions",
15
+ readOnlyHint: true,
16
+ openWorldHint: true,
17
+ },
18
+ _meta: {
19
+ requiresAuth: true,
20
+ requiresProject: true,
21
+ },
22
+ }, async (_, { projectId }) => {
23
+ const context = {
24
+ projectId,
25
+ };
26
+ try {
27
+ const existing = await backend.existingBackend(context);
28
+ const endpointsList = backend.allEndpoints(existing).sort(backend.compareFunctions);
29
+ const formattedList = endpointsList.map((endpoint) => ({
30
+ function: endpoint.id,
31
+ version: endpoint.platform === "gcfv2" ? "v2" : "v1",
32
+ trigger: backend.endpointTriggerType(endpoint),
33
+ location: endpoint.region,
34
+ memory: endpoint.availableMemoryMb || "---",
35
+ runtime: endpoint.runtime,
36
+ }));
37
+ if (!formattedList.length) {
38
+ return (0, util_1.toContent)([], {
39
+ contentPrefix: "No functions found in this project.\n\n",
40
+ });
41
+ }
42
+ return (0, util_1.toContent)(formattedList);
43
+ }
44
+ catch (err) {
45
+ const errMsg = (0, error_1.getErrMsg)((err === null || err === void 0 ? void 0 : err.original) || err, "Failed to list functions.");
46
+ return (0, util_1.mcpError)(errMsg);
47
+ }
48
+ });
package/lib/utils.js CHANGED
@@ -357,7 +357,10 @@ function datetimeString(d) {
357
357
  }
358
358
  exports.datetimeString = datetimeString;
359
359
  function isCloudEnvironment() {
360
- return !!process.env.CODESPACES || !!process.env.GOOGLE_CLOUD_WORKSTATIONS;
360
+ return (!!process.env.CODESPACES ||
361
+ !!process.env.GOOGLE_CLOUD_WORKSTATIONS ||
362
+ !!process.env.CLOUD_SHELL ||
363
+ !!process.env.GOOGLE_CLOUD_SHELL);
361
364
  }
362
365
  exports.isCloudEnvironment = isCloudEnvironment;
363
366
  function isRunningInWSL() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "14.22.0",
3
+ "version": "14.23.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -66,7 +66,7 @@
66
66
  "@electric-sql/pglite": "^0.3.3",
67
67
  "@electric-sql/pglite-tools": "^0.2.8",
68
68
  "@google-cloud/cloud-sql-connector": "^1.3.3",
69
- "@google-cloud/pubsub": "^4.5.0",
69
+ "@google-cloud/pubsub": "^4.11.0",
70
70
  "@inquirer/prompts": "^7.4.0",
71
71
  "@modelcontextprotocol/sdk": "^1.10.2",
72
72
  "abort-controller": "^3.0.0",
@@ -1090,7 +1090,6 @@
1090
1090
  },
1091
1091
  "properties": {
1092
1092
  "$schema": {
1093
- "format": "uri",
1094
1093
  "type": "string"
1095
1094
  },
1096
1095
  "apphosting": {