firebase-tools 9.17.0 → 9.21.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +3 -7
  2. package/lib/api.js +1 -0
  3. package/lib/apiv2.js +5 -3
  4. package/lib/appdistribution/client.js +84 -72
  5. package/lib/appdistribution/distribution.js +8 -26
  6. package/lib/appdistribution/options-parser-util.js +51 -0
  7. package/lib/command.js +1 -1
  8. package/lib/commands/appdistribution-distribute.js +74 -91
  9. package/lib/commands/appdistribution-testers-add.js +18 -0
  10. package/lib/commands/appdistribution-testers-remove.js +32 -0
  11. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  12. package/lib/commands/ext-configure.js +9 -1
  13. package/lib/commands/ext-dev-extension-delete.js +2 -1
  14. package/lib/commands/ext-dev-publish.js +11 -4
  15. package/lib/commands/ext-dev-unpublish.js +12 -4
  16. package/lib/commands/ext-install.js +115 -48
  17. package/lib/commands/ext-uninstall.js +6 -0
  18. package/lib/commands/ext-update.js +61 -18
  19. package/lib/commands/functions-config-export.js +115 -0
  20. package/lib/commands/functions-delete.js +45 -25
  21. package/lib/commands/functions-list.js +54 -0
  22. package/lib/commands/functions-log.js +5 -22
  23. package/lib/commands/hosting-channel-deploy.js +6 -4
  24. package/lib/commands/index.js +12 -0
  25. package/lib/deploy/functions/backend.js +118 -121
  26. package/lib/deploy/functions/checkIam.js +8 -8
  27. package/lib/deploy/functions/containerCleaner.js +5 -1
  28. package/lib/deploy/functions/deploy.js +11 -15
  29. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  30. package/lib/deploy/functions/prepare.js +67 -33
  31. package/lib/deploy/functions/pricing.js +17 -17
  32. package/lib/deploy/functions/prompts.js +24 -41
  33. package/lib/deploy/functions/release/executor.js +39 -0
  34. package/lib/deploy/functions/release/fabricator.js +362 -0
  35. package/lib/deploy/functions/release/index.js +69 -0
  36. package/lib/deploy/functions/release/planner.js +159 -0
  37. package/lib/deploy/functions/release/reporter.js +162 -0
  38. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  39. package/lib/deploy/functions/release/timer.js +14 -0
  40. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +102 -126
  41. package/lib/deploy/functions/runtimes/node/parseTriggers.js +34 -50
  42. package/lib/deploy/functions/triggerRegionHelper.js +40 -0
  43. package/lib/deploy/functions/validate.js +1 -24
  44. package/lib/downloadUtils.js +37 -0
  45. package/lib/emulator/auth/apiSpec.js +1788 -403
  46. package/lib/emulator/auth/handlers.js +6 -5
  47. package/lib/emulator/auth/operations.js +439 -40
  48. package/lib/emulator/auth/server.js +32 -11
  49. package/lib/emulator/auth/state.js +205 -5
  50. package/lib/emulator/auth/widget_ui.js +2 -2
  51. package/lib/emulator/download.js +2 -31
  52. package/lib/emulator/downloadableEmulators.js +7 -7
  53. package/lib/emulator/emulatorLogger.js +0 -3
  54. package/lib/emulator/events/types.js +16 -0
  55. package/lib/emulator/functionsEmulator.js +117 -20
  56. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  57. package/lib/emulator/functionsEmulatorShared.js +51 -7
  58. package/lib/emulator/functionsEmulatorShell.js +1 -1
  59. package/lib/emulator/pubsubEmulator.js +61 -40
  60. package/lib/emulator/storage/cloudFunctions.js +37 -7
  61. package/lib/extensions/askUserForConsent.js +16 -13
  62. package/lib/extensions/askUserForParam.js +72 -3
  63. package/lib/extensions/billingMigrationHelper.js +1 -11
  64. package/lib/extensions/changelog.js +2 -1
  65. package/lib/extensions/displayExtensionInfo.js +35 -33
  66. package/lib/extensions/emulator/optionsHelper.js +3 -3
  67. package/lib/extensions/emulator/triggerHelper.js +2 -32
  68. package/lib/extensions/extensionsApi.js +67 -94
  69. package/lib/extensions/extensionsHelper.js +49 -35
  70. package/lib/extensions/paramHelper.js +79 -36
  71. package/lib/extensions/refs.js +59 -0
  72. package/lib/extensions/secretsUtils.js +58 -0
  73. package/lib/extensions/updateHelper.js +21 -45
  74. package/lib/extensions/warnings.js +1 -7
  75. package/lib/functional.js +64 -0
  76. package/lib/functions/env.js +26 -13
  77. package/lib/functions/functionslog.js +40 -0
  78. package/lib/functions/runtimeConfigExport.js +137 -0
  79. package/lib/gcp/cloudfunctions.js +46 -38
  80. package/lib/gcp/cloudfunctionsv2.js +47 -47
  81. package/lib/gcp/cloudlogging.js +27 -21
  82. package/lib/gcp/cloudscheduler.js +22 -16
  83. package/lib/gcp/pubsub.js +1 -9
  84. package/lib/gcp/secretManager.js +111 -0
  85. package/lib/gcp/storage.js +16 -0
  86. package/lib/previews.js +1 -1
  87. package/lib/requireInteractive.js +12 -0
  88. package/lib/utils.js +30 -1
  89. package/package.json +5 -4
  90. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  91. package/lib/deploy/functions/deploymentTimer.js +0 -23
  92. package/lib/deploy/functions/errorHandler.js +0 -75
  93. package/lib/deploy/functions/release.js +0 -116
  94. package/lib/deploy/functions/tasks.js +0 -324
  95. package/lib/functionsDelete.js +0 -60
@@ -1,38 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.topicName = exports.scheduleName = exports.sameFunctionName = exports.functionName = exports.isEmptyBackend = exports.empty = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.triggerTag = exports.isEventTrigger = void 0;
3
+ exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
4
4
  const gcf = require("../../gcp/cloudfunctions");
5
5
  const gcfV2 = require("../../gcp/cloudfunctionsv2");
6
6
  const utils = require("../../utils");
7
7
  const error_1 = require("../../error");
8
8
  const previews_1 = require("../../previews");
9
- function isEventTrigger(trigger) {
10
- return "eventType" in trigger;
11
- }
12
- exports.isEventTrigger = isEventTrigger;
13
- function triggerTag(fn) {
14
- var _a, _b;
15
- if ((_a = fn.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) {
16
- if (fn.platform === "gcfv1") {
17
- return "v1.scheduled";
18
- }
19
- return "v2.scheduled";
9
+ function endpointTriggerType(endpoint) {
10
+ if (isScheduleTriggered(endpoint)) {
11
+ return "scheduled";
20
12
  }
21
- if ((_b = fn.labels) === null || _b === void 0 ? void 0 : _b["deployment-callable"]) {
22
- if (fn.platform === "gcfv1") {
23
- return "v1.callable";
24
- }
25
- return "v2.callable";
13
+ else if (isHttpsTriggered(endpoint)) {
14
+ return "https";
26
15
  }
27
- if (!isEventTrigger(fn.trigger)) {
28
- if (fn.platform === "gcfv1") {
29
- return "v1.https";
30
- }
31
- return "v2.https";
16
+ else if (isEventTriggered(endpoint)) {
17
+ return endpoint.eventTrigger.eventType;
18
+ }
19
+ else {
20
+ throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
32
21
  }
33
- return fn.trigger.eventType;
34
22
  }
35
- exports.triggerTag = triggerTag;
23
+ exports.endpointTriggerType = endpointTriggerType;
36
24
  function memoryOptionDisplayName(option) {
37
25
  return {
38
26
  128: "128MB",
@@ -46,38 +34,46 @@ function memoryOptionDisplayName(option) {
46
34
  }
47
35
  exports.memoryOptionDisplayName = memoryOptionDisplayName;
48
36
  exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
37
+ function isHttpsTriggered(triggered) {
38
+ return {}.hasOwnProperty.call(triggered, "httpsTrigger");
39
+ }
40
+ exports.isHttpsTriggered = isHttpsTriggered;
41
+ function isEventTriggered(triggered) {
42
+ return {}.hasOwnProperty.call(triggered, "eventTrigger");
43
+ }
44
+ exports.isEventTriggered = isEventTriggered;
45
+ function isScheduleTriggered(triggered) {
46
+ return {}.hasOwnProperty.call(triggered, "scheduleTrigger");
47
+ }
48
+ exports.isScheduleTriggered = isScheduleTriggered;
49
49
  function empty() {
50
50
  return {
51
51
  requiredAPIs: {},
52
- cloudFunctions: [],
53
- schedules: [],
54
- topics: [],
52
+ endpoints: {},
55
53
  environmentVariables: {},
56
54
  };
57
55
  }
58
56
  exports.empty = empty;
57
+ function of(...endpoints) {
58
+ const bkend = Object.assign({}, empty());
59
+ for (const endpoint of endpoints) {
60
+ bkend.endpoints[endpoint.region] = bkend.endpoints[endpoint.region] || {};
61
+ if (bkend.endpoints[endpoint.region][endpoint.id]) {
62
+ throw new Error("Trying to create a backend with the same endpiont twice");
63
+ }
64
+ bkend.endpoints[endpoint.region][endpoint.id] = endpoint;
65
+ }
66
+ return bkend;
67
+ }
68
+ exports.of = of;
59
69
  function isEmptyBackend(backend) {
60
- return (Object.keys(backend.requiredAPIs).length == 0 &&
61
- backend.cloudFunctions.length === 0 &&
62
- backend.schedules.length === 0 &&
63
- backend.topics.length === 0);
70
+ return (Object.keys(backend.requiredAPIs).length == 0 && Object.keys(backend.endpoints).length === 0);
64
71
  }
65
72
  exports.isEmptyBackend = isEmptyBackend;
66
73
  function functionName(cloudFunction) {
67
74
  return `projects/${cloudFunction.project}/locations/${cloudFunction.region}/functions/${cloudFunction.id}`;
68
75
  }
69
76
  exports.functionName = functionName;
70
- exports.sameFunctionName = (func) => (test) => {
71
- return func.id === test.id && func.region === test.region && func.project == test.project;
72
- };
73
- function scheduleName(schedule, appEngineLocation) {
74
- return `projects/${schedule.project}/locations/${appEngineLocation}/jobs/${schedule.id}`;
75
- }
76
- exports.scheduleName = scheduleName;
77
- function topicName(topic) {
78
- return `projects/${topic.project}/topics/${topic.id}`;
79
- }
80
- exports.topicName = topicName;
81
77
  function scheduleIdForFunction(cloudFunction) {
82
78
  return `firebase-schedule-${cloudFunction.id}-${cloudFunction.region}`;
83
79
  }
@@ -91,94 +87,39 @@ async function existingBackend(context, forceRefresh) {
91
87
  }
92
88
  exports.existingBackend = existingBackend;
93
89
  async function loadExistingBackend(ctx) {
94
- var _a, _b, _c;
90
+ var _a;
95
91
  ctx.loadedExistingBackend = true;
96
- ctx.existingBackend = {
97
- requiredAPIs: {},
98
- cloudFunctions: [],
99
- schedules: [],
100
- topics: [],
101
- environmentVariables: {},
102
- };
92
+ ctx.existingBackend = Object.assign({}, empty());
103
93
  ctx.unreachableRegions = {
104
94
  gcfV1: [],
105
95
  gcfV2: [],
106
96
  };
107
97
  const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
108
98
  for (const apiFunction of gcfV1Results.functions) {
109
- const specFunction = gcf.specFromFunction(apiFunction);
110
- ctx.existingBackend.cloudFunctions.push(specFunction);
111
- const isScheduled = ((_a = apiFunction.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) === "true";
112
- if (isScheduled) {
113
- const id = scheduleIdForFunction(specFunction);
114
- ctx.existingBackend.schedules.push({
115
- id,
116
- project: specFunction.project,
117
- transport: "pubsub",
118
- targetService: {
119
- id: specFunction.id,
120
- region: specFunction.region,
121
- project: specFunction.project,
122
- },
123
- });
124
- ctx.existingBackend.topics.push({
125
- id,
126
- project: specFunction.project,
127
- labels: exports.SCHEDULED_FUNCTION_LABEL,
128
- targetService: {
129
- id: specFunction.id,
130
- region: specFunction.region,
131
- project: specFunction.project,
132
- },
133
- });
134
- }
99
+ const endpoint = gcf.endpointFromFunction(apiFunction);
100
+ ctx.existingBackend.endpoints[endpoint.region] =
101
+ ctx.existingBackend.endpoints[endpoint.region] || {};
102
+ ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
135
103
  }
136
104
  ctx.unreachableRegions.gcfV1 = gcfV1Results.unreachable;
137
105
  if (!previews_1.previews.functionsv2) {
138
106
  return;
139
107
  }
140
- const gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
141
- for (const apiFunction of gcfV2Results.functions) {
142
- const specFunction = gcfV2.specFromFunction(apiFunction);
143
- ctx.existingBackend.cloudFunctions.push(specFunction);
144
- const pubsubScheduled = ((_b = apiFunction.labels) === null || _b === void 0 ? void 0 : _b["deployment-scheduled"]) === "true";
145
- const httpsScheduled = ((_c = apiFunction.labels) === null || _c === void 0 ? void 0 : _c["deployment-scheduled"]) === "https";
146
- if (pubsubScheduled) {
147
- const id = scheduleIdForFunction(specFunction);
148
- ctx.existingBackend.schedules.push({
149
- id,
150
- project: specFunction.project,
151
- transport: "pubsub",
152
- targetService: {
153
- id: specFunction.id,
154
- region: specFunction.region,
155
- project: specFunction.project,
156
- },
157
- });
158
- ctx.existingBackend.topics.push({
159
- id,
160
- project: specFunction.project,
161
- labels: exports.SCHEDULED_FUNCTION_LABEL,
162
- targetService: {
163
- id: specFunction.id,
164
- region: specFunction.region,
165
- project: specFunction.project,
166
- },
167
- });
168
- }
169
- if (httpsScheduled) {
170
- const id = scheduleIdForFunction(specFunction);
171
- ctx.existingBackend.schedules.push({
172
- id,
173
- project: specFunction.project,
174
- transport: "https",
175
- targetService: {
176
- id: specFunction.id,
177
- region: specFunction.region,
178
- project: specFunction.project,
179
- },
180
- });
108
+ let gcfV2Results;
109
+ try {
110
+ gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
111
+ }
112
+ catch (err) {
113
+ if (err.status === 404 && ((_a = err.message) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("method not found"))) {
114
+ return;
181
115
  }
116
+ throw err;
117
+ }
118
+ for (const apiFunction of gcfV2Results.functions) {
119
+ const endpoint = gcfV2.endpointFromFunction(apiFunction);
120
+ ctx.existingBackend.endpoints[endpoint.region] =
121
+ ctx.existingBackend.endpoints[endpoint.region] || {};
122
+ ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
182
123
  }
183
124
  ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
184
125
  }
@@ -189,12 +130,12 @@ async function checkAvailability(context, want) {
189
130
  }
190
131
  const gcfV1Regions = new Set();
191
132
  const gcfV2Regions = new Set();
192
- for (const fn of want.cloudFunctions) {
193
- if (fn.platform == "gcfv1") {
194
- gcfV1Regions.add(fn.region);
133
+ for (const ep of allEndpoints(want)) {
134
+ if (ep.platform == "gcfv1") {
135
+ gcfV1Regions.add(ep.region);
195
136
  }
196
137
  else {
197
- gcfV2Regions.add(fn.region);
138
+ gcfV2Regions.add(ep.region);
198
139
  }
199
140
  }
200
141
  const neededUnreachableV1 = ctx.unreachableRegions.gcfV1.filter((region) => gcfV1Regions.has(region));
@@ -221,3 +162,59 @@ async function checkAvailability(context, want) {
221
162
  }
222
163
  }
223
164
  exports.checkAvailability = checkAvailability;
165
+ function allEndpoints(backend) {
166
+ return Object.values(backend.endpoints).reduce((accum, perRegion) => {
167
+ return [...accum, ...Object.values(perRegion)];
168
+ }, []);
169
+ }
170
+ exports.allEndpoints = allEndpoints;
171
+ function someEndpoint(backend, predicate) {
172
+ for (const endpoints of Object.values(backend.endpoints)) {
173
+ if (Object.values(endpoints).some(predicate)) {
174
+ return true;
175
+ }
176
+ }
177
+ return false;
178
+ }
179
+ exports.someEndpoint = someEndpoint;
180
+ function matchingBackend(backend, predicate) {
181
+ const filtered = Object.assign({}, empty());
182
+ for (const endpoint of allEndpoints(backend)) {
183
+ if (!predicate(endpoint)) {
184
+ continue;
185
+ }
186
+ filtered.endpoints[endpoint.region] = filtered.endpoints[endpoint.region] || {};
187
+ filtered.endpoints[endpoint.region][endpoint.id] = endpoint;
188
+ }
189
+ return filtered;
190
+ }
191
+ exports.matchingBackend = matchingBackend;
192
+ function regionalEndpoints(backend, region) {
193
+ return backend.endpoints[region] ? Object.values(backend.endpoints[region]) : [];
194
+ }
195
+ exports.regionalEndpoints = regionalEndpoints;
196
+ exports.hasEndpoint = (backend) => (endpoint) => {
197
+ return !!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id];
198
+ };
199
+ exports.missingEndpoint = (backend) => (endpoint) => {
200
+ return !exports.hasEndpoint(backend)(endpoint);
201
+ };
202
+ function compareFunctions(left, right) {
203
+ if (left.platform != right.platform) {
204
+ return right.platform < left.platform ? -1 : 1;
205
+ }
206
+ if (left.region < right.region) {
207
+ return -1;
208
+ }
209
+ if (left.region > right.region) {
210
+ return 1;
211
+ }
212
+ if (left.id < right.id) {
213
+ return -1;
214
+ }
215
+ if (left.id > right.id) {
216
+ return 1;
217
+ }
218
+ return 0;
219
+ }
220
+ exports.compareFunctions = compareFunctions;
@@ -28,17 +28,17 @@ async function checkServiceAccountIam(projectId) {
28
28
  }
29
29
  exports.checkServiceAccountIam = checkServiceAccountIam;
30
30
  async function checkHttpIam(context, options, payload) {
31
- const functions = payload.functions.backend.cloudFunctions;
32
31
  const filterGroups = context.filters || functionsDeployHelper_1.getFilterGroups(options);
33
- const httpFunctions = functions
34
- .filter((f) => !backend.isEventTrigger(f.trigger))
32
+ const httpEndpoints = backend
33
+ .allEndpoints(payload.functions.backend)
34
+ .filter(backend.isHttpsTriggered)
35
35
  .filter((f) => functionsDeployHelper_1.functionMatchesAnyGroup(f, filterGroups));
36
- const existingFunctions = (await backend.existingBackend(context)).cloudFunctions;
37
- const newHttpFunctions = httpFunctions.filter((func) => !existingFunctions.find(backend.sameFunctionName(func)));
38
- if (newHttpFunctions.length === 0) {
36
+ const existing = await backend.existingBackend(context);
37
+ const newHttpsEndpoints = httpEndpoints.filter(backend.missingEndpoint(existing));
38
+ if (newHttpsEndpoints.length === 0) {
39
39
  return;
40
40
  }
41
- logger_1.logger.debug("[functions] found", newHttpFunctions.length, "new HTTP functions, testing setIamPolicy permission...");
41
+ logger_1.logger.debug("[functions] found", newHttpsEndpoints.length, "new HTTP functions, testing setIamPolicy permission...");
42
42
  let passed = true;
43
43
  try {
44
44
  const iamResult = await iam_1.testIamPermissions(context.projectId, [PERMISSION]);
@@ -51,7 +51,7 @@ async function checkHttpIam(context, options, payload) {
51
51
  if (!passed) {
52
52
  track("Error (User)", "deploy:functions:http_create_missing_iam");
53
53
  throw new error_1.FirebaseError(`Missing required permission on project ${cli_color_1.bold(context.projectId)} to deploy new HTTPS functions. The permission ${cli_color_1.bold(PERMISSION)} is required to deploy the following functions:\n\n- ` +
54
- newHttpFunctions.map((func) => func.id).join("\n- ") +
54
+ newHttpsEndpoints.map((func) => func.id).join("\n- ") +
55
55
  `\n\nTo address this error, please ask a project Owner to assign your account the "Cloud Functions Admin" role at the following URL:\n\nhttps://console.cloud.google.com/iam-admin/iam?project=${context.projectId}`);
56
56
  }
57
57
  logger_1.logger.debug("[functions] found setIamPolicy permission, proceeding with deploy");
@@ -37,10 +37,14 @@ async function retry(func) {
37
37
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
38
38
  const MAX_RETRIES = 3;
39
39
  const INITIAL_BACKOFF = 100;
40
+ const TIMEOUT_MS = 10000;
40
41
  let retry = 0;
41
42
  while (true) {
42
43
  try {
43
- return await func();
44
+ const timeout = new Promise((resolve, reject) => {
45
+ setTimeout(() => reject(new Error("Timeout")), TIMEOUT_MS);
46
+ });
47
+ return await Promise.race([func(), timeout]);
44
48
  }
45
49
  catch (error) {
46
50
  logger_1.logger.debug("Failed docker command with error", error);
@@ -11,6 +11,7 @@ const gcs = require("../../gcp/storage");
11
11
  const gcf = require("../../gcp/cloudfunctions");
12
12
  const gcfv2 = require("../../gcp/cloudfunctionsv2");
13
13
  const utils = require("../../utils");
14
+ const backend = require("./backend");
14
15
  const GCP_REGION = api_1.functionsUploadRegion;
15
16
  tmp_1.setGracefulCleanup();
16
17
  async function uploadSourceV1(context) {
@@ -37,34 +38,29 @@ async function deploy(context, options, payload) {
37
38
  if (!options.config.src.functions) {
38
39
  return;
39
40
  }
40
- await checkIam_1.checkHttpIam(context, options, payload);
41
41
  if (!context.functionsSourceV1 && !context.functionsSourceV2) {
42
42
  return;
43
43
  }
44
+ await checkIam_1.checkHttpIam(context, options, payload);
44
45
  try {
45
46
  const want = payload.functions.backend;
46
47
  const uploads = [];
47
- if (want.cloudFunctions.some((fn) => fn.platform === "gcfv1")) {
48
+ if (backend.allEndpoints(want).some((endpoint) => endpoint.platform === "gcfv1")) {
48
49
  uploads.push(uploadSourceV1(context));
49
50
  }
50
- if (want.cloudFunctions.some((fn) => fn.platform === "gcfv2")) {
51
- const functions = payload.functions.backend.cloudFunctions;
52
- const regions = [];
53
- for (const func of functions) {
54
- if (func.platform === "gcfv2" && -1 === regions.indexOf(func.region)) {
55
- regions.push(func.region);
56
- }
57
- }
58
- for (const region of regions) {
51
+ for (const region of Object.keys(want.endpoints)) {
52
+ if (backend.regionalEndpoints(want, region).some((e) => e.platform === "gcfv2")) {
59
53
  uploads.push(uploadSourceV2(context, region));
60
54
  }
61
55
  }
62
56
  await Promise.all(uploads);
63
57
  utils.assertDefined(options.config.src.functions.source, "Error: 'functions.source' is not defined");
64
- utils_1.logSuccess(clc.green.bold("functions:") +
65
- " " +
66
- clc.bold(options.config.src.functions.source) +
67
- " folder uploaded successfully");
58
+ if (uploads.length) {
59
+ utils_1.logSuccess(clc.green.bold("functions:") +
60
+ " " +
61
+ clc.bold(options.config.src.functions.source) +
62
+ " folder uploaded successfully");
63
+ }
68
64
  }
69
65
  catch (err) {
70
66
  utils_1.logWarning(clc.yellow("functions:") + " Upload Error: " + err.message);
@@ -1,11 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printTriggerUrls = exports.printSuccess = exports.logAndTrackDeployStats = exports.getFunctionLabel = exports.getFunctionId = exports.getFilterGroups = exports.functionMatchesGroup = exports.functionMatchesAnyGroup = void 0;
4
- const clc = require("cli-color");
5
- const logger_1 = require("../../logger");
6
- const backend = require("./backend");
7
- const track = require("../../track");
8
- const utils = require("../../utils");
3
+ exports.getFunctionLabel = exports.getFilterGroups = exports.functionMatchesGroup = exports.functionMatchesAnyGroup = void 0;
9
4
  function functionMatchesAnyGroup(func, filterGroups) {
10
5
  if (!filterGroups.length) {
11
6
  return true;
@@ -40,67 +35,7 @@ function getFilterGroups(options) {
40
35
  });
41
36
  }
42
37
  exports.getFilterGroups = getFilterGroups;
43
- function getFunctionId(fullName) {
44
- return fullName.split("/")[5];
45
- }
46
- exports.getFunctionId = getFunctionId;
47
- function getRegion(fullName) {
48
- return fullName.split("/")[3];
49
- }
50
- function getFunctionLabel(fnOrName) {
51
- if (typeof fnOrName === "string") {
52
- return getFunctionId(fnOrName) + "(" + getRegion(fnOrName) + ")";
53
- }
54
- else {
55
- return `${fnOrName.id}(${fnOrName.region})`;
56
- }
38
+ function getFunctionLabel(fn) {
39
+ return `${fn.id}(${fn.region})`;
57
40
  }
58
41
  exports.getFunctionLabel = getFunctionLabel;
59
- function logAndTrackDeployStats(queue, errorHandler) {
60
- const stats = queue.stats();
61
- logger_1.logger.debug(`Total Function Deployment time: ${stats.elapsed}`);
62
- logger_1.logger.debug(`${stats.total} Functions Deployed`);
63
- logger_1.logger.debug(`${errorHandler.errors.length} Functions Errored`);
64
- logger_1.logger.debug(`Average Function Deployment time: ${stats.avg}`);
65
- if (stats.total > 0) {
66
- if (errorHandler.errors.length === 0) {
67
- track("functions_deploy_result", "success", stats.total);
68
- }
69
- else if (errorHandler.errors.length < stats.total) {
70
- track("functions_deploy_result", "partial_success", stats.total - errorHandler.errors.length);
71
- track("functions_deploy_result", "partial_failure", errorHandler.errors.length);
72
- track("functions_deploy_result", "partial_error_ratio", errorHandler.errors.length / stats.total);
73
- }
74
- else {
75
- track("functions_deploy_result", "failure", stats.total);
76
- }
77
- }
78
- }
79
- exports.logAndTrackDeployStats = logAndTrackDeployStats;
80
- function printSuccess(func, type) {
81
- utils.logSuccess(clc.bold.green("functions[" + getFunctionLabel(func) + "]: ") +
82
- "Successful " +
83
- type +
84
- " operation. ");
85
- }
86
- exports.printSuccess = printSuccess;
87
- async function printTriggerUrls(context, want) {
88
- const have = await backend.existingBackend(context, true);
89
- const httpsFunctions = have.cloudFunctions.filter((fn) => {
90
- if (backend.isEventTrigger(fn.trigger)) {
91
- return false;
92
- }
93
- return want.cloudFunctions.some(backend.sameFunctionName(fn));
94
- });
95
- if (httpsFunctions.length === 0) {
96
- return;
97
- }
98
- for (const httpsFunc of httpsFunctions) {
99
- if (!httpsFunc.uri) {
100
- logger_1.logger.debug("Missing URI for HTTPS function in printTriggerUrls. This shouldn't happen");
101
- continue;
102
- }
103
- logger_1.logger.info(clc.bold("Function URL"), `(${getFunctionLabel(httpsFunc)}):`, httpsFunc.uri);
104
- }
105
- }
106
- exports.printTriggerUrls = printTriggerUrls;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prepare = void 0;
3
+ exports.inferDetailsFromExisting = exports.prepare = void 0;
4
4
  const clc = require("cli-color");
5
5
  const ensureCloudBuildEnabled_1 = require("./ensureCloudBuildEnabled");
6
6
  const functionsDeployHelper_1 = require("./functionsDeployHelper");
@@ -18,6 +18,7 @@ const runtimes = require("./runtimes");
18
18
  const validate = require("./validate");
19
19
  const utils = require("../../utils");
20
20
  const logger_1 = require("../../logger");
21
+ const triggerRegionHelper_1 = require("./triggerRegionHelper");
21
22
  function hasUserConfig(config) {
22
23
  return Object.keys(config).length > 1;
23
24
  }
@@ -52,57 +53,90 @@ async function prepare(context, options, payload) {
52
53
  projectAlias: options.projectAlias,
53
54
  };
54
55
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
56
+ const usedDotenv = hasDotenv(userEnvOpt);
55
57
  const tag = hasUserConfig(runtimeConfig)
56
- ? hasDotenv(userEnvOpt)
58
+ ? usedDotenv
57
59
  ? "mixed"
58
60
  : "runtime_config"
59
- : hasDotenv(userEnvOpt)
61
+ : usedDotenv
60
62
  ? "dotenv"
61
63
  : "none";
62
- track_1.track("functions_codebase_deploy_env_method", tag);
64
+ await track_1.track("functions_codebase_deploy_env_method", tag);
63
65
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
64
66
  const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs);
65
67
  wantBackend.environmentVariables = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
66
68
  payload.functions = { backend: wantBackend };
67
- if (backend.isEmptyBackend(wantBackend)) {
68
- return;
69
- }
70
- if (wantBackend.cloudFunctions.find((f) => f.platform === "gcfv2")) {
71
- const V2_APIS = {
72
- artifactregistry: "artifactregistry.googleapis.com",
73
- cloudrun: "run.googleapis.com",
74
- eventarc: "eventarc.googleapis.com",
75
- pubsub: "pubsub.googleapis.com",
76
- };
77
- const enablements = Object.entries(V2_APIS).map(([tag, api]) => {
78
- return ensureApiEnabled.ensure(context.projectId, api, tag);
69
+ if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
70
+ const V2_APIS = [
71
+ "artifactregistry.googleapis.com",
72
+ "run.googleapis.com",
73
+ "eventarc.googleapis.com",
74
+ "pubsub.googleapis.com",
75
+ "storage.googleapis.com",
76
+ ];
77
+ const enablements = V2_APIS.map((api) => {
78
+ return ensureApiEnabled.ensure(context.projectId, api, "functions");
79
79
  });
80
80
  await Promise.all(enablements);
81
81
  }
82
- utils_1.logBullet(clc.cyan.bold("functions:") +
83
- " preparing " +
84
- clc.bold(options.config.src.functions.source) +
85
- " directory for uploading...");
86
- if (wantBackend.cloudFunctions.find((fn) => fn.platform === "gcfv1")) {
82
+ if (backend.someEndpoint(wantBackend, () => true)) {
83
+ utils_1.logBullet(clc.cyan.bold("functions:") +
84
+ " preparing " +
85
+ clc.bold(options.config.src.functions.source) +
86
+ " directory for uploading...");
87
+ }
88
+ if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
87
89
  context.functionsSourceV1 = await prepareFunctionsUpload_1.prepareFunctionsUpload(runtimeConfig, options);
88
90
  }
89
- if (wantBackend.cloudFunctions.find((fn) => fn.platform === "gcfv2")) {
91
+ if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
90
92
  context.functionsSourceV2 = await prepareFunctionsUpload_1.prepareFunctionsUpload(undefined, options);
91
93
  }
92
- wantBackend.cloudFunctions.forEach((fn) => {
93
- fn.environmentVariables = wantBackend.environmentVariables;
94
- });
95
- await Promise.all(Object.keys(wantBackend.requiredAPIs).map((friendlyName) => {
96
- ensureApiEnabled.ensure(projectId, wantBackend.requiredAPIs[friendlyName], friendlyName, false);
94
+ for (const endpoint of backend.allEndpoints(wantBackend)) {
95
+ endpoint.environmentVariables = wantBackend.environmentVariables;
96
+ }
97
+ await Promise.all(Object.values(wantBackend.requiredAPIs).map((api) => {
98
+ return ensureApiEnabled.ensure(projectId, api, "functions", false);
97
99
  }));
98
- validate.functionIdsAreValid(wantBackend.cloudFunctions);
100
+ validate.functionIdsAreValid(backend.allEndpoints(wantBackend));
99
101
  context.filters = functionsDeployHelper_1.getFilterGroups(options);
100
- const wantFunctions = wantBackend.cloudFunctions.filter((fn) => {
101
- return functionsDeployHelper_1.functionMatchesAnyGroup(fn, context.filters);
102
+ const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => {
103
+ return functionsDeployHelper_1.functionMatchesAnyGroup(endpoint, context.filters);
102
104
  });
103
- const haveFunctions = (await backend.existingBackend(context)).cloudFunctions;
104
- await prompts_1.promptForFailurePolicies(options, wantFunctions, haveFunctions);
105
- await prompts_1.promptForMinInstances(options, wantFunctions, haveFunctions);
105
+ const haveBackend = await backend.existingBackend(context);
106
+ inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
107
+ await triggerRegionHelper_1.lookupMissingTriggerRegions(wantBackend);
108
+ await prompts_1.promptForFailurePolicies(options, matchingBackend, haveBackend);
109
+ await prompts_1.promptForMinInstances(options, matchingBackend, haveBackend);
106
110
  await backend.checkAvailability(context, wantBackend);
107
111
  }
108
112
  exports.prepare = prepare;
113
+ function inferDetailsFromExisting(want, have, usedDotenv) {
114
+ var _a;
115
+ for (const wantE of backend.allEndpoints(want)) {
116
+ const haveE = (_a = have.endpoints[wantE.region]) === null || _a === void 0 ? void 0 : _a[wantE.id];
117
+ if (!haveE) {
118
+ continue;
119
+ }
120
+ if (!usedDotenv) {
121
+ wantE.environmentVariables = Object.assign(Object.assign({}, haveE.environmentVariables), wantE.environmentVariables);
122
+ }
123
+ if (!wantE.availableMemoryMb && haveE.availableMemoryMb) {
124
+ wantE.availableMemoryMb = haveE.availableMemoryMb;
125
+ }
126
+ maybeCopyTriggerRegion(wantE, haveE);
127
+ }
128
+ }
129
+ exports.inferDetailsFromExisting = inferDetailsFromExisting;
130
+ function maybeCopyTriggerRegion(wantE, haveE) {
131
+ if (!backend.isEventTriggered(wantE) || !backend.isEventTriggered(haveE)) {
132
+ return;
133
+ }
134
+ if (wantE.eventTrigger.region || !haveE.eventTrigger.region) {
135
+ return;
136
+ }
137
+ if (JSON.stringify(haveE.eventTrigger.eventFilters) !==
138
+ JSON.stringify(wantE.eventTrigger.eventFilters)) {
139
+ return;
140
+ }
141
+ wantE.eventTrigger.region = haveE.eventTrigger.region;
142
+ }