firebase-tools 9.17.0 → 9.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/CHANGELOG.md +3 -7
- package/lib/api.js +1 -0
- package/lib/apiv2.js +5 -3
- package/lib/appdistribution/client.js +84 -72
- package/lib/appdistribution/distribution.js +8 -26
- package/lib/appdistribution/options-parser-util.js +51 -0
- package/lib/command.js +1 -1
- package/lib/commands/appdistribution-distribute.js +74 -91
- package/lib/commands/appdistribution-testers-add.js +18 -0
- package/lib/commands/appdistribution-testers-remove.js +32 -0
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/ext-configure.js +9 -1
- package/lib/commands/ext-dev-extension-delete.js +2 -1
- package/lib/commands/ext-dev-publish.js +11 -4
- package/lib/commands/ext-dev-unpublish.js +12 -4
- package/lib/commands/ext-install.js +115 -48
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +61 -18
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +45 -25
- package/lib/commands/functions-list.js +54 -0
- package/lib/commands/functions-log.js +5 -22
- package/lib/commands/hosting-channel-deploy.js +6 -4
- package/lib/commands/index.js +12 -0
- package/lib/deploy/functions/backend.js +118 -121
- package/lib/deploy/functions/checkIam.js +8 -8
- package/lib/deploy/functions/containerCleaner.js +5 -1
- package/lib/deploy/functions/deploy.js +11 -15
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +67 -33
- package/lib/deploy/functions/pricing.js +17 -17
- package/lib/deploy/functions/prompts.js +24 -41
- package/lib/deploy/functions/release/executor.js +39 -0
- package/lib/deploy/functions/release/fabricator.js +362 -0
- package/lib/deploy/functions/release/index.js +69 -0
- package/lib/deploy/functions/release/planner.js +159 -0
- package/lib/deploy/functions/release/reporter.js +162 -0
- package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
- package/lib/deploy/functions/release/timer.js +14 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +102 -126
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +34 -50
- package/lib/deploy/functions/triggerRegionHelper.js +40 -0
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +1788 -403
- package/lib/emulator/auth/handlers.js +6 -5
- package/lib/emulator/auth/operations.js +439 -40
- package/lib/emulator/auth/server.js +32 -11
- package/lib/emulator/auth/state.js +205 -5
- package/lib/emulator/auth/widget_ui.js +2 -2
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/emulatorLogger.js +0 -3
- package/lib/emulator/events/types.js +16 -0
- package/lib/emulator/functionsEmulator.js +117 -20
- package/lib/emulator/functionsEmulatorRuntime.js +46 -121
- package/lib/emulator/functionsEmulatorShared.js +51 -7
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/pubsubEmulator.js +61 -40
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/extensions/askUserForConsent.js +16 -13
- package/lib/extensions/askUserForParam.js +72 -3
- package/lib/extensions/billingMigrationHelper.js +1 -11
- package/lib/extensions/changelog.js +2 -1
- package/lib/extensions/displayExtensionInfo.js +35 -33
- package/lib/extensions/emulator/optionsHelper.js +3 -3
- package/lib/extensions/emulator/triggerHelper.js +2 -32
- package/lib/extensions/extensionsApi.js +67 -94
- package/lib/extensions/extensionsHelper.js +49 -35
- package/lib/extensions/paramHelper.js +79 -36
- package/lib/extensions/refs.js +59 -0
- package/lib/extensions/secretsUtils.js +58 -0
- package/lib/extensions/updateHelper.js +21 -45
- package/lib/extensions/warnings.js +1 -7
- package/lib/functional.js +64 -0
- package/lib/functions/env.js +26 -13
- package/lib/functions/functionslog.js +40 -0
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/cloudfunctions.js +46 -38
- package/lib/gcp/cloudfunctionsv2.js +47 -47
- package/lib/gcp/cloudlogging.js +27 -21
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/pubsub.js +1 -9
- package/lib/gcp/secretManager.js +111 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/previews.js +1 -1
- package/lib/requireInteractive.js +12 -0
- package/lib/utils.js +30 -1
- package/package.json +5 -4
- package/lib/deploy/functions/deploymentPlanner.js +0 -113
- package/lib/deploy/functions/deploymentTimer.js +0 -23
- package/lib/deploy/functions/errorHandler.js +0 -75
- package/lib/deploy/functions/release.js +0 -116
- package/lib/deploy/functions/tasks.js +0 -324
- package/lib/functionsDelete.js +0 -60
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
-
|
|
2
|
-
-
|
|
3
|
-
-
|
|
4
|
-
- `firebase ext:dev:init` now creates a placeholder `CHANGELOG.md` file.
|
|
5
|
-
- Fixes error when using a project number as the `--project` flag.
|
|
6
|
-
- `firebase init hosting` now supports the version 9 compatibility SDK (#3711).
|
|
7
|
-
- Fixes layout issues when editing fields in Firestore Emulator UI.
|
|
1
|
+
- Fix Auth Emulator deleteTenant not working with Node Admin (#3817).
|
|
2
|
+
- Fix Crashlytics Android Native Symbols not working on Windows due to ":" in the path (#3842)
|
|
3
|
+
- Fixes Firestore emulator UI showing requests out of order
|
package/lib/api.js
CHANGED
|
@@ -115,6 +115,7 @@ var api = {
|
|
|
115
115
|
serviceUsageOrigin: utils.envOverride("FIREBASE_SERVICE_USAGE_URL", "https://serviceusage.googleapis.com"),
|
|
116
116
|
githubOrigin: utils.envOverride("GITHUB_URL", "https://github.com"),
|
|
117
117
|
githubApiOrigin: utils.envOverride("GITHUB_API_URL", "https://api.github.com"),
|
|
118
|
+
secretManagerOrigin: utils.envOverride("CLOUD_SECRET_MANAGER_URL", "https://secretmanager.googleapis.com"),
|
|
118
119
|
githubClientId: utils.envOverride("GITHUB_CLIENT_ID", "89cf50f02ac6aaed3484"),
|
|
119
120
|
githubClientSecret: utils.envOverride("GITHUB_CLIENT_SECRET", "3330d14abc895d9a74d5f17cd7a00711fa2c5bf0"),
|
|
120
121
|
setRefreshToken: function (token) {
|
package/lib/apiv2.js
CHANGED
|
@@ -12,7 +12,7 @@ const error_1 = require("./error");
|
|
|
12
12
|
const logger_1 = require("./logger");
|
|
13
13
|
const responseToError = require("./responseToError");
|
|
14
14
|
const pkg = require("../package.json");
|
|
15
|
-
const CLI_VERSION = pkg.
|
|
15
|
+
const CLI_VERSION = pkg.version;
|
|
16
16
|
let accessToken = "";
|
|
17
17
|
let refreshToken = "";
|
|
18
18
|
function setRefreshToken(token = "") {
|
|
@@ -118,8 +118,10 @@ class Client {
|
|
|
118
118
|
reqOptions.headers.set("User-Agent", `FirebaseCLI/${CLI_VERSION}`);
|
|
119
119
|
}
|
|
120
120
|
reqOptions.headers.set("X-Client-Version", `FirebaseCLI/${CLI_VERSION}`);
|
|
121
|
-
if (reqOptions.
|
|
122
|
-
reqOptions.
|
|
121
|
+
if (!reqOptions.headers.has("Content-Type")) {
|
|
122
|
+
if (reqOptions.responseType === "json") {
|
|
123
|
+
reqOptions.headers.set("Content-Type", "application/json");
|
|
124
|
+
}
|
|
123
125
|
}
|
|
124
126
|
return reqOptions;
|
|
125
127
|
}
|
|
@@ -1,119 +1,104 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AppDistributionClient = exports.
|
|
3
|
+
exports.AppDistributionClient = exports.UploadReleaseResult = exports.IntegrationState = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const api = require("../api");
|
|
6
6
|
const utils = require("../utils");
|
|
7
|
+
const operationPoller = require("../operation-poller");
|
|
7
8
|
const error_1 = require("../error");
|
|
8
|
-
const
|
|
9
|
-
var
|
|
10
|
-
(function (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(function (AppView) {
|
|
27
|
-
AppView["BASIC"] = "BASIC";
|
|
28
|
-
AppView["FULL"] = "FULL";
|
|
29
|
-
})(AppView = exports.AppView || (exports.AppView = {}));
|
|
9
|
+
const apiv2_1 = require("../apiv2");
|
|
10
|
+
var IntegrationState;
|
|
11
|
+
(function (IntegrationState) {
|
|
12
|
+
IntegrationState["AAB_INTEGRATION_STATE_UNSPECIFIED"] = "AAB_INTEGRATION_STATE_UNSPECIFIED";
|
|
13
|
+
IntegrationState["INTEGRATED"] = "INTEGRATED";
|
|
14
|
+
IntegrationState["PLAY_ACCOUNT_NOT_LINKED"] = "PLAY_ACCOUNT_NOT_LINKED";
|
|
15
|
+
IntegrationState["NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT"] = "NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT";
|
|
16
|
+
IntegrationState["APP_NOT_PUBLISHED"] = "APP_NOT_PUBLISHED";
|
|
17
|
+
IntegrationState["AAB_STATE_UNAVAILABLE"] = "AAB_STATE_UNAVAILABLE";
|
|
18
|
+
IntegrationState["PLAY_IAS_TERMS_NOT_ACCEPTED"] = "PLAY_IAS_TERMS_NOT_ACCEPTED";
|
|
19
|
+
})(IntegrationState = exports.IntegrationState || (exports.IntegrationState = {}));
|
|
20
|
+
var UploadReleaseResult;
|
|
21
|
+
(function (UploadReleaseResult) {
|
|
22
|
+
UploadReleaseResult["UPLOAD_RELEASE_RESULT_UNSPECIFIED"] = "UPLOAD_RELEASE_RESULT_UNSPECIFIED";
|
|
23
|
+
UploadReleaseResult["RELEASE_CREATED"] = "RELEASE_CREATED";
|
|
24
|
+
UploadReleaseResult["RELEASE_UPDATED"] = "RELEASE_UPDATED";
|
|
25
|
+
UploadReleaseResult["RELEASE_UNMODIFIED"] = "RELEASE_UNMODIFIED";
|
|
26
|
+
})(UploadReleaseResult = exports.UploadReleaseResult || (exports.UploadReleaseResult = {}));
|
|
30
27
|
class AppDistributionClient {
|
|
31
|
-
constructor(
|
|
32
|
-
this.
|
|
28
|
+
constructor() {
|
|
29
|
+
this.appDistroV2Client = new apiv2_1.Client({
|
|
30
|
+
urlPrefix: api.appDistributionOrigin,
|
|
31
|
+
apiVersion: "v1",
|
|
32
|
+
});
|
|
33
33
|
}
|
|
34
|
-
async
|
|
35
|
-
const apiResponse = await api.request("GET", `/
|
|
34
|
+
async getAabInfo(appName) {
|
|
35
|
+
const apiResponse = await api.request("GET", `/v1/${appName}/aabInfo`, {
|
|
36
36
|
origin: api.appDistributionOrigin,
|
|
37
37
|
auth: true,
|
|
38
38
|
});
|
|
39
39
|
return _.get(apiResponse, "body");
|
|
40
40
|
}
|
|
41
|
-
async
|
|
42
|
-
const apiResponse = await api.request("POST", `/
|
|
41
|
+
async uploadRelease(appName, distribution) {
|
|
42
|
+
const apiResponse = await api.request("POST", `/upload/v1/${appName}/releases:upload`, {
|
|
43
43
|
auth: true,
|
|
44
44
|
origin: api.appDistributionOrigin,
|
|
45
45
|
headers: {
|
|
46
|
-
"X-
|
|
47
|
-
"X-
|
|
48
|
-
"X-APP-DISTRO-API-CLIENT-VERSION": pkg.version,
|
|
49
|
-
"X-GOOG-UPLOAD-FILE-NAME": distribution.getFileName(),
|
|
50
|
-
"X-GOOG-UPLOAD-PROTOCOL": "raw",
|
|
46
|
+
"X-Goog-Upload-File-Name": distribution.getFileName(),
|
|
47
|
+
"X-Goog-Upload-Protocol": "raw",
|
|
51
48
|
"Content-Type": "application/octet-stream",
|
|
52
49
|
},
|
|
53
50
|
data: distribution.readStream(),
|
|
54
51
|
json: false,
|
|
55
52
|
});
|
|
56
|
-
return _.get(JSON.parse(apiResponse.body), "
|
|
57
|
-
}
|
|
58
|
-
async pollUploadStatus(binaryName, retryCount = 0) {
|
|
59
|
-
const uploadStatus = await this.getUploadStatus(binaryName);
|
|
60
|
-
if (uploadStatus.status === UploadStatus.IN_PROGRESS) {
|
|
61
|
-
if (retryCount >= AppDistributionClient.MAX_POLLING_RETRIES) {
|
|
62
|
-
throw new error_1.FirebaseError("it took longer than expected to process your binary, please try again", { exit: 1 });
|
|
63
|
-
}
|
|
64
|
-
await new Promise((resolve) => setTimeout(resolve, AppDistributionClient.POLLING_INTERVAL_MS));
|
|
65
|
-
return this.pollUploadStatus(binaryName, retryCount + 1);
|
|
66
|
-
}
|
|
67
|
-
else if (uploadStatus.status === UploadStatus.SUCCESS) {
|
|
68
|
-
return uploadStatus.release.id;
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
throw new error_1.FirebaseError(`error processing your binary: ${uploadStatus.message} (Code: ${uploadStatus.errorCode})`);
|
|
72
|
-
}
|
|
53
|
+
return _.get(JSON.parse(apiResponse.body), "name");
|
|
73
54
|
}
|
|
74
|
-
async
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
55
|
+
async pollUploadStatus(operationName) {
|
|
56
|
+
return operationPoller.pollOperation({
|
|
57
|
+
pollerName: "App Distribution Upload Poller",
|
|
58
|
+
apiOrigin: api.appDistributionOrigin,
|
|
59
|
+
apiVersion: "v1",
|
|
60
|
+
operationResourceName: operationName,
|
|
61
|
+
masterTimeout: 5 * 60 * 1000,
|
|
62
|
+
backoff: 1000,
|
|
63
|
+
maxBackoff: 10 * 1000,
|
|
79
64
|
});
|
|
80
|
-
return _.get(apiResponse, "body");
|
|
81
65
|
}
|
|
82
|
-
async
|
|
66
|
+
async updateReleaseNotes(releaseName, releaseNotes) {
|
|
83
67
|
if (!releaseNotes) {
|
|
84
68
|
utils.logWarning("no release notes specified, skipping");
|
|
85
69
|
return;
|
|
86
70
|
}
|
|
87
|
-
utils.logBullet("
|
|
71
|
+
utils.logBullet("updating release notes...");
|
|
88
72
|
const data = {
|
|
73
|
+
name: releaseName,
|
|
89
74
|
releaseNotes: {
|
|
90
|
-
releaseNotes,
|
|
75
|
+
text: releaseNotes,
|
|
91
76
|
},
|
|
92
77
|
};
|
|
93
78
|
try {
|
|
94
|
-
await api.request("
|
|
79
|
+
await api.request("PATCH", `/v1/${releaseName}?updateMask=release_notes.text`, {
|
|
95
80
|
origin: api.appDistributionOrigin,
|
|
96
81
|
auth: true,
|
|
97
82
|
data,
|
|
98
83
|
});
|
|
99
84
|
}
|
|
100
85
|
catch (err) {
|
|
101
|
-
throw new error_1.FirebaseError(`failed to
|
|
86
|
+
throw new error_1.FirebaseError(`failed to update release notes with ${err.message}`, { exit: 1 });
|
|
102
87
|
}
|
|
103
88
|
utils.logSuccess("added release notes successfully");
|
|
104
89
|
}
|
|
105
|
-
async
|
|
106
|
-
if (
|
|
90
|
+
async distribute(releaseName, testerEmails = [], groupAliases = []) {
|
|
91
|
+
if (testerEmails.length === 0 && groupAliases.length === 0) {
|
|
107
92
|
utils.logWarning("no testers or groups specified, skipping");
|
|
108
93
|
return;
|
|
109
94
|
}
|
|
110
|
-
utils.logBullet("
|
|
95
|
+
utils.logBullet("distributing to testers/groups...");
|
|
111
96
|
const data = {
|
|
112
|
-
|
|
113
|
-
|
|
97
|
+
testerEmails,
|
|
98
|
+
groupAliases,
|
|
114
99
|
};
|
|
115
100
|
try {
|
|
116
|
-
await api.request("POST", `/
|
|
101
|
+
await api.request("POST", `/v1/${releaseName}:distribute`, {
|
|
117
102
|
origin: api.appDistributionOrigin,
|
|
118
103
|
auth: true,
|
|
119
104
|
data,
|
|
@@ -130,11 +115,38 @@ class AppDistributionClient {
|
|
|
130
115
|
errorMessage = "invalid groups";
|
|
131
116
|
}
|
|
132
117
|
}
|
|
133
|
-
throw new error_1.FirebaseError(`failed to
|
|
118
|
+
throw new error_1.FirebaseError(`failed to distribute to testers/groups: ${errorMessage}`, {
|
|
119
|
+
exit: 1,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
utils.logSuccess("distributed to testers/groups successfully");
|
|
123
|
+
}
|
|
124
|
+
async addTesters(projectName, emails) {
|
|
125
|
+
try {
|
|
126
|
+
await this.appDistroV2Client.request({
|
|
127
|
+
method: "POST",
|
|
128
|
+
path: `${projectName}/testers:batchAdd`,
|
|
129
|
+
body: { emails: emails },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
throw new error_1.FirebaseError(`Failed to add testers ${err}`);
|
|
134
|
+
}
|
|
135
|
+
utils.logSuccess(`Testers created successfully`);
|
|
136
|
+
}
|
|
137
|
+
async removeTesters(projectName, emails) {
|
|
138
|
+
let apiResponse;
|
|
139
|
+
try {
|
|
140
|
+
apiResponse = await this.appDistroV2Client.request({
|
|
141
|
+
method: "POST",
|
|
142
|
+
path: `${projectName}/testers:batchRemove`,
|
|
143
|
+
body: { emails: emails },
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
|
|
134
148
|
}
|
|
135
|
-
|
|
149
|
+
return apiResponse.body;
|
|
136
150
|
}
|
|
137
151
|
}
|
|
138
152
|
exports.AppDistributionClient = AppDistributionClient;
|
|
139
|
-
AppDistributionClient.MAX_POLLING_RETRIES = 60;
|
|
140
|
-
AppDistributionClient.POLLING_INTERVAL_MS = 2000;
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Distribution = exports.DistributionFileType = void 0;
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
5
|
const error_1 = require("../error");
|
|
6
|
-
const crypto = require("crypto");
|
|
7
6
|
const logger_1 = require("../logger");
|
|
8
7
|
const pathUtil = require("path");
|
|
9
8
|
var DistributionFileType;
|
|
@@ -16,20 +15,24 @@ class Distribution {
|
|
|
16
15
|
constructor(path) {
|
|
17
16
|
this.path = path;
|
|
18
17
|
if (!path) {
|
|
19
|
-
throw new error_1.FirebaseError("must specify a
|
|
18
|
+
throw new error_1.FirebaseError("must specify a release binary file");
|
|
20
19
|
}
|
|
21
20
|
const distributionType = path.split(".").pop();
|
|
22
21
|
if (distributionType !== DistributionFileType.IPA &&
|
|
23
22
|
distributionType !== DistributionFileType.APK &&
|
|
24
23
|
distributionType !== DistributionFileType.AAB) {
|
|
25
|
-
throw new error_1.FirebaseError("Unsupported
|
|
24
|
+
throw new error_1.FirebaseError("Unsupported file format, should be .ipa, .apk or .aab");
|
|
26
25
|
}
|
|
26
|
+
let stat;
|
|
27
27
|
try {
|
|
28
|
-
fs.
|
|
28
|
+
stat = fs.statSync(path);
|
|
29
29
|
}
|
|
30
30
|
catch (err) {
|
|
31
31
|
logger_1.logger.info(err);
|
|
32
|
-
throw new error_1.FirebaseError(
|
|
32
|
+
throw new error_1.FirebaseError(`File ${path} does not exist: verify that file points to a binary`);
|
|
33
|
+
}
|
|
34
|
+
if (!stat.isFile()) {
|
|
35
|
+
throw new error_1.FirebaseError(`${path} is not a file. Verify that it points to a binary.`);
|
|
33
36
|
}
|
|
34
37
|
this.path = path;
|
|
35
38
|
this.fileType = distributionType;
|
|
@@ -41,29 +44,8 @@ class Distribution {
|
|
|
41
44
|
readStream() {
|
|
42
45
|
return fs.createReadStream(this.path);
|
|
43
46
|
}
|
|
44
|
-
platform() {
|
|
45
|
-
switch (this.fileType) {
|
|
46
|
-
case DistributionFileType.IPA:
|
|
47
|
-
return "ios";
|
|
48
|
-
case DistributionFileType.AAB:
|
|
49
|
-
case DistributionFileType.APK:
|
|
50
|
-
return "android";
|
|
51
|
-
default:
|
|
52
|
-
throw new error_1.FirebaseError("Unsupported distribution file format, should be .ipa, .apk or .aab");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
47
|
getFileName() {
|
|
56
48
|
return this.fileName;
|
|
57
49
|
}
|
|
58
|
-
binaryName(app) {
|
|
59
|
-
return new Promise((resolve) => {
|
|
60
|
-
const hash = crypto.createHash("sha256");
|
|
61
|
-
const stream = this.readStream();
|
|
62
|
-
stream.on("data", (data) => hash.update(data));
|
|
63
|
-
stream.on("end", () => {
|
|
64
|
-
return resolve(`projects/${app.projectNumber}/apps/${app.appId}/releases/-/binaries/${hash.digest("hex")}`);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
50
|
}
|
|
69
51
|
exports.Distribution = Distribution;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAppName = exports.getProjectName = exports.ensureFileExists = exports.getEmails = exports.getTestersOrGroups = void 0;
|
|
4
|
+
const fs = require("fs-extra");
|
|
5
|
+
const error_1 = require("../error");
|
|
6
|
+
const projectUtils_1 = require("../projectUtils");
|
|
7
|
+
function getTestersOrGroups(value, file) {
|
|
8
|
+
if (!value && file) {
|
|
9
|
+
ensureFileExists(file);
|
|
10
|
+
value = fs.readFileSync(file, "utf8");
|
|
11
|
+
}
|
|
12
|
+
if (value) {
|
|
13
|
+
return splitter(value);
|
|
14
|
+
}
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
exports.getTestersOrGroups = getTestersOrGroups;
|
|
18
|
+
function getEmails(emails, file) {
|
|
19
|
+
if (emails.length == 0) {
|
|
20
|
+
ensureFileExists(file);
|
|
21
|
+
const readFile = fs.readFileSync(file, "utf8");
|
|
22
|
+
return splitter(readFile);
|
|
23
|
+
}
|
|
24
|
+
return emails;
|
|
25
|
+
}
|
|
26
|
+
exports.getEmails = getEmails;
|
|
27
|
+
function ensureFileExists(file, message = "") {
|
|
28
|
+
if (!fs.existsSync(file)) {
|
|
29
|
+
throw new error_1.FirebaseError(`File ${file} does not exist: ${message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.ensureFileExists = ensureFileExists;
|
|
33
|
+
function splitter(value) {
|
|
34
|
+
return value
|
|
35
|
+
.split(/[,\n]/)
|
|
36
|
+
.map((entry) => entry.trim())
|
|
37
|
+
.filter((entry) => !!entry);
|
|
38
|
+
}
|
|
39
|
+
async function getProjectName(options) {
|
|
40
|
+
const projectNumber = await projectUtils_1.needProjectNumber(options);
|
|
41
|
+
return `projects/${projectNumber}`;
|
|
42
|
+
}
|
|
43
|
+
exports.getProjectName = getProjectName;
|
|
44
|
+
function getAppName(options) {
|
|
45
|
+
if (!options.app) {
|
|
46
|
+
throw new error_1.FirebaseError("set the --app option to a valid Firebase app id and try again");
|
|
47
|
+
}
|
|
48
|
+
const appId = options.app;
|
|
49
|
+
return `projects/${appId.split(":")[1]}/apps/${appId}`;
|
|
50
|
+
}
|
|
51
|
+
exports.getAppName = getAppName;
|
package/lib/command.js
CHANGED
|
@@ -131,12 +131,12 @@ class Command {
|
|
|
131
131
|
}
|
|
132
132
|
const account = utils_1.getInheritedOption(options, "account");
|
|
133
133
|
options.account = account;
|
|
134
|
+
options.projectRoot = detectProjectRoot_1.detectProjectRoot(options);
|
|
134
135
|
const projectRoot = options.projectRoot;
|
|
135
136
|
const activeAccount = auth_1.selectAccount(account, projectRoot);
|
|
136
137
|
if (activeAccount) {
|
|
137
138
|
auth_1.setActiveAccount(options, activeAccount);
|
|
138
139
|
}
|
|
139
|
-
options.projectRoot = detectProjectRoot_1.detectProjectRoot(options);
|
|
140
140
|
this.applyRC(options);
|
|
141
141
|
if (options.project) {
|
|
142
142
|
await this.resolveProjectIdentifiers(options);
|
|
@@ -7,128 +7,111 @@ const requireAuth_1 = require("../requireAuth");
|
|
|
7
7
|
const client_1 = require("../appdistribution/client");
|
|
8
8
|
const error_1 = require("../error");
|
|
9
9
|
const distribution_1 = require("../appdistribution/distribution");
|
|
10
|
-
|
|
11
|
-
if (!fs.existsSync(file)) {
|
|
12
|
-
throw new error_1.FirebaseError(`File ${file} does not exist: ${message}`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function getAppId(appId) {
|
|
16
|
-
if (!appId) {
|
|
17
|
-
throw new error_1.FirebaseError("set the --app option to a valid Firebase app id and try again");
|
|
18
|
-
}
|
|
19
|
-
return appId;
|
|
20
|
-
}
|
|
10
|
+
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
21
11
|
function getReleaseNotes(releaseNotes, releaseNotesFile) {
|
|
22
12
|
if (releaseNotes) {
|
|
23
13
|
return releaseNotes.replace(/\\n/g, "\n");
|
|
24
14
|
}
|
|
25
15
|
else if (releaseNotesFile) {
|
|
26
|
-
ensureFileExists(releaseNotesFile);
|
|
16
|
+
options_parser_util_1.ensureFileExists(releaseNotesFile);
|
|
27
17
|
return fs.readFileSync(releaseNotesFile, "utf8");
|
|
28
18
|
}
|
|
29
19
|
return "";
|
|
30
20
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
ensureFileExists(file);
|
|
34
|
-
value = fs.readFileSync(file, "utf8");
|
|
35
|
-
}
|
|
36
|
-
if (value) {
|
|
37
|
-
return value
|
|
38
|
-
.split(/,|\n/)
|
|
39
|
-
.map((entry) => entry.trim())
|
|
40
|
-
.filter((entry) => !!entry);
|
|
41
|
-
}
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
module.exports = new command_1.Command("appdistribution:distribute <distribution-file>")
|
|
45
|
-
.description("upload a distribution")
|
|
21
|
+
module.exports = new command_1.Command("appdistribution:distribute <release-binary-file>")
|
|
22
|
+
.description("upload a release binary")
|
|
46
23
|
.option("--app <app_id>", "the app id of your Firebase app")
|
|
47
|
-
.option("--release-notes <string>", "release notes to include
|
|
48
|
-
.option("--release-notes-file <file>", "path to file with release notes
|
|
24
|
+
.option("--release-notes <string>", "release notes to include")
|
|
25
|
+
.option("--release-notes-file <file>", "path to file with release notes")
|
|
49
26
|
.option("--testers <string>", "a comma separated list of tester emails to distribute to")
|
|
50
27
|
.option("--testers-file <file>", "path to file with a comma separated list of tester emails to distribute to")
|
|
51
28
|
.option("--groups <string>", "a comma separated list of group aliases to distribute to")
|
|
52
29
|
.option("--groups-file <file>", "path to file with a comma separated list of group aliases to distribute to")
|
|
53
30
|
.before(requireAuth_1.requireAuth)
|
|
54
31
|
.action(async (file, options) => {
|
|
55
|
-
const
|
|
32
|
+
const appName = options_parser_util_1.getAppName(options);
|
|
56
33
|
const distribution = new distribution_1.Distribution(file);
|
|
57
34
|
const releaseNotes = getReleaseNotes(options.releaseNotes, options.releaseNotesFile);
|
|
58
|
-
const testers = getTestersOrGroups(options.testers, options.testersFile);
|
|
59
|
-
const groups = getTestersOrGroups(options.groups, options.groupsFile);
|
|
60
|
-
const requests = new client_1.AppDistributionClient(
|
|
61
|
-
let
|
|
35
|
+
const testers = options_parser_util_1.getTestersOrGroups(options.testers, options.testersFile);
|
|
36
|
+
const groups = options_parser_util_1.getTestersOrGroups(options.groups, options.groupsFile);
|
|
37
|
+
const requests = new client_1.AppDistributionClient();
|
|
38
|
+
let aabInfo;
|
|
39
|
+
if (distribution.distributionFileType() === distribution_1.DistributionFileType.AAB) {
|
|
40
|
+
try {
|
|
41
|
+
aabInfo = await requests.getAabInfo(appName);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.status === 404) {
|
|
45
|
+
throw new error_1.FirebaseError(`App Distribution could not find your app ${options.app}. ` +
|
|
46
|
+
`Make sure to onboard your app by pressing the "Get started" ` +
|
|
47
|
+
"button on the App Distribution page in the Firebase console: " +
|
|
48
|
+
"https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
|
|
49
|
+
}
|
|
50
|
+
throw new error_1.FirebaseError(`failed to determine AAB info. ${err.message}`, { exit: 1 });
|
|
51
|
+
}
|
|
52
|
+
if (aabInfo.integrationState !== client_1.IntegrationState.INTEGRATED &&
|
|
53
|
+
aabInfo.integrationState !== client_1.IntegrationState.AAB_STATE_UNAVAILABLE) {
|
|
54
|
+
switch (aabInfo.integrationState) {
|
|
55
|
+
case client_1.IntegrationState.PLAY_ACCOUNT_NOT_LINKED: {
|
|
56
|
+
throw new error_1.FirebaseError("This project is not linked to a Google Play account.");
|
|
57
|
+
}
|
|
58
|
+
case client_1.IntegrationState.APP_NOT_PUBLISHED: {
|
|
59
|
+
throw new error_1.FirebaseError('"This app is not published in the Google Play console.');
|
|
60
|
+
}
|
|
61
|
+
case client_1.IntegrationState.NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT: {
|
|
62
|
+
throw new error_1.FirebaseError("App with matching package name does not exist in Google Play.");
|
|
63
|
+
}
|
|
64
|
+
case client_1.IntegrationState.PLAY_IAS_TERMS_NOT_ACCEPTED: {
|
|
65
|
+
throw new error_1.FirebaseError("You must accept the Play Internal App Sharing (IAS) terms to upload AABs.");
|
|
66
|
+
}
|
|
67
|
+
default: {
|
|
68
|
+
throw new error_1.FirebaseError("App Distribution failed to process the AAB: " + aabInfo.integrationState);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
utils.logBullet("uploading binary...");
|
|
74
|
+
let releaseName;
|
|
62
75
|
try {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
const operationName = await requests.uploadRelease(appName, distribution);
|
|
77
|
+
const uploadResponse = await requests.pollUploadStatus(operationName);
|
|
78
|
+
const release = uploadResponse.release;
|
|
79
|
+
switch (uploadResponse.result) {
|
|
80
|
+
case client_1.UploadReleaseResult.RELEASE_CREATED:
|
|
81
|
+
utils.logSuccess(`uploaded new release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
82
|
+
break;
|
|
83
|
+
case client_1.UploadReleaseResult.RELEASE_UPDATED:
|
|
84
|
+
utils.logSuccess(`uploaded update to existing release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
85
|
+
break;
|
|
86
|
+
case client_1.UploadReleaseResult.RELEASE_UNMODIFIED:
|
|
87
|
+
utils.logSuccess(`re-uploaded already existing release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
utils.logSuccess(`uploaded release ${release.displayVersion} (${release.buildVersion}) successfully!`);
|
|
91
|
+
}
|
|
92
|
+
releaseName = uploadResponse.release.name;
|
|
67
93
|
}
|
|
68
94
|
catch (err) {
|
|
69
95
|
if (err.status === 404) {
|
|
70
|
-
throw new error_1.FirebaseError(`App Distribution could not find your app ${
|
|
96
|
+
throw new error_1.FirebaseError(`App Distribution could not find your app ${options.app}. ` +
|
|
71
97
|
`Make sure to onboard your app by pressing the "Get started" ` +
|
|
72
98
|
"button on the App Distribution page in the Firebase console: " +
|
|
73
99
|
"https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
|
|
74
100
|
}
|
|
75
|
-
throw new error_1.FirebaseError(`failed to
|
|
76
|
-
}
|
|
77
|
-
if (!app.contactEmail) {
|
|
78
|
-
throw new error_1.FirebaseError(`We could not find a contact email for app ${appId}. Please visit App Distribution within ` +
|
|
79
|
-
"the Firebase Console to set one up.", { exit: 1 });
|
|
80
|
-
}
|
|
81
|
-
if (distribution.distributionFileType() === distribution_1.DistributionFileType.AAB &&
|
|
82
|
-
app.aabState !== client_1.AabState.ACTIVE &&
|
|
83
|
-
app.aabState !== client_1.AabState.AAB_STATE_UNAVAILABLE) {
|
|
84
|
-
switch (app.aabState) {
|
|
85
|
-
case client_1.AabState.PLAY_ACCOUNT_NOT_LINKED: {
|
|
86
|
-
throw new error_1.FirebaseError("This project is not linked to a Google Play account.");
|
|
87
|
-
}
|
|
88
|
-
case client_1.AabState.APP_NOT_PUBLISHED: {
|
|
89
|
-
throw new error_1.FirebaseError('"This app is not published in the Google Play console.');
|
|
90
|
-
}
|
|
91
|
-
case client_1.AabState.NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT: {
|
|
92
|
-
throw new error_1.FirebaseError("App with matching package name does not exist in Google Play.");
|
|
93
|
-
}
|
|
94
|
-
case client_1.AabState.PLAY_IAS_TERMS_NOT_ACCEPTED: {
|
|
95
|
-
throw new error_1.FirebaseError("You must accept the Play Internal App Sharing (IAS) terms to upload AABs.");
|
|
96
|
-
}
|
|
97
|
-
default: {
|
|
98
|
-
throw new error_1.FirebaseError("App Distribution failed to process the AAB: " + app.aabState);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
let binaryName = await distribution.binaryName(app);
|
|
103
|
-
let releaseId;
|
|
104
|
-
const uploadStatus = await requests.getUploadStatus(binaryName);
|
|
105
|
-
if (uploadStatus.status === client_1.UploadStatus.SUCCESS) {
|
|
106
|
-
utils.logWarning("this distribution has been uploaded before, skipping upload");
|
|
107
|
-
releaseId = uploadStatus.release.id;
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
utils.logBullet("uploading distribution...");
|
|
111
|
-
try {
|
|
112
|
-
binaryName = await requests.uploadDistribution(distribution);
|
|
113
|
-
releaseId = await requests.pollUploadStatus(binaryName);
|
|
114
|
-
utils.logSuccess("uploaded distribution successfully!");
|
|
115
|
-
}
|
|
116
|
-
catch (err) {
|
|
117
|
-
throw new error_1.FirebaseError(`failed to upload distribution. ${err.message}`, { exit: 1 });
|
|
118
|
-
}
|
|
101
|
+
throw new error_1.FirebaseError(`failed to upload release. ${err.message}`, { exit: 1 });
|
|
119
102
|
}
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
if (
|
|
103
|
+
if (aabInfo && !aabInfo.testCertificate) {
|
|
104
|
+
aabInfo = await requests.getAabInfo(appName);
|
|
105
|
+
if (aabInfo.testCertificate) {
|
|
123
106
|
utils.logBullet("After you upload an AAB for the first time, App Distribution " +
|
|
124
107
|
"generates a new test certificate. All AAB uploads are re-signed with this test " +
|
|
125
108
|
"certificate. Use the certificate fingerprints below to register your app " +
|
|
126
109
|
"signing key with API providers, such as Google Sign-In and Google Maps.\n" +
|
|
127
|
-
`MD-1 certificate fingerprint: ${
|
|
128
|
-
`SHA-1 certificate fingerprint: ${
|
|
129
|
-
`SHA-256 certificate fingerprint: ${
|
|
110
|
+
`MD-1 certificate fingerprint: ${aabInfo.testCertificate.hashMd5}\n` +
|
|
111
|
+
`SHA-1 certificate fingerprint: ${aabInfo.testCertificate.hashSha1}\n` +
|
|
112
|
+
`SHA-256 certificate fingerprint: ${aabInfo.testCertificate.hashSha256}`);
|
|
130
113
|
}
|
|
131
114
|
}
|
|
132
|
-
await requests.
|
|
133
|
-
await requests.
|
|
115
|
+
await requests.updateReleaseNotes(releaseName, releaseNotes);
|
|
116
|
+
await requests.distribute(releaseName, testers, groups);
|
|
134
117
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const command_1 = require("../command");
|
|
4
|
+
const utils = require("../utils");
|
|
5
|
+
const requireAuth_1 = require("../requireAuth");
|
|
6
|
+
const client_1 = require("../appdistribution/client");
|
|
7
|
+
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
8
|
+
module.exports = new command_1.Command("appdistribution:testers:add [emails...]")
|
|
9
|
+
.description("add testers to project")
|
|
10
|
+
.option("--file <file>", "a path to a file containing a list of tester emails to be added")
|
|
11
|
+
.before(requireAuth_1.requireAuth)
|
|
12
|
+
.action(async (emails, options) => {
|
|
13
|
+
const projectName = await options_parser_util_1.getProjectName(options);
|
|
14
|
+
const appDistroClient = new client_1.AppDistributionClient();
|
|
15
|
+
const emailsToAdd = options_parser_util_1.getEmails(emails, options.file);
|
|
16
|
+
utils.logBullet(`Adding ${emailsToAdd.length} testers to project`);
|
|
17
|
+
await appDistroClient.addTesters(projectName, emailsToAdd);
|
|
18
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const command_1 = require("../command");
|
|
4
|
+
const utils = require("../utils");
|
|
5
|
+
const requireAuth_1 = require("../requireAuth");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
const client_1 = require("../appdistribution/client");
|
|
8
|
+
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
module.exports = new command_1.Command("appdistribution:testers:remove [emails...]")
|
|
11
|
+
.description("remove testers from a project")
|
|
12
|
+
.option("--file <file>", "a path to a file containing a list of tester emails to be removed")
|
|
13
|
+
.before(requireAuth_1.requireAuth)
|
|
14
|
+
.action(async (emails, options) => {
|
|
15
|
+
const projectName = await options_parser_util_1.getProjectName(options);
|
|
16
|
+
const appDistroClient = new client_1.AppDistributionClient();
|
|
17
|
+
const emailsArr = options_parser_util_1.getEmails(emails, options.file);
|
|
18
|
+
let deleteResponse;
|
|
19
|
+
try {
|
|
20
|
+
utils.logBullet(`Deleting ${emailsArr.length} testers from project`);
|
|
21
|
+
deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
|
|
25
|
+
}
|
|
26
|
+
if (!deleteResponse.emails) {
|
|
27
|
+
utils.logSuccess(`Testers did not exist`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
logger_1.logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`);
|
|
31
|
+
utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`);
|
|
32
|
+
});
|