firebase-tools 14.23.0 → 14.24.1
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/lib/appUtils.js +30 -13
- package/lib/appdistribution/client.js +2 -1
- package/lib/appdistribution/distribution.js +85 -2
- package/lib/appdistribution/options-parser-util.js +8 -5
- package/lib/commands/appdistribution-distribute.js +12 -85
- package/lib/commands/dataconnect-sdk-generate.js +3 -2
- package/lib/deploy/functions/services/dataconnect.js +14 -0
- package/lib/deploy/functions/services/index.js +11 -0
- package/lib/emulator/downloadableEmulatorInfo.json +26 -18
- package/lib/emulator/downloadableEmulators.js +59 -47
- package/lib/functions/events/v2.js +2 -1
- package/lib/init/features/dataconnect/sdk.js +18 -0
- package/lib/mcp/prompts/apptesting/index.js +9 -0
- package/lib/mcp/prompts/apptesting/run_test.js +127 -0
- package/lib/mcp/prompts/crashlytics/connect.js +33 -27
- package/lib/mcp/prompts/index.js +2 -0
- package/lib/mcp/tools/apptesting/index.js +9 -0
- package/lib/mcp/tools/apptesting/tests.js +100 -0
- package/lib/mcp/tools/crashlytics/events.js +92 -9
- package/lib/mcp/tools/index.js +6 -4
- package/lib/mcp/types.js +1 -0
- package/lib/mcp/util/apptesting/availability.js +24 -0
- package/lib/mcp/util/availability.js +2 -0
- package/lib/mcp/util.js +2 -0
- package/package.json +2 -2
- package/schema/connector-yaml.json +30 -0
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,7 @@ 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();
|
|
34
35
|
const flutterAppPromises = await Promise.all(pubSpecYamlFiles.map((f) => processFlutterDir(dirPath, f)));
|
|
35
36
|
const flutterApps = flutterAppPromises.flat();
|
|
36
37
|
const androidAppPromises = await Promise.all(srcMainFolders.map((f) => processAndroidDir(dirPath, f)));
|
|
@@ -41,7 +42,7 @@ async function detectApps(dirPath) {
|
|
|
41
42
|
const iosApps = iosAppPromises
|
|
42
43
|
.flat()
|
|
43
44
|
.filter((a) => !flutterApps.some((f) => isPathInside(f.directory, a.directory)));
|
|
44
|
-
return [...
|
|
45
|
+
return [...flutterApps, ...androidApps, ...iosApps, ...adminAndWebApps];
|
|
45
46
|
}
|
|
46
47
|
exports.detectApps = detectApps;
|
|
47
48
|
async function processIosDir(dirPath, filePath) {
|
|
@@ -108,14 +109,33 @@ function isPathInside(parent, child) {
|
|
|
108
109
|
const relativePath = path.relative(parent, child);
|
|
109
110
|
return !relativePath.startsWith(`..`);
|
|
110
111
|
}
|
|
111
|
-
|
|
112
|
+
function getAllDepsFromPackageJson(packageJson) {
|
|
113
|
+
var _a, _b;
|
|
114
|
+
const devDependencies = Object.keys((_a = packageJson.devDependencies) !== null && _a !== void 0 ? _a : {});
|
|
115
|
+
const dependencies = Object.keys((_b = packageJson.dependencies) !== null && _b !== void 0 ? _b : {});
|
|
116
|
+
const allDeps = Array.from(new Set([...devDependencies, ...dependencies]));
|
|
117
|
+
return allDeps;
|
|
118
|
+
}
|
|
119
|
+
exports.getAllDepsFromPackageJson = getAllDepsFromPackageJson;
|
|
120
|
+
async function packageJsonToAdminOrWebApp(dirPath, packageJsonFile) {
|
|
112
121
|
const fullPath = path.join(dirPath, packageJsonFile);
|
|
113
122
|
const packageJson = JSON.parse((await fs.readFile(fullPath)).toString());
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
const allDeps = getAllDepsFromPackageJson(packageJson);
|
|
124
|
+
const detectedApps = [];
|
|
125
|
+
if (allDeps.includes("firebase-admin") || allDeps.includes("firebase-functions")) {
|
|
126
|
+
detectedApps.push({
|
|
127
|
+
platform: Platform.ADMIN_NODE,
|
|
128
|
+
directory: path.dirname(packageJsonFile),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (allDeps.includes("firebase") || detectedApps.length === 0) {
|
|
132
|
+
detectedApps.push({
|
|
133
|
+
platform: Platform.WEB,
|
|
134
|
+
directory: path.dirname(packageJsonFile),
|
|
135
|
+
frameworks: getFrameworksFromPackageJson(packageJson),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return detectedApps;
|
|
119
139
|
}
|
|
120
140
|
const WEB_FRAMEWORKS = Object.values(Framework);
|
|
121
141
|
const WEB_FRAMEWORKS_SIGNALS = {
|
|
@@ -148,10 +168,7 @@ async function detectAppIdsForPlatform(dirPath, platform) {
|
|
|
148
168
|
return allAppIds.flat();
|
|
149
169
|
}
|
|
150
170
|
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]));
|
|
171
|
+
const allDeps = getAllDepsFromPackageJson(packageJson);
|
|
155
172
|
return WEB_FRAMEWORKS.filter((framework) => WEB_FRAMEWORKS_SIGNALS[framework].find((dep) => allDeps.includes(dep)));
|
|
156
173
|
}
|
|
157
174
|
function extractAppIdentifiersFlutter(fileContent) {
|
|
@@ -228,7 +228,7 @@ class AppDistributionClient {
|
|
|
228
228
|
}
|
|
229
229
|
utils.logSuccess(`Testers removed from group successfully`);
|
|
230
230
|
}
|
|
231
|
-
async createReleaseTest(releaseName, devices, loginCredential, testCaseName) {
|
|
231
|
+
async createReleaseTest(releaseName, devices, aiInstruction, loginCredential, testCaseName) {
|
|
232
232
|
try {
|
|
233
233
|
const response = await this.appDistroV1AlphaClient.request({
|
|
234
234
|
method: "POST",
|
|
@@ -237,6 +237,7 @@ class AppDistributionClient {
|
|
|
237
237
|
deviceExecutions: devices.map((device) => ({ device })),
|
|
238
238
|
loginCredential,
|
|
239
239
|
testCase: testCaseName,
|
|
240
|
+
aiInstructions: aiInstruction,
|
|
240
241
|
},
|
|
241
242
|
});
|
|
242
243
|
return response.body;
|
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Distribution = exports.DistributionFileType = void 0;
|
|
3
|
+
exports.awaitTestResults = exports.Distribution = exports.upload = exports.DistributionFileType = void 0;
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
|
-
const error_1 = require("../error");
|
|
6
5
|
const logger_1 = require("../logger");
|
|
7
6
|
const pathUtil = require("path");
|
|
7
|
+
const utils = require("../utils");
|
|
8
|
+
const types_1 = require("../appdistribution/types");
|
|
9
|
+
const error_1 = require("../error");
|
|
10
|
+
const TEST_MAX_POLLING_RETRIES = 40;
|
|
11
|
+
const TEST_POLLING_INTERVAL_MILLIS = 30000;
|
|
8
12
|
var DistributionFileType;
|
|
9
13
|
(function (DistributionFileType) {
|
|
10
14
|
DistributionFileType["IPA"] = "ipa";
|
|
11
15
|
DistributionFileType["APK"] = "apk";
|
|
12
16
|
DistributionFileType["AAB"] = "aab";
|
|
13
17
|
})(DistributionFileType = exports.DistributionFileType || (exports.DistributionFileType = {}));
|
|
18
|
+
async function upload(requests, appName, distribution) {
|
|
19
|
+
utils.logBullet("uploading binary...");
|
|
20
|
+
try {
|
|
21
|
+
const operationName = await requests.uploadRelease(appName, distribution);
|
|
22
|
+
const uploadResponse = await requests.pollUploadStatus(operationName);
|
|
23
|
+
const release = uploadResponse.release;
|
|
24
|
+
switch (uploadResponse.result) {
|
|
25
|
+
case types_1.UploadReleaseResult.RELEASE_CREATED:
|
|
26
|
+
utils.logSuccess(`uploaded new release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
27
|
+
break;
|
|
28
|
+
case types_1.UploadReleaseResult.RELEASE_UPDATED:
|
|
29
|
+
utils.logSuccess(`uploaded update to existing release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
30
|
+
break;
|
|
31
|
+
case types_1.UploadReleaseResult.RELEASE_UNMODIFIED:
|
|
32
|
+
utils.logSuccess(`re-uploaded already existing release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
utils.logSuccess(`uploaded release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
36
|
+
}
|
|
37
|
+
utils.logSuccess(`View this release in the Firebase console: ${release.firebaseConsoleUri}`);
|
|
38
|
+
utils.logSuccess(`Share this release with testers who have access: ${release.testingUri}`);
|
|
39
|
+
utils.logSuccess(`Download the release binary (link expires in 1 hour): ${release.binaryDownloadUri}`);
|
|
40
|
+
return uploadResponse.release.name;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if ((0, error_1.getErrStatus)(err) === 404) {
|
|
44
|
+
throw new error_1.FirebaseError(`App Distribution could not find your app ${appName}. ` +
|
|
45
|
+
`Make sure to onboard your app by pressing the "Get started" ` +
|
|
46
|
+
"button on the App Distribution page in the Firebase console: " +
|
|
47
|
+
"https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
|
|
48
|
+
}
|
|
49
|
+
throw new error_1.FirebaseError(`Failed to upload release. ${(0, error_1.getErrMsg)(err)}`, { exit: 1 });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.upload = upload;
|
|
14
53
|
class Distribution {
|
|
15
54
|
constructor(path) {
|
|
16
55
|
this.path = path;
|
|
@@ -49,3 +88,47 @@ class Distribution {
|
|
|
49
88
|
}
|
|
50
89
|
}
|
|
51
90
|
exports.Distribution = Distribution;
|
|
91
|
+
async function awaitTestResults(releaseTests, requests) {
|
|
92
|
+
const releaseTestNames = new Set(releaseTests.map((rt) => rt.name).filter((n) => !!n));
|
|
93
|
+
for (let i = 0; i < TEST_MAX_POLLING_RETRIES; i++) {
|
|
94
|
+
utils.logBullet(`${releaseTestNames.size} automated test results are pending...`);
|
|
95
|
+
await delay(TEST_POLLING_INTERVAL_MILLIS);
|
|
96
|
+
for (const releaseTestName of releaseTestNames) {
|
|
97
|
+
const releaseTest = await requests.getReleaseTest(releaseTestName);
|
|
98
|
+
if (releaseTest.deviceExecutions.every((e) => e.state === "PASSED")) {
|
|
99
|
+
releaseTestNames.delete(releaseTestName);
|
|
100
|
+
if (releaseTestNames.size === 0) {
|
|
101
|
+
utils.logSuccess("Automated test(s) passed!");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const execution of releaseTest.deviceExecutions) {
|
|
109
|
+
const device = deviceToString(execution.device);
|
|
110
|
+
switch (execution.state) {
|
|
111
|
+
case "PASSED":
|
|
112
|
+
case "IN_PROGRESS":
|
|
113
|
+
continue;
|
|
114
|
+
case "FAILED":
|
|
115
|
+
throw new error_1.FirebaseError(`Automated test failed for ${device}: ${execution.failedReason}`, { exit: 1 });
|
|
116
|
+
case "INCONCLUSIVE":
|
|
117
|
+
throw new error_1.FirebaseError(`Automated test inconclusive for ${device}: ${execution.inconclusiveReason}`, { exit: 1 });
|
|
118
|
+
default:
|
|
119
|
+
throw new error_1.FirebaseError(`Unsupported automated test state for ${device}: ${execution.state}`, { exit: 1 });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw new error_1.FirebaseError("It took longer than expected to run your test(s), please try again.", {
|
|
125
|
+
exit: 1,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
exports.awaitTestResults = awaitTestResults;
|
|
129
|
+
function delay(ms) {
|
|
130
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
131
|
+
}
|
|
132
|
+
function deviceToString(device) {
|
|
133
|
+
return `${device.model} (${device.version}/${device.orientation}/${device.locale})`;
|
|
134
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getLoginCredential = exports.parseTestDevices = exports.getAppName = exports.getProjectName = exports.ensureFileExists = exports.getEmails = exports.parseIntoStringArray = void 0;
|
|
3
|
+
exports.getLoginCredential = exports.parseTestDevices = exports.toAppName = exports.getAppName = exports.getProjectName = exports.ensureFileExists = exports.getEmails = exports.parseIntoStringArray = void 0;
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
const projectUtils_1 = require("../projectUtils");
|
|
7
|
-
function parseIntoStringArray(value, file) {
|
|
7
|
+
function parseIntoStringArray(value, file = "") {
|
|
8
8
|
if (!value && file) {
|
|
9
9
|
ensureFileExists(file);
|
|
10
10
|
value = fs.readFileSync(file, "utf8");
|
|
@@ -45,11 +45,14 @@ function getAppName(options) {
|
|
|
45
45
|
if (!options.app) {
|
|
46
46
|
throw new error_1.FirebaseError("set the --app option to a valid Firebase app id and try again");
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
return `projects/${appId.split(":")[1]}/apps/${appId}`;
|
|
48
|
+
return toAppName(options.app);
|
|
50
49
|
}
|
|
51
50
|
exports.getAppName = getAppName;
|
|
52
|
-
function
|
|
51
|
+
function toAppName(appId) {
|
|
52
|
+
return `projects/${appId.split(":")[1]}/apps/${appId}`;
|
|
53
|
+
}
|
|
54
|
+
exports.toAppName = toAppName;
|
|
55
|
+
function parseTestDevices(value, file = "") {
|
|
53
56
|
if (!value && file) {
|
|
54
57
|
ensureFileExists(file);
|
|
55
58
|
value = fs.readFileSync(file, "utf8");
|
|
@@ -3,15 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.command = void 0;
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
5
|
const command_1 = require("../command");
|
|
6
|
-
const utils = require("../utils");
|
|
7
6
|
const requireAuth_1 = require("../requireAuth");
|
|
8
|
-
const client_1 = require("../appdistribution/client");
|
|
9
|
-
const types_1 = require("../appdistribution/types");
|
|
10
7
|
const error_1 = require("../error");
|
|
11
8
|
const distribution_1 = require("../appdistribution/distribution");
|
|
12
9
|
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
13
|
-
const
|
|
14
|
-
const
|
|
10
|
+
const types_1 = require("../appdistribution/types");
|
|
11
|
+
const client_1 = require("../appdistribution/client");
|
|
12
|
+
const utils = require("../utils");
|
|
15
13
|
function getReleaseNotes(releaseNotes, releaseNotesFile) {
|
|
16
14
|
if (releaseNotes) {
|
|
17
15
|
return releaseNotes.replace(/\\n/g, "\n");
|
|
@@ -60,6 +58,9 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
60
58
|
usernameResourceName: options.testUsernameResource,
|
|
61
59
|
passwordResourceName: options.testPasswordResource,
|
|
62
60
|
});
|
|
61
|
+
await distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, options.testNonBlocking, loginCredential);
|
|
62
|
+
});
|
|
63
|
+
async function distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, testNonBlocking, loginCredential) {
|
|
63
64
|
const requests = new client_1.AppDistributionClient();
|
|
64
65
|
let aabInfo;
|
|
65
66
|
if (distribution.distributionFileType() === distribution_1.DistributionFileType.AAB) {
|
|
@@ -68,7 +69,7 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
68
69
|
}
|
|
69
70
|
catch (err) {
|
|
70
71
|
if ((0, error_1.getErrStatus)(err) === 404) {
|
|
71
|
-
throw new error_1.FirebaseError(`App Distribution could not find your app ${
|
|
72
|
+
throw new error_1.FirebaseError(`App Distribution could not find your app ${appName}. ` +
|
|
72
73
|
`Make sure to onboard your app by pressing the "Get started" ` +
|
|
73
74
|
"button on the App Distribution page in the Firebase console: " +
|
|
74
75
|
"https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
|
|
@@ -96,39 +97,7 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
-
let releaseName;
|
|
101
|
-
try {
|
|
102
|
-
const operationName = await requests.uploadRelease(appName, distribution);
|
|
103
|
-
const uploadResponse = await requests.pollUploadStatus(operationName);
|
|
104
|
-
const release = uploadResponse.release;
|
|
105
|
-
switch (uploadResponse.result) {
|
|
106
|
-
case types_1.UploadReleaseResult.RELEASE_CREATED:
|
|
107
|
-
utils.logSuccess(`uploaded new release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
108
|
-
break;
|
|
109
|
-
case types_1.UploadReleaseResult.RELEASE_UPDATED:
|
|
110
|
-
utils.logSuccess(`uploaded update to existing release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
111
|
-
break;
|
|
112
|
-
case types_1.UploadReleaseResult.RELEASE_UNMODIFIED:
|
|
113
|
-
utils.logSuccess(`re-uploaded already existing release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
114
|
-
break;
|
|
115
|
-
default:
|
|
116
|
-
utils.logSuccess(`uploaded release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
117
|
-
}
|
|
118
|
-
utils.logSuccess(`View this release in the Firebase console: ${release.firebaseConsoleUri}`);
|
|
119
|
-
utils.logSuccess(`Share this release with testers who have access: ${release.testingUri}`);
|
|
120
|
-
utils.logSuccess(`Download the release binary (link expires in 1 hour): ${release.binaryDownloadUri}`);
|
|
121
|
-
releaseName = uploadResponse.release.name;
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
if ((0, error_1.getErrStatus)(err) === 404) {
|
|
125
|
-
throw new error_1.FirebaseError(`App Distribution could not find your app ${options.app}. ` +
|
|
126
|
-
`Make sure to onboard your app by pressing the "Get started" ` +
|
|
127
|
-
"button on the App Distribution page in the Firebase console: " +
|
|
128
|
-
"https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
|
|
129
|
-
}
|
|
130
|
-
throw new error_1.FirebaseError(`Failed to upload release. ${(0, error_1.getErrMsg)(err)}`, { exit: 1 });
|
|
131
|
-
}
|
|
100
|
+
const releaseName = await (0, distribution_1.upload)(requests, appName, distribution);
|
|
132
101
|
if (aabInfo && !aabInfo.testCertificate) {
|
|
133
102
|
aabInfo = await requests.getAabInfo(appName);
|
|
134
103
|
if (aabInfo.testCertificate) {
|
|
@@ -147,59 +116,17 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
147
116
|
utils.logBullet("starting automated test (note: this feature is in beta)");
|
|
148
117
|
const releaseTestPromises = [];
|
|
149
118
|
if (!testCases.length) {
|
|
150
|
-
releaseTestPromises.push(requests.createReleaseTest(releaseName, testDevices, loginCredential));
|
|
119
|
+
releaseTestPromises.push(requests.createReleaseTest(releaseName, testDevices, undefined, loginCredential));
|
|
151
120
|
}
|
|
152
121
|
else {
|
|
153
122
|
for (const testCaseId of testCases) {
|
|
154
|
-
releaseTestPromises.push(requests.createReleaseTest(releaseName, testDevices, loginCredential, `${appName}/testCases/${testCaseId}`));
|
|
123
|
+
releaseTestPromises.push(requests.createReleaseTest(releaseName, testDevices, undefined, loginCredential, `${appName}/testCases/${testCaseId}`));
|
|
155
124
|
}
|
|
156
125
|
}
|
|
157
126
|
const releaseTests = await Promise.all(releaseTestPromises);
|
|
158
127
|
utils.logSuccess(`${releaseTests.length} release test(s) started successfully`);
|
|
159
|
-
if (!
|
|
160
|
-
await awaitTestResults(releaseTests, requests);
|
|
128
|
+
if (!testNonBlocking) {
|
|
129
|
+
await (0, distribution_1.awaitTestResults)(releaseTests, requests);
|
|
161
130
|
}
|
|
162
131
|
}
|
|
163
|
-
});
|
|
164
|
-
async function awaitTestResults(releaseTests, requests) {
|
|
165
|
-
const releaseTestNames = new Set(releaseTests.map((rt) => rt.name));
|
|
166
|
-
for (let i = 0; i < TEST_MAX_POLLING_RETRIES; i++) {
|
|
167
|
-
utils.logBullet(`${releaseTestNames.size} automated test results are pending...`);
|
|
168
|
-
await delay(TEST_POLLING_INTERVAL_MILLIS);
|
|
169
|
-
for (const releaseTestName of releaseTestNames) {
|
|
170
|
-
const releaseTest = await requests.getReleaseTest(releaseTestName);
|
|
171
|
-
if (releaseTest.deviceExecutions.every((e) => e.state === "PASSED")) {
|
|
172
|
-
releaseTestNames.delete(releaseTestName);
|
|
173
|
-
if (releaseTestNames.size === 0) {
|
|
174
|
-
utils.logSuccess("Automated test(s) passed!");
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
for (const execution of releaseTest.deviceExecutions) {
|
|
182
|
-
switch (execution.state) {
|
|
183
|
-
case "PASSED":
|
|
184
|
-
case "IN_PROGRESS":
|
|
185
|
-
continue;
|
|
186
|
-
case "FAILED":
|
|
187
|
-
throw new error_1.FirebaseError(`Automated test failed for ${deviceToString(execution.device)}: ${execution.failedReason}`, { exit: 1 });
|
|
188
|
-
case "INCONCLUSIVE":
|
|
189
|
-
throw new error_1.FirebaseError(`Automated test inconclusive for ${deviceToString(execution.device)}: ${execution.inconclusiveReason}`, { exit: 1 });
|
|
190
|
-
default:
|
|
191
|
-
throw new error_1.FirebaseError(`Unsupported automated test state for ${deviceToString(execution.device)}: ${execution.state}`, { exit: 1 });
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
throw new error_1.FirebaseError("It took longer than expected to run your test(s), please try again.", {
|
|
197
|
-
exit: 1,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
function delay(ms) {
|
|
201
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
202
|
-
}
|
|
203
|
-
function deviceToString(device) {
|
|
204
|
-
return `${device.model} (${device.version}/${device.orientation}/${device.locale})`;
|
|
205
132
|
}
|
|
@@ -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) {
|
|
@@ -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)) {
|
|
@@ -54,28 +54,36 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dataconnect": {
|
|
56
56
|
"darwin": {
|
|
57
|
-
"version": "2.
|
|
58
|
-
"expectedSize":
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v2.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
57
|
+
"version": "2.17.0",
|
|
58
|
+
"expectedSize": 29983584,
|
|
59
|
+
"expectedChecksum": "d9a3a5bd575dc24185ad473a440c4738",
|
|
60
|
+
"expectedChecksumSHA256": "da5485e68c7adbf86e3fb2f9ca550e4619f55ae75845009837780fcf16dd05cc",
|
|
61
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v2.17.0",
|
|
62
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.0"
|
|
63
|
+
},
|
|
64
|
+
"darwin_arm64": {
|
|
65
|
+
"version": "2.17.0",
|
|
66
|
+
"expectedSize": 29459746,
|
|
67
|
+
"expectedChecksum": "8362a56419a66507b1aead4630b9033c",
|
|
68
|
+
"expectedChecksumSHA256": "cbb8a3030f69c5aba81d1ef2d64d249f18100915f9738f59b815004b27983dab",
|
|
69
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v2.17.0",
|
|
70
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.0"
|
|
63
71
|
},
|
|
64
72
|
"win32": {
|
|
65
|
-
"version": "2.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v2.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
73
|
+
"version": "2.17.0",
|
|
74
|
+
"expectedSize": 30477824,
|
|
75
|
+
"expectedChecksum": "7d8434eee4f3d33cc8ec6c99c6056a77",
|
|
76
|
+
"expectedChecksumSHA256": "051c60be0651be4971409da7ab3a15cdfb693400e9293c89010edcb28016d061",
|
|
77
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v2.17.0",
|
|
78
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.0.exe"
|
|
71
79
|
},
|
|
72
80
|
"linux": {
|
|
73
|
-
"version": "2.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v2.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
81
|
+
"version": "2.17.0",
|
|
82
|
+
"expectedSize": 29905080,
|
|
83
|
+
"expectedChecksum": "ca7003aaee41e3c1261f9655c5f9dd8a",
|
|
84
|
+
"expectedChecksumSHA256": "79efd09f1bd685cbfa0157b2d08e0f6eb085ed82d83b48fabb7d102db6636c6c",
|
|
85
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v2.17.0",
|
|
86
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.0"
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isIncomaptibleArchError = exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName =
|
|
3
|
+
exports.isIncomaptibleArchError = exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = void 0;
|
|
4
4
|
const lsofi = require("lsofi");
|
|
5
5
|
const types_1 = require("./types");
|
|
6
6
|
const constants_1 = require("./constants");
|
|
@@ -20,51 +20,63 @@ const emulatorUpdateDetails = require("./downloadableEmulatorInfo.json");
|
|
|
20
20
|
const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000;
|
|
21
21
|
const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators");
|
|
22
22
|
const EMULATOR_UPDATE_DETAILS = emulatorUpdateDetails;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
23
|
+
function generateDownloadDetails(emulator) {
|
|
24
|
+
const emulatorUiDetails = experiments.isEnabled("emulatoruisnapshot")
|
|
25
|
+
? EMULATOR_UPDATE_DETAILS.ui.snapshot
|
|
26
|
+
: EMULATOR_UPDATE_DETAILS.ui.main;
|
|
27
|
+
const dataconnectDetails = process.platform === "darwin"
|
|
28
|
+
? process.arch === "arm64"
|
|
29
|
+
? EMULATOR_UPDATE_DETAILS.dataconnect.darwin_arm64
|
|
30
|
+
: EMULATOR_UPDATE_DETAILS.dataconnect.darwin
|
|
31
|
+
: process.platform === "win32"
|
|
32
|
+
? EMULATOR_UPDATE_DETAILS.dataconnect.win32
|
|
33
|
+
: EMULATOR_UPDATE_DETAILS.dataconnect.linux;
|
|
34
|
+
switch (emulator) {
|
|
35
|
+
case "database":
|
|
36
|
+
return {
|
|
37
|
+
downloadPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.database.downloadPathRelativeToCacheDir),
|
|
38
|
+
version: EMULATOR_UPDATE_DETAILS.database.version,
|
|
39
|
+
opts: Object.assign(Object.assign({}, EMULATOR_UPDATE_DETAILS.database), { cacheDir: CACHE_DIR, namePrefix: "firebase-database-emulator" }),
|
|
40
|
+
};
|
|
41
|
+
case "firestore":
|
|
42
|
+
return {
|
|
43
|
+
downloadPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.firestore.downloadPathRelativeToCacheDir),
|
|
44
|
+
version: EMULATOR_UPDATE_DETAILS.firestore.version,
|
|
45
|
+
opts: Object.assign(Object.assign({}, EMULATOR_UPDATE_DETAILS.firestore), { cacheDir: CACHE_DIR, namePrefix: "cloud-firestore-emulator" }),
|
|
46
|
+
};
|
|
47
|
+
case "storage":
|
|
48
|
+
return {
|
|
49
|
+
downloadPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.storage.downloadPathRelativeToCacheDir),
|
|
50
|
+
version: EMULATOR_UPDATE_DETAILS.storage.version,
|
|
51
|
+
opts: Object.assign(Object.assign({}, EMULATOR_UPDATE_DETAILS.storage), { cacheDir: CACHE_DIR, namePrefix: "cloud-storage-rules-emulator" }),
|
|
52
|
+
};
|
|
53
|
+
case "ui":
|
|
54
|
+
return {
|
|
55
|
+
version: emulatorUiDetails.version,
|
|
56
|
+
downloadPath: path.join(CACHE_DIR, emulatorUiDetails.downloadPathRelativeToCacheDir),
|
|
57
|
+
unzipDir: path.join(CACHE_DIR, `ui-v${emulatorUiDetails.version}`),
|
|
58
|
+
binaryPath: path.join(CACHE_DIR, emulatorUiDetails.binaryPathRelativeToCacheDir),
|
|
59
|
+
opts: Object.assign(Object.assign({}, emulatorUiDetails), { cacheDir: CACHE_DIR, skipCache: experiments.isEnabled("emulatoruisnapshot"), skipChecksumAndSize: experiments.isEnabled("emulatoruisnapshot"), namePrefix: "ui" }),
|
|
60
|
+
};
|
|
61
|
+
case "pubsub":
|
|
62
|
+
return {
|
|
63
|
+
downloadPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.pubsub.downloadPathRelativeToCacheDir),
|
|
64
|
+
version: EMULATOR_UPDATE_DETAILS.pubsub.version,
|
|
65
|
+
unzipDir: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`),
|
|
66
|
+
binaryPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.pubsub.binaryPathRelativeToCacheDir),
|
|
67
|
+
opts: Object.assign(Object.assign({}, EMULATOR_UPDATE_DETAILS.pubsub), { cacheDir: CACHE_DIR, namePrefix: "pubsub-emulator" }),
|
|
68
|
+
};
|
|
69
|
+
case "dataconnect":
|
|
70
|
+
return {
|
|
71
|
+
downloadPath: path.join(CACHE_DIR, dataconnectDetails.downloadPathRelativeToCacheDir),
|
|
72
|
+
version: dataconnectDetails.version,
|
|
73
|
+
binaryPath: path.join(CACHE_DIR, dataconnectDetails.downloadPathRelativeToCacheDir),
|
|
74
|
+
opts: Object.assign(Object.assign({}, dataconnectDetails), { cacheDir: CACHE_DIR, skipChecksumAndSize: false, namePrefix: "dataconnect-emulator", auth: false }),
|
|
75
|
+
};
|
|
76
|
+
default:
|
|
77
|
+
throw new Error(`Invalid downloadable emulator: ${emulator}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
68
80
|
const EmulatorDetails = {
|
|
69
81
|
database: {
|
|
70
82
|
name: types_1.Emulators.DATABASE,
|
|
@@ -318,7 +330,7 @@ async function _runBinary(emulator, command, extraEnv) {
|
|
|
318
330
|
});
|
|
319
331
|
}
|
|
320
332
|
function getDownloadDetails(emulator) {
|
|
321
|
-
const details =
|
|
333
|
+
const details = generateDownloadDetails(emulator);
|
|
322
334
|
const pathOverride = process.env[`${emulator.toUpperCase()}_EMULATOR_BINARY_PATH`];
|
|
323
335
|
if (pathOverride) {
|
|
324
336
|
const logger = emulatorLogger_1.EmulatorLogger.forEmulator(emulator);
|