firebase-tools 13.24.2 → 13.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/lib/appdistribution/client.js +62 -8
  2. package/lib/appdistribution/distribution.js +1 -1
  3. package/lib/apphosting/backend.js +4 -4
  4. package/lib/apphosting/config.js +2 -10
  5. package/lib/apphosting/secrets/index.js +7 -7
  6. package/lib/archiveDirectory.js +1 -1
  7. package/lib/auth.js +5 -3
  8. package/lib/command.js +9 -1
  9. package/lib/commands/appdistribution-distribute.js +4 -4
  10. package/lib/commands/{appdistribution-group-create.js → appdistribution-groups-create.js} +2 -1
  11. package/lib/commands/{appdistribution-group-delete.js → appdistribution-groups-delete.js} +3 -2
  12. package/lib/commands/appdistribution-groups-list.js +56 -0
  13. package/lib/commands/appdistribution-testers-list.js +54 -0
  14. package/lib/commands/appdistribution-testers-remove.js +1 -1
  15. package/lib/commands/apphosting-backends-delete.js +3 -1
  16. package/lib/commands/apphosting-backends-get.js +1 -1
  17. package/lib/commands/database-import.js +4 -2
  18. package/lib/commands/database-push.js +4 -2
  19. package/lib/commands/database-set.js +4 -2
  20. package/lib/commands/database-settings-get.js +1 -1
  21. package/lib/commands/database-settings-set.js +1 -1
  22. package/lib/commands/ext-dev-init.js +2 -2
  23. package/lib/commands/ext-dev-list.js +1 -1
  24. package/lib/commands/ext-dev-register.js +2 -2
  25. package/lib/commands/ext-dev-upload.js +2 -2
  26. package/lib/commands/ext-dev-usage.js +2 -2
  27. package/lib/commands/ext-install.js +2 -2
  28. package/lib/commands/index.js +5 -2
  29. package/lib/commands/use.js +1 -1
  30. package/lib/deploy/extensions/deploy.js +3 -1
  31. package/lib/deploy/extensions/deploymentSummary.js +4 -1
  32. package/lib/deploy/extensions/planner.js +14 -3
  33. package/lib/deploy/extensions/prepare.js +9 -9
  34. package/lib/deploy/functions/ensure.js +1 -1
  35. package/lib/deploy/lifecycleHooks.js +2 -1
  36. package/lib/emulator/apphosting/config.js +13 -3
  37. package/lib/emulator/apphosting/index.js +1 -0
  38. package/lib/emulator/apphosting/serve.js +9 -7
  39. package/lib/emulator/controller.js +3 -2
  40. package/lib/emulator/dataconnectEmulator.js +13 -12
  41. package/lib/emulator/downloadableEmulators.js +11 -11
  42. package/lib/emulator/emulatorLogger.js +3 -0
  43. package/lib/emulator/hub.js +10 -7
  44. package/lib/emulator/tasksEmulator.js +5 -2
  45. package/lib/emulator/ui.js +47 -25
  46. package/lib/error.js +8 -1
  47. package/lib/getProjectNumber.js +1 -1
  48. package/lib/init/features/genkit/index.js +417 -0
  49. package/lib/init/features/project.js +7 -6
  50. package/lib/logger.js +2 -2
  51. package/lib/management/projects.js +24 -4
  52. package/lib/projectUtils.js +1 -1
  53. package/lib/requireDatabaseInstance.js +1 -1
  54. package/lib/requirePermissions.js +1 -1
  55. package/lib/rulesDeploy.js +1 -1
  56. package/lib/templates.js +2 -2
  57. package/lib/utils.js +5 -8
  58. package/lib/vsCodeUtils.js +8 -0
  59. package/package.json +2 -2
  60. package/schema/firebase-config.json +3 -0
  61. package/templates/genkit/firebase.0.9.0.template +57 -0
  62. package/lib/init/features/genkit.js +0 -54
@@ -44,13 +44,13 @@ async function uploadExtensionAction(extensionRef, options) {
44
44
  profile = await (0, publisherApi_1.getPublisherProfile)("-", publisherId);
45
45
  }
46
46
  catch (err) {
47
- if (err.status === 404) {
47
+ if ((0, error_1.getErrStatus)(err) === 404) {
48
48
  throw (0, extensionsHelper_1.getMissingPublisherError)(publisherId);
49
49
  }
50
50
  throw err;
51
51
  }
52
52
  const projectNumber = `${(0, extensionsHelper_2.getPublisherProjectFromName)(profile.name)}`;
53
- const { projectId } = await (0, projects_1.getFirebaseProject)(projectNumber);
53
+ const { projectId } = await (0, projects_1.getProject)(projectNumber);
54
54
  await (0, tos_1.acceptLatestPublisherTOS)(options, projectNumber);
55
55
  let res;
56
56
  if (options.local) {
@@ -36,7 +36,7 @@ exports.command = new command_1.Command("ext:dev:usage <publisherId>")
36
36
  extensions = await (0, publisherApi_1.listExtensions)(publisherId);
37
37
  }
38
38
  catch (err) {
39
- throw new error_1.FirebaseError(err);
39
+ throw new error_1.FirebaseError((0, error_1.getErrMsg)(err));
40
40
  }
41
41
  if (extensions.length < 1) {
42
42
  throw new error_1.FirebaseError(`There are no published extensions associated with publisher ID ${clc.bold(publisherId)}. This could happen for two reasons:\n` +
@@ -77,7 +77,7 @@ exports.command = new command_1.Command("ext:dev:usage <publisherId>")
77
77
  }
78
78
  catch (err) {
79
79
  throw new error_1.FirebaseError(`Error occurred when fetching usage data for extension ${extensionName}`, {
80
- original: err,
80
+ original: (0, error_1.getError)(err),
81
81
  });
82
82
  }
83
83
  if (!response) {
@@ -110,8 +110,8 @@ exports.command = new command_1.Command("ext:install [extensionRef]")
110
110
  }
111
111
  catch (err) {
112
112
  if (!(err instanceof error_1.FirebaseError)) {
113
- throw new error_1.FirebaseError(`Error occurred saving the extension to manifest: ${err.message}`, {
114
- original: err,
113
+ throw new error_1.FirebaseError(`Error occurred saving the extension to manifest: ${(0, error_1.getErrMsg)(err)}`, {
114
+ original: (0, error_1.getError)(err),
115
115
  });
116
116
  }
117
117
  throw err;
@@ -17,11 +17,14 @@ function load(client) {
17
17
  client.appdistribution = {};
18
18
  client.appdistribution.distribute = loadCommand("appdistribution-distribute");
19
19
  client.appdistribution.testers = {};
20
+ client.appdistribution.testers.list = loadCommand("appdistribution-testers-list");
20
21
  client.appdistribution.testers.add = loadCommand("appdistribution-testers-add");
21
22
  client.appdistribution.testers.delete = loadCommand("appdistribution-testers-remove");
22
23
  client.appdistribution.group = {};
23
- client.appdistribution.group.create = loadCommand("appdistribution-group-create");
24
- client.appdistribution.group.delete = loadCommand("appdistribution-group-delete");
24
+ client.appdistribution.group.list = loadCommand("appdistribution-groups-list");
25
+ client.appdistribution.group.create = loadCommand("appdistribution-groups-create");
26
+ client.appdistribution.group.delete = loadCommand("appdistribution-groups-delete");
27
+ client.appdistribution.groups = client.appdistribution.group;
25
28
  client.apps = {};
26
29
  client.apps.create = loadCommand("apps-create");
27
30
  client.apps.list = loadCommand("apps-list");
@@ -53,7 +53,7 @@ exports.command = new command_1.Command("use [alias_or_project_id]")
53
53
  const hasAlias = options.rc.hasProjectAlias(newActive);
54
54
  const resolvedProject = options.rc.resolveAlias(newActive);
55
55
  (0, command_2.validateProjectId)(resolvedProject);
56
- return (0, projects_1.getFirebaseProject)(resolvedProject)
56
+ return (0, projects_1.getProject)(resolvedProject)
57
57
  .then((foundProject) => {
58
58
  project = foundProject;
59
59
  })
@@ -18,7 +18,9 @@ async function deploy(context, options, payload) {
18
18
  ...((_b = payload.instancesToUpdate) !== null && _b !== void 0 ? _b : []),
19
19
  ...((_c = payload.instancesToConfigure) !== null && _c !== void 0 ? _c : []),
20
20
  ]);
21
- await (0, secrets_1.handleSecretParams)(payload, context.have, options.nonInteractive);
21
+ if (context.have) {
22
+ await (0, secrets_1.handleSecretParams)(payload, context.have, options.nonInteractive);
23
+ }
22
24
  const errorHandler = new errors_1.ErrorHandler();
23
25
  const validationQueue = new queue_1.default({
24
26
  retries: 5,
@@ -11,7 +11,7 @@ const humanReadableUpdate = (from, to) => {
11
11
  to.ref &&
12
12
  from.ref.publisherId === to.ref.publisherId &&
13
13
  from.ref.extensionId === to.ref.extensionId) {
14
- return `\t${clc.bold(from.instanceId)} (${refs.toExtensionVersionRef(from.ref)} => ${(_a = to.ref) === null || _a === void 0 ? void 0 : _a.version})`;
14
+ return `\t${clc.bold(from.instanceId)} (${refs.toExtensionVersionRef(from.ref)} => ${((_a = to.ref) === null || _a === void 0 ? void 0 : _a.version) || ""})`;
15
15
  }
16
16
  else {
17
17
  const fromRef = from.ref
@@ -32,6 +32,9 @@ function updatesSummary(toUpdate, have) {
32
32
  const instancesToUpdate = toUpdate
33
33
  .map((to) => {
34
34
  const from = have.find((exists) => exists.instanceId === to.instanceId);
35
+ if (!from) {
36
+ return "";
37
+ }
35
38
  return humanReadableUpdate(from, to);
36
39
  })
37
40
  .join("\n");
@@ -46,6 +46,9 @@ async function getExtensionSpec(i) {
46
46
  throw new error_1.FirebaseError("InstanceSpec had no ref or localPath, unable to get extensionSpec");
47
47
  }
48
48
  }
49
+ if (!i.extensionSpec) {
50
+ throw new error_1.FirebaseError("Internal error getting extension");
51
+ }
49
52
  return i.extensionSpec;
50
53
  }
51
54
  exports.getExtensionSpec = getExtensionSpec;
@@ -54,8 +57,12 @@ async function haveDynamic(projectId) {
54
57
  .filter((i) => { var _a; return ((_a = i.labels) === null || _a === void 0 ? void 0 : _a.createdBy) === "SDK"; })
55
58
  .map((i) => {
56
59
  var _a;
60
+ const instanceId = i.name.split("/").pop();
61
+ if (!instanceId) {
62
+ throw new error_1.FirebaseError(`Internal error getting instanceId from ${i.name}`);
63
+ }
57
64
  const dep = {
58
- instanceId: i.name.split("/").pop(),
65
+ instanceId,
59
66
  params: i.config.params,
60
67
  systemParams: (_a = i.config.systemParams) !== null && _a !== void 0 ? _a : {},
61
68
  allowedEventTypes: i.config.allowedEventTypes,
@@ -77,8 +84,12 @@ async function have(projectId) {
77
84
  .filter((i) => { var _a; return !(((_a = i.labels) === null || _a === void 0 ? void 0 : _a.createdBy) === "SDK"); })
78
85
  .map((i) => {
79
86
  var _a;
87
+ const instanceId = i.name.split("/").pop();
88
+ if (!instanceId) {
89
+ throw new error_1.FirebaseError(`Internal error getting instanceId from ${i.name}`);
90
+ }
80
91
  const dep = {
81
- instanceId: i.name.split("/").pop(),
92
+ instanceId,
82
93
  params: i.config.params,
83
94
  systemParams: (_a = i.config.systemParams) !== null && _a !== void 0 ? _a : {},
84
95
  allowedEventTypes: i.config.allowedEventTypes,
@@ -196,7 +207,7 @@ async function want(args) {
196
207
  }
197
208
  }
198
209
  catch (err) {
199
- logger_1.logger.debug(`Got error reading extensions entry ${e}: ${err}`);
210
+ logger_1.logger.debug(`Got error reading extensions entry ${e[0]} (${e[1]}): ${(0, error_1.getErrMsg)(err)}`);
200
211
  errors.push(err);
201
212
  }
202
213
  }
@@ -18,6 +18,15 @@ const v2FunctionHelper_1 = require("./v2FunctionHelper");
18
18
  const tos_1 = require("../../extensions/tos");
19
19
  const common_1 = require("../../extensions/runtimes/common");
20
20
  const functionsDeployHelper_1 = require("../functions/functionsDeployHelper");
21
+ const matchesInstanceId = (dep) => (test) => {
22
+ return dep.instanceId === test.instanceId;
23
+ };
24
+ const isUpdate = (dep) => (test) => {
25
+ return dep.instanceId === test.instanceId && !refs.equal(dep.ref, test.ref);
26
+ };
27
+ const isConfigure = (dep) => (test) => {
28
+ return dep.instanceId === test.instanceId && refs.equal(dep.ref, test.ref);
29
+ };
21
30
  async function prepareHelper(context, options, payload, wantExtensions, haveExtensions, isDynamic) {
22
31
  var _a, _b;
23
32
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -146,12 +155,3 @@ async function prepare(context, options, payload) {
146
155
  return prepareHelper(context, options, payload, wantExtensions, haveExtensions, false);
147
156
  }
148
157
  exports.prepare = prepare;
149
- const matchesInstanceId = (dep) => (test) => {
150
- return dep.instanceId === test.instanceId;
151
- };
152
- const isUpdate = (dep) => (test) => {
153
- return dep.instanceId === test.instanceId && !refs.equal(dep.ref, test.ref);
154
- };
155
- const isConfigure = (dep) => (test) => {
156
- return dep.instanceId === test.instanceId && refs.equal(dep.ref, test.ref);
157
- };
@@ -15,7 +15,7 @@ const metadataCallCache = new Map();
15
15
  async function defaultServiceAccount(e) {
16
16
  let metadataCall = metadataCallCache.get(e.project);
17
17
  if (!metadataCall) {
18
- metadataCall = (0, projects_1.getFirebaseProject)(e.project);
18
+ metadataCall = (0, projects_1.getProject)(e.project);
19
19
  metadataCallCache.set(e.project, metadataCall);
20
20
  }
21
21
  const metadata = await metadataCall;
@@ -8,9 +8,10 @@ const error_1 = require("../error");
8
8
  const needProjectId = require("../projectUtils").needProjectId;
9
9
  const logger_1 = require("../logger");
10
10
  const path = require("path");
11
+ const vsCodeUtils_1 = require("../vsCodeUtils");
11
12
  function runCommand(command, childOptions) {
12
13
  const escapedCommand = command.replace(/\"/g, '\\"');
13
- const isVSCode = utils.isVSCodeExtension();
14
+ const isVSCode = (0, vsCodeUtils_1.isVSCodeExtension)();
14
15
  const nodeExecutable = isVSCode ? "node" : process.execPath;
15
16
  const crossEnvShellPath = isVSCode
16
17
  ? path.resolve(__dirname, "./cross-env/dist/bin/cross-env-shell.js")
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getLocalAppHostingConfiguration = void 0;
4
4
  const path_1 = require("path");
5
5
  const config_1 = require("../../apphosting/config");
6
- async function getLocalAppHostingConfiguration(cwd) {
7
- const appHostingConfigPaths = (0, config_1.discoverConfigsAtBackendRoot)(cwd);
6
+ const yaml_1 = require("../../apphosting/yaml");
7
+ async function getLocalAppHostingConfiguration(backendDir) {
8
+ const appHostingConfigPaths = (0, config_1.listAppHostingFilesInPath)(backendDir);
8
9
  const fileNameToPathMap = new Map();
9
10
  for (const path of appHostingConfigPaths) {
10
11
  const fileName = (0, path_1.basename)(path);
@@ -12,6 +13,15 @@ async function getLocalAppHostingConfiguration(cwd) {
12
13
  }
13
14
  const baseFilePath = fileNameToPathMap.get(config_1.APPHOSTING_BASE_YAML_FILE);
14
15
  const localFilePath = fileNameToPathMap.get(config_1.APPHOSTING_LOCAL_YAML_FILE);
15
- return await (0, config_1.loadConfigForEnvironment)(localFilePath !== null && localFilePath !== void 0 ? localFilePath : baseFilePath, baseFilePath);
16
+ if (!baseFilePath && !localFilePath) {
17
+ return yaml_1.AppHostingYamlConfig.empty();
18
+ }
19
+ if (!baseFilePath || !localFilePath) {
20
+ return await yaml_1.AppHostingYamlConfig.loadFromFile((baseFilePath || localFilePath));
21
+ }
22
+ const localYamlConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localFilePath);
23
+ const baseConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(baseFilePath);
24
+ baseConfig.merge(localYamlConfig);
25
+ return baseConfig;
16
26
  }
17
27
  exports.getLocalAppHostingConfiguration = getLocalAppHostingConfiguration;
@@ -11,6 +11,7 @@ class AppHostingEmulator {
11
11
  async start() {
12
12
  const { hostname, port } = await (0, serve_1.start)({
13
13
  startCommand: this.args.startCommandOverride,
14
+ rootDirectory: this.args.rootDirectory,
14
15
  });
15
16
  this.args.options.host = hostname;
16
17
  this.args.options.port = port;
@@ -9,19 +9,21 @@ const spawn_1 = require("../../init/spawn");
9
9
  const utils_2 = require("./utils");
10
10
  const types_1 = require("../types");
11
11
  const config_1 = require("./config");
12
+ const projectPath_1 = require("../../projectPath");
12
13
  async function start(options) {
13
14
  const hostname = constants_1.DEFAULT_HOST;
14
15
  let port = constants_1.DEFAULT_PORTS.apphosting;
15
16
  while (!(await availablePort(hostname, port))) {
16
17
  port += 1;
17
18
  }
18
- serve(port, options === null || options === void 0 ? void 0 : options.startCommand);
19
+ serve(port, options === null || options === void 0 ? void 0 : options.startCommand, options === null || options === void 0 ? void 0 : options.rootDirectory);
19
20
  return { hostname, port };
20
21
  }
21
22
  exports.start = start;
22
- async function serve(port, startCommand) {
23
- const rootDir = process.cwd();
24
- const apphostingLocalConfig = await (0, config_1.getLocalAppHostingConfiguration)(rootDir);
23
+ async function serve(port, startCommand, backendRelativeDir) {
24
+ backendRelativeDir = backendRelativeDir !== null && backendRelativeDir !== void 0 ? backendRelativeDir : "./";
25
+ const backendRoot = (0, projectPath_1.resolveProjectPath)({}, backendRelativeDir);
26
+ const apphostingLocalConfig = await (0, config_1.getLocalAppHostingConfiguration)(backendRoot);
25
27
  const environmentVariablesAsRecord = {};
26
28
  for (const env of apphostingLocalConfig.environmentVariables) {
27
29
  environmentVariablesAsRecord[env.variable] = env.value;
@@ -29,12 +31,12 @@ async function serve(port, startCommand) {
29
31
  const environmentVariablesToInject = Object.assign(Object.assign({}, environmentVariablesAsRecord), { PORT: port.toString() });
30
32
  if (startCommand) {
31
33
  utils_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `running custom start command: '${startCommand}'`);
32
- await (0, spawn_1.spawnWithCommandString)(startCommand, rootDir, environmentVariablesToInject);
34
+ await (0, spawn_1.spawnWithCommandString)(startCommand, backendRoot, environmentVariablesToInject);
33
35
  return;
34
36
  }
35
- const packageManager = await (0, utils_1.discoverPackageManager)(rootDir);
37
+ const packageManager = await (0, utils_1.discoverPackageManager)(backendRoot);
36
38
  utils_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `starting app with: '${packageManager} run dev'`);
37
- await (0, spawn_1.wrapSpawn)(packageManager, ["run", "dev"], rootDir, environmentVariablesToInject);
39
+ await (0, spawn_1.wrapSpawn)(packageManager, ["run", "dev"], backendRoot, environmentVariablesToInject);
38
40
  }
39
41
  function availablePort(host, port) {
40
42
  return (0, portUtils_1.checkListenable)({
@@ -47,6 +47,7 @@ const fileUtils_1 = require("../dataconnect/fileUtils");
47
47
  const tasksEmulator_1 = require("./tasksEmulator");
48
48
  const apphosting_1 = require("./apphosting");
49
49
  const webhook_1 = require("../dataconnect/webhook");
50
+ const api_1 = require("../api");
50
51
  const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
51
52
  async function exportOnExit(options) {
52
53
  const exportOnExitDir = options.exportOnExit;
@@ -264,7 +265,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
264
265
  portFixed: !!wsPortConfig,
265
266
  };
266
267
  }
267
- if (emulator === types_1.Emulators.DATACONNECT) {
268
+ if (emulator === types_1.Emulators.DATACONNECT && !(0, api_1.dataConnectLocalConnString)()) {
268
269
  const pglitePortConfig = (_f = (_e = options.config.src.emulators) === null || _e === void 0 ? void 0 : _e.dataconnect) === null || _f === void 0 ? void 0 : _f.postgresPort;
269
270
  listenConfig["dataconnect.postgres"] = {
270
271
  host: config.host,
@@ -608,6 +609,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
608
609
  host: apphostingAddr.host,
609
610
  port: apphostingAddr.port,
610
611
  startCommandOverride: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride,
612
+ rootDirectory: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.rootDirectory,
611
613
  options,
612
614
  });
613
615
  await startEmulator(apphostingEmulator);
@@ -629,7 +631,6 @@ async function startAll(options, showUI = true, runningTestScript = false) {
629
631
  if (listenForEmulator.ui) {
630
632
  const ui = new ui_1.EmulatorUI({
631
633
  projectId: projectId,
632
- auto_download: true,
633
634
  listen: listenForEmulator[types_1.Emulators.UI],
634
635
  });
635
636
  await startEmulator(ui);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DataConnectEmulatorClient = exports.DataConnectEmulator = exports.dataConnectEmulatorEvents = void 0;
4
4
  const childProcess = require("child_process");
5
5
  const events_1 = require("events");
6
+ const clc = require("colorette");
6
7
  const api_1 = require("../api");
7
8
  const constants_1 = require("./constants");
8
9
  const downloadableEmulators_1 = require("./downloadableEmulators");
@@ -26,26 +27,23 @@ class DataConnectEmulator {
26
27
  this.emulatorClient = new DataConnectEmulatorClient();
27
28
  }
28
29
  async start() {
29
- var _a, _b, _c, _d;
30
+ var _a, _b, _c;
30
31
  let resolvedConfigDir;
31
32
  try {
32
33
  resolvedConfigDir = this.args.config.path(this.args.configDir);
33
34
  const info = await DataConnectEmulator.build({ configDir: resolvedConfigDir });
34
35
  if ((0, types_2.requiresVector)(info.metadata)) {
35
36
  if (constants_1.Constants.isDemoProject(this.args.projectId)) {
36
- this.logger.logLabeled("WARN", "Data Connect", "Detected a 'demo-' project, but vector embeddings require a real project. Operations that use vector_embed will fail.");
37
+ this.logger.logLabeled("WARN", "dataconnect", "Detected a 'demo-' project, but vector embeddings require a real project. Operations that use vector_embed will fail.");
37
38
  }
38
39
  else {
39
- this.logger.logLabeled("WARN", "Data Connect", "Operations that use vector_embed will make calls to production Vertex AI");
40
+ this.logger.logLabeled("WARN", "dataconnect", "Operations that use vector_embed will make calls to production Vertex AI");
40
41
  }
41
42
  }
42
43
  }
43
44
  catch (err) {
44
45
  this.logger.log("DEBUG", `'fdc build' failed with error: ${err.message}`);
45
46
  }
46
- const info = await (0, load_1.load)(this.args.projectId, this.args.config, this.args.configDir);
47
- const dbId = ((_a = info.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.database) || "postgres";
48
- const serviceId = info.dataConnectYaml.serviceId;
49
47
  await (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, {
50
48
  auto_download: this.args.auto_download,
51
49
  listen: (0, portUtils_1.listenSpecsToString)(this.args.listen),
@@ -55,11 +53,14 @@ class DataConnectEmulator {
55
53
  });
56
54
  this.usingExistingEmulator = false;
57
55
  if (this.args.autoconnectToPostgres) {
56
+ const info = await (0, load_1.load)(this.args.projectId, this.args.config, this.args.configDir);
57
+ const dbId = ((_a = info.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.database) || "postgres";
58
+ const serviceId = info.dataConnectYaml.serviceId;
58
59
  const pgPort = (_b = this.args.postgresListen) === null || _b === void 0 ? void 0 : _b[0].port;
59
60
  const pgHost = (_c = this.args.postgresListen) === null || _c === void 0 ? void 0 : _c[0].address;
60
61
  let connStr = (0, api_1.dataConnectLocalConnString)();
61
- if ((0, api_1.dataConnectLocalConnString)()) {
62
- this.logger.logLabeled("INFO", "Data Connect", `FIREBASE_DATACONNECT_POSTGRESQL_STRING is set to ${(0, api_1.dataConnectLocalConnString)()} - using that instead of starting a new database`);
62
+ if (connStr) {
63
+ this.logger.logLabeled("INFO", "dataconnect", `FIREBASE_DATACONNECT_POSTGRESQL_STRING is set to ${clc.bold(connStr)} - using that instead of starting a new database`);
63
64
  }
64
65
  else if (pgHost && pgPort) {
65
66
  const pgServer = new pgliteServer_1.PostgresServer(dbId, "postgres");
@@ -71,11 +72,11 @@ class DataConnectEmulator {
71
72
  this.logger.logLabeled("ERROR", "Data Connect", `${err}`);
72
73
  }
73
74
  else {
74
- this.logger.logLabeled("ERROR", "Data Connect", `Postgres threw an unexpected error, shutting down the Data Connect emulator: ${err}`);
75
+ this.logger.logLabeled("ERROR", "dataconnect", `Postgres threw an unexpected error, shutting down the Data Connect emulator: ${err}`);
75
76
  }
76
77
  void (0, controller_1.cleanShutdown)();
77
78
  });
78
- this.logger.logLabeled("INFO", "Data Connect", `Started up Postgres server, listening on ${(_d = server.address()) === null || _d === void 0 ? void 0 : _d.toString()}`);
79
+ this.logger.logLabeled("INFO", "dataconnect", `Started up Postgres server, listening on ${JSON.stringify(server.address())}`);
79
80
  }
80
81
  await this.connectToPostgres(new URL(connStr), dbId, serviceId);
81
82
  }
@@ -84,14 +85,14 @@ class DataConnectEmulator {
84
85
  async connect() {
85
86
  const emuInfo = await this.emulatorClient.getInfo();
86
87
  if (!emuInfo) {
87
- this.logger.logLabeled("ERROR", "Data Connect", "Could not connect to Data Connect emulator. Check dataconnect-debug.log for more details.");
88
+ this.logger.logLabeled("ERROR", "dataconnect", "Could not connect to Data Connect emulator. Check dataconnect-debug.log for more details.");
88
89
  return Promise.reject();
89
90
  }
90
91
  return Promise.resolve();
91
92
  }
92
93
  async stop() {
93
94
  if (this.usingExistingEmulator) {
94
- this.logger.logLabeled("INFO", "Data Connect", "Skipping cleanup of Data Connect emulator, as it was not started by this process.");
95
+ this.logger.logLabeled("INFO", "dataconnect", "Skipping cleanup of Data Connect emulator, as it was not started by this process.");
95
96
  return;
96
97
  }
97
98
  return (0, downloadableEmulators_1.stop)(types_1.Emulators.DATACONNECT);
@@ -48,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
48
48
  },
49
49
  dataconnect: process.platform === "darwin"
50
50
  ? {
51
- version: "1.6.1",
52
- expectedSize: 25309952,
53
- expectedChecksum: "826692eb4ed9cf11becb595c386eb7bf",
51
+ version: "1.7.3",
52
+ expectedSize: 25211648,
53
+ expectedChecksum: "8410794304b2ae340c3facf07d7edc16",
54
54
  }
55
55
  : process.platform === "win32"
56
56
  ? {
57
- version: "1.6.1",
58
- expectedSize: 25737728,
59
- expectedChecksum: "1e87a3a1f95d7b21cebf18f551619167",
57
+ version: "1.7.3",
58
+ expectedSize: 25641984,
59
+ expectedChecksum: "a4bd0f9d9d884528fa4494e4d7918c08",
60
60
  }
61
61
  : {
62
- version: "1.6.1",
63
- expectedSize: 25223320,
64
- expectedChecksum: "b13218419fcaaa3ad22005ce8378412d",
62
+ version: "1.7.3",
63
+ expectedSize: 25125016,
64
+ expectedChecksum: "48660e6370aeed973f33c3420c3255fb",
65
65
  },
66
66
  };
67
67
  exports.DownloadDetails = {
@@ -233,8 +233,8 @@ const Commands = {
233
233
  shell: true,
234
234
  },
235
235
  ui: {
236
- binary: "node",
237
- args: [getExecPath(types_1.Emulators.UI)],
236
+ binary: "",
237
+ args: [],
238
238
  optionalArgs: [],
239
239
  joinArgs: false,
240
240
  shell: false,
@@ -210,6 +210,9 @@ You can probably fix this by running "npm install ${systemLog.data.name}@latest"
210
210
  case "BULLET":
211
211
  utils.logLabeledBullet(label, text, "info", mergedData);
212
212
  break;
213
+ case "INFO":
214
+ utils.logLabeledBullet(label, text, "info", mergedData);
215
+ break;
213
216
  case "SUCCESS":
214
217
  utils.logLabeledSuccess(label, text, "info", mergedData);
215
218
  break;
@@ -10,7 +10,7 @@ const types_1 = require("./types");
10
10
  const hubExport_1 = require("./hubExport");
11
11
  const registry_1 = require("./registry");
12
12
  const ExpressBasedEmulator_1 = require("./ExpressBasedEmulator");
13
- const utils_1 = require("../utils");
13
+ const vsCodeUtils_1 = require("../vsCodeUtils");
14
14
  const pkg = require("../../package.json");
15
15
  class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
16
16
  static readLocatorFile(projectId) {
@@ -20,7 +20,7 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
20
20
  }
21
21
  const data = fs.readFileSync(locatorPath, "utf8").toString();
22
22
  const locator = JSON.parse(data);
23
- if (!utils_1.isVSCodeExtension && locator.version !== this.CLI_VERSION) {
23
+ if (!vsCodeUtils_1.isVSCodeExtension && locator.version !== this.CLI_VERSION) {
24
24
  logger_1.logger.debug(`Found locator with mismatched version, ignoring: ${JSON.stringify(locator)}`);
25
25
  return undefined;
26
26
  }
@@ -41,17 +41,20 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
41
41
  await super.start();
42
42
  await this.writeLocatorFile();
43
43
  }
44
+ getRunningEmulatorsMapping() {
45
+ const emulators = {};
46
+ for (const info of registry_1.EmulatorRegistry.listRunningWithInfo()) {
47
+ emulators[info.name] = Object.assign({ listen: this.args.listenForEmulator[info.name] }, info);
48
+ }
49
+ return emulators;
50
+ }
44
51
  async createExpressApp() {
45
52
  const app = await super.createExpressApp();
46
53
  app.get("/", (req, res) => {
47
54
  res.json(Object.assign(Object.assign({}, this.getLocator()), { host: utils.connectableHostname(this.args.listen[0].address), port: this.args.listen[0].port }));
48
55
  });
49
56
  app.get(EmulatorHub.PATH_EMULATORS, (req, res) => {
50
- const body = {};
51
- for (const info of registry_1.EmulatorRegistry.listRunningWithInfo()) {
52
- body[info.name] = Object.assign({ listen: this.args.listenForEmulator[info.name] }, info);
53
- }
54
- res.json(body);
57
+ res.json(this.getRunningEmulatorsMapping());
55
58
  });
56
59
  app.post(EmulatorHub.PATH_EXPORT, async (req, res) => {
57
60
  if (req.headers.origin) {
@@ -116,7 +116,10 @@ class TasksEmulator {
116
116
  const locationId = req.params.location_id;
117
117
  const queueName = req.params.queue_name;
118
118
  if (!this.validateQueueId(queueName)) {
119
- res.status(400).send("Invalid Queue ID");
119
+ res.status(400).json({
120
+ error: "Queue ID must start with a letter followed by up to 62 letters, numbers, " +
121
+ "hyphens, or underscores and must end with a letter or a number",
122
+ });
120
123
  return;
121
124
  }
122
125
  const key = `queue:${projectId}-${locationId}-${queueName}`;
@@ -139,7 +142,7 @@ class TasksEmulator {
139
142
  defaultUri: body.defaultUri,
140
143
  };
141
144
  if (taskQueueConfig.rateLimits.maxConcurrentDispatches > 5000) {
142
- res.status(400).send("cannot set maxConcurrentDispatches to a value over 5000");
145
+ res.status(400).json({ error: "cannot set maxConcurrentDispatches to a value over 5000" });
143
146
  return;
144
147
  }
145
148
  this.controller.createQueue(key, taskQueueConfig);
@@ -1,52 +1,74 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EmulatorUI = void 0;
4
+ const express = require("express");
5
+ const path = require("path");
4
6
  const types_1 = require("./types");
5
7
  const downloadableEmulators = require("./downloadableEmulators");
6
8
  const registry_1 = require("./registry");
7
9
  const error_1 = require("../error");
10
+ const emulatorLogger_1 = require("./emulatorLogger");
8
11
  const constants_1 = require("./constants");
9
12
  const track_1 = require("../track");
10
13
  const ExpressBasedEmulator_1 = require("./ExpressBasedEmulator");
11
14
  const experiments_1 = require("../experiments");
12
- class EmulatorUI {
15
+ class EmulatorUI extends ExpressBasedEmulator_1.ExpressBasedEmulator {
13
16
  constructor(args) {
17
+ super({
18
+ listen: args.listen,
19
+ });
14
20
  this.args = args;
15
21
  }
16
- start() {
22
+ async start() {
23
+ await super.start();
24
+ }
25
+ async createExpressApp() {
17
26
  if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.HUB)) {
18
27
  throw new error_1.FirebaseError(`Cannot start ${constants_1.Constants.description(types_1.Emulators.UI)} without ${constants_1.Constants.description(types_1.Emulators.HUB)}!`);
19
28
  }
20
- const { auto_download: autoDownload, projectId } = this.args;
21
- const env = {
22
- LISTEN: JSON.stringify(ExpressBasedEmulator_1.ExpressBasedEmulator.listenOptionsFromSpecs(this.args.listen)),
23
- GCLOUD_PROJECT: projectId,
24
- [constants_1.Constants.FIREBASE_EMULATOR_HUB]: registry_1.EmulatorRegistry.url(types_1.Emulators.HUB).host,
25
- };
26
- const session = (0, track_1.emulatorSession)();
27
- if (session) {
28
- env[constants_1.Constants.FIREBASE_GA_SESSION] = JSON.stringify(session);
29
- }
29
+ const hub = registry_1.EmulatorRegistry.get(types_1.Emulators.HUB);
30
+ const app = await super.createExpressApp();
31
+ const { projectId } = this.args;
30
32
  const enabledExperiments = Object.keys(experiments_1.ALL_EXPERIMENTS).filter((experimentName) => (0, experiments_1.isEnabled)(experimentName));
31
- env[constants_1.Constants.FIREBASE_ENABLED_EXPERIMENTS] = JSON.stringify(enabledExperiments);
32
- return downloadableEmulators.start(types_1.Emulators.UI, { auto_download: autoDownload }, env);
33
+ const emulatorGaSession = (0, track_1.emulatorSession)();
34
+ await downloadableEmulators.downloadIfNecessary(types_1.Emulators.UI);
35
+ const downloadDetails = downloadableEmulators.getDownloadDetails(types_1.Emulators.UI);
36
+ const webDir = path.join(downloadDetails.unzipDir, "client");
37
+ app.get("/api/config", this.jsonHandler(() => {
38
+ const json = Object.assign({ projectId, experiments: [] }, hub.getRunningEmulatorsMapping());
39
+ if (emulatorGaSession) {
40
+ json.analytics = emulatorGaSession;
41
+ }
42
+ if (enabledExperiments) {
43
+ json.experiments = enabledExperiments;
44
+ }
45
+ return Promise.resolve(json);
46
+ }));
47
+ app.use(express.static(webDir));
48
+ app.get("*", (_, res) => {
49
+ res.sendFile(path.join(webDir, "index.html"));
50
+ });
51
+ return app;
33
52
  }
34
53
  connect() {
35
54
  return Promise.resolve();
36
55
  }
37
- stop() {
38
- return downloadableEmulators.stop(types_1.Emulators.UI);
39
- }
40
- getInfo() {
41
- return {
42
- name: this.getName(),
43
- host: this.args.listen[0].address,
44
- port: this.args.listen[0].port,
45
- pid: downloadableEmulators.getPID(types_1.Emulators.UI),
46
- };
47
- }
48
56
  getName() {
49
57
  return types_1.Emulators.UI;
50
58
  }
59
+ jsonHandler(handler) {
60
+ return (req, res) => {
61
+ handler(req).then((body) => {
62
+ res.status(200).json(body);
63
+ }, (err) => {
64
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.UI).log("ERROR", err);
65
+ res.status(500).json({
66
+ message: err.message,
67
+ stack: err.stack,
68
+ raw: err,
69
+ });
70
+ });
71
+ };
72
+ }
51
73
  }
52
74
  exports.EmulatorUI = EmulatorUI;