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.
- package/README.md +13 -5
- package/lib/apiv2.js +12 -2
- package/lib/appUtils.js +32 -13
- package/lib/appdistribution/client.js +62 -16
- package/lib/appdistribution/types.js +1 -12
- package/lib/appdistribution/yaml_helper.js +69 -0
- package/lib/commands/appdistribution-groups-list.js +3 -5
- package/lib/commands/appdistribution-testcases-export.js +32 -0
- package/lib/commands/appdistribution-testcases-import.js +34 -0
- package/lib/commands/appdistribution-testers-list.js +3 -5
- package/lib/commands/dataconnect-sdk-generate.js +3 -2
- package/lib/commands/index.js +8 -5
- package/lib/commands/init.js +5 -7
- package/lib/deploy/functions/services/dataconnect.js +14 -0
- package/lib/deploy/functions/services/index.js +11 -0
- package/lib/emulator/auth/operations.js +7 -1
- package/lib/emulator/downloadableEmulatorInfo.json +33 -25
- package/lib/emulator/downloadableEmulators.js +59 -47
- package/lib/emulator/functionsEmulatorRuntime.js +8 -0
- package/lib/emulator/taskQueue.js +5 -0
- package/lib/experiments.js +0 -6
- package/lib/firestore/api.js +22 -4
- package/lib/functions/events/v2.js +2 -1
- package/lib/init/features/dataconnect/sdk.js +18 -0
- package/lib/init/features/hosting/github.js +6 -6
- package/lib/init/features/hosting/index.js +37 -37
- package/lib/management/provisioning/errorHandler.js +54 -0
- package/lib/management/provisioning/provision.js +2 -5
- package/lib/mcp/tools/crashlytics/events.js +92 -9
- package/lib/mcp/tools/functions/index.js +2 -1
- package/lib/mcp/tools/functions/list_functions.js +48 -0
- package/lib/utils.js +4 -1
- package/package.json +2 -2
- package/schema/connector-yaml.json +30 -0
- 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] [](https://cursor.com/en/install-mcp?name=firebase&config=
|
|
1
|
+
# Firebase CLI and MCP Server [![Actions Status][gh-actions-badge]][gh-actions] [![Node Version][node-badge]][npm] [![NPM version][npm-badge]][npm] [](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
|
|
91
|
-
|
|
|
92
|
-
| **appdistribution:distribute**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 [...
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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.
|
|
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
|
|
20
|
+
let groups;
|
|
22
21
|
const spinner = ora("Preparing the list of your App Distribution Groups").start();
|
|
23
22
|
try {
|
|
24
|
-
|
|
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
|
|
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
|
|
19
|
+
let testers;
|
|
21
20
|
const spinner = ora("Preparing the list of your App Distribution testers").start();
|
|
22
21
|
try {
|
|
23
|
-
|
|
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
|
|
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) {
|
package/lib/commands/index.js
CHANGED
|
@@ -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.
|
|
24
|
-
client.appdistribution.
|
|
25
|
-
client.appdistribution.
|
|
26
|
-
client.appdistribution.
|
|
27
|
-
client.appdistribution.
|
|
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");
|
package/lib/commands/init.js
CHANGED
|
@@ -105,13 +105,11 @@ if ((0, experiments_1.isEnabled)("apptesting")) {
|
|
|
105
105
|
checked: false,
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
2067
|
+
signal,
|
|
2062
2068
|
});
|
|
2063
2069
|
ok = res.ok;
|
|
2064
2070
|
status = res.status;
|