firebase-tools 11.2.0 → 11.3.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/checkValidTargetFilters.js +3 -3
- package/lib/commands/ext-export.js +2 -0
- 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 +2 -2
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +2 -2
- package/lib/deploy/lifecycleHooks.js +1 -1
- package/lib/deploy/storage/prepare.js +1 -1
- package/lib/downloadUtils.js +1 -1
- package/lib/emulator/downloadableEmulators.js +14 -14
- package/lib/emulator/functionsEmulator.js +10 -9
- package/lib/emulator/functionsEmulatorRuntime.js +70 -64
- package/lib/emulator/functionsEmulatorShared.js +7 -5
- package/lib/emulator/functionsRuntimeWorker.js +35 -15
- 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 +5 -1
- package/lib/functionsConfig.js +1 -1
- 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 +11 -1
- package/lib/requireConfig.js +6 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ function targetsHaveNoFilters(...targets) {
|
|
|
20
20
|
return targets.some((t) => !t.includes(":"));
|
|
21
21
|
}
|
|
22
22
|
async function checkValidTargetFilters(options) {
|
|
23
|
-
const only =
|
|
23
|
+
const only = !options.only ? [] : options.only.split(",");
|
|
24
24
|
return new Promise((resolve, reject) => {
|
|
25
25
|
if (!only.length) {
|
|
26
26
|
return resolve();
|
|
@@ -28,10 +28,10 @@ async function checkValidTargetFilters(options) {
|
|
|
28
28
|
if (options.except) {
|
|
29
29
|
return reject(new error_1.FirebaseError("Cannot specify both --only and --except"));
|
|
30
30
|
}
|
|
31
|
-
const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !["hosting", "functions"].includes(t));
|
|
31
|
+
const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !["hosting", "functions", "firestore"].includes(t));
|
|
32
32
|
const targetsForNonFilteredTypes = targetsForTypes(only, ...nonFilteredTypes);
|
|
33
33
|
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
|
|
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
35
|
}
|
|
36
36
|
const targetsForFunctions = targetsForTypes(only, "functions");
|
|
37
37
|
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
|
});
|
|
@@ -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
|
+
}
|
|
@@ -39,7 +39,7 @@ async function prepare(context, options, payload) {
|
|
|
39
39
|
ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
|
|
40
40
|
ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
|
|
41
41
|
ensure.cloudBuildEnabled(projectId),
|
|
42
|
-
ensureApiEnabled.
|
|
42
|
+
ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "artifactregistry"),
|
|
43
43
|
]);
|
|
44
44
|
const firebaseConfig = await functionsConfig.getFirebaseConfig(options);
|
|
45
45
|
context.firebaseConfig = firebaseConfig;
|
|
@@ -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;
|
|
@@ -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
|
}
|
|
@@ -179,7 +179,7 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
|
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
181
|
const endpointId = annotation.name;
|
|
182
|
-
const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", region: regions, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime, serviceAccount: annotation.serviceAccountEmail ||
|
|
182
|
+
const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", region: regions, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime, serviceAccount: annotation.serviceAccountEmail || null }, triggered);
|
|
183
183
|
if (annotation.vpcConnector != null) {
|
|
184
184
|
let maybeId = annotation.vpcConnector;
|
|
185
185
|
if (maybeId && !maybeId.includes("/")) {
|
|
@@ -31,7 +31,7 @@ function runCommand(command, childOptions) {
|
|
|
31
31
|
reject(new Error("Command terminated with signal " + signal));
|
|
32
32
|
}
|
|
33
33
|
else if (code !== 0) {
|
|
34
|
-
reject(new Error("Command terminated with non-zero exit code" + code));
|
|
34
|
+
reject(new Error("Command terminated with non-zero exit code " + code));
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
37
|
resolve();
|
|
@@ -11,7 +11,7 @@ async function default_1(context, options) {
|
|
|
11
11
|
_.set(context, "storage.rules", rulesConfig);
|
|
12
12
|
const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.FIREBASE_STORAGE);
|
|
13
13
|
_.set(context, "storage.rulesDeploy", rulesDeploy);
|
|
14
|
-
if (
|
|
14
|
+
if (typeof rulesConfig === "object" && rulesConfig !== null) {
|
|
15
15
|
const defaultBucket = await gcp.storage.getDefaultBucket(options.project);
|
|
16
16
|
rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })];
|
|
17
17
|
_.set(context, "storage.rules", rulesConfig);
|
package/lib/downloadUtils.js
CHANGED
|
@@ -20,7 +20,7 @@ async function downloadToTmp(remoteUrl) {
|
|
|
20
20
|
resolveOnHTTPError: true,
|
|
21
21
|
});
|
|
22
22
|
if (res.status !== 200) {
|
|
23
|
-
throw new error_1.FirebaseError(`download failed, status ${res.status}
|
|
23
|
+
throw new error_1.FirebaseError(`download failed, status ${res.status}: ${await res.response.text()}`);
|
|
24
24
|
}
|
|
25
25
|
const total = parseInt(res.response.headers.get("content-length") || "0", 10);
|
|
26
26
|
const totalMb = Math.ceil(total / 1000000);
|
|
@@ -29,13 +29,13 @@ exports.DownloadDetails = {
|
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
31
|
firestore: {
|
|
32
|
-
downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.
|
|
33
|
-
version: "1.14.
|
|
32
|
+
downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.4.jar"),
|
|
33
|
+
version: "1.14.4",
|
|
34
34
|
opts: {
|
|
35
35
|
cacheDir: CACHE_DIR,
|
|
36
|
-
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.
|
|
37
|
-
expectedSize:
|
|
38
|
-
expectedChecksum: "
|
|
36
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.4.jar",
|
|
37
|
+
expectedSize: 61017177,
|
|
38
|
+
expectedChecksum: "953d10e73798484aa0b84c45005faadb",
|
|
39
39
|
namePrefix: "cloud-firestore-emulator",
|
|
40
40
|
},
|
|
41
41
|
},
|
|
@@ -55,7 +55,7 @@ exports.DownloadDetails = {
|
|
|
55
55
|
version: "SNAPSHOT",
|
|
56
56
|
downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
|
|
57
57
|
unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
|
|
58
|
-
binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.
|
|
58
|
+
binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server", "server.js"),
|
|
59
59
|
opts: {
|
|
60
60
|
cacheDir: CACHE_DIR,
|
|
61
61
|
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
|
|
@@ -67,15 +67,15 @@ exports.DownloadDetails = {
|
|
|
67
67
|
},
|
|
68
68
|
}
|
|
69
69
|
: {
|
|
70
|
-
version: "1.
|
|
71
|
-
downloadPath: path.join(CACHE_DIR, "ui-v1.
|
|
72
|
-
unzipDir: path.join(CACHE_DIR, "ui-v1.
|
|
73
|
-
binaryPath: path.join(CACHE_DIR, "ui-v1.
|
|
70
|
+
version: "1.8.1",
|
|
71
|
+
downloadPath: path.join(CACHE_DIR, "ui-v1.8.1.zip"),
|
|
72
|
+
unzipDir: path.join(CACHE_DIR, "ui-v1.8.1"),
|
|
73
|
+
binaryPath: path.join(CACHE_DIR, "ui-v1.8.1", "server", "server.js"),
|
|
74
74
|
opts: {
|
|
75
75
|
cacheDir: CACHE_DIR,
|
|
76
|
-
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.
|
|
77
|
-
expectedSize:
|
|
78
|
-
expectedChecksum: "
|
|
76
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.8.1.zip",
|
|
77
|
+
expectedSize: 3056552,
|
|
78
|
+
expectedChecksum: "92590fdda20f9883588438d9551111b5",
|
|
79
79
|
namePrefix: "ui",
|
|
80
80
|
},
|
|
81
81
|
},
|
|
@@ -164,7 +164,7 @@ const Commands = {
|
|
|
164
164
|
},
|
|
165
165
|
ui: {
|
|
166
166
|
binary: "node",
|
|
167
|
-
args: [
|
|
167
|
+
args: [getExecPath(types_1.Emulators.UI)],
|
|
168
168
|
optionalArgs: [],
|
|
169
169
|
joinArgs: false,
|
|
170
170
|
},
|
|
@@ -272,8 +272,13 @@ class FunctionsEmulator {
|
|
|
272
272
|
await runtimeDelegate.build();
|
|
273
273
|
logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
|
|
274
274
|
const environment = Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env);
|
|
275
|
+
const userEnvOpt = {
|
|
276
|
+
functionsSource: emulatableBackend.functionsDir,
|
|
277
|
+
projectId: this.args.projectId,
|
|
278
|
+
projectAlias: this.args.projectAlias,
|
|
279
|
+
};
|
|
275
280
|
const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
|
|
276
|
-
const discoveredBackend = (0, build_1.resolveBackend)(discoveredBuild, environment);
|
|
281
|
+
const discoveredBackend = await (0, build_1.resolveBackend)(discoveredBuild, userEnvOpt, environment);
|
|
277
282
|
const endpoints = backend.allEndpoints(discoveredBackend);
|
|
278
283
|
(0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
|
|
279
284
|
for (const e of endpoints) {
|
|
@@ -778,9 +783,10 @@ class FunctionsEmulator {
|
|
|
778
783
|
}
|
|
779
784
|
const runtimeEnv = this.getRuntimeEnvs(backend, trigger);
|
|
780
785
|
const secretEnvs = await this.resolveSecretEnvs(backend, trigger);
|
|
786
|
+
const socketPath = (0, functionsEmulatorShared_1.getTemporarySocketPath)();
|
|
781
787
|
const childProcess = spawn(opts.nodeBinary, args, {
|
|
782
788
|
cwd: backend.functionsDir,
|
|
783
|
-
env: Object.assign(Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), runtimeEnv), secretEnvs),
|
|
789
|
+
env: Object.assign(Object.assign(Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), runtimeEnv), secretEnvs), { PORT: socketPath }),
|
|
784
790
|
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
785
791
|
});
|
|
786
792
|
if (!childProcess.stderr) {
|
|
@@ -812,6 +818,7 @@ class FunctionsEmulator {
|
|
|
812
818
|
}),
|
|
813
819
|
events: emitter,
|
|
814
820
|
cwd: backend.functionsDir,
|
|
821
|
+
socketPath,
|
|
815
822
|
shutdown: () => {
|
|
816
823
|
childProcess.kill();
|
|
817
824
|
},
|
|
@@ -950,12 +957,6 @@ class FunctionsEmulator {
|
|
|
950
957
|
await worker.waitForSocketReady();
|
|
951
958
|
void (0, track_1.track)(EVENT_INVOKE, "https");
|
|
952
959
|
this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
|
|
953
|
-
if (!worker.lastArgs) {
|
|
954
|
-
throw new error_1.FirebaseError("Cannot execute on a worker with no arguments");
|
|
955
|
-
}
|
|
956
|
-
if (!worker.lastArgs.frb.socketPath) {
|
|
957
|
-
throw new error_1.FirebaseError(`Cannot execute on a worker without a socketPath: ${JSON.stringify(worker.lastArgs)}`);
|
|
958
|
-
}
|
|
959
960
|
const url = new url_1.URL(`${req.protocol}://${req.hostname}${req.url}`);
|
|
960
961
|
const path = `${url.pathname}${url.search}`.replace(new RegExp(`\/${this.args.projectId}\/[^\/]*\/${triggerName}\/?`), "/");
|
|
961
962
|
this.logger.log("DEBUG", `[functions] Got req.url=${req.url}, mapping to path=${path}`);
|
|
@@ -963,7 +964,7 @@ class FunctionsEmulator {
|
|
|
963
964
|
method,
|
|
964
965
|
path,
|
|
965
966
|
headers: req.headers,
|
|
966
|
-
socketPath: worker.
|
|
967
|
+
socketPath: worker.runtime.socketPath,
|
|
967
968
|
}, (runtimeRes) => {
|
|
968
969
|
function forwardStatusAndHeaders() {
|
|
969
970
|
res.status(runtimeRes.statusCode || 200);
|