firebase-tools 10.4.0 → 10.5.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/accountExporter.js +95 -84
- package/lib/bin/firebase.js +1 -1
- package/lib/commands/emulators-start.js +6 -1
- package/lib/commands/ext-configure.js +4 -4
- package/lib/commands/ext-dev-emulators-start.js +5 -1
- package/lib/commands/ext-install.js +5 -4
- package/lib/commands/ext-update.js +2 -1
- package/lib/commands/functions-config-export.js +3 -1
- package/lib/config.js +11 -4
- package/lib/deploy/functions/checkIam.js +44 -1
- package/lib/deploy/functions/deploy.js +3 -7
- package/lib/deploy/functions/prepare.js +7 -5
- package/lib/deploy/functions/prepareFunctionsUpload.js +7 -13
- package/lib/deploy/functions/release/fabricator.js +13 -1
- package/lib/deploy/functions/release/index.js +1 -1
- package/lib/deploy/functions/services/firebaseAlerts.js +1 -17
- package/lib/deploy/functions/services/index.js +2 -1
- package/lib/deploy/hosting/deploy.js +10 -0
- package/lib/emulator/auth/operations.js +21 -20
- package/lib/emulator/auth/state.js +79 -43
- package/lib/emulator/commandUtils.js +72 -2
- package/lib/emulator/controller.js +23 -8
- package/lib/emulator/downloadableEmulators.js +18 -11
- package/lib/emulator/functionsEmulator.js +8 -18
- package/lib/emulator/functionsEmulatorShared.js +27 -1
- package/lib/emulator/shared/request.js +19 -0
- package/lib/emulator/storage/apis/firebase.js +20 -28
- package/lib/emulator/storage/apis/gcloud.js +69 -57
- package/lib/emulator/storage/files.js +42 -49
- package/lib/emulator/storage/index.js +14 -2
- package/lib/emulator/storage/rules/utils.js +11 -3
- package/lib/extensions/askUserForParam.js +43 -15
- package/lib/extensions/extensionsHelper.js +11 -2
- package/lib/extensions/paramHelper.js +7 -3
- package/lib/functions/projectConfig.js +34 -0
- package/lib/init/features/functions/index.js +4 -2
- package/lib/init/features/hosting/index.js +32 -41
- package/lib/init/features/index.js +22 -12
- package/lib/init/index.js +28 -11
- package/lib/requirePermissions.js +4 -1
- package/lib/serve/functions.js +5 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/schema/firebase-config.json +93 -36
- package/lib/emulator/storage/list.js +0 -18
package/lib/accountExporter.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serialExportUsers = exports.validateOptions = void 0;
|
|
4
|
+
const _ = require("lodash");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const apiv2_1 = require("./apiv2");
|
|
8
|
+
const error_1 = require("./error");
|
|
9
|
+
const api_1 = require("./api");
|
|
10
|
+
const utils = require("./utils");
|
|
11
|
+
const apiClient = new apiv2_1.Client({
|
|
12
|
+
urlPrefix: api_1.googleOrigin,
|
|
11
13
|
});
|
|
12
|
-
|
|
14
|
+
const EXPORTED_JSON_KEYS = [
|
|
13
15
|
"localId",
|
|
14
16
|
"email",
|
|
15
17
|
"emailVerified",
|
|
@@ -23,44 +25,53 @@ var EXPORTED_JSON_KEYS = [
|
|
|
23
25
|
"disabled",
|
|
24
26
|
"customAttributes",
|
|
25
27
|
];
|
|
26
|
-
|
|
28
|
+
const EXPORTED_JSON_KEYS_RENAMING = {
|
|
27
29
|
lastLoginAt: "lastSignedInAt",
|
|
28
30
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
const EXPORTED_PROVIDER_USER_INFO_KEYS = [
|
|
32
|
+
"providerId",
|
|
33
|
+
"rawId",
|
|
34
|
+
"email",
|
|
35
|
+
"displayName",
|
|
36
|
+
"photoUrl",
|
|
37
|
+
];
|
|
38
|
+
const PROVIDER_ID_INDEX_MAP = new Map([
|
|
39
|
+
["google.com", 7],
|
|
40
|
+
["facebook.com", 11],
|
|
41
|
+
["twitter.com", 15],
|
|
42
|
+
["github.com", 19],
|
|
43
|
+
]);
|
|
44
|
+
function escapeComma(str) {
|
|
45
|
+
if (str.includes(",")) {
|
|
38
46
|
return `"${str}"`;
|
|
39
47
|
}
|
|
40
48
|
return str;
|
|
41
|
-
}
|
|
42
|
-
|
|
49
|
+
}
|
|
50
|
+
function convertToNormalBase64(data) {
|
|
43
51
|
return data.replace(/_/g, "/").replace(/-/g, "+");
|
|
44
|
-
}
|
|
45
|
-
|
|
52
|
+
}
|
|
53
|
+
function addProviderUserInfo(providerInfo, arr, startPos) {
|
|
46
54
|
arr[startPos] = providerInfo.rawId;
|
|
47
55
|
arr[startPos + 1] = providerInfo.email || "";
|
|
48
|
-
arr[startPos + 2] =
|
|
56
|
+
arr[startPos + 2] = escapeComma(providerInfo.displayName || "");
|
|
49
57
|
arr[startPos + 3] = providerInfo.photoUrl || "";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
}
|
|
59
|
+
function transUserToArray(user) {
|
|
60
|
+
const arr = Array(27).fill("");
|
|
53
61
|
arr[0] = user.localId;
|
|
54
62
|
arr[1] = user.email || "";
|
|
55
63
|
arr[2] = user.emailVerified || false;
|
|
56
|
-
arr[3] =
|
|
57
|
-
arr[4] =
|
|
58
|
-
arr[5] =
|
|
64
|
+
arr[3] = convertToNormalBase64(user.passwordHash || "");
|
|
65
|
+
arr[4] = convertToNormalBase64(user.salt || "");
|
|
66
|
+
arr[5] = escapeComma(user.displayName || "");
|
|
59
67
|
arr[6] = user.photoUrl || "";
|
|
60
|
-
for (
|
|
61
|
-
|
|
62
|
-
if (providerInfo
|
|
63
|
-
|
|
68
|
+
for (let i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) {
|
|
69
|
+
const providerInfo = user.providerUserInfo[i];
|
|
70
|
+
if (providerInfo) {
|
|
71
|
+
const providerIndex = PROVIDER_ID_INDEX_MAP.get(providerInfo.providerId);
|
|
72
|
+
if (providerIndex) {
|
|
73
|
+
addProviderUserInfo(providerInfo, arr, providerIndex);
|
|
74
|
+
}
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
arr[23] = user.createdAt;
|
|
@@ -69,36 +80,35 @@ var _transUserToArray = function (user) {
|
|
|
69
80
|
arr[26] = user.disabled;
|
|
70
81
|
arr[27] = user.customAttributes;
|
|
71
82
|
return arr;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
_.each(_.pick(user, EXPORTED_JSON_KEYS),
|
|
76
|
-
|
|
83
|
+
}
|
|
84
|
+
function transUserJson(user) {
|
|
85
|
+
const newUser = {};
|
|
86
|
+
_.each(_.pick(user, EXPORTED_JSON_KEYS), (value, key) => {
|
|
87
|
+
const newKey = EXPORTED_JSON_KEYS_RENAMING[key] || key;
|
|
77
88
|
newUser[newKey] = value;
|
|
78
89
|
});
|
|
79
90
|
if (newUser.passwordHash) {
|
|
80
|
-
newUser.passwordHash =
|
|
91
|
+
newUser.passwordHash = convertToNormalBase64(newUser.passwordHash);
|
|
81
92
|
}
|
|
82
93
|
if (newUser.salt) {
|
|
83
|
-
newUser.salt =
|
|
94
|
+
newUser.salt = convertToNormalBase64(newUser.salt);
|
|
84
95
|
}
|
|
85
96
|
if (user.providerUserInfo) {
|
|
86
97
|
newUser.providerUserInfo = [];
|
|
87
|
-
user.providerUserInfo
|
|
88
|
-
if (
|
|
89
|
-
|
|
98
|
+
for (const providerInfo of user.providerUserInfo) {
|
|
99
|
+
if (PROVIDER_ID_INDEX_MAP.has(providerInfo.providerId)) {
|
|
100
|
+
newUser.providerUserInfo.push(_.pick(providerInfo, EXPORTED_PROVIDER_USER_INFO_KEYS));
|
|
90
101
|
}
|
|
91
|
-
|
|
92
|
-
});
|
|
102
|
+
}
|
|
93
103
|
}
|
|
94
104
|
return newUser;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
}
|
|
106
|
+
function validateOptions(options, fileName) {
|
|
107
|
+
const exportOptions = {};
|
|
98
108
|
if (fileName === undefined) {
|
|
99
|
-
throw new FirebaseError("Must specify data file"
|
|
109
|
+
throw new error_1.FirebaseError("Must specify data file");
|
|
100
110
|
}
|
|
101
|
-
|
|
111
|
+
const extName = path.extname(fileName.toLowerCase());
|
|
102
112
|
if (extName === ".csv") {
|
|
103
113
|
exportOptions.format = "csv";
|
|
104
114
|
}
|
|
@@ -106,44 +116,44 @@ var validateOptions = function (options, fileName) {
|
|
|
106
116
|
exportOptions.format = "json";
|
|
107
117
|
}
|
|
108
118
|
else if (options.format) {
|
|
109
|
-
|
|
119
|
+
const format = options.format.toLowerCase();
|
|
110
120
|
if (format === "csv" || format === "json") {
|
|
111
121
|
exportOptions.format = format;
|
|
112
122
|
}
|
|
113
123
|
else {
|
|
114
|
-
throw new FirebaseError("Unsupported data file format, should be csv or json"
|
|
124
|
+
throw new error_1.FirebaseError("Unsupported data file format, should be csv or json");
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
127
|
else {
|
|
118
|
-
throw new FirebaseError("Please specify data file format in file name, or use `format` parameter"
|
|
119
|
-
exit: 1,
|
|
120
|
-
});
|
|
128
|
+
throw new error_1.FirebaseError("Please specify data file format in file name, or use `format` parameter");
|
|
121
129
|
}
|
|
122
130
|
return exportOptions;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
}
|
|
132
|
+
exports.validateOptions = validateOptions;
|
|
133
|
+
function createWriteUsersToFile() {
|
|
134
|
+
let jsonSep = "";
|
|
135
|
+
return (userList, format, writeStream) => {
|
|
136
|
+
userList.map((user) => {
|
|
128
137
|
if (user.passwordHash && user.version !== 0) {
|
|
129
138
|
delete user.passwordHash;
|
|
130
139
|
delete user.salt;
|
|
131
140
|
}
|
|
132
141
|
if (format === "csv") {
|
|
133
|
-
writeStream.write(
|
|
142
|
+
writeStream.write(transUserToArray(user).join(",") + "," + os.EOL, "utf8");
|
|
134
143
|
}
|
|
135
144
|
else {
|
|
136
|
-
writeStream.write(jsonSep + JSON.stringify(
|
|
145
|
+
writeStream.write(jsonSep + JSON.stringify(transUserJson(user), null, 2), "utf8");
|
|
137
146
|
jsonSep = "," + os.EOL;
|
|
138
147
|
}
|
|
139
148
|
});
|
|
140
149
|
};
|
|
141
|
-
}
|
|
142
|
-
|
|
150
|
+
}
|
|
151
|
+
async function serialExportUsers(projectId, options) {
|
|
152
|
+
var _a;
|
|
143
153
|
if (!options.writeUsersToFile) {
|
|
144
|
-
options.writeUsersToFile =
|
|
154
|
+
options.writeUsersToFile = createWriteUsersToFile();
|
|
145
155
|
}
|
|
146
|
-
|
|
156
|
+
const postBody = {
|
|
147
157
|
targetProjectId: projectId,
|
|
148
158
|
maxResults: options.batchSize,
|
|
149
159
|
};
|
|
@@ -153,13 +163,12 @@ var serialExportUsers = function (projectId, options) {
|
|
|
153
163
|
if (!options.timeoutRetryCount) {
|
|
154
164
|
options.timeoutRetryCount = 0;
|
|
155
165
|
}
|
|
156
|
-
|
|
157
|
-
.post("/identitytoolkit/v3/relyingparty/downloadAccount", postBody, {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.then(function (ret) {
|
|
166
|
+
try {
|
|
167
|
+
const ret = await apiClient.post("/identitytoolkit/v3/relyingparty/downloadAccount", postBody, {
|
|
168
|
+
skipLog: { resBody: true },
|
|
169
|
+
});
|
|
161
170
|
options.timeoutRetryCount = 0;
|
|
162
|
-
|
|
171
|
+
const userList = ret.body.users;
|
|
163
172
|
if (userList && userList.length > 0) {
|
|
164
173
|
options.writeUsersToFile(userList, options.format, options.writeStream);
|
|
165
174
|
utils.logSuccess("Exported " + userList.length + " account(s) successfully.");
|
|
@@ -169,19 +178,21 @@ var serialExportUsers = function (projectId, options) {
|
|
|
169
178
|
options.nextPageToken = ret.body.nextPageToken;
|
|
170
179
|
return serialExportUsers(projectId, options);
|
|
171
180
|
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (err.original.code === "ETIMEDOUT") {
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
if (((_a = err.original) === null || _a === void 0 ? void 0 : _a.code) === "ETIMEDOUT") {
|
|
175
184
|
options.timeoutRetryCount++;
|
|
176
185
|
if (options.timeoutRetryCount > 5) {
|
|
177
186
|
return err;
|
|
178
187
|
}
|
|
179
188
|
return serialExportUsers(projectId, options);
|
|
180
189
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
190
|
+
if (err instanceof error_1.FirebaseError) {
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
throw new error_1.FirebaseError(`Failed to export accounts: ${err}`, { original: err });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
exports.serialExportUsers = serialExportUsers;
|
package/lib/bin/firebase.js
CHANGED
|
@@ -20,7 +20,7 @@ marked.setOptions({
|
|
|
20
20
|
renderer: new TerminalRenderer(),
|
|
21
21
|
});
|
|
22
22
|
const updateMessage = `Update available ${clc.xterm(240)("{currentVersion}")} → ${clc.green("{latestVersion}")}\n` +
|
|
23
|
-
`To update to the latest version using npm, run
|
|
23
|
+
`To update to the latest version using npm, run\n${clc.cyan("npm install -g firebase-tools")}\n` +
|
|
24
24
|
`For other CLI management options, visit the ${marked("[CLI documentation](https://firebase.google.com/docs/cli#update-cli)")}`;
|
|
25
25
|
updateNotifier.notify({ defer: true, isGlobal: true, message: updateMessage });
|
|
26
26
|
const client = require("..");
|
|
@@ -8,6 +8,7 @@ const registry_1 = require("../emulator/registry");
|
|
|
8
8
|
const types_1 = require("../emulator/types");
|
|
9
9
|
const clc = require("cli-color");
|
|
10
10
|
const constants_1 = require("../emulator/constants");
|
|
11
|
+
const utils_1 = require("../utils");
|
|
11
12
|
const Table = require("cli-table");
|
|
12
13
|
function stylizeLink(url) {
|
|
13
14
|
return clc.underline(clc.bold(url));
|
|
@@ -22,8 +23,9 @@ module.exports = new command_1.Command("emulators:start")
|
|
|
22
23
|
.option(commandUtils.FLAG_EXPORT_ON_EXIT, commandUtils.DESC_EXPORT_ON_EXIT)
|
|
23
24
|
.action(async (options) => {
|
|
24
25
|
const killSignalPromise = commandUtils.shutdownWhenKilled(options);
|
|
26
|
+
let deprecationNotices;
|
|
25
27
|
try {
|
|
26
|
-
await controller.startAll(options);
|
|
28
|
+
({ deprecationNotices } = await controller.startAll(options));
|
|
27
29
|
}
|
|
28
30
|
catch (e) {
|
|
29
31
|
await controller.cleanShutdown();
|
|
@@ -85,5 +87,8 @@ ${clc.blackBright(" Other reserved ports:")} ${reservedPortsString}
|
|
|
85
87
|
|
|
86
88
|
Issues? Report them at ${stylizeLink("https://github.com/firebase/firebase-tools/issues")} and attach the *-debug.log files.
|
|
87
89
|
`);
|
|
90
|
+
for (const notice of deprecationNotices) {
|
|
91
|
+
(0, utils_1.logLabeledWarning)("emulators", notice, "warn");
|
|
92
|
+
}
|
|
88
93
|
await killSignalPromise;
|
|
89
94
|
});
|
|
@@ -35,7 +35,7 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
35
35
|
.before(extensionsHelper_1.diagnoseAndFixProject)
|
|
36
36
|
.action(async (instanceId, options) => {
|
|
37
37
|
var _a;
|
|
38
|
-
const projectId = (0, projectUtils_1.
|
|
38
|
+
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
39
39
|
if (options.local) {
|
|
40
40
|
if (options.nonInteractive) {
|
|
41
41
|
throw new error_1.FirebaseError(`Command not supported in non-interactive mode, edit ./extensions/${instanceId}.env directly instead`);
|
|
@@ -77,7 +77,7 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
77
77
|
try {
|
|
78
78
|
let existingInstance;
|
|
79
79
|
try {
|
|
80
|
-
existingInstance = await extensionsApi.getInstance(projectId, instanceId);
|
|
80
|
+
existingInstance = await extensionsApi.getInstance((0, projectUtils_1.needProjectId)({ projectId }), instanceId);
|
|
81
81
|
}
|
|
82
82
|
catch (err) {
|
|
83
83
|
if (err.status === 404) {
|
|
@@ -113,13 +113,13 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
113
113
|
}
|
|
114
114
|
spinner.start();
|
|
115
115
|
const res = await extensionsApi.configureInstance({
|
|
116
|
-
projectId,
|
|
116
|
+
projectId: (0, projectUtils_1.needProjectId)({ projectId }),
|
|
117
117
|
instanceId,
|
|
118
118
|
params: paramBindings,
|
|
119
119
|
});
|
|
120
120
|
spinner.stop();
|
|
121
121
|
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `successfully configured ${clc.bold(instanceId)}.`);
|
|
122
|
-
utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl(projectId, `/extensions/instances/${instanceId}?tab=config`)}`));
|
|
122
|
+
utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl((0, projectUtils_1.needProjectId)({ projectId }), `/extensions/instances/${instanceId}?tab=config`)}`));
|
|
123
123
|
manifest.showDeprecationWarning();
|
|
124
124
|
return res;
|
|
125
125
|
}
|
|
@@ -19,9 +19,10 @@ module.exports = new command_1.Command("ext:dev:emulators:start")
|
|
|
19
19
|
.action(async (options) => {
|
|
20
20
|
const killSignalPromise = commandUtils.shutdownWhenKilled(options);
|
|
21
21
|
const emulatorOptions = await optionsHelper.buildOptions(options);
|
|
22
|
+
let deprecationNotices;
|
|
22
23
|
try {
|
|
23
24
|
commandUtils.beforeEmulatorCommand(emulatorOptions);
|
|
24
|
-
await controller.startAll(emulatorOptions);
|
|
25
|
+
({ deprecationNotices } = await controller.startAll(emulatorOptions));
|
|
25
26
|
}
|
|
26
27
|
catch (e) {
|
|
27
28
|
await controller.cleanShutdown();
|
|
@@ -31,5 +32,8 @@ module.exports = new command_1.Command("ext:dev:emulators:start")
|
|
|
31
32
|
throw e;
|
|
32
33
|
}
|
|
33
34
|
utils.logSuccess("All emulators ready, it is now safe to connect.");
|
|
35
|
+
for (const notice of deprecationNotices) {
|
|
36
|
+
utils.logLabeledWarning("emulators", notice, "warn");
|
|
37
|
+
}
|
|
34
38
|
await killSignalPromise;
|
|
35
39
|
});
|
|
@@ -47,7 +47,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
47
47
|
.before(extensionsHelper_1.diagnoseAndFixProject)
|
|
48
48
|
.action(async (extensionName, options) => {
|
|
49
49
|
var _a;
|
|
50
|
-
const projectId = (0, projectUtils_1.
|
|
50
|
+
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
51
51
|
const paramsEnvPath = ((_a = options.params) !== null && _a !== void 0 ? _a : "");
|
|
52
52
|
let learnMore = false;
|
|
53
53
|
if (!extensionName) {
|
|
@@ -71,7 +71,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
71
71
|
if (options.local) {
|
|
72
72
|
throw new error_1.FirebaseError("Installing a local source locally is not supported yet, please use ext:dev:emulator commands");
|
|
73
73
|
}
|
|
74
|
-
source = await infoInstallBySource(projectId, extensionName);
|
|
74
|
+
source = await infoInstallBySource((0, projectUtils_1.needProjectId)({ projectId }), extensionName);
|
|
75
75
|
}
|
|
76
76
|
else {
|
|
77
77
|
void (0, track_1.track)("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0);
|
|
@@ -120,7 +120,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
120
120
|
try {
|
|
121
121
|
return installExtension({
|
|
122
122
|
paramsEnvPath,
|
|
123
|
-
projectId,
|
|
123
|
+
projectId: projectId,
|
|
124
124
|
extensionName,
|
|
125
125
|
source,
|
|
126
126
|
extVersion,
|
|
@@ -195,7 +195,8 @@ async function installToManifest(options) {
|
|
|
195
195
|
manifest.showPreviewWarning();
|
|
196
196
|
}
|
|
197
197
|
async function installExtension(options) {
|
|
198
|
-
const {
|
|
198
|
+
const { extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } = options;
|
|
199
|
+
const projectId = (0, projectUtils_1.needProjectId)({ projectId: options.projectId });
|
|
199
200
|
const spec = (source === null || source === void 0 ? void 0 : source.spec) || (extVersion === null || extVersion === void 0 ? void 0 : extVersion.spec);
|
|
200
201
|
if (!spec) {
|
|
201
202
|
throw new error_1.FirebaseError(`Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.`);
|
|
@@ -42,8 +42,8 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
42
42
|
.option("--local", "save the update to firebase.json rather than directly update an existing Extension instance on a Firebase project")
|
|
43
43
|
.action(async (instanceId, updateSource, options) => {
|
|
44
44
|
var _a, _b;
|
|
45
|
-
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
46
45
|
if (options.local) {
|
|
46
|
+
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
47
47
|
const config = manifest.loadConfig(options);
|
|
48
48
|
const oldRef = manifest.getInstanceRef(instanceId, config);
|
|
49
49
|
const oldExtensionVersion = await extensionsApi.getExtensionVersion(refs.toExtensionVersionRef(oldRef));
|
|
@@ -95,6 +95,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
95
95
|
}
|
|
96
96
|
const spinner = ora(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`);
|
|
97
97
|
try {
|
|
98
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
98
99
|
let existingInstance;
|
|
99
100
|
try {
|
|
100
101
|
existingInstance = await extensionsApi.getInstance(projectId, instanceId);
|
|
@@ -13,6 +13,7 @@ const utils_1 = require("../utils");
|
|
|
13
13
|
const functional_1 = require("../functional");
|
|
14
14
|
const configExport = require("../functions/runtimeConfigExport");
|
|
15
15
|
const requireConfig_1 = require("../requireConfig");
|
|
16
|
+
const projectConfig_1 = require("../functions/projectConfig");
|
|
16
17
|
const REQUIRED_PERMISSIONS = [
|
|
17
18
|
"runtimeconfig.configs.list",
|
|
18
19
|
"runtimeconfig.configs.get",
|
|
@@ -79,6 +80,8 @@ exports.default = new command_1.Command("functions:config:export")
|
|
|
79
80
|
.before(requireConfig_1.requireConfig)
|
|
80
81
|
.before(requireInteractive_1.default)
|
|
81
82
|
.action(async (options) => {
|
|
83
|
+
const config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions)[0];
|
|
84
|
+
const functionsDir = config.source;
|
|
82
85
|
let pInfos = configExport.getProjectInfos(options);
|
|
83
86
|
checkReservedAliases(pInfos);
|
|
84
87
|
(0, utils_1.logBullet)("Importing functions configs from projects [" +
|
|
@@ -108,7 +111,6 @@ exports.default = new command_1.Command("functions:config:export")
|
|
|
108
111
|
const filesToWrite = fromEntries((0, functional_1.zip)(filenames, dotEnvs));
|
|
109
112
|
filesToWrite[".env.local"] = `${header}\n# .env.local file contains environment variables for the Functions Emulator.\n`;
|
|
110
113
|
filesToWrite[".env"] = `${header}# .env file contains environment variables that applies to all projects.\n`;
|
|
111
|
-
const functionsDir = options.config.get("functions.source", ".");
|
|
112
114
|
for (const [filename, content] of Object.entries(filesToWrite)) {
|
|
113
115
|
await options.config.askWriteProjectFile(path.join(functionsDir, filename), content);
|
|
114
116
|
}
|
package/lib/config.js
CHANGED
|
@@ -39,10 +39,17 @@ class Config {
|
|
|
39
39
|
_.set(this.data, target, this.materialize(target));
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
|
-
if (this.projectDir &&
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (this.projectDir && fsutils.dirExistsSync(this.path(Config.DEFAULT_FUNCTIONS_SOURCE))) {
|
|
43
|
+
if (Array.isArray(this.get("functions"))) {
|
|
44
|
+
if (!this.get("functions.[0].source")) {
|
|
45
|
+
this.set("functions.[0].source", Config.DEFAULT_FUNCTIONS_SOURCE);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (!this.get("functions.source")) {
|
|
50
|
+
this.set("functions.source", Config.DEFAULT_FUNCTIONS_SOURCE);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
46
53
|
}
|
|
47
54
|
}
|
|
48
55
|
materialize(target) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureServiceAgentRoles = exports.mergeBindings = exports.checkHttpIam = exports.checkServiceAccountIam = void 0;
|
|
3
|
+
exports.ensureServiceAgentRoles = exports.mergeBindings = exports.obtainEventarcServiceAgentBindings = exports.obtainDefaultComputeServiceAgentBindings = exports.obtainPubSubServiceAgentBindings = exports.obtainBinding = exports.checkHttpIam = exports.checkServiceAccountIam = exports.EVENTARC_SERVICE_AGENT_ROLE = exports.EVENTARC_EVENT_RECEIVER_ROLE = exports.RUN_INVOKER_ROLE = exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = void 0;
|
|
4
4
|
const cli_color_1 = require("cli-color");
|
|
5
5
|
const logger_1 = require("../../logger");
|
|
6
6
|
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
@@ -12,6 +12,10 @@ const utils = require("../../utils");
|
|
|
12
12
|
const resourceManager_1 = require("../../gcp/resourceManager");
|
|
13
13
|
const services_1 = require("./services");
|
|
14
14
|
const PERMISSION = "cloudfunctions.functions.setIamPolicy";
|
|
15
|
+
exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator";
|
|
16
|
+
exports.RUN_INVOKER_ROLE = "roles/run.invoker";
|
|
17
|
+
exports.EVENTARC_EVENT_RECEIVER_ROLE = "roles/eventarc.eventReceiver";
|
|
18
|
+
exports.EVENTARC_SERVICE_AGENT_ROLE = "roles/eventarc.serviceAgent";
|
|
15
19
|
async function checkServiceAccountIam(projectId) {
|
|
16
20
|
const saEmail = `${projectId}@appspot.gserviceaccount.com`;
|
|
17
21
|
let passed = false;
|
|
@@ -67,6 +71,37 @@ function reduceEventsToServices(services, endpoint) {
|
|
|
67
71
|
}
|
|
68
72
|
return services;
|
|
69
73
|
}
|
|
74
|
+
function obtainBinding(existingPolicy, serviceAccount, role) {
|
|
75
|
+
let binding = existingPolicy.bindings.find((b) => b.role === role);
|
|
76
|
+
if (!binding) {
|
|
77
|
+
binding = {
|
|
78
|
+
role,
|
|
79
|
+
members: [],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (!binding.members.find((m) => m === serviceAccount)) {
|
|
83
|
+
binding.members.push(serviceAccount);
|
|
84
|
+
}
|
|
85
|
+
return binding;
|
|
86
|
+
}
|
|
87
|
+
exports.obtainBinding = obtainBinding;
|
|
88
|
+
function obtainPubSubServiceAgentBindings(projectNumber, existingPolicy) {
|
|
89
|
+
const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
|
|
90
|
+
return [obtainBinding(existingPolicy, pubsubServiceAgent, exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE)];
|
|
91
|
+
}
|
|
92
|
+
exports.obtainPubSubServiceAgentBindings = obtainPubSubServiceAgentBindings;
|
|
93
|
+
function obtainDefaultComputeServiceAgentBindings(projectNumber, existingPolicy) {
|
|
94
|
+
const defaultComputeServiceAgent = `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`;
|
|
95
|
+
const invokerBinding = obtainBinding(existingPolicy, defaultComputeServiceAgent, exports.RUN_INVOKER_ROLE);
|
|
96
|
+
const eventReceiverBinding = obtainBinding(existingPolicy, defaultComputeServiceAgent, exports.EVENTARC_EVENT_RECEIVER_ROLE);
|
|
97
|
+
return [invokerBinding, eventReceiverBinding];
|
|
98
|
+
}
|
|
99
|
+
exports.obtainDefaultComputeServiceAgentBindings = obtainDefaultComputeServiceAgentBindings;
|
|
100
|
+
function obtainEventarcServiceAgentBindings(projectNumber, existingPolicy) {
|
|
101
|
+
const eventarcServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`;
|
|
102
|
+
return [obtainBinding(existingPolicy, eventarcServiceAgent, exports.EVENTARC_SERVICE_AGENT_ROLE)];
|
|
103
|
+
}
|
|
104
|
+
exports.obtainEventarcServiceAgentBindings = obtainEventarcServiceAgentBindings;
|
|
70
105
|
function mergeBindings(policy, allRequiredBindings) {
|
|
71
106
|
for (const requiredBindings of allRequiredBindings) {
|
|
72
107
|
if (requiredBindings.length === 0) {
|
|
@@ -107,6 +142,14 @@ async function ensureServiceAgentRoles(projectNumber, want, have) {
|
|
|
107
142
|
const findRequiredBindings = [];
|
|
108
143
|
newServices.forEach((service) => findRequiredBindings.push(service.requiredProjectBindings(projectNumber, policy)));
|
|
109
144
|
const allRequiredBindings = await Promise.all(findRequiredBindings);
|
|
145
|
+
if (haveServices.length === 0) {
|
|
146
|
+
allRequiredBindings.push(obtainPubSubServiceAgentBindings(projectNumber, policy));
|
|
147
|
+
allRequiredBindings.push(obtainDefaultComputeServiceAgentBindings(projectNumber, policy));
|
|
148
|
+
allRequiredBindings.push(obtainEventarcServiceAgentBindings(projectNumber, policy));
|
|
149
|
+
}
|
|
150
|
+
if (!allRequiredBindings.find((bindings) => bindings.length > 0)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
110
153
|
mergeBindings(policy, allRequiredBindings);
|
|
111
154
|
try {
|
|
112
155
|
await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
|
|
@@ -9,7 +9,6 @@ const utils_1 = require("../../utils");
|
|
|
9
9
|
const gcs = require("../../gcp/storage");
|
|
10
10
|
const gcf = require("../../gcp/cloudfunctions");
|
|
11
11
|
const gcfv2 = require("../../gcp/cloudfunctionsv2");
|
|
12
|
-
const utils = require("../../utils");
|
|
13
12
|
const backend = require("./backend");
|
|
14
13
|
(0, tmp_1.setGracefulCleanup)();
|
|
15
14
|
async function uploadSourceV1(context, region) {
|
|
@@ -33,7 +32,7 @@ async function uploadSourceV2(context, region) {
|
|
|
33
32
|
context.storage = Object.assign(Object.assign({}, context.storage), { [region]: res.storageSource });
|
|
34
33
|
}
|
|
35
34
|
async function deploy(context, options, payload) {
|
|
36
|
-
if (!
|
|
35
|
+
if (!context.config) {
|
|
37
36
|
return;
|
|
38
37
|
}
|
|
39
38
|
if (!context.functionsSourceV1 && !context.functionsSourceV2) {
|
|
@@ -53,12 +52,9 @@ async function deploy(context, options, payload) {
|
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
await Promise.all(uploads);
|
|
56
|
-
|
|
55
|
+
const source = context.config.source;
|
|
57
56
|
if (uploads.length) {
|
|
58
|
-
(0, utils_1.logSuccess)(clc.green.bold("functions:")
|
|
59
|
-
" " +
|
|
60
|
-
clc.bold(options.config.src.functions.source) +
|
|
61
|
-
" folder uploaded successfully");
|
|
57
|
+
(0, utils_1.logSuccess)(`${clc.green.bold("functions:")} ${clc.bold(source)} folder uploaded successfully`);
|
|
62
58
|
}
|
|
63
59
|
}
|
|
64
60
|
catch (err) {
|
|
@@ -19,6 +19,7 @@ const logger_1 = require("../../logger");
|
|
|
19
19
|
const triggerRegionHelper_1 = require("./triggerRegionHelper");
|
|
20
20
|
const checkIam_1 = require("./checkIam");
|
|
21
21
|
const error_1 = require("../../error");
|
|
22
|
+
const projectConfig_1 = require("../../functions/projectConfig");
|
|
22
23
|
function hasUserConfig(config) {
|
|
23
24
|
return Object.keys(config).length > 1;
|
|
24
25
|
}
|
|
@@ -28,16 +29,17 @@ function hasDotenv(opts) {
|
|
|
28
29
|
async function prepare(context, options, payload) {
|
|
29
30
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
30
31
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
31
|
-
|
|
32
|
+
context.config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions)[0];
|
|
33
|
+
const sourceDirName = context.config.source;
|
|
32
34
|
if (!sourceDirName) {
|
|
33
|
-
throw new error_1.FirebaseError(`No functions code detected at default location (./functions), and no functions
|
|
35
|
+
throw new error_1.FirebaseError(`No functions code detected at default location (./functions), and no functions source defined in firebase.json`);
|
|
34
36
|
}
|
|
35
37
|
const sourceDir = options.config.path(sourceDirName);
|
|
36
38
|
const delegateContext = {
|
|
37
39
|
projectId,
|
|
38
40
|
sourceDir,
|
|
39
41
|
projectDir: options.config.projectDir,
|
|
40
|
-
runtime:
|
|
42
|
+
runtime: context.config.runtime || "",
|
|
41
43
|
};
|
|
42
44
|
const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext);
|
|
43
45
|
logger_1.logger.debug(`Validating ${runtimeDelegate.name} source`);
|
|
@@ -95,10 +97,10 @@ async function prepare(context, options, payload) {
|
|
|
95
97
|
" directory for uploading...");
|
|
96
98
|
}
|
|
97
99
|
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
|
|
98
|
-
context.functionsSourceV1 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(
|
|
100
|
+
context.functionsSourceV1 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config, runtimeConfig);
|
|
99
101
|
}
|
|
100
102
|
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
101
|
-
context.functionsSourceV2 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(
|
|
103
|
+
context.functionsSourceV2 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config);
|
|
102
104
|
}
|
|
103
105
|
for (const endpoint of backend.allEndpoints(wantBackend)) {
|
|
104
106
|
endpoint.environmentVariables = wantBackend.environmentVariables;
|