firebase-tools 10.2.1 → 10.2.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 (105) hide show
  1. package/lib/appdistribution/options-parser-util.js +1 -1
  2. package/lib/auth.js +3 -3
  3. package/lib/command.js +1 -1
  4. package/lib/commands/apps-android-sha-create.js +2 -2
  5. package/lib/commands/apps-sdkconfig.js +1 -1
  6. package/lib/commands/database-rules-list.js +2 -2
  7. package/lib/commands/emulators-start.js +1 -1
  8. package/lib/commands/ext-dev-init.js +49 -49
  9. package/lib/commands/ext-export.js +12 -2
  10. package/lib/commands/ext-install.js +104 -104
  11. package/lib/commands/ext-uninstall.js +8 -8
  12. package/lib/commands/ext-update.js +9 -9
  13. package/lib/commands/functions-config-export.js +1 -1
  14. package/lib/commands/hosting-clone.js +3 -3
  15. package/lib/commands/remoteconfig-get.js +1 -1
  16. package/lib/deploy/extensions/deploymentSummary.js +3 -3
  17. package/lib/deploy/extensions/params.js +3 -0
  18. package/lib/deploy/extensions/planner.js +2 -1
  19. package/lib/deploy/extensions/tasks.js +1 -1
  20. package/lib/deploy/functions/backend.js +12 -5
  21. package/lib/deploy/functions/checkIam.js +1 -1
  22. package/lib/deploy/functions/containerCleaner.js +3 -3
  23. package/lib/deploy/functions/ensure.js +3 -3
  24. package/lib/deploy/functions/functionsDeployHelper.js +2 -2
  25. package/lib/deploy/functions/prepare.js +3 -2
  26. package/lib/deploy/functions/pricing.js +1 -1
  27. package/lib/deploy/functions/prompts.js +2 -2
  28. package/lib/deploy/functions/release/fabricator.js +3 -3
  29. package/lib/deploy/functions/release/index.js +1 -1
  30. package/lib/deploy/functions/release/planner.js +11 -8
  31. package/lib/deploy/functions/release/reporter.js +3 -0
  32. package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
  33. package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
  34. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +16 -11
  35. package/lib/deploy/functions/runtimes/golang/index.js +2 -2
  36. package/lib/deploy/functions/runtimes/node/index.js +26 -0
  37. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
  38. package/lib/deploy/functions/runtimes/node/parseTriggers.js +28 -7
  39. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  40. package/lib/deploy/functions/validate.js +3 -3
  41. package/lib/deploy/hosting/deploy.js +2 -2
  42. package/lib/deploy/hosting/hashcache.js +21 -19
  43. package/lib/deploy/hosting/uploader.js +5 -5
  44. package/lib/deploy/remoteconfig/functions.js +2 -2
  45. package/lib/emulator/auth/cloudFunctions.js +1 -1
  46. package/lib/emulator/auth/operations.js +1 -1
  47. package/lib/emulator/constants.js +3 -0
  48. package/lib/emulator/controller.js +47 -19
  49. package/lib/emulator/download.js +18 -1
  50. package/lib/emulator/downloadableEmulators.js +1 -1
  51. package/lib/emulator/emulatorLogger.js +12 -1
  52. package/lib/emulator/extensions/validation.js +35 -0
  53. package/lib/emulator/extensionsEmulator.js +140 -0
  54. package/lib/emulator/functionsEmulator.js +86 -39
  55. package/lib/emulator/functionsEmulatorRuntime.js +44 -36
  56. package/lib/emulator/functionsEmulatorShell.js +1 -1
  57. package/lib/emulator/functionsEmulatorUtils.js +4 -4
  58. package/lib/emulator/functionsRuntimeWorker.js +2 -2
  59. package/lib/emulator/hub.js +4 -3
  60. package/lib/emulator/loggingEmulator.js +1 -1
  61. package/lib/emulator/pubsubEmulator.js +1 -1
  62. package/lib/emulator/registry.js +10 -2
  63. package/lib/emulator/storage/apis/firebase.js +31 -26
  64. package/lib/emulator/storage/apis/gcloud.js +7 -12
  65. package/lib/emulator/storage/files.js +36 -34
  66. package/lib/emulator/storage/index.js +2 -2
  67. package/lib/emulator/storage/metadata.js +2 -2
  68. package/lib/emulator/storage/rules/runtime.js +8 -7
  69. package/lib/emulator/types.js +3 -0
  70. package/lib/ensureApiEnabled.js +5 -1
  71. package/lib/error.js +1 -1
  72. package/lib/extensions/askUserForParam.js +1 -1
  73. package/lib/extensions/changelog.js +3 -1
  74. package/lib/extensions/checkProjectBilling.js +1 -1
  75. package/lib/extensions/displayExtensionInfo.js +1 -1
  76. package/lib/extensions/emulator/optionsHelper.js +24 -8
  77. package/lib/extensions/emulator/specHelper.js +10 -23
  78. package/lib/extensions/export.js +1 -51
  79. package/lib/extensions/extensionsApi.js +1 -1
  80. package/lib/extensions/extensionsHelper.js +13 -9
  81. package/lib/extensions/manifest.js +48 -0
  82. package/lib/extensions/metricsUtils.js +4 -4
  83. package/lib/extensions/paramHelper.js +4 -4
  84. package/lib/extensions/refs.js +1 -1
  85. package/lib/extensions/secretsUtils.js +3 -3
  86. package/lib/functional.js +1 -1
  87. package/lib/functions/env.js +2 -1
  88. package/lib/gcp/cloudfunctions.js +24 -5
  89. package/lib/gcp/cloudfunctionsv2.js +18 -5
  90. package/lib/gcp/cloudtasks.js +1 -1
  91. package/lib/gcp/docker.js +2 -2
  92. package/lib/gcp/run.js +2 -2
  93. package/lib/hosting/api.js +1 -1
  94. package/lib/hosting/proxy.js +2 -2
  95. package/lib/init/features/account.js +1 -1
  96. package/lib/management/database.js +1 -1
  97. package/lib/previews.js +1 -1
  98. package/lib/utils.js +1 -1
  99. package/npm-shrinkwrap.json +786 -393
  100. package/package.json +1 -1
  101. package/schema/firebase-config.json +5 -0
  102. package/templates/init/functions/javascript/package.lint.json +3 -3
  103. package/templates/init/functions/javascript/package.nolint.json +2 -2
  104. package/templates/init/functions/typescript/package.lint.json +7 -7
  105. package/templates/init/functions/typescript/package.nolint.json +3 -3
@@ -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.isHttpsTriggered = 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.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = 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");
@@ -13,6 +13,9 @@ function endpointTriggerType(endpoint) {
13
13
  else if (isHttpsTriggered(endpoint)) {
14
14
  return "https";
15
15
  }
16
+ else if (isCallableTriggered(endpoint)) {
17
+ return "callable";
18
+ }
16
19
  else if (isEventTriggered(endpoint)) {
17
20
  return endpoint.eventTrigger.eventType;
18
21
  }
@@ -43,6 +46,10 @@ function isHttpsTriggered(triggered) {
43
46
  return {}.hasOwnProperty.call(triggered, "httpsTrigger");
44
47
  }
45
48
  exports.isHttpsTriggered = isHttpsTriggered;
49
+ function isCallableTriggered(triggered) {
50
+ return {}.hasOwnProperty.call(triggered, "callableTrigger");
51
+ }
52
+ exports.isCallableTriggered = isCallableTriggered;
46
53
  function isEventTriggered(triggered) {
47
54
  return {}.hasOwnProperty.call(triggered, "eventTrigger");
48
55
  }
@@ -57,7 +64,7 @@ function isTaskQueueTriggered(triggered) {
57
64
  exports.isTaskQueueTriggered = isTaskQueueTriggered;
58
65
  function empty() {
59
66
  return {
60
- requiredAPIs: {},
67
+ requiredAPIs: [],
61
68
  endpoints: {},
62
69
  environmentVariables: {},
63
70
  };
@@ -76,7 +83,7 @@ function of(...endpoints) {
76
83
  }
77
84
  exports.of = of;
78
85
  function isEmptyBackend(backend) {
79
- return (Object.keys(backend.requiredAPIs).length == 0 && Object.keys(backend.endpoints).length === 0);
86
+ return (Object.keys(backend.requiredAPIs).length === 0 && Object.keys(backend.endpoints).length === 0);
80
87
  }
81
88
  exports.isEmptyBackend = isEmptyBackend;
82
89
  function functionName(cloudFunction) {
@@ -140,7 +147,7 @@ async function checkAvailability(context, want) {
140
147
  const gcfV1Regions = new Set();
141
148
  const gcfV2Regions = new Set();
142
149
  for (const ep of allEndpoints(want)) {
143
- if (ep.platform == "gcfv1") {
150
+ if (ep.platform === "gcfv1") {
144
151
  gcfV1Regions.add(ep.region);
145
152
  }
146
153
  else {
@@ -219,7 +226,7 @@ const missingEndpoint = (backend) => (endpoint) => {
219
226
  };
220
227
  exports.missingEndpoint = missingEndpoint;
221
228
  function compareFunctions(left, right) {
222
- if (left.platform != right.platform) {
229
+ if (left.platform !== right.platform) {
223
230
  return right.platform < left.platform ? -1 : 1;
224
231
  }
225
232
  if (left.region < right.region) {
@@ -52,7 +52,7 @@ async function checkHttpIam(context, options, payload) {
52
52
  return;
53
53
  }
54
54
  if (!passed) {
55
- track("Error (User)", "deploy:functions:http_create_missing_iam");
55
+ void track("Error (User)", "deploy:functions:http_create_missing_iam");
56
56
  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- ` +
57
57
  newHttpsEndpoints.map((func) => func.id).join("\n- ") +
58
58
  `\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}`);
@@ -71,7 +71,7 @@ async function cleanupBuildImages(haveFunctions, deletedFunctions, cleaners = {}
71
71
  let message = "Unhandled error cleaning up build images. This could result in a small monthly bill if not corrected. ";
72
72
  message +=
73
73
  "You can attempt to delete these images by redeploying or you can delete them manually at";
74
- if (failedDomains.size == 1) {
74
+ if (failedDomains.size === 1) {
75
75
  message += " " + failedDomains.values().next().value;
76
76
  }
77
77
  else {
@@ -205,7 +205,7 @@ async function listGcfPaths(projectId, locations, dockerHelpers = {}) {
205
205
  .map((results) => results.children)
206
206
  .reduce((acc, val) => [...acc, ...val], [])
207
207
  .filter((loc) => locationsSet.has(loc));
208
- if (failedSubdomains.length == subdomains.size) {
208
+ if (failedSubdomains.length === subdomains.size) {
209
209
  throw new error_1.FirebaseError("Failed to search all subdomains.");
210
210
  }
211
211
  else if (failedSubdomains.length > 0) {
@@ -237,7 +237,7 @@ async function deleteGcfArtifacts(projectId, locations, dockerHelpers = {}) {
237
237
  }
238
238
  });
239
239
  await Promise.all(deleteLocations);
240
- if (failedSubdomains.length == subdomains.size) {
240
+ if (failedSubdomains.length === subdomains.size) {
241
241
  throw new error_1.FirebaseError("Failed to search all subdomains.");
242
242
  }
243
243
  else if (failedSubdomains.length > 0) {
@@ -26,7 +26,7 @@ async function defaultServiceAccount(e) {
26
26
  }
27
27
  exports.defaultServiceAccount = defaultServiceAccount;
28
28
  function nodeBillingError(projectId) {
29
- track("functions_runtime_notices", "nodejs10_billing_error");
29
+ void 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
@@ -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
- track("functions_runtime_notices", "nodejs10_permission_error");
39
+ void 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:
@@ -99,7 +99,7 @@ async function secretAccess(projectId, wantBackend, haveBackend) {
99
99
  for (const serviceAccount of serviceAccounts) {
100
100
  (_a = wantSecrets[secret]) === null || _a === void 0 ? void 0 : _a.delete(serviceAccount);
101
101
  }
102
- if (((_b = wantSecrets[secret]) === null || _b === void 0 ? void 0 : _b.size) == 0) {
102
+ if (((_b = wantSecrets[secret]) === null || _b === void 0 ? void 0 : _b.size) === 0) {
103
103
  delete wantSecrets[secret];
104
104
  }
105
105
  }
@@ -10,7 +10,7 @@ function functionMatchesAnyGroup(func, filterGroups) {
10
10
  exports.functionMatchesAnyGroup = functionMatchesAnyGroup;
11
11
  function functionMatchesGroup(func, groupChunks) {
12
12
  const functionNameChunks = func.id.split("-").slice(0, groupChunks.length);
13
- if (functionNameChunks.length != groupChunks.length) {
13
+ if (functionNameChunks.length !== groupChunks.length) {
14
14
  return false;
15
15
  }
16
16
  for (let i = 0; i < groupChunks.length; i += 1) {
@@ -28,7 +28,7 @@ function getFilterGroups(options) {
28
28
  const only = options.only.split(",");
29
29
  const onlyFunctions = only.filter((filter) => {
30
30
  const opts = filter.split(":");
31
- return opts[0] == "functions" && opts[1];
31
+ return opts[0] === "functions" && opts[1];
32
32
  });
33
33
  return onlyFunctions.map((filter) => {
34
34
  return filter.split(":")[1].split(/[.-]/);
@@ -69,7 +69,7 @@ async function prepare(context, options, payload) {
69
69
  : usedDotenv
70
70
  ? "dotenv"
71
71
  : "none";
72
- await (0, track_1.track)("functions_codebase_deploy_env_method", tag);
72
+ void (0, track_1.track)("functions_codebase_deploy_env_method", tag);
73
73
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
74
74
  const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, firebaseEnvs);
75
75
  wantBackend.environmentVariables = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
@@ -102,7 +102,7 @@ async function prepare(context, options, payload) {
102
102
  for (const endpoint of backend.allEndpoints(wantBackend)) {
103
103
  endpoint.environmentVariables = wantBackend.environmentVariables;
104
104
  }
105
- await Promise.all(Object.values(wantBackend.requiredAPIs).map((api) => {
105
+ await Promise.all(Object.values(wantBackend.requiredAPIs).map(({ api }) => {
106
106
  return ensureApiEnabled.ensure(projectId, api, "functions", false);
107
107
  }));
108
108
  validate.endpointsAreValid(wantBackend);
@@ -134,6 +134,7 @@ function inferDetailsFromExisting(want, have, usedDotenv) {
134
134
  if (!wantE.availableMemoryMb && haveE.availableMemoryMb) {
135
135
  wantE.availableMemoryMb = haveE.availableMemoryMb;
136
136
  }
137
+ wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS";
137
138
  maybeCopyTriggerRegion(wantE, haveE);
138
139
  }
139
140
  }
@@ -106,7 +106,7 @@ function canCalculateMinInstanceCost(endpoint) {
106
106
  if (!endpoint.minInstances) {
107
107
  return true;
108
108
  }
109
- if (endpoint.platform == "gcfv1") {
109
+ if (endpoint.platform === "gcfv1") {
110
110
  if (!MB_TO_GHZ[endpoint.availableMemoryMb || 256]) {
111
111
  return false;
112
112
  }
@@ -21,7 +21,7 @@ async function promptForFailurePolicies(options, want, have) {
21
21
  const existing = (_a = have.endpoints[endpoint.region]) === null || _a === void 0 ? void 0 : _a[endpoint.id];
22
22
  return !(existing && backend.isEventTriggered(existing) && existing.eventTrigger.retry);
23
23
  });
24
- if (newRetryEndpoints.length == 0) {
24
+ if (newRetryEndpoints.length === 0) {
25
25
  return;
26
26
  }
27
27
  const warnMessage = "The following functions will newly be retried in case of failure: " +
@@ -137,7 +137,7 @@ async function promptForMinInstances(options, want, have) {
137
137
  costLine = `With these options, your minimum bill will be $${cost} in a 30-day month`;
138
138
  }
139
139
  let cudAnnotation = "";
140
- if (backend.someEndpoint(want, (fn) => fn.platform == "gcfv2" && !!fn.minInstances)) {
140
+ if (backend.someEndpoint(want, (fn) => fn.platform === "gcfv2" && !!fn.minInstances)) {
141
141
  cudAnnotation =
142
142
  "\nThis bill can be lowered with a one year commitment. See https://cloud.google.com/run/cud for more";
143
143
  }
@@ -216,12 +216,12 @@ class Fabricator {
216
216
  })
217
217
  .catch(rethrowAs(endpoint, "create topic"));
218
218
  }
219
- const resultFunction = (await this.functionExecutor
219
+ const resultFunction = await this.functionExecutor
220
220
  .run(async () => {
221
221
  const op = await gcfV2.createFunction(apiFunction);
222
222
  return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
223
223
  })
224
- .catch(rethrowAs(endpoint, "create")));
224
+ .catch(rethrowAs(endpoint, "create"));
225
225
  endpoint.uri = resultFunction.serviceConfig.uri;
226
226
  const serviceName = resultFunction.serviceConfig.service;
227
227
  if (backend.isHttpsTriggered(endpoint)) {
@@ -243,7 +243,7 @@ class Fabricator {
243
243
  }
244
244
  }
245
245
  const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
246
- if (mem >= backend.MIN_MEMORY_FOR_CONCURRENCY && endpoint.concurrency != 1) {
246
+ if (mem >= backend.MIN_MEMORY_FOR_CONCURRENCY && endpoint.concurrency !== 1) {
247
247
  await this.setConcurrency(endpoint, serviceName, endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY);
248
248
  }
249
249
  }
@@ -56,7 +56,7 @@ async function release(context, options, payload) {
56
56
  await containerCleaner.cleanupBuildImages(haveEndpoints, deletedEndpoints, opts);
57
57
  const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
58
58
  if (allErrors.length) {
59
- const opts = allErrors.length == 1 ? { original: allErrors[0] } : { children: allErrors };
59
+ const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors };
60
60
  throw new error_1.FirebaseError("There was an error deploying functions", Object.assign(Object.assign({}, opts), { exit: 2 }));
61
61
  }
62
62
  }
@@ -74,10 +74,10 @@ function upgradedToGCFv2WithoutSettingConcurrency(want, have) {
74
74
  }
75
75
  exports.upgradedToGCFv2WithoutSettingConcurrency = upgradedToGCFv2WithoutSettingConcurrency;
76
76
  function changedTriggerRegion(want, have) {
77
- if (want.platform != "gcfv2") {
77
+ if (want.platform !== "gcfv2") {
78
78
  return false;
79
79
  }
80
- if (have.platform != "gcfv2") {
80
+ if (have.platform !== "gcfv2") {
81
81
  return false;
82
82
  }
83
83
  if (!backend.isEventTriggered(want)) {
@@ -86,7 +86,7 @@ function changedTriggerRegion(want, have) {
86
86
  if (!backend.isEventTriggered(have)) {
87
87
  return false;
88
88
  }
89
- return want.eventTrigger.region != have.eventTrigger.region;
89
+ return want.eventTrigger.region !== have.eventTrigger.region;
90
90
  }
91
91
  exports.changedTriggerRegion = changedTriggerRegion;
92
92
  function changedV2PubSubTopic(want, have) {
@@ -102,13 +102,13 @@ function changedV2PubSubTopic(want, have) {
102
102
  if (!backend.isEventTriggered(have)) {
103
103
  return false;
104
104
  }
105
- if (want.eventTrigger.eventType != gcfv2.PUBSUB_PUBLISH_EVENT) {
105
+ if (want.eventTrigger.eventType !== gcfv2.PUBSUB_PUBLISH_EVENT) {
106
106
  return false;
107
107
  }
108
108
  if (have.eventTrigger.eventType !== gcfv2.PUBSUB_PUBLISH_EVENT) {
109
109
  return false;
110
110
  }
111
- return have.eventTrigger.eventFilters["resource"] != want.eventTrigger.eventFilters["resource"];
111
+ return have.eventTrigger.eventFilters["resource"] !== want.eventTrigger.eventFilters["resource"];
112
112
  }
113
113
  exports.changedV2PubSubTopic = changedV2PubSubTopic;
114
114
  function upgradedScheduleFromV1ToV2(want, have) {
@@ -132,6 +132,9 @@ function checkForIllegalUpdate(want, have) {
132
132
  if (backend.isHttpsTriggered(e)) {
133
133
  return "an HTTPS";
134
134
  }
135
+ else if (backend.isCallableTriggered(e)) {
136
+ return "a callable";
137
+ }
135
138
  else if (backend.isEventTriggered(e)) {
136
139
  return "a background triggered";
137
140
  }
@@ -145,17 +148,17 @@ function checkForIllegalUpdate(want, have) {
145
148
  };
146
149
  const wantType = triggerType(want);
147
150
  const haveType = triggerType(have);
148
- if (wantType != haveType) {
151
+ if (wantType !== haveType) {
149
152
  throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_2.getFunctionLabel)(want)}] Changing from ${haveType} function to ${wantType} function is not allowed. Please delete your function and create a new one instead.`);
150
153
  }
151
- if (want.platform == "gcfv1" && have.platform == "gcfv2") {
154
+ if (want.platform === "gcfv1" && have.platform === "gcfv2") {
152
155
  throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_2.getFunctionLabel)(want)}] Functions cannot be downgraded from GCFv2 to GCFv1`);
153
156
  }
154
157
  exports.checkForV2Upgrade(want, have);
155
158
  }
156
159
  exports.checkForIllegalUpdate = checkForIllegalUpdate;
157
160
  function checkForV2Upgrade(want, have) {
158
- if (want.platform == "gcfv2" && have.platform == "gcfv1") {
161
+ if (want.platform === "gcfv2" && have.platform === "gcfv1") {
159
162
  throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_2.getFunctionLabel)(have)}] Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.`);
160
163
  }
161
164
  }
@@ -162,6 +162,9 @@ function triggerTag(endpoint) {
162
162
  if (backend.isTaskQueueTriggered(endpoint)) {
163
163
  return `${prefix}.taskQueue`;
164
164
  }
165
+ if (backend.isCallableTriggered(endpoint)) {
166
+ return `${prefix}.callable`;
167
+ }
165
168
  if (backend.isHttpsTriggered(endpoint)) {
166
169
  if ((_a = endpoint.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
167
170
  return `${prefix}.callable`;
@@ -29,18 +29,18 @@ exports.yamlToBackend = yamlToBackend;
29
29
  async function detectFromYaml(directory, project, runtime) {
30
30
  let text;
31
31
  try {
32
- text = await exports.readFileAsync(path.join(directory, "backend.yaml"), "utf8");
32
+ text = await exports.readFileAsync(path.join(directory, "functions.yaml"), "utf8");
33
33
  }
34
34
  catch (err) {
35
35
  if (err.code === "ENOENT") {
36
- logger_1.logger.debug("Could not find backend.yaml. Must use http discovery");
36
+ logger_1.logger.debug("Could not find functions.yaml. Must use http discovery");
37
37
  }
38
38
  else {
39
- logger_1.logger.debug("Unexpected error looking for backend.yaml file:", err);
39
+ logger_1.logger.debug("Unexpected error looking for functions.yaml file:", err);
40
40
  }
41
41
  return;
42
42
  }
43
- logger_1.logger.debug("Found backend.yaml. Got spec:", text);
43
+ logger_1.logger.debug("Found functions.yaml. Got spec:", text);
44
44
  const parsed = yaml.load(text);
45
45
  return yamlToBackend(parsed, project, api.functionsDefaultRegion, runtime);
46
46
  }
@@ -54,7 +54,7 @@ async function detectFromPort(port, project, runtime, timeout = 30000) {
54
54
  });
55
55
  while (true) {
56
56
  try {
57
- res = await Promise.race([(0, node_fetch_1.default)(`http://localhost:${port}/backend.yaml`), timedOut]);
57
+ res = await Promise.race([(0, node_fetch_1.default)(`http://localhost:${port}/__/functions.yaml`), timedOut]);
58
58
  break;
59
59
  }
60
60
  catch (err) {
@@ -65,7 +65,7 @@ async function detectFromPort(port, project, runtime, timeout = 30000) {
65
65
  }
66
66
  }
67
67
  const text = await res.text();
68
- logger_1.logger.debug("Got response from /backend.yaml", text);
68
+ logger_1.logger.debug("Got response from /__/functions.yaml", text);
69
69
  let parsed;
70
70
  try {
71
71
  parsed = yaml.load(text);
@@ -21,7 +21,7 @@ function assertKeyTypes(prefix, yaml, schema) {
21
21
  const key = keyAsString;
22
22
  const fullKey = prefix ? prefix + "." + key : key;
23
23
  if (!schema[key] || schema[key] === "omit") {
24
- throw new error_1.FirebaseError(`Unexpected key ${fullKey}. You may need to install a newer version of the Firebase CLI`);
24
+ throw new error_1.FirebaseError(`Unexpected key ${fullKey}. You may need to install a newer version of the Firebase CLI.`);
25
25
  }
26
26
  if (schema[key] === "string") {
27
27
  if (typeof value !== "string") {
@@ -12,7 +12,7 @@ function backendFromV1Alpha1(yaml, project, region, runtime) {
12
12
  (0, parsing_1.requireKeys)("", manifest, "endpoints");
13
13
  (0, parsing_1.assertKeyTypes)("", manifest, {
14
14
  specVersion: "string",
15
- requiredAPIs: "object",
15
+ requiredAPIs: "array",
16
16
  endpoints: "object",
17
17
  });
18
18
  for (const id of Object.keys(manifest.endpoints)) {
@@ -25,15 +25,14 @@ function backendFromV1Alpha1(yaml, project, region, runtime) {
25
25
  }
26
26
  exports.backendFromV1Alpha1 = backendFromV1Alpha1;
27
27
  function parseRequiredAPIs(manifest) {
28
- const requiredAPIs = {};
29
- if (typeof manifest !== "object" || Array.isArray(manifest)) {
30
- throw new error_1.FirebaseError("Expected requiredApis to be a map of string to string");
31
- }
32
- for (const [api, reason] of Object.entries(manifest.requiredAPIs || {})) {
28
+ const requiredAPIs = manifest.requiredAPIs || [];
29
+ for (const { api, reason } of requiredAPIs) {
30
+ if (typeof api !== "string") {
31
+ throw new error_1.FirebaseError(`Invalid api "${JSON.stringify(api)}. Expected string`);
32
+ }
33
33
  if (typeof reason !== "string") {
34
34
  throw new error_1.FirebaseError(`Invalid reason "${JSON.stringify(reason)} for API ${api}. Expected string`);
35
35
  }
36
- requiredAPIs[api] = reason;
37
36
  }
38
37
  return requiredAPIs;
39
38
  }
@@ -51,13 +50,13 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
51
50
  concurrency: "number",
52
51
  serviceAccountEmail: "string",
53
52
  timeout: "string",
54
- vpcConnector: "string",
55
- vpcConnectorEgressSettings: "string",
53
+ vpc: "object",
56
54
  labels: "object",
57
55
  ingressSettings: "string",
58
56
  environmentVariables: "object",
59
57
  secretEnvironmentVariables: "array",
60
58
  httpsTrigger: "object",
59
+ callableTrigger: "object",
61
60
  eventTrigger: "object",
62
61
  scheduleTrigger: "object",
63
62
  taskQueueTrigger: "object",
@@ -66,6 +65,9 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
66
65
  if (ep.httpsTrigger) {
67
66
  triggerCount++;
68
67
  }
68
+ if (ep.callableTrigger) {
69
+ triggerCount++;
70
+ }
69
71
  if (ep.eventTrigger) {
70
72
  triggerCount++;
71
73
  }
@@ -76,7 +78,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
76
78
  triggerCount++;
77
79
  }
78
80
  if (!triggerCount) {
79
- throw new error_1.FirebaseError("Expected trigger in endpoint" + id);
81
+ throw new error_1.FirebaseError("Expected trigger in endpoint " + id);
80
82
  }
81
83
  if (triggerCount > 1) {
82
84
  throw new error_1.FirebaseError("Multiple triggers defined for endpoint" + id);
@@ -101,6 +103,9 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
101
103
  triggered = { httpsTrigger: {} };
102
104
  (0, proto_1.copyIfPresent)(triggered.httpsTrigger, ep.httpsTrigger, "invoker");
103
105
  }
106
+ else if (backend.isCallableTriggered(ep)) {
107
+ triggered = { callableTrigger: {} };
108
+ }
104
109
  else if (backend.isScheduleTriggered(ep)) {
105
110
  (0, parsing_1.assertKeyTypes)(prefix + ".scheduleTrigger", ep.scheduleTrigger, {
106
111
  schedule: "string",
@@ -149,7 +154,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
149
154
  region,
150
155
  project,
151
156
  runtime, entryPoint: ep.entryPoint }, triggered);
152
- (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeout", "vpcConnector", "vpcConnectorEgressSettings", "labels", "ingressSettings", "environmentVariables");
157
+ (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeout", "vpc", "labels", "ingressSettings", "environmentVariables");
153
158
  allParsed.push(parsed);
154
159
  }
155
160
  return allParsed;
@@ -71,7 +71,7 @@ class Delegate {
71
71
  },
72
72
  stdio: ["ignore", "pipe", "pipe"],
73
73
  });
74
- if (genBinary.status != 0) {
74
+ if (genBinary.status !== 0) {
75
75
  throw new error_1.FirebaseError("Failed to run codegen", {
76
76
  children: [new Error(genBinary.stderr.toString())],
77
77
  });
@@ -96,7 +96,7 @@ class Delegate {
96
96
  childProcess.once("exit", resolve);
97
97
  childProcess.once("error", reject);
98
98
  });
99
- await (0, node_fetch_1.default)(`http://localhost:${adminPort}/quitquitquit`);
99
+ await (0, node_fetch_1.default)(`http://localhost:${adminPort}/__/quitquitquit`);
100
100
  setTimeout(() => {
101
101
  if (!childProcess.killed) {
102
102
  childProcess.kill("SIGKILL");
@@ -4,6 +4,8 @@ exports.Delegate = exports.tryCreateDelegate = void 0;
4
4
  const util_1 = require("util");
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
+ const spawn = require("cross-spawn");
8
+ const node_fetch_1 = require("node-fetch");
7
9
  const error_1 = require("../../../../error");
8
10
  const parseRuntimeAndValidateSDK_1 = require("./parseRuntimeAndValidateSDK");
9
11
  const logger_1 = require("../../../../logger");
@@ -50,6 +52,30 @@ class Delegate {
50
52
  watch() {
51
53
  return Promise.resolve(() => Promise.resolve());
52
54
  }
55
+ serve(port, envs) {
56
+ var _a;
57
+ const childProcess = spawn("./node_modules/.bin/firebase-functions", [this.sourceDir], {
58
+ env: Object.assign(Object.assign({}, envs), { PORT: port.toString(), FUNCTIONS_CONTROL_API: "true", HOME: process.env.HOME, PATH: process.env.PATH }),
59
+ cwd: this.sourceDir,
60
+ stdio: ["ignore", "pipe", "inherit"],
61
+ });
62
+ (_a = childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
63
+ logger_1.logger.debug(chunk.toString());
64
+ });
65
+ return Promise.resolve(async () => {
66
+ const p = new Promise((resolve, reject) => {
67
+ childProcess.once("exit", resolve);
68
+ childProcess.once("error", reject);
69
+ });
70
+ await (0, node_fetch_1.default)(`http://localhost:${port}/__/quitquitquit`);
71
+ setTimeout(() => {
72
+ if (!childProcess.killed) {
73
+ childProcess.kill("SIGKILL");
74
+ }
75
+ }, 10000);
76
+ return p;
77
+ });
78
+ }
53
79
  async discoverSpec(config, env) {
54
80
  return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
55
81
  }
@@ -46,11 +46,11 @@ function getRuntimeChoice(sourceDir, runtimeFromConfig) {
46
46
  ? exports.UNSUPPORTED_NODE_VERSION_FIREBASE_JSON_MSG
47
47
  : exports.UNSUPPORTED_NODE_VERSION_PACKAGE_JSON_MSG) + exports.DEPRECATED_NODE_VERSION_INFO;
48
48
  if (!runtime || !ENGINE_RUNTIMES_NAMES.includes(runtime)) {
49
- track("functions_runtime_notices", "package_missing_runtime");
49
+ void track("functions_runtime_notices", "package_missing_runtime");
50
50
  throw new error_1.FirebaseError(errorMessage, { exit: 1 });
51
51
  }
52
52
  if (runtimes.isDeprecatedRuntime(runtime) || !runtimes.isValidRuntime(runtime)) {
53
- track("functions_runtime_notices", `${runtime}_deploy_prohibited`);
53
+ void track("functions_runtime_notices", `${runtime}_deploy_prohibited`);
54
54
  throw new error_1.FirebaseError(errorMessage, { exit: 1 });
55
55
  }
56
56
  return runtime;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addResourcesToBackend = exports.discoverBackend = exports.useStrategy = void 0;
3
+ exports.addResourcesToBackend = exports.mergeRequiredAPIs = exports.discoverBackend = exports.useStrategy = void 0;
4
4
  const path = require("path");
5
5
  const _ = require("lodash");
6
6
  const child_process_1 = require("child_process");
@@ -58,17 +58,34 @@ async function discoverBackend(projectId, sourceDir, runtime, configValues, envs
58
58
  return want;
59
59
  }
60
60
  exports.discoverBackend = discoverBackend;
61
+ function mergeRequiredAPIs(backend) {
62
+ const apiToReasons = {};
63
+ for (const { api, reason } of backend.requiredAPIs) {
64
+ const reasons = apiToReasons[api] || new Set();
65
+ reasons.add(reason);
66
+ apiToReasons[api] = reasons;
67
+ }
68
+ const merged = [];
69
+ for (const [api, reasons] of Object.entries(apiToReasons)) {
70
+ merged.push({ api, reason: Array.from(reasons).join(" ") });
71
+ }
72
+ backend.requiredAPIs = merged;
73
+ }
74
+ exports.mergeRequiredAPIs = mergeRequiredAPIs;
61
75
  function addResourcesToBackend(projectId, runtime, annotation, want) {
62
76
  Object.freeze(annotation);
63
77
  for (const region of annotation.regions || [api.functionsDefaultRegion]) {
64
78
  let triggered;
65
79
  const triggerCount = +!!annotation.httpsTrigger + +!!annotation.eventTrigger + +!!annotation.taskQueueTrigger;
66
- if (triggerCount != 1) {
80
+ if (triggerCount !== 1) {
67
81
  throw new error_1.FirebaseError("Unexpected annotation generated by the Firebase Functions SDK. This should never happen.");
68
82
  }
69
83
  if (annotation.taskQueueTrigger) {
70
84
  triggered = { taskQueueTrigger: annotation.taskQueueTrigger };
71
- want.requiredAPIs["cloudtasks"] = "cloudtasks.googleapis.com";
85
+ want.requiredAPIs.push({
86
+ api: "cloudtasks.googleapis.com",
87
+ reason: "Needed for task queue functions.",
88
+ });
72
89
  }
73
90
  else if (annotation.httpsTrigger) {
74
91
  const trigger = {};
@@ -79,8 +96,10 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
79
96
  triggered = { httpsTrigger: trigger };
80
97
  }
81
98
  else if (annotation.schedule) {
82
- want.requiredAPIs["pubsub"] = "pubsub.googleapis.com";
83
- want.requiredAPIs["scheduler"] = "cloudscheduler.googleapis.com";
99
+ want.requiredAPIs.push({
100
+ api: "cloudscheduler.googleapis.com",
101
+ reason: "Needed for scheduled functions.",
102
+ });
84
103
  triggered = { scheduleTrigger: annotation.schedule };
85
104
  }
86
105
  else {
@@ -105,7 +124,8 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
105
124
  if (maybeId && !maybeId.includes("/")) {
106
125
  maybeId = `projects/${projectId}/locations/${region}/connectors/${maybeId}`;
107
126
  }
108
- endpoint.vpcConnector = maybeId;
127
+ endpoint.vpc = { connector: maybeId };
128
+ proto.renameIfPresent(endpoint.vpc, annotation, "egressSettings", "vpcConnectorEgressSettings");
109
129
  }
110
130
  if (annotation.secrets) {
111
131
  const secretEnvs = [];
@@ -119,9 +139,10 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
119
139
  }
120
140
  endpoint.secretEnvironmentVariables = secretEnvs;
121
141
  }
122
- proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "vpcConnectorEgressSettings", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
142
+ proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
123
143
  want.endpoints[region] = want.endpoints[region] || {};
124
144
  want.endpoints[region][endpoint.id] = endpoint;
145
+ mergeRequiredAPIs(want);
125
146
  }
126
147
  }
127
148
  exports.addResourcesToBackend = addResourcesToBackend;
@@ -52,7 +52,7 @@ exports.getLatestSDKVersion = getLatestSDKVersion;
52
52
  function checkFunctionsSDKVersion(currentVersion) {
53
53
  try {
54
54
  if (semver.lt(currentVersion, MIN_SDK_VERSION)) {
55
- track("functions_runtime_notices", "functions_sdk_too_old");
55
+ void track("functions_runtime_notices", "functions_sdk_too_old");
56
56
  utils.logWarning(exports.FUNCTIONS_SDK_VERSION_TOO_OLD_WARNING);
57
57
  }
58
58
  const latest = exports.getLatestSDKVersion();
@@ -63,7 +63,7 @@ function checkFunctionsSDKVersion(currentVersion) {
63
63
  return;
64
64
  }
65
65
  utils.logWarning(clc.bold.yellow("functions: ") +
66
- "package.json indicates an outdated version of firebase-functions.\nPlease upgrade using " +
66
+ "package.json indicates an outdated version of firebase-functions. Please upgrade using " +
67
67
  clc.bold("npm install --save firebase-functions@latest") +
68
68
  " in your functions directory.");
69
69
  if (semver.major(currentVersion) < semver.major(latest)) {