firebase-tools 9.19.0 → 9.20.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 +1 -3
- package/lib/api.js +1 -0
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/ext-configure.js +2 -0
- package/lib/commands/ext-install.js +23 -2
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +9 -2
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +44 -35
- package/lib/commands/functions-list.js +1 -1
- package/lib/commands/index.js +8 -0
- package/lib/deploy/functions/backend.js +13 -4
- package/lib/deploy/functions/prepare.js +3 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +13 -1
- package/lib/deploy/functions/triggerRegionHelper.js +32 -0
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +513 -1
- package/lib/emulator/auth/handlers.js +4 -3
- package/lib/emulator/auth/operations.js +144 -14
- package/lib/emulator/auth/server.js +10 -13
- package/lib/emulator/auth/state.js +132 -13
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/functionsEmulatorRuntime.js +29 -7
- package/lib/extensions/askUserForConsent.js +14 -1
- package/lib/extensions/askUserForParam.js +72 -3
- package/lib/extensions/extensionsApi.js +1 -0
- package/lib/extensions/extensionsHelper.js +1 -0
- package/lib/extensions/paramHelper.js +3 -2
- package/lib/extensions/secretsUtils.js +58 -0
- package/lib/functional.js +8 -1
- package/lib/functions/env.js +10 -4
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/cloudfunctions.js +4 -2
- package/lib/gcp/cloudfunctionsv2.js +6 -0
- 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/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateDotenvFilename = exports.toDotenvFormat = exports.hydrateEnvs = exports.configToEnv = exports.convertKey = exports.hydrateConfigs = exports.getProjectInfos = void 0;
|
|
4
|
+
const clc = require("cli-color");
|
|
5
|
+
const env = require("./env");
|
|
6
|
+
const functionsConfig = require("../functionsConfig");
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
const logger_1 = require("../logger");
|
|
9
|
+
const projectUtils_1 = require("../projectUtils");
|
|
10
|
+
const rc_1 = require("../rc");
|
|
11
|
+
const utils_1 = require("../utils");
|
|
12
|
+
const functional_1 = require("../functional");
|
|
13
|
+
function getProjectInfos(options) {
|
|
14
|
+
const result = {};
|
|
15
|
+
const rc = rc_1.loadRC(options);
|
|
16
|
+
if (rc.projects) {
|
|
17
|
+
for (const [alias, projectId] of Object.entries(rc.projects)) {
|
|
18
|
+
if (Object.keys(result).includes(projectId)) {
|
|
19
|
+
utils_1.logWarning(`Multiple aliases found for ${clc.bold(projectId)}. ` +
|
|
20
|
+
`Preferring alias (${clc.bold(result[projectId])}) over (${clc.bold(alias)}).`);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
result[projectId] = alias;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const projectId = projectUtils_1.getProjectId(options);
|
|
27
|
+
if (projectId && !Object.keys(result).includes(projectId)) {
|
|
28
|
+
result[projectId] = projectId;
|
|
29
|
+
}
|
|
30
|
+
return Object.entries(result).map(([k, v]) => {
|
|
31
|
+
const result = { projectId: k };
|
|
32
|
+
if (k !== v) {
|
|
33
|
+
result.alias = v;
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
exports.getProjectInfos = getProjectInfos;
|
|
39
|
+
async function hydrateConfigs(pInfos) {
|
|
40
|
+
const hydrate = pInfos.map((info) => {
|
|
41
|
+
return functionsConfig
|
|
42
|
+
.materializeAll(info.projectId)
|
|
43
|
+
.then((config) => {
|
|
44
|
+
info.config = config;
|
|
45
|
+
return;
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
logger_1.logger.debug(`Failed to fetch runtime config for project ${info.projectId}: ${err.message}`);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
await Promise.all(hydrate);
|
|
52
|
+
}
|
|
53
|
+
exports.hydrateConfigs = hydrateConfigs;
|
|
54
|
+
function convertKey(configKey, prefix) {
|
|
55
|
+
const baseKey = configKey
|
|
56
|
+
.toUpperCase()
|
|
57
|
+
.replace(/\./g, "_")
|
|
58
|
+
.replace(/-/g, "_");
|
|
59
|
+
let envKey = baseKey;
|
|
60
|
+
try {
|
|
61
|
+
env.validateKey(envKey);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (err instanceof env.KeyValidationError) {
|
|
65
|
+
envKey = prefix + envKey;
|
|
66
|
+
env.validateKey(envKey);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return envKey;
|
|
70
|
+
}
|
|
71
|
+
exports.convertKey = convertKey;
|
|
72
|
+
function configToEnv(configs, prefix) {
|
|
73
|
+
const success = [];
|
|
74
|
+
const errors = [];
|
|
75
|
+
for (const [configKey, value] of functional_1.flatten(configs)) {
|
|
76
|
+
try {
|
|
77
|
+
const envKey = convertKey(configKey, prefix);
|
|
78
|
+
success.push({ origKey: configKey, newKey: envKey, value: value });
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (err instanceof env.KeyValidationError) {
|
|
82
|
+
errors.push({
|
|
83
|
+
origKey: configKey,
|
|
84
|
+
newKey: err.key,
|
|
85
|
+
err: err.message,
|
|
86
|
+
value: value,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
throw new error_1.FirebaseError("Unexpected error while converting config", {
|
|
91
|
+
exit: 2,
|
|
92
|
+
original: err,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { success, errors };
|
|
98
|
+
}
|
|
99
|
+
exports.configToEnv = configToEnv;
|
|
100
|
+
function hydrateEnvs(pInfos, prefix) {
|
|
101
|
+
let errMsg = "";
|
|
102
|
+
for (const pInfo of pInfos) {
|
|
103
|
+
const { success, errors } = configToEnv(pInfo.config, prefix);
|
|
104
|
+
if (errors.length > 0) {
|
|
105
|
+
const msg = `${pInfo.projectId} ` +
|
|
106
|
+
`${pInfo.alias ? "(" + pInfo.alias + ")" : ""}:\n` +
|
|
107
|
+
errors.map((err) => `\t${err.origKey} => ${clc.bold(err.newKey)} (${err.err})`).join("\n") +
|
|
108
|
+
"\n";
|
|
109
|
+
errMsg += msg;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
pInfo.envs = success;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return errMsg;
|
|
116
|
+
}
|
|
117
|
+
exports.hydrateEnvs = hydrateEnvs;
|
|
118
|
+
function escape(s) {
|
|
119
|
+
const result = s
|
|
120
|
+
.replace("\n", "\\n")
|
|
121
|
+
.replace("\r", "\\r")
|
|
122
|
+
.replace("\t", "\\t")
|
|
123
|
+
.replace("\v", "\\v");
|
|
124
|
+
return result.replace(/(['"])/g, "\\$1");
|
|
125
|
+
}
|
|
126
|
+
function toDotenvFormat(envs, header = "") {
|
|
127
|
+
const lines = envs.map(({ newKey, value }) => `${newKey}="${escape(value)}"`);
|
|
128
|
+
const maxLineLen = Math.max(...lines.map((l) => l.length));
|
|
129
|
+
return (`${header}\n` +
|
|
130
|
+
lines.map((line, idx) => `${line.padEnd(maxLineLen)} # from ${envs[idx].origKey}`).join("\n"));
|
|
131
|
+
}
|
|
132
|
+
exports.toDotenvFormat = toDotenvFormat;
|
|
133
|
+
function generateDotenvFilename(pInfo) {
|
|
134
|
+
var _a;
|
|
135
|
+
return `.env.${(_a = pInfo.alias) !== null && _a !== void 0 ? _a : pInfo.projectId}`;
|
|
136
|
+
}
|
|
137
|
+
exports.generateDotenvFilename = generateDotenvFilename;
|
|
@@ -198,9 +198,11 @@ async function list(projectId, region) {
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
catch (err) {
|
|
201
|
-
logger_1.logger.debug(
|
|
201
|
+
logger_1.logger.debug(`[functions] failed to list functions for ${projectId}`);
|
|
202
202
|
logger_1.logger.debug(`[functions] ${err === null || err === void 0 ? void 0 : err.message}`);
|
|
203
|
-
|
|
203
|
+
throw new error_1.FirebaseError(`Failed to list functions for ${projectId}`, {
|
|
204
|
+
original: err,
|
|
205
|
+
});
|
|
204
206
|
}
|
|
205
207
|
}
|
|
206
208
|
async function listFunctions(projectId, region) {
|
|
@@ -143,6 +143,9 @@ function functionFromSpec(cloudFunction, source) {
|
|
|
143
143
|
gcfFunction.eventTrigger = {
|
|
144
144
|
eventType: cloudFunction.trigger.eventType,
|
|
145
145
|
};
|
|
146
|
+
if (cloudFunction.trigger.region) {
|
|
147
|
+
gcfFunction.eventTrigger.triggerRegion = cloudFunction.trigger.region;
|
|
148
|
+
}
|
|
146
149
|
if (gcfFunction.eventTrigger.eventType === exports.PUBSUB_PUBLISH_EVENT) {
|
|
147
150
|
gcfFunction.eventTrigger.pubsubTopic = cloudFunction.trigger.eventFilters.resource;
|
|
148
151
|
}
|
|
@@ -169,6 +172,9 @@ function specFromFunction(gcfFunction) {
|
|
|
169
172
|
eventFilters: {},
|
|
170
173
|
retry: false,
|
|
171
174
|
};
|
|
175
|
+
if (gcfFunction.eventTrigger.triggerRegion) {
|
|
176
|
+
trigger.region = gcfFunction.eventTrigger.triggerRegion;
|
|
177
|
+
}
|
|
172
178
|
if (gcfFunction.eventTrigger.pubsubTopic) {
|
|
173
179
|
trigger.eventFilters.resource = gcfFunction.eventTrigger.pubsubTopic;
|
|
174
180
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.grantServiceAgentRole = exports.addVersion = exports.createSecret = exports.parseSecretResourceName = exports.secretExists = exports.getSecretLabels = exports.getSecret = exports.listSecrets = void 0;
|
|
4
|
+
const api = require("../api");
|
|
5
|
+
async function listSecrets(projectId) {
|
|
6
|
+
const listRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets`, {
|
|
7
|
+
auth: true,
|
|
8
|
+
origin: api.secretManagerOrigin,
|
|
9
|
+
});
|
|
10
|
+
return listRes.body.secrets.map((s) => parseSecretResourceName(s.name));
|
|
11
|
+
}
|
|
12
|
+
exports.listSecrets = listSecrets;
|
|
13
|
+
async function getSecret(projectId, name) {
|
|
14
|
+
const getRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets/${name}`, {
|
|
15
|
+
auth: true,
|
|
16
|
+
origin: api.secretManagerOrigin,
|
|
17
|
+
});
|
|
18
|
+
return parseSecretResourceName(getRes.body.name);
|
|
19
|
+
}
|
|
20
|
+
exports.getSecret = getSecret;
|
|
21
|
+
async function getSecretLabels(projectId, name) {
|
|
22
|
+
const getRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets/${name}`, {
|
|
23
|
+
auth: true,
|
|
24
|
+
origin: api.secretManagerOrigin,
|
|
25
|
+
});
|
|
26
|
+
return getRes.body.labels;
|
|
27
|
+
}
|
|
28
|
+
exports.getSecretLabels = getSecretLabels;
|
|
29
|
+
async function secretExists(projectId, name) {
|
|
30
|
+
try {
|
|
31
|
+
await getSecret(projectId, name);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
if (err.status === 404) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.secretExists = secretExists;
|
|
42
|
+
function parseSecretResourceName(resourceName) {
|
|
43
|
+
const nameTokens = resourceName.split("/");
|
|
44
|
+
return {
|
|
45
|
+
projectId: nameTokens[1],
|
|
46
|
+
name: nameTokens[3],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
exports.parseSecretResourceName = parseSecretResourceName;
|
|
50
|
+
async function createSecret(projectId, name, labels) {
|
|
51
|
+
const createRes = await api.request("POST", `/v1beta1/projects/${projectId}/secrets?secretId=${name}`, {
|
|
52
|
+
auth: true,
|
|
53
|
+
origin: api.secretManagerOrigin,
|
|
54
|
+
data: {
|
|
55
|
+
replication: {
|
|
56
|
+
automatic: {},
|
|
57
|
+
},
|
|
58
|
+
labels,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
return parseSecretResourceName(createRes.body.name);
|
|
62
|
+
}
|
|
63
|
+
exports.createSecret = createSecret;
|
|
64
|
+
async function addVersion(secret, payloadData) {
|
|
65
|
+
const res = await api.request("POST", `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:addVersion`, {
|
|
66
|
+
auth: true,
|
|
67
|
+
origin: api.secretManagerOrigin,
|
|
68
|
+
data: {
|
|
69
|
+
payload: {
|
|
70
|
+
data: Buffer.from(payloadData).toString("base64"),
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const nameTokens = res.body.name.split("/");
|
|
75
|
+
return {
|
|
76
|
+
secret: {
|
|
77
|
+
projectId: nameTokens[1],
|
|
78
|
+
name: nameTokens[3],
|
|
79
|
+
},
|
|
80
|
+
versionId: nameTokens[5],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
exports.addVersion = addVersion;
|
|
84
|
+
async function grantServiceAgentRole(secret, serviceAccountEmail, role) {
|
|
85
|
+
const getPolicyRes = await api.request("GET", `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy`, {
|
|
86
|
+
auth: true,
|
|
87
|
+
origin: api.secretManagerOrigin,
|
|
88
|
+
});
|
|
89
|
+
const bindings = getPolicyRes.body.bindings || [];
|
|
90
|
+
if (bindings.find((b) => b.role == role &&
|
|
91
|
+
b.members.find((m) => m == `serviceAccount:${serviceAccountEmail}`))) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
bindings.push({
|
|
95
|
+
role: role,
|
|
96
|
+
members: [`serviceAccount:${serviceAccountEmail}`],
|
|
97
|
+
});
|
|
98
|
+
await api.request("POST", `/v1beta1/projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, {
|
|
99
|
+
auth: true,
|
|
100
|
+
origin: api.secretManagerOrigin,
|
|
101
|
+
data: {
|
|
102
|
+
policy: {
|
|
103
|
+
bindings,
|
|
104
|
+
},
|
|
105
|
+
updateMask: {
|
|
106
|
+
paths: "bindings",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
exports.grantServiceAgentRole = grantServiceAgentRole;
|
package/lib/gcp/storage.js
CHANGED
|
@@ -61,9 +61,25 @@ function _deleteObject(location) {
|
|
|
61
61
|
origin: api.storageOrigin,
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
+
async function _getBucket(bucketName) {
|
|
65
|
+
try {
|
|
66
|
+
const result = await api.request("GET", `/storage/v1/b/${bucketName}`, {
|
|
67
|
+
auth: true,
|
|
68
|
+
origin: api.storageOrigin,
|
|
69
|
+
});
|
|
70
|
+
return result.body;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
logger.debug(err);
|
|
74
|
+
throw new FirebaseError("Failed to obtain the storage bucket", {
|
|
75
|
+
original: err,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
64
79
|
module.exports = {
|
|
65
80
|
getDefaultBucket: _getDefaultBucket,
|
|
66
81
|
deleteObject: _deleteObject,
|
|
67
82
|
upload: _uploadSource,
|
|
68
83
|
uploadObject: _uploadObject,
|
|
84
|
+
getBucket: _getBucket,
|
|
69
85
|
};
|
package/lib/previews.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.previews = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
5
|
const configstore_1 = require("./configstore");
|
|
6
|
-
exports.previews = Object.assign({ rtdbrules: false, ext: false, extdev: false, rtdbmanagement: false, functionsv2: false, golang: false, deletegcfartifacts: false, dotenv: false }, configstore_1.configstore.get("previews"));
|
|
6
|
+
exports.previews = Object.assign({ rtdbrules: false, ext: false, extdev: false, rtdbmanagement: false, functionsv2: false, golang: false, deletegcfartifacts: false, dotenv: false, crashlyticsSymbolsUpload: false }, configstore_1.configstore.get("previews"));
|
|
7
7
|
if (process.env.FIREBASE_CLI_PREVIEWS) {
|
|
8
8
|
process.env.FIREBASE_CLI_PREVIEWS.split(",").forEach((feature) => {
|
|
9
9
|
if (lodash_1.has(exports.previews, feature)) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const error_1 = require("./error");
|
|
4
|
+
function requireInteractive(options) {
|
|
5
|
+
if (options.nonInteractive) {
|
|
6
|
+
return Promise.reject(new error_1.FirebaseError("This command cannot run in non-interactive mode", {
|
|
7
|
+
exit: 1,
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
return Promise.resolve();
|
|
11
|
+
}
|
|
12
|
+
exports.default = requireInteractive;
|