firebase-tools 14.22.0 → 14.24.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 (35) hide show
  1. package/README.md +13 -5
  2. package/lib/apiv2.js +12 -2
  3. package/lib/appUtils.js +32 -13
  4. package/lib/appdistribution/client.js +62 -16
  5. package/lib/appdistribution/types.js +1 -12
  6. package/lib/appdistribution/yaml_helper.js +69 -0
  7. package/lib/commands/appdistribution-groups-list.js +3 -5
  8. package/lib/commands/appdistribution-testcases-export.js +32 -0
  9. package/lib/commands/appdistribution-testcases-import.js +34 -0
  10. package/lib/commands/appdistribution-testers-list.js +3 -5
  11. package/lib/commands/dataconnect-sdk-generate.js +3 -2
  12. package/lib/commands/index.js +8 -5
  13. package/lib/commands/init.js +5 -7
  14. package/lib/deploy/functions/services/dataconnect.js +14 -0
  15. package/lib/deploy/functions/services/index.js +11 -0
  16. package/lib/emulator/auth/operations.js +7 -1
  17. package/lib/emulator/downloadableEmulatorInfo.json +33 -25
  18. package/lib/emulator/downloadableEmulators.js +59 -47
  19. package/lib/emulator/functionsEmulatorRuntime.js +8 -0
  20. package/lib/emulator/taskQueue.js +5 -0
  21. package/lib/experiments.js +0 -6
  22. package/lib/firestore/api.js +22 -4
  23. package/lib/functions/events/v2.js +2 -1
  24. package/lib/init/features/dataconnect/sdk.js +18 -0
  25. package/lib/init/features/hosting/github.js +6 -6
  26. package/lib/init/features/hosting/index.js +37 -37
  27. package/lib/management/provisioning/errorHandler.js +54 -0
  28. package/lib/management/provisioning/provision.js +2 -5
  29. package/lib/mcp/tools/crashlytics/events.js +92 -9
  30. package/lib/mcp/tools/functions/index.js +2 -1
  31. package/lib/mcp/tools/functions/list_functions.js +48 -0
  32. package/lib/utils.js +4 -1
  33. package/package.json +2 -2
  34. package/schema/connector-yaml.json +30 -0
  35. package/schema/firebase-config.json +0 -1
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;
package/lib/appUtils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectFiles = exports.extractAppIdentifiersAndroid = exports.extractAppIdentifierIos = exports.extractAppIdentifiersFlutter = exports.detectApps = exports.getPlatformsFromFolder = exports.appDescription = exports.Framework = exports.Platform = void 0;
3
+ exports.detectFiles = exports.extractAppIdentifiersAndroid = exports.extractAppIdentifierIos = exports.extractAppIdentifiersFlutter = exports.getAllDepsFromPackageJson = exports.detectApps = exports.getPlatformsFromFolder = exports.appDescription = exports.Framework = exports.Platform = void 0;
4
4
  const fs = require("fs-extra");
5
5
  const path = require("path");
6
6
  const glob_1 = require("glob");
@@ -10,6 +10,7 @@ var Platform;
10
10
  Platform["WEB"] = "WEB";
11
11
  Platform["IOS"] = "IOS";
12
12
  Platform["FLUTTER"] = "FLUTTER";
13
+ Platform["ADMIN_NODE"] = "ADMIN_NODE";
13
14
  })(Platform = exports.Platform || (exports.Platform = {}));
14
15
  var Framework;
15
16
  (function (Framework) {
@@ -30,7 +31,9 @@ async function detectApps(dirPath) {
30
31
  const pubSpecYamlFiles = await detectFiles(dirPath, "pubspec.yaml");
31
32
  const srcMainFolders = await detectFiles(dirPath, "src/main/");
32
33
  const xCodeProjects = await detectFiles(dirPath, "*.xcodeproj/");
33
- const webApps = await Promise.all(packageJsonFiles.map((p) => packageJsonToWebApp(dirPath, p)));
34
+ const adminAndWebApps = (await Promise.all(packageJsonFiles.map((p) => packageJsonToAdminOrWebApp(dirPath, p)))).flat();
35
+ console.log("packageJsonFiles", packageJsonFiles);
36
+ console.log("adminAndWebApps", adminAndWebApps);
34
37
  const flutterAppPromises = await Promise.all(pubSpecYamlFiles.map((f) => processFlutterDir(dirPath, f)));
35
38
  const flutterApps = flutterAppPromises.flat();
36
39
  const androidAppPromises = await Promise.all(srcMainFolders.map((f) => processAndroidDir(dirPath, f)));
@@ -41,7 +44,7 @@ async function detectApps(dirPath) {
41
44
  const iosApps = iosAppPromises
42
45
  .flat()
43
46
  .filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory)));
44
- return [...webApps, ...flutterApps, ...androidApps, ...iosApps];
47
+ return [...flutterApps, ...androidApps, ...iosApps, ...adminAndWebApps];
45
48
  }
46
49
  exports.detectApps = detectApps;
47
50
  async function processIosDir(dirPath, filePath) {
@@ -108,14 +111,33 @@ function isPathInside(parent, child) {
108
111
  const relativePath = path.relative(parent, child);
109
112
  return !relativePath.startsWith(`..`);
110
113
  }
111
- async function packageJsonToWebApp(dirPath, packageJsonFile) {
114
+ function getAllDepsFromPackageJson(packageJson) {
115
+ var _a, _b;
116
+ const devDependencies = Object.keys((_a = packageJson.devDependencies) !== null && _a !== void 0 ? _a : {});
117
+ const dependencies = Object.keys((_b = packageJson.dependencies) !== null && _b !== void 0 ? _b : {});
118
+ const allDeps = Array.from(new Set([...devDependencies, ...dependencies]));
119
+ return allDeps;
120
+ }
121
+ exports.getAllDepsFromPackageJson = getAllDepsFromPackageJson;
122
+ async function packageJsonToAdminOrWebApp(dirPath, packageJsonFile) {
112
123
  const fullPath = path.join(dirPath, packageJsonFile);
113
124
  const packageJson = JSON.parse((await fs.readFile(fullPath)).toString());
114
- return {
115
- platform: Platform.WEB,
116
- directory: path.dirname(packageJsonFile),
117
- frameworks: getFrameworksFromPackageJson(packageJson),
118
- };
125
+ const allDeps = getAllDepsFromPackageJson(packageJson);
126
+ const detectedApps = [];
127
+ if (allDeps.includes("firebase-admin") || allDeps.includes("firebase-functions")) {
128
+ detectedApps.push({
129
+ platform: Platform.ADMIN_NODE,
130
+ directory: path.dirname(packageJsonFile),
131
+ });
132
+ }
133
+ if (allDeps.includes("firebase") || detectedApps.length === 0) {
134
+ detectedApps.push({
135
+ platform: Platform.WEB,
136
+ directory: path.dirname(packageJsonFile),
137
+ frameworks: getFrameworksFromPackageJson(packageJson),
138
+ });
139
+ }
140
+ return detectedApps;
119
141
  }
120
142
  const WEB_FRAMEWORKS = Object.values(Framework);
121
143
  const WEB_FRAMEWORKS_SIGNALS = {
@@ -148,10 +170,7 @@ async function detectAppIdsForPlatform(dirPath, platform) {
148
170
  return allAppIds.flat();
149
171
  }
150
172
  function getFrameworksFromPackageJson(packageJson) {
151
- var _a, _b;
152
- const devDependencies = Object.keys((_a = packageJson.devDependencies) !== null && _a !== void 0 ? _a : {});
153
- const dependencies = Object.keys((_b = packageJson.dependencies) !== null && _b !== void 0 ? _b : {});
154
- const allDeps = Array.from(new Set([...devDependencies, ...dependencies]));
173
+ const allDeps = getAllDepsFromPackageJson(packageJson);
155
174
  return WEB_FRAMEWORKS.filter((framework) => WEB_FRAMEWORKS_SIGNALS[framework].find((dep) => allDeps.includes(dep)));
156
175
  }
157
176
  function extractAppIdentifiersFlutter(fileContent) {
@@ -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;
@@ -72,11 +72,12 @@ exports.command = new command_1.Command("dataconnect:sdk:generate")
72
72
  async function loadAllWithSDKs(projectId, config) {
73
73
  const serviceInfos = await (0, load_1.loadAll)(projectId || hub_1.EmulatorHub.MISSING_PROJECT_PLACEHOLDER, config);
74
74
  return serviceInfos.filter((serviceInfo) => serviceInfo.connectorInfo.some((c) => {
75
- var _a, _b, _c, _d;
75
+ var _a, _b, _c, _d, _e;
76
76
  return (((_a = c.connectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk) ||
77
77
  ((_b = c.connectorYaml.generate) === null || _b === void 0 ? void 0 : _b.kotlinSdk) ||
78
78
  ((_c = c.connectorYaml.generate) === null || _c === void 0 ? void 0 : _c.swiftSdk) ||
79
- ((_d = c.connectorYaml.generate) === null || _d === void 0 ? void 0 : _d.dartSdk));
79
+ ((_d = c.connectorYaml.generate) === null || _d === void 0 ? void 0 : _d.dartSdk) ||
80
+ ((_e = c.connectorYaml.generate) === null || _e === void 0 ? void 0 : _e.adminNodeSdk));
80
81
  }));
81
82
  }
82
83
  async function generateSDKsInAll(options, serviceInfosWithSDKs, justRanInit) {
@@ -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",
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureDataConnectTriggerRegion = void 0;
4
+ const error_1 = require("../../../error");
5
+ function ensureDataConnectTriggerRegion(endpoint) {
6
+ if (!endpoint.eventTrigger.region) {
7
+ endpoint.eventTrigger.region = endpoint.region;
8
+ }
9
+ if (endpoint.eventTrigger.region !== endpoint.region) {
10
+ throw new error_1.FirebaseError("The Firebase Data Connect trigger location must match the function region.");
11
+ }
12
+ return Promise.resolve();
13
+ }
14
+ exports.ensureDataConnectTriggerRegion = ensureDataConnectTriggerRegion;
@@ -9,6 +9,7 @@ const database_1 = require("./database");
9
9
  const remoteConfig_1 = require("./remoteConfig");
10
10
  const testLab_1 = require("./testLab");
11
11
  const firestore_1 = require("./firestore");
12
+ const dataconnect_1 = require("./dataconnect");
12
13
  const noop = () => Promise.resolve();
13
14
  exports.noop = noop;
14
15
  const noopProjectBindings = () => Promise.resolve([]);
@@ -85,6 +86,15 @@ const firestoreService = {
85
86
  registerTrigger: exports.noop,
86
87
  unregisterTrigger: exports.noop,
87
88
  };
89
+ const dataconnectService = {
90
+ name: "dataconnect",
91
+ api: "firebasedataconnect.googleapis.com",
92
+ requiredProjectBindings: exports.noopProjectBindings,
93
+ ensureTriggerRegion: dataconnect_1.ensureDataConnectTriggerRegion,
94
+ validateTrigger: exports.noop,
95
+ registerTrigger: exports.noop,
96
+ unregisterTrigger: exports.noop,
97
+ };
88
98
  const EVENT_SERVICE_MAPPING = {
89
99
  "google.cloud.pubsub.topic.v1.messagePublished": pubSubService,
90
100
  "google.cloud.storage.object.v1.finalized": storageService,
@@ -110,6 +120,7 @@ const EVENT_SERVICE_MAPPING = {
110
120
  "google.cloud.firestore.document.v1.created.withAuthContext": firestoreService,
111
121
  "google.cloud.firestore.document.v1.updated.withAuthContext": firestoreService,
112
122
  "google.cloud.firestore.document.v1.deleted.withAuthContext": firestoreService,
123
+ "google.firebase.dataconnect.connector.v1.mutationExecuted": dataconnectService,
113
124
  };
114
125
  function serviceForEndpoint(endpoint) {
115
126
  if (backend.isEventTriggered(endpoint)) {
@@ -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;