firebase-tools 13.25.0 → 13.27.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 +62 -8
- package/lib/appdistribution/distribution.js +1 -1
- package/lib/apphosting/backend.js +4 -4
- package/lib/apphosting/config.js +86 -9
- package/lib/apphosting/secrets/index.js +5 -43
- package/lib/apphosting/yaml.js +3 -0
- package/lib/archiveDirectory.js +1 -1
- package/lib/auth.js +1 -1
- package/lib/command.js +9 -1
- package/lib/commands/appdistribution-distribute.js +4 -4
- package/lib/commands/{appdistribution-group-create.js → appdistribution-groups-create.js} +2 -1
- package/lib/commands/{appdistribution-group-delete.js → appdistribution-groups-delete.js} +3 -2
- package/lib/commands/appdistribution-groups-list.js +56 -0
- package/lib/commands/appdistribution-testers-list.js +54 -0
- package/lib/commands/appdistribution-testers-remove.js +1 -1
- package/lib/commands/apphosting-backends-delete.js +3 -1
- package/lib/commands/apphosting-backends-get.js +1 -1
- package/lib/commands/apphosting-config-export.js +4 -26
- package/lib/commands/database-import.js +4 -2
- package/lib/commands/database-push.js +4 -2
- package/lib/commands/database-set.js +4 -2
- package/lib/commands/database-settings-get.js +1 -1
- package/lib/commands/database-settings-set.js +1 -1
- package/lib/commands/ext-dev-init.js +2 -2
- package/lib/commands/ext-dev-list.js +1 -1
- package/lib/commands/ext-dev-register.js +2 -2
- package/lib/commands/ext-dev-upload.js +2 -2
- package/lib/commands/ext-dev-usage.js +2 -2
- package/lib/commands/ext-install.js +2 -2
- package/lib/commands/index.js +7 -6
- package/lib/commands/use.js +1 -1
- package/lib/deploy/extensions/deploy.js +3 -1
- package/lib/deploy/extensions/deploymentSummary.js +4 -1
- package/lib/deploy/extensions/planner.js +14 -3
- package/lib/deploy/extensions/prepare.js +9 -9
- package/lib/deploy/functions/ensure.js +1 -1
- package/lib/deploy/functions/release/fabricator.js +3 -3
- package/lib/deploy/lifecycleHooks.js +2 -1
- package/lib/emulator/apphosting/config.js +13 -3
- package/lib/emulator/apphosting/developmentServer.js +32 -0
- package/lib/emulator/apphosting/index.js +5 -3
- package/lib/emulator/apphosting/serve.js +15 -12
- package/lib/emulator/commandUtils.js +2 -1
- package/lib/emulator/controller.js +27 -18
- package/lib/emulator/dataconnect/pgliteServer.js +51 -18
- package/lib/emulator/dataconnectEmulator.js +26 -2
- package/lib/emulator/downloadableEmulators.js +11 -11
- package/lib/emulator/hub.js +26 -7
- package/lib/emulator/hubExport.js +23 -0
- package/lib/emulator/initEmulators.js +49 -0
- package/lib/emulator/types.js +2 -2
- package/lib/emulator/ui.js +47 -25
- package/lib/error.js +8 -1
- package/lib/gcp/cloudfunctionsv2.js +1 -0
- package/lib/getProjectNumber.js +1 -1
- package/lib/init/features/emulators.js +8 -0
- package/lib/init/features/project.js +7 -6
- package/lib/logger.js +2 -2
- package/lib/management/projects.js +24 -4
- package/lib/projectUtils.js +1 -1
- package/lib/requireDatabaseInstance.js +1 -1
- package/lib/requirePermissions.js +1 -1
- package/lib/rulesDeploy.js +1 -1
- package/lib/templates.js +2 -2
- package/lib/utils.js +20 -8
- package/lib/vsCodeUtils.js +8 -0
- package/package.json +2 -2
- package/schema/firebase-config.json +6 -0
- package/lib/emulator/apphosting/utils.js +0 -18
|
@@ -65,7 +65,7 @@ class AppDistributionClient {
|
|
|
65
65
|
await this.appDistroV1Client.patch(`/${releaseName}`, data, { queryParams });
|
|
66
66
|
}
|
|
67
67
|
catch (err) {
|
|
68
|
-
throw new error_1.FirebaseError(`failed to update release notes with ${
|
|
68
|
+
throw new error_1.FirebaseError(`failed to update release notes with ${(0, error_1.getErrMsg)(err)}`);
|
|
69
69
|
}
|
|
70
70
|
utils.logSuccess("added release notes successfully");
|
|
71
71
|
}
|
|
@@ -98,6 +98,39 @@ class AppDistributionClient {
|
|
|
98
98
|
}
|
|
99
99
|
utils.logSuccess("distributed to testers/groups successfully");
|
|
100
100
|
}
|
|
101
|
+
async listTesters(projectName, groupName) {
|
|
102
|
+
const listTestersResponse = {
|
|
103
|
+
testers: [],
|
|
104
|
+
};
|
|
105
|
+
const client = this.appDistroV1Client;
|
|
106
|
+
let pageToken;
|
|
107
|
+
const filter = groupName ? `groups=${projectName}/groups/${groupName}` : null;
|
|
108
|
+
do {
|
|
109
|
+
const queryParams = pageToken ? { pageToken } : {};
|
|
110
|
+
if (filter != null) {
|
|
111
|
+
queryParams["filter"] = filter;
|
|
112
|
+
}
|
|
113
|
+
let apiResponse;
|
|
114
|
+
try {
|
|
115
|
+
apiResponse = await client.get(`${projectName}/testers`, {
|
|
116
|
+
queryParams,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
throw new error_1.FirebaseError(`Client request failed to list testers ${err}`);
|
|
121
|
+
}
|
|
122
|
+
for (const t of apiResponse.body.testers) {
|
|
123
|
+
listTestersResponse.testers.push({
|
|
124
|
+
name: t.name,
|
|
125
|
+
displayName: t.displayName,
|
|
126
|
+
groups: t.groups,
|
|
127
|
+
lastActivityTime: new Date(t.lastActivityTime),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
pageToken = apiResponse.body.nextPageToken;
|
|
131
|
+
} while (pageToken);
|
|
132
|
+
return listTestersResponse;
|
|
133
|
+
}
|
|
101
134
|
async addTesters(projectName, emails) {
|
|
102
135
|
try {
|
|
103
136
|
await this.appDistroV1Client.request({
|
|
@@ -107,7 +140,7 @@ class AppDistributionClient {
|
|
|
107
140
|
});
|
|
108
141
|
}
|
|
109
142
|
catch (err) {
|
|
110
|
-
throw new error_1.FirebaseError(`Failed to add testers ${err}`);
|
|
143
|
+
throw new error_1.FirebaseError(`Failed to add testers ${(0, error_1.getErrMsg)(err)}`);
|
|
111
144
|
}
|
|
112
145
|
utils.logSuccess(`Testers created successfully`);
|
|
113
146
|
}
|
|
@@ -121,10 +154,31 @@ class AppDistributionClient {
|
|
|
121
154
|
});
|
|
122
155
|
}
|
|
123
156
|
catch (err) {
|
|
124
|
-
throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
|
|
157
|
+
throw new error_1.FirebaseError(`Failed to remove testers ${(0, error_1.getErrMsg)(err)}`);
|
|
125
158
|
}
|
|
126
159
|
return apiResponse.body;
|
|
127
160
|
}
|
|
161
|
+
async listGroups(projectName) {
|
|
162
|
+
const listGroupsResponse = {
|
|
163
|
+
groups: [],
|
|
164
|
+
};
|
|
165
|
+
const client = this.appDistroV1Client;
|
|
166
|
+
let pageToken;
|
|
167
|
+
do {
|
|
168
|
+
const queryParams = pageToken ? { pageToken } : {};
|
|
169
|
+
try {
|
|
170
|
+
const apiResponse = await client.get(`${projectName}/groups`, {
|
|
171
|
+
queryParams,
|
|
172
|
+
});
|
|
173
|
+
listGroupsResponse.groups.push(...(apiResponse.body.groups || []));
|
|
174
|
+
pageToken = apiResponse.body.nextPageToken;
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
throw new error_1.FirebaseError(`Client failed to list groups ${err}`);
|
|
178
|
+
}
|
|
179
|
+
} while (pageToken);
|
|
180
|
+
return listGroupsResponse;
|
|
181
|
+
}
|
|
128
182
|
async createGroup(projectName, displayName, alias) {
|
|
129
183
|
let apiResponse;
|
|
130
184
|
try {
|
|
@@ -135,7 +189,7 @@ class AppDistributionClient {
|
|
|
135
189
|
});
|
|
136
190
|
}
|
|
137
191
|
catch (err) {
|
|
138
|
-
throw new error_1.FirebaseError(`Failed to create group ${err}`);
|
|
192
|
+
throw new error_1.FirebaseError(`Failed to create group ${(0, error_1.getErrMsg)(err)}`);
|
|
139
193
|
}
|
|
140
194
|
return apiResponse.body;
|
|
141
195
|
}
|
|
@@ -147,7 +201,7 @@ class AppDistributionClient {
|
|
|
147
201
|
});
|
|
148
202
|
}
|
|
149
203
|
catch (err) {
|
|
150
|
-
throw new error_1.FirebaseError(`Failed to delete group ${err}`);
|
|
204
|
+
throw new error_1.FirebaseError(`Failed to delete group ${(0, error_1.getErrMsg)(err)}`);
|
|
151
205
|
}
|
|
152
206
|
utils.logSuccess(`Group deleted successfully`);
|
|
153
207
|
}
|
|
@@ -160,7 +214,7 @@ class AppDistributionClient {
|
|
|
160
214
|
});
|
|
161
215
|
}
|
|
162
216
|
catch (err) {
|
|
163
|
-
throw new error_1.FirebaseError(`Failed to add testers to group ${err}`);
|
|
217
|
+
throw new error_1.FirebaseError(`Failed to add testers to group ${(0, error_1.getErrMsg)(err)}`);
|
|
164
218
|
}
|
|
165
219
|
utils.logSuccess(`Testers added to group successfully`);
|
|
166
220
|
}
|
|
@@ -173,7 +227,7 @@ class AppDistributionClient {
|
|
|
173
227
|
});
|
|
174
228
|
}
|
|
175
229
|
catch (err) {
|
|
176
|
-
throw new error_1.FirebaseError(`Failed to remove testers from group ${err}`);
|
|
230
|
+
throw new error_1.FirebaseError(`Failed to remove testers from group ${(0, error_1.getErrMsg)(err)}`);
|
|
177
231
|
}
|
|
178
232
|
utils.logSuccess(`Testers removed from group successfully`);
|
|
179
233
|
}
|
|
@@ -190,7 +244,7 @@ class AppDistributionClient {
|
|
|
190
244
|
return response.body;
|
|
191
245
|
}
|
|
192
246
|
catch (err) {
|
|
193
|
-
throw new error_1.FirebaseError(`Failed to create release test ${err}`);
|
|
247
|
+
throw new error_1.FirebaseError(`Failed to create release test ${(0, error_1.getErrMsg)(err)}`);
|
|
194
248
|
}
|
|
195
249
|
}
|
|
196
250
|
async getReleaseTest(releaseTestName) {
|
|
@@ -28,7 +28,7 @@ class Distribution {
|
|
|
28
28
|
stat = fs.statSync(path);
|
|
29
29
|
}
|
|
30
30
|
catch (err) {
|
|
31
|
-
logger_1.logger.info(err);
|
|
31
|
+
logger_1.logger.info((0, error_1.getErrMsg)(err));
|
|
32
32
|
throw new error_1.FirebaseError(`File ${path} does not exist: verify that file points to a binary`);
|
|
33
33
|
}
|
|
34
34
|
if (!stat.isFile()) {
|
|
@@ -172,10 +172,10 @@ async function promptNewBackendId(projectId, location, prompt) {
|
|
|
172
172
|
await apphosting.getBackend(projectId, location, backendId);
|
|
173
173
|
}
|
|
174
174
|
catch (err) {
|
|
175
|
-
if (err
|
|
175
|
+
if ((0, error_1.getErrStatus)(err) === 404) {
|
|
176
176
|
return backendId;
|
|
177
177
|
}
|
|
178
|
-
throw new error_1.FirebaseError(`Failed to check if backend with id ${backendId} already exists in ${location}`, { original: err });
|
|
178
|
+
throw new error_1.FirebaseError(`Failed to check if backend with id ${backendId} already exists in ${location}`, { original: (0, error_1.getError)(err) });
|
|
179
179
|
}
|
|
180
180
|
(0, utils_1.logWarning)(`Backend with id ${backendId} already exists in ${location}`);
|
|
181
181
|
}
|
|
@@ -207,7 +207,7 @@ async function provisionDefaultComputeServiceAccount(projectId) {
|
|
|
207
207
|
await iam.createServiceAccount(projectId, DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME, "Default service account used to run builds and deploys for Firebase App Hosting", "Firebase App Hosting compute service account");
|
|
208
208
|
}
|
|
209
209
|
catch (err) {
|
|
210
|
-
if (err
|
|
210
|
+
if ((0, error_1.getErrStatus)(err) !== 409) {
|
|
211
211
|
throw err;
|
|
212
212
|
}
|
|
213
213
|
}
|
|
@@ -254,7 +254,7 @@ async function getBackendForLocation(projectId, location, backendId) {
|
|
|
254
254
|
}
|
|
255
255
|
catch (err) {
|
|
256
256
|
throw new error_1.FirebaseError(`No backend named "${backendId}" found in ${location}.`, {
|
|
257
|
-
original: err,
|
|
257
|
+
original: (0, error_1.getError)(err),
|
|
258
258
|
});
|
|
259
259
|
}
|
|
260
260
|
}
|
package/lib/apphosting/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadConfigForEnvironment = exports.maybeAddSecretToYaml = exports.upsertEnv = exports.findEnv = exports.store = exports.load = exports.
|
|
3
|
+
exports.loadConfigToExportSecrets = exports.loadConfigForEnvironment = exports.exportConfig = exports.maybeAddSecretToYaml = exports.upsertEnv = exports.findEnv = exports.store = exports.load = exports.listAppHostingFilesInPath = exports.discoverBackendRoot = exports.APPHOSTING_YAML_FILE_REGEX = exports.APPHOSTING_LOCAL_YAML_FILE = exports.APPHOSTING_BASE_YAML_FILE = void 0;
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const yaml = require("yaml");
|
|
@@ -9,9 +9,16 @@ const prompt = require("../prompt");
|
|
|
9
9
|
const dialogs = require("./secrets/dialogs");
|
|
10
10
|
const yaml_1 = require("./yaml");
|
|
11
11
|
const error_1 = require("../error");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
const secrets_1 = require("./secrets");
|
|
14
|
+
const logger_1 = require("../logger");
|
|
15
|
+
const utils_2 = require("../utils");
|
|
16
|
+
const projects_1 = require("../management/projects");
|
|
12
17
|
exports.APPHOSTING_BASE_YAML_FILE = "apphosting.yaml";
|
|
13
18
|
exports.APPHOSTING_LOCAL_YAML_FILE = "apphosting.local.yaml";
|
|
14
19
|
exports.APPHOSTING_YAML_FILE_REGEX = /^apphosting(\.[a-z0-9_]+)?\.yaml$/;
|
|
20
|
+
const SECRET_CONFIG = "Secret";
|
|
21
|
+
const EXPORTABLE_CONFIG = [SECRET_CONFIG];
|
|
15
22
|
function discoverBackendRoot(cwd) {
|
|
16
23
|
let dir = cwd;
|
|
17
24
|
while (!fs.fileExistsSync((0, path_1.resolve)(dir, exports.APPHOSTING_BASE_YAML_FILE))) {
|
|
@@ -27,20 +34,13 @@ function discoverBackendRoot(cwd) {
|
|
|
27
34
|
return dir;
|
|
28
35
|
}
|
|
29
36
|
exports.discoverBackendRoot = discoverBackendRoot;
|
|
30
|
-
function discoverConfigsAtBackendRoot(cwd) {
|
|
31
|
-
const backendRoot = discoverBackendRoot(cwd);
|
|
32
|
-
if (!backendRoot) {
|
|
33
|
-
throw new error_1.FirebaseError("Unable to find your project's root, ensure the apphosting.yaml config is initialized. Try 'firebase init apphosting'");
|
|
34
|
-
}
|
|
35
|
-
return listAppHostingFilesInPath(backendRoot);
|
|
36
|
-
}
|
|
37
|
-
exports.discoverConfigsAtBackendRoot = discoverConfigsAtBackendRoot;
|
|
38
37
|
function listAppHostingFilesInPath(path) {
|
|
39
38
|
return fs
|
|
40
39
|
.listFiles(path)
|
|
41
40
|
.filter((file) => exports.APPHOSTING_YAML_FILE_REGEX.test(file))
|
|
42
41
|
.map((file) => (0, path_1.join)(path, file));
|
|
43
42
|
}
|
|
43
|
+
exports.listAppHostingFilesInPath = listAppHostingFilesInPath;
|
|
44
44
|
function load(yamlPath) {
|
|
45
45
|
const raw = fs.readFile(yamlPath);
|
|
46
46
|
return yaml.parseDocument(raw);
|
|
@@ -116,6 +116,49 @@ async function maybeAddSecretToYaml(secretName) {
|
|
|
116
116
|
dynamicDispatch.store(path, projectYaml);
|
|
117
117
|
}
|
|
118
118
|
exports.maybeAddSecretToYaml = maybeAddSecretToYaml;
|
|
119
|
+
async function exportConfig(cwd, projectRoot, backendRoot, projectId, userGivenConfigFile) {
|
|
120
|
+
const choices = await prompt.prompt({}, [
|
|
121
|
+
{
|
|
122
|
+
type: "checkbox",
|
|
123
|
+
name: "configurations",
|
|
124
|
+
message: "What configs would you like to export?",
|
|
125
|
+
choices: EXPORTABLE_CONFIG,
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
if (!choices.configurations.includes(SECRET_CONFIG)) {
|
|
129
|
+
logger_1.logger.info("No configs selected to export");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!projectId) {
|
|
133
|
+
const project = await (0, projects_1.getOrPromptProject)({});
|
|
134
|
+
projectId = project.projectId;
|
|
135
|
+
}
|
|
136
|
+
let localAppHostingConfig = yaml_1.AppHostingYamlConfig.empty();
|
|
137
|
+
const localAppHostingConfigPath = (0, path_1.resolve)(backendRoot, exports.APPHOSTING_LOCAL_YAML_FILE);
|
|
138
|
+
if (fs.fileExistsSync(localAppHostingConfigPath)) {
|
|
139
|
+
localAppHostingConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localAppHostingConfigPath);
|
|
140
|
+
}
|
|
141
|
+
const configToExport = await loadConfigToExportSecrets(cwd, userGivenConfigFile);
|
|
142
|
+
const secretsToExport = configToExport.secrets;
|
|
143
|
+
if (!secretsToExport) {
|
|
144
|
+
logger_1.logger.info("No secrets found to export in the chosen App Hosting config files");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const secretMaterial = await (0, secrets_1.fetchSecrets)(projectId, secretsToExport);
|
|
148
|
+
for (const [key, value] of secretMaterial) {
|
|
149
|
+
localAppHostingConfig.addEnvironmentVariable({
|
|
150
|
+
variable: key,
|
|
151
|
+
value: value,
|
|
152
|
+
availability: ["RUNTIME"],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
localAppHostingConfig.clearSecrets();
|
|
156
|
+
localAppHostingConfig.upsertFile(localAppHostingConfigPath);
|
|
157
|
+
logger_1.logger.info(`Wrote secrets as environment variables to ${exports.APPHOSTING_LOCAL_YAML_FILE}.`);
|
|
158
|
+
(0, utils_2.updateOrCreateGitignore)(projectRoot, [exports.APPHOSTING_LOCAL_YAML_FILE]);
|
|
159
|
+
logger_1.logger.info(`${exports.APPHOSTING_LOCAL_YAML_FILE} has been automatically added to your .gitignore.`);
|
|
160
|
+
}
|
|
161
|
+
exports.exportConfig = exportConfig;
|
|
119
162
|
async function loadConfigForEnvironment(envYamlPath, baseYamlPath) {
|
|
120
163
|
const envYamlConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(envYamlPath);
|
|
121
164
|
if (baseYamlPath) {
|
|
@@ -126,3 +169,37 @@ async function loadConfigForEnvironment(envYamlPath, baseYamlPath) {
|
|
|
126
169
|
return envYamlConfig;
|
|
127
170
|
}
|
|
128
171
|
exports.loadConfigForEnvironment = loadConfigForEnvironment;
|
|
172
|
+
async function loadConfigToExportSecrets(cwd, userGivenConfigFile) {
|
|
173
|
+
if (userGivenConfigFile && !exports.APPHOSTING_YAML_FILE_REGEX.test(userGivenConfigFile)) {
|
|
174
|
+
throw new error_1.FirebaseError("Invalid apphosting yaml config file provided. File must be in format: 'apphosting.yaml' or 'apphosting.<environment>.yaml'");
|
|
175
|
+
}
|
|
176
|
+
const allConfigs = getValidConfigs(cwd);
|
|
177
|
+
let userGivenConfigFilePath;
|
|
178
|
+
if (userGivenConfigFile) {
|
|
179
|
+
if (!allConfigs.has(userGivenConfigFile)) {
|
|
180
|
+
throw new error_1.FirebaseError(`The provided app hosting config file "${userGivenConfigFile}" does not exist`);
|
|
181
|
+
}
|
|
182
|
+
userGivenConfigFilePath = allConfigs.get(userGivenConfigFile);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
userGivenConfigFilePath = await (0, utils_1.promptForAppHostingYaml)(allConfigs, "Which environment would you like to export secrets from Secret Manager for?");
|
|
186
|
+
}
|
|
187
|
+
if (userGivenConfigFile === exports.APPHOSTING_BASE_YAML_FILE) {
|
|
188
|
+
return yaml_1.AppHostingYamlConfig.loadFromFile(allConfigs.get(exports.APPHOSTING_BASE_YAML_FILE));
|
|
189
|
+
}
|
|
190
|
+
const baseFilePath = allConfigs.get(exports.APPHOSTING_BASE_YAML_FILE);
|
|
191
|
+
return await loadConfigForEnvironment(userGivenConfigFilePath, baseFilePath);
|
|
192
|
+
}
|
|
193
|
+
exports.loadConfigToExportSecrets = loadConfigToExportSecrets;
|
|
194
|
+
function getValidConfigs(cwd) {
|
|
195
|
+
const appHostingConfigPaths = listAppHostingFilesInPath(cwd).filter((path) => !path.endsWith(exports.APPHOSTING_LOCAL_YAML_FILE));
|
|
196
|
+
if (appHostingConfigPaths.length === 0) {
|
|
197
|
+
throw new error_1.FirebaseError("No apphosting.*.yaml configs found");
|
|
198
|
+
}
|
|
199
|
+
const fileNameToPathMap = new Map();
|
|
200
|
+
for (const path of appHostingConfigPaths) {
|
|
201
|
+
const fileName = (0, path_1.basename)(path);
|
|
202
|
+
fileNameToPathMap.set(fileName, path);
|
|
203
|
+
}
|
|
204
|
+
return fileNameToPathMap;
|
|
205
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getSecretNameParts = exports.
|
|
3
|
+
exports.getSecretNameParts = exports.fetchSecrets = exports.upsertSecret = exports.grantSecretAccess = exports.serviceAccountsForBackend = exports.toMulti = void 0;
|
|
4
4
|
const error_1 = require("../../error");
|
|
5
5
|
const gcsm = require("../../gcp/secretManager");
|
|
6
6
|
const gcb = require("../../gcp/cloudbuild");
|
|
@@ -10,10 +10,6 @@ const secretManager_1 = require("../../gcp/secretManager");
|
|
|
10
10
|
const secretManager_2 = require("../../gcp/secretManager");
|
|
11
11
|
const utils = require("../../utils");
|
|
12
12
|
const prompt = require("../../prompt");
|
|
13
|
-
const path_1 = require("path");
|
|
14
|
-
const config_1 = require("../config");
|
|
15
|
-
const utils_1 = require("../utils");
|
|
16
|
-
const yaml_1 = require("../yaml");
|
|
17
13
|
function toMulti(accounts) {
|
|
18
14
|
const m = {
|
|
19
15
|
buildServiceAccounts: [accounts.buildServiceAccount],
|
|
@@ -59,14 +55,14 @@ async function grantSecretAccess(projectId, projectNumber, secretName, accounts)
|
|
|
59
55
|
existingBindings = (await gcsm.getIamPolicy({ projectId, name: secretName })).bindings || [];
|
|
60
56
|
}
|
|
61
57
|
catch (err) {
|
|
62
|
-
throw new error_1.FirebaseError(`Failed to get IAM bindings on secret: ${secretName}. Ensure you have the permissions to do so and try again.`, { original: err });
|
|
58
|
+
throw new error_1.FirebaseError(`Failed to get IAM bindings on secret: ${secretName}. Ensure you have the permissions to do so and try again.`, { original: (0, error_1.getError)(err) });
|
|
63
59
|
}
|
|
64
60
|
try {
|
|
65
61
|
const updatedBindings = existingBindings.concat(newBindings);
|
|
66
62
|
await gcsm.setIamPolicy({ projectId, name: secretName }, updatedBindings);
|
|
67
63
|
}
|
|
68
64
|
catch (err) {
|
|
69
|
-
throw new error_1.FirebaseError(`Failed to set IAM bindings ${JSON.stringify(newBindings)} on secret: ${secretName}. Ensure you have the permissions to do so and try again.`, { original: err });
|
|
65
|
+
throw new error_1.FirebaseError(`Failed to set IAM bindings ${JSON.stringify(newBindings)} on secret: ${secretName}. Ensure you have the permissions to do so and try again.`, { original: (0, error_1.getError)(err) });
|
|
70
66
|
}
|
|
71
67
|
utils.logSuccess(`Successfully set IAM bindings on secret ${secretName}.\n`);
|
|
72
68
|
}
|
|
@@ -78,8 +74,8 @@ async function upsertSecret(project, secret, location) {
|
|
|
78
74
|
existing = await gcsm.getSecret(project, secret);
|
|
79
75
|
}
|
|
80
76
|
catch (err) {
|
|
81
|
-
if (err
|
|
82
|
-
throw new error_1.FirebaseError("Unexpected error loading secret", { original: err });
|
|
77
|
+
if ((0, error_1.getErrStatus)(err) !== 404) {
|
|
78
|
+
throw new error_1.FirebaseError("Unexpected error loading secret", { original: (0, error_1.getError)(err) });
|
|
83
79
|
}
|
|
84
80
|
await gcsm.createSecret(project, secret, gcsm.labels("apphosting"), location);
|
|
85
81
|
return true;
|
|
@@ -125,40 +121,6 @@ async function fetchSecrets(projectId, secrets) {
|
|
|
125
121
|
return secretsKeyValuePairs;
|
|
126
122
|
}
|
|
127
123
|
exports.fetchSecrets = fetchSecrets;
|
|
128
|
-
async function loadConfigToExport(cwd, userGivenConfigFile) {
|
|
129
|
-
if (userGivenConfigFile && !config_1.APPHOSTING_YAML_FILE_REGEX.test(userGivenConfigFile)) {
|
|
130
|
-
throw new error_1.FirebaseError("Invalid apphosting yaml config file provided. File must be in format: 'apphosting.yaml' or 'apphosting.<environment>.yaml'");
|
|
131
|
-
}
|
|
132
|
-
const allConfigs = discoverConfigs(cwd);
|
|
133
|
-
let userGivenConfigFilePath;
|
|
134
|
-
if (userGivenConfigFile) {
|
|
135
|
-
if (!allConfigs.has(userGivenConfigFile)) {
|
|
136
|
-
throw new error_1.FirebaseError(`The provided app hosting config file "${userGivenConfigFile}" does not exist`);
|
|
137
|
-
}
|
|
138
|
-
userGivenConfigFilePath = allConfigs.get(userGivenConfigFile);
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
userGivenConfigFilePath = await (0, utils_1.promptForAppHostingYaml)(allConfigs, "Which environment would you like to export secrets from Secret Manager for?");
|
|
142
|
-
}
|
|
143
|
-
if (userGivenConfigFile === config_1.APPHOSTING_BASE_YAML_FILE) {
|
|
144
|
-
return yaml_1.AppHostingYamlConfig.loadFromFile(allConfigs.get(config_1.APPHOSTING_BASE_YAML_FILE));
|
|
145
|
-
}
|
|
146
|
-
const baseFilePath = allConfigs.get(config_1.APPHOSTING_BASE_YAML_FILE);
|
|
147
|
-
return await (0, config_1.loadConfigForEnvironment)(userGivenConfigFilePath, baseFilePath);
|
|
148
|
-
}
|
|
149
|
-
exports.loadConfigToExport = loadConfigToExport;
|
|
150
|
-
function discoverConfigs(cwd) {
|
|
151
|
-
const appHostingConfigPaths = (0, config_1.discoverConfigsAtBackendRoot)(cwd).filter((path) => !path.endsWith(config_1.APPHOSTING_LOCAL_YAML_FILE));
|
|
152
|
-
if (appHostingConfigPaths.length === 0) {
|
|
153
|
-
throw new error_1.FirebaseError("No apphosting.*.yaml configs found");
|
|
154
|
-
}
|
|
155
|
-
const fileNameToPathMap = new Map();
|
|
156
|
-
for (const path of appHostingConfigPaths) {
|
|
157
|
-
const fileName = (0, path_1.basename)(path);
|
|
158
|
-
fileNameToPathMap.set(fileName, path);
|
|
159
|
-
}
|
|
160
|
-
return fileNameToPathMap;
|
|
161
|
-
}
|
|
162
124
|
function getSecretNameParts(secret) {
|
|
163
125
|
let [name, version] = secret.split("@");
|
|
164
126
|
if (!version) {
|
package/lib/apphosting/yaml.js
CHANGED
|
@@ -43,6 +43,9 @@ class AppHostingYamlConfig {
|
|
|
43
43
|
addSecret(secret) {
|
|
44
44
|
this._secrets.set(secret.variable, secret);
|
|
45
45
|
}
|
|
46
|
+
clearSecrets() {
|
|
47
|
+
this._secrets.clear();
|
|
48
|
+
}
|
|
46
49
|
merge(other) {
|
|
47
50
|
for (const [key, value] of other._environmentVariables) {
|
|
48
51
|
this._environmentVariables.set(key, value);
|
package/lib/archiveDirectory.js
CHANGED
|
@@ -39,7 +39,7 @@ async function archiveDirectory(sourceDirectory, options = {}) {
|
|
|
39
39
|
if (err instanceof error_1.FirebaseError) {
|
|
40
40
|
throw err;
|
|
41
41
|
}
|
|
42
|
-
throw new error_1.FirebaseError("Failed to create archive.", { original: err });
|
|
42
|
+
throw new error_1.FirebaseError("Failed to create archive.", { original: (0, error_1.getError)(err) });
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
exports.archiveDirectory = archiveDirectory;
|
package/lib/auth.js
CHANGED
|
@@ -541,7 +541,7 @@ async function getAccessToken(refreshToken, authScopes) {
|
|
|
541
541
|
return (0, requireAuth_1.refreshAuth)();
|
|
542
542
|
}
|
|
543
543
|
catch (err) {
|
|
544
|
-
logger_1.logger.debug(`Unable to refresh token: ${err}`);
|
|
544
|
+
logger_1.logger.debug(`Unable to refresh token: ${(0, error_1.getErrMsg)(err)}`);
|
|
545
545
|
}
|
|
546
546
|
throw new error_1.FirebaseError("Unable to getAccessToken");
|
|
547
547
|
}
|
package/lib/command.js
CHANGED
|
@@ -19,6 +19,7 @@ class Command {
|
|
|
19
19
|
this.name = "";
|
|
20
20
|
this.descriptionText = "";
|
|
21
21
|
this.options = [];
|
|
22
|
+
this.aliases = [];
|
|
22
23
|
this.actionFn = () => {
|
|
23
24
|
};
|
|
24
25
|
this.befores = [];
|
|
@@ -30,6 +31,10 @@ class Command {
|
|
|
30
31
|
this.descriptionText = t;
|
|
31
32
|
return this;
|
|
32
33
|
}
|
|
34
|
+
alias(alias) {
|
|
35
|
+
this.aliases.push(alias);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
33
38
|
option(...args) {
|
|
34
39
|
this.options.push(args);
|
|
35
40
|
return this;
|
|
@@ -57,6 +62,9 @@ class Command {
|
|
|
57
62
|
if (this.descriptionText) {
|
|
58
63
|
cmd.description(this.descriptionText);
|
|
59
64
|
}
|
|
65
|
+
if (this.aliases) {
|
|
66
|
+
cmd.aliases(this.aliases);
|
|
67
|
+
}
|
|
60
68
|
this.options.forEach((args) => {
|
|
61
69
|
const flags = args.shift();
|
|
62
70
|
cmd.option(flags, ...args);
|
|
@@ -211,7 +219,7 @@ class Command {
|
|
|
211
219
|
var _a;
|
|
212
220
|
if ((_a = options.project) === null || _a === void 0 ? void 0 : _a.match(/^\d+$/)) {
|
|
213
221
|
await (0, requireAuth_1.requireAuth)(options);
|
|
214
|
-
const { projectId, projectNumber } = await (0, projects_1.
|
|
222
|
+
const { projectId, projectNumber } = await (0, projects_1.getProject)(options.project);
|
|
215
223
|
options.projectId = projectId;
|
|
216
224
|
options.projectNumber = projectNumber;
|
|
217
225
|
}
|
|
@@ -61,13 +61,13 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
61
61
|
aabInfo = await requests.getAabInfo(appName);
|
|
62
62
|
}
|
|
63
63
|
catch (err) {
|
|
64
|
-
if (err
|
|
64
|
+
if ((0, error_1.getErrStatus)(err) === 404) {
|
|
65
65
|
throw new error_1.FirebaseError(`App Distribution could not find your app ${options.app}. ` +
|
|
66
66
|
`Make sure to onboard your app by pressing the "Get started" ` +
|
|
67
67
|
"button on the App Distribution page in the Firebase console: " +
|
|
68
68
|
"https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
|
|
69
69
|
}
|
|
70
|
-
throw new error_1.FirebaseError(`failed to determine AAB info. ${err
|
|
70
|
+
throw new error_1.FirebaseError(`failed to determine AAB info. ${(0, error_1.getErrMsg)(err)}`, { exit: 1 });
|
|
71
71
|
}
|
|
72
72
|
if (aabInfo.integrationState !== types_1.IntegrationState.INTEGRATED &&
|
|
73
73
|
aabInfo.integrationState !== types_1.IntegrationState.AAB_STATE_UNAVAILABLE) {
|
|
@@ -115,13 +115,13 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
115
115
|
releaseName = uploadResponse.release.name;
|
|
116
116
|
}
|
|
117
117
|
catch (err) {
|
|
118
|
-
if (err
|
|
118
|
+
if ((0, error_1.getErrStatus)(err) === 404) {
|
|
119
119
|
throw new error_1.FirebaseError(`App Distribution could not find your app ${options.app}. ` +
|
|
120
120
|
`Make sure to onboard your app by pressing the "Get started" ` +
|
|
121
121
|
"button on the App Distribution page in the Firebase console: " +
|
|
122
122
|
"https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
|
|
123
123
|
}
|
|
124
|
-
throw new error_1.FirebaseError(`Failed to upload release. ${err
|
|
124
|
+
throw new error_1.FirebaseError(`Failed to upload release. ${(0, error_1.getErrMsg)(err)}`, { exit: 1 });
|
|
125
125
|
}
|
|
126
126
|
if (aabInfo && !aabInfo.testCertificate) {
|
|
127
127
|
aabInfo = await requests.getAabInfo(appName);
|
|
@@ -6,8 +6,9 @@ const utils = require("../utils");
|
|
|
6
6
|
const requireAuth_1 = require("../requireAuth");
|
|
7
7
|
const client_1 = require("../appdistribution/client");
|
|
8
8
|
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
9
|
-
exports.command = new command_1.Command("appdistribution:
|
|
9
|
+
exports.command = new command_1.Command("appdistribution:groups:create <displayName> [alias]")
|
|
10
10
|
.description("create group in project")
|
|
11
|
+
.alias("appdistribution:group:create")
|
|
11
12
|
.before(requireAuth_1.requireAuth)
|
|
12
13
|
.action(async (displayName, alias, options) => {
|
|
13
14
|
const projectName = await (0, options_parser_util_1.getProjectName)(options);
|
|
@@ -7,8 +7,9 @@ const requireAuth_1 = require("../requireAuth");
|
|
|
7
7
|
const error_1 = require("../error");
|
|
8
8
|
const client_1 = require("../appdistribution/client");
|
|
9
9
|
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
10
|
-
exports.command = new command_1.Command("appdistribution:
|
|
10
|
+
exports.command = new command_1.Command("appdistribution:groups:delete <alias>")
|
|
11
11
|
.description("delete group from a project")
|
|
12
|
+
.alias("appdistribution:group:delete")
|
|
12
13
|
.before(requireAuth_1.requireAuth)
|
|
13
14
|
.action(async (alias, options) => {
|
|
14
15
|
const projectName = await (0, options_parser_util_1.getProjectName)(options);
|
|
@@ -18,7 +19,7 @@ exports.command = new command_1.Command("appdistribution:group:delete <alias>")
|
|
|
18
19
|
await appDistroClient.deleteGroup(`${projectName}/groups/${alias}`);
|
|
19
20
|
}
|
|
20
21
|
catch (err) {
|
|
21
|
-
throw new error_1.FirebaseError(`Failed to delete group ${err}`);
|
|
22
|
+
throw new error_1.FirebaseError(`Failed to delete group ${(0, error_1.getErrMsg)(err)}`);
|
|
22
23
|
}
|
|
23
24
|
utils.logSuccess(`Group ${alias} has successfully been deleted`);
|
|
24
25
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const ora = require("ora");
|
|
5
|
+
const client_1 = require("../appdistribution/client");
|
|
6
|
+
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
7
|
+
const command_1 = require("../command");
|
|
8
|
+
const error_1 = require("../error");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
const requireAuth_1 = require("../requireAuth");
|
|
11
|
+
const utils = require("../utils");
|
|
12
|
+
const Table = require("cli-table");
|
|
13
|
+
exports.command = new command_1.Command("appdistribution:groups:list")
|
|
14
|
+
.description("list groups in project")
|
|
15
|
+
.alias("appdistribution:group:list")
|
|
16
|
+
.before(requireAuth_1.requireAuth)
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
var _a;
|
|
19
|
+
const projectName = await (0, options_parser_util_1.getProjectName)(options);
|
|
20
|
+
const appDistroClient = new client_1.AppDistributionClient();
|
|
21
|
+
let groupsResponse;
|
|
22
|
+
const spinner = ora("Preparing the list of your App Distribution Groups").start();
|
|
23
|
+
try {
|
|
24
|
+
groupsResponse = await appDistroClient.listGroups(projectName);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
spinner.fail();
|
|
28
|
+
throw new error_1.FirebaseError("Failed to list groups.", {
|
|
29
|
+
exit: 1,
|
|
30
|
+
original: err,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
spinner.succeed();
|
|
34
|
+
const groups = (_a = groupsResponse.groups) !== null && _a !== void 0 ? _a : [];
|
|
35
|
+
printGroupsTable(groups);
|
|
36
|
+
utils.logSuccess(`Groups listed successfully`);
|
|
37
|
+
return groupsResponse;
|
|
38
|
+
});
|
|
39
|
+
function printGroupsTable(groups) {
|
|
40
|
+
const tableHead = ["Group", "Display Name", "Tester Count", "Release Count", "Invite Link Count"];
|
|
41
|
+
const table = new Table({
|
|
42
|
+
head: tableHead,
|
|
43
|
+
style: { head: ["green"] },
|
|
44
|
+
});
|
|
45
|
+
for (const group of groups) {
|
|
46
|
+
const name = group.name.split("/").pop();
|
|
47
|
+
table.push([
|
|
48
|
+
name,
|
|
49
|
+
group.displayName,
|
|
50
|
+
group.testerCount || 0,
|
|
51
|
+
group.releaseCount || 0,
|
|
52
|
+
group.inviteLinkCount || 0,
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
logger_1.logger.info(table.toString());
|
|
56
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const ora = require("ora");
|
|
5
|
+
const client_1 = require("../appdistribution/client");
|
|
6
|
+
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
7
|
+
const command_1 = require("../command");
|
|
8
|
+
const error_1 = require("../error");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
const requireAuth_1 = require("../requireAuth");
|
|
11
|
+
const utils = require("../utils");
|
|
12
|
+
const Table = require("cli-table");
|
|
13
|
+
exports.command = new command_1.Command("appdistribution:testers:list [group]")
|
|
14
|
+
.description("list testers in project")
|
|
15
|
+
.before(requireAuth_1.requireAuth)
|
|
16
|
+
.action(async (group, options) => {
|
|
17
|
+
var _a;
|
|
18
|
+
const projectName = await (0, options_parser_util_1.getProjectName)(options);
|
|
19
|
+
const appDistroClient = new client_1.AppDistributionClient();
|
|
20
|
+
let testersResponse;
|
|
21
|
+
const spinner = ora("Preparing the list of your App Distribution testers").start();
|
|
22
|
+
try {
|
|
23
|
+
testersResponse = await appDistroClient.listTesters(projectName, group);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
spinner.fail();
|
|
27
|
+
throw new error_1.FirebaseError("Failed to list testers.", {
|
|
28
|
+
exit: 1,
|
|
29
|
+
original: err,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
spinner.succeed();
|
|
33
|
+
const testers = (_a = testersResponse.testers) !== null && _a !== void 0 ? _a : [];
|
|
34
|
+
printTestersTable(testers);
|
|
35
|
+
utils.logSuccess(`Testers listed successfully`);
|
|
36
|
+
return testersResponse;
|
|
37
|
+
});
|
|
38
|
+
function printTestersTable(testers) {
|
|
39
|
+
var _a;
|
|
40
|
+
const tableHead = ["Name", "Display Name", "Last Activity Time", "Groups"];
|
|
41
|
+
const table = new Table({
|
|
42
|
+
head: tableHead,
|
|
43
|
+
style: { head: ["green"] },
|
|
44
|
+
});
|
|
45
|
+
for (const tester of testers) {
|
|
46
|
+
const name = tester.name.split("/").pop();
|
|
47
|
+
const groups = tester.groups
|
|
48
|
+
.map((grp) => grp.split("/").pop())
|
|
49
|
+
.sort()
|
|
50
|
+
.join(";");
|
|
51
|
+
table.push([name, (_a = tester.displayName) !== null && _a !== void 0 ? _a : "", tester.lastActivityTime, groups]);
|
|
52
|
+
}
|
|
53
|
+
logger_1.logger.info(table.toString());
|
|
54
|
+
}
|
|
@@ -28,7 +28,7 @@ exports.command = new command_1.Command("appdistribution:testers:remove [emails.
|
|
|
28
28
|
deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr);
|
|
29
29
|
}
|
|
30
30
|
catch (err) {
|
|
31
|
-
throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
|
|
31
|
+
throw new error_1.FirebaseError(`Failed to remove testers ${(0, error_1.getErrMsg)(err)}`);
|
|
32
32
|
}
|
|
33
33
|
if (!deleteResponse.emails) {
|
|
34
34
|
utils.logSuccess(`Testers did not exist`);
|
|
@@ -44,6 +44,8 @@ exports.command = new command_1.Command("apphosting:backends:delete <backend>")
|
|
|
44
44
|
}
|
|
45
45
|
catch (err) {
|
|
46
46
|
spinner.stop();
|
|
47
|
-
throw new error_1.FirebaseError(`Failed to delete backend: ${backendId}.`, {
|
|
47
|
+
throw new error_1.FirebaseError(`Failed to delete backend: ${backendId}.`, {
|
|
48
|
+
original: (0, error_1.getError)(err),
|
|
49
|
+
});
|
|
48
50
|
}
|
|
49
51
|
});
|