firebase-tools 15.19.1 → 15.21.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/lib/appdistribution/client.js +6 -5
- package/lib/appdistribution/options-parser-util.js +21 -0
- package/lib/apphosting/constants.js +2 -1
- package/lib/apphosting/localbuilds.js +4 -2
- package/lib/archiveDirectory.js +2 -2
- package/lib/auth.js +2 -3
- package/lib/bin/cli.js +26 -18
- package/lib/commands/appdistribution-distribute.js +13 -4
- package/lib/commands/apptesting.js +18 -3
- package/lib/commands/crashlytics-symbols-upload.js +2 -2
- package/lib/crashlytics/sourcemap.js +3 -4
- package/lib/database/import.js +2 -2
- package/lib/dataconnect/build.js +6 -6
- package/lib/deploy/apphosting/util.js +9 -1
- package/lib/deploy/dataconnect/context.js +0 -2
- package/lib/deploy/dataconnect/deploy.js +0 -19
- package/lib/deploy/functions/prepare.js +47 -107
- package/lib/deploy/functions/prepareFunctionsUpload.js +1 -2
- package/lib/deploy/functions/release/index.js +0 -5
- package/lib/deploy/functions/services/ailogic.js +17 -10
- package/lib/deploy/functions/services/auth.js +3 -0
- package/lib/deploy/functions/services/database.js +18 -0
- package/lib/deploy/functions/services/dataconnect.js +20 -0
- package/lib/deploy/functions/services/firestore.js +12 -0
- package/lib/deploy/functions/services/index.js +18 -7
- package/lib/deploy/functions/services/storage.js +14 -0
- package/lib/deploy/functions/triggerRegionHelper.js +2 -4
- package/lib/emulator/auth/apiSpec.js +307 -33
- package/lib/emulator/auth/cloudFunctions.js +2 -2
- package/lib/emulator/auth/operations.js +99 -9
- package/lib/emulator/auth/state.js +27 -0
- package/lib/emulator/downloadableEmulatorInfo.json +24 -24
- package/lib/emulator/functionsEmulatorShell.js +4 -4
- package/lib/emulator/functionsRuntimeWorker.js +2 -2
- package/lib/emulator/pubsubEmulator.js +3 -3
- package/lib/emulator/storage/apis/firebase.js +2 -2
- package/lib/emulator/storage/cloudFunctions.js +2 -2
- package/lib/emulator/storage/metadata.js +1 -2
- package/lib/emulator/storage/persistence.js +2 -2
- package/lib/emulator/storage/upload.js +3 -3
- package/lib/env.js +20 -4
- package/lib/experiments.js +1 -2
- package/lib/frameworks/angular/index.js +3 -2
- package/lib/frameworks/angular/utils.js +100 -2
- package/lib/frameworks/astro/index.js +1 -1
- package/lib/frameworks/astro/utils.js +1 -1
- package/lib/functions/python.js +4 -4
- package/lib/gcp/location.js +16 -1
- package/lib/hosting/cloudRunProxy.js +8 -0
- package/lib/index.js +2 -2
- package/lib/init/features/apphosting.js +8 -1
- package/lib/init/features/dataconnect/create_app.js +3 -4
- package/lib/init/features/dataconnect/sdk.js +2 -2
- package/lib/init/features/functions/python.js +32 -20
- package/lib/localFunction.js +4 -2
- package/lib/mcp/tools/apptesting/tests.js +3 -1
- package/lib/track.js +2 -2
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/lib/utils.js +70 -0
- package/package.json +1 -6
|
@@ -225,17 +225,18 @@ class AppDistributionClient {
|
|
|
225
225
|
}
|
|
226
226
|
utils.logSuccess(`Testers removed from group successfully`);
|
|
227
227
|
}
|
|
228
|
-
async createReleaseTest(releaseName, devices,
|
|
228
|
+
async createReleaseTest(releaseName, devices, options = {}) {
|
|
229
229
|
try {
|
|
230
230
|
const response = await this.appDistroV1AlphaClient.request({
|
|
231
231
|
method: "POST",
|
|
232
232
|
path: `${releaseName}/tests`,
|
|
233
233
|
body: {
|
|
234
234
|
deviceExecutions: devices.map((device) => ({ device })),
|
|
235
|
-
loginCredential,
|
|
236
|
-
testCase: testCaseName,
|
|
237
|
-
aiInstructions: aiInstructions,
|
|
238
|
-
displayName: displayName,
|
|
235
|
+
loginCredential: options.loginCredential,
|
|
236
|
+
testCase: options.testCaseName,
|
|
237
|
+
aiInstructions: options.aiInstructions,
|
|
238
|
+
displayName: options.displayName,
|
|
239
|
+
resultsBucket: options.resultsBucket,
|
|
239
240
|
},
|
|
240
241
|
});
|
|
241
242
|
return response.body;
|
|
@@ -8,6 +8,7 @@ exports.getAppName = getAppName;
|
|
|
8
8
|
exports.toAppName = toAppName;
|
|
9
9
|
exports.parseTestDevices = parseTestDevices;
|
|
10
10
|
exports.getLoginCredential = getLoginCredential;
|
|
11
|
+
exports.getResultsBucket = getResultsBucket;
|
|
11
12
|
const fs = require("fs-extra");
|
|
12
13
|
const error_1 = require("../error");
|
|
13
14
|
const projectUtils_1 = require("../projectUtils");
|
|
@@ -137,3 +138,23 @@ function getLoginCredential(args) {
|
|
|
137
138
|
function isPresenceMismatched(value1, value2) {
|
|
138
139
|
return (value1 && !value2) || (!value1 && value2);
|
|
139
140
|
}
|
|
141
|
+
const APP_NAME_REGEX = /^projects\/(?<projectNumber>[^\/]+)\/apps\/(?<appId>[^\/]+)$/;
|
|
142
|
+
const BUCKET_NAME_FORMAT_REGEX = /^[a-z0-9_.-]+$/;
|
|
143
|
+
function getResultsBucket(bucket, appName) {
|
|
144
|
+
if (!bucket) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
let bucketName = bucket;
|
|
148
|
+
if (bucketName.startsWith("gs://")) {
|
|
149
|
+
bucketName = bucketName.substring(5);
|
|
150
|
+
}
|
|
151
|
+
if (!BUCKET_NAME_FORMAT_REGEX.test(bucketName)) {
|
|
152
|
+
throw new error_1.FirebaseError(`Invalid results bucket format: ${bucket}`);
|
|
153
|
+
}
|
|
154
|
+
const match = APP_NAME_REGEX.exec(appName);
|
|
155
|
+
if (!match || typeof match.groups === "undefined") {
|
|
156
|
+
throw new error_1.FirebaseError(`Invalid app name: ${appName}`);
|
|
157
|
+
}
|
|
158
|
+
const { projectNumber } = match.groups;
|
|
159
|
+
return `projects/${projectNumber}/buckets/${bucketName}`;
|
|
160
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LOCAL_BUILD_DIR_NAME = exports.ALLOWED_DEPLOY_METHODS = exports.DEFAULT_DEPLOY_METHOD = exports.DEFAULT_LOCATION = void 0;
|
|
3
|
+
exports.CLOUD_RUN_SIZE_LIMIT_BYTES = exports.LOCAL_BUILD_DIR_NAME = exports.ALLOWED_DEPLOY_METHODS = exports.DEFAULT_DEPLOY_METHOD = exports.DEFAULT_LOCATION = void 0;
|
|
4
4
|
exports.DEFAULT_LOCATION = "us-east4";
|
|
5
5
|
exports.DEFAULT_DEPLOY_METHOD = "github";
|
|
6
6
|
exports.ALLOWED_DEPLOY_METHODS = [{ name: "Deploy using github", value: "github" }];
|
|
7
7
|
exports.LOCAL_BUILD_DIR_NAME = ".local_build";
|
|
8
|
+
exports.CLOUD_RUN_SIZE_LIMIT_BYTES = 250 * 1024 * 1024;
|
|
@@ -31,11 +31,13 @@ function executeUniversalMakerBinary(universalMakerBinary, projectRoot, addedEnv
|
|
|
31
31
|
},
|
|
32
32
|
stdio: "pipe",
|
|
33
33
|
});
|
|
34
|
+
const failed = !!res.error || res.status !== 0;
|
|
35
|
+
const log = (msg) => (failed ? logger_1.logger.info(msg) : logger_1.logger.debug(msg));
|
|
34
36
|
if (res.stdout) {
|
|
35
|
-
|
|
37
|
+
log("[Universal Maker stdout]:\n" + res.stdout.toString());
|
|
36
38
|
}
|
|
37
39
|
if (res.stderr) {
|
|
38
|
-
|
|
40
|
+
log("[Universal Maker stderr]:\n" + res.stderr.toString());
|
|
39
41
|
}
|
|
40
42
|
if (res.error) {
|
|
41
43
|
throw res.error;
|
package/lib/archiveDirectory.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.archiveDirectory = archiveDirectory;
|
|
4
4
|
const archiver = require("archiver");
|
|
5
|
-
const
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
8
8
|
const tmp = require("tmp");
|
|
@@ -21,7 +21,7 @@ async function archiveDirectory(sourceDirectory, options = {}) {
|
|
|
21
21
|
const makeArchive = zipDirectory(sourceDirectory, tempFile, options);
|
|
22
22
|
try {
|
|
23
23
|
const archive = await makeArchive;
|
|
24
|
-
logger_1.logger.debug(`Archived ${
|
|
24
|
+
logger_1.logger.debug(`Archived ${(0, utils_1.formatFilesize)(archive.size)} in ${sourceDirectory}.`);
|
|
25
25
|
return archive;
|
|
26
26
|
}
|
|
27
27
|
catch (err) {
|
package/lib/auth.js
CHANGED
|
@@ -37,7 +37,6 @@ const logger_1 = require("./logger");
|
|
|
37
37
|
const prompt_1 = require("./prompt");
|
|
38
38
|
const scopes = require("./scopes");
|
|
39
39
|
const defaultCredentials_1 = require("./defaultCredentials");
|
|
40
|
-
const uuid_1 = require("uuid");
|
|
41
40
|
const crypto_1 = require("crypto");
|
|
42
41
|
const track_1 = require("./track");
|
|
43
42
|
const api_1 = require("./api");
|
|
@@ -308,7 +307,7 @@ async function loginPrototyper() {
|
|
|
308
307
|
urlPrefix: (0, api_1.authProxyOrigin)(),
|
|
309
308
|
auth: false,
|
|
310
309
|
});
|
|
311
|
-
const sessionId = (0,
|
|
310
|
+
const sessionId = (0, crypto_1.randomUUID)();
|
|
312
311
|
const codeVerifier = (0, crypto_1.randomBytes)(32).toString("hex");
|
|
313
312
|
const codeChallenge = urlsafeBase64((0, crypto_1.createHash)("sha256").update(codeVerifier).digest("base64"));
|
|
314
313
|
const attestToken = (await authProxyClient.post("/attest", {
|
|
@@ -341,7 +340,7 @@ async function loginRemotely() {
|
|
|
341
340
|
urlPrefix: (0, api_1.authProxyOrigin)(),
|
|
342
341
|
auth: false,
|
|
343
342
|
});
|
|
344
|
-
const sessionId = (0,
|
|
343
|
+
const sessionId = (0, crypto_1.randomUUID)();
|
|
345
344
|
const codeVerifier = (0, crypto_1.randomBytes)(32).toString("hex");
|
|
346
345
|
const codeChallenge = urlsafeBase64((0, crypto_1.createHash)("sha256").update(codeVerifier).digest("base64"));
|
|
347
346
|
const attestToken = (await authProxyClient.post("/attest", {
|
package/lib/bin/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadAllCommands = loadAllCommands;
|
|
3
4
|
exports.cli = cli;
|
|
4
5
|
const updateNotifierPkg = require("update-notifier-cjs");
|
|
5
6
|
const clc = require("colorette");
|
|
@@ -17,6 +18,29 @@ const fsutils = require("../fsutils");
|
|
|
17
18
|
const utils = require("../utils");
|
|
18
19
|
const fetchMOTD_1 = require("../fetchMOTD");
|
|
19
20
|
const command_1 = require("../command");
|
|
21
|
+
const env_1 = require("../env");
|
|
22
|
+
function loadAllCommands(client) {
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
const loadAll = (obj) => {
|
|
25
|
+
if (seen.has(obj))
|
|
26
|
+
return;
|
|
27
|
+
seen.add(obj);
|
|
28
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
29
|
+
if (key === "cli") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if ((0, command_1.isCommandModule)(value)) {
|
|
33
|
+
value.load();
|
|
34
|
+
}
|
|
35
|
+
if ((typeof value === "object" || typeof value === "function") &&
|
|
36
|
+
value !== null &&
|
|
37
|
+
!Array.isArray(value)) {
|
|
38
|
+
loadAll(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
loadAll(client);
|
|
43
|
+
}
|
|
20
44
|
function cli(pkg) {
|
|
21
45
|
const updateNotifier = updateNotifierPkg({ pkg });
|
|
22
46
|
const args = process.argv.slice(2);
|
|
@@ -31,6 +55,7 @@ function cli(pkg) {
|
|
|
31
55
|
logger_1.logger.debug("CLI Version: ", pkg.version);
|
|
32
56
|
logger_1.logger.debug("Platform: ", process.platform);
|
|
33
57
|
logger_1.logger.debug("Node Version: ", process.version);
|
|
58
|
+
logger_1.logger.debug("Detected Agent:", (0, env_1.detectAIAgent)());
|
|
34
59
|
logger_1.logger.debug("Time: ", new Date().toString());
|
|
35
60
|
if (utils.envOverrides.length) {
|
|
36
61
|
logger_1.logger.debug("Env Overrides:", utils.envOverrides.join(", "));
|
|
@@ -97,24 +122,7 @@ function cli(pkg) {
|
|
|
97
122
|
client.getCommand(commandName);
|
|
98
123
|
}
|
|
99
124
|
if (isHelp) {
|
|
100
|
-
|
|
101
|
-
const loadAll = (obj) => {
|
|
102
|
-
if (seen.has(obj))
|
|
103
|
-
return;
|
|
104
|
-
seen.add(obj);
|
|
105
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
106
|
-
if ((0, command_1.isCommandModule)(value)) {
|
|
107
|
-
value.load();
|
|
108
|
-
}
|
|
109
|
-
else if (typeof value === "object" &&
|
|
110
|
-
value !== null &&
|
|
111
|
-
!Array.isArray(value) &&
|
|
112
|
-
key !== "cli") {
|
|
113
|
-
loadAll(value);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
loadAll(client);
|
|
125
|
+
loadAllCommands(client);
|
|
118
126
|
}
|
|
119
127
|
if (!args.length) {
|
|
120
128
|
client.cli.help();
|
|
@@ -39,9 +39,11 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
39
39
|
.option("--test-non-blocking", "run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
|
|
40
40
|
.option("--test-case-ids <string>", "a comma-separated list of test case IDs.")
|
|
41
41
|
.option("--test-case-ids-file <file>", "path to file with a comma- or newline-separated list of test case IDs.")
|
|
42
|
+
.option("--results-bucket <bucket>", "The name of a Google Cloud Storage bucket where raw results of any automated tests will be stored. If this flag is not set, Firebase creates a bucket for you. Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.")
|
|
42
43
|
.before(requireAuth_1.requireAuth)
|
|
43
44
|
.action(async (file, options) => {
|
|
44
45
|
const appName = (0, options_parser_util_1.getAppName)(options);
|
|
46
|
+
const resultsBucket = (0, options_parser_util_1.getResultsBucket)(options.resultsBucket, appName);
|
|
45
47
|
const distribution = new distribution_1.Distribution(file);
|
|
46
48
|
const releaseNotes = getReleaseNotes(options.releaseNotes, options.releaseNotesFile);
|
|
47
49
|
const testers = (0, options_parser_util_1.parseIntoStringArray)(options.testers, options.testersFile);
|
|
@@ -58,9 +60,9 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
58
60
|
usernameResourceName: options.testUsernameResource,
|
|
59
61
|
passwordResourceName: options.testPasswordResource,
|
|
60
62
|
});
|
|
61
|
-
await distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, options.testNonBlocking, loginCredential);
|
|
63
|
+
await distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, options.testNonBlocking, loginCredential, resultsBucket);
|
|
62
64
|
});
|
|
63
|
-
async function distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, testNonBlocking, loginCredential) {
|
|
65
|
+
async function distribute(appName, distribution, testCases, testDevices, releaseNotes, testers, groups, testNonBlocking, loginCredential, resultsBucket) {
|
|
64
66
|
const requests = new client_1.AppDistributionClient();
|
|
65
67
|
let aabInfo;
|
|
66
68
|
if (distribution.distributionFileType() === distribution_1.DistributionFileType.AAB) {
|
|
@@ -116,11 +118,18 @@ async function distribute(appName, distribution, testCases, testDevices, release
|
|
|
116
118
|
utils.logBullet("starting automated test (note: this feature is in beta)");
|
|
117
119
|
const releaseTestPromises = [];
|
|
118
120
|
if (!testCases.length) {
|
|
119
|
-
releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices,
|
|
121
|
+
releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices, {
|
|
122
|
+
loginCredential,
|
|
123
|
+
resultsBucket,
|
|
124
|
+
}));
|
|
120
125
|
}
|
|
121
126
|
else {
|
|
122
127
|
for (const testCaseId of testCases) {
|
|
123
|
-
releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices,
|
|
128
|
+
releaseTestPromises.push(requests.createReleaseTest(release.name, testDevices, {
|
|
129
|
+
loginCredential,
|
|
130
|
+
testCaseName: `${appName}/testCases/${testCaseId}`,
|
|
131
|
+
resultsBucket,
|
|
132
|
+
}));
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
const releaseTests = await Promise.all(releaseTestPromises);
|
|
@@ -29,15 +29,25 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
|
|
|
29
29
|
.option("--test-devices <string>", "Semicolon-separated list of devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
|
|
30
30
|
.option("--test-devices-file <string>", "Path to file containing a list of semicolon- or newline-separated devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
|
|
31
31
|
.option("--test-non-blocking", "Run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
|
|
32
|
+
.option("--results-bucket <bucket>", "The name of a Google Cloud Storage bucket where raw test results will be stored. If this flag is not set, Firebase creates a default bucket for you. Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.")
|
|
33
|
+
.option("--test-username <string>", "username for automatic login")
|
|
34
|
+
.option("--test-password <string>", "password for automatic login. If using a real password, use --test-password-file instead to avoid putting sensitive info in history and logs.")
|
|
35
|
+
.option("--test-password-file <string>", "path to file containing password for automatic login")
|
|
32
36
|
.before(requireAuth_1.requireAuth)
|
|
33
37
|
.action(async (target, options) => {
|
|
34
38
|
const appName = (0, options_parser_util_1.getAppName)(options);
|
|
39
|
+
const resultsBucket = (0, options_parser_util_1.getResultsBucket)(options.resultsBucket, appName);
|
|
35
40
|
const testDir = path.resolve(options.testDir || "tests");
|
|
36
41
|
if (!(0, fsutils_1.dirExistsSync)(testDir)) {
|
|
37
42
|
throw new error_1.FirebaseError(`Tests directory not found: ${testDir}. Use the --test-dir flag to choose a different directory.`);
|
|
38
43
|
}
|
|
39
44
|
const tests = await (0, parseTestFiles_1.parseTestFiles)(testDir, undefined, options.testFilePattern, options.testNamePattern);
|
|
40
45
|
const testDevices = (0, options_parser_util_1.parseTestDevices)(options.testDevices, options.testDevicesFile);
|
|
46
|
+
const loginCredential = (0, options_parser_util_1.getLoginCredential)({
|
|
47
|
+
username: options.testUsername,
|
|
48
|
+
password: options.testPassword,
|
|
49
|
+
passwordFile: options.testPasswordFile,
|
|
50
|
+
});
|
|
41
51
|
if (!tests.length) {
|
|
42
52
|
throw new error_1.FirebaseError(`No tests found under test directory ${testDir}`);
|
|
43
53
|
}
|
|
@@ -60,7 +70,7 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
|
|
|
60
70
|
utils.logBullet(`Using release ${release.displayVersion} created at ${release.createTime}`);
|
|
61
71
|
}
|
|
62
72
|
invokeSpinner.start();
|
|
63
|
-
releaseTests = await invokeTests(client, release.name, tests, !testDevices.length ? defaultDevices : testDevices);
|
|
73
|
+
releaseTests = await invokeTests(client, release.name, tests, !testDevices.length ? defaultDevices : testDevices, resultsBucket, loginCredential);
|
|
64
74
|
invokeSpinner.text = `${(0, parseTestFiles_1.pluralizeTests)(releaseTests.length)} started successfully!`;
|
|
65
75
|
invokeSpinner.succeed();
|
|
66
76
|
}
|
|
@@ -76,14 +86,19 @@ exports.command = new command_1.Command("apptesting:execute [release-binary-file
|
|
|
76
86
|
utils.logBullet(`View detailed results in the Firebase Console:\n${release.firebaseConsoleUri}`);
|
|
77
87
|
}
|
|
78
88
|
});
|
|
79
|
-
async function invokeTests(client, releaseName, testDefs, devices) {
|
|
89
|
+
async function invokeTests(client, releaseName, testDefs, devices, resultsBucket, loginCredential) {
|
|
80
90
|
try {
|
|
81
91
|
const releaseTests = [];
|
|
82
92
|
for (const testDef of testDefs) {
|
|
83
93
|
const aiInstructions = {
|
|
84
94
|
steps: testDef.testCase.steps,
|
|
85
95
|
};
|
|
86
|
-
releaseTests.push(await client.createReleaseTest(releaseName, devices,
|
|
96
|
+
releaseTests.push(await client.createReleaseTest(releaseName, devices, {
|
|
97
|
+
aiInstructions,
|
|
98
|
+
displayName: testDef.testCase.displayName,
|
|
99
|
+
resultsBucket,
|
|
100
|
+
loginCredential,
|
|
101
|
+
}));
|
|
87
102
|
}
|
|
88
103
|
return releaseTests;
|
|
89
104
|
}
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.command = void 0;
|
|
4
4
|
const os = require("os");
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
7
|
const command_1 = require("../command");
|
|
8
8
|
const error_1 = require("../error");
|
|
9
9
|
const utils = require("../utils");
|
|
@@ -28,7 +28,7 @@ exports.command = new command_1.Command("crashlytics:symbols:upload <symbolFiles
|
|
|
28
28
|
const jarOptions = {
|
|
29
29
|
app,
|
|
30
30
|
generator,
|
|
31
|
-
cachePath: path.join(SYMBOL_CACHE_ROOT_DIR, `crashlytics-${
|
|
31
|
+
cachePath: path.join(SYMBOL_CACHE_ROOT_DIR, `crashlytics-${(0, crypto_1.randomUUID)()}`, "nativeSymbols", app.replace(/:/g, "-"), generator),
|
|
32
32
|
symbolFile: "",
|
|
33
33
|
generate: true,
|
|
34
34
|
};
|
|
@@ -15,7 +15,6 @@ exports.registerSourceMap = registerSourceMap;
|
|
|
15
15
|
const fs = require("fs");
|
|
16
16
|
const path = require("path");
|
|
17
17
|
const node_child_process_1 = require("node:child_process");
|
|
18
|
-
const pLimit = require("p-limit");
|
|
19
18
|
const apiv2_1 = require("../apiv2");
|
|
20
19
|
const error_1 = require("../error");
|
|
21
20
|
const logger_1 = require("../logger");
|
|
@@ -104,7 +103,7 @@ async function findSourceMapMappings(files, rootDir) {
|
|
|
104
103
|
const mappings = [];
|
|
105
104
|
const mapFilePathsSet = new Set(mapFiles.map((f) => f.name));
|
|
106
105
|
const mapFilesLinkedInJsComment = new Set();
|
|
107
|
-
const limit = pLimit(exports.CONCURRENCY);
|
|
106
|
+
const limit = (0, utils_1.pLimit)(exports.CONCURRENCY);
|
|
108
107
|
const results = await Promise.all(jsFiles.map((jsFile) => limit(async () => {
|
|
109
108
|
const mapFilePath = await getLinkedSourceMapPath(jsFile.name);
|
|
110
109
|
return { jsFile, mapFilePath };
|
|
@@ -164,7 +163,7 @@ async function getLinkedSourceMapPath(jsFilePath) {
|
|
|
164
163
|
}
|
|
165
164
|
async function uploadSourceMaps(mappings, request) {
|
|
166
165
|
const { projectId, bucketName, appVersion, options } = request;
|
|
167
|
-
const limit = pLimit(exports.CONCURRENCY);
|
|
166
|
+
const limit = (0, utils_1.pLimit)(exports.CONCURRENCY);
|
|
168
167
|
const results = await Promise.all(mappings.map((mapping) => limit(async () => {
|
|
169
168
|
const uploadRequest = {
|
|
170
169
|
projectId,
|
|
@@ -253,7 +252,7 @@ async function registerSourceMap(sourceMap) {
|
|
|
253
252
|
const client = new apiv2_1.Client({
|
|
254
253
|
urlPrefix: "https://firebasetelemetryadmin.googleapis.com",
|
|
255
254
|
auth: true,
|
|
256
|
-
apiVersion: "
|
|
255
|
+
apiVersion: "v1alpha",
|
|
257
256
|
});
|
|
258
257
|
try {
|
|
259
258
|
await client.patch(sourceMap.name, sourceMap, { queryParams: { allowMissing: "true" } });
|
package/lib/database/import.js
CHANGED
|
@@ -8,7 +8,7 @@ const StreamObject = require("stream-json/streamers/StreamObject");
|
|
|
8
8
|
const apiv2_1 = require("../apiv2");
|
|
9
9
|
const node_fetch_1 = require("node-fetch");
|
|
10
10
|
const error_1 = require("../error");
|
|
11
|
-
const
|
|
11
|
+
const utils_1 = require("../utils");
|
|
12
12
|
class BatchChunks extends stream.Transform {
|
|
13
13
|
constructor(maxSize, opts) {
|
|
14
14
|
super({ ...opts, objectMode: true });
|
|
@@ -92,7 +92,7 @@ class DatabaseImporter {
|
|
|
92
92
|
this.payloadSize = payloadSize;
|
|
93
93
|
this.nonFatalRetryTimeout = 1000;
|
|
94
94
|
this.client = new apiv2_1.Client({ urlPrefix: dbUrl.origin, auth: true });
|
|
95
|
-
this.limit = pLimit(concurrency);
|
|
95
|
+
this.limit = (0, utils_1.pLimit)(concurrency);
|
|
96
96
|
}
|
|
97
97
|
async execute() {
|
|
98
98
|
await this.checkLocationIsEmpty();
|
package/lib/dataconnect/build.js
CHANGED
|
@@ -36,9 +36,9 @@ async function build(options, configDir, deployStats) {
|
|
|
36
36
|
async function handleBuildErrors(errors, nonInteractive, force, dryRun) {
|
|
37
37
|
const fatalDeploys = errors.filter((w) => w.extensions?.warningLevel === "ALWAYS_REQUIRED");
|
|
38
38
|
if (fatalDeploys.length) {
|
|
39
|
-
utils.logLabeledError("dataconnect", `There are
|
|
39
|
+
utils.logLabeledError("dataconnect", `There are requirements that are always required and cannot be bypassed:\n` +
|
|
40
40
|
(0, graphqlError_1.prettifyTable)(fatalDeploys));
|
|
41
|
-
throw new error_1.FirebaseError("
|
|
41
|
+
throw new error_1.FirebaseError("Failed due to unbypassable requirements.");
|
|
42
42
|
}
|
|
43
43
|
if (errors.filter((w) => !w.extensions?.warningLevel).length) {
|
|
44
44
|
throw new error_1.FirebaseError(`There are errors in your schema and connector files:\n${errors.map(graphqlError_1.prettify).join("\n")}`);
|
|
@@ -47,7 +47,7 @@ async function handleBuildErrors(errors, nonInteractive, force, dryRun) {
|
|
|
47
47
|
if (requiredForces.length && !force) {
|
|
48
48
|
utils.logLabeledError("dataconnect", `There are changes in your schema or connectors that will result in broken behavior:\n` +
|
|
49
49
|
(0, graphqlError_1.prettifyTable)(requiredForces));
|
|
50
|
-
throw new error_1.FirebaseError("Rerun this command with --force to
|
|
50
|
+
throw new error_1.FirebaseError("Rerun this command with --force to proceed with these changes.");
|
|
51
51
|
}
|
|
52
52
|
const interactiveAcks = errors.filter((w) => w.extensions?.warningLevel === "INTERACTIVE_ACK");
|
|
53
53
|
const requiredAcks = errors.filter((w) => w.extensions?.warningLevel === "REQUIRE_ACK");
|
|
@@ -59,7 +59,7 @@ async function handleBuildErrors(errors, nonInteractive, force, dryRun) {
|
|
|
59
59
|
utils.logLabeledWarning("dataconnect", `There are changes in your schema or connectors that may break your existing applications or introduce operations that are insecure. These changes require explicit acknowledgement to proceed. You may either reject the changes and update your sources with the suggested workaround(s), if any, or acknowledge these changes and proceed with the deployment:\n` +
|
|
60
60
|
(0, graphqlError_1.prettifyTable)(requiredAcks));
|
|
61
61
|
if (nonInteractive && !force) {
|
|
62
|
-
throw new error_1.FirebaseError("Explicit acknowledgement required for breaking schema or connector changes and new insecure operations. Rerun this command with --force to
|
|
62
|
+
throw new error_1.FirebaseError("Explicit acknowledgement required for breaking schema or connector changes and new insecure operations. Rerun this command with --force to proceed with these changes.");
|
|
63
63
|
}
|
|
64
64
|
else if (!nonInteractive && !force && !dryRun) {
|
|
65
65
|
const result = await (0, prompt_1.select)({
|
|
@@ -68,7 +68,7 @@ async function handleBuildErrors(errors, nonInteractive, force, dryRun) {
|
|
|
68
68
|
default: "abort",
|
|
69
69
|
});
|
|
70
70
|
if (result === "abort") {
|
|
71
|
-
throw new error_1.FirebaseError(
|
|
71
|
+
throw new error_1.FirebaseError("Aborted.");
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -82,7 +82,7 @@ async function handleBuildErrors(errors, nonInteractive, force, dryRun) {
|
|
|
82
82
|
default: "proceed",
|
|
83
83
|
});
|
|
84
84
|
if (result === "abort") {
|
|
85
|
-
throw new error_1.FirebaseError(
|
|
85
|
+
throw new error_1.FirebaseError("Aborted.");
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -9,9 +9,11 @@ const path = require("path");
|
|
|
9
9
|
const tar = require("tar");
|
|
10
10
|
const tmp = require("tmp");
|
|
11
11
|
const error_1 = require("../../error");
|
|
12
|
+
const logger_1 = require("../../logger");
|
|
12
13
|
const fsAsync = require("../../fsAsync");
|
|
13
14
|
const utils_1 = require("../../utils");
|
|
14
|
-
|
|
15
|
+
const constants_1 = require("../../apphosting/constants");
|
|
16
|
+
async function createLocalBuildTarArchive(config, rootDir, outputFiles, sizeLimitBytes = constants_1.CLOUD_RUN_SIZE_LIMIT_BYTES) {
|
|
15
17
|
const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".tar.gz" }).name;
|
|
16
18
|
const filesToPackage = outputFiles.length > 0 ? outputFiles : ["."];
|
|
17
19
|
const allFiles = [];
|
|
@@ -52,6 +54,12 @@ async function createLocalBuildTarArchive(config, rootDir, outputFiles) {
|
|
|
52
54
|
cwd: rootDir,
|
|
53
55
|
portable: true,
|
|
54
56
|
}, allFiles);
|
|
57
|
+
const stats = fs.statSync(tmpFile);
|
|
58
|
+
if (config.localBuild && stats.size > sizeLimitBytes) {
|
|
59
|
+
const sizeInMB = stats.size / (1024 * 1024);
|
|
60
|
+
const limitInMB = sizeLimitBytes / (1024 * 1024);
|
|
61
|
+
logger_1.logger.warn(`The final build artifact is larger than ${limitInMB.toFixed(0)}MB (current size: ${sizeInMB.toFixed(2)}MB). Please reduce the size of your build artifacts.`);
|
|
62
|
+
}
|
|
55
63
|
return tmpFile;
|
|
56
64
|
}
|
|
57
65
|
async function createSourceDeployArchive(config, rootDir, targetSubDir) {
|
|
@@ -7,7 +7,6 @@ function initDeployStats() {
|
|
|
7
7
|
numBuildErrors: 0,
|
|
8
8
|
numBuildWarnings: new Map(),
|
|
9
9
|
numServiceCreated: 0,
|
|
10
|
-
numServiceDeleted: 0,
|
|
11
10
|
numSchemaMigrated: 0,
|
|
12
11
|
numConnectorUpdatedBeforeSchema: 0,
|
|
13
12
|
numConnectorUpdatedAfterSchema: 0,
|
|
@@ -24,7 +23,6 @@ function deployStatsParams(stats) {
|
|
|
24
23
|
return {
|
|
25
24
|
missing_billing: (!!stats.missingBilling).toString(),
|
|
26
25
|
num_service_created: stats.numServiceCreated,
|
|
27
|
-
num_service_deleted: stats.numServiceDeleted,
|
|
28
26
|
num_schema_migrated: stats.numSchemaMigrated,
|
|
29
27
|
num_connector_updated_before_schema: stats.numConnectorUpdatedBeforeSchema,
|
|
30
28
|
num_connector_updated_after_schema: stats.numConnectorUpdatedAfterSchema,
|
|
@@ -9,7 +9,6 @@ const provisionCloudSql_1 = require("../../dataconnect/provisionCloudSql");
|
|
|
9
9
|
const names_1 = require("../../dataconnect/names");
|
|
10
10
|
const api_1 = require("../../api");
|
|
11
11
|
const ensureApiEnabled = require("../../ensureApiEnabled");
|
|
12
|
-
const prompt_1 = require("../../prompt");
|
|
13
12
|
async function default_1(context, options) {
|
|
14
13
|
const dataconnect = context.dataconnect;
|
|
15
14
|
if (!dataconnect) {
|
|
@@ -31,29 +30,11 @@ async function default_1(context, options) {
|
|
|
31
30
|
filters?.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
|
|
32
31
|
});
|
|
33
32
|
dataconnect.deployStats.numServiceCreated = servicesToCreate.length;
|
|
34
|
-
const servicesToDelete = filters
|
|
35
|
-
? []
|
|
36
|
-
: services.filter((s) => !serviceInfos.some((si) => matches(si, s)));
|
|
37
|
-
dataconnect.deployStats.numServiceDeleted = servicesToDelete.length;
|
|
38
33
|
await Promise.all(servicesToCreate.map(async (s) => {
|
|
39
34
|
const { projectId, locationId, serviceId } = splitName(s.serviceName);
|
|
40
35
|
await client.createService(projectId, locationId, serviceId);
|
|
41
36
|
utils.logLabeledSuccess("dataconnect", `Created service ${s.serviceName}`);
|
|
42
37
|
}));
|
|
43
|
-
if (servicesToDelete.length) {
|
|
44
|
-
const serviceToDeleteList = servicesToDelete.map((s) => " - " + s.name).join("\n");
|
|
45
|
-
if (await (0, prompt_1.confirm)({
|
|
46
|
-
force: false,
|
|
47
|
-
nonInteractive: options.nonInteractive,
|
|
48
|
-
message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${serviceToDeleteList}\nWould you like to delete these services?`,
|
|
49
|
-
default: false,
|
|
50
|
-
})) {
|
|
51
|
-
await Promise.all(servicesToDelete.map(async (s) => {
|
|
52
|
-
await client.deleteService(s.name);
|
|
53
|
-
utils.logLabeledSuccess("dataconnect", `Deleted service ${s.name}`);
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
38
|
utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
|
|
58
39
|
await Promise.all(serviceInfos
|
|
59
40
|
.filter((si) => {
|