firebase-tools 10.2.2 → 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 (54) hide show
  1. package/lib/commands/ext-configure.js +58 -4
  2. package/lib/commands/ext-export.js +4 -9
  3. package/lib/commands/ext-install.js +63 -4
  4. package/lib/commands/ext-uninstall.js +9 -0
  5. package/lib/commands/ext-update.js +55 -2
  6. package/lib/config.js +6 -3
  7. package/lib/deploy/extensions/planner.js +6 -6
  8. package/lib/deploy/functions/backend.js +10 -1
  9. package/lib/deploy/functions/checkIam.js +4 -4
  10. package/lib/deploy/functions/prepare.js +2 -1
  11. package/lib/deploy/functions/release/fabricator.js +4 -4
  12. package/lib/deploy/functions/release/planner.js +34 -20
  13. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
  14. package/lib/deploy/functions/runtimes/node/index.js +27 -0
  15. package/lib/deploy/functions/runtimes/node/parseTriggers.js +24 -8
  16. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  17. package/lib/deploy/functions/services/index.js +9 -1
  18. package/lib/deploy/functions/services/storage.js +10 -4
  19. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  20. package/lib/emulator/constants.js +1 -0
  21. package/lib/emulator/controller.js +9 -7
  22. package/lib/emulator/extensions/validation.js +37 -2
  23. package/lib/emulator/extensionsEmulator.js +44 -9
  24. package/lib/emulator/functionsEmulator.js +13 -8
  25. package/lib/emulator/functionsEmulatorShared.js +17 -10
  26. package/lib/emulator/storage/apis/firebase.js +312 -335
  27. package/lib/emulator/storage/apis/gcloud.js +238 -113
  28. package/lib/emulator/storage/crc.js +5 -1
  29. package/lib/emulator/storage/errors.js +9 -0
  30. package/lib/emulator/storage/files.js +161 -304
  31. package/lib/emulator/storage/index.js +27 -73
  32. package/lib/emulator/storage/metadata.js +63 -49
  33. package/lib/emulator/storage/multipart.js +62 -0
  34. package/lib/emulator/storage/persistence.js +78 -0
  35. package/lib/emulator/storage/rules/config.js +33 -0
  36. package/lib/emulator/storage/rules/manager.js +81 -0
  37. package/lib/emulator/storage/rules/utils.js +48 -0
  38. package/lib/emulator/storage/server.js +2 -2
  39. package/lib/emulator/storage/upload.js +106 -0
  40. package/lib/extensions/emulator/optionsHelper.js +35 -3
  41. package/lib/extensions/extensionsHelper.js +19 -10
  42. package/lib/extensions/manifest.js +109 -13
  43. package/lib/extensions/paramHelper.js +5 -4
  44. package/lib/functions/env.js +4 -6
  45. package/lib/functions/events/v2.js +11 -0
  46. package/lib/gcp/cloudfunctions.js +18 -6
  47. package/lib/gcp/cloudfunctionsv2.js +30 -12
  48. package/lib/gcp/resourceManager.js +4 -4
  49. package/lib/serve/functions.js +2 -1
  50. package/lib/utils.js +14 -1
  51. package/npm-shrinkwrap.json +2 -2
  52. package/package.json +1 -1
  53. package/lib/deploy/extensions/params.js +0 -42
  54. 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, " +
@@ -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"));
@@ -106,16 +106,32 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
106
106
  triggered = {
107
107
  eventTrigger: {
108
108
  eventType: annotation.eventTrigger.eventType,
109
- eventFilters: {
110
- resource: annotation.eventTrigger.resource,
111
- },
109
+ eventFilters: [
110
+ {
111
+ attribute: "resource",
112
+ value: annotation.eventTrigger.resource,
113
+ },
114
+ ],
112
115
  retry: !!annotation.failurePolicy,
113
116
  },
114
117
  };
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
- };
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
+ }
119
135
  }
120
136
  }
121
137
  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
  }
@@ -90,6 +90,7 @@ class Constants {
90
90
  }
91
91
  exports.Constants = Constants;
92
92
  Constants.FAKE_PROJECT_ID_PREFIX = "demo-";
93
+ Constants.FAKE_PROJECT_NUMBER = "0";
93
94
  Constants.DEFAULT_DATABASE_EMULATOR_NAMESPACE = "fake-server";
94
95
  Constants.FIRESTORE_EMULATOR_HOST = "FIRESTORE_EMULATOR_HOST";
95
96
  Constants.FIREBASE_DATABASE_EMULATOR_HOST = "FIREBASE_DATABASE_EMULATOR_HOST";
@@ -33,6 +33,7 @@ const prompt_1 = require("../prompt");
33
33
  const commandUtils_1 = require("./commandUtils");
34
34
  const fsutils_1 = require("../fsutils");
35
35
  const storage_1 = require("./storage");
36
+ const config_1 = require("./storage/rules/config");
36
37
  const getDefaultDatabaseInstance_1 = require("../getDefaultDatabaseInstance");
37
38
  const auth_2 = require("../auth");
38
39
  const extensionsEmulator_1 = require("./extensionsEmulator");
@@ -259,12 +260,15 @@ async function startAll(options, showUI = true) {
259
260
  emulatableBackends.push({
260
261
  functionsDir,
261
262
  env: Object.assign({}, options.extDevEnv),
263
+ secretEnv: [],
262
264
  predefinedTriggers: options.extDevTriggers,
263
265
  nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || options.config.get("functions.runtime")),
264
266
  });
265
267
  }
266
268
  if (shouldStart(options, types_1.Emulators.EXTENSIONS) && previews_1.previews.extensionsemulator) {
267
- const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
269
+ const projectNumber = constants_1.Constants.isDemoProject(projectId)
270
+ ? constants_1.Constants.FAKE_PROJECT_NUMBER
271
+ : await (0, projectUtils_1.needProjectNumber)(options);
268
272
  const aliases = (0, projectUtils_1.getAliases)(options, projectId);
269
273
  const extensionEmulator = new extensionsEmulator_1.ExtensionsEmulator({
270
274
  projectId,
@@ -274,7 +278,8 @@ async function startAll(options, showUI = true) {
274
278
  extensions: options.config.get("extensions"),
275
279
  });
276
280
  const extensionsBackends = await extensionEmulator.getExtensionBackends();
277
- emulatableBackends.push(...extensionsBackends);
281
+ const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers(options, extensionsBackends);
282
+ emulatableBackends.push(...filteredExtensionsBackends);
278
283
  void track("Emulator Run", types_1.Emulators.EXTENSIONS);
279
284
  registry_1.EmulatorRegistry.registerExtensionsEmulator();
280
285
  }
@@ -302,6 +307,7 @@ async function startAll(options, showUI = true) {
302
307
  host: functionsAddr.host,
303
308
  port: functionsAddr.port,
304
309
  debugPort: inspectFunctions,
310
+ projectAlias: options.projectAlias,
305
311
  });
306
312
  await startEmulator(functionsEmulator);
307
313
  }
@@ -421,15 +427,11 @@ async function startAll(options, showUI = true) {
421
427
  }
422
428
  if (shouldStart(options, types_1.Emulators.STORAGE)) {
423
429
  const storageAddr = await getAndCheckAddress(types_1.Emulators.STORAGE, options);
424
- const storageConfig = options.config.data.storage;
425
- if (!(storageConfig === null || storageConfig === void 0 ? void 0 : storageConfig.rules)) {
426
- throw new error_1.FirebaseError("Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration");
427
- }
428
430
  const storageEmulator = new storage_1.StorageEmulator({
429
431
  host: storageAddr.host,
430
432
  port: storageAddr.port,
431
433
  projectId: projectId,
432
- rules: options.config.path(storageConfig.rules),
434
+ rules: (0, config_1.getStorageRulesConfig)(projectId, options),
433
435
  });
434
436
  await startEmulator(storageEmulator);
435
437
  if (exportMetadata.storage) {
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUnemulatedAPIs = void 0;
3
+ exports.checkForUnemulatedTriggerTypes = exports.getUnemulatedAPIs = void 0;
4
4
  const planner = require("../../deploy/extensions/planner");
5
+ const controller_1 = require("../controller");
6
+ const constants_1 = require("../constants");
5
7
  const ensureApiEnabled_1 = require("../../ensureApiEnabled");
8
+ const functionsEmulatorShared_1 = require("../functionsEmulatorShared");
9
+ const types_1 = require("../types");
6
10
  const EMULATED_APIS = [
7
11
  "storage-component.googleapis.com",
8
12
  "firestore.googleapis.com",
@@ -20,7 +24,8 @@ async function getUnemulatedAPIs(projectId, instances) {
20
24
  unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId);
21
25
  }
22
26
  else {
23
- const enabled = await (0, ensureApiEnabled_1.check)(projectId, api.apiName, "extensions", true);
27
+ const enabled = !constants_1.Constants.isDemoProject(projectId) &&
28
+ (await (0, ensureApiEnabled_1.check)(projectId, api.apiName, "extensions", true));
24
29
  unemulatedAPIs[api.apiName] = {
25
30
  apiName: api.apiName,
26
31
  instanceIds: [i.instanceId],
@@ -33,3 +38,33 @@ async function getUnemulatedAPIs(projectId, instances) {
33
38
  return Object.values(unemulatedAPIs);
34
39
  }
35
40
  exports.getUnemulatedAPIs = getUnemulatedAPIs;
41
+ function checkForUnemulatedTriggerTypes(backend, options) {
42
+ var _a;
43
+ const triggers = (_a = backend.predefinedTriggers) !== null && _a !== void 0 ? _a : [];
44
+ const unemulatedTriggers = triggers
45
+ .filter((definition) => {
46
+ if (definition.httpsTrigger) {
47
+ return false;
48
+ }
49
+ if (definition.eventTrigger) {
50
+ const service = (0, functionsEmulatorShared_1.getFunctionService)(definition);
51
+ switch (service) {
52
+ case constants_1.Constants.SERVICE_FIRESTORE:
53
+ return !(0, controller_1.shouldStart)(options, types_1.Emulators.FIRESTORE);
54
+ case constants_1.Constants.SERVICE_REALTIME_DATABASE:
55
+ return !(0, controller_1.shouldStart)(options, types_1.Emulators.DATABASE);
56
+ case constants_1.Constants.SERVICE_PUBSUB:
57
+ return !(0, controller_1.shouldStart)(options, types_1.Emulators.PUBSUB);
58
+ case constants_1.Constants.SERVICE_AUTH:
59
+ return !(0, controller_1.shouldStart)(options, types_1.Emulators.AUTH);
60
+ case constants_1.Constants.SERVICE_STORAGE:
61
+ return !(0, controller_1.shouldStart)(options, types_1.Emulators.STORAGE);
62
+ default:
63
+ return true;
64
+ }
65
+ }
66
+ })
67
+ .map((definition) => constants_1.Constants.getServiceName((0, functionsEmulatorShared_1.getFunctionService)(definition)));
68
+ return [...new Set(unemulatedTriggers)];
69
+ }
70
+ exports.checkForUnemulatedTriggerTypes = checkForUnemulatedTriggerTypes;
@@ -17,10 +17,12 @@ const types_1 = require("./types");
17
17
  const validation_1 = require("./extensions/validation");
18
18
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
19
19
  const shortenUrl_1 = require("../shortenUrl");
20
+ const constants_1 = require("./constants");
20
21
  class ExtensionsEmulator {
21
22
  constructor(args) {
22
23
  this.want = [];
23
24
  this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.EXTENSIONS);
25
+ this.pendingDownloads = new Map();
24
26
  this.args = args;
25
27
  }
26
28
  async readManifest() {
@@ -31,7 +33,7 @@ class ExtensionsEmulator {
31
33
  aliases: (_a = this.args.aliases) !== null && _a !== void 0 ? _a : [],
32
34
  projectDir: this.args.projectDir,
33
35
  extensions: this.args.extensions,
34
- checkLocal: true,
36
+ emulatorMode: true,
35
37
  });
36
38
  }
37
39
  async ensureSourceCode(instance) {
@@ -42,13 +44,21 @@ class ExtensionsEmulator {
42
44
  const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
43
45
  path.join(os.homedir(), ".cache", "firebase", "extensions");
44
46
  const sourceCodePath = path.join(cacheDir, ref);
47
+ if (this.pendingDownloads.get(ref)) {
48
+ await this.pendingDownloads.get(ref);
49
+ }
45
50
  if (!this.hasValidSource({ path: sourceCodePath, extRef: ref })) {
46
- const extensionVersion = await planner.getExtensionVersion(instance);
47
- await (0, download_1.downloadExtensionVersion)(ref, extensionVersion.sourceDownloadUri, sourceCodePath);
48
- this.installAndBuildSourceCode(sourceCodePath);
51
+ const promise = this.downloadSource(instance, ref, sourceCodePath);
52
+ this.pendingDownloads.set(ref, promise);
53
+ await promise;
49
54
  }
50
55
  return sourceCodePath;
51
56
  }
57
+ async downloadSource(instance, ref, sourceCodePath) {
58
+ const extensionVersion = await planner.getExtensionVersion(instance);
59
+ await (0, download_1.downloadExtensionVersion)(ref, extensionVersion.sourceDownloadUri, sourceCodePath);
60
+ this.installAndBuildSourceCode(sourceCodePath);
61
+ }
52
62
  hasValidSource(args) {
53
63
  const requiredFiles = [
54
64
  "./extension.yaml",
@@ -87,12 +97,13 @@ class ExtensionsEmulator {
87
97
  const extensionDir = await this.ensureSourceCode(instance);
88
98
  const functionsDir = path.join(extensionDir, "functions");
89
99
  const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
90
- const { extensionTriggers, nodeMajorVersion } = await (0, optionsHelper_1.getExtensionFunctionInfo)(extensionDir, instance.instanceId, env);
100
+ const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(extensionDir, instance.instanceId, env);
91
101
  const extension = await planner.getExtension(instance);
92
102
  const extensionVersion = await planner.getExtensionVersion(instance);
93
103
  return {
94
104
  functionsDir,
95
- env,
105
+ env: nonSecretEnv,
106
+ secretEnv: secretEnvVariables,
96
107
  predefinedTriggers: extensionTriggers,
97
108
  nodeMajorVersion: nodeMajorVersion,
98
109
  extensionInstanceId: instance.instanceId,
@@ -131,10 +142,34 @@ class ExtensionsEmulator {
131
142
  apiToWarn.enabled ? "" : clc.bold.underline(enablementUri),
132
143
  ]);
133
144
  }
134
- this.logger.logLabeled("WARN", "Extensions", `The following Extensions make calls to Google Cloud APIs that do not have Emulators. ` +
135
- `These calls will go to production Google Cloud APIs which may have real effects on ${this.args.projectId}.\n` +
136
- table.toString());
145
+ if (constants_1.Constants.isDemoProject(this.args.projectId)) {
146
+ this.logger.logLabeled("WARN", "Extensions", "The following Extensions make calls to Google Cloud APIs that do not have Emulators. " +
147
+ `${clc.bold(this.args.projectId)} is a demo project, so these Extensions may not work as expected.\n` +
148
+ table.toString());
149
+ }
150
+ else {
151
+ this.logger.logLabeled("WARN", "Extensions", "The following Extensions make calls to Google Cloud APIs that do not have Emulators. " +
152
+ `These calls will go to production Google Cloud APIs which may have real effects on ${clc.bold(this.args.projectId)}.\n` +
153
+ table.toString());
154
+ }
155
+ }
156
+ }
157
+ filterUnemulatedTriggers(options, backends) {
158
+ let foundUnemulatedTrigger = false;
159
+ const filteredBackends = backends.filter((backend) => {
160
+ const unemulatedServices = (0, validation_1.checkForUnemulatedTriggerTypes)(backend, options);
161
+ if (unemulatedServices.length) {
162
+ foundUnemulatedTrigger = true;
163
+ const msg = ` ignored becuase it includes ${unemulatedServices.join(", ")} triggered functions, and the ${unemulatedServices.join(", ")} emulator does not exist or is not running.`;
164
+ this.logger.logLabeled("WARN", `extensions[${backend.extensionInstanceId}]`, msg);
165
+ }
166
+ return unemulatedServices.length === 0;
167
+ });
168
+ if (foundUnemulatedTrigger) {
169
+ const msg = "No Cloud Functions for these instances will be emulated, because partially emulating an Extension can lead to unexpected behavior. ";
170
+ this.logger.log("WARN", msg);
137
171
  }
172
+ return filteredBackends;
138
173
  }
139
174
  }
140
175
  exports.ExtensionsEmulator = ExtensionsEmulator;
@@ -79,7 +79,6 @@ class FunctionsEmulator {
79
79
  createHubServer() {
80
80
  this.workQueue.start();
81
81
  const hub = express();
82
- hub.use(cors({ origin: true }));
83
82
  const dataMiddleware = (req, res, next) => {
84
83
  const chunks = [];
85
84
  req.on("data", (chunk) => {
@@ -158,7 +157,7 @@ class FunctionsEmulator {
158
157
  const listBackendsHandler = (req, res) => {
159
158
  res.json({ backends: this.getBackendInfo() });
160
159
  };
161
- hub.get(listBackendsRoute, dataMiddleware, listBackendsHandler);
160
+ hub.get(listBackendsRoute, cors({ origin: true }), listBackendsHandler);
162
161
  hub.post(backgroundFunctionRoute, dataMiddleware, backgroundHandler);
163
162
  hub.post(multicastFunctionRoute, dataMiddleware, multicastHandler);
164
163
  hub.all(httpsFunctionRoutes, dataMiddleware, httpsHandler);
@@ -253,7 +252,7 @@ class FunctionsEmulator {
253
252
  }
254
253
  let triggerDefinitions;
255
254
  if (emulatableBackend.predefinedTriggers) {
256
- triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsByRegion)(emulatableBackend.predefinedTriggers);
255
+ triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsByRegion)(emulatableBackend.predefinedTriggers, emulatableBackend.secretEnv);
257
256
  }
258
257
  else {
259
258
  const runtimeConfig = this.getRuntimeConfig(emulatableBackend);
@@ -495,9 +494,13 @@ class FunctionsEmulator {
495
494
  .map((t) => t.def);
496
495
  return this.args.emulatableBackends.map((e) => {
497
496
  var _a;
497
+ const envWithSecrets = Object.assign({}, e.env);
498
+ for (const s of e.secretEnv) {
499
+ envWithSecrets[s.key] = backend.secretVersionName(s);
500
+ }
498
501
  return {
499
502
  directory: e.functionsDir,
500
- env: e.env,
503
+ env: envWithSecrets,
501
504
  extensionInstanceId: e.extensionInstanceId,
502
505
  extension: e.extension,
503
506
  extensionVersion: e.extensionVersion,
@@ -570,6 +573,7 @@ class FunctionsEmulator {
570
573
  const projectInfo = {
571
574
  functionsSource: backend.functionsDir,
572
575
  projectId: this.args.projectId,
576
+ projectAlias: this.args.projectAlias,
573
577
  isEmulator: true,
574
578
  };
575
579
  if (functionsEnv.hasUserEnvs(projectInfo)) {
@@ -662,11 +666,12 @@ class FunctionsEmulator {
662
666
  if (trigger) {
663
667
  const secrets = trigger.secretEnvironmentVariables || [];
664
668
  const accesses = secrets
665
- .filter((s) => !secretEnvs[s.secret])
669
+ .filter((s) => !secretEnvs[s.key])
666
670
  .map(async (s) => {
667
- this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.key}@latest`);
668
- const value = await (0, secretManager_1.accessSecretVersion)(this.getProjectId(), s.key, "latest");
669
- return [s.secret, value];
671
+ var _a;
672
+ this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.secret}@latest`);
673
+ const value = await (0, secretManager_1.accessSecretVersion)(this.getProjectId(), s.secret, (_a = s.version) !== null && _a !== void 0 ? _a : "latest");
674
+ return [s.key, value];
670
675
  });
671
676
  const accessResults = await (0, utils_1.allSettled)(accesses);
672
677
  const errs = [];