firebase-tools 10.2.2 → 10.4.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 (76) hide show
  1. package/lib/commands/deploy.js +1 -1
  2. package/lib/commands/experimental-functions-shell.js +1 -1
  3. package/lib/commands/ext-configure.js +68 -7
  4. package/lib/commands/ext-export.js +10 -9
  5. package/lib/commands/ext-install.js +73 -9
  6. package/lib/commands/ext-uninstall.js +9 -0
  7. package/lib/commands/ext-update.js +58 -3
  8. package/lib/commands/functions-config-export.js +2 -2
  9. package/lib/commands/functions-shell.js +1 -1
  10. package/lib/commands/hosting-channel-create.js +2 -2
  11. package/lib/commands/hosting-channel-delete.js +2 -2
  12. package/lib/commands/hosting-channel-deploy.js +2 -2
  13. package/lib/commands/hosting-channel-list.js +2 -2
  14. package/lib/commands/hosting-channel-open.js +2 -2
  15. package/lib/commands/hosting-sites-delete.js +2 -2
  16. package/lib/commands/serve.js +1 -1
  17. package/lib/commands/target-apply.js +2 -2
  18. package/lib/commands/target-clear.js +2 -2
  19. package/lib/commands/target-remove.js +2 -2
  20. package/lib/commands/target.js +2 -2
  21. package/lib/config.js +9 -3
  22. package/lib/deploy/extensions/planner.js +15 -9
  23. package/lib/deploy/functions/backend.js +10 -1
  24. package/lib/deploy/functions/checkIam.js +4 -4
  25. package/lib/deploy/functions/prepare.js +2 -1
  26. package/lib/deploy/functions/release/fabricator.js +4 -4
  27. package/lib/deploy/functions/release/planner.js +34 -20
  28. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
  29. package/lib/deploy/functions/runtimes/node/index.js +27 -0
  30. package/lib/deploy/functions/runtimes/node/parseTriggers.js +36 -13
  31. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  32. package/lib/deploy/functions/services/index.js +9 -1
  33. package/lib/deploy/functions/services/storage.js +10 -4
  34. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  35. package/lib/emulator/auth/apiSpec.js +37 -0
  36. package/lib/emulator/commandUtils.js +2 -2
  37. package/lib/emulator/constants.js +1 -0
  38. package/lib/emulator/controller.js +9 -7
  39. package/lib/emulator/extensions/validation.js +37 -2
  40. package/lib/emulator/extensionsEmulator.js +47 -9
  41. package/lib/emulator/functionsEmulator.js +17 -12
  42. package/lib/emulator/functionsEmulatorShared.js +34 -11
  43. package/lib/emulator/storage/apis/firebase.js +316 -341
  44. package/lib/emulator/storage/apis/gcloud.js +238 -113
  45. package/lib/emulator/storage/crc.js +5 -1
  46. package/lib/emulator/storage/errors.js +9 -0
  47. package/lib/emulator/storage/files.js +161 -304
  48. package/lib/emulator/storage/index.js +25 -74
  49. package/lib/emulator/storage/metadata.js +63 -49
  50. package/lib/emulator/storage/multipart.js +62 -0
  51. package/lib/emulator/storage/persistence.js +78 -0
  52. package/lib/emulator/storage/rules/config.js +34 -0
  53. package/lib/emulator/storage/rules/manager.js +98 -0
  54. package/lib/emulator/storage/rules/runtime.js +4 -0
  55. package/lib/emulator/storage/rules/utils.js +48 -0
  56. package/lib/emulator/storage/server.js +2 -2
  57. package/lib/emulator/storage/upload.js +106 -0
  58. package/lib/extensions/askUserForParam.js +77 -28
  59. package/lib/extensions/emulator/optionsHelper.js +35 -3
  60. package/lib/extensions/extensionsHelper.js +19 -10
  61. package/lib/extensions/manifest.js +142 -14
  62. package/lib/extensions/paramHelper.js +32 -9
  63. package/lib/fsutils.js +14 -1
  64. package/lib/functions/env.js +4 -6
  65. package/lib/functions/events/v2.js +11 -0
  66. package/lib/gcp/cloudfunctions.js +20 -7
  67. package/lib/gcp/cloudfunctionsv2.js +30 -12
  68. package/lib/gcp/resourceManager.js +4 -4
  69. package/lib/requireConfig.js +11 -9
  70. package/lib/serve/functions.js +2 -1
  71. package/lib/utils.js +14 -1
  72. package/npm-shrinkwrap.json +2 -2
  73. package/package.json +1 -1
  74. package/lib/deploy/extensions/params.js +0 -42
  75. package/lib/deploy/functions/eventTypes.js +0 -10
  76. package/lib/prepareUpload.js +0 -44
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const clc = require("cli-color");
4
4
  const command_1 = require("../command");
5
5
  const logger_1 = require("../logger");
6
- const requireConfig = require("../requireConfig");
6
+ const requireConfig_1 = require("../requireConfig");
7
7
  const utils = require("../utils");
8
8
  const error_1 = require("../error");
9
9
  exports.default = new command_1.Command("target:apply <type> <name> <resources...>")
10
10
  .description("apply a deploy target to a resource")
11
- .before(requireConfig)
11
+ .before(requireConfig_1.requireConfig)
12
12
  .action((type, name, resources, options) => {
13
13
  if (!options.project) {
14
14
  throw new error_1.FirebaseError(`Must have an active project to set deploy targets. Try ${clc.bold("firebase use --add")}`);
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const clc = require("cli-color");
4
4
  const command_1 = require("../command");
5
- const requireConfig = require("../requireConfig");
5
+ const requireConfig_1 = require("../requireConfig");
6
6
  const utils = require("../utils");
7
7
  exports.default = new command_1.Command("target:clear <type> <target>")
8
8
  .description("clear all resources from a named resource target")
9
- .before(requireConfig)
9
+ .before(requireConfig_1.requireConfig)
10
10
  .action((type, name, options) => {
11
11
  const existed = options.rc.clearTarget(options.project, type, name);
12
12
  if (existed) {
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const clc = require("cli-color");
4
4
  const command_1 = require("../command");
5
- const requireConfig = require("../requireConfig");
5
+ const requireConfig_1 = require("../requireConfig");
6
6
  const utils = require("../utils");
7
7
  exports.default = new command_1.Command("target:remove <type> <resource>")
8
8
  .description("remove a resource target")
9
- .before(requireConfig)
9
+ .before(requireConfig_1.requireConfig)
10
10
  .action((type, resource, options) => {
11
11
  const name = options.rc.removeTarget(options.project, type, resource);
12
12
  if (name) {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const clc = require("cli-color");
4
4
  const command_1 = require("../command");
5
5
  const logger_1 = require("../logger");
6
- const requireConfig = require("../requireConfig");
6
+ const requireConfig_1 = require("../requireConfig");
7
7
  const utils = require("../utils");
8
8
  function logTargets(type, targets) {
9
9
  logger_1.logger.info(clc.cyan("[ " + type + " ]"));
@@ -13,7 +13,7 @@ function logTargets(type, targets) {
13
13
  }
14
14
  exports.default = new command_1.Command("target [type]")
15
15
  .description("display configured deploy targets for the current project")
16
- .before(requireConfig)
16
+ .before(requireConfig_1.requireConfig)
17
17
  .action((type, options) => {
18
18
  if (!options.project) {
19
19
  return utils.reject("No active project, cannot list deploy targets.");
package/lib/config.js CHANGED
@@ -17,12 +17,12 @@ const logger_1 = require("./logger");
17
17
  const loadCJSON = require("./loadCJSON");
18
18
  const parseBoltRules = require("./parseBoltRules");
19
19
  class Config {
20
- constructor(src, options) {
20
+ constructor(src, options = {}) {
21
21
  this.data = {};
22
22
  this.defaults = {};
23
23
  this.notes = {};
24
- this.options = options || {};
25
- this.projectDir = options.projectDir || (0, detectProjectRoot_1.detectProjectRoot)(options);
24
+ this.options = options;
25
+ this.projectDir = this.options.projectDir || (0, detectProjectRoot_1.detectProjectRoot)(this.options);
26
26
  this._src = src;
27
27
  if (this._src.firebase) {
28
28
  this.defaults.project = this._src.firebase;
@@ -144,6 +144,12 @@ class Config {
144
144
  fs.ensureFileSync(this.path(p));
145
145
  fs.writeFileSync(this.path(p), content, "utf8");
146
146
  }
147
+ projectFileExists(p) {
148
+ return fs.existsSync(this.path(p));
149
+ }
150
+ deleteProjectFile(p) {
151
+ fs.removeSync(this.path(p));
152
+ }
147
153
  askWriteProjectFile(p, content, force) {
148
154
  const writeTo = this.path(p);
149
155
  let next;
@@ -2,12 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveVersion = exports.want = exports.have = exports.getExtension = exports.getExtensionVersion = void 0;
4
4
  const semver = require("semver");
5
- const error_1 = require("../../error");
6
5
  const extensionsApi = require("../../extensions/extensionsApi");
7
- const extensionsHelper_1 = require("../../extensions/extensionsHelper");
8
6
  const refs = require("../../extensions/refs");
9
- const params_1 = require("./params");
7
+ const error_1 = require("../../error");
8
+ const extensionsHelper_1 = require("../../extensions/extensionsHelper");
10
9
  const logger_1 = require("../../logger");
10
+ const manifest_1 = require("../../extensions/manifest");
11
11
  async function getExtensionVersion(i) {
12
12
  if (!i.extensionVersion) {
13
13
  if (!i.ref) {
@@ -52,15 +52,15 @@ async function want(args) {
52
52
  const instanceId = e[0];
53
53
  const ref = refs.parse(e[1]);
54
54
  ref.version = await resolveVersion(ref);
55
- const params = (0, params_1.readParams)({
55
+ const params = (0, manifest_1.readInstanceParam)({
56
56
  projectDir: args.projectDir,
57
57
  instanceId,
58
58
  projectId: args.projectId,
59
59
  projectNumber: args.projectNumber,
60
60
  aliases: args.aliases,
61
- checkLocal: args.checkLocal,
61
+ checkLocal: args.emulatorMode,
62
62
  });
63
- const autoPopulatedParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
63
+ const autoPopulatedParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId, args.emulatorMode);
64
64
  const subbedParams = (0, extensionsHelper_1.substituteParams)(params, autoPopulatedParams);
65
65
  instanceSpecs.push({
66
66
  instanceId,
@@ -81,11 +81,17 @@ async function want(args) {
81
81
  }
82
82
  exports.want = want;
83
83
  async function resolveVersion(ref) {
84
- if (!ref.version || ref.version === "latest") {
85
- return "latest";
86
- }
87
84
  const extensionRef = refs.toExtensionRef(ref);
88
85
  const versions = await extensionsApi.listExtensionVersions(extensionRef);
86
+ if (versions.length === 0) {
87
+ throw new error_1.FirebaseError(`No versions found for ${extensionRef}`);
88
+ }
89
+ if (!ref.version || ref.version === "latest") {
90
+ return versions
91
+ .map((ev) => ev.spec.version)
92
+ .sort(semver.compare)
93
+ .pop();
94
+ }
89
95
  const maxSatisfying = semver.maxSatisfying(versions.map((ev) => ev.spec.version), ref.version);
90
96
  if (!maxSatisfying) {
91
97
  throw new error_1.FirebaseError(`No version of ${extensionRef} matches requested version ${ref.version}`);
@@ -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.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
3
+ exports.compareFunctions = exports.findEventFilter = 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;
4
4
  const gcf = require("../../gcp/cloudfunctions");
5
5
  const gcfV2 = require("../../gcp/cloudfunctionsv2");
6
6
  const utils = require("../../utils");
@@ -42,6 +42,11 @@ exports.memoryOptionDisplayName = memoryOptionDisplayName;
42
42
  exports.DEFAULT_MEMORY = 256;
43
43
  exports.MIN_MEMORY_FOR_CONCURRENCY = 2048;
44
44
  exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
45
+ function secretVersionName(s) {
46
+ var _a;
47
+ return `projects/${s.projectId}/secrets/${s.secret}/versions/${(_a = s.version) !== null && _a !== void 0 ? _a : "latest"}`;
48
+ }
49
+ exports.secretVersionName = secretVersionName;
45
50
  function isHttpsTriggered(triggered) {
46
51
  return {}.hasOwnProperty.call(triggered, "httpsTrigger");
47
52
  }
@@ -225,6 +230,10 @@ const missingEndpoint = (backend) => (endpoint) => {
225
230
  return !(0, exports.hasEndpoint)(backend)(endpoint);
226
231
  };
227
232
  exports.missingEndpoint = missingEndpoint;
233
+ function findEventFilter(endpoint, attribute) {
234
+ return endpoint.eventTrigger.eventFilters.find((ef) => ef.attribute === attribute);
235
+ }
236
+ exports.findEventFilter = findEventFilter;
228
237
  function compareFunctions(left, right) {
229
238
  if (left.platform !== right.platform) {
230
239
  return right.platform < left.platform ? -1 : 1;
@@ -87,7 +87,7 @@ function mergeBindings(policy, allRequiredBindings) {
87
87
  }
88
88
  }
89
89
  exports.mergeBindings = mergeBindings;
90
- async function ensureServiceAgentRoles(projectId, want, have) {
90
+ async function ensureServiceAgentRoles(projectNumber, want, have) {
91
91
  const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
92
92
  const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
93
93
  const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
@@ -96,7 +96,7 @@ async function ensureServiceAgentRoles(projectId, want, have) {
96
96
  }
97
97
  let policy;
98
98
  try {
99
- policy = await (0, resourceManager_1.getIamPolicy)(projectId);
99
+ policy = await (0, resourceManager_1.getIamPolicy)(projectNumber);
100
100
  }
101
101
  catch (err) {
102
102
  utils.logLabeledBullet("functions", "Could not verify the necessary IAM configuration for the following newly-integrated services: " +
@@ -105,11 +105,11 @@ async function ensureServiceAgentRoles(projectId, want, have) {
105
105
  return;
106
106
  }
107
107
  const findRequiredBindings = [];
108
- newServices.forEach((service) => findRequiredBindings.push(service.requiredProjectBindings(projectId, policy)));
108
+ newServices.forEach((service) => findRequiredBindings.push(service.requiredProjectBindings(projectNumber, policy)));
109
109
  const allRequiredBindings = await Promise.all(findRequiredBindings);
110
110
  mergeBindings(policy, allRequiredBindings);
111
111
  try {
112
- await (0, resourceManager_1.setIamPolicy)(projectId, policy, "bindings");
112
+ await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
113
113
  }
114
114
  catch (err) {
115
115
  throw new error_1.FirebaseError("We failed to modify the IAM policy for the project. The functions " +
@@ -27,6 +27,7 @@ function hasDotenv(opts) {
27
27
  }
28
28
  async function prepare(context, options, payload) {
29
29
  const projectId = (0, projectUtils_1.needProjectId)(options);
30
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
30
31
  const sourceDirName = options.config.get("functions.source");
31
32
  if (!sourceDirName) {
32
33
  throw new error_1.FirebaseError(`No functions code detected at default location (./functions), and no functions.source defined in firebase.json`);
@@ -111,7 +112,7 @@ async function prepare(context, options, payload) {
111
112
  return (0, functionsDeployHelper_1.functionMatchesAnyGroup)(endpoint, context.filters);
112
113
  });
113
114
  const haveBackend = await backend.existingBackend(context);
114
- await (0, checkIam_1.ensureServiceAgentRoles)(projectId, wantBackend, haveBackend);
115
+ await (0, checkIam_1.ensureServiceAgentRoles)(projectNumber, wantBackend, haveBackend);
115
116
  inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
116
117
  await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
117
118
  await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
@@ -51,12 +51,12 @@ class Fabricator {
51
51
  totalTime: 0,
52
52
  results: [],
53
53
  };
54
- const deployRegions = Object.values(plan).map(async (changes) => {
55
- const results = await this.applyRegionalChanges(changes);
54
+ const deployChangesets = Object.values(plan).map(async (changes) => {
55
+ const results = await this.applyChangeset(changes);
56
56
  summary.results.push(...results);
57
57
  return;
58
58
  });
59
- const promiseResults = await utils.allSettled(deployRegions);
59
+ const promiseResults = await utils.allSettled(deployChangesets);
60
60
  const errs = promiseResults
61
61
  .filter((r) => r.status === "rejected")
62
62
  .map((r) => r.reason);
@@ -66,7 +66,7 @@ class Fabricator {
66
66
  summary.totalTime = timer.stop();
67
67
  return summary;
68
68
  }
69
- async applyRegionalChanges(changes) {
69
+ async applyChangeset(changes) {
70
70
  const deployResults = [];
71
71
  const handle = async (op, endpoint, fn) => {
72
72
  const timer = new timer_1.Timer();
@@ -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, " +
@@ -90,6 +103,7 @@ function changedTriggerRegion(want, have) {
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) {
@@ -149,17 +163,17 @@ function checkForIllegalUpdate(want, have) {
149
163
  const wantType = triggerType(want);
150
164
  const haveType = triggerType(have);
151
165
  if (wantType !== haveType) {
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.`);
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.`);
153
167
  }
154
168
  if (want.platform === "gcfv1" && have.platform === "gcfv2") {
155
- throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_2.getFunctionLabel)(want)}] Functions cannot be downgraded from GCFv2 to GCFv1`);
169
+ throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Functions cannot be downgraded from GCFv2 to GCFv1`);
156
170
  }
157
171
  exports.checkForV2Upgrade(want, have);
158
172
  }
159
173
  exports.checkForIllegalUpdate = checkForIllegalUpdate;
160
174
  function checkForV2Upgrade(want, have) {
161
175
  if (want.platform === "gcfv2" && have.platform === "gcfv1") {
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.`);
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.`);
163
177
  }
164
178
  }
165
179
  exports.checkForV2Upgrade = checkForV2Upgrade;
@@ -88,13 +88,18 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
88
88
  if (backend.isEventTriggered(ep)) {
89
89
  (0, parsing_1.requireKeys)(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters");
90
90
  (0, parsing_1.assertKeyTypes)(prefix + ".eventTrigger", ep.eventTrigger, {
91
- eventFilters: "object",
91
+ eventFilters: "array",
92
92
  eventType: "string",
93
93
  retry: "boolean",
94
94
  region: "string",
95
95
  serviceAccountEmail: "string",
96
96
  });
97
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
+ }
98
103
  }
99
104
  else if (backend.isHttpsTriggered(ep)) {
100
105
  (0, parsing_1.assertKeyTypes)(prefix + ".httpsTrigger", ep.httpsTrigger, {
@@ -4,14 +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");
7
9
  const spawn = require("cross-spawn");
8
10
  const node_fetch_1 = require("node-fetch");
9
11
  const error_1 = require("../../../../error");
10
12
  const parseRuntimeAndValidateSDK_1 = require("./parseRuntimeAndValidateSDK");
11
13
  const logger_1 = require("../../../../logger");
14
+ const previews_1 = require("../../../../previews");
15
+ const utils_1 = require("../../../../utils");
16
+ const discovery = require("../discovery");
12
17
  const validate = require("./validate");
13
18
  const versioning = require("./versioning");
14
19
  const parseTriggers = require("./parseTriggers");
20
+ const MIN_FUNCTIONS_SDK_VERSION = "3.19.0";
15
21
  async function tryCreateDelegate(context) {
16
22
  const packageJsonPath = path.join(context.sourceDir, "package.json");
17
23
  if (!(await (0, util_1.promisify)(fs.exists)(packageJsonPath))) {
@@ -77,6 +83,27 @@ class Delegate {
77
83
  });
78
84
  }
79
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
+ }
80
107
  return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
81
108
  }
82
109
  }
@@ -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"));
@@ -73,6 +73,7 @@ function mergeRequiredAPIs(backend) {
73
73
  }
74
74
  exports.mergeRequiredAPIs = mergeRequiredAPIs;
75
75
  function addResourcesToBackend(projectId, runtime, annotation, want) {
76
+ var _a;
76
77
  Object.freeze(annotation);
77
78
  for (const region of annotation.regions || [api.functionsDefaultRegion]) {
78
79
  let triggered;
@@ -88,12 +89,18 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
88
89
  });
89
90
  }
90
91
  else if (annotation.httpsTrigger) {
91
- const trigger = {};
92
- if (annotation.failurePolicy) {
93
- logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
92
+ if ((_a = annotation.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
93
+ delete annotation.labels["deployment-callable"];
94
+ triggered = { callableTrigger: {} };
95
+ }
96
+ else {
97
+ const trigger = {};
98
+ if (annotation.failurePolicy) {
99
+ logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
100
+ }
101
+ proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker");
102
+ triggered = { httpsTrigger: trigger };
94
103
  }
95
- proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker");
96
- triggered = { httpsTrigger: trigger };
97
104
  }
98
105
  else if (annotation.schedule) {
99
106
  want.requiredAPIs.push({
@@ -106,16 +113,32 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
106
113
  triggered = {
107
114
  eventTrigger: {
108
115
  eventType: annotation.eventTrigger.eventType,
109
- eventFilters: {
110
- resource: annotation.eventTrigger.resource,
111
- },
116
+ eventFilters: [
117
+ {
118
+ attribute: "resource",
119
+ value: annotation.eventTrigger.resource,
120
+ },
121
+ ],
112
122
  retry: !!annotation.failurePolicy,
113
123
  },
114
124
  };
115
- if (eventTypes_1.STORAGE_V2_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
116
- triggered.eventTrigger.eventFilters = {
117
- bucket: annotation.eventTrigger.resource,
118
- };
125
+ if (annotation.platform === "gcfv2") {
126
+ if (annotation.eventTrigger.eventType === v2events.PUBSUB_PUBLISH_EVENT) {
127
+ triggered.eventTrigger.eventFilters = [
128
+ {
129
+ attribute: "topic",
130
+ value: annotation.eventTrigger.resource,
131
+ },
132
+ ];
133
+ }
134
+ if (v2events.STORAGE_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
135
+ triggered.eventTrigger.eventFilters = [
136
+ {
137
+ attribute: "bucket",
138
+ value: annotation.eventTrigger.resource,
139
+ },
140
+ ];
141
+ }
119
142
  }
120
143
  }
121
144
  const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", id: annotation.name, region: region, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime }, triggered);
@@ -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)) {
@@ -2,12 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureStorageTriggerRegion = exports.obtainStorageBindings = void 0;
4
4
  const storage = require("../../../gcp/storage");
5
+ const backend = require("../backend");
5
6
  const logger_1 = require("../../../logger");
6
7
  const error_1 = require("../../../error");
7
8
  const location_1 = require("../../../gcp/location");
8
9
  const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
9
- async function obtainStorageBindings(projectId, existingPolicy) {
10
- const storageResponse = await storage.getServiceAccount(projectId);
10
+ async function obtainStorageBindings(projectNumber, existingPolicy) {
11
+ const storageResponse = await storage.getServiceAccount(projectNumber);
11
12
  const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
12
13
  let pubsubBinding = existingPolicy.bindings.find((b) => b.role === PUBSUB_PUBLISHER_ROLE);
13
14
  if (!pubsubBinding) {
@@ -22,11 +23,16 @@ async function obtainStorageBindings(projectId, existingPolicy) {
22
23
  return [pubsubBinding];
23
24
  }
24
25
  exports.obtainStorageBindings = obtainStorageBindings;
25
- async function ensureStorageTriggerRegion(endpoint, eventTrigger) {
26
+ async function ensureStorageTriggerRegion(endpoint) {
27
+ const { eventTrigger } = endpoint;
26
28
  if (!eventTrigger.region) {
27
29
  logger_1.logger.debug("Looking up bucket region for the storage event trigger");
30
+ const bucketFilter = backend.findEventFilter(endpoint, "bucket");
31
+ if (!bucketFilter) {
32
+ throw new error_1.FirebaseError("Storage event trigger unexpectedly missing event filter with bucket attribute.");
33
+ }
28
34
  try {
29
- const bucket = await storage.getBucket(eventTrigger.eventFilters.bucket);
35
+ const bucket = await storage.getBucket(bucketFilter.value);
30
36
  eventTrigger.region = bucket.location.toLowerCase();
31
37
  logger_1.logger.debug("Setting the event trigger region to", eventTrigger.region, ".");
32
38
  }
@@ -9,7 +9,7 @@ async function ensureTriggerRegions(want) {
9
9
  if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep)) {
10
10
  continue;
11
11
  }
12
- regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep, ep.eventTrigger));
12
+ regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep));
13
13
  }
14
14
  await Promise.all(regionLookups);
15
15
  }