firebase-tools 10.2.0 → 10.3.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 (139) hide show
  1. package/lib/apiv2.js +3 -0
  2. package/lib/appdistribution/options-parser-util.js +1 -1
  3. package/lib/auth.js +3 -3
  4. package/lib/command.js +1 -1
  5. package/lib/commands/apps-android-sha-create.js +2 -2
  6. package/lib/commands/apps-sdkconfig.js +1 -1
  7. package/lib/commands/auth-import.js +1 -1
  8. package/lib/commands/database-rules-list.js +2 -2
  9. package/lib/commands/emulators-start.js +1 -1
  10. package/lib/commands/ext-configure.js +58 -4
  11. package/lib/commands/ext-dev-init.js +49 -49
  12. package/lib/commands/ext-export.js +7 -2
  13. package/lib/commands/ext-install.js +163 -104
  14. package/lib/commands/ext-uninstall.js +17 -8
  15. package/lib/commands/ext-update.js +64 -11
  16. package/lib/commands/functions-config-clone.js +1 -1
  17. package/lib/commands/functions-config-export.js +1 -1
  18. package/lib/commands/hosting-clone.js +3 -3
  19. package/lib/commands/remoteconfig-get.js +1 -1
  20. package/lib/config.js +6 -3
  21. package/lib/deploy/extensions/deploymentSummary.js +3 -3
  22. package/lib/deploy/extensions/planner.js +7 -6
  23. package/lib/deploy/extensions/tasks.js +1 -1
  24. package/lib/deploy/functions/backend.js +21 -5
  25. package/lib/deploy/functions/checkIam.js +5 -5
  26. package/lib/deploy/functions/containerCleaner.js +3 -3
  27. package/lib/deploy/functions/ensure.js +3 -3
  28. package/lib/deploy/functions/functionsDeployHelper.js +2 -2
  29. package/lib/deploy/functions/prepare.js +5 -3
  30. package/lib/deploy/functions/pricing.js +1 -1
  31. package/lib/deploy/functions/prompts.js +2 -2
  32. package/lib/deploy/functions/release/fabricator.js +7 -7
  33. package/lib/deploy/functions/release/index.js +1 -1
  34. package/lib/deploy/functions/release/planner.js +43 -26
  35. package/lib/deploy/functions/release/reporter.js +3 -0
  36. package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
  37. package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +22 -12
  39. package/lib/deploy/functions/runtimes/golang/index.js +2 -2
  40. package/lib/deploy/functions/runtimes/node/index.js +53 -0
  41. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
  42. package/lib/deploy/functions/runtimes/node/parseTriggers.js +52 -15
  43. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  44. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  45. package/lib/deploy/functions/services/index.js +9 -1
  46. package/lib/deploy/functions/services/storage.js +10 -4
  47. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  48. package/lib/deploy/functions/validate.js +3 -3
  49. package/lib/deploy/hosting/client.js +9 -0
  50. package/lib/deploy/hosting/convertConfig.js +6 -0
  51. package/lib/deploy/hosting/deploy.js +2 -2
  52. package/lib/deploy/hosting/hashcache.js +21 -19
  53. package/lib/deploy/hosting/index.js +5 -5
  54. package/lib/deploy/hosting/prepare.js +25 -25
  55. package/lib/deploy/hosting/release.js +21 -24
  56. package/lib/deploy/hosting/uploader.js +5 -5
  57. package/lib/deploy/remoteconfig/functions.js +2 -2
  58. package/lib/emulator/auth/cloudFunctions.js +1 -1
  59. package/lib/emulator/auth/operations.js +1 -1
  60. package/lib/emulator/commandUtils.js +5 -1
  61. package/lib/emulator/constants.js +4 -0
  62. package/lib/emulator/controller.js +54 -24
  63. package/lib/emulator/download.js +18 -1
  64. package/lib/emulator/downloadableEmulators.js +30 -13
  65. package/lib/emulator/emulatorLogger.js +12 -1
  66. package/lib/emulator/extensions/validation.js +70 -0
  67. package/lib/emulator/extensionsEmulator.js +175 -0
  68. package/lib/emulator/functionsEmulator.js +106 -44
  69. package/lib/emulator/functionsEmulatorRuntime.js +44 -36
  70. package/lib/emulator/functionsEmulatorShared.js +17 -10
  71. package/lib/emulator/functionsEmulatorShell.js +1 -1
  72. package/lib/emulator/functionsEmulatorUtils.js +4 -4
  73. package/lib/emulator/functionsRuntimeWorker.js +2 -2
  74. package/lib/emulator/hub.js +4 -3
  75. package/lib/emulator/loggingEmulator.js +1 -1
  76. package/lib/emulator/pubsubEmulator.js +1 -1
  77. package/lib/emulator/registry.js +10 -2
  78. package/lib/emulator/storage/apis/firebase.js +314 -332
  79. package/lib/emulator/storage/apis/gcloud.js +241 -121
  80. package/lib/emulator/storage/crc.js +5 -1
  81. package/lib/emulator/storage/errors.js +9 -0
  82. package/lib/emulator/storage/files.js +159 -300
  83. package/lib/emulator/storage/index.js +27 -73
  84. package/lib/emulator/storage/metadata.js +65 -51
  85. package/lib/emulator/storage/multipart.js +62 -0
  86. package/lib/emulator/storage/persistence.js +78 -0
  87. package/lib/emulator/storage/rules/config.js +33 -0
  88. package/lib/emulator/storage/rules/manager.js +81 -0
  89. package/lib/emulator/storage/rules/runtime.js +8 -7
  90. package/lib/emulator/storage/rules/utils.js +48 -0
  91. package/lib/emulator/storage/server.js +2 -2
  92. package/lib/emulator/storage/upload.js +106 -0
  93. package/lib/emulator/types.js +3 -0
  94. package/lib/ensureApiEnabled.js +5 -1
  95. package/lib/error.js +1 -1
  96. package/lib/extensions/askUserForParam.js +1 -1
  97. package/lib/extensions/changelog.js +3 -1
  98. package/lib/extensions/checkProjectBilling.js +1 -1
  99. package/lib/extensions/displayExtensionInfo.js +1 -1
  100. package/lib/extensions/emulator/optionsHelper.js +56 -8
  101. package/lib/extensions/emulator/specHelper.js +10 -23
  102. package/lib/extensions/export.js +1 -51
  103. package/lib/extensions/extensionsApi.js +1 -1
  104. package/lib/extensions/extensionsHelper.js +32 -19
  105. package/lib/extensions/listExtensions.js +2 -0
  106. package/lib/extensions/manifest.js +144 -0
  107. package/lib/extensions/metricsUtils.js +4 -4
  108. package/lib/extensions/paramHelper.js +9 -8
  109. package/lib/extensions/refs.js +1 -1
  110. package/lib/extensions/secretsUtils.js +3 -3
  111. package/lib/functional.js +1 -1
  112. package/lib/functions/env.js +6 -7
  113. package/lib/functions/events/v2.js +11 -0
  114. package/lib/gcp/cloudfunctions.js +42 -11
  115. package/lib/gcp/cloudfunctionsv2.js +48 -17
  116. package/lib/gcp/cloudtasks.js +1 -1
  117. package/lib/gcp/docker.js +2 -2
  118. package/lib/gcp/resourceManager.js +4 -4
  119. package/lib/gcp/run.js +2 -2
  120. package/lib/gcp/storage.js +1 -0
  121. package/lib/hosting/api.js +1 -1
  122. package/lib/hosting/functionsProxy.js +15 -5
  123. package/lib/hosting/proxy.js +2 -2
  124. package/lib/init/features/account.js +1 -1
  125. package/lib/management/database.js +1 -1
  126. package/lib/previews.js +1 -1
  127. package/lib/responseToError.js +16 -7
  128. package/lib/serve/functions.js +2 -1
  129. package/lib/serve/hosting.js +1 -1
  130. package/lib/utils.js +15 -2
  131. package/npm-shrinkwrap.json +904 -412
  132. package/package.json +3 -3
  133. package/schema/firebase-config.json +32 -0
  134. package/templates/init/functions/javascript/package.lint.json +3 -3
  135. package/templates/init/functions/javascript/package.nolint.json +2 -2
  136. package/templates/init/functions/typescript/package.lint.json +7 -7
  137. package/templates/init/functions/typescript/package.nolint.json +3 -3
  138. package/lib/deploy/extensions/params.js +0 -39
  139. package/lib/deploy/functions/eventTypes.js +0 -10
@@ -1,27 +1,39 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.calculateRegionalChanges = void 0;
3
+ exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.calculateChangesets = void 0;
4
4
  const functionsDeployHelper_1 = require("../functionsDeployHelper");
5
- const functionsDeployHelper_2 = require("../functionsDeployHelper");
6
5
  const deploymentTool_1 = require("../../../deploymentTool");
7
6
  const error_1 = require("../../../error");
8
7
  const utils = require("../../../utils");
9
8
  const backend = require("../backend");
10
- const gcfv2 = require("../../../gcp/cloudfunctionsv2");
11
- function calculateRegionalChanges(want, have, options) {
12
- const endpointsToCreate = Object.keys(want)
9
+ const v2events = require("../../../functions/events/v2");
10
+ function calculateChangesets(want, have, keyFn, options) {
11
+ const toCreate = utils.groupBy(Object.keys(want)
13
12
  .filter((id) => !have[id])
14
- .map((id) => want[id]);
15
- const endpointsToDelete = Object.keys(have)
13
+ .map((id) => want[id]), keyFn);
14
+ const toDelete = utils.groupBy(Object.keys(have)
16
15
  .filter((id) => !want[id])
17
16
  .filter((id) => options.deleteAll || (0, deploymentTool_1.isFirebaseManaged)(have[id].labels || {}))
18
- .map((id) => have[id]);
19
- const endpointsToUpdate = Object.keys(want)
17
+ .map((id) => have[id]), keyFn);
18
+ const toUpdate = utils.groupBy(Object.keys(want)
20
19
  .filter((id) => have[id])
21
- .map((id) => calculateUpdate(want[id], have[id]));
22
- return { endpointsToCreate, endpointsToUpdate, endpointsToDelete };
20
+ .map((id) => calculateUpdate(want[id], have[id])), (eu) => keyFn(eu.endpoint));
21
+ const result = {};
22
+ const keys = new Set([
23
+ ...Object.keys(toCreate),
24
+ ...Object.keys(toDelete),
25
+ ...Object.keys(toUpdate),
26
+ ]);
27
+ for (const key of keys) {
28
+ result[key] = {
29
+ endpointsToCreate: toCreate[key] || [],
30
+ endpointsToUpdate: toUpdate[key] || [],
31
+ endpointsToDelete: toDelete[key] || [],
32
+ };
33
+ }
34
+ return result;
23
35
  }
24
- exports.calculateRegionalChanges = calculateRegionalChanges;
36
+ exports.calculateChangesets = calculateChangesets;
25
37
  function calculateUpdate(want, have) {
26
38
  checkForIllegalUpdate(want, have);
27
39
  const update = {
@@ -37,7 +49,7 @@ function calculateUpdate(want, have) {
37
49
  }
38
50
  exports.calculateUpdate = calculateUpdate;
39
51
  function createDeploymentPlan(want, have, options = {}) {
40
- const deployment = {};
52
+ let deployment = {};
41
53
  want = backend.matchingBackend(want, (endpoint) => {
42
54
  return (0, functionsDeployHelper_1.functionMatchesAnyGroup)(endpoint, options.filters || []);
43
55
  });
@@ -46,7 +58,8 @@ function createDeploymentPlan(want, have, options = {}) {
46
58
  });
47
59
  const regions = new Set([...Object.keys(want.endpoints), ...Object.keys(have.endpoints)]);
48
60
  for (const region of regions) {
49
- deployment[region] = calculateRegionalChanges(want.endpoints[region] || {}, have.endpoints[region] || {}, options);
61
+ const changesets = calculateChangesets(want.endpoints[region] || {}, have.endpoints[region] || {}, (e) => `${e.region}-${e.availableMemoryMb || "default"}`, options);
62
+ deployment = Object.assign(Object.assign({}, deployment), changesets);
50
63
  }
51
64
  if (upgradedToGCFv2WithoutSettingConcurrency(want, have)) {
52
65
  utils.logLabeledBullet("functions", "You are updating one or more functions to Google Cloud Functions v2, " +
@@ -74,10 +87,10 @@ function upgradedToGCFv2WithoutSettingConcurrency(want, have) {
74
87
  }
75
88
  exports.upgradedToGCFv2WithoutSettingConcurrency = upgradedToGCFv2WithoutSettingConcurrency;
76
89
  function changedTriggerRegion(want, have) {
77
- if (want.platform != "gcfv2") {
90
+ if (want.platform !== "gcfv2") {
78
91
  return false;
79
92
  }
80
- if (have.platform != "gcfv2") {
93
+ if (have.platform !== "gcfv2") {
81
94
  return false;
82
95
  }
83
96
  if (!backend.isEventTriggered(want)) {
@@ -86,10 +99,11 @@ function changedTriggerRegion(want, have) {
86
99
  if (!backend.isEventTriggered(have)) {
87
100
  return false;
88
101
  }
89
- return want.eventTrigger.region != have.eventTrigger.region;
102
+ return want.eventTrigger.region !== have.eventTrigger.region;
90
103
  }
91
104
  exports.changedTriggerRegion = changedTriggerRegion;
92
105
  function changedV2PubSubTopic(want, have) {
106
+ var _a, _b;
93
107
  if (want.platform !== "gcfv2") {
94
108
  return false;
95
109
  }
@@ -102,13 +116,13 @@ function changedV2PubSubTopic(want, have) {
102
116
  if (!backend.isEventTriggered(have)) {
103
117
  return false;
104
118
  }
105
- if (want.eventTrigger.eventType != gcfv2.PUBSUB_PUBLISH_EVENT) {
119
+ if (want.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) {
106
120
  return false;
107
121
  }
108
- if (have.eventTrigger.eventType !== gcfv2.PUBSUB_PUBLISH_EVENT) {
122
+ if (have.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) {
109
123
  return false;
110
124
  }
111
- return have.eventTrigger.eventFilters["resource"] != want.eventTrigger.eventFilters["resource"];
125
+ return (((_a = backend.findEventFilter(have, "topic")) === null || _a === void 0 ? void 0 : _a.value) !== ((_b = backend.findEventFilter(want, "topic")) === null || _b === void 0 ? void 0 : _b.value));
112
126
  }
113
127
  exports.changedV2PubSubTopic = changedV2PubSubTopic;
114
128
  function upgradedScheduleFromV1ToV2(want, have) {
@@ -132,6 +146,9 @@ function checkForIllegalUpdate(want, have) {
132
146
  if (backend.isHttpsTriggered(e)) {
133
147
  return "an HTTPS";
134
148
  }
149
+ else if (backend.isCallableTriggered(e)) {
150
+ return "a callable";
151
+ }
135
152
  else if (backend.isEventTriggered(e)) {
136
153
  return "a background triggered";
137
154
  }
@@ -145,18 +162,18 @@ function checkForIllegalUpdate(want, have) {
145
162
  };
146
163
  const wantType = triggerType(want);
147
164
  const haveType = triggerType(have);
148
- if (wantType != haveType) {
149
- 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.`);
165
+ if (wantType !== haveType) {
166
+ throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Changing from ${haveType} function to ${wantType} function is not allowed. Please delete your function and create a new one instead.`);
150
167
  }
151
- if (want.platform == "gcfv1" && have.platform == "gcfv2") {
152
- throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_2.getFunctionLabel)(want)}] Functions cannot be downgraded from GCFv2 to GCFv1`);
168
+ if (want.platform === "gcfv1" && have.platform === "gcfv2") {
169
+ throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Functions cannot be downgraded from GCFv2 to GCFv1`);
153
170
  }
154
171
  exports.checkForV2Upgrade(want, have);
155
172
  }
156
173
  exports.checkForIllegalUpdate = checkForIllegalUpdate;
157
174
  function checkForV2Upgrade(want, have) {
158
- if (want.platform == "gcfv2" && have.platform == "gcfv1") {
159
- 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.`);
175
+ if (want.platform === "gcfv2" && have.platform === "gcfv1") {
176
+ throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.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
177
  }
161
178
  }
162
179
  exports.checkForV2Upgrade = checkForV2Upgrade;
@@ -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);
@@ -86,13 +88,18 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
86
88
  if (backend.isEventTriggered(ep)) {
87
89
  (0, parsing_1.requireKeys)(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters");
88
90
  (0, parsing_1.assertKeyTypes)(prefix + ".eventTrigger", ep.eventTrigger, {
89
- eventFilters: "object",
91
+ eventFilters: "array",
90
92
  eventType: "string",
91
93
  retry: "boolean",
92
94
  region: "string",
93
95
  serviceAccountEmail: "string",
94
96
  });
95
97
  triggered = { eventTrigger: ep.eventTrigger };
98
+ for (const eventFilter of triggered.eventTrigger.eventFilters) {
99
+ if (eventFilter.attribute === "topic" && !eventFilter.value.startsWith("projects/")) {
100
+ eventFilter.value = `projects/${project}/topics/${eventFilter.value}`;
101
+ }
102
+ }
96
103
  }
97
104
  else if (backend.isHttpsTriggered(ep)) {
98
105
  (0, parsing_1.assertKeyTypes)(prefix + ".httpsTrigger", ep.httpsTrigger, {
@@ -101,6 +108,9 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
101
108
  triggered = { httpsTrigger: {} };
102
109
  (0, proto_1.copyIfPresent)(triggered.httpsTrigger, ep.httpsTrigger, "invoker");
103
110
  }
111
+ else if (backend.isCallableTriggered(ep)) {
112
+ triggered = { callableTrigger: {} };
113
+ }
104
114
  else if (backend.isScheduleTriggered(ep)) {
105
115
  (0, parsing_1.assertKeyTypes)(prefix + ".scheduleTrigger", ep.scheduleTrigger, {
106
116
  schedule: "string",
@@ -149,7 +159,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
149
159
  region,
150
160
  project,
151
161
  runtime, entryPoint: ep.entryPoint }, triggered);
152
- (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeout", "vpcConnector", "vpcConnectorEgressSettings", "labels", "ingressSettings", "environmentVariables");
162
+ (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeout", "vpc", "labels", "ingressSettings", "environmentVariables");
153
163
  allParsed.push(parsed);
154
164
  }
155
165
  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,12 +4,20 @@ 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 portfinder = require("portfinder");
8
+ const semver = require("semver");
9
+ const spawn = require("cross-spawn");
10
+ const node_fetch_1 = require("node-fetch");
7
11
  const error_1 = require("../../../../error");
8
12
  const parseRuntimeAndValidateSDK_1 = require("./parseRuntimeAndValidateSDK");
9
13
  const logger_1 = require("../../../../logger");
14
+ const previews_1 = require("../../../../previews");
15
+ const utils_1 = require("../../../../utils");
16
+ const discovery = require("../discovery");
10
17
  const validate = require("./validate");
11
18
  const versioning = require("./versioning");
12
19
  const parseTriggers = require("./parseTriggers");
20
+ const MIN_FUNCTIONS_SDK_VERSION = "3.19.0";
13
21
  async function tryCreateDelegate(context) {
14
22
  const packageJsonPath = path.join(context.sourceDir, "package.json");
15
23
  if (!(await (0, util_1.promisify)(fs.exists)(packageJsonPath))) {
@@ -50,7 +58,52 @@ class Delegate {
50
58
  watch() {
51
59
  return Promise.resolve(() => Promise.resolve());
52
60
  }
61
+ serve(port, envs) {
62
+ var _a;
63
+ const childProcess = spawn("./node_modules/.bin/firebase-functions", [this.sourceDir], {
64
+ env: Object.assign(Object.assign({}, envs), { PORT: port.toString(), FUNCTIONS_CONTROL_API: "true", HOME: process.env.HOME, PATH: process.env.PATH }),
65
+ cwd: this.sourceDir,
66
+ stdio: ["ignore", "pipe", "inherit"],
67
+ });
68
+ (_a = childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
69
+ logger_1.logger.debug(chunk.toString());
70
+ });
71
+ return Promise.resolve(async () => {
72
+ const p = new Promise((resolve, reject) => {
73
+ childProcess.once("exit", resolve);
74
+ childProcess.once("error", reject);
75
+ });
76
+ await (0, node_fetch_1.default)(`http://localhost:${port}/__/quitquitquit`);
77
+ setTimeout(() => {
78
+ if (!childProcess.killed) {
79
+ childProcess.kill("SIGKILL");
80
+ }
81
+ }, 10000);
82
+ return p;
83
+ });
84
+ }
53
85
  async discoverSpec(config, env) {
86
+ if (previews_1.previews.functionsv2) {
87
+ if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) {
88
+ (0, utils_1.logLabeledWarning)("functions", `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` +
89
+ `Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}`);
90
+ return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
91
+ }
92
+ let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
93
+ if (!discovered) {
94
+ const getPort = (0, util_1.promisify)(portfinder.getPort);
95
+ const port = await getPort();
96
+ const kill = await this.serve(port, env);
97
+ try {
98
+ discovered = await discovery.detectFromPort(port, this.projectId, this.runtime);
99
+ }
100
+ finally {
101
+ await kill();
102
+ }
103
+ }
104
+ discovered.environmentVariables = env;
105
+ return discovered;
106
+ }
54
107
  return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
55
108
  }
56
109
  }
@@ -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");
@@ -9,7 +9,7 @@ const logger_1 = require("../../../../logger");
9
9
  const backend = require("../../backend");
10
10
  const api = require("../../../../api");
11
11
  const proto = require("../../../../gcp/proto");
12
- const eventTypes_1 = require("../../eventTypes");
12
+ const v2events = require("../../../../functions/events/v2");
13
13
  const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js");
14
14
  function removeInspectOptions(options) {
15
15
  return options.filter((opt) => !opt.startsWith("--inspect"));
@@ -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,24 +96,42 @@ 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 {
87
106
  triggered = {
88
107
  eventTrigger: {
89
108
  eventType: annotation.eventTrigger.eventType,
90
- eventFilters: {
91
- resource: annotation.eventTrigger.resource,
92
- },
109
+ eventFilters: [
110
+ {
111
+ attribute: "resource",
112
+ value: annotation.eventTrigger.resource,
113
+ },
114
+ ],
93
115
  retry: !!annotation.failurePolicy,
94
116
  },
95
117
  };
96
- if (eventTypes_1.STORAGE_V2_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
97
- triggered.eventTrigger.eventFilters = {
98
- bucket: annotation.eventTrigger.resource,
99
- };
118
+ if (annotation.platform === "gcfv2") {
119
+ if (annotation.eventTrigger.eventType === v2events.PUBSUB_PUBLISH_EVENT) {
120
+ triggered.eventTrigger.eventFilters = [
121
+ {
122
+ attribute: "topic",
123
+ value: annotation.eventTrigger.resource,
124
+ },
125
+ ];
126
+ }
127
+ if (v2events.STORAGE_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
128
+ triggered.eventTrigger.eventFilters = [
129
+ {
130
+ attribute: "bucket",
131
+ value: annotation.eventTrigger.resource,
132
+ },
133
+ ];
134
+ }
100
135
  }
101
136
  }
102
137
  const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", id: annotation.name, region: region, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime }, triggered);
@@ -105,7 +140,8 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
105
140
  if (maybeId && !maybeId.includes("/")) {
106
141
  maybeId = `projects/${projectId}/locations/${region}/connectors/${maybeId}`;
107
142
  }
108
- endpoint.vpcConnector = maybeId;
143
+ endpoint.vpc = { connector: maybeId };
144
+ proto.renameIfPresent(endpoint.vpc, annotation, "egressSettings", "vpcConnectorEgressSettings");
109
145
  }
110
146
  if (annotation.secrets) {
111
147
  const secretEnvs = [];
@@ -119,9 +155,10 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
119
155
  }
120
156
  endpoint.secretEnvironmentVariables = secretEnvs;
121
157
  }
122
- proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "vpcConnectorEgressSettings", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
158
+ proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
123
159
  want.endpoints[region] = want.endpoints[region] || {};
124
160
  want.endpoints[region][endpoint.id] = endpoint;
161
+ mergeRequiredAPIs(want);
125
162
  }
126
163
  }
127
164
  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)) {
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureFirebaseAlertsTriggerRegion = exports.obtainFirebaseAlertsBindings = exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = void 0;
4
+ const error_1 = require("../../../error");
5
+ exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator";
6
+ function obtainFirebaseAlertsBindings(projectNumber, existingPolicy) {
7
+ const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
8
+ let pubsubBinding = existingPolicy.bindings.find((b) => b.role === exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE);
9
+ if (!pubsubBinding) {
10
+ pubsubBinding = {
11
+ role: exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE,
12
+ members: [],
13
+ };
14
+ }
15
+ if (!pubsubBinding.members.find((m) => m === pubsubServiceAgent)) {
16
+ pubsubBinding.members.push(pubsubServiceAgent);
17
+ }
18
+ return Promise.resolve([pubsubBinding]);
19
+ }
20
+ exports.obtainFirebaseAlertsBindings = obtainFirebaseAlertsBindings;
21
+ function ensureFirebaseAlertsTriggerRegion(endpoint) {
22
+ if (!endpoint.eventTrigger.region) {
23
+ endpoint.eventTrigger.region = "global";
24
+ }
25
+ if (endpoint.eventTrigger.region !== "global") {
26
+ throw new error_1.FirebaseError("A firebase alerts trigger must specify 'global' trigger location");
27
+ }
28
+ return Promise.resolve();
29
+ }
30
+ exports.ensureFirebaseAlertsTriggerRegion = ensureFirebaseAlertsTriggerRegion;
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
3
+ exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.FirebaseAlertsService = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
4
4
  const backend = require("../backend");
5
5
  const storage_1 = require("./storage");
6
+ const firebaseAlerts_1 = require("./firebaseAlerts");
6
7
  const noop = () => Promise.resolve();
7
8
  exports.NoOpService = {
8
9
  name: "noop",
@@ -22,12 +23,19 @@ exports.StorageService = {
22
23
  requiredProjectBindings: storage_1.obtainStorageBindings,
23
24
  ensureTriggerRegion: storage_1.ensureStorageTriggerRegion,
24
25
  };
26
+ exports.FirebaseAlertsService = {
27
+ name: "firebasealerts",
28
+ api: "logging.googleapis.com",
29
+ requiredProjectBindings: firebaseAlerts_1.obtainFirebaseAlertsBindings,
30
+ ensureTriggerRegion: firebaseAlerts_1.ensureFirebaseAlertsTriggerRegion,
31
+ };
25
32
  exports.EVENT_SERVICE_MAPPING = {
26
33
  "google.cloud.pubsub.topic.v1.messagePublished": exports.PubSubService,
27
34
  "google.cloud.storage.object.v1.finalized": exports.StorageService,
28
35
  "google.cloud.storage.object.v1.archived": exports.StorageService,
29
36
  "google.cloud.storage.object.v1.deleted": exports.StorageService,
30
37
  "google.cloud.storage.object.v1.metadataUpdated": exports.StorageService,
38
+ "google.firebase.firebasealerts.alerts.v1.published": exports.FirebaseAlertsService,
31
39
  };
32
40
  function serviceForEndpoint(endpoint) {
33
41
  if (!backend.isEventTriggered(endpoint)) {