firebase-tools 11.3.0 → 11.4.2

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.
Files changed (50) hide show
  1. package/README.md +8 -15
  2. package/lib/apiv2.js +5 -0
  3. package/lib/checkValidTargetFilters.js +3 -2
  4. package/lib/command.js +1 -0
  5. package/lib/commands/hosting-clone.js +5 -0
  6. package/lib/commands/login-ci.js +2 -0
  7. package/lib/database/rulesConfig.js +35 -8
  8. package/lib/deploy/functions/backend.js +6 -4
  9. package/lib/deploy/functions/build.js +107 -95
  10. package/lib/deploy/functions/ensure.js +1 -1
  11. package/lib/deploy/functions/params.js +5 -2
  12. package/lib/deploy/functions/prepare.js +3 -3
  13. package/lib/deploy/functions/pricing.js +3 -2
  14. package/lib/deploy/functions/prompts.js +1 -1
  15. package/lib/deploy/functions/release/fabricator.js +8 -7
  16. package/lib/deploy/functions/release/index.js +4 -0
  17. package/lib/deploy/functions/runtimes/discovery/parsing.js +19 -8
  18. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +115 -107
  19. package/lib/deploy/functions/runtimes/node/parseTriggers.js +53 -21
  20. package/lib/deploy/functions/services/storage.js +6 -0
  21. package/lib/deploy/hosting/convertConfig.js +87 -16
  22. package/lib/deploy/hosting/deploy.js +1 -1
  23. package/lib/deploy/index.js +1 -1
  24. package/lib/deploy/storage/prepare.js +29 -6
  25. package/lib/emulator/controller.js +0 -1
  26. package/lib/emulator/functionsEmulator.js +3 -0
  27. package/lib/emulator/functionsEmulatorRuntime.js +1 -1
  28. package/lib/emulator/functionsEmulatorShared.js +6 -11
  29. package/lib/emulator/storage/files.js +19 -22
  30. package/lib/emulator/storage/metadata.js +6 -6
  31. package/lib/emulator/storage/persistence.js +26 -12
  32. package/lib/extensions/displayExtensionInfo.js +1 -101
  33. package/lib/extensions/emulator/triggerHelper.js +2 -2
  34. package/lib/extensions/updateHelper.js +1 -7
  35. package/lib/functional.js +16 -1
  36. package/lib/functions/env.js +47 -2
  37. package/lib/gcp/cloudfunctions.js +21 -8
  38. package/lib/gcp/cloudfunctionsv2.js +43 -19
  39. package/lib/gcp/cloudscheduler.js +25 -13
  40. package/lib/gcp/cloudtasks.js +4 -3
  41. package/lib/gcp/iam.js +20 -17
  42. package/lib/gcp/proto.js +18 -6
  43. package/lib/gcp/resourceManager.js +25 -3
  44. package/lib/index.js +1 -1
  45. package/lib/previews.js +1 -1
  46. package/lib/rc.js +3 -9
  47. package/lib/requireAuth.js +4 -0
  48. package/lib/rulesDeploy.js +40 -3
  49. package/npm-shrinkwrap.json +48 -37
  50. package/package.json +7 -4
@@ -23,35 +23,46 @@ function assertKeyTypes(prefix, yaml, schema) {
23
23
  if (!schema[key] || schema[key] === "omit") {
24
24
  throw new error_1.FirebaseError(`Unexpected key ${fullKey}. You may need to install a newer version of the Firebase CLI.`);
25
25
  }
26
- const schemaType = schema[key];
26
+ let schemaType = schema[key];
27
27
  if (typeof schemaType === "function") {
28
28
  if (!schemaType(value)) {
29
- throw new error_1.FirebaseError(`${Array.isArray(value) ? "array" : typeof value} ${fullKey} failed validation`);
29
+ const friendlyName = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
30
+ throw new error_1.FirebaseError(`${friendlyName} ${fullKey} failed validation`);
30
31
  }
32
+ continue;
31
33
  }
32
- else if (schemaType === "string") {
34
+ if (value === null) {
35
+ if (schemaType.endsWith("?")) {
36
+ continue;
37
+ }
38
+ throw new error_1.FirebaseError(`Expected ${fullKey}} to be type ${schemaType}; was null`);
39
+ }
40
+ if (schemaType.endsWith("?")) {
41
+ schemaType = schemaType.slice(0, schemaType.length - 1);
42
+ }
43
+ if (schemaType === "string") {
33
44
  if (typeof value !== "string") {
34
- throw new error_1.FirebaseError(`Expected ${fullKey} to be string; was ${typeof value}`);
45
+ throw new error_1.FirebaseError(`Expected ${fullKey} to be type string; was ${typeof value}`);
35
46
  }
36
47
  }
37
48
  else if (schemaType === "number") {
38
49
  if (typeof value !== "number") {
39
- throw new error_1.FirebaseError(`Expected ${fullKey} to be a number; was ${typeof value}`);
50
+ throw new error_1.FirebaseError(`Expected ${fullKey} to be type number; was ${typeof value}`);
40
51
  }
41
52
  }
42
53
  else if (schemaType === "boolean") {
43
54
  if (typeof value !== "boolean") {
44
- throw new error_1.FirebaseError(`Expected ${fullKey} to be a boolean; was ${typeof value}`);
55
+ throw new error_1.FirebaseError(`Expected ${fullKey} to be type boolean; was ${typeof value}`);
45
56
  }
46
57
  }
47
58
  else if (schemaType === "array") {
48
59
  if (!Array.isArray(value)) {
49
- throw new error_1.FirebaseError(`Expected ${fullKey} to be an array; was ${typeof value}`);
60
+ throw new error_1.FirebaseError(`Expected ${fullKey} to be type array; was ${typeof value}`);
50
61
  }
51
62
  }
52
63
  else if (schemaType === "object") {
53
64
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
54
- throw new error_1.FirebaseError(`Expected ${fullKey} to be an object; was ${typeof value}`);
65
+ throw new error_1.FirebaseError(`Expected ${fullKey} to be type object; was ${typeof value}`);
55
66
  }
56
67
  }
57
68
  else {
@@ -1,15 +1,4 @@
1
1
  "use strict";
2
- var __rest = (this && this.__rest) || function (s, e) {
3
- var t = {};
4
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
- t[p] = s[p];
6
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
- t[p[i]] = s[p[i]];
10
- }
11
- return t;
12
- };
13
2
  Object.defineProperty(exports, "__esModule", { value: true });
14
3
  exports.backendFromV1Alpha1 = exports.buildFromV1Alpha1 = void 0;
15
4
  const backend = require("../../backend");
@@ -17,6 +6,7 @@ const build = require("../../build");
17
6
  const proto_1 = require("../../../../gcp/proto");
18
7
  const parsing_1 = require("./parsing");
19
8
  const error_1 = require("../../../../error");
9
+ const functional_1 = require("../../../../functional");
20
10
  const CHANNEL_NAME_REGEX = new RegExp("(projects\\/" +
21
11
  "(?<project>(?:\\d+)|(?:[A-Za-z]+[A-Za-z\\d-]*[A-Za-z\\d]?))\\/)?" +
22
12
  "locations\\/" +
@@ -28,10 +18,12 @@ function buildFromV1Alpha1(yaml, project, region, runtime) {
28
18
  (0, parsing_1.requireKeys)("", manifest, "endpoints");
29
19
  (0, parsing_1.assertKeyTypes)("", manifest, {
30
20
  specVersion: "string",
21
+ params: "array",
31
22
  requiredAPIs: "array",
32
23
  endpoints: "object",
33
24
  });
34
25
  const bd = build.empty();
26
+ bd.params = manifest.params || [];
35
27
  bd.requiredAPIs = parseRequiredAPIs(manifest);
36
28
  for (const id of Object.keys(manifest.endpoints)) {
37
29
  const me = manifest.endpoints[id];
@@ -49,6 +41,7 @@ function backendFromV1Alpha1(yaml, project, region, runtime) {
49
41
  (0, parsing_1.requireKeys)("", manifest, "endpoints");
50
42
  (0, parsing_1.assertKeyTypes)("", manifest, {
51
43
  specVersion: "string",
44
+ params: "array",
52
45
  requiredAPIs: "array",
53
46
  endpoints: "object",
54
47
  });
@@ -79,29 +72,30 @@ function assertManifestEndpoint(ep, id) {
79
72
  region: "array",
80
73
  platform: (platform) => backend.AllFunctionsPlatforms.includes(platform),
81
74
  entryPoint: "string",
82
- availableMemoryMb: (mem) => backend.AllMemoryOptions.includes(mem),
83
- maxInstances: "number",
84
- minInstances: "number",
85
- concurrency: "number",
86
- serviceAccountEmail: "string",
87
- timeoutSeconds: "number",
88
- vpc: "object",
89
- labels: "object",
90
- ingressSettings: (setting) => backend.AllIngressSettings.includes(setting),
91
- environmentVariables: "object",
92
- secretEnvironmentVariables: "array",
75
+ availableMemoryMb: (mem) => mem === null || backend.isValidMemoryOption(mem),
76
+ maxInstances: "number?",
77
+ minInstances: "number?",
78
+ concurrency: "number?",
79
+ serviceAccount: "string?",
80
+ serviceAccountEmail: "string?",
81
+ timeoutSeconds: "number?",
82
+ vpc: "object?",
83
+ labels: "object?",
84
+ ingressSettings: (setting) => setting === null || backend.AllIngressSettings.includes(setting),
85
+ environmentVariables: "object?",
86
+ secretEnvironmentVariables: "array?",
93
87
  httpsTrigger: "object",
94
88
  callableTrigger: "object",
95
89
  eventTrigger: "object",
96
90
  scheduleTrigger: "object",
97
91
  taskQueueTrigger: "object",
98
92
  blockingTrigger: "object",
99
- cpu: (cpu) => typeof cpu === "number" || cpu === "gcf_gen1",
93
+ cpu: (cpu) => cpu === null || typeof cpu === "number" || cpu === "gcf_gen1",
100
94
  });
101
95
  if (ep.vpc) {
102
96
  (0, parsing_1.assertKeyTypes)(prefix + ".vpc", ep.vpc, {
103
97
  connector: "string",
104
- egressSettings: (setting) => backend.AllVpcEgressSettings.includes(setting),
98
+ egressSettings: (setting) => setting === null || backend.AllVpcEgressSettings.includes(setting),
105
99
  });
106
100
  (0, parsing_1.requireKeys)(prefix + ".vpc", ep.vpc, "connector");
107
101
  }
@@ -138,13 +132,14 @@ function assertManifestEndpoint(ep, id) {
138
132
  eventType: "string",
139
133
  retry: "boolean",
140
134
  region: "string",
141
- serviceAccountEmail: "string",
135
+ serviceAccount: "string?",
136
+ serviceAccountEmail: "string?",
142
137
  channel: "string",
143
138
  });
144
139
  }
145
140
  else if (backend.isHttpsTriggered(ep)) {
146
141
  (0, parsing_1.assertKeyTypes)(prefix + ".httpsTrigger", ep.httpsTrigger, {
147
- invoker: "array",
142
+ invoker: "array?",
148
143
  });
149
144
  }
150
145
  else if (backend.isCallableTriggered(ep)) {
@@ -152,36 +147,39 @@ function assertManifestEndpoint(ep, id) {
152
147
  else if (backend.isScheduleTriggered(ep)) {
153
148
  (0, parsing_1.assertKeyTypes)(prefix + ".scheduleTrigger", ep.scheduleTrigger, {
154
149
  schedule: "string",
155
- timeZone: "string",
156
- retryConfig: "object",
150
+ timeZone: "string?",
151
+ retryConfig: "object?",
157
152
  });
158
- (0, parsing_1.assertKeyTypes)(prefix + ".scheduleTrigger.retryConfig", ep.scheduleTrigger.retryConfig, {
159
- retryCount: "number",
160
- maxDoublings: "number",
161
- minBackoffDuration: "string",
162
- maxBackoffDuration: "string",
163
- maxRetryDuration: "string",
153
+ (0, parsing_1.assertKeyTypes)(prefix + ".scheduleTrigger.retryConfig", ep.scheduleTrigger.retryConfig || {}, {
154
+ retryCount: "number?",
155
+ maxDoublings: "number?",
156
+ minBackoffSeconds: "number?",
157
+ maxBackoffSeconds: "number?",
158
+ maxRetrySeconds: "number?",
159
+ minBackoffDuration: "string?",
160
+ maxBackoffDuration: "string?",
161
+ maxRetryDuration: "string?",
164
162
  });
165
163
  }
166
164
  else if (backend.isTaskQueueTriggered(ep)) {
167
165
  (0, parsing_1.assertKeyTypes)(prefix + ".taskQueueTrigger", ep.taskQueueTrigger, {
168
- rateLimits: "object",
169
- retryConfig: "object",
170
- invoker: "array",
166
+ rateLimits: "object?",
167
+ retryConfig: "object?",
168
+ invoker: "array?",
171
169
  });
172
170
  if (ep.taskQueueTrigger.rateLimits) {
173
171
  (0, parsing_1.assertKeyTypes)(prefix + ".taskQueueTrigger.rateLimits", ep.taskQueueTrigger.rateLimits, {
174
- maxConcurrentDispatches: "number",
175
- maxDispatchesPerSecond: "number",
172
+ maxConcurrentDispatches: "number?",
173
+ maxDispatchesPerSecond: "number?",
176
174
  });
177
175
  }
178
176
  if (ep.taskQueueTrigger.retryConfig) {
179
177
  (0, parsing_1.assertKeyTypes)(prefix + ".taskQueueTrigger.retryConfig", ep.taskQueueTrigger.retryConfig, {
180
- maxAttempts: "number",
181
- maxRetrySeconds: "number",
182
- minBackoffSeconds: "number",
183
- maxBackoffSeconds: "number",
184
- maxDoublings: "number",
178
+ maxAttempts: "number?",
179
+ maxRetrySeconds: "number?",
180
+ minBackoffSeconds: "number?",
181
+ maxBackoffSeconds: "number?",
182
+ maxDoublings: "number?",
185
183
  });
186
184
  }
187
185
  }
@@ -198,18 +196,24 @@ function assertManifestEndpoint(ep, id) {
198
196
  }
199
197
  }
200
198
  function parseEndpointForBuild(id, ep, project, defaultRegion, runtime) {
199
+ var _a;
201
200
  let triggered;
202
201
  if (backend.isEventTriggered(ep)) {
203
- const newTrigger = __rest(ep.eventTrigger, []);
204
- delete newTrigger.serviceAccountEmail;
205
- triggered = { eventTrigger: newTrigger };
206
- triggered.eventTrigger.serviceAccount = ep.eventTrigger.serviceAccountEmail;
207
- (0, proto_1.renameIfPresent)(triggered.eventTrigger, ep.eventTrigger, "channel", "channel", (c) => resolveChannelName(project, c, defaultRegion));
208
- for (const [k, v] of Object.entries(triggered.eventTrigger.eventFilters)) {
209
- if (k === "topic" && !v.startsWith("projects/")) {
210
- triggered.eventTrigger.eventFilters[k] = `projects/${project}/topics/${v}`;
202
+ const eventTrigger = {
203
+ eventType: ep.eventTrigger.eventType,
204
+ retry: ep.eventTrigger.retry,
205
+ };
206
+ (0, proto_1.renameIfPresent)(eventTrigger, ep.eventTrigger, "serviceAccount", "serviceAccountEmail");
207
+ (0, proto_1.copyIfPresent)(eventTrigger, ep.eventTrigger, "serviceAccount", "eventFilterPathPatterns", "region");
208
+ (0, proto_1.convertIfPresent)(eventTrigger, ep.eventTrigger, "channel", (c) => resolveChannelName(project, c, defaultRegion));
209
+ (0, proto_1.convertIfPresent)(eventTrigger, ep.eventTrigger, "eventFilters", (filters) => {
210
+ const copy = Object.assign({}, filters);
211
+ if (copy["topic"] && !copy["topic"].startsWith("projects/")) {
212
+ copy["topic"] = `projects/${project}/topics/${copy["topic"]}`;
211
213
  }
212
- }
214
+ return copy;
215
+ });
216
+ triggered = { eventTrigger };
213
217
  }
214
218
  else if (backend.isHttpsTriggered(ep)) {
215
219
  triggered = { httpsTrigger: {} };
@@ -221,39 +225,39 @@ function parseEndpointForBuild(id, ep, project, defaultRegion, runtime) {
221
225
  else if (backend.isScheduleTriggered(ep)) {
222
226
  const st = {
223
227
  schedule: ep.scheduleTrigger.schedule || "",
224
- timeZone: ep.scheduleTrigger.timeZone || "",
225
- retryConfig: {},
228
+ timeZone: (_a = ep.scheduleTrigger.timeZone) !== null && _a !== void 0 ? _a : null,
226
229
  };
227
230
  if (ep.scheduleTrigger.retryConfig) {
228
- st.retryConfig = {
229
- retryCount: ep.scheduleTrigger.retryConfig.retryCount,
230
- maxDoublings: ep.scheduleTrigger.retryConfig.maxDoublings,
231
- };
232
- if (ep.scheduleTrigger.retryConfig.maxRetryDuration) {
233
- st.retryConfig.maxRetrySeconds = (0, proto_1.secondsFromDuration)(ep.scheduleTrigger.retryConfig.maxRetryDuration);
234
- }
235
- if (ep.scheduleTrigger.retryConfig.maxBackoffDuration) {
236
- st.retryConfig.maxBackoffSeconds = (0, proto_1.secondsFromDuration)(ep.scheduleTrigger.retryConfig.maxBackoffDuration);
237
- }
238
- if (ep.scheduleTrigger.retryConfig.minBackoffDuration) {
239
- st.retryConfig.minBackoffSeconds = (0, proto_1.secondsFromDuration)(ep.scheduleTrigger.retryConfig.minBackoffDuration);
240
- }
231
+ st.retryConfig = {};
232
+ (0, proto_1.copyIfPresent)(st.retryConfig, ep.scheduleTrigger.retryConfig, "retryCount", "minBackoffSeconds", "maxBackoffSeconds", "maxRetrySeconds", "maxDoublings");
233
+ (0, proto_1.convertIfPresent)(st.retryConfig, ep.scheduleTrigger.retryConfig, "minBackoffSeconds", "minBackoffDuration", (0, functional_1.nullsafeVisitor)(proto_1.secondsFromDuration));
234
+ (0, proto_1.convertIfPresent)(st.retryConfig, ep.scheduleTrigger.retryConfig, "maxBackoffSeconds", "maxBackoffDuration", (0, functional_1.nullsafeVisitor)(proto_1.secondsFromDuration));
235
+ (0, proto_1.convertIfPresent)(st.retryConfig, ep.scheduleTrigger.retryConfig, "maxRetrySeconds", "maxRetryDuration", (0, functional_1.nullsafeVisitor)(proto_1.secondsFromDuration));
236
+ }
237
+ else if (ep.scheduleTrigger.retryConfig === null) {
238
+ st.retryConfig = null;
241
239
  }
242
240
  triggered = { scheduleTrigger: st };
243
241
  }
244
242
  else if (backend.isTaskQueueTriggered(ep)) {
245
- const tq = {
246
- invoker: ep.taskQueueTrigger.invoker,
247
- rateLimits: ep.taskQueueTrigger.rateLimits,
248
- };
243
+ const tq = {};
244
+ if (ep.taskQueueTrigger.invoker) {
245
+ tq.invoker = ep.taskQueueTrigger.invoker;
246
+ }
247
+ else if (ep.taskQueueTrigger.invoker === null) {
248
+ tq.invoker = null;
249
+ }
249
250
  if (ep.taskQueueTrigger.retryConfig) {
250
- tq.retryConfig = {
251
- maxRetryDurationSeconds: ep.taskQueueTrigger.retryConfig.maxRetrySeconds,
252
- maxBackoffSeconds: ep.taskQueueTrigger.retryConfig.maxBackoffSeconds,
253
- minBackoffSeconds: ep.taskQueueTrigger.retryConfig.minBackoffSeconds,
254
- maxDoublings: ep.taskQueueTrigger.retryConfig.maxDoublings,
255
- maxAttempts: ep.taskQueueTrigger.retryConfig.maxAttempts,
256
- };
251
+ tq.retryConfig = Object.assign({}, ep.taskQueueTrigger.retryConfig);
252
+ }
253
+ else if (ep.taskQueueTrigger.retryConfig === null) {
254
+ tq.retryConfig = null;
255
+ }
256
+ if (ep.taskQueueTrigger.rateLimits) {
257
+ tq.rateLimits = Object.assign({}, ep.taskQueueTrigger.rateLimits);
258
+ }
259
+ else if (ep.taskQueueTrigger.rateLimits === null) {
260
+ tq.rateLimits = null;
257
261
  }
258
262
  triggered = { taskQueueTrigger: tq };
259
263
  }
@@ -265,18 +269,16 @@ function parseEndpointForBuild(id, ep, project, defaultRegion, runtime) {
265
269
  "firebase-tools with npm install -g firebase-tools@latest");
266
270
  }
267
271
  const parsed = Object.assign({ platform: ep.platform || "gcfv2", region: ep.region || [defaultRegion], project,
268
- runtime, entryPoint: ep.entryPoint, serviceAccount: ep.serviceAccountEmail || null }, triggered);
269
- (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables");
270
- (0, proto_1.renameIfPresent)(parsed, ep, "secretEnvironmentVariables", "secretEnvironmentVariables", (senvs) => {
271
- const secretEnvironmentVariables = [];
272
- for (const { key, secret } of senvs) {
273
- secretEnvironmentVariables.push({
274
- key,
275
- secret: secret || key,
276
- projectId: project,
277
- });
272
+ runtime, entryPoint: ep.entryPoint }, triggered);
273
+ (0, proto_1.renameIfPresent)(parsed, ep, "serviceAccount", "serviceAccountEmail");
274
+ (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "cpu", "maxInstances", "minInstances", "concurrency", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "serviceAccount");
275
+ (0, proto_1.convertIfPresent)(parsed, ep, "secretEnvironmentVariables", (senvs) => {
276
+ if (!senvs) {
277
+ return null;
278
278
  }
279
- return secretEnvironmentVariables;
279
+ return senvs.map(({ key, secret }) => {
280
+ return { key, secret: secret || key, projectId: project };
281
+ });
280
282
  });
281
283
  return parsed;
282
284
  }
@@ -284,17 +286,25 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
284
286
  const allParsed = [];
285
287
  const prefix = `endpoints[${id}]`;
286
288
  const ep = manifest.endpoints[id];
287
- assertManifestEndpoint(ep, prefix);
289
+ assertManifestEndpoint(ep, id);
288
290
  for (const region of ep.region || [defaultRegion]) {
289
291
  let triggered;
290
292
  if (backend.isEventTriggered(ep)) {
291
- triggered = { eventTrigger: ep.eventTrigger };
292
- (0, proto_1.renameIfPresent)(triggered.eventTrigger, ep.eventTrigger, "channel", "channel", (c) => resolveChannelName(project, c, defaultRegion));
293
- for (const [k, v] of Object.entries(triggered.eventTrigger.eventFilters)) {
294
- if (k === "topic" && !v.startsWith("projects/")) {
295
- triggered.eventTrigger.eventFilters[k] = `projects/${project}/topics/${v}`;
293
+ const eventTrigger = {
294
+ eventType: ep.eventTrigger.eventType,
295
+ retry: false,
296
+ };
297
+ (0, proto_1.renameIfPresent)(eventTrigger, ep.eventTrigger, "serviceAccount", "serviceAccountEmail");
298
+ (0, proto_1.copyIfPresent)(eventTrigger, ep.eventTrigger, "eventFilterPathPatterns", "retry", "serviceAccount", "region");
299
+ (0, proto_1.convertIfPresent)(eventTrigger, ep.eventTrigger, "channel", (c) => resolveChannelName(project, c, defaultRegion));
300
+ (0, proto_1.convertIfPresent)(eventTrigger, ep.eventTrigger, "eventFilters", (filters) => {
301
+ const copy = Object.assign({}, filters);
302
+ if (copy["topic"] && !copy["topic"].startsWith("projects/")) {
303
+ copy["topic"] = `projects/${project}/topics/${copy["topic"]}`;
296
304
  }
297
- }
305
+ return copy;
306
+ });
307
+ triggered = { eventTrigger };
298
308
  }
299
309
  else if (backend.isHttpsTriggered(ep)) {
300
310
  triggered = { httpsTrigger: {} };
@@ -321,17 +331,15 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
321
331
  region,
322
332
  project,
323
333
  runtime, entryPoint: ep.entryPoint }, triggered);
324
- (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "cpu");
325
- (0, proto_1.renameIfPresent)(parsed, ep, "secretEnvironmentVariables", "secretEnvironmentVariables", (senvs) => {
326
- const secretEnvironmentVariables = [];
327
- for (const { key, secret } of senvs) {
328
- secretEnvironmentVariables.push({
329
- key,
330
- secret: secret || key,
331
- projectId: project,
332
- });
334
+ (0, proto_1.renameIfPresent)(parsed, ep, "serviceAccount", "serviceAccountEmail");
335
+ (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccount", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "cpu");
336
+ (0, proto_1.convertIfPresent)(parsed, ep, "secretEnvironmentVariables", (senvs) => {
337
+ if (!senvs) {
338
+ return null;
333
339
  }
334
- return secretEnvironmentVariables;
340
+ return senvs.map(({ key, secret }) => {
341
+ return { key, secret: secret || key, projectId: project };
342
+ });
335
343
  });
336
344
  allParsed.push(parsed);
337
345
  }
@@ -10,6 +10,7 @@ const backend = require("../../backend");
10
10
  const api = require("../../../../api");
11
11
  const proto = require("../../../../gcp/proto");
12
12
  const events = require("../../../../functions/events");
13
+ const functional_1 = require("../../../../functional");
13
14
  const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js");
14
15
  function removeInspectOptions(options) {
15
16
  return options.filter((opt) => !opt.startsWith("--inspect"));
@@ -88,8 +89,9 @@ function mergeRequiredAPIs(backend) {
88
89
  }
89
90
  exports.mergeRequiredAPIs = mergeRequiredAPIs;
90
91
  function addResourcesToBuild(projectId, runtime, annotation, want) {
91
- var _a;
92
+ var _a, _b;
92
93
  Object.freeze(annotation);
94
+ const toSeconds = (0, functional_1.nullsafeVisitor)(proto.secondsFromDuration);
93
95
  const regions = annotation.regions || [api.functionsDefaultRegion];
94
96
  let triggered;
95
97
  const triggerCount = +!!annotation.httpsTrigger +
@@ -110,9 +112,11 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
110
112
  proto.copyIfPresent(triggered.taskQueueTrigger, annotation.taskQueueTrigger, "invoker");
111
113
  proto.copyIfPresent(triggered.taskQueueTrigger, annotation.taskQueueTrigger, "rateLimits");
112
114
  if (annotation.taskQueueTrigger.retryConfig) {
113
- triggered.taskQueueTrigger.retryConfig = Object.assign(annotation.taskQueueTrigger.retryConfig, {
114
- maxRetryDurationSeconds: proto.secondsFromDuration(annotation.taskQueueTrigger.retryConfig.maxRetryDuration || "0"),
115
- });
115
+ triggered.taskQueueTrigger.retryConfig = {};
116
+ proto.copyIfPresent(triggered.taskQueueTrigger.retryConfig, annotation.taskQueueTrigger.retryConfig, "maxAttempts", "maxDoublings");
117
+ proto.convertIfPresent(triggered.taskQueueTrigger.retryConfig, annotation.taskQueueTrigger.retryConfig, "minBackoffSeconds", "minBackoff", toSeconds);
118
+ proto.convertIfPresent(triggered.taskQueueTrigger.retryConfig, annotation.taskQueueTrigger.retryConfig, "maxBackoffSeconds", "maxBackoff", toSeconds);
119
+ proto.convertIfPresent(triggered.taskQueueTrigger.retryConfig, annotation.taskQueueTrigger.retryConfig, "maxRetryDurationSeconds", "maxRetryDuration", toSeconds);
116
120
  }
117
121
  }
118
122
  else if (annotation.httpsTrigger) {
@@ -139,21 +143,16 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
139
143
  triggered = {
140
144
  scheduleTrigger: {
141
145
  schedule: annotation.schedule.schedule,
142
- timeZone: annotation.schedule.timeZone || "America/Los_Angeles",
146
+ timeZone: (_b = annotation.schedule.timeZone) !== null && _b !== void 0 ? _b : null,
143
147
  retryConfig: {},
144
148
  },
145
149
  };
146
150
  if (annotation.schedule.retryConfig) {
147
- if (annotation.schedule.retryConfig.maxBackoffDuration) {
148
- triggered.scheduleTrigger.retryConfig.maxBackoffSeconds = proto.secondsFromDuration(annotation.schedule.retryConfig.maxBackoffDuration);
149
- }
150
- if (annotation.schedule.retryConfig.minBackoffDuration) {
151
- triggered.scheduleTrigger.retryConfig.minBackoffSeconds = proto.secondsFromDuration(annotation.schedule.retryConfig.minBackoffDuration);
152
- }
153
- if (annotation.schedule.retryConfig.maxRetryDuration) {
154
- triggered.scheduleTrigger.retryConfig.maxRetrySeconds = proto.secondsFromDuration(annotation.schedule.retryConfig.maxRetryDuration);
155
- }
156
- proto.copyIfPresent(triggered.scheduleTrigger.retryConfig, annotation.schedule.retryConfig, "maxDoublings", "retryCount");
151
+ triggered.scheduleTrigger.retryConfig = {};
152
+ proto.copyIfPresent(triggered.scheduleTrigger.retryConfig, annotation.schedule.retryConfig, "retryCount", "maxDoublings");
153
+ proto.convertIfPresent(triggered.scheduleTrigger.retryConfig, annotation.schedule.retryConfig, "maxRetrySeconds", "maxRetryDuration", toSeconds);
154
+ proto.convertIfPresent(triggered.scheduleTrigger.retryConfig, annotation.schedule.retryConfig, "minBackoffSeconds", "minBackoffDuration", toSeconds);
155
+ proto.convertIfPresent(triggered.scheduleTrigger.retryConfig, annotation.schedule.retryConfig, "maxBackoffSeconds", "maxBackoffDuration", toSeconds);
157
156
  }
158
157
  }
159
158
  else if (annotation.blockingTrigger) {
@@ -169,7 +168,7 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
169
168
  },
170
169
  };
171
170
  }
172
- else {
171
+ else if (annotation.eventTrigger) {
173
172
  triggered = {
174
173
  eventTrigger: {
175
174
  eventType: annotation.eventTrigger.eventType,
@@ -178,8 +177,13 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
178
177
  },
179
178
  };
180
179
  }
180
+ else {
181
+ throw new error_1.FirebaseError("Do not understand Cloud Function annotation without a trigger" +
182
+ JSON.stringify(annotation, null, 2));
183
+ }
181
184
  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 || null }, triggered);
185
+ const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", region: regions, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime }, triggered);
186
+ proto.renameIfPresent(endpoint, annotation, "serviceAccount", "serviceAccountEmail");
183
187
  if (annotation.vpcConnector != null) {
184
188
  let maybeId = annotation.vpcConnector;
185
189
  if (maybeId && !maybeId.includes("/")) {
@@ -188,8 +192,17 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
188
192
  endpoint.vpc = { connector: maybeId };
189
193
  proto.renameIfPresent(endpoint.vpc, annotation, "egressSettings", "vpcConnectorEgressSettings");
190
194
  }
191
- proto.copyIfPresent(endpoint, annotation, "concurrency", "labels", "ingressSettings", "maxInstances", "minInstances", "availableMemoryMb");
192
- proto.renameIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
195
+ proto.copyIfPresent(endpoint, annotation, "concurrency", "labels", "maxInstances", "minInstances", "availableMemoryMb");
196
+ proto.convertIfPresent(endpoint, annotation, "ingressSettings", (str) => {
197
+ if (str === null) {
198
+ return null;
199
+ }
200
+ if (!backend.AllIngressSettings.includes(str)) {
201
+ throw new Error(`Invalid ingress setting ${str}`);
202
+ }
203
+ return str;
204
+ });
205
+ proto.convertIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
193
206
  want.endpoints[endpointId] = endpoint;
194
207
  }
195
208
  exports.addResourcesToBuild = addResourcesToBuild;
@@ -285,8 +298,27 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
285
298
  }
286
299
  endpoint.secretEnvironmentVariables = secretEnvs;
287
300
  }
288
- proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "ingressSettings", "maxInstances", "minInstances", "availableMemoryMb");
289
- proto.renameIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
301
+ proto.copyIfPresent(endpoint, annotation, "concurrency", "labels", "maxInstances", "minInstances");
302
+ proto.renameIfPresent(endpoint, annotation, "serviceAccount", "serviceAccountEmail");
303
+ proto.convertIfPresent(endpoint, annotation, "ingressSettings", (ingress) => {
304
+ if (ingress == null) {
305
+ return null;
306
+ }
307
+ if (!backend.AllIngressSettings.includes(ingress)) {
308
+ throw new error_1.FirebaseError(`Invalid ingress setting ${ingress}`);
309
+ }
310
+ return ingress;
311
+ });
312
+ proto.convertIfPresent(endpoint, annotation, "availableMemoryMb", (mem) => {
313
+ if (mem === null) {
314
+ return null;
315
+ }
316
+ if (!backend.isValidMemoryOption(mem)) {
317
+ throw new error_1.FirebaseError(`This version of firebase-tools does not know about the memory option ${mem}. Is an upgrade necessary?`);
318
+ }
319
+ return mem;
320
+ });
321
+ proto.convertIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
290
322
  want.endpoints[region] = want.endpoints[region] || {};
291
323
  want.endpoints[region][endpoint.id] = endpoint;
292
324
  mergeRequiredAPIs(want);
@@ -17,9 +17,15 @@ async function obtainStorageBindings(projectNumber) {
17
17
  }
18
18
  exports.obtainStorageBindings = obtainStorageBindings;
19
19
  async function ensureStorageTriggerRegion(endpoint) {
20
+ var _a;
20
21
  const { eventTrigger } = endpoint;
21
22
  if (!eventTrigger.region) {
22
23
  logger_1.logger.debug("Looking up bucket region for the storage event trigger");
24
+ if (!((_a = eventTrigger.eventFilters) === null || _a === void 0 ? void 0 : _a.bucket)) {
25
+ throw new error_1.FirebaseError("Error: storage event trigger is missing bucket filter: " +
26
+ JSON.stringify(eventTrigger, null, 2));
27
+ }
28
+ logger_1.logger.debug(`Looking up bucket region for the storage event trigger on bucket ${eventTrigger.eventFilters.bucket}`);
23
29
  try {
24
30
  const bucket = await storage.getBucket(eventTrigger.eventFilters.bucket);
25
31
  eventTrigger.region = bucket.location.toLowerCase();