firebase-tools 11.18.0 → 11.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/lib/api.js +3 -2
- package/lib/bin/firebase.js +0 -0
- package/lib/commands/functions-delete.js +1 -1
- package/lib/commands/index.js +5 -0
- package/lib/commands/internaltesting-functions-discover.js +25 -0
- package/lib/deploy/extensions/prepare.js +6 -1
- package/lib/deploy/extensions/v2FunctionHelper.js +53 -0
- package/lib/deploy/functions/build.js +17 -2
- package/lib/deploy/functions/cel.js +90 -13
- package/lib/deploy/functions/params.js +141 -6
- package/lib/deploy/functions/prepare.js +40 -25
- package/lib/deploy/functions/release/fabricator.js +27 -1
- package/lib/deploy/functions/runtimes/discovery/parsing.js +7 -1
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +1 -1
- package/lib/deploy/functions/runtimes/node/index.js +2 -2
- package/lib/deploy/functions/runtimes/node/versioning.js +30 -14
- package/lib/emulator/auth/operations.js +1 -1
- package/lib/emulator/commandUtils.js +2 -1
- package/lib/emulator/downloadableEmulators.js +6 -6
- package/lib/emulator/env.js +29 -27
- package/lib/emulator/extensionsEmulator.js +14 -9
- package/lib/emulator/functionsEmulator.js +16 -8
- package/lib/emulator/pubsubEmulator.js +13 -1
- package/lib/emulator/storage/rules/runtime.js +2 -2
- package/lib/emulator/workQueue.js +11 -6
- package/lib/experiments.js +6 -0
- package/lib/extensions/emulator/triggerHelper.js +12 -2
- package/lib/frameworks/index.js +7 -1
- package/lib/frameworks/next/constants.js +10 -0
- package/lib/frameworks/next/index.js +146 -146
- package/lib/frameworks/next/utils.js +65 -7
- package/lib/gcp/eventarc.js +42 -0
- package/lib/serve/hosting.js +5 -5
- package/npm-shrinkwrap.json +463 -737
- package/package.json +2 -5
package/lib/api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.setScopes = void 0;
|
|
3
|
+
exports.githubClientSecret = exports.githubClientId = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.deployOrigin = exports.consoleOrigin = exports.authOrigin = exports.appengineOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
|
|
4
|
+
exports.setScopes = exports.getScopes = void 0;
|
|
5
5
|
const constants_1 = require("./emulator/constants");
|
|
6
6
|
const logger_1 = require("./logger");
|
|
7
7
|
const scopes = require("./scopes");
|
|
@@ -22,6 +22,7 @@ exports.consoleOrigin = utils.envOverride("FIREBASE_CONSOLE_URL", "https://conso
|
|
|
22
22
|
exports.deployOrigin = utils.envOverride("FIREBASE_DEPLOY_URL", utils.envOverride("FIREBASE_UPLOAD_URL", "https://deploy.firebase.com"));
|
|
23
23
|
exports.dynamicLinksOrigin = utils.envOverride("FIREBASE_DYNAMIC_LINKS_URL", "https://firebasedynamiclinks.googleapis.com");
|
|
24
24
|
exports.dynamicLinksKey = utils.envOverride("FIREBASE_DYNAMIC_LINKS_KEY", "AIzaSyB6PtY5vuiSB8MNgt20mQffkOlunZnHYiQ");
|
|
25
|
+
exports.eventarcOrigin = utils.envOverride("EVENTARC_URL", "https://eventarc.googleapis.com");
|
|
25
26
|
exports.firebaseApiOrigin = utils.envOverride("FIREBASE_API_URL", "https://firebase.googleapis.com");
|
|
26
27
|
exports.firebaseExtensionsRegistryOrigin = utils.envOverride("FIREBASE_EXT_REGISTRY_ORIGIN", "https://extensions-registry.firebaseapp.com");
|
|
27
28
|
exports.firedataOrigin = utils.envOverride("FIREBASE_FIREDATA_URL", "https://mobilesdk-pa.googleapis.com");
|
package/lib/bin/firebase.js
CHANGED
|
File without changes
|
|
@@ -30,7 +30,7 @@ exports.command = new command_1.Command("functions:delete [filters...]")
|
|
|
30
30
|
}
|
|
31
31
|
const context = {
|
|
32
32
|
projectId: (0, projectUtils_1.needProjectId)(options),
|
|
33
|
-
filters: filters.map((f) => ({ idChunks: f.split(
|
|
33
|
+
filters: filters.map((f) => ({ idChunks: f.split(/[-.]/) })),
|
|
34
34
|
};
|
|
35
35
|
const [config, existingBackend] = await Promise.all([
|
|
36
36
|
functionsConfig.getFirebaseConfig(options),
|
package/lib/commands/index.js
CHANGED
|
@@ -137,6 +137,11 @@ function load(client) {
|
|
|
137
137
|
client.hosting.sites.get = loadCommand("hosting-sites-get");
|
|
138
138
|
client.hosting.sites.list = loadCommand("hosting-sites-list");
|
|
139
139
|
client.init = loadCommand("init");
|
|
140
|
+
if (experiments.isEnabled("internaltesting")) {
|
|
141
|
+
client.internaltesting = {};
|
|
142
|
+
client.internaltesting.functions = {};
|
|
143
|
+
client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover");
|
|
144
|
+
}
|
|
140
145
|
client.login = loadCommand("login");
|
|
141
146
|
client.login.add = loadCommand("login-add");
|
|
142
147
|
client.login.ci = loadCommand("login-ci");
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const logger_1 = require("../logger");
|
|
6
|
+
const prepare_1 = require("../deploy/functions/prepare");
|
|
7
|
+
const projectConfig_1 = require("../functions/projectConfig");
|
|
8
|
+
const adminSdkConfig_1 = require("../emulator/adminSdkConfig");
|
|
9
|
+
const projectUtils_1 = require("../projectUtils");
|
|
10
|
+
const error_1 = require("../error");
|
|
11
|
+
exports.command = new command_1.Command("internaltesting:functions:discover")
|
|
12
|
+
.description("discover function triggers defined in the current project directory")
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
15
|
+
const fnConfig = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
16
|
+
const firebaseConfig = await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId);
|
|
17
|
+
if (!firebaseConfig) {
|
|
18
|
+
throw new error_1.FirebaseError("Admin SDK config unexpectedly undefined - have you run firebase init?");
|
|
19
|
+
}
|
|
20
|
+
const builds = await (0, prepare_1.loadCodebases)(fnConfig, options, firebaseConfig, {
|
|
21
|
+
firebase: firebaseConfig,
|
|
22
|
+
});
|
|
23
|
+
logger_1.logger.info(JSON.stringify(builds, null, 2));
|
|
24
|
+
return builds;
|
|
25
|
+
});
|
|
@@ -14,8 +14,9 @@ const secretsUtils_1 = require("../../extensions/secretsUtils");
|
|
|
14
14
|
const secrets_1 = require("./secrets");
|
|
15
15
|
const warnings_1 = require("../../extensions/warnings");
|
|
16
16
|
const etags_1 = require("../../extensions/etags");
|
|
17
|
+
const v2FunctionHelper_1 = require("./v2FunctionHelper");
|
|
17
18
|
async function prepare(context, options, payload) {
|
|
18
|
-
var _a;
|
|
19
|
+
var _a, _b;
|
|
19
20
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
20
21
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
21
22
|
const aliases = (0, projectUtils_1.getAliases)(options, projectId);
|
|
@@ -49,6 +50,10 @@ async function prepare(context, options, payload) {
|
|
|
49
50
|
if (usingSecrets.some((i) => i)) {
|
|
50
51
|
await (0, secretsUtils_1.ensureSecretManagerApiEnabled)(options);
|
|
51
52
|
}
|
|
53
|
+
const usingV2Functions = await Promise.all((_b = context.want) === null || _b === void 0 ? void 0 : _b.map(v2FunctionHelper_1.checkSpecForV2Functions));
|
|
54
|
+
if (usingV2Functions) {
|
|
55
|
+
await (0, v2FunctionHelper_1.ensureNecessaryV2ApisAndRoles)(options);
|
|
56
|
+
}
|
|
52
57
|
payload.instancesToCreate = context.want.filter((i) => { var _a; return !((_a = context.have) === null || _a === void 0 ? void 0 : _a.some(matchesInstanceId(i))); });
|
|
53
58
|
payload.instancesToConfigure = context.want.filter((i) => { var _a; return (_a = context.have) === null || _a === void 0 ? void 0 : _a.some(isConfigure(i)); });
|
|
54
59
|
payload.instancesToUpdate = context.want.filter((i) => { var _a; return (_a = context.have) === null || _a === void 0 ? void 0 : _a.some(isUpdate(i)); });
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureNecessaryV2ApisAndRoles = exports.checkSpecForV2Functions = void 0;
|
|
4
|
+
const getProjectNumber_1 = require("../../getProjectNumber");
|
|
5
|
+
const resourceManager = require("../../gcp/resourceManager");
|
|
6
|
+
const logger_1 = require("../../logger");
|
|
7
|
+
const error_1 = require("../../error");
|
|
8
|
+
const ensureApiEnabled_1 = require("../../ensureApiEnabled");
|
|
9
|
+
const planner = require("./planner");
|
|
10
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
11
|
+
const SERVICE_AGENT_ROLE = "roles/eventarc.eventReceiver";
|
|
12
|
+
async function checkSpecForV2Functions(i) {
|
|
13
|
+
const extensionSpec = await planner.getExtensionSpec(i);
|
|
14
|
+
return extensionSpec.resources.some((r) => r.type === "firebaseextensions.v1beta.v2function");
|
|
15
|
+
}
|
|
16
|
+
exports.checkSpecForV2Functions = checkSpecForV2Functions;
|
|
17
|
+
async function ensureNecessaryV2ApisAndRoles(options) {
|
|
18
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
19
|
+
await (0, ensureApiEnabled_1.ensure)(projectId, "compute.googleapis.com", "extensions", options.markdown);
|
|
20
|
+
await ensureComputeP4SARole(projectId);
|
|
21
|
+
}
|
|
22
|
+
exports.ensureNecessaryV2ApisAndRoles = ensureNecessaryV2ApisAndRoles;
|
|
23
|
+
async function ensureComputeP4SARole(projectId) {
|
|
24
|
+
const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
|
|
25
|
+
const saEmail = `${projectNumber}-compute@developer.gserviceaccount.com`;
|
|
26
|
+
let policy;
|
|
27
|
+
try {
|
|
28
|
+
policy = await resourceManager.getIamPolicy(projectId);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
if (e instanceof error_1.FirebaseError && e.status === 403) {
|
|
32
|
+
throw new error_1.FirebaseError("Unable to get project IAM policy, permission denied (403). Please " +
|
|
33
|
+
"make sure you have sufficient project privileges or if this is a brand new project " +
|
|
34
|
+
"try again in a few minutes.");
|
|
35
|
+
}
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
if (policy.bindings.find((b) => b.role === SERVICE_AGENT_ROLE && b.members.includes("serviceAccount:" + saEmail))) {
|
|
39
|
+
logger_1.logger.debug("Compute Service API Agent IAM policy OK");
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
logger_1.logger.debug("Firebase Extensions Service Agent is missing a required IAM role " +
|
|
44
|
+
"`Firebase Extensions API Service Agent`.");
|
|
45
|
+
policy.bindings.push({
|
|
46
|
+
role: SERVICE_AGENT_ROLE,
|
|
47
|
+
members: ["serviceAccount:" + saEmail],
|
|
48
|
+
});
|
|
49
|
+
await resourceManager.setIamPolicy(projectId, policy, "bindings");
|
|
50
|
+
logger_1.logger.debug("Compute Service API Agent IAM policy updated successfully");
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -84,6 +84,7 @@ function envWithTypes(definedParams, rawEnvs) {
|
|
|
84
84
|
string: true,
|
|
85
85
|
boolean: true,
|
|
86
86
|
number: true,
|
|
87
|
+
list: true,
|
|
87
88
|
};
|
|
88
89
|
for (const param of definedParams) {
|
|
89
90
|
if (param.name === envName) {
|
|
@@ -92,6 +93,7 @@ function envWithTypes(definedParams, rawEnvs) {
|
|
|
92
93
|
string: true,
|
|
93
94
|
boolean: false,
|
|
94
95
|
number: false,
|
|
96
|
+
list: false,
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
else if (param.type === "int") {
|
|
@@ -99,6 +101,7 @@ function envWithTypes(definedParams, rawEnvs) {
|
|
|
99
101
|
string: false,
|
|
100
102
|
boolean: false,
|
|
101
103
|
number: true,
|
|
104
|
+
list: false,
|
|
102
105
|
};
|
|
103
106
|
}
|
|
104
107
|
else if (param.type === "boolean") {
|
|
@@ -106,6 +109,15 @@ function envWithTypes(definedParams, rawEnvs) {
|
|
|
106
109
|
string: false,
|
|
107
110
|
boolean: true,
|
|
108
111
|
number: false,
|
|
112
|
+
list: false,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
else if (param.type === "list") {
|
|
116
|
+
providedType = {
|
|
117
|
+
string: false,
|
|
118
|
+
boolean: false,
|
|
119
|
+
number: false,
|
|
120
|
+
list: true,
|
|
109
121
|
};
|
|
110
122
|
}
|
|
111
123
|
}
|
|
@@ -163,10 +175,13 @@ function toBackend(build, paramValues) {
|
|
|
163
175
|
if (r.resolveBoolean(bdEndpoint.omit || false)) {
|
|
164
176
|
continue;
|
|
165
177
|
}
|
|
166
|
-
let regions =
|
|
167
|
-
if (
|
|
178
|
+
let regions = [];
|
|
179
|
+
if (!bdEndpoint.region) {
|
|
168
180
|
regions = [api.functionsDefaultRegion];
|
|
169
181
|
}
|
|
182
|
+
else {
|
|
183
|
+
regions = params.resolveList(bdEndpoint.region, paramValues);
|
|
184
|
+
}
|
|
170
185
|
for (const region of regions) {
|
|
171
186
|
const trigger = discoverTrigger(bdEndpoint, region, r);
|
|
172
187
|
if (typeof bdEndpoint.platform === "undefined") {
|
|
@@ -11,6 +11,9 @@ const comparisonRegexp = new RegExp(/{{ params\.(\S+) CMP (.+) }}/.source.replac
|
|
|
11
11
|
const dualTernaryRegexp = new RegExp(/{{ params\.(\S+) CMP params\.(\S+) \? (.+) : (.+) }/.source.replace("CMP", CMP));
|
|
12
12
|
const ternaryRegexp = new RegExp(/{{ params\.(\S+) CMP (.+) \? (.+) : (.+) }/.source.replace("CMP", CMP));
|
|
13
13
|
const literalTernaryRegexp = /{{ params\.(\S+) \? (.+) : (.+) }/;
|
|
14
|
+
function listEquals(a, b) {
|
|
15
|
+
return a.every((item) => b.includes(item)) && b.every((item) => a.includes(item));
|
|
16
|
+
}
|
|
14
17
|
function isCelExpression(value) {
|
|
15
18
|
return typeof value === "string" && value.includes("{{") && value.includes("}}");
|
|
16
19
|
}
|
|
@@ -37,6 +40,7 @@ class ExprParseError extends error_1.FirebaseError {
|
|
|
37
40
|
}
|
|
38
41
|
exports.ExprParseError = ExprParseError;
|
|
39
42
|
function resolveExpression(wantType, expr, params) {
|
|
43
|
+
expr = preprocessLists(wantType, expr, params);
|
|
40
44
|
if (isIdentityExpression(expr)) {
|
|
41
45
|
return resolveIdentity(wantType, expr, params);
|
|
42
46
|
}
|
|
@@ -60,10 +64,52 @@ function resolveExpression(wantType, expr, params) {
|
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
exports.resolveExpression = resolveExpression;
|
|
67
|
+
function preprocessLists(wantType, expr, params) {
|
|
68
|
+
let rv = expr;
|
|
69
|
+
const listMatcher = /\[[^\[\]]*\]/g;
|
|
70
|
+
let match;
|
|
71
|
+
while ((match = listMatcher.exec(expr)) != null) {
|
|
72
|
+
const list = match[0];
|
|
73
|
+
const resolved = resolveList("string", list, params);
|
|
74
|
+
rv = rv.replace(list, JSON.stringify(resolved));
|
|
75
|
+
}
|
|
76
|
+
return rv;
|
|
77
|
+
}
|
|
78
|
+
function resolveList(wantType, list, params) {
|
|
79
|
+
if (!list.startsWith("[") || !list.endsWith("]")) {
|
|
80
|
+
throw new ExprParseError("Invalid list: must start with '[' and end with ']'");
|
|
81
|
+
}
|
|
82
|
+
else if (list === "[]") {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const rv = [];
|
|
86
|
+
const entries = list.slice(1, -1).split(",");
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const trimmed = entry.trim();
|
|
89
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
90
|
+
rv.push(trimmed.slice(1, -1));
|
|
91
|
+
}
|
|
92
|
+
else if (trimmed.startsWith("{{") && trimmed.endsWith("}}")) {
|
|
93
|
+
rv.push(resolveExpression("string", trimmed, params));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const paramMatch = paramRegexp.exec(trimmed);
|
|
97
|
+
if (!paramMatch) {
|
|
98
|
+
throw new ExprParseError(`Malformed list component ${trimmed}`);
|
|
99
|
+
}
|
|
100
|
+
else if (!(paramMatch[1] in params)) {
|
|
101
|
+
throw new ExprParseError(`List expansion referenced nonexistent param ${paramMatch[1]}`);
|
|
102
|
+
}
|
|
103
|
+
rv.push(resolveParamListOrLiteral("string", trimmed, params));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return rv;
|
|
107
|
+
}
|
|
63
108
|
function assertType(wantType, paramName, paramValue) {
|
|
64
109
|
if ((wantType === "string" && !paramValue.legalString) ||
|
|
65
110
|
(wantType === "number" && !paramValue.legalNumber) ||
|
|
66
|
-
(wantType === "boolean" && !paramValue.legalBoolean)
|
|
111
|
+
(wantType === "boolean" && !paramValue.legalBoolean) ||
|
|
112
|
+
(wantType === "string[]" && !paramValue.legalList)) {
|
|
67
113
|
throw new ExprParseError(`Illegal type coercion of param ${paramName} to type ${wantType}`);
|
|
68
114
|
}
|
|
69
115
|
}
|
|
@@ -78,6 +124,9 @@ function readParamValue(wantType, paramName, paramValue) {
|
|
|
78
124
|
else if (wantType === "boolean") {
|
|
79
125
|
return paramValue.asBoolean();
|
|
80
126
|
}
|
|
127
|
+
else if (wantType === "string[]") {
|
|
128
|
+
return paramValue.asList();
|
|
129
|
+
}
|
|
81
130
|
else {
|
|
82
131
|
(0, functional_1.assertExhaustive)(wantType);
|
|
83
132
|
}
|
|
@@ -103,9 +152,9 @@ function resolveComparison(expr, params) {
|
|
|
103
152
|
const test = function (a, b) {
|
|
104
153
|
switch (cmp) {
|
|
105
154
|
case "!=":
|
|
106
|
-
return a !== b;
|
|
155
|
+
return Array.isArray(a) ? !listEquals(a, b) : a !== b;
|
|
107
156
|
case "==":
|
|
108
|
-
return a === b;
|
|
157
|
+
return Array.isArray(a) ? listEquals(a, b) : a === b;
|
|
109
158
|
case ">=":
|
|
110
159
|
return a >= b;
|
|
111
160
|
case "<=":
|
|
@@ -136,6 +185,13 @@ function resolveComparison(expr, params) {
|
|
|
136
185
|
rhs = resolveLiteral("boolean", match[3]);
|
|
137
186
|
return test(lhsVal.asBoolean(), rhs);
|
|
138
187
|
}
|
|
188
|
+
else if (lhsVal.legalList) {
|
|
189
|
+
if (!["==", "!="].includes(cmp)) {
|
|
190
|
+
throw new ExprParseError(`Unsupported comparison operation ${cmp} on list operands in expression ${expr}`);
|
|
191
|
+
}
|
|
192
|
+
rhs = resolveLiteral("string[]", match[3]);
|
|
193
|
+
return test(lhsVal.asList(), rhs);
|
|
194
|
+
}
|
|
139
195
|
else {
|
|
140
196
|
throw new ExprParseError(`Could not infer type of param ${lhsName} used in comparison operation`);
|
|
141
197
|
}
|
|
@@ -149,9 +205,9 @@ function resolveDualComparison(expr, params) {
|
|
|
149
205
|
const test = function (a, b) {
|
|
150
206
|
switch (cmp) {
|
|
151
207
|
case "!=":
|
|
152
|
-
return a !== b;
|
|
208
|
+
return Array.isArray(a) ? !listEquals(a, b) : a !== b;
|
|
153
209
|
case "==":
|
|
154
|
-
return a === b;
|
|
210
|
+
return Array.isArray(a) ? listEquals(a, b) : a === b;
|
|
155
211
|
case ">=":
|
|
156
212
|
return a >= b;
|
|
157
213
|
case "<=":
|
|
@@ -192,6 +248,15 @@ function resolveDualComparison(expr, params) {
|
|
|
192
248
|
}
|
|
193
249
|
return test(lhsVal.asBoolean(), rhsVal.asBoolean());
|
|
194
250
|
}
|
|
251
|
+
else if (lhsVal.legalList) {
|
|
252
|
+
if (!rhsVal.legalList) {
|
|
253
|
+
throw new ExprParseError(`CEL comparison expression ${expr} has type mismatch between the operands`);
|
|
254
|
+
}
|
|
255
|
+
if (!["==", "!="].includes(cmp)) {
|
|
256
|
+
throw new ExprParseError(`Unsupported comparison operation ${cmp} on list operands in expression ${expr}`);
|
|
257
|
+
}
|
|
258
|
+
return test(lhsVal.asList(), rhsVal.asList());
|
|
259
|
+
}
|
|
195
260
|
else {
|
|
196
261
|
throw new ExprParseError(`could not infer type of param ${lhsName} used in comparison operation`);
|
|
197
262
|
}
|
|
@@ -204,10 +269,10 @@ function resolveTernary(wantType, expr, params) {
|
|
|
204
269
|
const comparisonExpr = `{{ params.${match[1]} ${match[2]} ${match[3]} }}`;
|
|
205
270
|
const isTrue = resolveComparison(comparisonExpr, params);
|
|
206
271
|
if (isTrue) {
|
|
207
|
-
return
|
|
272
|
+
return resolveParamListOrLiteral(wantType, match[4], params);
|
|
208
273
|
}
|
|
209
274
|
else {
|
|
210
|
-
return
|
|
275
|
+
return resolveParamListOrLiteral(wantType, match[5], params);
|
|
211
276
|
}
|
|
212
277
|
}
|
|
213
278
|
function resolveDualTernary(wantType, expr, params) {
|
|
@@ -218,10 +283,10 @@ function resolveDualTernary(wantType, expr, params) {
|
|
|
218
283
|
const comparisonExpr = `{{ params.${match[1]} ${match[2]} params.${match[3]} }}`;
|
|
219
284
|
const isTrue = resolveDualComparison(comparisonExpr, params);
|
|
220
285
|
if (isTrue) {
|
|
221
|
-
return
|
|
286
|
+
return resolveParamListOrLiteral(wantType, match[4], params);
|
|
222
287
|
}
|
|
223
288
|
else {
|
|
224
|
-
return
|
|
289
|
+
return resolveParamListOrLiteral(wantType, match[5], params);
|
|
225
290
|
}
|
|
226
291
|
}
|
|
227
292
|
function resolveLiteralTernary(wantType, expr, params) {
|
|
@@ -238,13 +303,13 @@ function resolveLiteralTernary(wantType, expr, params) {
|
|
|
238
303
|
throw new ExprParseError("CEL ternary expression '" + expr + "' is conditional on non-boolean param " + paramName);
|
|
239
304
|
}
|
|
240
305
|
if (paramValue.asBoolean()) {
|
|
241
|
-
return
|
|
306
|
+
return resolveParamListOrLiteral(wantType, match[2], params);
|
|
242
307
|
}
|
|
243
308
|
else {
|
|
244
|
-
return
|
|
309
|
+
return resolveParamListOrLiteral(wantType, match[3], params);
|
|
245
310
|
}
|
|
246
311
|
}
|
|
247
|
-
function
|
|
312
|
+
function resolveParamListOrLiteral(wantType, field, params) {
|
|
248
313
|
const match = paramRegexp.exec(field);
|
|
249
314
|
if (!match) {
|
|
250
315
|
return resolveLiteral(wantType, field);
|
|
@@ -259,7 +324,19 @@ function resolveLiteral(wantType, value) {
|
|
|
259
324
|
if (paramRegexp.exec(value)) {
|
|
260
325
|
throw new ExprParseError("CEL tried to evaluate param." + value + " in a context which only permits literal values");
|
|
261
326
|
}
|
|
262
|
-
if (wantType === "
|
|
327
|
+
if (wantType === "string[]") {
|
|
328
|
+
const parsed = JSON.parse(value);
|
|
329
|
+
if (!Array.isArray(parsed)) {
|
|
330
|
+
throw new ExprParseError(`CEL tried to read non-list ${JSON.stringify(parsed)} as a list`);
|
|
331
|
+
}
|
|
332
|
+
for (const shouldBeString of parsed) {
|
|
333
|
+
if (typeof shouldBeString !== "string") {
|
|
334
|
+
throw new ExprParseError(`Evaluated CEL list ${JSON.stringify(parsed)} contained non-string values`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return parsed;
|
|
338
|
+
}
|
|
339
|
+
else if (wantType === "number") {
|
|
263
340
|
if (isNaN(+value)) {
|
|
264
341
|
throw new ExprParseError("CEL literal " + value + " does not seem to be a number");
|
|
265
342
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveParams = exports.ParamValue = exports.isResourceInput = exports.isSelectInput = exports.isTextInput = exports.resolveBoolean = exports.resolveString = exports.resolveInt = void 0;
|
|
3
|
+
exports.resolveParams = exports.ParamValue = exports.isMultiSelectInput = exports.isResourceInput = exports.isSelectInput = exports.isTextInput = exports.resolveBoolean = exports.resolveList = exports.resolveString = exports.resolveInt = void 0;
|
|
4
4
|
const logger_1 = require("../../logger");
|
|
5
5
|
const error_1 = require("../../error");
|
|
6
6
|
const prompt_1 = require("../../prompt");
|
|
@@ -38,6 +38,21 @@ function resolveString(from, paramValues) {
|
|
|
38
38
|
return output;
|
|
39
39
|
}
|
|
40
40
|
exports.resolveString = resolveString;
|
|
41
|
+
function resolveList(from, paramValues) {
|
|
42
|
+
if (!from) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
else if (Array.isArray(from)) {
|
|
46
|
+
return from.map((entry) => resolveString(entry, paramValues));
|
|
47
|
+
}
|
|
48
|
+
else if (typeof from === "string") {
|
|
49
|
+
return (0, cel_1.resolveExpression)("string[]", from, paramValues);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
(0, functional_1.assertExhaustive)(from);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.resolveList = resolveList;
|
|
41
56
|
function resolveBoolean(from, paramValues) {
|
|
42
57
|
if (typeof from === "boolean") {
|
|
43
58
|
return from;
|
|
@@ -57,6 +72,10 @@ function isResourceInput(input) {
|
|
|
57
72
|
return {}.hasOwnProperty.call(input, "resource");
|
|
58
73
|
}
|
|
59
74
|
exports.isResourceInput = isResourceInput;
|
|
75
|
+
function isMultiSelectInput(input) {
|
|
76
|
+
return {}.hasOwnProperty.call(input, "multiSelect");
|
|
77
|
+
}
|
|
78
|
+
exports.isMultiSelectInput = isMultiSelectInput;
|
|
60
79
|
class ParamValue {
|
|
61
80
|
constructor(rawValue, internal, types) {
|
|
62
81
|
this.rawValue = rawValue;
|
|
@@ -64,16 +83,32 @@ class ParamValue {
|
|
|
64
83
|
this.legalString = types.string || false;
|
|
65
84
|
this.legalBoolean = types.boolean || false;
|
|
66
85
|
this.legalNumber = types.number || false;
|
|
86
|
+
this.legalList = types.list || false;
|
|
87
|
+
this.delimiter = ",";
|
|
88
|
+
}
|
|
89
|
+
static fromList(ls, delimiter = ",") {
|
|
90
|
+
const pv = new ParamValue(ls.join(delimiter), false, { list: true });
|
|
91
|
+
pv.setDelimiter(delimiter);
|
|
92
|
+
return pv;
|
|
93
|
+
}
|
|
94
|
+
setDelimiter(delimiter) {
|
|
95
|
+
this.delimiter = delimiter;
|
|
67
96
|
}
|
|
68
97
|
toString() {
|
|
69
98
|
return this.rawValue;
|
|
70
99
|
}
|
|
100
|
+
toSDK() {
|
|
101
|
+
return this.legalList ? JSON.stringify(this.asList()) : this.toString();
|
|
102
|
+
}
|
|
71
103
|
asString() {
|
|
72
104
|
return this.rawValue;
|
|
73
105
|
}
|
|
74
106
|
asBoolean() {
|
|
75
107
|
return ["true", "y", "yes", "1"].includes(this.rawValue);
|
|
76
108
|
}
|
|
109
|
+
asList() {
|
|
110
|
+
return this.rawValue.split(this.delimiter);
|
|
111
|
+
}
|
|
77
112
|
asNumber() {
|
|
78
113
|
return +this.rawValue;
|
|
79
114
|
}
|
|
@@ -94,6 +129,8 @@ function resolveDefaultCEL(type, expr, currentEnv) {
|
|
|
94
129
|
return resolveString(expr, currentEnv);
|
|
95
130
|
case "int":
|
|
96
131
|
return resolveInt(expr, currentEnv);
|
|
132
|
+
case "list":
|
|
133
|
+
return resolveList(expr, currentEnv);
|
|
97
134
|
default:
|
|
98
135
|
throw new error_1.FirebaseError("Build specified parameter with default " + expr + " of unsupported type");
|
|
99
136
|
}
|
|
@@ -108,6 +145,9 @@ function canSatisfyParam(param, value) {
|
|
|
108
145
|
else if (param.type === "boolean") {
|
|
109
146
|
return typeof value === "boolean";
|
|
110
147
|
}
|
|
148
|
+
else if (param.type === "list") {
|
|
149
|
+
return Array.isArray(value);
|
|
150
|
+
}
|
|
111
151
|
else if (param.type === "secret") {
|
|
112
152
|
return false;
|
|
113
153
|
}
|
|
@@ -147,7 +187,7 @@ async function resolveParams(params, firebaseConfig, userEnvs, nonInteractive) {
|
|
|
147
187
|
exports.resolveParams = resolveParams;
|
|
148
188
|
function populateDefaultParams(config) {
|
|
149
189
|
const defaultParams = {};
|
|
150
|
-
if (config.databaseURL !== "") {
|
|
190
|
+
if (config.databaseURL && config.databaseURL !== "") {
|
|
151
191
|
defaultParams["DATABASE_URL"] = new ParamValue(config.databaseURL, true, {
|
|
152
192
|
string: true,
|
|
153
193
|
boolean: false,
|
|
@@ -164,7 +204,7 @@ function populateDefaultParams(config) {
|
|
|
164
204
|
boolean: false,
|
|
165
205
|
number: false,
|
|
166
206
|
});
|
|
167
|
-
if (config.storageBucket !== "") {
|
|
207
|
+
if (config.storageBucket && config.storageBucket !== "") {
|
|
168
208
|
defaultParams["STORAGE_BUCKET"] = new ParamValue(config.storageBucket, true, {
|
|
169
209
|
string: true,
|
|
170
210
|
boolean: false,
|
|
@@ -207,11 +247,52 @@ async function promptParam(param, projectId, resolvedDefault) {
|
|
|
207
247
|
const provided = await promptBooleanParam(param, resolvedDefault);
|
|
208
248
|
return new ParamValue(provided.toString(), false, { boolean: true });
|
|
209
249
|
}
|
|
250
|
+
else if (param.type === "list") {
|
|
251
|
+
const provided = await promptList(param, projectId, resolvedDefault);
|
|
252
|
+
return ParamValue.fromList(provided, param.delimiter);
|
|
253
|
+
}
|
|
210
254
|
else if (param.type === "secret") {
|
|
211
255
|
throw new error_1.FirebaseError(`Somehow ended up trying to interactively prompt for secret parameter ${param.name}, which should never happen.`);
|
|
212
256
|
}
|
|
213
257
|
(0, functional_1.assertExhaustive)(param);
|
|
214
258
|
}
|
|
259
|
+
async function promptList(param, projectId, resolvedDefault) {
|
|
260
|
+
if (!param.input) {
|
|
261
|
+
const defaultToText = { text: {} };
|
|
262
|
+
param.input = defaultToText;
|
|
263
|
+
}
|
|
264
|
+
let prompt;
|
|
265
|
+
if (isSelectInput(param.input)) {
|
|
266
|
+
throw new error_1.FirebaseError("List params cannot have non-list selector inputs");
|
|
267
|
+
}
|
|
268
|
+
else if (isMultiSelectInput(param.input)) {
|
|
269
|
+
prompt = `Select a value for ${param.label || param.name}:`;
|
|
270
|
+
if (param.description) {
|
|
271
|
+
prompt += ` \n(${param.description})`;
|
|
272
|
+
}
|
|
273
|
+
prompt += "\nSelect an option with the arrow keys, and use Enter to confirm your choice. ";
|
|
274
|
+
return promptSelectMultiple(prompt, param.input, resolvedDefault, (res) => res);
|
|
275
|
+
}
|
|
276
|
+
else if (isTextInput(param.input)) {
|
|
277
|
+
prompt = `Enter a list of strings (delimiter: ${param.delimiter ? param.delimiter : ","}) for ${param.label || param.name}:`;
|
|
278
|
+
if (param.description) {
|
|
279
|
+
prompt += ` \n(${param.description})`;
|
|
280
|
+
}
|
|
281
|
+
return promptText(prompt, param.input, resolvedDefault, (res) => {
|
|
282
|
+
return res.split(param.delimiter || ",");
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else if (isResourceInput(param.input)) {
|
|
286
|
+
prompt = `Select values for ${param.label || param.name}:`;
|
|
287
|
+
if (param.description) {
|
|
288
|
+
prompt += ` \n(${param.description})`;
|
|
289
|
+
}
|
|
290
|
+
return promptResourceStrings(prompt, param.input, projectId);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
(0, functional_1.assertExhaustive)(param.input);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
215
296
|
async function promptBooleanParam(param, resolvedDefault) {
|
|
216
297
|
if (!param.input) {
|
|
217
298
|
const defaultToText = { text: {} };
|
|
@@ -227,6 +308,9 @@ async function promptBooleanParam(param, resolvedDefault) {
|
|
|
227
308
|
prompt += "\nSelect an option with the arrow keys, and use Enter to confirm your choice. ";
|
|
228
309
|
return promptSelect(prompt, param.input, resolvedDefault, isTruthyInput);
|
|
229
310
|
}
|
|
311
|
+
else if (isMultiSelectInput(param.input)) {
|
|
312
|
+
throw new error_1.FirebaseError("Non-list params cannot have multi selector inputs");
|
|
313
|
+
}
|
|
230
314
|
else if (isTextInput(param.input)) {
|
|
231
315
|
prompt = `Enter a boolean value for ${param.label || param.name}:`;
|
|
232
316
|
if (param.description) {
|
|
@@ -254,6 +338,9 @@ async function promptStringParam(param, projectId, resolvedDefault) {
|
|
|
254
338
|
}
|
|
255
339
|
return promptResourceString(prompt, param.input, projectId, resolvedDefault);
|
|
256
340
|
}
|
|
341
|
+
else if (isMultiSelectInput(param.input)) {
|
|
342
|
+
throw new error_1.FirebaseError("Non-list params cannot have multi selector inputs");
|
|
343
|
+
}
|
|
257
344
|
else if (isSelectInput(param.input)) {
|
|
258
345
|
prompt = `Select a value for ${param.label || param.name}:`;
|
|
259
346
|
if (param.description) {
|
|
@@ -295,7 +382,10 @@ async function promptIntParam(param, resolvedDefault) {
|
|
|
295
382
|
return +res;
|
|
296
383
|
});
|
|
297
384
|
}
|
|
298
|
-
if (
|
|
385
|
+
else if (isMultiSelectInput(param.input)) {
|
|
386
|
+
throw new error_1.FirebaseError("Non-list params cannot have multi selector inputs");
|
|
387
|
+
}
|
|
388
|
+
else if (isTextInput(param.input)) {
|
|
299
389
|
prompt = `Enter an integer value for ${param.label || param.name}:`;
|
|
300
390
|
if (param.description) {
|
|
301
391
|
prompt += ` \n(${param.description})`;
|
|
@@ -338,6 +428,30 @@ async function promptResourceString(prompt, input, projectId, resolvedDefault) {
|
|
|
338
428
|
return promptText(prompt, { text: {} }, resolvedDefault, (res) => res);
|
|
339
429
|
}
|
|
340
430
|
}
|
|
431
|
+
async function promptResourceStrings(prompt, input, projectId) {
|
|
432
|
+
const notFound = new error_1.FirebaseError(`No instances of ${input.resource.type} found.`);
|
|
433
|
+
switch (input.resource.type) {
|
|
434
|
+
case "storage.googleapis.com/Bucket":
|
|
435
|
+
const buckets = await (0, storage_1.listBuckets)(projectId);
|
|
436
|
+
if (buckets.length === 0) {
|
|
437
|
+
throw notFound;
|
|
438
|
+
}
|
|
439
|
+
const forgedInput = {
|
|
440
|
+
multiSelect: {
|
|
441
|
+
options: buckets.map((bucketName) => {
|
|
442
|
+
return { label: bucketName, value: bucketName };
|
|
443
|
+
}),
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
return promptSelectMultiple(prompt, forgedInput, undefined, (res) => res);
|
|
447
|
+
default:
|
|
448
|
+
logger_1.logger.warn(`Warning: unknown resource type ${input.resource.type}; defaulting to raw text input...`);
|
|
449
|
+
return promptText(prompt, { text: {} }, undefined, (res) => res.split(","));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function shouldRetry(obj) {
|
|
453
|
+
return typeof obj === "object" && obj.message !== undefined;
|
|
454
|
+
}
|
|
341
455
|
async function promptText(prompt, input, resolvedDefault, converter) {
|
|
342
456
|
const res = await (0, prompt_1.promptOnce)({
|
|
343
457
|
type: "input",
|
|
@@ -353,7 +467,7 @@ async function promptText(prompt, input, resolvedDefault, converter) {
|
|
|
353
467
|
}
|
|
354
468
|
}
|
|
355
469
|
const converted = converter(res.toString());
|
|
356
|
-
if (
|
|
470
|
+
if (shouldRetry(converted)) {
|
|
357
471
|
logger_1.logger.error(converted.message);
|
|
358
472
|
return promptText(prompt, input, resolvedDefault, converter);
|
|
359
473
|
}
|
|
@@ -374,9 +488,30 @@ async function promptSelect(prompt, input, resolvedDefault, converter) {
|
|
|
374
488
|
}),
|
|
375
489
|
});
|
|
376
490
|
const converted = converter(response);
|
|
377
|
-
if (
|
|
491
|
+
if (shouldRetry(converted)) {
|
|
378
492
|
logger_1.logger.error(converted.message);
|
|
379
493
|
return promptSelect(prompt, input, resolvedDefault, converter);
|
|
380
494
|
}
|
|
381
495
|
return converted;
|
|
382
496
|
}
|
|
497
|
+
async function promptSelectMultiple(prompt, input, resolvedDefault, converter) {
|
|
498
|
+
const response = await (0, prompt_1.promptOnce)({
|
|
499
|
+
name: "input",
|
|
500
|
+
type: "checkbox",
|
|
501
|
+
default: resolvedDefault,
|
|
502
|
+
message: prompt,
|
|
503
|
+
choices: input.multiSelect.options.map((option) => {
|
|
504
|
+
return {
|
|
505
|
+
checked: false,
|
|
506
|
+
name: option.label,
|
|
507
|
+
value: option.value.toString(),
|
|
508
|
+
};
|
|
509
|
+
}),
|
|
510
|
+
});
|
|
511
|
+
const converted = converter(response);
|
|
512
|
+
if (shouldRetry(converted)) {
|
|
513
|
+
logger_1.logger.error(converted.message);
|
|
514
|
+
return promptSelectMultiple(prompt, input, resolvedDefault, converter);
|
|
515
|
+
}
|
|
516
|
+
return converted;
|
|
517
|
+
}
|