firebase-tools 11.3.0 → 11.4.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/apiv2.js +5 -0
- package/lib/checkValidTargetFilters.js +3 -2
- package/lib/database/rulesConfig.js +35 -8
- package/lib/deploy/functions/release/index.js +4 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +3 -0
- package/lib/deploy/hosting/convertConfig.js +76 -16
- package/lib/deploy/index.js +1 -1
- package/lib/deploy/storage/prepare.js +29 -6
- package/lib/emulator/controller.js +0 -1
- package/lib/emulator/functionsEmulator.js +3 -0
- package/lib/emulator/functionsEmulatorRuntime.js +1 -1
- package/lib/functions/env.js +47 -2
- package/lib/gcp/iam.js +20 -17
- package/lib/rc.js +3 -9
- package/lib/rulesDeploy.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/lib/apiv2.js
CHANGED
|
@@ -15,6 +15,7 @@ const responseToError_1 = require("./responseToError");
|
|
|
15
15
|
const FormData = require("form-data");
|
|
16
16
|
const pkg = require("../package.json");
|
|
17
17
|
const CLI_VERSION = pkg.version;
|
|
18
|
+
const GOOG_QUOTA_USER = "x-goog-quota-user";
|
|
18
19
|
let accessToken = "";
|
|
19
20
|
let refreshToken = "";
|
|
20
21
|
function setRefreshToken(token = "") {
|
|
@@ -309,6 +310,10 @@ class Client {
|
|
|
309
310
|
}
|
|
310
311
|
const logURL = this.requestURL(options);
|
|
311
312
|
logger_1.logger.debug(`>>> [apiv2][query] ${options.method} ${logURL} ${queryParamsLog}`);
|
|
313
|
+
const headers = options.headers;
|
|
314
|
+
if (headers && headers.has(GOOG_QUOTA_USER)) {
|
|
315
|
+
logger_1.logger.debug(`>>> [apiv2][(partial)header] ${options.method} ${logURL} x-goog-quota-user=${headers.get(GOOG_QUOTA_USER) || ""}`);
|
|
316
|
+
}
|
|
312
317
|
if (options.body !== undefined) {
|
|
313
318
|
let logBody = "[omitted]";
|
|
314
319
|
if (!((_b = options.skipLog) === null || _b === void 0 ? void 0 : _b.body)) {
|
|
@@ -19,6 +19,7 @@ function targetsHaveFilters(...targets) {
|
|
|
19
19
|
function targetsHaveNoFilters(...targets) {
|
|
20
20
|
return targets.some((t) => !t.includes(":"));
|
|
21
21
|
}
|
|
22
|
+
const FILTERABLE_TARGETS = new Set(["hosting", "functions", "firestore", "storage", "database"]);
|
|
22
23
|
async function checkValidTargetFilters(options) {
|
|
23
24
|
const only = !options.only ? [] : options.only.split(",");
|
|
24
25
|
return new Promise((resolve, reject) => {
|
|
@@ -28,10 +29,10 @@ async function checkValidTargetFilters(options) {
|
|
|
28
29
|
if (options.except) {
|
|
29
30
|
return reject(new error_1.FirebaseError("Cannot specify both --only and --except"));
|
|
30
31
|
}
|
|
31
|
-
const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !
|
|
32
|
+
const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !FILTERABLE_TARGETS.has(t));
|
|
32
33
|
const targetsForNonFilteredTypes = targetsForTypes(only, ...nonFilteredTypes);
|
|
33
34
|
if (targetsForNonFilteredTypes.length && targetsHaveFilters(...targetsForNonFilteredTypes)) {
|
|
34
|
-
return reject(new error_1.FirebaseError("Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions, hosting, and firestore"));
|
|
35
|
+
return reject(new error_1.FirebaseError("Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions, hosting, storage, and firestore"));
|
|
35
36
|
}
|
|
36
37
|
const targetsForFunctions = targetsForTypes(only, "functions");
|
|
37
38
|
if (targetsForFunctions.length &&
|
|
@@ -19,6 +19,23 @@ function getRulesConfig(projectId, options) {
|
|
|
19
19
|
if (dbConfig === undefined) {
|
|
20
20
|
return [];
|
|
21
21
|
}
|
|
22
|
+
const rc = options.rc;
|
|
23
|
+
let allDatabases = !options.only;
|
|
24
|
+
const onlyDatabases = new Set();
|
|
25
|
+
if (options.only) {
|
|
26
|
+
const split = options.only.split(",");
|
|
27
|
+
if (split.includes("database")) {
|
|
28
|
+
allDatabases = true;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
for (const value of split) {
|
|
32
|
+
if (value.startsWith("database:")) {
|
|
33
|
+
const target = value.split(":")[1];
|
|
34
|
+
onlyDatabases.add(target);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
22
39
|
if (!Array.isArray(dbConfig)) {
|
|
23
40
|
if (dbConfig && dbConfig.rules) {
|
|
24
41
|
utils.assertIsStringOrUndefined(options.instance);
|
|
@@ -31,22 +48,32 @@ function getRulesConfig(projectId, options) {
|
|
|
31
48
|
}
|
|
32
49
|
}
|
|
33
50
|
const results = [];
|
|
34
|
-
const rc = options.rc;
|
|
35
51
|
for (const c of dbConfig) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
const { instance, target } = c;
|
|
53
|
+
if (target) {
|
|
54
|
+
if (allDatabases || onlyDatabases.has(target)) {
|
|
55
|
+
rc.requireTarget(projectId, "database", target);
|
|
56
|
+
const instances = rc.target(projectId, "database", target);
|
|
57
|
+
for (const i of instances) {
|
|
58
|
+
results.push({ instance: i, rules: c.rules });
|
|
59
|
+
}
|
|
60
|
+
onlyDatabases.delete(target);
|
|
41
61
|
}
|
|
42
62
|
}
|
|
43
|
-
else if (
|
|
44
|
-
|
|
63
|
+
else if (instance) {
|
|
64
|
+
if (allDatabases) {
|
|
65
|
+
results.push(c);
|
|
66
|
+
}
|
|
45
67
|
}
|
|
46
68
|
else {
|
|
47
69
|
throw new error_1.FirebaseError('Must supply either "target" or "instance" in database config');
|
|
48
70
|
}
|
|
49
71
|
}
|
|
72
|
+
if (!allDatabases && onlyDatabases.size !== 0) {
|
|
73
|
+
throw new error_1.FirebaseError(`Could not find configurations in firebase.json for the following database targets: ${[
|
|
74
|
+
...onlyDatabases,
|
|
75
|
+
].join(", ")}`);
|
|
76
|
+
}
|
|
50
77
|
return results;
|
|
51
78
|
}
|
|
52
79
|
exports.getRulesConfig = getRulesConfig;
|
|
@@ -67,6 +67,10 @@ async function release(context, options, payload) {
|
|
|
67
67
|
const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
|
|
68
68
|
if (allErrors.length) {
|
|
69
69
|
const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors };
|
|
70
|
+
logger_1.logger.debug("Functions deploy failed.");
|
|
71
|
+
for (const error of allErrors) {
|
|
72
|
+
logger_1.logger.debug(JSON.stringify(error, null, 2));
|
|
73
|
+
}
|
|
70
74
|
throw new error_1.FirebaseError("There was an error deploying functions", Object.assign(Object.assign({}, opts), { exit: 2 }));
|
|
71
75
|
}
|
|
72
76
|
}
|
|
@@ -28,10 +28,12 @@ function buildFromV1Alpha1(yaml, project, region, runtime) {
|
|
|
28
28
|
(0, parsing_1.requireKeys)("", manifest, "endpoints");
|
|
29
29
|
(0, parsing_1.assertKeyTypes)("", manifest, {
|
|
30
30
|
specVersion: "string",
|
|
31
|
+
params: "array",
|
|
31
32
|
requiredAPIs: "array",
|
|
32
33
|
endpoints: "object",
|
|
33
34
|
});
|
|
34
35
|
const bd = build.empty();
|
|
36
|
+
bd.params = manifest.params || [];
|
|
35
37
|
bd.requiredAPIs = parseRequiredAPIs(manifest);
|
|
36
38
|
for (const id of Object.keys(manifest.endpoints)) {
|
|
37
39
|
const me = manifest.endpoints[id];
|
|
@@ -49,6 +51,7 @@ function backendFromV1Alpha1(yaml, project, region, runtime) {
|
|
|
49
51
|
(0, parsing_1.requireKeys)("", manifest, "endpoints");
|
|
50
52
|
(0, parsing_1.assertKeyTypes)("", manifest, {
|
|
51
53
|
specVersion: "string",
|
|
54
|
+
params: "array",
|
|
52
55
|
requiredAPIs: "array",
|
|
53
56
|
endpoints: "object",
|
|
54
57
|
});
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.convertConfig = void 0;
|
|
4
4
|
const error_1 = require("../../error");
|
|
5
5
|
const backend_1 = require("../functions/backend");
|
|
6
|
+
const backend = require("../functions/backend");
|
|
6
7
|
function has(obj, k) {
|
|
7
8
|
return obj[k] !== undefined;
|
|
8
9
|
}
|
|
@@ -39,24 +40,66 @@ async function convertConfig(context, payload, config, finalize) {
|
|
|
39
40
|
if (!config) {
|
|
40
41
|
return out;
|
|
41
42
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
43
|
+
const endpointFromBackend = (targetBackend, functionsEndpointInfo) => {
|
|
44
|
+
const backendsForId = backend.allEndpoints(targetBackend).filter((endpoint) => {
|
|
45
|
+
return endpoint.id === functionsEndpointInfo.serviceId;
|
|
46
|
+
});
|
|
47
|
+
const matchingBackends = backendsForId.filter((endpoint) => {
|
|
48
|
+
return ((!functionsEndpointInfo.region || endpoint.region === functionsEndpointInfo.region) &&
|
|
49
|
+
(!functionsEndpointInfo.platform || endpoint.platform === functionsEndpointInfo.platform));
|
|
50
|
+
});
|
|
51
|
+
if (matchingBackends.length > 1) {
|
|
52
|
+
throw new error_1.FirebaseError(`More than one backend found for function name: ${functionsEndpointInfo.serviceId}. If the function is deployed in multiple regions, you must specify a region.`);
|
|
53
|
+
}
|
|
54
|
+
if (matchingBackends.length === 1) {
|
|
55
|
+
const endpoint = matchingBackends[0];
|
|
56
|
+
if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint)) {
|
|
57
|
+
return endpoint;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
};
|
|
62
|
+
const endpointBeingDeployed = (functionsEndpointInfo) => {
|
|
44
63
|
for (const { wantBackend } of Object.values(payload.functions || {})) {
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
if (!wantBackend) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const endpoint = endpointFromBackend(wantBackend, functionsEndpointInfo);
|
|
68
|
+
if (endpoint) {
|
|
47
69
|
return endpoint;
|
|
70
|
+
}
|
|
48
71
|
}
|
|
49
|
-
return
|
|
72
|
+
return;
|
|
50
73
|
};
|
|
51
|
-
const matchingEndpoint = async (
|
|
52
|
-
const pendingEndpoint = endpointBeingDeployed(
|
|
74
|
+
const matchingEndpoint = async (functionsEndpointInfo) => {
|
|
75
|
+
const pendingEndpoint = endpointBeingDeployed(functionsEndpointInfo);
|
|
53
76
|
if (pendingEndpoint)
|
|
54
77
|
return pendingEndpoint;
|
|
55
78
|
const backend = await (0, backend_1.existingBackend)(context);
|
|
56
79
|
return (0, backend_1.allEndpoints)(backend).find((it) => (0, backend_1.isHttpsTriggered)(it) &&
|
|
57
|
-
it.
|
|
58
|
-
it.
|
|
59
|
-
it.region === region);
|
|
80
|
+
it.id === functionsEndpointInfo.serviceId &&
|
|
81
|
+
(!functionsEndpointInfo.platform || it.platform === functionsEndpointInfo.platform) &&
|
|
82
|
+
(!functionsEndpointInfo.region || it.region === functionsEndpointInfo.region));
|
|
83
|
+
};
|
|
84
|
+
const findEndpointWithValidRegion = async (rewrite, context) => {
|
|
85
|
+
if ("function" in rewrite) {
|
|
86
|
+
const foundEndpointToBeDeployed = endpointBeingDeployed({
|
|
87
|
+
serviceId: rewrite.function,
|
|
88
|
+
region: rewrite.region,
|
|
89
|
+
});
|
|
90
|
+
if (foundEndpointToBeDeployed) {
|
|
91
|
+
return foundEndpointToBeDeployed;
|
|
92
|
+
}
|
|
93
|
+
const existingBackend = await backend.existingBackend(context);
|
|
94
|
+
const endpointAlreadyDeployed = endpointFromBackend(existingBackend, {
|
|
95
|
+
serviceId: rewrite.function,
|
|
96
|
+
region: rewrite.region,
|
|
97
|
+
});
|
|
98
|
+
if (endpointAlreadyDeployed) {
|
|
99
|
+
return endpointAlreadyDeployed;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
60
103
|
};
|
|
61
104
|
if (Array.isArray(config.rewrites)) {
|
|
62
105
|
out.rewrites = [];
|
|
@@ -66,19 +109,30 @@ async function convertConfig(context, payload, config, finalize) {
|
|
|
66
109
|
vRewrite.path = rewrite.destination;
|
|
67
110
|
}
|
|
68
111
|
else if ("function" in rewrite) {
|
|
69
|
-
if (!finalize &&
|
|
112
|
+
if (!finalize &&
|
|
113
|
+
endpointBeingDeployed({
|
|
114
|
+
serviceId: rewrite.function,
|
|
115
|
+
platform: "gcfv2",
|
|
116
|
+
region: rewrite.region,
|
|
117
|
+
})) {
|
|
70
118
|
continue;
|
|
71
|
-
|
|
119
|
+
}
|
|
120
|
+
const endpoint = await matchingEndpoint({
|
|
121
|
+
serviceId: rewrite.function,
|
|
122
|
+
platform: "gcfv2",
|
|
123
|
+
region: rewrite.region,
|
|
124
|
+
});
|
|
72
125
|
if (endpoint) {
|
|
73
126
|
vRewrite.run = { serviceId: endpoint.id, region: endpoint.region };
|
|
74
127
|
}
|
|
75
128
|
else {
|
|
76
129
|
vRewrite.function = rewrite.function;
|
|
77
|
-
|
|
78
|
-
|
|
130
|
+
const foundEndpoint = await findEndpointWithValidRegion(rewrite, context);
|
|
131
|
+
if (foundEndpoint) {
|
|
132
|
+
vRewrite.functionRegion = foundEndpoint.region;
|
|
79
133
|
}
|
|
80
134
|
else {
|
|
81
|
-
|
|
135
|
+
throw new error_1.FirebaseError(`Unable to find a valid endpoint for function ${vRewrite.function}`);
|
|
82
136
|
}
|
|
83
137
|
}
|
|
84
138
|
}
|
|
@@ -86,8 +140,14 @@ async function convertConfig(context, payload, config, finalize) {
|
|
|
86
140
|
vRewrite.dynamicLinks = rewrite.dynamicLinks;
|
|
87
141
|
}
|
|
88
142
|
else if ("run" in rewrite) {
|
|
89
|
-
if (!finalize &&
|
|
143
|
+
if (!finalize &&
|
|
144
|
+
endpointBeingDeployed({
|
|
145
|
+
serviceId: rewrite.run.serviceId,
|
|
146
|
+
platform: "gcfv2",
|
|
147
|
+
region: rewrite.run.region,
|
|
148
|
+
})) {
|
|
90
149
|
continue;
|
|
150
|
+
}
|
|
91
151
|
vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run);
|
|
92
152
|
}
|
|
93
153
|
out.rewrites.push(vRewrite);
|
package/lib/deploy/index.js
CHANGED
|
@@ -52,7 +52,7 @@ const deploy = async function (targetNames, options, customContext = {}) {
|
|
|
52
52
|
for (const targetName of targetNames) {
|
|
53
53
|
const target = TARGETS[targetName];
|
|
54
54
|
if (!target) {
|
|
55
|
-
return Promise.reject(new error_1.FirebaseError((0, cli_color_1.bold)(targetName)
|
|
55
|
+
return Promise.reject(new error_1.FirebaseError(`${(0, cli_color_1.bold)(targetName)} is not a valid deploy target`));
|
|
56
56
|
}
|
|
57
57
|
predeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "predeploy"));
|
|
58
58
|
prepares.push(target.prepare);
|
|
@@ -3,25 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const _ = require("lodash");
|
|
4
4
|
const gcp = require("../../gcp");
|
|
5
5
|
const rulesDeploy_1 = require("../../rulesDeploy");
|
|
6
|
+
const error_1 = require("../../error");
|
|
6
7
|
async function default_1(context, options) {
|
|
7
8
|
let rulesConfig = options.config.get("storage");
|
|
8
9
|
if (!rulesConfig) {
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
12
|
+
const onlyTargets = new Set();
|
|
13
|
+
let allStorage = !options.only;
|
|
14
|
+
if (options.only) {
|
|
15
|
+
const split = options.only.split(",");
|
|
16
|
+
if (split.includes("storage")) {
|
|
17
|
+
allStorage = true;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
for (const value of split) {
|
|
21
|
+
if (value.startsWith("storage:")) {
|
|
22
|
+
onlyTargets.add(value.split(":")[1]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
11
27
|
_.set(context, "storage.rules", rulesConfig);
|
|
12
28
|
const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.FIREBASE_STORAGE);
|
|
13
29
|
_.set(context, "storage.rulesDeploy", rulesDeploy);
|
|
14
|
-
if (
|
|
30
|
+
if (!Array.isArray(rulesConfig)) {
|
|
15
31
|
const defaultBucket = await gcp.storage.getDefaultBucket(options.project);
|
|
16
32
|
rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })];
|
|
17
33
|
_.set(context, "storage.rules", rulesConfig);
|
|
18
34
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
for (const ruleConfig of rulesConfig) {
|
|
36
|
+
const target = ruleConfig.target;
|
|
37
|
+
if (target) {
|
|
38
|
+
options.rc.requireTarget(context.projectId, "storage", target);
|
|
22
39
|
}
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
if (allStorage || onlyTargets.has(target)) {
|
|
41
|
+
rulesDeploy.addFile(ruleConfig.rules);
|
|
42
|
+
onlyTargets.delete(target);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!allStorage && onlyTargets.size !== 0) {
|
|
46
|
+
throw new error_1.FirebaseError(`Could not find rules for the following storage targets: ${[...onlyTargets].join(", ")}`);
|
|
47
|
+
}
|
|
25
48
|
await rulesDeploy.compile();
|
|
26
49
|
}
|
|
27
50
|
exports.default = default_1;
|
|
@@ -301,7 +301,6 @@ async function startAll(options, showUI = true) {
|
|
|
301
301
|
const extensionsBackends = await extensionEmulator.getExtensionBackends();
|
|
302
302
|
const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers(options, extensionsBackends);
|
|
303
303
|
emulatableBackends.push(...filteredExtensionsBackends);
|
|
304
|
-
void (0, track_1.track)("Emulator Run", types_1.Emulators.EXTENSIONS);
|
|
305
304
|
await startEmulator(extensionEmulator);
|
|
306
305
|
}
|
|
307
306
|
if (emulatableBackends.length) {
|
|
@@ -646,6 +646,9 @@ class FunctionsEmulator {
|
|
|
646
646
|
envs.GCLOUD_PROJECT = this.args.projectId;
|
|
647
647
|
envs.K_REVISION = "1";
|
|
648
648
|
envs.PORT = "80";
|
|
649
|
+
if (trigger === null || trigger === void 0 ? void 0 : trigger.timeoutSeconds) {
|
|
650
|
+
envs.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS = trigger.timeoutSeconds.toString();
|
|
651
|
+
}
|
|
649
652
|
if (trigger) {
|
|
650
653
|
const target = trigger.entryPoint;
|
|
651
654
|
envs.FUNCTION_TARGET = target;
|
|
@@ -537,8 +537,8 @@ async function processHTTPS(trigger) {
|
|
|
537
537
|
rejectEphemeralServer(err);
|
|
538
538
|
}
|
|
539
539
|
});
|
|
540
|
-
logDebug(`Attempting to listen to port: ${process.env.PORT}`);
|
|
541
540
|
server = ephemeralServer.listen(process.env.PORT);
|
|
541
|
+
logDebug(`Listening to port: ${process.env.PORT}`);
|
|
542
542
|
server.on("error", rejectEphemeralServer);
|
|
543
543
|
});
|
|
544
544
|
}
|
package/lib/functions/env.js
CHANGED
|
@@ -51,6 +51,17 @@ const ESCAPE_SEQUENCES_TO_CHARACTERS = {
|
|
|
51
51
|
"\\'": "'",
|
|
52
52
|
'\\"': '"',
|
|
53
53
|
};
|
|
54
|
+
const ALL_ESCAPE_SEQUENCES_RE = /\\[nrtv\\'"]/g;
|
|
55
|
+
const CHARACTERS_TO_ESCAPE_SEQUENCES = {
|
|
56
|
+
"\n": "\\n",
|
|
57
|
+
"\r": "\\r",
|
|
58
|
+
"\t": "\\t",
|
|
59
|
+
"\v": "\\v",
|
|
60
|
+
"\\": "\\\\",
|
|
61
|
+
"'": "\\'",
|
|
62
|
+
'"': '\\"',
|
|
63
|
+
};
|
|
64
|
+
const ALL_ESCAPABLE_CHARACTERS_RE = /[\n\r\t\v\\'"]/g;
|
|
54
65
|
function parse(data) {
|
|
55
66
|
const envs = {};
|
|
56
67
|
const errors = [];
|
|
@@ -63,7 +74,7 @@ function parse(data) {
|
|
|
63
74
|
if ((quotesMatch = /^(["'])(.*)\1$/ms.exec(v)) != null) {
|
|
64
75
|
v = quotesMatch[2];
|
|
65
76
|
if (quotesMatch[1] === '"') {
|
|
66
|
-
v = v.replace(
|
|
77
|
+
v = v.replace(ALL_ESCAPE_SEQUENCES_RE, (match) => ESCAPE_SEQUENCES_TO_CHARACTERS[match]);
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
envs[k] = v;
|
|
@@ -146,9 +157,43 @@ function hasUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, })
|
|
|
146
157
|
}
|
|
147
158
|
exports.hasUserEnvs = hasUserEnvs;
|
|
148
159
|
function writeUserEnvs(toWrite, envOpts) {
|
|
149
|
-
|
|
160
|
+
if (Object.keys(toWrite).length === 0) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const { functionsSource, projectId, projectAlias, isEmulator } = envOpts;
|
|
164
|
+
let envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
|
|
165
|
+
if (envFiles.length === 0) {
|
|
166
|
+
envFiles = [createEnvFile(envOpts)];
|
|
167
|
+
}
|
|
168
|
+
const currentEnvs = loadUserEnvs(envOpts);
|
|
169
|
+
for (const k of Object.keys(toWrite)) {
|
|
170
|
+
validateKey(k);
|
|
171
|
+
if (currentEnvs.hasOwnProperty(k)) {
|
|
172
|
+
throw new error_1.FirebaseError(`Attempted to write param-defined key ${k} to .env files, but it was already defined.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const mostSpecificEnv = path.join(functionsSource, envFiles[envFiles.length - 1]);
|
|
176
|
+
(0, utils_1.logBullet)(clc.cyan.bold("functions: ") + `Writing new parameter values to disk: ${mostSpecificEnv}`);
|
|
177
|
+
for (const k of Object.keys(toWrite)) {
|
|
178
|
+
fs.appendFileSync(mostSpecificEnv, formatUserEnvForWrite(k, toWrite[k]));
|
|
179
|
+
}
|
|
150
180
|
}
|
|
151
181
|
exports.writeUserEnvs = writeUserEnvs;
|
|
182
|
+
function createEnvFile(envOpts) {
|
|
183
|
+
const fileToWrite = envOpts.isEmulator
|
|
184
|
+
? FUNCTIONS_EMULATOR_DOTENV
|
|
185
|
+
: `.env.${envOpts.projectAlias || envOpts.projectId}`;
|
|
186
|
+
logger_1.logger.debug(`Creating ${fileToWrite}...`);
|
|
187
|
+
fs.writeFileSync(path.join(envOpts.functionsSource, fileToWrite), "", { flag: "wx" });
|
|
188
|
+
return fileToWrite;
|
|
189
|
+
}
|
|
190
|
+
function formatUserEnvForWrite(key, value) {
|
|
191
|
+
const escapedValue = value.replace(ALL_ESCAPABLE_CHARACTERS_RE, (match) => CHARACTERS_TO_ESCAPE_SEQUENCES[match]);
|
|
192
|
+
if (escapedValue !== value) {
|
|
193
|
+
return `${key}="${escapedValue}"\n`;
|
|
194
|
+
}
|
|
195
|
+
return `${key}=${escapedValue}\n`;
|
|
196
|
+
}
|
|
152
197
|
function loadUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
|
|
153
198
|
var _a;
|
|
154
199
|
const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
|
package/lib/gcp/iam.js
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.testIamPermissions = exports.testResourceIamPermissions = exports.getRole = exports.deleteServiceAccount = exports.createServiceAccountKey = exports.getServiceAccount = exports.createServiceAccount = void 0;
|
|
4
4
|
const api_1 = require("../api");
|
|
5
|
-
const lodash_1 = require("lodash");
|
|
6
5
|
const logger_1 = require("../logger");
|
|
7
6
|
const apiv2_1 = require("../apiv2");
|
|
8
|
-
const
|
|
9
|
-
const apiClient = new apiv2_1.Client({ urlPrefix: api_1.iamOrigin, apiVersion: API_VERSION });
|
|
7
|
+
const apiClient = new apiv2_1.Client({ urlPrefix: api_1.iamOrigin, apiVersion: "v1" });
|
|
10
8
|
async function createServiceAccount(projectId, accountId, description, displayName) {
|
|
11
9
|
const response = await apiClient.post(`/projects/${projectId}/serviceAccounts`, {
|
|
12
10
|
accountId,
|
|
@@ -31,8 +29,8 @@ async function createServiceAccountKey(projectId, serviceAccountName) {
|
|
|
31
29
|
return response.body;
|
|
32
30
|
}
|
|
33
31
|
exports.createServiceAccountKey = createServiceAccountKey;
|
|
34
|
-
function deleteServiceAccount(projectId, accountEmail) {
|
|
35
|
-
|
|
32
|
+
async function deleteServiceAccount(projectId, accountEmail) {
|
|
33
|
+
await apiClient.delete(`/projects/${projectId}/serviceAccounts/${accountEmail}`, {
|
|
36
34
|
resolveOnHTTPError: true,
|
|
37
35
|
});
|
|
38
36
|
}
|
|
@@ -44,25 +42,30 @@ async function getRole(role) {
|
|
|
44
42
|
return response.body;
|
|
45
43
|
}
|
|
46
44
|
exports.getRole = getRole;
|
|
47
|
-
async function testResourceIamPermissions(origin, apiVersion, resourceName, permissions) {
|
|
45
|
+
async function testResourceIamPermissions(origin, apiVersion, resourceName, permissions, quotaUser = "") {
|
|
48
46
|
const localClient = new apiv2_1.Client({ urlPrefix: origin, apiVersion });
|
|
49
47
|
if (process.env.FIREBASE_SKIP_INFORMATIONAL_IAM) {
|
|
50
|
-
logger_1.logger.debug(
|
|
51
|
-
return { allowed: permissions, missing: [], passed: true };
|
|
48
|
+
logger_1.logger.debug(`[iam] skipping informational check of permissions ${JSON.stringify(permissions)} on resource ${resourceName}`);
|
|
49
|
+
return { allowed: Array.from(permissions).sort(), missing: [], passed: true };
|
|
50
|
+
}
|
|
51
|
+
const headers = {};
|
|
52
|
+
if (quotaUser) {
|
|
53
|
+
headers["x-goog-quota-user"] = quotaUser;
|
|
54
|
+
}
|
|
55
|
+
const response = await localClient.post(`/${resourceName}:testIamPermissions`, { permissions }, { headers });
|
|
56
|
+
const allowed = new Set(response.body.permissions || []);
|
|
57
|
+
const missing = new Set(permissions);
|
|
58
|
+
for (const p of allowed) {
|
|
59
|
+
missing.delete(p);
|
|
52
60
|
}
|
|
53
|
-
const response = await localClient.post(`/${resourceName}:testIamPermissions`, {
|
|
54
|
-
permissions,
|
|
55
|
-
});
|
|
56
|
-
const allowed = (response.body.permissions || []).sort();
|
|
57
|
-
const missing = (0, lodash_1.difference)(permissions, allowed);
|
|
58
61
|
return {
|
|
59
|
-
allowed,
|
|
60
|
-
missing,
|
|
61
|
-
passed: missing.
|
|
62
|
+
allowed: Array.from(allowed).sort(),
|
|
63
|
+
missing: Array.from(missing).sort(),
|
|
64
|
+
passed: missing.size === 0,
|
|
62
65
|
};
|
|
63
66
|
}
|
|
64
67
|
exports.testResourceIamPermissions = testResourceIamPermissions;
|
|
65
68
|
async function testIamPermissions(projectId, permissions) {
|
|
66
|
-
return testResourceIamPermissions(api_1.resourceManagerOrigin, "v1", `projects/${projectId}`, permissions);
|
|
69
|
+
return testResourceIamPermissions(api_1.resourceManagerOrigin, "v1", `projects/${projectId}`, permissions, `projects/${projectId}`);
|
|
67
70
|
}
|
|
68
71
|
exports.testIamPermissions = testIamPermissions;
|
package/lib/rc.js
CHANGED
|
@@ -136,15 +136,9 @@ class RC {
|
|
|
136
136
|
requireTarget(project, type, name) {
|
|
137
137
|
const target = this.target(project, type, name);
|
|
138
138
|
if (!target.length) {
|
|
139
|
-
throw new error_1.FirebaseError(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
clc.bold(project) +
|
|
143
|
-
". Configure with:\n\n firebase target:apply " +
|
|
144
|
-
type +
|
|
145
|
-
" " +
|
|
146
|
-
name +
|
|
147
|
-
" <resources...>");
|
|
139
|
+
throw new error_1.FirebaseError(`Deploy target ${clc.bold(name)} not configured for project ${clc.bold(project)}. Configure with:
|
|
140
|
+
|
|
141
|
+
firebase target:apply ${type} ${name} <resources...>`);
|
|
148
142
|
}
|
|
149
143
|
return target;
|
|
150
144
|
}
|
package/lib/rulesDeploy.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RulesDeploy = exports.RulesetServiceType = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
|
-
const fs = require("fs");
|
|
6
|
+
const fs = require("fs-extra");
|
|
7
7
|
const gcp = require("./gcp");
|
|
8
8
|
const logger_1 = require("./logger");
|
|
9
9
|
const error_1 = require("./error");
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.0",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "firebase-tools",
|
|
9
|
-
"version": "11.
|
|
9
|
+
"version": "11.4.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@google-cloud/pubsub": "^3.0.1",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.0",
|
|
4
4
|
"description": "Command-Line Interface for Firebase",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"test:extensions-deploy": "bash ./scripts/extensions-deploy-tests/run.sh",
|
|
31
31
|
"test:extensions-emulator": "bash ./scripts/extensions-emulator-tests/run.sh",
|
|
32
32
|
"test:hosting": "bash ./scripts/hosting-tests/run.sh",
|
|
33
|
+
"test:hosting-rewrites": "bash ./scripts/hosting-tests/rewrites-tests/run.sh",
|
|
33
34
|
"test:triggers-end-to-end": "bash ./scripts/triggers-end-to-end-tests/run.sh",
|
|
34
35
|
"test:storage-deploy": "bash ./scripts/storage-deploy-tests/run.sh",
|
|
35
36
|
"test:storage-emulator-integration": "bash ./scripts/storage-emulator-integration/run.sh"
|