firebase-tools 10.2.1 → 10.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/lib/appdistribution/options-parser-util.js +1 -1
  2. package/lib/auth.js +3 -3
  3. package/lib/command.js +1 -1
  4. package/lib/commands/apps-android-sha-create.js +2 -2
  5. package/lib/commands/apps-sdkconfig.js +1 -1
  6. package/lib/commands/database-rules-list.js +2 -2
  7. package/lib/commands/emulators-start.js +1 -1
  8. package/lib/commands/ext-dev-init.js +49 -49
  9. package/lib/commands/ext-export.js +12 -2
  10. package/lib/commands/ext-install.js +104 -104
  11. package/lib/commands/ext-uninstall.js +8 -8
  12. package/lib/commands/ext-update.js +9 -9
  13. package/lib/commands/functions-config-export.js +1 -1
  14. package/lib/commands/hosting-clone.js +3 -3
  15. package/lib/commands/remoteconfig-get.js +1 -1
  16. package/lib/deploy/extensions/deploymentSummary.js +3 -3
  17. package/lib/deploy/extensions/params.js +3 -0
  18. package/lib/deploy/extensions/planner.js +2 -1
  19. package/lib/deploy/extensions/tasks.js +1 -1
  20. package/lib/deploy/functions/backend.js +12 -5
  21. package/lib/deploy/functions/checkIam.js +1 -1
  22. package/lib/deploy/functions/containerCleaner.js +3 -3
  23. package/lib/deploy/functions/ensure.js +3 -3
  24. package/lib/deploy/functions/functionsDeployHelper.js +2 -2
  25. package/lib/deploy/functions/prepare.js +3 -2
  26. package/lib/deploy/functions/pricing.js +1 -1
  27. package/lib/deploy/functions/prompts.js +2 -2
  28. package/lib/deploy/functions/release/fabricator.js +3 -3
  29. package/lib/deploy/functions/release/index.js +1 -1
  30. package/lib/deploy/functions/release/planner.js +11 -8
  31. package/lib/deploy/functions/release/reporter.js +3 -0
  32. package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
  33. package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
  34. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +16 -11
  35. package/lib/deploy/functions/runtimes/golang/index.js +2 -2
  36. package/lib/deploy/functions/runtimes/node/index.js +26 -0
  37. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
  38. package/lib/deploy/functions/runtimes/node/parseTriggers.js +28 -7
  39. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  40. package/lib/deploy/functions/validate.js +3 -3
  41. package/lib/deploy/hosting/deploy.js +2 -2
  42. package/lib/deploy/hosting/hashcache.js +21 -19
  43. package/lib/deploy/hosting/uploader.js +5 -5
  44. package/lib/deploy/remoteconfig/functions.js +2 -2
  45. package/lib/emulator/auth/cloudFunctions.js +1 -1
  46. package/lib/emulator/auth/operations.js +1 -1
  47. package/lib/emulator/constants.js +3 -0
  48. package/lib/emulator/controller.js +47 -19
  49. package/lib/emulator/download.js +18 -1
  50. package/lib/emulator/downloadableEmulators.js +1 -1
  51. package/lib/emulator/emulatorLogger.js +12 -1
  52. package/lib/emulator/extensions/validation.js +35 -0
  53. package/lib/emulator/extensionsEmulator.js +140 -0
  54. package/lib/emulator/functionsEmulator.js +86 -39
  55. package/lib/emulator/functionsEmulatorRuntime.js +44 -36
  56. package/lib/emulator/functionsEmulatorShell.js +1 -1
  57. package/lib/emulator/functionsEmulatorUtils.js +4 -4
  58. package/lib/emulator/functionsRuntimeWorker.js +2 -2
  59. package/lib/emulator/hub.js +4 -3
  60. package/lib/emulator/loggingEmulator.js +1 -1
  61. package/lib/emulator/pubsubEmulator.js +1 -1
  62. package/lib/emulator/registry.js +10 -2
  63. package/lib/emulator/storage/apis/firebase.js +31 -26
  64. package/lib/emulator/storage/apis/gcloud.js +7 -12
  65. package/lib/emulator/storage/files.js +36 -34
  66. package/lib/emulator/storage/index.js +2 -2
  67. package/lib/emulator/storage/metadata.js +2 -2
  68. package/lib/emulator/storage/rules/runtime.js +8 -7
  69. package/lib/emulator/types.js +3 -0
  70. package/lib/ensureApiEnabled.js +5 -1
  71. package/lib/error.js +1 -1
  72. package/lib/extensions/askUserForParam.js +1 -1
  73. package/lib/extensions/changelog.js +3 -1
  74. package/lib/extensions/checkProjectBilling.js +1 -1
  75. package/lib/extensions/displayExtensionInfo.js +1 -1
  76. package/lib/extensions/emulator/optionsHelper.js +24 -8
  77. package/lib/extensions/emulator/specHelper.js +10 -23
  78. package/lib/extensions/export.js +1 -51
  79. package/lib/extensions/extensionsApi.js +1 -1
  80. package/lib/extensions/extensionsHelper.js +13 -9
  81. package/lib/extensions/manifest.js +48 -0
  82. package/lib/extensions/metricsUtils.js +4 -4
  83. package/lib/extensions/paramHelper.js +4 -4
  84. package/lib/extensions/refs.js +1 -1
  85. package/lib/extensions/secretsUtils.js +3 -3
  86. package/lib/functional.js +1 -1
  87. package/lib/functions/env.js +2 -1
  88. package/lib/gcp/cloudfunctions.js +24 -5
  89. package/lib/gcp/cloudfunctionsv2.js +18 -5
  90. package/lib/gcp/cloudtasks.js +1 -1
  91. package/lib/gcp/docker.js +2 -2
  92. package/lib/gcp/run.js +2 -2
  93. package/lib/hosting/api.js +1 -1
  94. package/lib/hosting/proxy.js +2 -2
  95. package/lib/init/features/account.js +1 -1
  96. package/lib/management/database.js +1 -1
  97. package/lib/previews.js +1 -1
  98. package/lib/utils.js +1 -1
  99. package/npm-shrinkwrap.json +786 -393
  100. package/package.json +1 -1
  101. package/schema/firebase-config.json +5 -0
  102. package/templates/init/functions/javascript/package.lint.json +3 -3
  103. package/templates/init/functions/javascript/package.nolint.json +2 -2
  104. package/templates/init/functions/typescript/package.lint.json +7 -7
  105. package/templates/init/functions/typescript/package.nolint.json +3 -3
@@ -14,7 +14,7 @@ function endpointsAreValid(wantBackend) {
14
14
  functionIdsAreValid(backend.allEndpoints(wantBackend));
15
15
  const gcfV1WithConcurrency = backend
16
16
  .allEndpoints(wantBackend)
17
- .filter((endpoint) => (endpoint.concurrency || 1) != 1 && endpoint.platform == "gcfv1")
17
+ .filter((endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1")
18
18
  .map((endpoint) => endpoint.id);
19
19
  if (gcfV1WithConcurrency.length) {
20
20
  const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency.join(",")} because they are GCF gen 1`;
@@ -23,7 +23,7 @@ function endpointsAreValid(wantBackend) {
23
23
  const tooSmallForConcurrency = backend
24
24
  .allEndpoints(wantBackend)
25
25
  .filter((endpoint) => {
26
- if ((endpoint.concurrency || 1) == 1) {
26
+ if ((endpoint.concurrency || 1) === 1) {
27
27
  return false;
28
28
  }
29
29
  const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
@@ -98,7 +98,7 @@ async function validateSecretVersions(projectId, endpoints) {
98
98
  for (const result of results) {
99
99
  if (result.status === "fulfilled") {
100
100
  const sv = result.value;
101
- if (sv.state != "ENABLED") {
101
+ if (sv.state !== "ENABLED") {
102
102
  errs.push(new error_1.FirebaseError(`Expected secret ${sv.secret.name}@${sv.versionId} to be in state ENABLED not ${sv.state}.`));
103
103
  }
104
104
  secretVersions[sv.secret.name] = sv;
@@ -53,7 +53,7 @@ async function deploy(context, options) {
53
53
  await uploader.start();
54
54
  }
55
55
  catch (err) {
56
- track("Hosting Deploy", "failure");
56
+ void track("Hosting Deploy", "failure");
57
57
  throw err;
58
58
  }
59
59
  finally {
@@ -65,7 +65,7 @@ async function deploy(context, options) {
65
65
  (0, utils_1.logLabeledSuccess)("hosting[" + deploy.site + "]", "file upload complete");
66
66
  const dt = Date.now() - t0;
67
67
  logger_1.logger.debug("[hosting] deploy completed after " + dt + "ms");
68
- track("Hosting Deploy", "success", dt);
68
+ void track("Hosting Deploy", "success", dt);
69
69
  return runDeploys(deploys, debugging);
70
70
  }
71
71
  const debugging = !!(options.debug || options.nonInteractive);
@@ -1,46 +1,48 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dump = exports.load = void 0;
2
4
  const fs = require("fs-extra");
3
5
  const path = require("path");
4
- const { logger } = require("../../logger");
6
+ const logger_1 = require("../../logger");
5
7
  function cachePath(cwd, name) {
6
- return path.resolve(cwd, ".firebase/hosting." + name + ".cache");
8
+ return path.resolve(cwd, `.firebase/hosting.${name}.cache`);
7
9
  }
8
- exports.load = function (cwd, name) {
10
+ function load(cwd, name) {
9
11
  try {
10
- const out = {};
11
- const lines = fs.readFileSync(cachePath(cwd, name), {
12
- encoding: "utf8",
13
- });
14
- lines.split("\n").forEach(function (line) {
12
+ const out = new Map();
13
+ const lines = fs.readFileSync(cachePath(cwd, name), "utf8");
14
+ for (const line of lines.split("\n")) {
15
15
  const d = line.split(",");
16
16
  if (d.length === 3) {
17
- out[d[0]] = { mtime: parseInt(d[1]), hash: d[2] };
17
+ out.set(d[0], { mtime: parseInt(d[1]), hash: d[2] });
18
18
  }
19
- });
19
+ }
20
20
  return out;
21
21
  }
22
22
  catch (e) {
23
23
  if (e.code === "ENOENT") {
24
- logger.debug("[hosting] hash cache [" + name + "] not populated");
24
+ logger_1.logger.debug(`[hosting] hash cache [${name}] not populated`);
25
25
  }
26
26
  else {
27
- logger.debug("[hosting] hash cache [" + name + "] load error:", e.message);
27
+ logger_1.logger.debug(`[hosting] hash cache [${name}] load error: ${e.message}`);
28
28
  }
29
- return {};
29
+ return new Map();
30
30
  }
31
- };
32
- exports.dump = function (cwd, name, data) {
31
+ }
32
+ exports.load = load;
33
+ function dump(cwd, name, data) {
33
34
  let st = "";
34
35
  let count = 0;
35
36
  for (const [path, d] of data) {
36
37
  count++;
37
- st += path + "," + d.mtime + "," + d.hash + "\n";
38
+ st += `${path},${d.mtime},${d.hash}\n`;
38
39
  }
39
40
  try {
40
41
  fs.outputFileSync(cachePath(cwd, name), st, { encoding: "utf8" });
41
- logger.debug("[hosting] hash cache [" + name + "] stored for", count, "files");
42
+ logger_1.logger.debug(`[hosting] hash cache [${name}] stored for ${count} files`);
42
43
  }
43
44
  catch (e) {
44
- logger.debug("[hosting] unable to store hash cache [" + name + "]", e.stack);
45
+ logger_1.logger.debug(`[hosting] unable to store hash cache [${name}]: ${e.stack}`);
45
46
  }
46
- };
47
+ }
48
+ exports.dump = dump;
@@ -11,7 +11,7 @@ const zlib = require("zlib");
11
11
  const apiv2_1 = require("../../apiv2");
12
12
  const queue_1 = require("../../throttler/queue");
13
13
  const api_1 = require("../../api");
14
- const hashcache = require("./hashcache");
14
+ const hashcache_1 = require("./hashcache");
15
15
  const logger_1 = require("../../logger");
16
16
  const error_1 = require("../../error");
17
17
  const MIN_UPLOAD_TIMEOUT = 30000;
@@ -54,7 +54,7 @@ class Uploader {
54
54
  this.public = options.public || this.cwd;
55
55
  this.files = options.files;
56
56
  this.fileCount = this.files.length;
57
- this.cache = hashcache.load(this.projectRoot, this.hashcacheName());
57
+ this.cache = (0, hashcache_1.load)(this.projectRoot, this.hashcacheName());
58
58
  this.cacheNew = new Map();
59
59
  this.sizeMap = {};
60
60
  this.hashMap = {};
@@ -78,7 +78,7 @@ class Uploader {
78
78
  .wait()
79
79
  .then(this.queuePopulate.bind(this))
80
80
  .then(() => {
81
- hashcache.dump(this.projectRoot, this.hashcacheName(), this.cacheNew);
81
+ (0, hashcache_1.dump)(this.projectRoot, this.hashcacheName(), this.cacheNew);
82
82
  logger_1.logger.debug("[hosting][hash queue][FINAL]", this.hashQueue.stats());
83
83
  this.populateQueue.close();
84
84
  return this.populateQueue.wait();
@@ -91,7 +91,7 @@ class Uploader {
91
91
  this.uploadQueue.wait().catch((err) => {
92
92
  if (err.message.includes("content hash")) {
93
93
  logger_1.logger.debug("[hosting][upload queue] upload failed with content hash error. Deleting hash cache");
94
- hashcache.dump(this.projectRoot, this.hashcacheName(), new Map());
94
+ (0, hashcache_1.dump)(this.projectRoot, this.hashcacheName(), new Map());
95
95
  }
96
96
  });
97
97
  const fin = (err) => {
@@ -123,7 +123,7 @@ class Uploader {
123
123
  const stats = fs.statSync(path.resolve(this.public, filePath));
124
124
  const mtime = stats.mtime.getTime();
125
125
  this.sizeMap[filePath] = stats.size;
126
- const cached = this.cache[filePath];
126
+ const cached = this.cache.get(filePath);
127
127
  if (cached && cached.mtime === mtime) {
128
128
  this.cacheNew.set(filePath, cached);
129
129
  this.addHash(filePath, cached.hash);
@@ -20,10 +20,10 @@ async function getEtag(projectNumber, versionNumber) {
20
20
  exports.getEtag = getEtag;
21
21
  function validateInputRemoteConfigTemplate(template) {
22
22
  const templateCopy = JSON.parse(JSON.stringify(template));
23
- if (!templateCopy || templateCopy == "null" || templateCopy == "undefined") {
23
+ if (!templateCopy || templateCopy === "null" || templateCopy === "undefined") {
24
24
  throw new error_1.FirebaseError(`Invalid Remote Config template: ${JSON.stringify(templateCopy)}`);
25
25
  }
26
- if (typeof templateCopy.etag !== "string" || templateCopy.etag == "") {
26
+ if (typeof templateCopy.etag !== "string" || templateCopy.etag === "") {
27
27
  throw new error_1.FirebaseError("ETag must be a non-empty string");
28
28
  }
29
29
  if (templateCopy.conditions && !Array.isArray(templateCopy.conditions)) {
@@ -35,7 +35,7 @@ class AuthCloudFunction {
35
35
  catch (e) {
36
36
  err = e;
37
37
  }
38
- if (err || (res === null || res === void 0 ? void 0 : res.status) != 200) {
38
+ if (err || (res === null || res === void 0 ? void 0 : res.status) !== 200) {
39
39
  this.logger.logLabeled("WARN", "functions", `Firebase Authentication function was not triggered due to emulation error. Please file a bug.`);
40
40
  }
41
41
  }
@@ -1378,7 +1378,7 @@ function mfaSignInFinalize(state, reqBody) {
1378
1378
  (0, errors_1.assert)(sessionInfo, "MISSING_SESSION_INFO");
1379
1379
  const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
1380
1380
  let { user, signInProvider } = parsePendingCredential(state, reqBody.mfaPendingCredential);
1381
- const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((enrollment) => enrollment.unobfuscatedPhoneInfo == phoneNumber);
1381
+ const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber);
1382
1382
  (0, errors_1.assert)(enrollment && enrollment.mfaEnrollmentId, "MFA_ENROLLMENT_NOT_FOUND");
1383
1383
  user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
1384
1384
  (0, errors_1.assert)(!user.disabled, "USER_DISABLED");
@@ -8,6 +8,7 @@ const DEFAULT_PORTS = {
8
8
  logging: 4500,
9
9
  hosting: 5000,
10
10
  functions: 5001,
11
+ extensions: 5001,
11
12
  firestore: 8080,
12
13
  pubsub: 8085,
13
14
  database: 9000,
@@ -25,6 +26,7 @@ exports.FIND_AVAILBLE_PORT_BY_DEFAULT = {
25
26
  pubsub: false,
26
27
  auth: false,
27
28
  storage: false,
29
+ extensions: false,
28
30
  };
29
31
  exports.EMULATOR_DESCRIPTION = {
30
32
  ui: "Emulator UI",
@@ -37,6 +39,7 @@ exports.EMULATOR_DESCRIPTION = {
37
39
  pubsub: "Pub/Sub Emulator",
38
40
  auth: "Authentication Emulator",
39
41
  storage: "Storage Emulator",
42
+ extensions: "Extensions Emulator",
40
43
  };
41
44
  const DEFAULT_HOST = "localhost";
42
45
  class Constants {
@@ -35,8 +35,14 @@ const fsutils_1 = require("../fsutils");
35
35
  const storage_1 = require("./storage");
36
36
  const getDefaultDatabaseInstance_1 = require("../getDefaultDatabaseInstance");
37
37
  const auth_2 = require("../auth");
38
+ const extensionsEmulator_1 = require("./extensionsEmulator");
39
+ const previews_1 = require("../previews");
40
+ const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
38
41
  async function getAndCheckAddress(emulator, options) {
39
42
  var _a, _b, _c, _d;
43
+ if (emulator === types_1.Emulators.EXTENSIONS) {
44
+ emulator = types_1.Emulators.FUNCTIONS;
45
+ }
40
46
  let host = ((_b = (_a = options.config.src.emulators) === null || _a === void 0 ? void 0 : _a[emulator]) === null || _b === void 0 ? void 0 : _b.host) || constants_1.Constants.getDefaultHost(emulator);
41
47
  if (host === "localhost" && utils.isRunningInWSL()) {
42
48
  host = "127.0.0.1";
@@ -56,7 +62,7 @@ async function getAndCheckAddress(emulator, options) {
56
62
  if (!portOpen) {
57
63
  if (findAvailablePort) {
58
64
  const newPort = await portUtils.findAvailablePort(host, port);
59
- if (newPort != port) {
65
+ if (newPort !== port) {
60
66
  loggerForEmulator.logLabeled("WARN", emulator, `${constants_1.Constants.description(emulator)} unable to start on port ${port}, starting on ${newPort} instead.`);
61
67
  port = newPort;
62
68
  }
@@ -86,7 +92,7 @@ async function getAndCheckAddress(emulator, options) {
86
92
  }
87
93
  async function startEmulator(instance) {
88
94
  const name = instance.getName();
89
- track("Emulator Run", name);
95
+ void track("Emulator Run", name);
90
96
  await registry_1.EmulatorRegistry.start(instance);
91
97
  }
92
98
  exports.startEmulator = startEmulator;
@@ -115,7 +121,11 @@ async function cleanShutdown() {
115
121
  }
116
122
  exports.cleanShutdown = cleanShutdown;
117
123
  function filterEmulatorTargets(options) {
118
- let targets = types_1.ALL_SERVICE_EMULATORS.filter((e) => {
124
+ let targets = [...types_1.ALL_SERVICE_EMULATORS];
125
+ if (previews_1.previews.extensionsemulator) {
126
+ targets.push(types_1.Emulators.EXTENSIONS);
127
+ }
128
+ targets = targets.filter((e) => {
119
129
  return options.config.has(e) || options.config.has(`emulators.${e}`);
120
130
  });
121
131
  const onlyOptions = options.only;
@@ -222,7 +232,7 @@ async function startAll(options, showUI = true) {
222
232
  if (shouldStart(options, types_1.Emulators.HUB)) {
223
233
  const hubAddr = await getAndCheckAddress(types_1.Emulators.HUB, options);
224
234
  const hub = new hub_1.EmulatorHub(Object.assign({ projectId }, hubAddr));
225
- track("emulators:start", "hub");
235
+ void track("emulators:start", "hub");
226
236
  await startEmulator(hub);
227
237
  }
228
238
  let exportMetadata = {
@@ -239,19 +249,43 @@ async function startAll(options, showUI = true) {
239
249
  hubLogger.logLabeled("WARN", "emulators", `Could not find import/export metadata file, ${clc.bold("skipping data import!")}`);
240
250
  }
241
251
  }
252
+ const emulatableBackends = [];
253
+ const projectDir = (options.extDevDir || options.config.projectDir);
242
254
  if (shouldStart(options, types_1.Emulators.FUNCTIONS)) {
243
- const functionsLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS);
244
- const functionsAddr = await getAndCheckAddress(types_1.Emulators.FUNCTIONS, options);
245
- const projectId = (0, projectUtils_1.needProjectId)(options);
246
255
  utils.assertDefined(options.config.src.functions);
247
256
  utils.assertDefined(options.config.src.functions.source, "Error: 'functions.source' is not defined");
248
- utils.assertIsStringOrUndefined(options.extensionDir);
249
- const projectDir = options.extensionDir || options.config.projectDir;
257
+ utils.assertIsStringOrUndefined(options.extDevDir);
250
258
  const functionsDir = path.join(projectDir, options.config.src.functions.source);
259
+ emulatableBackends.push({
260
+ functionsDir,
261
+ env: Object.assign({}, options.extDevEnv),
262
+ predefinedTriggers: options.extDevTriggers,
263
+ nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || options.config.get("functions.runtime")),
264
+ });
265
+ }
266
+ if (shouldStart(options, types_1.Emulators.EXTENSIONS) && previews_1.previews.extensionsemulator) {
267
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
268
+ const aliases = (0, projectUtils_1.getAliases)(options, projectId);
269
+ const extensionEmulator = new extensionsEmulator_1.ExtensionsEmulator({
270
+ projectId,
271
+ projectDir: options.config.projectDir,
272
+ projectNumber,
273
+ aliases,
274
+ extensions: options.config.get("extensions"),
275
+ });
276
+ const extensionsBackends = await extensionEmulator.getExtensionBackends();
277
+ emulatableBackends.push(...extensionsBackends);
278
+ void track("Emulator Run", types_1.Emulators.EXTENSIONS);
279
+ registry_1.EmulatorRegistry.registerExtensionsEmulator();
280
+ }
281
+ if (emulatableBackends.length) {
282
+ const functionsLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS);
283
+ const functionsAddr = await getAndCheckAddress(types_1.Emulators.FUNCTIONS, options);
284
+ const projectId = (0, projectUtils_1.needProjectId)(options);
251
285
  let inspectFunctions;
252
286
  if (options.inspectFunctions) {
253
287
  inspectFunctions = commandUtils.parseInspectionPort(options);
254
- functionsLogger.logLabeled("WARN", "functions", `You are running the functions emulator in debug mode (port=${inspectFunctions}). This means that functions will execute in sequence rather than in parallel.`);
288
+ functionsLogger.logLabeled("WARN", "functions", `You are running the Functions emulator in debug mode (port=${inspectFunctions}). This means that functions will execute in sequence rather than in parallel.`);
255
289
  }
256
290
  const emulatorsNotRunning = types_1.ALL_SERVICE_EMULATORS.filter((e) => {
257
291
  return e !== types_1.Emulators.FUNCTIONS && !shouldStart(options, e);
@@ -260,14 +294,6 @@ async function startAll(options, showUI = true) {
260
294
  functionsLogger.logLabeled("WARN", "functions", `The following emulators are not running, calls to these services from the Functions emulator will affect production: ${clc.bold(emulatorsNotRunning.join(", "))}`);
261
295
  }
262
296
  const account = (0, auth_2.getProjectDefaultAccount)(options.projectRoot);
263
- const emulatableBackends = [
264
- {
265
- functionsDir,
266
- env: Object.assign({}, options.extensionEnv),
267
- predefinedTriggers: options.extensionTriggers,
268
- nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extensionNodeVersion || options.config.get("functions.runtime")),
269
- },
270
- ];
271
297
  const functionsEmulator = new functionsEmulator_1.FunctionsEmulator({
272
298
  projectId,
273
299
  projectDir,
@@ -425,13 +451,15 @@ async function startAll(options, showUI = true) {
425
451
  if (showUI && !shouldStart(options, types_1.Emulators.UI)) {
426
452
  hubLogger.logLabeled("WARN", "emulators", "The Emulator UI requires a project ID to start. Configure your default project with 'firebase use' or pass the --project flag.");
427
453
  }
428
- if (showUI && shouldStart(options, types_1.Emulators.UI)) {
454
+ if (showUI && (shouldStart(options, types_1.Emulators.UI) || START_LOGGING_EMULATOR)) {
429
455
  const loggingAddr = await getAndCheckAddress(types_1.Emulators.LOGGING, options);
430
456
  const loggingEmulator = new loggingEmulator_1.LoggingEmulator({
431
457
  host: loggingAddr.host,
432
458
  port: loggingAddr.port,
433
459
  });
434
460
  await startEmulator(loggingEmulator);
461
+ }
462
+ if (showUI && shouldStart(options, types_1.Emulators.UI)) {
435
463
  const uiAddr = await getAndCheckAddress(types_1.Emulators.UI, options);
436
464
  const ui = new ui_1.EmulatorUI(Object.assign({ projectId: projectId, auto_download: true }, uiAddr));
437
465
  await startEmulator(ui);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.downloadEmulator = void 0;
3
+ exports.downloadExtensionVersion = exports.downloadEmulator = void 0;
4
4
  const crypto = require("crypto");
5
5
  const fs = require("fs-extra");
6
6
  const path = require("path");
@@ -32,6 +32,23 @@ async function downloadEmulator(name) {
32
32
  removeOldFiles(name, emulator);
33
33
  }
34
34
  exports.downloadEmulator = downloadEmulator;
35
+ async function downloadExtensionVersion(extensionVersionRef, sourceDownloadUri, targetDir) {
36
+ const emulatorLogger = emulatorLogger_1.EmulatorLogger.forExtension({ ref: extensionVersionRef });
37
+ emulatorLogger.logLabeled("BULLET", "extensions", `Starting download for ${extensionVersionRef} source code...`);
38
+ try {
39
+ fs.mkdirSync(targetDir);
40
+ }
41
+ catch (err) {
42
+ emulatorLogger.logLabeled("BULLET", "extensions", `cache directory for ${extensionVersionRef} already exists...`);
43
+ }
44
+ emulatorLogger.logLabeled("BULLET", "extensions", `downloading ${sourceDownloadUri}...`);
45
+ const sourceCodeZip = await downloadUtils.downloadToTmp(sourceDownloadUri);
46
+ await unzip(sourceCodeZip, targetDir);
47
+ fs.chmodSync(targetDir, 0o755);
48
+ emulatorLogger.logLabeled("BULLET", "extensions", `Downloaded to ${targetDir}...`);
49
+ await new Promise((resolve) => setTimeout(resolve, 1000));
50
+ }
51
+ exports.downloadExtensionVersion = downloadExtensionVersion;
35
52
  function unzip(zipPath, unzipDir) {
36
53
  return new Promise((resolve, reject) => {
37
54
  fs.createReadStream(zipPath)
@@ -148,8 +148,8 @@ const Commands = {
148
148
  storage: {
149
149
  binary: "java",
150
150
  args: [
151
- "-jar",
152
151
  "-Duser.language=en",
152
+ "-jar",
153
153
  getExecPath(types_1.Emulators.STORAGE),
154
154
  "serve",
155
155
  ],
@@ -34,7 +34,7 @@ class EmulatorLogger {
34
34
  },
35
35
  });
36
36
  }
37
- static forFunction(functionName) {
37
+ static forFunction(functionName, extensionLogInfo) {
38
38
  return new EmulatorLogger({
39
39
  metadata: {
40
40
  emulator: {
@@ -43,6 +43,17 @@ class EmulatorLogger {
43
43
  function: {
44
44
  name: functionName,
45
45
  },
46
+ extension: extensionLogInfo,
47
+ },
48
+ });
49
+ }
50
+ static forExtension(extensionLogInfo) {
51
+ return new EmulatorLogger({
52
+ metadata: {
53
+ emulator: {
54
+ name: "extensions",
55
+ },
56
+ extension: extensionLogInfo,
46
57
  },
47
58
  });
48
59
  }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getUnemulatedAPIs = void 0;
4
+ const planner = require("../../deploy/extensions/planner");
5
+ const ensureApiEnabled_1 = require("../../ensureApiEnabled");
6
+ const EMULATED_APIS = [
7
+ "storage-component.googleapis.com",
8
+ "firestore.googleapis.com",
9
+ "pubsub.googleapis.com",
10
+ "identitytoolkit.googleapis.com",
11
+ ];
12
+ async function getUnemulatedAPIs(projectId, instances) {
13
+ var _a;
14
+ const unemulatedAPIs = {};
15
+ for (const i of instances) {
16
+ const extensionVersion = await planner.getExtensionVersion(i);
17
+ for (const api of (_a = extensionVersion.spec.apis) !== null && _a !== void 0 ? _a : []) {
18
+ if (!EMULATED_APIS.includes(api.apiName)) {
19
+ if (unemulatedAPIs[api.apiName]) {
20
+ unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId);
21
+ }
22
+ else {
23
+ const enabled = await (0, ensureApiEnabled_1.check)(projectId, api.apiName, "extensions", true);
24
+ unemulatedAPIs[api.apiName] = {
25
+ apiName: api.apiName,
26
+ instanceIds: [i.instanceId],
27
+ enabled,
28
+ };
29
+ }
30
+ }
31
+ }
32
+ }
33
+ return Object.values(unemulatedAPIs);
34
+ }
35
+ exports.getUnemulatedAPIs = getUnemulatedAPIs;
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExtensionsEmulator = void 0;
4
+ const fs = require("fs-extra");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const clc = require("cli-color");
8
+ const Table = require("cli-table");
9
+ const child_process_1 = require("child_process");
10
+ const planner = require("../deploy/extensions/planner");
11
+ const error_1 = require("../error");
12
+ const refs_1 = require("../extensions/refs");
13
+ const download_1 = require("./download");
14
+ const optionsHelper_1 = require("../extensions/emulator/optionsHelper");
15
+ const emulatorLogger_1 = require("./emulatorLogger");
16
+ const types_1 = require("./types");
17
+ const validation_1 = require("./extensions/validation");
18
+ const ensureApiEnabled_1 = require("../ensureApiEnabled");
19
+ const shortenUrl_1 = require("../shortenUrl");
20
+ class ExtensionsEmulator {
21
+ constructor(args) {
22
+ this.want = [];
23
+ this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.EXTENSIONS);
24
+ this.args = args;
25
+ }
26
+ async readManifest() {
27
+ var _a;
28
+ this.want = await planner.want({
29
+ projectId: this.args.projectId,
30
+ projectNumber: this.args.projectNumber,
31
+ aliases: (_a = this.args.aliases) !== null && _a !== void 0 ? _a : [],
32
+ projectDir: this.args.projectDir,
33
+ extensions: this.args.extensions,
34
+ checkLocal: true,
35
+ });
36
+ }
37
+ async ensureSourceCode(instance) {
38
+ if (!instance.ref) {
39
+ throw new error_1.FirebaseError(`No ref found for ${instance.instanceId}. Emulating local extensions is not yet supported.`);
40
+ }
41
+ const ref = (0, refs_1.toExtensionVersionRef)(instance.ref);
42
+ const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
43
+ path.join(os.homedir(), ".cache", "firebase", "extensions");
44
+ const sourceCodePath = path.join(cacheDir, ref);
45
+ 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);
49
+ }
50
+ return sourceCodePath;
51
+ }
52
+ hasValidSource(args) {
53
+ const requiredFiles = [
54
+ "./extension.yaml",
55
+ "./functions/package.json",
56
+ "./functions/node_modules",
57
+ ];
58
+ for (const requiredFile of requiredFiles) {
59
+ const f = path.join(args.path, requiredFile);
60
+ if (!fs.existsSync(f)) {
61
+ emulatorLogger_1.EmulatorLogger.forExtension({ ref: args.extRef }).logLabeled("BULLET", "extensions", `Detected invalid source code for ${args.extRef}, expected to find ${f}`);
62
+ return false;
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+ installAndBuildSourceCode(sourceCodePath) {
68
+ const npmInstall = (0, child_process_1.spawnSync)("npm", ["--prefix", `/${sourceCodePath}/functions/`, "install"], {
69
+ encoding: "utf8",
70
+ });
71
+ if (npmInstall.error) {
72
+ throw npmInstall.error;
73
+ }
74
+ const npmRunGCPBuild = (0, child_process_1.spawnSync)("npm", ["--prefix", `/${sourceCodePath}/functions/`, "run", "gcp-build"], { encoding: "utf8" });
75
+ if (npmRunGCPBuild.error) {
76
+ throw npmRunGCPBuild.error;
77
+ }
78
+ }
79
+ async getExtensionBackends() {
80
+ await this.readManifest();
81
+ await this.checkAndWarnAPIs(this.want);
82
+ return Promise.all(this.want.map((i) => {
83
+ return this.toEmulatableBackend(i);
84
+ }));
85
+ }
86
+ async toEmulatableBackend(instance) {
87
+ const extensionDir = await this.ensureSourceCode(instance);
88
+ const functionsDir = path.join(extensionDir, "functions");
89
+ const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
90
+ const { extensionTriggers, nodeMajorVersion } = await (0, optionsHelper_1.getExtensionFunctionInfo)(extensionDir, instance.instanceId, env);
91
+ const extension = await planner.getExtension(instance);
92
+ const extensionVersion = await planner.getExtensionVersion(instance);
93
+ return {
94
+ functionsDir,
95
+ env,
96
+ predefinedTriggers: extensionTriggers,
97
+ nodeMajorVersion: nodeMajorVersion,
98
+ extensionInstanceId: instance.instanceId,
99
+ extension,
100
+ extensionVersion,
101
+ };
102
+ }
103
+ autoPopulatedParams(instance) {
104
+ const projectId = this.args.projectId;
105
+ return {
106
+ PROJECT_ID: projectId !== null && projectId !== void 0 ? projectId : "",
107
+ EXT_INSTANCE_ID: instance.instanceId,
108
+ DATABASE_INSTANCE: projectId !== null && projectId !== void 0 ? projectId : "",
109
+ DATABASE_URL: `https://${projectId}.firebaseio.com`,
110
+ STORAGE_BUCKET: `${projectId}.appspot.com`,
111
+ };
112
+ }
113
+ async checkAndWarnAPIs(instances) {
114
+ const apisToWarn = await (0, validation_1.getUnemulatedAPIs)(this.args.projectId, instances);
115
+ if (apisToWarn.length) {
116
+ const table = new Table({
117
+ head: [
118
+ "API Name",
119
+ "Instances using this API",
120
+ `Enabled on ${this.args.projectId}`,
121
+ `Enable this API`,
122
+ ],
123
+ style: { head: ["yellow"] },
124
+ });
125
+ for (const apiToWarn of apisToWarn) {
126
+ const enablementUri = await (0, shortenUrl_1.shortenUrl)((0, ensureApiEnabled_1.enableApiURI)(this.args.projectId, apiToWarn.apiName));
127
+ table.push([
128
+ apiToWarn.apiName,
129
+ apiToWarn.instanceIds,
130
+ apiToWarn.enabled ? "Yes" : "No",
131
+ apiToWarn.enabled ? "" : clc.bold.underline(enablementUri),
132
+ ]);
133
+ }
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());
137
+ }
138
+ }
139
+ }
140
+ exports.ExtensionsEmulator = ExtensionsEmulator;