firebase-tools 11.2.1 → 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/commands/ext-export.js +2 -0
- package/lib/database/rulesConfig.js +35 -8
- package/lib/deploy/extensions/planner.js +1 -0
- package/lib/deploy/extensions/prepare.js +18 -1
- package/lib/deploy/extensions/release.js +4 -0
- package/lib/deploy/functions/build.js +43 -52
- package/lib/deploy/functions/params.js +189 -0
- package/lib/deploy/functions/prepare.js +1 -1
- package/lib/deploy/functions/release/index.js +4 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +3 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +1 -1
- package/lib/deploy/hosting/convertConfig.js +76 -16
- package/lib/deploy/index.js +1 -1
- package/lib/deploy/lifecycleHooks.js +1 -1
- package/lib/deploy/storage/prepare.js +29 -6
- package/lib/downloadUtils.js +1 -1
- package/lib/emulator/controller.js +0 -1
- package/lib/emulator/downloadableEmulators.js +9 -9
- package/lib/emulator/functionsEmulator.js +9 -1
- package/lib/emulator/functionsEmulatorRuntime.js +68 -58
- package/lib/emulator/functionsRuntimeWorker.js +35 -9
- package/lib/emulator/storage/apis/gcloud.js +1 -1
- package/lib/extensions/etags.js +28 -0
- package/lib/extensions/warnings.js +7 -1
- package/lib/functions/env.js +51 -2
- package/lib/functionsConfig.js +1 -1
- package/lib/gcp/iam.js +20 -17
- package/lib/gcp/rules.js +2 -3
- package/lib/gcp/runtimeconfig.js +1 -1
- package/lib/hosting/api.js +9 -11
- package/lib/hosting/expireUtils.js +2 -2
- package/lib/management/projects.js +6 -7
- package/lib/profileReport.js +16 -14
- package/lib/rc.js +14 -10
- package/lib/requireConfig.js +6 -6
- 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 and
|
|
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 &&
|
|
@@ -4,6 +4,7 @@ exports.command = void 0;
|
|
|
4
4
|
const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
|
|
5
5
|
const command_1 = require("../command");
|
|
6
6
|
const planner = require("../deploy/extensions/planner");
|
|
7
|
+
const etags_1 = require("../extensions/etags");
|
|
7
8
|
const export_1 = require("../extensions/export");
|
|
8
9
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
9
10
|
const manifest = require("../extensions/manifest");
|
|
@@ -63,4 +64,5 @@ exports.command = new command_1.Command("ext:export")
|
|
|
63
64
|
nonInteractive: options.nonInteractive,
|
|
64
65
|
force: options.force,
|
|
65
66
|
}, true);
|
|
67
|
+
(0, etags_1.saveEtags)(options.rc, projectId, have);
|
|
66
68
|
});
|
|
@@ -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;
|
|
@@ -13,6 +13,7 @@ const extensionsHelper_1 = require("../../extensions/extensionsHelper");
|
|
|
13
13
|
const secretsUtils_1 = require("../../extensions/secretsUtils");
|
|
14
14
|
const secrets_1 = require("./secrets");
|
|
15
15
|
const warnings_1 = require("../../extensions/warnings");
|
|
16
|
+
const etags_1 = require("../../extensions/etags");
|
|
16
17
|
async function prepare(context, options, payload) {
|
|
17
18
|
var _a;
|
|
18
19
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
@@ -28,6 +29,22 @@ async function prepare(context, options, payload) {
|
|
|
28
29
|
projectDir: options.config.projectDir,
|
|
29
30
|
extensions: options.config.get("extensions"),
|
|
30
31
|
});
|
|
32
|
+
const etagsChanged = (0, etags_1.detectEtagChanges)(options.rc, projectId, context.have);
|
|
33
|
+
if (etagsChanged.length) {
|
|
34
|
+
(0, warnings_1.outOfBandChangesWarning)(etagsChanged);
|
|
35
|
+
if (!options.force && options.nonInteractive) {
|
|
36
|
+
throw new error_1.FirebaseError("Pass the --force flag to overwrite out of band changes in non-interactive mode");
|
|
37
|
+
}
|
|
38
|
+
else if (!options.force &&
|
|
39
|
+
!options.nonInteractive &&
|
|
40
|
+
!(await prompt.promptOnce({
|
|
41
|
+
type: "confirm",
|
|
42
|
+
message: `Do you wish to continue deploying these extension instances?`,
|
|
43
|
+
default: false,
|
|
44
|
+
}))) {
|
|
45
|
+
throw new error_1.FirebaseError("Deployment cancelled");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
31
48
|
const usingSecrets = await Promise.all((_a = context.want) === null || _a === void 0 ? void 0 : _a.map(secrets_1.checkSpecForSecrets));
|
|
32
49
|
if (usingSecrets.some((i) => i)) {
|
|
33
50
|
await (0, secretsUtils_1.ensureSecretManagerApiEnabled)(options);
|
|
@@ -44,7 +61,7 @@ async function prepare(context, options, payload) {
|
|
|
44
61
|
!options.nonInteractive &&
|
|
45
62
|
!(await prompt.promptOnce({
|
|
46
63
|
type: "confirm",
|
|
47
|
-
message: `Do you wish to continue deploying these
|
|
64
|
+
message: `Do you wish to continue deploying these extension instances?`,
|
|
48
65
|
default: true,
|
|
49
66
|
}))) {
|
|
50
67
|
throw new error_1.FirebaseError("Deployment cancelled");
|
|
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.release = void 0;
|
|
4
4
|
const queue_1 = require("../../throttler/queue");
|
|
5
5
|
const tasks = require("./tasks");
|
|
6
|
+
const planner = require("./planner");
|
|
6
7
|
const error_1 = require("../../error");
|
|
7
8
|
const errors_1 = require("./errors");
|
|
8
9
|
const projectUtils_1 = require("../../projectUtils");
|
|
10
|
+
const etags_1 = require("../../extensions/etags");
|
|
9
11
|
async function release(context, options, payload) {
|
|
10
12
|
var _a, _b, _c, _d;
|
|
11
13
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
@@ -35,6 +37,8 @@ async function release(context, options, payload) {
|
|
|
35
37
|
deploymentQueue.process();
|
|
36
38
|
deploymentQueue.close();
|
|
37
39
|
await deploymentPromise;
|
|
40
|
+
const newHave = await planner.have(projectId);
|
|
41
|
+
(0, etags_1.saveEtags)(options.rc, projectId, newHave);
|
|
38
42
|
if (errorHandler.hasErrors()) {
|
|
39
43
|
errorHandler.print();
|
|
40
44
|
throw new error_1.FirebaseError(`Extensions deployment failed.`);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveBackend = exports.of = exports.empty = void 0;
|
|
3
|
+
exports.toBackend = exports.resolveBackend = exports.of = exports.empty = void 0;
|
|
4
4
|
const backend = require("./backend");
|
|
5
5
|
const proto = require("../../gcp/proto");
|
|
6
6
|
const api = require("../../.../../api");
|
|
7
|
+
const params = require("./params");
|
|
8
|
+
const previews_1 = require("../../previews");
|
|
7
9
|
const error_1 = require("../../error");
|
|
8
10
|
const functional_1 = require("../../functional");
|
|
9
11
|
function empty() {
|
|
@@ -20,45 +22,19 @@ function of(endpoints) {
|
|
|
20
22
|
return build;
|
|
21
23
|
}
|
|
22
24
|
exports.of = of;
|
|
23
|
-
function resolveInt(from) {
|
|
24
|
-
if (from == null) {
|
|
25
|
-
return 0;
|
|
26
|
-
}
|
|
27
|
-
else if (typeof from === "string") {
|
|
28
|
-
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
29
|
-
}
|
|
30
|
-
return from;
|
|
31
|
-
}
|
|
32
|
-
function resolveString(from) {
|
|
33
|
-
if (from == null) {
|
|
34
|
-
return "";
|
|
35
|
-
}
|
|
36
|
-
else if (from.includes("{{") && from.includes("}}")) {
|
|
37
|
-
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
38
|
-
}
|
|
39
|
-
return from;
|
|
40
|
-
}
|
|
41
|
-
function resolveBoolean(from) {
|
|
42
|
-
if (from == null) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
else if (typeof from === "string") {
|
|
46
|
-
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
47
|
-
}
|
|
48
|
-
return from;
|
|
49
|
-
}
|
|
50
25
|
function isMemoryOption(value) {
|
|
51
26
|
return value == null || [128, 256, 512, 1024, 2048, 4096, 8192].includes(value);
|
|
52
27
|
}
|
|
53
|
-
function resolveBackend(build, userEnvs) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expectedEnv +
|
|
59
|
-
" but it was not present in the user dotenv files or Cloud Secret Manager");
|
|
60
|
-
}
|
|
28
|
+
async function resolveBackend(build, userEnvOpt, userEnvs) {
|
|
29
|
+
const projectId = userEnvOpt.projectId;
|
|
30
|
+
let paramValues = {};
|
|
31
|
+
if (previews_1.previews.functionsparams) {
|
|
32
|
+
paramValues = await params.resolveParams(build.params, projectId, userEnvs);
|
|
61
33
|
}
|
|
34
|
+
return toBackend(build, paramValues);
|
|
35
|
+
}
|
|
36
|
+
exports.resolveBackend = resolveBackend;
|
|
37
|
+
function toBackend(build, paramValues) {
|
|
62
38
|
const bkEndpoints = [];
|
|
63
39
|
for (const endpointId of Object.keys(build.endpoints)) {
|
|
64
40
|
const bdEndpoint = build.endpoints[endpointId];
|
|
@@ -67,7 +43,7 @@ function resolveBackend(build, userEnvs) {
|
|
|
67
43
|
regions = [api.functionsDefaultRegion];
|
|
68
44
|
}
|
|
69
45
|
for (const region of regions) {
|
|
70
|
-
const trigger = discoverTrigger(bdEndpoint);
|
|
46
|
+
const trigger = discoverTrigger(bdEndpoint, paramValues);
|
|
71
47
|
if (typeof bdEndpoint.platform === "undefined") {
|
|
72
48
|
throw new error_1.FirebaseError("platform can't be undefined");
|
|
73
49
|
}
|
|
@@ -76,21 +52,25 @@ function resolveBackend(build, userEnvs) {
|
|
|
76
52
|
}
|
|
77
53
|
let timeout;
|
|
78
54
|
if (bdEndpoint.timeoutSeconds) {
|
|
79
|
-
timeout = resolveInt(bdEndpoint.timeoutSeconds);
|
|
55
|
+
timeout = params.resolveInt(bdEndpoint.timeoutSeconds, paramValues);
|
|
80
56
|
}
|
|
81
57
|
else {
|
|
82
58
|
timeout = 60;
|
|
83
59
|
}
|
|
84
60
|
const bkEndpoint = Object.assign({ id: endpointId, project: bdEndpoint.project, region: region, entryPoint: bdEndpoint.entryPoint, platform: bdEndpoint.platform, runtime: bdEndpoint.runtime, timeoutSeconds: timeout }, trigger);
|
|
85
|
-
proto.renameIfPresent(bkEndpoint, bdEndpoint, "maxInstances", "maxInstances",
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
proto.renameIfPresent(bkEndpoint, bdEndpoint, "maxInstances", "maxInstances", (from) => {
|
|
62
|
+
return params.resolveInt(from, paramValues);
|
|
63
|
+
});
|
|
64
|
+
proto.renameIfPresent(bkEndpoint, bdEndpoint, "minInstances", "minInstances", (from) => {
|
|
65
|
+
return params.resolveInt(from, paramValues);
|
|
66
|
+
});
|
|
67
|
+
proto.renameIfPresent(bkEndpoint, bdEndpoint, "concurrency", "concurrency", (from) => {
|
|
68
|
+
return params.resolveInt(from, paramValues);
|
|
69
|
+
});
|
|
88
70
|
proto.copyIfPresent(bkEndpoint, bdEndpoint, "ingressSettings", "availableMemoryMb", "environmentVariables", "labels");
|
|
89
71
|
proto.copyIfPresent(bkEndpoint, bdEndpoint, "secretEnvironmentVariables");
|
|
90
72
|
if (bdEndpoint.vpc) {
|
|
91
|
-
bkEndpoint.vpc = {
|
|
92
|
-
connector: resolveString(bdEndpoint.vpc.connector).replace("$REGION", region),
|
|
93
|
-
};
|
|
73
|
+
bkEndpoint.vpc = { connector: params.resolveString(bdEndpoint.vpc.connector, paramValues) };
|
|
94
74
|
proto.copyIfPresent(bkEndpoint.vpc, bdEndpoint.vpc, "egressSettings");
|
|
95
75
|
}
|
|
96
76
|
proto.renameIfPresent(bkEndpoint, bdEndpoint, "serviceAccountEmail", "serviceAccount");
|
|
@@ -104,13 +84,16 @@ function resolveBackend(build, userEnvs) {
|
|
|
104
84
|
bkend.requiredAPIs = build.requiredAPIs;
|
|
105
85
|
return bkend;
|
|
106
86
|
}
|
|
107
|
-
exports.
|
|
108
|
-
function discoverTrigger(endpoint) {
|
|
87
|
+
exports.toBackend = toBackend;
|
|
88
|
+
function discoverTrigger(endpoint, paramValues) {
|
|
89
|
+
const resolveInt = (from) => params.resolveInt(from, paramValues);
|
|
90
|
+
const resolveString = (from) => params.resolveString(from, paramValues);
|
|
91
|
+
const resolveBoolean = (from) => params.resolveBoolean(from, paramValues);
|
|
109
92
|
let trigger;
|
|
110
93
|
if ("httpsTrigger" in endpoint) {
|
|
111
94
|
const bkHttps = {};
|
|
112
95
|
if (endpoint.httpsTrigger.invoker) {
|
|
113
|
-
bkHttps.invoker =
|
|
96
|
+
bkHttps.invoker = endpoint.httpsTrigger.invoker;
|
|
114
97
|
}
|
|
115
98
|
trigger = { httpsTrigger: bkHttps };
|
|
116
99
|
}
|
|
@@ -122,22 +105,30 @@ function discoverTrigger(endpoint) {
|
|
|
122
105
|
}
|
|
123
106
|
else if ("eventTrigger" in endpoint) {
|
|
124
107
|
const bkEventFilters = {};
|
|
125
|
-
for (const key
|
|
126
|
-
|
|
127
|
-
bkEventFilters[key] = resolveString(endpoint.eventTrigger.eventFilters[key]);
|
|
128
|
-
}
|
|
108
|
+
for (const [key, value] of Object.entries(endpoint.eventTrigger.eventFilters)) {
|
|
109
|
+
bkEventFilters[key] = params.resolveString(value, paramValues);
|
|
129
110
|
}
|
|
130
111
|
const bkEvent = {
|
|
131
112
|
eventType: endpoint.eventTrigger.eventType,
|
|
132
113
|
eventFilters: bkEventFilters,
|
|
133
|
-
retry: resolveBoolean(endpoint.eventTrigger.retry),
|
|
114
|
+
retry: resolveBoolean(endpoint.eventTrigger.retry || false),
|
|
134
115
|
};
|
|
116
|
+
if (endpoint.eventTrigger.eventFilterPathPatterns) {
|
|
117
|
+
const bkEventFiltersPathPatterns = {};
|
|
118
|
+
for (const [key, value] of Object.entries(endpoint.eventTrigger.eventFilterPathPatterns)) {
|
|
119
|
+
bkEventFiltersPathPatterns[key] = params.resolveString(value, paramValues);
|
|
120
|
+
}
|
|
121
|
+
bkEvent.eventFilterPathPatterns = bkEventFiltersPathPatterns;
|
|
122
|
+
}
|
|
135
123
|
if (endpoint.eventTrigger.serviceAccount) {
|
|
136
124
|
bkEvent.serviceAccountEmail = endpoint.eventTrigger.serviceAccount;
|
|
137
125
|
}
|
|
138
126
|
if (endpoint.eventTrigger.region) {
|
|
139
127
|
bkEvent.region = resolveString(endpoint.eventTrigger.region);
|
|
140
128
|
}
|
|
129
|
+
if (endpoint.eventTrigger.channel) {
|
|
130
|
+
bkEvent.channel = endpoint.eventTrigger.channel;
|
|
131
|
+
}
|
|
141
132
|
trigger = { eventTrigger: bkEvent };
|
|
142
133
|
}
|
|
143
134
|
else if ("scheduleTrigger" in endpoint) {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveParams = exports.resolveBoolean = exports.resolveString = exports.resolveInt = void 0;
|
|
4
|
+
const logger_1 = require("../../logger");
|
|
5
|
+
const error_1 = require("../../error");
|
|
6
|
+
const prompt_1 = require("../../prompt");
|
|
7
|
+
const functional_1 = require("../../functional");
|
|
8
|
+
function isCEL(expr) {
|
|
9
|
+
return typeof expr === "string" && expr.includes("{{") && expr.includes("}}");
|
|
10
|
+
}
|
|
11
|
+
function dependenciesCEL(expr) {
|
|
12
|
+
const deps = [];
|
|
13
|
+
const paramCapture = /{{ params\.(\w+) }}/g;
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = paramCapture.exec(expr)) != null) {
|
|
16
|
+
deps.push(match[1]);
|
|
17
|
+
}
|
|
18
|
+
return deps;
|
|
19
|
+
}
|
|
20
|
+
function resolveInt(from, paramValues) {
|
|
21
|
+
if (typeof from === "number") {
|
|
22
|
+
return from;
|
|
23
|
+
}
|
|
24
|
+
const match = /\A{{ params\.(\w+) }}\z/.exec(from);
|
|
25
|
+
if (!match) {
|
|
26
|
+
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
27
|
+
}
|
|
28
|
+
const referencedParamValue = paramValues[match[1]];
|
|
29
|
+
if (typeof referencedParamValue !== "number") {
|
|
30
|
+
throw new error_1.FirebaseError("Referenced numeric parameter '" +
|
|
31
|
+
match +
|
|
32
|
+
"' resolved to non-number value " +
|
|
33
|
+
referencedParamValue);
|
|
34
|
+
}
|
|
35
|
+
return referencedParamValue;
|
|
36
|
+
}
|
|
37
|
+
exports.resolveInt = resolveInt;
|
|
38
|
+
function resolveString(from, paramValues) {
|
|
39
|
+
if (!isCEL(from)) {
|
|
40
|
+
return from;
|
|
41
|
+
}
|
|
42
|
+
let output = from;
|
|
43
|
+
const paramCapture = /{{ params\.(\w+) }}/g;
|
|
44
|
+
let match;
|
|
45
|
+
while ((match = paramCapture.exec(from)) != null) {
|
|
46
|
+
const referencedParamValue = paramValues[match[1]];
|
|
47
|
+
if (typeof referencedParamValue !== "string") {
|
|
48
|
+
throw new error_1.FirebaseError("Referenced string parameter '" +
|
|
49
|
+
match[1] +
|
|
50
|
+
"' resolved to non-string value " +
|
|
51
|
+
referencedParamValue);
|
|
52
|
+
}
|
|
53
|
+
output = output.replace(`{{ params.${match[1]} }}`, referencedParamValue);
|
|
54
|
+
}
|
|
55
|
+
if (isCEL(output)) {
|
|
56
|
+
throw new error_1.FirebaseError("CEL evaluation of non-identity expression '" + from + "' not yet supported");
|
|
57
|
+
}
|
|
58
|
+
return output;
|
|
59
|
+
}
|
|
60
|
+
exports.resolveString = resolveString;
|
|
61
|
+
function resolveBoolean(from, paramValues) {
|
|
62
|
+
if (typeof from === "string" && /{{ params\.(\w+) }}/.test(from)) {
|
|
63
|
+
const match = /{{ params\.(\w+) }}/.exec(from);
|
|
64
|
+
const referencedParamValue = paramValues[match[1]];
|
|
65
|
+
if (typeof referencedParamValue !== "boolean") {
|
|
66
|
+
throw new error_1.FirebaseError("Referenced boolean parameter '" +
|
|
67
|
+
match +
|
|
68
|
+
"' resolved to non-boolean value " +
|
|
69
|
+
referencedParamValue);
|
|
70
|
+
}
|
|
71
|
+
return referencedParamValue;
|
|
72
|
+
}
|
|
73
|
+
else if (typeof from === "string") {
|
|
74
|
+
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
75
|
+
}
|
|
76
|
+
return from;
|
|
77
|
+
}
|
|
78
|
+
exports.resolveBoolean = resolveBoolean;
|
|
79
|
+
function resolveDefaultCEL(type, expr, currentEnv) {
|
|
80
|
+
const deps = dependenciesCEL(expr);
|
|
81
|
+
const allDepsFound = deps.every((dep) => !!currentEnv[dep]);
|
|
82
|
+
if (!allDepsFound) {
|
|
83
|
+
throw new error_1.FirebaseError("Build specified parameter with un-resolvable default value " +
|
|
84
|
+
expr +
|
|
85
|
+
"; dependencies missing.");
|
|
86
|
+
}
|
|
87
|
+
switch (type) {
|
|
88
|
+
case "string":
|
|
89
|
+
return resolveString(expr, currentEnv);
|
|
90
|
+
case "int":
|
|
91
|
+
return resolveInt(expr, currentEnv);
|
|
92
|
+
default:
|
|
93
|
+
throw new error_1.FirebaseError("Build specified parameter with default " + expr + " of unsupported type");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function canSatisfyParam(param, value) {
|
|
97
|
+
if (param.type === "string") {
|
|
98
|
+
return typeof value === "string";
|
|
99
|
+
}
|
|
100
|
+
else if (param.type === "int") {
|
|
101
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
102
|
+
}
|
|
103
|
+
(0, functional_1.assertExhaustive)(param);
|
|
104
|
+
}
|
|
105
|
+
async function resolveParams(params, projectId, userEnvs) {
|
|
106
|
+
const paramValues = {};
|
|
107
|
+
for (const param of params.filter((param) => userEnvs.hasOwnProperty(param.param))) {
|
|
108
|
+
if (!canSatisfyParam(param, userEnvs[param.param])) {
|
|
109
|
+
throw new error_1.FirebaseError("Parameter " +
|
|
110
|
+
param.param +
|
|
111
|
+
" resolved to value from dotenv files " +
|
|
112
|
+
userEnvs[param.param] +
|
|
113
|
+
" of wrong type");
|
|
114
|
+
}
|
|
115
|
+
paramValues[param.param] = userEnvs[param.param];
|
|
116
|
+
}
|
|
117
|
+
for (const param of params.filter((param) => !userEnvs.hasOwnProperty(param.param))) {
|
|
118
|
+
let paramDefault = param.default;
|
|
119
|
+
if (paramDefault && isCEL(paramDefault)) {
|
|
120
|
+
paramDefault = resolveDefaultCEL(param.type, paramDefault, paramValues);
|
|
121
|
+
}
|
|
122
|
+
if (paramDefault && !canSatisfyParam(param, paramDefault)) {
|
|
123
|
+
throw new error_1.FirebaseError("Parameter " + param.param + " has default value " + paramDefault + " of wrong type");
|
|
124
|
+
}
|
|
125
|
+
paramValues[param.param] = await promptParam(param, paramDefault);
|
|
126
|
+
}
|
|
127
|
+
return paramValues;
|
|
128
|
+
}
|
|
129
|
+
exports.resolveParams = resolveParams;
|
|
130
|
+
async function promptParam(param, resolvedDefault) {
|
|
131
|
+
if (param.type === "string") {
|
|
132
|
+
return promptStringParam(param, resolvedDefault);
|
|
133
|
+
}
|
|
134
|
+
else if (param.type === "int") {
|
|
135
|
+
return promptIntParam(param, resolvedDefault);
|
|
136
|
+
}
|
|
137
|
+
(0, functional_1.assertExhaustive)(param);
|
|
138
|
+
}
|
|
139
|
+
async function promptStringParam(param, resolvedDefault) {
|
|
140
|
+
if (!param.input) {
|
|
141
|
+
const defaultToText = { type: "text", text: {} };
|
|
142
|
+
param.input = defaultToText;
|
|
143
|
+
}
|
|
144
|
+
switch (param.input.type) {
|
|
145
|
+
case "select":
|
|
146
|
+
throw new error_1.FirebaseError("Build specified string parameter " + param.param + " with unsupported input type 'select'");
|
|
147
|
+
case "text":
|
|
148
|
+
default:
|
|
149
|
+
let prompt = `Enter a value for ${param.label || param.param}`;
|
|
150
|
+
if (param.description) {
|
|
151
|
+
prompt += ` \n(${param.description})`;
|
|
152
|
+
}
|
|
153
|
+
return await (0, prompt_1.promptOnce)({
|
|
154
|
+
name: param.param,
|
|
155
|
+
type: "input",
|
|
156
|
+
default: resolvedDefault,
|
|
157
|
+
message: prompt,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function promptIntParam(param, resolvedDefault) {
|
|
162
|
+
if (!param.input) {
|
|
163
|
+
const defaultToText = { type: "text", text: {} };
|
|
164
|
+
param.input = defaultToText;
|
|
165
|
+
}
|
|
166
|
+
switch (param.input.type) {
|
|
167
|
+
case "select":
|
|
168
|
+
throw new error_1.FirebaseError("Build specified int parameter " + param.param + " with unsupported input type 'select'");
|
|
169
|
+
case "text":
|
|
170
|
+
default:
|
|
171
|
+
let prompt = `Enter a value for ${param.label || param.param}`;
|
|
172
|
+
if (param.description) {
|
|
173
|
+
prompt += ` \n(${param.description})`;
|
|
174
|
+
}
|
|
175
|
+
let res;
|
|
176
|
+
while (true) {
|
|
177
|
+
res = await (0, prompt_1.promptOnce)({
|
|
178
|
+
name: param.param,
|
|
179
|
+
type: "number",
|
|
180
|
+
default: resolvedDefault,
|
|
181
|
+
message: prompt,
|
|
182
|
+
});
|
|
183
|
+
if (Number.isInteger(res)) {
|
|
184
|
+
return res;
|
|
185
|
+
}
|
|
186
|
+
logger_1.logger.error(`${param.label || param.param} must be an integer; retrying...`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -78,7 +78,7 @@ async function prepare(context, options, payload) {
|
|
|
78
78
|
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
79
79
|
const envs = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
|
|
80
80
|
const wantBuild = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs);
|
|
81
|
-
const wantBackend = build.resolveBackend(wantBuild, userEnvs);
|
|
81
|
+
const wantBackend = await build.resolveBackend(wantBuild, userEnvOpt, userEnvs);
|
|
82
82
|
wantBackend.environmentVariables = envs;
|
|
83
83
|
for (const endpoint of backend.allEndpoints(wantBackend)) {
|
|
84
84
|
endpoint.environmentVariables = wantBackend.environmentVariables;
|
|
@@ -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
|
});
|
|
@@ -126,7 +126,7 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
|
|
|
126
126
|
logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
|
|
127
127
|
}
|
|
128
128
|
if (annotation.httpsTrigger.invoker) {
|
|
129
|
-
trigger.invoker = annotation.httpsTrigger.invoker
|
|
129
|
+
trigger.invoker = annotation.httpsTrigger.invoker;
|
|
130
130
|
}
|
|
131
131
|
triggered = { httpsTrigger: trigger };
|
|
132
132
|
}
|