firebase-tools 10.1.3 → 10.2.1

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 (61) hide show
  1. package/lib/api.js +1 -0
  2. package/lib/apiv2.js +4 -0
  3. package/lib/auth.js +62 -25
  4. package/lib/commands/auth-import.js +1 -1
  5. package/lib/commands/ext-configure.js +1 -0
  6. package/lib/commands/ext-install.js +1 -0
  7. package/lib/commands/ext-uninstall.js +1 -0
  8. package/lib/commands/ext-update.js +1 -0
  9. package/lib/commands/functions-config-clone.js +1 -1
  10. package/lib/commands/functions-secrets-access.js +17 -0
  11. package/lib/commands/functions-secrets-destroy.js +40 -0
  12. package/lib/commands/functions-secrets-get.js +21 -0
  13. package/lib/commands/functions-secrets-prune.js +50 -0
  14. package/lib/commands/functions-secrets-set.js +46 -0
  15. package/lib/commands/index.js +7 -3
  16. package/lib/commands/login.js +1 -1
  17. package/lib/deploy/functions/backend.js +9 -1
  18. package/lib/deploy/functions/ensure.js +112 -0
  19. package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
  20. package/lib/deploy/functions/prepare.js +12 -18
  21. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +1 -0
  22. package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -0
  23. package/lib/deploy/functions/validate.js +56 -1
  24. package/lib/deploy/hosting/client.js +9 -0
  25. package/lib/deploy/hosting/convertConfig.js +6 -0
  26. package/lib/deploy/hosting/index.js +5 -5
  27. package/lib/deploy/hosting/prepare.js +25 -25
  28. package/lib/deploy/hosting/release.js +21 -24
  29. package/lib/emulator/commandUtils.js +5 -1
  30. package/lib/emulator/controller.js +3 -1
  31. package/lib/emulator/downloadableEmulators.js +29 -12
  32. package/lib/emulator/emulatorLogger.js +7 -0
  33. package/lib/emulator/functionsEmulator.js +122 -80
  34. package/lib/emulator/functionsEmulatorRuntime.js +100 -83
  35. package/lib/emulator/functionsEmulatorShared.js +51 -1
  36. package/lib/emulator/functionsEmulatorShell.js +1 -2
  37. package/lib/emulator/functionsRuntimeWorker.js +1 -1
  38. package/lib/emulator/storage/apis/gcloud.js +2 -2
  39. package/lib/emulator/storage/files.js +8 -3
  40. package/lib/extensions/askUserForParam.js +1 -1
  41. package/lib/extensions/diagnose.js +56 -0
  42. package/lib/extensions/extensionsHelper.js +10 -17
  43. package/lib/extensions/listExtensions.js +2 -0
  44. package/lib/extensions/resolveSource.js +1 -53
  45. package/lib/extensions/secretsUtils.js +1 -1
  46. package/lib/extensions/updateHelper.js +0 -14
  47. package/lib/extensions/utils.js +4 -2
  48. package/lib/functions/env.js +5 -7
  49. package/lib/functions/secrets.js +112 -0
  50. package/lib/gcp/cloudfunctions.js +2 -2
  51. package/lib/gcp/secretManager.js +128 -46
  52. package/lib/gcp/storage.js +5 -3
  53. package/lib/hosting/functionsProxy.js +15 -5
  54. package/lib/previews.js +1 -1
  55. package/lib/responseToError.js +16 -7
  56. package/lib/serve/functions.js +2 -2
  57. package/lib/serve/hosting.js +1 -1
  58. package/lib/utils.js +6 -1
  59. package/npm-shrinkwrap.json +124 -45
  60. package/package.json +4 -4
  61. package/schema/firebase-config.json +27 -0
@@ -2,20 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.inferDetailsFromExisting = exports.prepare = void 0;
4
4
  const clc = require("cli-color");
5
- const ensureCloudBuildEnabled_1 = require("./ensureCloudBuildEnabled");
6
- const functionsDeployHelper_1 = require("./functionsDeployHelper");
7
- const utils_1 = require("../../utils");
8
- const prepareFunctionsUpload_1 = require("./prepareFunctionsUpload");
9
- const prompts_1 = require("./prompts");
10
5
  const backend = require("./backend");
11
6
  const ensureApiEnabled = require("../../ensureApiEnabled");
12
7
  const functionsConfig = require("../../functionsConfig");
13
8
  const functionsEnv = require("../../functions/env");
14
- const previews_1 = require("../../previews");
15
- const projectUtils_1 = require("../../projectUtils");
16
- const track_1 = require("../../track");
17
9
  const runtimes = require("./runtimes");
18
10
  const validate = require("./validate");
11
+ const ensure = require("./ensure");
12
+ const functionsDeployHelper_1 = require("./functionsDeployHelper");
13
+ const utils_1 = require("../../utils");
14
+ const prepareFunctionsUpload_1 = require("./prepareFunctionsUpload");
15
+ const prompts_1 = require("./prompts");
16
+ const projectUtils_1 = require("../../projectUtils");
17
+ const track_1 = require("../../track");
19
18
  const logger_1 = require("../../logger");
20
19
  const triggerRegionHelper_1 = require("./triggerRegionHelper");
21
20
  const checkIam_1 = require("./checkIam");
@@ -24,14 +23,7 @@ function hasUserConfig(config) {
24
23
  return Object.keys(config).length > 1;
25
24
  }
26
25
  function hasDotenv(opts) {
27
- return previews_1.previews.dotenv && functionsEnv.hasUserEnvs(opts);
28
- }
29
- async function maybeEnableAR(projectId) {
30
- if (!previews_1.previews.artifactregistry) {
31
- return ensureApiEnabled.check(projectId, "artifactregistry.googleapis.com", "functions", true);
32
- }
33
- await ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions");
34
- return true;
26
+ return functionsEnv.hasUserEnvs(opts);
35
27
  }
36
28
  async function prepare(context, options, payload) {
37
29
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -54,8 +46,8 @@ async function prepare(context, options, payload) {
54
46
  const checkAPIsEnabled = await Promise.all([
55
47
  ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
56
48
  ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
57
- (0, ensureCloudBuildEnabled_1.ensureCloudBuildEnabled)(projectId),
58
- maybeEnableAR(projectId),
49
+ ensure.cloudBuildEnabled(projectId),
50
+ ensure.maybeEnableAR(projectId),
59
51
  ]);
60
52
  context.runtimeConfigEnabled = checkAPIsEnabled[1];
61
53
  context.artifactRegistryEnabled = checkAPIsEnabled[3];
@@ -125,6 +117,8 @@ async function prepare(context, options, payload) {
125
117
  await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
126
118
  await (0, prompts_1.promptForMinInstances)(options, matchingBackend, haveBackend);
127
119
  await backend.checkAvailability(context, wantBackend);
120
+ await validate.secretsAreValid(projectId, matchingBackend);
121
+ await ensure.secretAccess(projectId, matchingBackend, haveBackend);
128
122
  }
129
123
  exports.prepare = prepare;
130
124
  function inferDetailsFromExisting(want, have, usedDotenv) {
@@ -56,6 +56,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
56
56
  labels: "object",
57
57
  ingressSettings: "string",
58
58
  environmentVariables: "object",
59
+ secretEnvironmentVariables: "array",
59
60
  httpsTrigger: "object",
60
61
  eventTrigger: "object",
61
62
  scheduleTrigger: "object",
@@ -107,6 +107,18 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
107
107
  }
108
108
  endpoint.vpcConnector = maybeId;
109
109
  }
110
+ if (annotation.secrets) {
111
+ const secretEnvs = [];
112
+ for (const secret of annotation.secrets) {
113
+ const secretEnv = {
114
+ secret,
115
+ projectId,
116
+ key: secret,
117
+ };
118
+ secretEnvs.push(secretEnv);
119
+ }
120
+ endpoint.secretEnvironmentVariables = secretEnvs;
121
+ }
110
122
  proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "vpcConnectorEgressSettings", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
111
123
  want.endpoints[region] = want.endpoints[region] || {};
112
124
  want.endpoints[region][endpoint.id] = endpoint;
@@ -1,11 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreValid = void 0;
3
+ exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreValid = void 0;
4
4
  const path = require("path");
5
5
  const clc = require("cli-color");
6
6
  const error_1 = require("../../error");
7
+ const secretManager_1 = require("../../gcp/secretManager");
8
+ const logger_1 = require("../../logger");
7
9
  const fsutils = require("../../fsutils");
8
10
  const backend = require("./backend");
11
+ const utils = require("../../utils");
12
+ const secrets = require("../../functions/secrets");
9
13
  function endpointsAreValid(wantBackend) {
10
14
  functionIdsAreValid(backend.allEndpoints(wantBackend));
11
15
  const gcfV1WithConcurrency = backend
@@ -62,3 +66,54 @@ function functionIdsAreValid(functions) {
62
66
  }
63
67
  }
64
68
  exports.functionIdsAreValid = functionIdsAreValid;
69
+ async function secretsAreValid(projectId, wantBackend) {
70
+ const endpoints = backend
71
+ .allEndpoints(wantBackend)
72
+ .filter((e) => e.secretEnvironmentVariables && e.secretEnvironmentVariables.length > 0);
73
+ validatePlatformTargets(endpoints);
74
+ await validateSecretVersions(projectId, endpoints);
75
+ }
76
+ exports.secretsAreValid = secretsAreValid;
77
+ function validatePlatformTargets(endpoints) {
78
+ const supportedPlatforms = ["gcfv1"];
79
+ const unsupported = endpoints.filter((e) => !supportedPlatforms.includes(e.platform));
80
+ if (unsupported.length > 0) {
81
+ const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`);
82
+ throw new error_1.FirebaseError(`Tried to set secret environment variables on ${errs.join(", ")}. ` +
83
+ `Only ${supportedPlatforms.join(", ")} support secret environments.`);
84
+ }
85
+ }
86
+ async function validateSecretVersions(projectId, endpoints) {
87
+ const toResolve = new Set();
88
+ for (const s of secrets.of(endpoints)) {
89
+ toResolve.add(s.secret);
90
+ }
91
+ const results = await utils.allSettled(Array.from(toResolve).map(async (secret) => {
92
+ const sv = await (0, secretManager_1.getSecretVersion)(projectId, secret, "latest");
93
+ logger_1.logger.debug(`Resolved secret version of ${clc.bold(secret)} to ${clc.bold(sv.versionId)}.`);
94
+ return sv;
95
+ }));
96
+ const secretVersions = {};
97
+ const errs = [];
98
+ for (const result of results) {
99
+ if (result.status === "fulfilled") {
100
+ const sv = result.value;
101
+ if (sv.state != "ENABLED") {
102
+ errs.push(new error_1.FirebaseError(`Expected secret ${sv.secret.name}@${sv.versionId} to be in state ENABLED not ${sv.state}.`));
103
+ }
104
+ secretVersions[sv.secret.name] = sv;
105
+ }
106
+ else {
107
+ errs.push(new error_1.FirebaseError(result.reason.message));
108
+ }
109
+ }
110
+ if (errs.length) {
111
+ throw new error_1.FirebaseError("Failed to validate secret versions", { children: errs });
112
+ }
113
+ for (const s of secrets.of(endpoints)) {
114
+ s.version = secretVersions[s.secret].versionId;
115
+ if (!s.version) {
116
+ throw new error_1.FirebaseError("Secret version is unexpectedly undefined. This should never happen.");
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.client = void 0;
4
+ const api_1 = require("../../api");
5
+ const apiv2_1 = require("../../apiv2");
6
+ exports.client = new apiv2_1.Client({
7
+ urlPrefix: api_1.hostingApiOrigin,
8
+ apiVersion: "v1beta1",
9
+ });
@@ -46,6 +46,12 @@ function convertConfig(config) {
46
46
  }
47
47
  else if ("function" in rewrite) {
48
48
  vRewrite.function = rewrite.function;
49
+ if (rewrite.region) {
50
+ vRewrite.functionRegion = rewrite.region;
51
+ }
52
+ else {
53
+ vRewrite.functionRegion = "us-central1";
54
+ }
49
55
  }
50
56
  else if ("dynamicLinks" in rewrite) {
51
57
  vRewrite.dynamicLinks = rewrite.dynamicLinks;
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.release = exports.deploy = exports.prepare = void 0;
4
- const prepare = require("./prepare");
5
- exports.prepare = prepare;
6
- const deploy_1 = require("./deploy");
4
+ var prepare_1 = require("./prepare");
5
+ Object.defineProperty(exports, "prepare", { enumerable: true, get: function () { return prepare_1.prepare; } });
6
+ var deploy_1 = require("./deploy");
7
7
  Object.defineProperty(exports, "deploy", { enumerable: true, get: function () { return deploy_1.deploy; } });
8
- const release = require("./release");
9
- exports.release = release;
8
+ var release_1 = require("./release");
9
+ Object.defineProperty(exports, "release", { enumerable: true, get: function () { return release_1.release; } });
@@ -1,44 +1,44 @@
1
1
  "use strict";
2
- const _ = require("lodash");
3
- const api = require("../../api");
4
- const { convertConfig } = require("./convertConfig");
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepare = void 0;
4
+ const error_1 = require("../../error");
5
+ const client_1 = require("./client");
6
+ const projectUtils_1 = require("../../projectUtils");
7
+ const normalizedHostingConfigs_1 = require("../../hosting/normalizedHostingConfigs");
8
+ const validate_1 = require("./validate");
9
+ const convertConfig_1 = require("./convertConfig");
5
10
  const deploymentTool = require("../../deploymentTool");
6
- const { FirebaseError } = require("../../error");
7
- const { normalizedHostingConfigs } = require("../../hosting/normalizedHostingConfigs");
8
- const { validateDeploy } = require("./validate");
9
- module.exports = function (context, options) {
11
+ async function prepare(context, options) {
10
12
  if (options.public) {
11
- if (_.isArray(options.config.get("hosting"))) {
12
- throw new FirebaseError("Cannot specify --public option with multi-site configuration.");
13
+ if (Array.isArray(options.config.get("hosting"))) {
14
+ throw new error_1.FirebaseError("Cannot specify --public option with multi-site configuration.");
13
15
  }
14
16
  options.config.set("hosting.public", options.public);
15
17
  }
16
- const configs = normalizedHostingConfigs(options, { resolveTargets: true });
18
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
19
+ const configs = (0, normalizedHostingConfigs_1.normalizedHostingConfigs)(options, { resolveTargets: true });
17
20
  if (configs.length === 0) {
18
21
  return Promise.resolve();
19
22
  }
20
23
  context.hosting = {
21
- deploys: configs.map(function (cfg) {
24
+ deploys: configs.map((cfg) => {
22
25
  return { config: cfg, site: cfg.site };
23
26
  }),
24
27
  };
25
28
  const versionCreates = [];
26
- _.each(context.hosting.deploys, function (deploy) {
29
+ for (const deploy of context.hosting.deploys) {
27
30
  const cfg = deploy.config;
28
- validateDeploy(deploy, options);
31
+ (0, validate_1.validateDeploy)(deploy, options);
29
32
  const data = {
30
- config: convertConfig(cfg),
33
+ config: (0, convertConfig_1.convertConfig)(cfg),
31
34
  labels: deploymentTool.labels(),
32
35
  };
33
- versionCreates.push(api
34
- .request("POST", `/v1beta1/sites/${deploy.site}/versions`, {
35
- origin: api.hostingApiOrigin,
36
- auth: true,
37
- data,
38
- })
39
- .then(function (result) {
40
- deploy.version = result.body.name;
36
+ versionCreates.push(client_1.client
37
+ .post(`/projects/${projectNumber}/sites/${deploy.site}/versions`, data)
38
+ .then((res) => {
39
+ deploy.version = res.body.name;
41
40
  }));
42
- });
43
- return Promise.all(versionCreates);
44
- };
41
+ }
42
+ await Promise.all(versionCreates);
43
+ }
44
+ exports.prepare = prepare;
@@ -1,34 +1,31 @@
1
1
  "use strict";
2
- const api = require("../../api");
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.release = void 0;
4
+ const client_1 = require("./client");
5
+ const logger_1 = require("../../logger");
6
+ const projectUtils_1 = require("../../projectUtils");
3
7
  const utils = require("../../utils");
4
- const { logger } = require("../../logger");
5
- module.exports = function (context, options) {
8
+ async function release(context, options) {
6
9
  if (!context.hosting || !context.hosting.deploys) {
7
- return Promise.resolve();
10
+ return;
8
11
  }
9
- logger.debug(JSON.stringify(context.hosting.deploys, null, 2));
10
- return Promise.all(context.hosting.deploys.map(async function (deploy) {
11
- utils.logLabeledBullet("hosting[" + deploy.site + "]", "finalizing version...");
12
- const finalizeResult = await api.request("PATCH", `/v1beta1/${deploy.version}?updateMask=status`, {
13
- origin: api.hostingApiOrigin,
14
- auth: true,
15
- data: { status: "FINALIZED" },
16
- });
17
- logger.debug("[hosting] finalized version for " + deploy.site + ":", finalizeResult.body);
18
- utils.logLabeledSuccess("hosting[" + deploy.site + "]", "version finalized");
19
- utils.logLabeledBullet("hosting[" + deploy.site + "]", "releasing new version...");
12
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
13
+ logger_1.logger.debug(JSON.stringify(context.hosting.deploys, null, 2));
14
+ await Promise.all(context.hosting.deploys.map(async (deploy) => {
15
+ utils.logLabeledBullet(`hosting[${deploy.site}]`, "finalizing version...");
16
+ const finalizeResult = await client_1.client.patch(`/${deploy.version}`, { status: "FINALIZED" }, { queryParams: { updateMask: "status" } });
17
+ logger_1.logger.debug(`[hosting] finalized version for ${deploy.site}:${finalizeResult.body}`);
18
+ utils.logLabeledSuccess(`hosting[${deploy.site}]`, "version finalized");
19
+ utils.logLabeledBullet(`hosting[${deploy.site}]`, "releasing new version...");
20
20
  const channelSegment = context.hostingChannel && context.hostingChannel !== "live"
21
21
  ? `/channels/${context.hostingChannel}`
22
22
  : "";
23
23
  if (channelSegment) {
24
- logger.debug("[hosting] releasing to channel:", context.hostingChannel);
24
+ logger_1.logger.debug("[hosting] releasing to channel:", context.hostingChannel);
25
25
  }
26
- const releaseResult = await api.request("POST", `/v1beta1/sites/${deploy.site}${channelSegment}/releases?version_name=${deploy.version}`, {
27
- auth: true,
28
- origin: api.hostingApiOrigin,
29
- data: { message: options.message || null },
30
- });
31
- logger.debug("[hosting] release:", releaseResult.body);
32
- utils.logLabeledSuccess("hosting[" + deploy.site + "]", "release complete");
26
+ const releaseResult = await client_1.client.post(`/projects/${projectNumber}/sites/${deploy.site}${channelSegment}/releases`, { message: options.message || null }, { queryParams: { versionName: deploy.version } });
27
+ logger_1.logger.debug("[hosting] release:", releaseResult.body);
28
+ utils.logLabeledSuccess(`hosting[${deploy.site}]`, "release complete");
33
29
  }));
34
- };
30
+ }
31
+ exports.release = release;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.emulatorExec = exports.shutdownWhenKilled = exports.setExportOnExitOptions = exports.parseInspectionPort = exports.beforeEmulatorCommand = exports.warnEmulatorNotSupported = exports.printNoticeIfEmulated = exports.DESC_TEST_PARAMS = exports.FLAG_TEST_PARAMS = exports.DESC_TEST_CONFIG = exports.FLAG_TEST_CONFIG = exports.DESC_UI = exports.FLAG_UI = exports.EXPORT_ON_EXIT_USAGE_ERROR = exports.DESC_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT_NAME = exports.DESC_IMPORT = exports.FLAG_IMPORT = exports.DESC_INSPECT_FUNCTIONS = exports.FLAG_INSPECT_FUNCTIONS = exports.DESC_ONLY = exports.FLAG_ONLY = void 0;
3
+ exports.emulatorExec = exports.shutdownWhenKilled = exports.setExportOnExitOptions = exports.parseInspectionPort = exports.beforeEmulatorCommand = exports.warnEmulatorNotSupported = exports.printNoticeIfEmulated = exports.DESC_TEST_PARAMS = exports.FLAG_TEST_PARAMS = exports.DESC_TEST_CONFIG = exports.FLAG_TEST_CONFIG = exports.DESC_UI = exports.FLAG_UI = exports.EXPORT_ON_EXIT_CWD_DANGER = exports.EXPORT_ON_EXIT_USAGE_ERROR = exports.DESC_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT_NAME = exports.DESC_IMPORT = exports.FLAG_IMPORT = exports.DESC_INSPECT_FUNCTIONS = exports.FLAG_INSPECT_FUNCTIONS = exports.DESC_ONLY = exports.FLAG_ONLY = void 0;
4
4
  const clc = require("cli-color");
5
5
  const childProcess = require("child_process");
6
6
  const controller = require("../emulator/controller");
@@ -36,6 +36,7 @@ exports.DESC_EXPORT_ON_EXIT = "automatically export emulator data (emulators:exp
36
36
  `when no dir is provided the location of ${exports.FLAG_IMPORT} is used`;
37
37
  exports.EXPORT_ON_EXIT_USAGE_ERROR = `"${exports.FLAG_EXPORT_ON_EXIT_NAME}" must be used with "${exports.FLAG_IMPORT}"` +
38
38
  ` or provide a dir directly to "${exports.FLAG_EXPORT_ON_EXIT}"`;
39
+ exports.EXPORT_ON_EXIT_CWD_DANGER = `"${exports.FLAG_EXPORT_ON_EXIT_NAME}" must not point to the current directory or parents. Please choose a new/dedicated directory for exports.`;
39
40
  exports.FLAG_UI = "--ui";
40
41
  exports.DESC_UI = "run the Emulator UI";
41
42
  exports.FLAG_TEST_CONFIG = "--test-config <firebase.json file>";
@@ -131,6 +132,9 @@ function setExportOnExitOptions(options) {
131
132
  if (options.exportOnExit === true || !options.exportOnExit) {
132
133
  throw new error_1.FirebaseError(exports.EXPORT_ON_EXIT_USAGE_ERROR);
133
134
  }
135
+ if (path.resolve(".").startsWith(path.resolve(options.exportOnExit))) {
136
+ throw new error_1.FirebaseError(exports.EXPORT_ON_EXIT_CWD_DANGER);
137
+ }
134
138
  }
135
139
  return;
136
140
  }
@@ -246,7 +246,8 @@ async function startAll(options, showUI = true) {
246
246
  utils.assertDefined(options.config.src.functions);
247
247
  utils.assertDefined(options.config.src.functions.source, "Error: 'functions.source' is not defined");
248
248
  utils.assertIsStringOrUndefined(options.extensionDir);
249
- const functionsDir = path.join(options.extensionDir || options.config.projectDir, options.config.src.functions.source);
249
+ const projectDir = options.extensionDir || options.config.projectDir;
250
+ const functionsDir = path.join(projectDir, options.config.src.functions.source);
250
251
  let inspectFunctions;
251
252
  if (options.inspectFunctions) {
252
253
  inspectFunctions = commandUtils.parseInspectionPort(options);
@@ -269,6 +270,7 @@ async function startAll(options, showUI = true) {
269
270
  ];
270
271
  const functionsEmulator = new functionsEmulator_1.FunctionsEmulator({
271
272
  projectId,
273
+ projectDir,
272
274
  emulatableBackends,
273
275
  account,
274
276
  host: functionsAddr.host,
@@ -13,6 +13,7 @@ const path = require("path");
13
13
  const os = require("os");
14
14
  const registry_1 = require("./registry");
15
15
  const download_1 = require("../emulator/download");
16
+ const previews_1 = require("../previews");
16
17
  const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000;
17
18
  const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators");
18
19
  exports.DownloadDetails = {
@@ -49,19 +50,35 @@ exports.DownloadDetails = {
49
50
  namePrefix: "cloud-storage-rules-emulator",
50
51
  },
51
52
  },
52
- ui: {
53
- version: "1.6.4",
54
- downloadPath: path.join(CACHE_DIR, "ui-v1.6.4.zip"),
55
- unzipDir: path.join(CACHE_DIR, "ui-v1.6.4"),
56
- binaryPath: path.join(CACHE_DIR, "ui-v1.6.4", "server.bundle.js"),
57
- opts: {
58
- cacheDir: CACHE_DIR,
59
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.4.zip",
60
- expectedSize: 3757300,
61
- expectedChecksum: "20d4ee71e4ff7527b1843b6a8636142e",
62
- namePrefix: "ui",
53
+ ui: previews_1.previews.emulatoruisnapshot
54
+ ? {
55
+ version: "SNAPSHOT",
56
+ downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
57
+ unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
58
+ binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.bundle.js"),
59
+ opts: {
60
+ cacheDir: CACHE_DIR,
61
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
62
+ expectedSize: -1,
63
+ expectedChecksum: "",
64
+ skipCache: true,
65
+ skipChecksumAndSize: true,
66
+ namePrefix: "ui",
67
+ },
68
+ }
69
+ : {
70
+ version: "1.6.5",
71
+ downloadPath: path.join(CACHE_DIR, "ui-v1.6.5.zip"),
72
+ unzipDir: path.join(CACHE_DIR, "ui-v1.6.5"),
73
+ binaryPath: path.join(CACHE_DIR, "ui-v1.6.5", "server.bundle.js"),
74
+ opts: {
75
+ cacheDir: CACHE_DIR,
76
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.5.zip",
77
+ expectedSize: 3816994,
78
+ expectedChecksum: "92dfff4b2ef8ab616e8a60cc93e0a00b",
79
+ namePrefix: "ui",
80
+ },
63
81
  },
64
- },
65
82
  pubsub: {
66
83
  downloadPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0.zip"),
67
84
  version: "0.1.0",
@@ -13,6 +13,7 @@ const TYPE_VERBOSITY = {
13
13
  USER: 2,
14
14
  WARN: 2,
15
15
  WARN_ONCE: 2,
16
+ ERROR: 2,
16
17
  };
17
18
  var Verbosity;
18
19
  (function (Verbosity) {
@@ -79,6 +80,9 @@ class EmulatorLogger {
79
80
  case "SUCCESS":
80
81
  utils.logSuccess(text, "info", mergedData);
81
82
  break;
83
+ case "ERROR":
84
+ utils.logBullet(text, "error", mergedData);
85
+ break;
82
86
  }
83
87
  }
84
88
  handleRuntimeLog(log, ignore = []) {
@@ -196,6 +200,9 @@ You can probably fix this by running "npm install ${systemLog.data.name}@latest"
196
200
  EmulatorLogger.warnOnceCache.add(text);
197
201
  }
198
202
  break;
203
+ case "ERROR":
204
+ utils.logLabeledError(label, text, "error", mergedData);
205
+ break;
199
206
  }
200
207
  }
201
208
  static shouldSupress(type) {