firebase-tools 11.14.1 → 11.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/lib/bin/firebase.js +3 -3
  2. package/lib/commands/deploy.js +3 -3
  3. package/lib/commands/emulators-start.js +20 -16
  4. package/lib/deploy/functions/params.js +9 -1
  5. package/lib/deploy/functions/release/fabricator.js +1 -0
  6. package/lib/deploy/hosting/convertConfig.js +2 -1
  7. package/lib/deploy/index.js +10 -0
  8. package/lib/emulator/ExpressBasedEmulator.js +92 -0
  9. package/lib/emulator/auth/cloudFunctions.js +3 -12
  10. package/lib/emulator/auth/index.js +2 -2
  11. package/lib/emulator/auth/utils.js +1 -1
  12. package/lib/emulator/commandUtils.js +21 -44
  13. package/lib/emulator/constants.js +2 -9
  14. package/lib/emulator/controller.js +129 -130
  15. package/lib/emulator/databaseEmulator.js +3 -8
  16. package/lib/emulator/dns.js +49 -0
  17. package/lib/emulator/downloadableEmulators.js +19 -13
  18. package/lib/emulator/emulatorLogger.js +14 -7
  19. package/lib/emulator/env.js +35 -0
  20. package/lib/emulator/eventarcEmulator.js +2 -8
  21. package/lib/emulator/extensions/postinstall.js +8 -8
  22. package/lib/emulator/extensionsEmulator.js +7 -7
  23. package/lib/emulator/firestoreEmulator.js +3 -11
  24. package/lib/emulator/functionsEmulator.js +24 -60
  25. package/lib/emulator/functionsEmulatorShared.js +5 -3
  26. package/lib/emulator/functionsEmulatorShell.js +1 -1
  27. package/lib/emulator/hub.js +50 -58
  28. package/lib/emulator/hubClient.js +24 -10
  29. package/lib/emulator/hubExport.js +3 -11
  30. package/lib/emulator/portUtils.js +208 -29
  31. package/lib/emulator/pubsubEmulator.js +11 -12
  32. package/lib/emulator/registry.js +11 -20
  33. package/lib/emulator/storage/apis/gcloud.js +3 -6
  34. package/lib/emulator/storage/cloudFunctions.js +2 -7
  35. package/lib/emulator/storage/metadata.js +11 -5
  36. package/lib/emulator/storage/rules/runtime.js +1 -6
  37. package/lib/emulator/ui.js +6 -7
  38. package/lib/experiments.js +10 -1
  39. package/lib/extensions/displayExtensionInfo.js +43 -6
  40. package/lib/extensions/secretsUtils.js +2 -1
  41. package/lib/frameworks/angular/index.js +1 -1
  42. package/lib/frameworks/express/index.js +1 -1
  43. package/lib/frameworks/index.js +33 -17
  44. package/lib/frameworks/next/index.js +1 -1
  45. package/lib/frameworks/nuxt/index.js +1 -1
  46. package/lib/frameworks/vite/index.js +1 -1
  47. package/lib/functions/ensureTargeted.js +21 -0
  48. package/lib/functions/env.js +33 -19
  49. package/lib/functionsShellCommandAction.js +12 -5
  50. package/lib/gcp/run.js +0 -1
  51. package/lib/handlePreviewToggles.js +2 -2
  52. package/lib/hosting/functionsProxy.js +2 -3
  53. package/lib/hosting/implicitInit.js +2 -11
  54. package/lib/hosting/runTags.js +3 -4
  55. package/lib/serve/functions.js +8 -10
  56. package/lib/serve/hosting.js +10 -4
  57. package/lib/serve/index.js +2 -2
  58. package/lib/utils.js +14 -1
  59. package/npm-shrinkwrap.json +2 -2
  60. package/package.json +1 -1
  61. package/templates/hosting/init.js +3 -2
  62. package/lib/emulator/emulatorServer.js +0 -29
@@ -1,13 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.waitForPortClosed = exports.checkPortOpen = exports.findAvailablePort = exports.suggestUnrestricted = exports.isRestricted = void 0;
4
- const pf = require("portfinder");
3
+ exports.resolveHostAndAssignPorts = exports.waitForPortClosed = exports.checkListenable = void 0;
4
+ const clc = require("colorette");
5
5
  const tcpport = require("tcp-port-used");
6
- const dns = require("dns");
6
+ const node_net_1 = require("node:net");
7
7
  const error_1 = require("../error");
8
- const logger_1 = require("../logger");
9
- dns.setDefaultResultOrder("ipv4first");
10
- const RESTRICTED_PORTS = [
8
+ const utils = require("../utils");
9
+ const dns_1 = require("./dns");
10
+ const types_1 = require("./types");
11
+ const constants_1 = require("./constants");
12
+ const emulatorLogger_1 = require("./emulatorLogger");
13
+ const RESTRICTED_PORTS = new Set([
11
14
  1,
12
15
  7,
13
16
  9,
@@ -75,11 +78,10 @@ const RESTRICTED_PORTS = [
75
78
  6668,
76
79
  6669,
77
80
  6697,
78
- ];
81
+ ]);
79
82
  function isRestricted(port) {
80
- return RESTRICTED_PORTS.includes(port);
83
+ return RESTRICTED_PORTS.has(port);
81
84
  }
82
- exports.isRestricted = isRestricted;
83
85
  function suggestUnrestricted(port) {
84
86
  if (!isRestricted(port)) {
85
87
  return port;
@@ -90,27 +92,37 @@ function suggestUnrestricted(port) {
90
92
  }
91
93
  return newPort;
92
94
  }
93
- exports.suggestUnrestricted = suggestUnrestricted;
94
- async function findAvailablePort(host, start, avoidRestricted = true) {
95
- const openPort = await pf.getPortPromise({ host, port: start });
96
- if (avoidRestricted && isRestricted(openPort)) {
97
- logger_1.logger.debug(`portUtils: skipping restricted port ${openPort}`);
98
- return findAvailablePort(host, suggestUnrestricted(openPort), avoidRestricted);
99
- }
100
- return openPort;
101
- }
102
- exports.findAvailablePort = findAvailablePort;
103
- async function checkPortOpen(port, host) {
104
- try {
105
- const inUse = await tcpport.check(port, host);
106
- return !inUse;
107
- }
108
- catch (e) {
109
- logger_1.logger.debug(`port check error: ${e}`);
110
- return false;
111
- }
95
+ async function checkListenable(arg1, port) {
96
+ const addr = port === undefined ? arg1 : listenSpec(arg1, port);
97
+ return new Promise((resolve, reject) => {
98
+ const dummyServer = (0, node_net_1.createServer)(() => {
99
+ });
100
+ dummyServer.once("error", (err) => {
101
+ dummyServer.removeAllListeners();
102
+ const e = err;
103
+ if (e.code === "EADDRINUSE" || e.code === "EACCES") {
104
+ resolve(false);
105
+ }
106
+ else {
107
+ reject(e);
108
+ }
109
+ });
110
+ dummyServer.once("listening", () => {
111
+ dummyServer.removeAllListeners();
112
+ dummyServer.close((err) => {
113
+ dummyServer.removeAllListeners();
114
+ if (err) {
115
+ reject(err);
116
+ }
117
+ else {
118
+ resolve(true);
119
+ }
120
+ });
121
+ });
122
+ dummyServer.listen({ host: addr.address, port: addr.port, ipv6Only: addr.family === "IPv6" });
123
+ });
112
124
  }
113
- exports.checkPortOpen = checkPortOpen;
125
+ exports.checkListenable = checkListenable;
114
126
  async function waitForPortClosed(port, host) {
115
127
  const interval = 250;
116
128
  const timeout = 60000;
@@ -122,3 +134,170 @@ async function waitForPortClosed(port, host) {
122
134
  }
123
135
  }
124
136
  exports.waitForPortClosed = waitForPortClosed;
137
+ const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
138
+ database: true,
139
+ firestore: true,
140
+ "firestore.websocket": true,
141
+ pubsub: true,
142
+ hub: false,
143
+ ui: false,
144
+ auth: true,
145
+ eventarc: true,
146
+ extensions: true,
147
+ functions: true,
148
+ logging: true,
149
+ storage: true,
150
+ hosting: true,
151
+ };
152
+ const MAX_PORT = 65535;
153
+ async function resolveHostAndAssignPorts(listenConfig) {
154
+ const lookupForHost = new Map();
155
+ const takenPorts = new Map();
156
+ const result = {};
157
+ const tasks = [];
158
+ for (const name of Object.keys(listenConfig)) {
159
+ const config = listenConfig[name];
160
+ if (!config) {
161
+ continue;
162
+ }
163
+ else if (config instanceof Array) {
164
+ result[name] = config;
165
+ for (const { port } of config) {
166
+ takenPorts.set(port, name);
167
+ }
168
+ continue;
169
+ }
170
+ const { host, port, portFixed } = config;
171
+ let lookup = lookupForHost.get(host);
172
+ if (!lookup) {
173
+ lookup = dns_1.Resolver.DEFAULT.lookupAll(host);
174
+ lookupForHost.set(host, lookup);
175
+ }
176
+ const findAddrs = lookup.then(async (addrs) => {
177
+ const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket" ? types_1.Emulators.FIRESTORE : name);
178
+ if (addrs.some((addr) => addr.address === dns_1.IPV6_UNSPECIFIED.address)) {
179
+ if (!addrs.some((addr) => addr.address === dns_1.IPV4_UNSPECIFIED.address)) {
180
+ emuLogger.logLabeled("DEBUG", name, `testing listening on IPv4 wildcard in addition to IPv6. To listen on IPv6 only, use "::0" instead.`);
181
+ addrs.push(dns_1.IPV4_UNSPECIFIED);
182
+ }
183
+ }
184
+ for (let p = port; p <= MAX_PORT; p++) {
185
+ if (takenPorts.has(p)) {
186
+ continue;
187
+ }
188
+ if (!portFixed && RESTRICTED_PORTS.has(p)) {
189
+ emuLogger.logLabeled("DEBUG", name, `portUtils: skipping restricted port ${p}`);
190
+ continue;
191
+ }
192
+ const available = [];
193
+ const unavailable = [];
194
+ let i;
195
+ for (i = 0; i < addrs.length; i++) {
196
+ const addr = addrs[i];
197
+ const listen = listenSpec(addr, p);
198
+ if (await checkListenable(listen)) {
199
+ available.push(listen);
200
+ }
201
+ else {
202
+ if (!portFixed) {
203
+ if (i > 0) {
204
+ emuLogger.logLabeled("DEBUG", name, `Port ${p} taken on secondary address ${addr.address}, will keep searching to find a better port.`);
205
+ }
206
+ break;
207
+ }
208
+ unavailable.push(addr.address);
209
+ }
210
+ }
211
+ if (i === addrs.length) {
212
+ if (unavailable.length > 0) {
213
+ if (unavailable[0] === addrs[0].address) {
214
+ return fixedPortNotAvailable(name, host, port, emuLogger, unavailable);
215
+ }
216
+ warnPartiallyAvailablePort(emuLogger, port, available, unavailable);
217
+ }
218
+ if (takenPorts.has(p)) {
219
+ continue;
220
+ }
221
+ takenPorts.set(p, name);
222
+ if (RESTRICTED_PORTS.has(p)) {
223
+ const suggested = suggestUnrestricted(port);
224
+ emuLogger.logLabeled("WARN", name, `Port ${port} is restricted by some web browsers, including Chrome. You may want to choose a different port such as ${suggested}.`);
225
+ }
226
+ if (p !== port && name !== "firestore.websocket") {
227
+ emuLogger.logLabeled("WARN", `${portDescription(name)} unable to start on port ${port}, starting on ${p} instead.`);
228
+ }
229
+ if (available.length > 1 && EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY[name]) {
230
+ emuLogger.logLabeled("DEBUG", name, `${portDescription(name)} only supports listening on one address (${available[0].address}). Not listening on ${addrs
231
+ .slice(1)
232
+ .map((s) => s.address)
233
+ .join(",")}`);
234
+ result[name] = [available[0]];
235
+ }
236
+ else {
237
+ result[name] = available;
238
+ }
239
+ return;
240
+ }
241
+ }
242
+ return utils.reject(`Could not find any open port in ${port}-${MAX_PORT} for ${portDescription(name)}`, {});
243
+ });
244
+ tasks.push(findAddrs);
245
+ }
246
+ await Promise.all(tasks);
247
+ return result;
248
+ }
249
+ exports.resolveHostAndAssignPorts = resolveHostAndAssignPorts;
250
+ function portDescription(name) {
251
+ return name === "firestore.websocket"
252
+ ? `websocket server for ${types_1.Emulators.FIRESTORE}`
253
+ : constants_1.Constants.description(name);
254
+ }
255
+ function warnPartiallyAvailablePort(emuLogger, port, available, unavailable) {
256
+ emuLogger.logLabeled("WARN", `Port ${port} is available on ` +
257
+ available.map((s) => s.address).join(",") +
258
+ ` but not ${unavailable.join(",")}. This may cause issues with some clients.`);
259
+ emuLogger.logLabeled("WARN", `If you encounter connectivity issues, consider switching to a different port or explicitly specifying ${clc.yellow('"host": "<ip address>"')} instead of hostname in firebase.json`);
260
+ }
261
+ function fixedPortNotAvailable(name, host, port, emuLogger, unavailableAddrs) {
262
+ if (unavailableAddrs.length !== 1 || unavailableAddrs[0] !== host) {
263
+ host = `${host} (${unavailableAddrs.join(",")})`;
264
+ }
265
+ const description = portDescription(name);
266
+ emuLogger.logLabeled("WARN", `Port ${port} is not open on ${host}, could not start ${description}.`);
267
+ if (name === "firestore.websocket") {
268
+ emuLogger.logLabeled("WARN", `To select a different port, specify that port in a firebase.json config file:
269
+ {
270
+ // ...
271
+ "emulators": {
272
+ "${types_1.Emulators.FIRESTORE}": {
273
+ "host": "${clc.yellow("HOST")}",
274
+ ...
275
+ "websocketPort": "${clc.yellow("WEBSOCKET_PORT")}"
276
+ }
277
+ }
278
+ }`);
279
+ }
280
+ else {
281
+ emuLogger.logLabeled("WARN", `To select a different host/port, specify that host/port in a firebase.json config file:
282
+ {
283
+ // ...
284
+ "emulators": {
285
+ "${emuLogger.name}": {
286
+ "host": "${clc.yellow("HOST")}",
287
+ "port": "${clc.yellow("PORT")}"
288
+ }
289
+ }
290
+ }`);
291
+ }
292
+ return utils.reject(`Could not start ${description}, port taken.`, {});
293
+ }
294
+ function listenSpec(lookup, port) {
295
+ if (lookup.family !== 4 && lookup.family !== 6) {
296
+ throw new Error(`Unsupported address family "${lookup.family}" for address ${lookup.address}.`);
297
+ }
298
+ return {
299
+ address: lookup.address,
300
+ family: lookup.family === 4 ? "IPv4" : "IPv6",
301
+ port: port,
302
+ };
303
+ }
@@ -4,7 +4,6 @@ exports.PubsubEmulator = void 0;
4
4
  const uuid = require("uuid");
5
5
  const pubsub_1 = require("@google-cloud/pubsub");
6
6
  const downloadableEmulators = require("./downloadableEmulators");
7
- const apiv2_1 = require("../apiv2");
8
7
  const emulatorLogger_1 = require("./emulatorLogger");
9
8
  const types_1 = require("../emulator/types");
10
9
  const constants_1 = require("./constants");
@@ -14,14 +13,18 @@ class PubsubEmulator {
14
13
  constructor(args) {
15
14
  this.args = args;
16
15
  this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.PUBSUB);
17
- const { host, port } = this.getInfo();
18
- this.pubsub = new pubsub_1.PubSub({
19
- apiEndpoint: `${host}:${port}`,
20
- projectId: this.args.projectId,
21
- });
22
16
  this.triggersForTopic = new Map();
23
17
  this.subscriptionForTopic = new Map();
24
18
  }
19
+ get pubsub() {
20
+ if (!this._pubsub) {
21
+ this._pubsub = new pubsub_1.PubSub({
22
+ apiEndpoint: registry_1.EmulatorRegistry.url(types_1.Emulators.PUBSUB).host,
23
+ projectId: this.args.projectId,
24
+ });
25
+ }
26
+ return this._pubsub;
27
+ }
25
28
  async start() {
26
29
  return downloadableEmulators.start(types_1.Emulators.PUBSUB, this.args);
27
30
  }
@@ -94,14 +97,10 @@ class PubsubEmulator {
94
97
  ensureFunctionsClient() {
95
98
  if (this.client !== undefined)
96
99
  return;
97
- const funcEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
98
- if (!funcEmulator) {
100
+ if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.FUNCTIONS)) {
99
101
  throw new error_1.FirebaseError(`Attempted to execute pubsub trigger but could not find the Functions emulator`);
100
102
  }
101
- this.client = new apiv2_1.Client({
102
- urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(funcEmulator.getInfo())}`,
103
- auth: false,
104
- });
103
+ this.client = registry_1.EmulatorRegistry.client(types_1.Emulators.FUNCTIONS);
105
104
  }
106
105
  createLegacyEventRequestBody(topic, message) {
107
106
  return {
@@ -6,6 +6,8 @@ const error_1 = require("../error");
6
6
  const portUtils = require("./portUtils");
7
7
  const constants_1 = require("./constants");
8
8
  const emulatorLogger_1 = require("./emulatorLogger");
9
+ const utils_1 = require("../utils");
10
+ const apiv2_1 = require("../apiv2");
9
11
  class EmulatorRegistry {
10
12
  static async start(instance) {
11
13
  const description = constants_1.Constants.description(instance.getName());
@@ -16,7 +18,7 @@ class EmulatorRegistry {
16
18
  await instance.start();
17
19
  if (instance.getName() !== types_1.Emulators.EXTENSIONS) {
18
20
  const info = instance.getInfo();
19
- await portUtils.waitForPortClosed(info.port, info.host);
21
+ await portUtils.waitForPortClosed(info.port, (0, utils_1.connectableHostname)(info.host));
20
22
  }
21
23
  }
22
24
  static async stop(name) {
@@ -74,20 +76,12 @@ class EmulatorRegistry {
74
76
  return this.INSTANCES.get(emulator);
75
77
  }
76
78
  static getInfo(emulator) {
77
- const instance = this.INSTANCES.get(emulator === types_1.Emulators.EXTENSIONS ? types_1.Emulators.FUNCTIONS : emulator);
78
- if (!instance) {
79
+ var _a;
80
+ const info = (_a = EmulatorRegistry.get(emulator)) === null || _a === void 0 ? void 0 : _a.getInfo();
81
+ if (!info) {
79
82
  return undefined;
80
83
  }
81
- return instance.getInfo();
82
- }
83
- static getInfoHostString(info) {
84
- const { host, port } = info;
85
- if (host.includes(":")) {
86
- return `[${host}]:${port}`;
87
- }
88
- else {
89
- return `${host}:${port}`;
90
- }
84
+ return Object.assign(Object.assign({}, info), { host: (0, utils_1.connectableHostname)(info.host) });
91
85
  }
92
86
  static url(emulator, req) {
93
87
  const url = new URL("http://unknown/");
@@ -101,13 +95,7 @@ class EmulatorRegistry {
101
95
  }
102
96
  const info = EmulatorRegistry.getInfo(emulator);
103
97
  if (info) {
104
- if (info.host === "0.0.0.0") {
105
- url.hostname = "127.0.0.1";
106
- }
107
- else if (info.host === "::") {
108
- url.hostname = "[::1]";
109
- }
110
- else if (info.host.includes(":")) {
98
+ if (info.host.includes(":")) {
111
99
  url.hostname = `[${info.host}]`;
112
100
  }
113
101
  else {
@@ -120,6 +108,9 @@ class EmulatorRegistry {
120
108
  }
121
109
  return url;
122
110
  }
111
+ static client(emulator, options = {}) {
112
+ return new apiv2_1.Client(Object.assign({ urlPrefix: EmulatorRegistry.url(emulator).toString(), auth: false }, options));
113
+ }
123
114
  static set(emulator, instance) {
124
115
  this.INSTANCES.set(emulator, instance);
125
116
  }
@@ -187,7 +187,6 @@ function createCloudEndpoints(emulator) {
187
187
  return res.json(new metadata_1.CloudStorageObjectMetadata(metadata));
188
188
  });
189
189
  gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", async (req, res) => {
190
- var _a, _b;
191
190
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN_ONCE", "Cloud Storage ACLs are not supported in the Storage Emulator. All related methods will succeed, but have no effect.");
192
191
  let getObjectResponse;
193
192
  try {
@@ -207,11 +206,13 @@ function createCloudEndpoints(emulator) {
207
206
  }
208
207
  const { metadata } = getObjectResponse;
209
208
  metadata.update({});
209
+ const selfLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
210
+ selfLink.pathname = `/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}/acl/allUsers`;
210
211
  return res.json({
211
212
  kind: "storage#objectAccessControl",
212
213
  object: metadata.name,
213
214
  id: `${req.params.bucketId}/${metadata.name}/${metadata.generation}/allUsers`,
214
- selfLink: `http://${(_a = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _a === void 0 ? void 0 : _a.host}:${(_b = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _b === void 0 ? void 0 : _b.port}/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}/acl/allUsers`,
215
+ selfLink: selfLink.toString(),
215
216
  bucket: metadata.bucket,
216
217
  entity: req.body.entity,
217
218
  role: req.body.role,
@@ -222,10 +223,6 @@ function createCloudEndpoints(emulator) {
222
223
  gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
223
224
  const uploadType = req.query.uploadType || req.header("X-Goog-Upload-Protocol");
224
225
  if (uploadType === "resumable") {
225
- const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
226
- if (emulatorInfo === undefined) {
227
- return res.sendStatus(500);
228
- }
229
226
  const name = getIncomingFileNameFromRequest(req.query, req.body);
230
227
  if (name === undefined) {
231
228
  res.sendStatus(400);
@@ -6,7 +6,6 @@ const registry_1 = require("../registry");
6
6
  const types_1 = require("../types");
7
7
  const emulatorLogger_1 = require("../emulatorLogger");
8
8
  const metadata_1 = require("./metadata");
9
- const apiv2_1 = require("../../apiv2");
10
9
  const STORAGE_V2_ACTION_MAP = {
11
10
  finalize: "finalized",
12
11
  metadataUpdate: "metadataUpdated",
@@ -17,16 +16,12 @@ class StorageCloudFunctions {
17
16
  constructor(projectId) {
18
17
  this.projectId = projectId;
19
18
  this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
20
- this.multicastOrigin = "";
21
19
  this.multicastPath = "";
22
20
  this.enabled = false;
23
- const functionsEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
24
- if (functionsEmulator) {
21
+ if (registry_1.EmulatorRegistry.isRunning(types_1.Emulators.FUNCTIONS)) {
25
22
  this.enabled = true;
26
- this.functionsEmulatorInfo = functionsEmulator.getInfo();
27
- this.multicastOrigin = `http://${registry_1.EmulatorRegistry.getInfoHostString(this.functionsEmulatorInfo)}`;
28
23
  this.multicastPath = `/functions/projects/${projectId}/trigger_multicast`;
29
- this.client = new apiv2_1.Client({ urlPrefix: this.multicastOrigin, auth: false });
24
+ this.client = registry_1.EmulatorRegistry.client(types_1.Emulators.FUNCTIONS);
30
25
  }
31
26
  }
32
27
  async dispatch(action, object) {
@@ -237,11 +237,12 @@ class OutgoingFirebaseMetadata {
237
237
  exports.OutgoingFirebaseMetadata = OutgoingFirebaseMetadata;
238
238
  class CloudStorageBucketMetadata {
239
239
  constructor(id) {
240
- var _a, _b;
241
240
  this.kind = "storage#bucket";
242
241
  this.name = id;
243
242
  this.id = id;
244
- this.selfLink = `http://${(_a = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _a === void 0 ? void 0 : _a.host}:${(_b = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _b === void 0 ? void 0 : _b.port}/v1/b/${this.id}`;
243
+ const selfLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
244
+ selfLink.pathname = `/v1/b/${this.id}`;
245
+ this.selfLink = selfLink.toString();
245
246
  this.timeCreated = toSerializedDate(new Date());
246
247
  this.updated = this.timeCreated;
247
248
  this.projectNumber = "000000000000";
@@ -269,7 +270,6 @@ class CloudStorageObjectAccessControlMetadata {
269
270
  exports.CloudStorageObjectAccessControlMetadata = CloudStorageObjectAccessControlMetadata;
270
271
  class CloudStorageObjectMetadata {
271
272
  constructor(metadata) {
272
- var _a, _b, _c, _d;
273
273
  this.kind = "storage#object";
274
274
  this.name = metadata.name;
275
275
  this.bucket = metadata.bucket;
@@ -311,8 +311,14 @@ class CloudStorageObjectMetadata {
311
311
  this.crc32c = (0, crc_1.crc32cToString)(metadata.crc32c);
312
312
  this.timeStorageClassUpdated = toSerializedDate(metadata.timeCreated);
313
313
  this.id = `${metadata.bucket}/${metadata.name}/${metadata.generation}`;
314
- this.selfLink = `http://${(_a = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _a === void 0 ? void 0 : _a.host}:${(_b = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _b === void 0 ? void 0 : _b.port}/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
315
- this.mediaLink = `http://${(_c = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _c === void 0 ? void 0 : _c.host}:${(_d = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _d === void 0 ? void 0 : _d.port}/download/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}?generation=${metadata.generation}&alt=media`;
314
+ const selfLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
315
+ selfLink.pathname = `/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
316
+ this.selfLink = selfLink.toString();
317
+ const mediaLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
318
+ mediaLink.pathname = `/download/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
319
+ mediaLink.searchParams.set("generation", metadata.generation.toString());
320
+ mediaLink.searchParams.set("alt", "media");
321
+ this.mediaLink = mediaLink.toString();
316
322
  }
317
323
  }
318
324
  exports.CloudStorageObjectMetadata = CloudStorageObjectMetadata;
@@ -15,7 +15,6 @@ const download_1 = require("../../download");
15
15
  const fs = require("fs-extra");
16
16
  const downloadableEmulators_1 = require("../../downloadableEmulators");
17
17
  const registry_1 = require("../../registry");
18
- const apiv2_1 = require("../../../apiv2");
19
18
  const lock = new AsyncLock();
20
19
  const synchonizationKey = "key";
21
20
  class StorageRulesetInstance {
@@ -301,12 +300,8 @@ function toExpressionValue(obj) {
301
300
  throw new error_1.FirebaseError(`Cannot convert "${obj}" of type ${typeof obj} for Firebase Storage rules runtime`);
302
301
  }
303
302
  async function fetchFirestoreDocument(projectId, request) {
304
- const url = registry_1.EmulatorRegistry.url(types_2.Emulators.FIRESTORE);
305
303
  const pathname = `projects/${projectId}${request.context.path}`;
306
- const client = new apiv2_1.Client({
307
- urlPrefix: url.toString(),
308
- apiVersion: "v1",
309
- });
304
+ const client = registry_1.EmulatorRegistry.client(types_2.Emulators.FIRESTORE, { apiVersion: "v1", auth: true });
310
305
  try {
311
306
  const doc = await client.get(pathname);
312
307
  const { name, fields } = doc.body;
@@ -7,6 +7,7 @@ const registry_1 = require("./registry");
7
7
  const error_1 = require("../error");
8
8
  const constants_1 = require("./constants");
9
9
  const track_1 = require("../track");
10
+ const ExpressBasedEmulator_1 = require("./ExpressBasedEmulator");
10
11
  class EmulatorUI {
11
12
  constructor(args) {
12
13
  this.args = args;
@@ -15,13 +16,11 @@ class EmulatorUI {
15
16
  if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.HUB)) {
16
17
  throw new error_1.FirebaseError(`Cannot start ${constants_1.Constants.description(types_1.Emulators.UI)} without ${constants_1.Constants.description(types_1.Emulators.HUB)}!`);
17
18
  }
18
- const hubInfo = registry_1.EmulatorRegistry.get(types_1.Emulators.HUB).getInfo();
19
- const { auto_download: autoDownload, host, port, projectId } = this.args;
19
+ const { auto_download: autoDownload, projectId } = this.args;
20
20
  const env = {
21
- HOST: host.toString(),
22
- PORT: port.toString(),
21
+ LISTEN: JSON.stringify(ExpressBasedEmulator_1.ExpressBasedEmulator.listenOptionsFromSpecs(this.args.listen)),
23
22
  GCLOUD_PROJECT: projectId,
24
- [constants_1.Constants.FIREBASE_EMULATOR_HUB]: registry_1.EmulatorRegistry.getInfoHostString(hubInfo),
23
+ [constants_1.Constants.FIREBASE_EMULATOR_HUB]: registry_1.EmulatorRegistry.url(types_1.Emulators.HUB).host,
25
24
  };
26
25
  const session = (0, track_1.emulatorSession)();
27
26
  if (session) {
@@ -38,8 +37,8 @@ class EmulatorUI {
38
37
  getInfo() {
39
38
  return {
40
39
  name: this.getName(),
41
- host: this.args.host,
42
- port: this.args.port,
40
+ host: this.args.listen[0].address,
41
+ port: this.args.listen[0].port,
43
42
  pid: downloadableEmulators.getPID(types_1.Emulators.UI),
44
43
  };
45
44
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.flushToDisk = exports.assertEnabled = exports.setEnabled = exports.isEnabled = exports.experimentNameAutocorrect = exports.isValidExperiment = exports.ALL_EXPERIMENTS = void 0;
3
+ exports.flushToDisk = exports.assertEnabled = exports.enableExperimentsFromCliEnvVariable = exports.setEnabled = exports.isEnabled = exports.experimentNameAutocorrect = exports.isValidExperiment = exports.ALL_EXPERIMENTS = void 0;
4
4
  const colorette_1 = require("colorette");
5
5
  const leven = require("leven");
6
6
  const configstore_1 = require("./configstore");
@@ -112,6 +112,15 @@ function setEnabled(name, to) {
112
112
  }
113
113
  }
114
114
  exports.setEnabled = setEnabled;
115
+ function enableExperimentsFromCliEnvVariable() {
116
+ const experiments = process.env.FIREBASE_CLI_EXPERIMENTS || "";
117
+ for (const experiment of experiments.split(",")) {
118
+ if (isValidExperiment(experiment)) {
119
+ setEnabled(experiment, true);
120
+ }
121
+ }
122
+ }
123
+ exports.enableExperimentsFromCliEnvVariable = enableExperimentsFromCliEnvVariable;
115
124
  function assertEnabled(name, task) {
116
125
  if (!isEnabled(name)) {
117
126
  throw new error_1.FirebaseError(`Cannot ${task} because the experiment ${(0, colorette_1.bold)(name)} is not enabled. To enable ${(0, colorette_1.bold)(name)} run ${(0, colorette_1.bold)(`firebase experiments:enable ${name}`)}`);
@@ -9,11 +9,13 @@ const extensionsHelper_1 = require("./extensionsHelper");
9
9
  const logger_1 = require("../logger");
10
10
  const error_1 = require("../error");
11
11
  const iam = require("../gcp/iam");
12
+ const secretsUtils_1 = require("./secretsUtils");
12
13
  marked.setOptions({
13
14
  renderer: new TerminalRenderer(),
14
15
  });
16
+ const TASKS_ROLE = "cloudtasks.enqueuer";
17
+ const TASKS_API = "cloudtasks.googleapis.com";
15
18
  async function displayExtInfo(extensionName, publisher, spec, published = false) {
16
- var _a, _b;
17
19
  const lines = [];
18
20
  lines.push(`**Name**: ${spec.displayName}`);
19
21
  if (publisher) {
@@ -26,13 +28,17 @@ async function displayExtInfo(extensionName, publisher, spec, published = false)
26
28
  if (spec.license) {
27
29
  lines.push(`**License**: ${spec.license}`);
28
30
  }
29
- lines.push(`**Source code**: ${spec.sourceUrl}`);
31
+ if (spec.sourceUrl) {
32
+ lines.push(`**Source code**: ${spec.sourceUrl}`);
33
+ }
30
34
  }
31
- if ((_a = spec.apis) === null || _a === void 0 ? void 0 : _a.length) {
32
- lines.push(displayApis(spec.apis));
35
+ const apis = impliedApis(spec);
36
+ if (apis.length) {
37
+ lines.push(displayApis(apis));
33
38
  }
34
- if ((_b = spec.roles) === null || _b === void 0 ? void 0 : _b.length) {
35
- lines.push(await displayRoles(spec.roles));
39
+ const roles = impliedRoles(spec);
40
+ if (roles.length) {
41
+ lines.push(await displayRoles(roles));
36
42
  }
37
43
  if (lines.length > 0) {
38
44
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `information about '${clc.bold(extensionName)}':`);
@@ -73,3 +79,34 @@ function displayApis(apis) {
73
79
  });
74
80
  return "**APIs used by this Extension**:\n" + lines.join("\n");
75
81
  }
82
+ function usesTasks(spec) {
83
+ return spec.resources.some((r) => { var _a; return ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.taskQueueTrigger) !== undefined; });
84
+ }
85
+ function impliedRoles(spec) {
86
+ var _a, _b, _c;
87
+ const roles = [];
88
+ if ((0, secretsUtils_1.usesSecrets)(spec) && !((_a = spec.roles) === null || _a === void 0 ? void 0 : _a.some((r) => r.role === secretsUtils_1.SECRET_ROLE))) {
89
+ roles.push({
90
+ role: secretsUtils_1.SECRET_ROLE,
91
+ reason: "Allows the extension to read secret values from Cloud Secret Manager",
92
+ });
93
+ }
94
+ if (usesTasks(spec) && !((_b = spec.roles) === null || _b === void 0 ? void 0 : _b.some((r) => r.role === TASKS_ROLE))) {
95
+ roles.push({
96
+ role: TASKS_ROLE,
97
+ reason: "Allows the extension to enqueue Cloud Tasks",
98
+ });
99
+ }
100
+ return roles.concat((_c = spec.roles) !== null && _c !== void 0 ? _c : []);
101
+ }
102
+ function impliedApis(spec) {
103
+ var _a, _b;
104
+ const apis = [];
105
+ if (usesTasks(spec) && !((_a = spec.apis) === null || _a === void 0 ? void 0 : _a.some((a) => a.apiName === TASKS_API))) {
106
+ apis.push({
107
+ apiName: TASKS_API,
108
+ reason: "Allows the extension to enqueue Cloud Tasks",
109
+ });
110
+ }
111
+ return apis.concat((_b = spec.apis) !== null && _b !== void 0 ? _b : []);
112
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prettySecretName = exports.getSecretLabels = exports.getActiveSecrets = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = exports.SECRET_LABEL = void 0;
3
+ exports.prettySecretName = exports.getSecretLabels = exports.getActiveSecrets = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = exports.SECRET_ROLE = exports.SECRET_LABEL = void 0;
4
4
  const getProjectNumber_1 = require("../getProjectNumber");
5
5
  const utils = require("../utils");
6
6
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
@@ -9,6 +9,7 @@ const types_1 = require("./types");
9
9
  const secretManagerApi = require("../gcp/secretManager");
10
10
  const logger_1 = require("../logger");
11
11
  exports.SECRET_LABEL = "firebase-extensions-managed";
12
+ exports.SECRET_ROLE = "secretmanager.secretAccessor";
12
13
  async function ensureSecretManagerApiEnabled(options) {
13
14
  const projectId = (0, projectUtils_1.needProjectId)(options);
14
15
  return await (0, ensureApiEnabled_1.ensure)(projectId, "secretmanager.googleapis.com", "extensions", options.markdown);