firebase-tools 10.6.0 → 10.7.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 (83) hide show
  1. package/lib/command.js +4 -4
  2. package/lib/commands/deploy.js +1 -1
  3. package/lib/commands/emulators-start.js +7 -2
  4. package/lib/commands/ext-configure.js +15 -5
  5. package/lib/commands/ext-export.js +6 -5
  6. package/lib/commands/ext-install.js +28 -44
  7. package/lib/commands/ext-update.js +9 -1
  8. package/lib/commands/functions-delete.js +2 -5
  9. package/lib/commands/hosting-channel-deploy.js +2 -2
  10. package/lib/deploy/database/deploy.js +4 -0
  11. package/lib/deploy/database/index.js +1 -0
  12. package/lib/deploy/extensions/deploy.js +4 -4
  13. package/lib/deploy/extensions/deploymentSummary.js +8 -5
  14. package/lib/deploy/extensions/planner.js +36 -9
  15. package/lib/deploy/extensions/prepare.js +1 -1
  16. package/lib/deploy/extensions/secrets.js +2 -2
  17. package/lib/deploy/extensions/tasks.js +60 -21
  18. package/lib/deploy/functions/backend.js +17 -2
  19. package/lib/deploy/functions/build.js +162 -0
  20. package/lib/deploy/functions/checkIam.js +6 -5
  21. package/lib/deploy/functions/deploy.js +14 -15
  22. package/lib/deploy/functions/ensure.js +4 -4
  23. package/lib/deploy/functions/functionsDeployHelper.js +54 -23
  24. package/lib/deploy/functions/prepare.js +85 -42
  25. package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
  26. package/lib/deploy/functions/pricing.js +6 -3
  27. package/lib/deploy/functions/prompts.js +1 -7
  28. package/lib/deploy/functions/release/fabricator.js +43 -2
  29. package/lib/deploy/functions/release/index.js +10 -6
  30. package/lib/deploy/functions/release/planner.js +9 -6
  31. package/lib/deploy/functions/release/reporter.js +14 -11
  32. package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
  33. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +50 -3
  34. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  35. package/lib/deploy/functions/runtimes/node/parseTriggers.js +24 -5
  36. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  37. package/lib/deploy/functions/services/auth.js +95 -0
  38. package/lib/deploy/functions/services/index.js +41 -21
  39. package/lib/deploy/functions/validate.js +8 -5
  40. package/lib/deploy/hosting/args.js +2 -0
  41. package/lib/deploy/hosting/convertConfig.js +37 -8
  42. package/lib/deploy/hosting/deploy.js +3 -3
  43. package/lib/deploy/hosting/prepare.js +2 -2
  44. package/lib/deploy/hosting/release.js +6 -2
  45. package/lib/deploy/index.js +82 -93
  46. package/lib/deploy/remoteconfig/deploy.js +4 -0
  47. package/lib/deploy/remoteconfig/index.js +3 -1
  48. package/lib/emulator/auth/operations.js +5 -0
  49. package/lib/emulator/auth/utils.js +3 -25
  50. package/lib/emulator/controller.js +5 -5
  51. package/lib/emulator/downloadableEmulators.js +39 -23
  52. package/lib/emulator/extensions/validation.js +2 -2
  53. package/lib/emulator/extensionsEmulator.js +85 -21
  54. package/lib/emulator/functionsEmulator.js +79 -7
  55. package/lib/emulator/functionsEmulatorShared.js +20 -1
  56. package/lib/emulator/registry.js +34 -12
  57. package/lib/emulator/storage/apis/firebase.js +7 -2
  58. package/lib/emulator/storage/apis/gcloud.js +6 -3
  59. package/lib/emulator/storage/files.js +9 -1
  60. package/lib/ensureApiEnabled.js +8 -4
  61. package/lib/extensions/changelog.js +1 -1
  62. package/lib/extensions/emulator/optionsHelper.js +4 -3
  63. package/lib/extensions/emulator/specHelper.js +7 -1
  64. package/lib/extensions/extensionsHelper.js +30 -24
  65. package/lib/extensions/manifest.js +27 -7
  66. package/lib/extensions/paramHelper.js +5 -5
  67. package/lib/extensions/provisioningHelper.js +2 -2
  68. package/lib/extensions/warnings.js +3 -3
  69. package/lib/functions/events/index.js +7 -0
  70. package/lib/functions/events/v1.js +6 -0
  71. package/lib/functions/projectConfig.js +24 -3
  72. package/lib/gcp/cloudfunctions.js +31 -5
  73. package/lib/gcp/cloudfunctionsv2.js +27 -2
  74. package/lib/gcp/identityPlatform.js +44 -0
  75. package/lib/gcp/secretManager.js +1 -1
  76. package/lib/metaprogramming.js +2 -0
  77. package/lib/previews.js +1 -1
  78. package/lib/serve/hosting.js +25 -12
  79. package/lib/serve/index.js +6 -0
  80. package/lib/track.js +15 -21
  81. package/npm-shrinkwrap.json +44 -2
  82. package/package.json +4 -1
  83. package/schema/firebase-config.json +6 -0
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
3
+ exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.AllMemoryOptions = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.endpointTriggerType = void 0;
4
4
  const gcf = require("../../gcp/cloudfunctions");
5
5
  const gcfV2 = require("../../gcp/cloudfunctionsv2");
6
6
  const utils = require("../../utils");
@@ -22,11 +22,21 @@ function endpointTriggerType(endpoint) {
22
22
  else if (isTaskQueueTriggered(endpoint)) {
23
23
  return "taskQueue";
24
24
  }
25
+ else if (isBlockingTriggered(endpoint)) {
26
+ return endpoint.blockingTrigger.eventType;
27
+ }
25
28
  else {
26
29
  throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
27
30
  }
28
31
  }
29
32
  exports.endpointTriggerType = endpointTriggerType;
33
+ exports.AllVpcEgressSettings = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"];
34
+ exports.AllIngressSettings = [
35
+ "ALLOW_ALL",
36
+ "ALLOW_INTERNAL_ONLY",
37
+ "ALLOW_INTERNAL_AND_GCLB",
38
+ ];
39
+ exports.AllMemoryOptions = [128, 256, 512, 1024, 2048, 4096, 8192];
30
40
  function memoryOptionDisplayName(option) {
31
41
  return {
32
42
  128: "128MB",
@@ -47,6 +57,7 @@ function secretVersionName(s) {
47
57
  return `projects/${s.projectId}/secrets/${s.secret}/versions/${(_a = s.version) !== null && _a !== void 0 ? _a : "latest"}`;
48
58
  }
49
59
  exports.secretVersionName = secretVersionName;
60
+ exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2"];
50
61
  function isHttpsTriggered(triggered) {
51
62
  return {}.hasOwnProperty.call(triggered, "httpsTrigger");
52
63
  }
@@ -67,6 +78,10 @@ function isTaskQueueTriggered(triggered) {
67
78
  return {}.hasOwnProperty.call(triggered, "taskQueueTrigger");
68
79
  }
69
80
  exports.isTaskQueueTriggered = isTaskQueueTriggered;
81
+ function isBlockingTriggered(triggered) {
82
+ return {}.hasOwnProperty.call(triggered, "blockingTrigger");
83
+ }
84
+ exports.isBlockingTriggered = isBlockingTriggered;
70
85
  function empty() {
71
86
  return {
72
87
  requiredAPIs: [],
@@ -207,7 +222,7 @@ function findEndpoint(backend, predicate) {
207
222
  }
208
223
  exports.findEndpoint = findEndpoint;
209
224
  function matchingBackend(backend, predicate) {
210
- const filtered = Object.assign({}, empty());
225
+ const filtered = Object.assign(Object.assign({}, backend), { endpoints: {} });
211
226
  for (const endpoint of allEndpoints(backend)) {
212
227
  if (!predicate(endpoint)) {
213
228
  continue;
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveBackend = void 0;
4
+ const backend = require("./backend");
5
+ const proto = require("../../gcp/proto");
6
+ const api = require("../../.../../api");
7
+ const error_1 = require("../../error");
8
+ const functional_1 = require("../../functional");
9
+ function resolveInt(from) {
10
+ if (from == null) {
11
+ return 0;
12
+ }
13
+ else if (typeof from === "string") {
14
+ throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
15
+ }
16
+ return from;
17
+ }
18
+ function resolveString(from) {
19
+ if (from == null) {
20
+ return "";
21
+ }
22
+ else if (from.includes("{{") && from.includes("}}")) {
23
+ throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
24
+ }
25
+ return from;
26
+ }
27
+ function resolveBoolean(from) {
28
+ if (from == null) {
29
+ return false;
30
+ }
31
+ else if (typeof from === "string") {
32
+ throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
33
+ }
34
+ return from;
35
+ }
36
+ function isMemoryOption(value) {
37
+ return value == null || [128, 256, 512, 1024, 2048, 4096, 8192].includes(value);
38
+ }
39
+ function resolveBackend(build) {
40
+ const bkEndpoints = [];
41
+ for (const endpointId of Object.keys(build.endpoints)) {
42
+ const endpoint = build.endpoints[endpointId];
43
+ let regions = endpoint.region;
44
+ if (typeof regions === "undefined") {
45
+ regions = [api.functionsDefaultRegion];
46
+ }
47
+ for (const region of regions) {
48
+ const trigger = discoverTrigger(endpoint);
49
+ if (typeof endpoint.platform === "undefined") {
50
+ throw new error_1.FirebaseError("platform can't be undefined");
51
+ }
52
+ if (!isMemoryOption(endpoint.availableMemoryMb)) {
53
+ throw new error_1.FirebaseError("available memory must be a supported value, if present");
54
+ }
55
+ let timeout;
56
+ if (endpoint.timeoutSeconds) {
57
+ timeout = resolveInt(endpoint.timeoutSeconds);
58
+ }
59
+ else {
60
+ timeout = 60;
61
+ }
62
+ const bkEndpoint = Object.assign({ id: endpointId, project: "", region: region, entryPoint: endpoint.entryPoint, platform: endpoint.platform, runtime: "", labels: endpoint.labels, environmentVariables: endpoint.environmentVariables, secretEnvironmentVariables: undefined, availableMemoryMb: endpoint.availableMemoryMb, timeoutSeconds: timeout }, trigger);
63
+ proto.renameIfPresent(bkEndpoint, endpoint, "maxInstances", "maxInstances", resolveInt);
64
+ proto.renameIfPresent(bkEndpoint, endpoint, "minInstances", "minInstances", resolveInt);
65
+ proto.renameIfPresent(bkEndpoint, endpoint, "concurrency", "concurrency", resolveInt);
66
+ proto.copyIfPresent(bkEndpoint, endpoint, "ingressSettings");
67
+ if (endpoint.vpc) {
68
+ bkEndpoint.vpc = {
69
+ connector: resolveString(endpoint.vpc.connector),
70
+ egressSettings: endpoint.vpc.egressSettings,
71
+ };
72
+ }
73
+ if (endpoint.serviceAccount) {
74
+ bkEndpoint.serviceAccountEmail = endpoint.serviceAccount;
75
+ }
76
+ else {
77
+ bkEndpoint.serviceAccountEmail = "default";
78
+ }
79
+ bkEndpoints.push(bkEndpoint);
80
+ }
81
+ }
82
+ const bkend = backend.of(...bkEndpoints);
83
+ bkend.requiredAPIs = build.requiredAPIs;
84
+ return bkend;
85
+ }
86
+ exports.resolveBackend = resolveBackend;
87
+ function discoverTrigger(endpoint) {
88
+ let trigger;
89
+ if ("httpsTrigger" in endpoint) {
90
+ const bkHttps = {};
91
+ if (endpoint.httpsTrigger.invoker) {
92
+ bkHttps.invoker = [endpoint.httpsTrigger.invoker];
93
+ }
94
+ trigger = { httpsTrigger: bkHttps };
95
+ }
96
+ else if ("callableTrigger" in endpoint) {
97
+ trigger = { callableTrigger: {} };
98
+ }
99
+ else if ("blockingTrigger" in endpoint) {
100
+ throw new error_1.FirebaseError("blocking triggers not supported");
101
+ }
102
+ else if ("eventTrigger" in endpoint) {
103
+ const bkEventFilters = {};
104
+ for (const key in endpoint.eventTrigger.eventFilters) {
105
+ if (typeof key === "string") {
106
+ bkEventFilters[key] = resolveString(endpoint.eventTrigger.eventFilters[key]);
107
+ }
108
+ }
109
+ const bkEvent = {
110
+ eventType: endpoint.eventTrigger.eventType,
111
+ eventFilters: bkEventFilters,
112
+ retry: resolveBoolean(endpoint.eventTrigger.retry),
113
+ };
114
+ if (endpoint.eventTrigger.serviceAccount) {
115
+ bkEvent.serviceAccountEmail = endpoint.eventTrigger.serviceAccount;
116
+ }
117
+ if (endpoint.eventTrigger.region) {
118
+ bkEvent.region = resolveString(endpoint.eventTrigger.region);
119
+ }
120
+ trigger = { eventTrigger: bkEvent };
121
+ }
122
+ else if ("scheduleTrigger" in endpoint) {
123
+ const bkSchedule = {
124
+ schedule: resolveString(endpoint.scheduleTrigger.schedule),
125
+ timeZone: resolveString(endpoint.scheduleTrigger.timeZone),
126
+ };
127
+ proto.renameIfPresent(bkSchedule, endpoint.scheduleTrigger, "retryConfig", "retryConfig", resolveInt);
128
+ trigger = { scheduleTrigger: bkSchedule };
129
+ }
130
+ else if ("taskQueueTrigger" in endpoint) {
131
+ const bkTaskQueue = {};
132
+ if (endpoint.taskQueueTrigger.rateLimits) {
133
+ const bkRateLimits = {};
134
+ proto.renameIfPresent(bkRateLimits, endpoint.taskQueueTrigger.rateLimits, "maxConcurrentDispatches", "maxConcurrentDispatches", resolveInt);
135
+ proto.renameIfPresent(bkRateLimits, endpoint.taskQueueTrigger.rateLimits, "maxDispatchesPerSecond", "maxDispatchesPerSecond", resolveInt);
136
+ bkTaskQueue.rateLimits = bkRateLimits;
137
+ }
138
+ if (endpoint.taskQueueTrigger.retryConfig) {
139
+ const bkRetryConfig = {};
140
+ proto.renameIfPresent(bkRetryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", "maxAttempts", resolveInt);
141
+ proto.renameIfPresent(bkRetryConfig, endpoint.taskQueueTrigger.retryConfig, "maxBackoffSeconds", "maxBackoffSeconds", (from) => {
142
+ return proto.durationFromSeconds(resolveInt(from));
143
+ });
144
+ proto.renameIfPresent(bkRetryConfig, endpoint.taskQueueTrigger.retryConfig, "minBackoffSeconds", "minBackoffSeconds", (from) => {
145
+ return proto.durationFromSeconds(resolveInt(from));
146
+ });
147
+ proto.renameIfPresent(bkRetryConfig, endpoint.taskQueueTrigger.retryConfig, "maxRetrySeconds", "maxRetryDurationSeconds", (from) => {
148
+ return proto.durationFromSeconds(resolveInt(from));
149
+ });
150
+ proto.renameIfPresent(bkRetryConfig, endpoint.taskQueueTrigger.retryConfig, "maxDoublings", "maxDoublings", resolveInt);
151
+ bkTaskQueue.retryConfig = bkRetryConfig;
152
+ }
153
+ if (endpoint.taskQueueTrigger.invoker) {
154
+ bkTaskQueue.invoker = endpoint.taskQueueTrigger.invoker.map((sa) => resolveString(sa));
155
+ }
156
+ trigger = { taskQueueTrigger: bkTaskQueue };
157
+ }
158
+ else {
159
+ (0, functional_1.assertExhaustive)(endpoint);
160
+ }
161
+ return trigger;
162
+ }
@@ -7,7 +7,7 @@ const functionsDeployHelper_1 = require("./functionsDeployHelper");
7
7
  const error_1 = require("../../error");
8
8
  const iam = require("../../gcp/iam");
9
9
  const backend = require("./backend");
10
- const track = require("../../track");
10
+ const track_1 = require("../../track");
11
11
  const utils = require("../../utils");
12
12
  const resourceManager_1 = require("../../gcp/resourceManager");
13
13
  const services_1 = require("./services");
@@ -35,11 +35,12 @@ async function checkServiceAccountIam(projectId) {
35
35
  }
36
36
  exports.checkServiceAccountIam = checkServiceAccountIam;
37
37
  async function checkHttpIam(context, options, payload) {
38
- const filterGroups = context.filters || (0, functionsDeployHelper_1.getFilterGroups)(options);
38
+ const filters = context.filters || (0, functionsDeployHelper_1.getEndpointFilters)(options);
39
+ const wantBackend = payload.functions.wantBackend;
39
40
  const httpEndpoints = backend
40
- .allEndpoints(payload.functions.backend)
41
+ .allEndpoints(wantBackend)
41
42
  .filter(backend.isHttpsTriggered)
42
- .filter((f) => (0, functionsDeployHelper_1.functionMatchesAnyGroup)(f, filterGroups));
43
+ .filter((f) => (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(f, filters));
43
44
  const existing = await backend.existingBackend(context);
44
45
  const newHttpsEndpoints = httpEndpoints.filter(backend.missingEndpoint(existing));
45
46
  if (newHttpsEndpoints.length === 0) {
@@ -56,7 +57,7 @@ async function checkHttpIam(context, options, payload) {
56
57
  return;
57
58
  }
58
59
  if (!passed) {
59
- void track("Error (User)", "deploy:functions:http_create_missing_iam");
60
+ void (0, track_1.track)("Error (User)", "deploy:functions:http_create_missing_iam");
60
61
  throw new error_1.FirebaseError(`Missing required permission on project ${(0, cli_color_1.bold)(context.projectId)} to deploy new HTTPS functions. The permission ${(0, cli_color_1.bold)(PERMISSION)} is required to deploy the following functions:\n\n- ` +
61
62
  newHttpsEndpoints.map((func) => func.id).join("\n- ") +
62
63
  `\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}`);
@@ -13,10 +13,10 @@ const backend = require("./backend");
13
13
  (0, tmp_1.setGracefulCleanup)();
14
14
  async function uploadSourceV1(context, region) {
15
15
  const uploadUrl = await gcf.generateUploadUrl(context.projectId, region);
16
- context.sourceUrl = uploadUrl;
16
+ context.source.sourceUrl = uploadUrl;
17
17
  const uploadOpts = {
18
- file: context.functionsSourceV1,
19
- stream: fs.createReadStream(context.functionsSourceV1),
18
+ file: context.source.functionsSourceV1,
19
+ stream: fs.createReadStream(context.source.functionsSourceV1),
20
20
  };
21
21
  await gcs.upload(uploadOpts, uploadUrl, {
22
22
  "x-goog-content-length-range": "0,104857600",
@@ -25,31 +25,30 @@ async function uploadSourceV1(context, region) {
25
25
  async function uploadSourceV2(context, region) {
26
26
  const res = await gcfv2.generateUploadUrl(context.projectId, region);
27
27
  const uploadOpts = {
28
- file: context.functionsSourceV2,
29
- stream: fs.createReadStream(context.functionsSourceV2),
28
+ file: context.source.functionsSourceV2,
29
+ stream: fs.createReadStream(context.source.functionsSourceV2),
30
30
  };
31
31
  await gcs.upload(uploadOpts, res.uploadUrl);
32
- context.storage = Object.assign(Object.assign({}, context.storage), { [region]: res.storageSource });
32
+ context.source.storage = res.storageSource;
33
33
  }
34
34
  async function deploy(context, options, payload) {
35
+ var _a, _b, _c, _d;
35
36
  if (!context.config) {
36
37
  return;
37
38
  }
38
- if (!context.functionsSourceV1 && !context.functionsSourceV2) {
39
+ if (!((_a = context.source) === null || _a === void 0 ? void 0 : _a.functionsSourceV1) && !((_b = context.source) === null || _b === void 0 ? void 0 : _b.functionsSourceV2)) {
39
40
  return;
40
41
  }
41
42
  await (0, checkIam_1.checkHttpIam)(context, options, payload);
42
43
  try {
43
- const want = payload.functions.backend;
44
+ const want = payload.functions.wantBackend;
44
45
  const uploads = [];
45
- const v1Endpoints = backend.allEndpoints(want).filter((e) => e.platform === "gcfv1");
46
- if (v1Endpoints.length > 0) {
47
- uploads.push(uploadSourceV1(context, v1Endpoints[0].region));
46
+ const byPlatform = (0, utils_1.groupBy)(backend.allEndpoints(want), (e) => e.platform);
47
+ if (((_c = byPlatform.gcfv1) === null || _c === void 0 ? void 0 : _c.length) > 0) {
48
+ uploads.push(uploadSourceV1(context, byPlatform.gcfv1[0].region));
48
49
  }
49
- for (const region of Object.keys(want.endpoints)) {
50
- if (backend.regionalEndpoints(want, region).some((e) => e.platform === "gcfv2")) {
51
- uploads.push(uploadSourceV2(context, region));
52
- }
50
+ if (((_d = byPlatform.gcfv2) === null || _d === void 0 ? void 0 : _d.length) > 0) {
51
+ uploads.push(uploadSourceV2(context, byPlatform.gcfv2[0].region));
53
52
  }
54
53
  await Promise.all(uploads);
55
54
  const source = context.config.source;
@@ -9,7 +9,7 @@ const secretManager_1 = require("../../gcp/secretManager");
9
9
  const previews_1 = require("../../previews");
10
10
  const projects_1 = require("../../management/projects");
11
11
  const functional_1 = require("../../functional");
12
- const track = require("../../track");
12
+ const track_1 = require("../../track");
13
13
  const backend = require("./backend");
14
14
  const ensureApiEnabled = require("../../ensureApiEnabled");
15
15
  const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime";
@@ -26,9 +26,9 @@ async function defaultServiceAccount(e) {
26
26
  }
27
27
  exports.defaultServiceAccount = defaultServiceAccount;
28
28
  function nodeBillingError(projectId) {
29
- void track("functions_runtime_notices", "nodejs10_billing_error");
29
+ void (0, track_1.track)("functions_runtime_notices", "nodejs10_billing_error");
30
30
  return new error_1.FirebaseError(`Cloud Functions deployment requires the pay-as-you-go (Blaze) billing plan. To upgrade your project, visit the following URL:
31
-
31
+
32
32
  https://console.firebase.google.com/project/${projectId}/usage/details
33
33
 
34
34
  For additional information about this requirement, see Firebase FAQs:
@@ -36,7 +36,7 @@ For additional information about this requirement, see Firebase FAQs:
36
36
  ${FAQ_URL}`, { exit: 1 });
37
37
  }
38
38
  function nodePermissionError(projectId) {
39
- void track("functions_runtime_notices", "nodejs10_permission_error");
39
+ void (0, track_1.track)("functions_runtime_notices", "nodejs10_permission_error");
40
40
  return new error_1.FirebaseError(`Cloud Functions deployment requires the Cloud Build API to be enabled. The current credentials do not have permission to enable APIs for project ${clc.bold(projectId)}.
41
41
 
42
42
  Please ask a project owner to visit the following URL to enable Cloud Build:
@@ -1,40 +1,71 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getFunctionLabel = exports.getFilterGroups = exports.functionMatchesGroup = exports.functionMatchesAnyGroup = void 0;
4
- function functionMatchesAnyGroup(func, filterGroups) {
5
- if (!filterGroups.length) {
3
+ exports.getFunctionLabel = exports.getEndpointFilters = exports.parseFunctionSelector = exports.endpointMatchesFilter = exports.endpointMatchesAnyFilter = void 0;
4
+ const projectConfig = require("../../functions/projectConfig");
5
+ function endpointMatchesAnyFilter(endpoint, filters) {
6
+ if (!filters) {
6
7
  return true;
7
8
  }
8
- return filterGroups.some((groupChunk) => functionMatchesGroup(func, groupChunk));
9
+ return filters.some((filter) => endpointMatchesFilter(endpoint, filter));
9
10
  }
10
- exports.functionMatchesAnyGroup = functionMatchesAnyGroup;
11
- function functionMatchesGroup(func, groupChunks) {
12
- const functionNameChunks = func.id.split("-").slice(0, groupChunks.length);
13
- if (functionNameChunks.length !== groupChunks.length) {
11
+ exports.endpointMatchesAnyFilter = endpointMatchesAnyFilter;
12
+ function endpointMatchesFilter(endpoint, filter) {
13
+ if (endpoint.codebase && filter.codebase) {
14
+ if (endpoint.codebase !== filter.codebase) {
15
+ return false;
16
+ }
17
+ }
18
+ if (!filter.idChunks) {
19
+ return true;
20
+ }
21
+ const idChunks = endpoint.id.split("-");
22
+ if (idChunks.length < filter.idChunks.length) {
14
23
  return false;
15
24
  }
16
- for (let i = 0; i < groupChunks.length; i += 1) {
17
- if (groupChunks[i] !== functionNameChunks[i]) {
25
+ for (let i = 0; i < filter.idChunks.length; i += 1) {
26
+ if (idChunks[i] !== filter.idChunks[i]) {
18
27
  return false;
19
28
  }
20
29
  }
21
30
  return true;
22
31
  }
23
- exports.functionMatchesGroup = functionMatchesGroup;
24
- function getFilterGroups(options) {
32
+ exports.endpointMatchesFilter = endpointMatchesFilter;
33
+ function parseFunctionSelector(selector) {
34
+ const fragments = selector.split(":");
35
+ if (fragments.length < 2) {
36
+ return [
37
+ { codebase: fragments[0] },
38
+ { codebase: projectConfig.DEFAULT_CODEBASE, idChunks: fragments[0].split(/[-.]/) },
39
+ ];
40
+ }
41
+ return [
42
+ {
43
+ codebase: fragments[0],
44
+ idChunks: fragments[1].split(/[-.]/),
45
+ },
46
+ ];
47
+ }
48
+ exports.parseFunctionSelector = parseFunctionSelector;
49
+ function getEndpointFilters(options) {
25
50
  if (!options.only) {
26
- return [];
27
- }
28
- const only = options.only.split(",");
29
- const onlyFunctions = only.filter((filter) => {
30
- const opts = filter.split(":");
31
- return opts[0] === "functions" && opts[1];
32
- });
33
- return onlyFunctions.map((filter) => {
34
- return filter.split(":")[1].split(/[.-]/);
35
- });
51
+ return undefined;
52
+ }
53
+ const selectors = options.only.split(",");
54
+ const filters = [];
55
+ for (let selector of selectors) {
56
+ if (selector.startsWith("functions:")) {
57
+ selector = selector.replace("functions:", "");
58
+ if (selector.length > 0) {
59
+ filters.push(...parseFunctionSelector(selector));
60
+ }
61
+ }
62
+ }
63
+ if (filters.length === 0) {
64
+ return undefined;
65
+ }
66
+ return filters;
36
67
  }
37
- exports.getFilterGroups = getFilterGroups;
68
+ exports.getEndpointFilters = getEndpointFilters;
38
69
  function getFunctionLabel(fn) {
39
70
  return `${fn.id}(${fn.region})`;
40
71
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.inferDetailsFromExisting = exports.prepare = void 0;
3
+ exports.inferBlockingDetails = exports.inferDetailsFromExisting = exports.prepare = void 0;
4
4
  const clc = require("cli-color");
5
5
  const backend = require("./backend");
6
6
  const ensureApiEnabled = require("../../ensureApiEnabled");
@@ -21,6 +21,7 @@ const checkIam_1 = require("./checkIam");
21
21
  const error_1 = require("../../error");
22
22
  const projectConfig_1 = require("../../functions/projectConfig");
23
23
  const previews_1 = require("../../previews");
24
+ const v1_1 = require("../../functions/events/v1");
24
25
  function hasUserConfig(config) {
25
26
  return Object.keys(config).length > 1;
26
27
  }
@@ -31,6 +32,25 @@ async function prepare(context, options, payload) {
31
32
  const projectId = (0, projectUtils_1.needProjectId)(options);
32
33
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
33
34
  context.config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions)[0];
35
+ context.filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
36
+ if (context.filters &&
37
+ !context.filters.map((f) => f.codebase).includes(context.config.codebase)) {
38
+ throw new error_1.FirebaseError("No function matches given --only filters. Aborting deployment.");
39
+ }
40
+ const checkAPIsEnabled = await Promise.all([
41
+ ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
42
+ ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
43
+ ensure.cloudBuildEnabled(projectId),
44
+ ensure.maybeEnableAR(projectId),
45
+ ]);
46
+ context.artifactRegistryEnabled = checkAPIsEnabled[3];
47
+ const firebaseConfig = await functionsConfig.getFirebaseConfig(options);
48
+ context.firebaseConfig = firebaseConfig;
49
+ let runtimeConfig = { firebase: firebaseConfig };
50
+ if (checkAPIsEnabled[1]) {
51
+ runtimeConfig = Object.assign(Object.assign({}, runtimeConfig), (await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId)));
52
+ }
53
+ (0, utils_1.logLabeledBullet)("functions", `preparing codebase ${clc.bold(context.config.codebase)} for deployment`);
34
54
  const sourceDirName = context.config.source;
35
55
  if (!sourceDirName) {
36
56
  throw new error_1.FirebaseError(`No functions code detected at default location (./functions), and no functions source defined in firebase.json`);
@@ -47,17 +67,6 @@ async function prepare(context, options, payload) {
47
67
  await runtimeDelegate.validate();
48
68
  logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
49
69
  await runtimeDelegate.build();
50
- const checkAPIsEnabled = await Promise.all([
51
- ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
52
- ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
53
- ensure.cloudBuildEnabled(projectId),
54
- ensure.maybeEnableAR(projectId),
55
- ]);
56
- context.runtimeConfigEnabled = checkAPIsEnabled[1];
57
- context.artifactRegistryEnabled = checkAPIsEnabled[3];
58
- const firebaseConfig = await functionsConfig.getFirebaseConfig(options);
59
- context.firebaseConfig = firebaseConfig;
60
- const runtimeConfig = await (0, prepareFunctionsUpload_1.getFunctionsConfig)(context);
61
70
  const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
62
71
  const userEnvOpt = {
63
72
  functionsSource: sourceDir,
@@ -77,25 +86,13 @@ async function prepare(context, options, payload) {
77
86
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
78
87
  const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs);
79
88
  wantBackend.environmentVariables = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
80
- payload.functions = { backend: wantBackend };
81
- if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
82
- const V2_APIS = [
83
- "artifactregistry.googleapis.com",
84
- "run.googleapis.com",
85
- "eventarc.googleapis.com",
86
- "pubsub.googleapis.com",
87
- "storage.googleapis.com",
88
- ];
89
- const enablements = V2_APIS.map((api) => {
90
- return ensureApiEnabled.ensure(context.projectId, api, "functions");
91
- });
92
- await Promise.all(enablements);
89
+ for (const endpoint of backend.allEndpoints(wantBackend)) {
90
+ endpoint.environmentVariables = wantBackend.environmentVariables;
91
+ endpoint.codebase = context.config.codebase;
93
92
  }
93
+ const source = {};
94
94
  if (backend.someEndpoint(wantBackend, () => true)) {
95
- (0, utils_1.logBullet)(clc.cyan.bold("functions:") +
96
- " preparing " +
97
- clc.bold(sourceDirName) +
98
- " directory for uploading...");
95
+ (0, utils_1.logLabeledBullet)("functions", `preparing ${clc.bold(sourceDirName)} directory for uploading...`);
99
96
  }
100
97
  if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
101
98
  if (!previews_1.previews.functionsv2) {
@@ -107,29 +104,48 @@ async function prepare(context, options, payload) {
107
104
  "If Cloud Functions for Firebase gen 2 is in beta, get the latest " +
108
105
  "version of Firebse Tools with `npm i -g firebase-tools@latest`");
109
106
  }
110
- context.functionsSourceV2 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config);
107
+ source.functionsSourceV2 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config);
111
108
  }
112
109
  if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
113
- context.functionsSourceV1 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config, runtimeConfig);
114
- }
115
- for (const endpoint of backend.allEndpoints(wantBackend)) {
116
- endpoint.environmentVariables = wantBackend.environmentVariables;
110
+ source.functionsSourceV1 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config, runtimeConfig);
117
111
  }
112
+ context.source = source;
113
+ const wantEndpointNames = backend.allEndpoints(wantBackend).map((e) => backend.functionName(e));
114
+ const haveBackend = backend.matchingBackend(await backend.existingBackend(context), (endpoint) => {
115
+ var _a;
116
+ if (endpoint.codebase === ((_a = context.config) === null || _a === void 0 ? void 0 : _a.codebase)) {
117
+ return true;
118
+ }
119
+ return wantEndpointNames.includes(backend.functionName(endpoint));
120
+ });
121
+ inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
122
+ await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
123
+ validate.endpointsAreValid(wantBackend);
124
+ inferBlockingDetails(wantBackend);
125
+ payload.functions = { wantBackend: wantBackend, haveBackend: haveBackend };
118
126
  await Promise.all(Object.values(wantBackend.requiredAPIs).map(({ api }) => {
119
127
  return ensureApiEnabled.ensure(projectId, api, "functions", false);
120
128
  }));
121
- validate.endpointsAreValid(wantBackend);
122
- context.filters = (0, functionsDeployHelper_1.getFilterGroups)(options);
129
+ if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
130
+ const V2_APIS = [
131
+ "artifactregistry.googleapis.com",
132
+ "run.googleapis.com",
133
+ "eventarc.googleapis.com",
134
+ "pubsub.googleapis.com",
135
+ "storage.googleapis.com",
136
+ ];
137
+ const enablements = V2_APIS.map((api) => {
138
+ return ensureApiEnabled.ensure(context.projectId, api, "functions");
139
+ });
140
+ await Promise.all(enablements);
141
+ }
123
142
  const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => {
124
- return (0, functionsDeployHelper_1.functionMatchesAnyGroup)(endpoint, context.filters);
143
+ return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, context.filters);
125
144
  });
126
- const haveBackend = await backend.existingBackend(context);
127
- await (0, checkIam_1.ensureServiceAgentRoles)(projectNumber, wantBackend, haveBackend);
128
- inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
129
- await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
130
145
  await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
131
146
  await (0, prompts_1.promptForMinInstances)(options, matchingBackend, haveBackend);
132
- await backend.checkAvailability(context, wantBackend);
147
+ await backend.checkAvailability(context, matchingBackend);
148
+ await (0, checkIam_1.ensureServiceAgentRoles)(projectNumber, matchingBackend, haveBackend);
133
149
  await validate.secretsAreValid(projectId, matchingBackend);
134
150
  await ensure.secretAccess(projectId, matchingBackend, haveBackend);
135
151
  }
@@ -165,3 +181,30 @@ function maybeCopyTriggerRegion(wantE, haveE) {
165
181
  }
166
182
  wantE.eventTrigger.region = haveE.eventTrigger.region;
167
183
  }
184
+ function inferBlockingDetails(want) {
185
+ var _a, _b, _c;
186
+ const authBlockingEndpoints = backend
187
+ .allEndpoints(want)
188
+ .filter((ep) => backend.isBlockingTriggered(ep) &&
189
+ v1_1.AUTH_BLOCKING_EVENTS.includes(ep.blockingTrigger.eventType));
190
+ if (authBlockingEndpoints.length === 0) {
191
+ return;
192
+ }
193
+ let accessToken = false;
194
+ let idToken = false;
195
+ let refreshToken = false;
196
+ for (const blockingEp of authBlockingEndpoints) {
197
+ accessToken || (accessToken = !!((_a = blockingEp.blockingTrigger.options) === null || _a === void 0 ? void 0 : _a.accessToken));
198
+ idToken || (idToken = !!((_b = blockingEp.blockingTrigger.options) === null || _b === void 0 ? void 0 : _b.idToken));
199
+ refreshToken || (refreshToken = !!((_c = blockingEp.blockingTrigger.options) === null || _c === void 0 ? void 0 : _c.refreshToken));
200
+ }
201
+ for (const blockingEp of authBlockingEndpoints) {
202
+ if (!blockingEp.blockingTrigger.options) {
203
+ blockingEp.blockingTrigger.options = {};
204
+ }
205
+ blockingEp.blockingTrigger.options.accessToken = accessToken;
206
+ blockingEp.blockingTrigger.options.idToken = idToken;
207
+ blockingEp.blockingTrigger.options.refreshToken = refreshToken;
208
+ }
209
+ }
210
+ exports.inferBlockingDetails = inferBlockingDetails;