firebase-tools 13.26.0 → 13.27.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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadConfigForEnvironment = exports.maybeAddSecretToYaml = exports.upsertEnv = exports.findEnv = exports.store = exports.load = exports.listAppHostingFilesInPath = exports.discoverBackendRoot = exports.APPHOSTING_YAML_FILE_REGEX = exports.APPHOSTING_LOCAL_YAML_FILE = exports.APPHOSTING_BASE_YAML_FILE = void 0;
3
+ exports.loadConfigToExportSecrets = exports.loadConfigForEnvironment = exports.exportConfig = exports.maybeAddSecretToYaml = exports.upsertEnv = exports.findEnv = exports.store = exports.load = exports.listAppHostingFilesInPath = exports.discoverBackendRoot = exports.APPHOSTING_YAML_FILE_REGEX = exports.APPHOSTING_LOCAL_YAML_FILE = exports.APPHOSTING_BASE_YAML_FILE = void 0;
4
4
  const path_1 = require("path");
5
5
  const fs_1 = require("fs");
6
6
  const yaml = require("yaml");
@@ -8,9 +8,17 @@ const fs = require("../fsutils");
8
8
  const prompt = require("../prompt");
9
9
  const dialogs = require("./secrets/dialogs");
10
10
  const yaml_1 = require("./yaml");
11
+ const error_1 = require("../error");
12
+ const utils_1 = require("./utils");
13
+ const secrets_1 = require("./secrets");
14
+ const logger_1 = require("../logger");
15
+ const utils_2 = require("../utils");
16
+ const projects_1 = require("../management/projects");
11
17
  exports.APPHOSTING_BASE_YAML_FILE = "apphosting.yaml";
12
18
  exports.APPHOSTING_LOCAL_YAML_FILE = "apphosting.local.yaml";
13
19
  exports.APPHOSTING_YAML_FILE_REGEX = /^apphosting(\.[a-z0-9_]+)?\.yaml$/;
20
+ const SECRET_CONFIG = "Secret";
21
+ const EXPORTABLE_CONFIG = [SECRET_CONFIG];
14
22
  function discoverBackendRoot(cwd) {
15
23
  let dir = cwd;
16
24
  while (!fs.fileExistsSync((0, path_1.resolve)(dir, exports.APPHOSTING_BASE_YAML_FILE))) {
@@ -108,6 +116,49 @@ async function maybeAddSecretToYaml(secretName) {
108
116
  dynamicDispatch.store(path, projectYaml);
109
117
  }
110
118
  exports.maybeAddSecretToYaml = maybeAddSecretToYaml;
119
+ async function exportConfig(cwd, projectRoot, backendRoot, projectId, userGivenConfigFile) {
120
+ const choices = await prompt.prompt({}, [
121
+ {
122
+ type: "checkbox",
123
+ name: "configurations",
124
+ message: "What configs would you like to export?",
125
+ choices: EXPORTABLE_CONFIG,
126
+ },
127
+ ]);
128
+ if (!choices.configurations.includes(SECRET_CONFIG)) {
129
+ logger_1.logger.info("No configs selected to export");
130
+ return;
131
+ }
132
+ if (!projectId) {
133
+ const project = await (0, projects_1.getOrPromptProject)({});
134
+ projectId = project.projectId;
135
+ }
136
+ let localAppHostingConfig = yaml_1.AppHostingYamlConfig.empty();
137
+ const localAppHostingConfigPath = (0, path_1.resolve)(backendRoot, exports.APPHOSTING_LOCAL_YAML_FILE);
138
+ if (fs.fileExistsSync(localAppHostingConfigPath)) {
139
+ localAppHostingConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localAppHostingConfigPath);
140
+ }
141
+ const configToExport = await loadConfigToExportSecrets(cwd, userGivenConfigFile);
142
+ const secretsToExport = configToExport.secrets;
143
+ if (!secretsToExport) {
144
+ logger_1.logger.info("No secrets found to export in the chosen App Hosting config files");
145
+ return;
146
+ }
147
+ const secretMaterial = await (0, secrets_1.fetchSecrets)(projectId, secretsToExport);
148
+ for (const [key, value] of secretMaterial) {
149
+ localAppHostingConfig.addEnvironmentVariable({
150
+ variable: key,
151
+ value: value,
152
+ availability: ["RUNTIME"],
153
+ });
154
+ }
155
+ localAppHostingConfig.clearSecrets();
156
+ localAppHostingConfig.upsertFile(localAppHostingConfigPath);
157
+ logger_1.logger.info(`Wrote secrets as environment variables to ${exports.APPHOSTING_LOCAL_YAML_FILE}.`);
158
+ (0, utils_2.updateOrCreateGitignore)(projectRoot, [exports.APPHOSTING_LOCAL_YAML_FILE]);
159
+ logger_1.logger.info(`${exports.APPHOSTING_LOCAL_YAML_FILE} has been automatically added to your .gitignore.`);
160
+ }
161
+ exports.exportConfig = exportConfig;
111
162
  async function loadConfigForEnvironment(envYamlPath, baseYamlPath) {
112
163
  const envYamlConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(envYamlPath);
113
164
  if (baseYamlPath) {
@@ -118,3 +169,37 @@ async function loadConfigForEnvironment(envYamlPath, baseYamlPath) {
118
169
  return envYamlConfig;
119
170
  }
120
171
  exports.loadConfigForEnvironment = loadConfigForEnvironment;
172
+ async function loadConfigToExportSecrets(cwd, userGivenConfigFile) {
173
+ if (userGivenConfigFile && !exports.APPHOSTING_YAML_FILE_REGEX.test(userGivenConfigFile)) {
174
+ throw new error_1.FirebaseError("Invalid apphosting yaml config file provided. File must be in format: 'apphosting.yaml' or 'apphosting.<environment>.yaml'");
175
+ }
176
+ const allConfigs = getValidConfigs(cwd);
177
+ let userGivenConfigFilePath;
178
+ if (userGivenConfigFile) {
179
+ if (!allConfigs.has(userGivenConfigFile)) {
180
+ throw new error_1.FirebaseError(`The provided app hosting config file "${userGivenConfigFile}" does not exist`);
181
+ }
182
+ userGivenConfigFilePath = allConfigs.get(userGivenConfigFile);
183
+ }
184
+ else {
185
+ userGivenConfigFilePath = await (0, utils_1.promptForAppHostingYaml)(allConfigs, "Which environment would you like to export secrets from Secret Manager for?");
186
+ }
187
+ if (userGivenConfigFile === exports.APPHOSTING_BASE_YAML_FILE) {
188
+ return yaml_1.AppHostingYamlConfig.loadFromFile(allConfigs.get(exports.APPHOSTING_BASE_YAML_FILE));
189
+ }
190
+ const baseFilePath = allConfigs.get(exports.APPHOSTING_BASE_YAML_FILE);
191
+ return await loadConfigForEnvironment(userGivenConfigFilePath, baseFilePath);
192
+ }
193
+ exports.loadConfigToExportSecrets = loadConfigToExportSecrets;
194
+ function getValidConfigs(cwd) {
195
+ const appHostingConfigPaths = listAppHostingFilesInPath(cwd).filter((path) => !path.endsWith(exports.APPHOSTING_LOCAL_YAML_FILE));
196
+ if (appHostingConfigPaths.length === 0) {
197
+ throw new error_1.FirebaseError("No apphosting.*.yaml configs found");
198
+ }
199
+ const fileNameToPathMap = new Map();
200
+ for (const path of appHostingConfigPaths) {
201
+ const fileName = (0, path_1.basename)(path);
202
+ fileNameToPathMap.set(fileName, path);
203
+ }
204
+ return fileNameToPathMap;
205
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSecretNameParts = exports.loadConfigToExport = exports.fetchSecrets = exports.upsertSecret = exports.grantSecretAccess = exports.serviceAccountsForBackend = exports.toMulti = void 0;
3
+ exports.getSecretNameParts = exports.fetchSecrets = exports.upsertSecret = exports.grantSecretAccess = exports.serviceAccountsForBackend = exports.toMulti = void 0;
4
4
  const error_1 = require("../../error");
5
5
  const gcsm = require("../../gcp/secretManager");
6
6
  const gcb = require("../../gcp/cloudbuild");
@@ -10,10 +10,6 @@ const secretManager_1 = require("../../gcp/secretManager");
10
10
  const secretManager_2 = require("../../gcp/secretManager");
11
11
  const utils = require("../../utils");
12
12
  const prompt = require("../../prompt");
13
- const path_1 = require("path");
14
- const config_1 = require("../config");
15
- const utils_1 = require("../utils");
16
- const yaml_1 = require("../yaml");
17
13
  function toMulti(accounts) {
18
14
  const m = {
19
15
  buildServiceAccounts: [accounts.buildServiceAccount],
@@ -125,40 +121,6 @@ async function fetchSecrets(projectId, secrets) {
125
121
  return secretsKeyValuePairs;
126
122
  }
127
123
  exports.fetchSecrets = fetchSecrets;
128
- async function loadConfigToExport(cwd, userGivenConfigFile) {
129
- if (userGivenConfigFile && !config_1.APPHOSTING_YAML_FILE_REGEX.test(userGivenConfigFile)) {
130
- throw new error_1.FirebaseError("Invalid apphosting yaml config file provided. File must be in format: 'apphosting.yaml' or 'apphosting.<environment>.yaml'");
131
- }
132
- const allConfigs = getValidConfigs(cwd);
133
- let userGivenConfigFilePath;
134
- if (userGivenConfigFile) {
135
- if (!allConfigs.has(userGivenConfigFile)) {
136
- throw new error_1.FirebaseError(`The provided app hosting config file "${userGivenConfigFile}" does not exist`);
137
- }
138
- userGivenConfigFilePath = allConfigs.get(userGivenConfigFile);
139
- }
140
- else {
141
- userGivenConfigFilePath = await (0, utils_1.promptForAppHostingYaml)(allConfigs, "Which environment would you like to export secrets from Secret Manager for?");
142
- }
143
- if (userGivenConfigFile === config_1.APPHOSTING_BASE_YAML_FILE) {
144
- return yaml_1.AppHostingYamlConfig.loadFromFile(allConfigs.get(config_1.APPHOSTING_BASE_YAML_FILE));
145
- }
146
- const baseFilePath = allConfigs.get(config_1.APPHOSTING_BASE_YAML_FILE);
147
- return await (0, config_1.loadConfigForEnvironment)(userGivenConfigFilePath, baseFilePath);
148
- }
149
- exports.loadConfigToExport = loadConfigToExport;
150
- function getValidConfigs(cwd) {
151
- const appHostingConfigPaths = (0, config_1.listAppHostingFilesInPath)(cwd).filter((path) => !path.endsWith(config_1.APPHOSTING_LOCAL_YAML_FILE));
152
- if (appHostingConfigPaths.length === 0) {
153
- throw new error_1.FirebaseError("No apphosting.*.yaml configs found");
154
- }
155
- const fileNameToPathMap = new Map();
156
- for (const path of appHostingConfigPaths) {
157
- const fileName = (0, path_1.basename)(path);
158
- fileNameToPathMap.set(fileName, path);
159
- }
160
- return fileNameToPathMap;
161
- }
162
124
  function getSecretNameParts(secret) {
163
125
  let [name, version] = secret.split("@");
164
126
  if (!version) {
@@ -43,6 +43,9 @@ class AppHostingYamlConfig {
43
43
  addSecret(secret) {
44
44
  this._secrets.set(secret.variable, secret);
45
45
  }
46
+ clearSecrets() {
47
+ this._secrets.clear();
48
+ }
46
49
  merge(other) {
47
50
  for (const [key, value] of other._environmentVariables) {
48
51
  this._environmentVariables.set(key, value);
@@ -2,17 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const command_1 = require("../command");
5
- const logger_1 = require("../logger");
6
5
  const projectUtils_1 = require("../projectUtils");
7
6
  const requireAuth_1 = require("../requireAuth");
8
7
  const secretManager = require("../gcp/secretManager");
9
8
  const requirePermissions_1 = require("../requirePermissions");
10
9
  const config_1 = require("../apphosting/config");
11
- const secrets_1 = require("../apphosting/secrets");
12
- const path_1 = require("path");
13
- const fs = require("../fsutils");
14
- const yaml_1 = require("../apphosting/yaml");
15
10
  const error_1 = require("../error");
11
+ const detectProjectRoot_1 = require("../detectProjectRoot");
16
12
  exports.command = new command_1.Command("apphosting:config:export")
17
13
  .description("Export App Hosting configurations such as secrets into an apphosting.local.yaml file")
18
14
  .option("-s, --secrets <apphosting.yaml or apphosting.<environment>.yaml file to export secrets from>", "This command combines the base apphosting.yaml with the specified environment-specific file (e.g., apphosting.staging.yaml). If keys conflict, the environment-specific file takes precedence.")
@@ -20,32 +16,14 @@ exports.command = new command_1.Command("apphosting:config:export")
20
16
  .before(secretManager.ensureApi)
21
17
  .before(requirePermissions_1.requirePermissions, ["secretmanager.versions.access"])
22
18
  .action(async (options) => {
19
+ var _a;
23
20
  const projectId = (0, projectUtils_1.needProjectId)(options);
24
21
  const environmentConfigFile = options.secrets;
25
22
  const cwd = process.cwd();
26
- let localAppHostingConfig = yaml_1.AppHostingYamlConfig.empty();
27
23
  const backendRoot = (0, config_1.discoverBackendRoot)(cwd);
28
24
  if (!backendRoot) {
29
25
  throw new error_1.FirebaseError("Missing apphosting.yaml: This command requires an apphosting.yaml configuration file. Please run 'firebase init apphosting' and try again.");
30
26
  }
31
- const localAppHostingConfigPath = (0, path_1.resolve)(backendRoot, config_1.APPHOSTING_LOCAL_YAML_FILE);
32
- if (fs.fileExistsSync(localAppHostingConfigPath)) {
33
- localAppHostingConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localAppHostingConfigPath);
34
- }
35
- const configToExport = await (0, secrets_1.loadConfigToExport)(cwd, environmentConfigFile);
36
- const secretsToExport = configToExport.secrets;
37
- if (!secretsToExport) {
38
- logger_1.logger.warn("No secrets found to export in the chosen App Hosting config files");
39
- return;
40
- }
41
- const secretMaterial = await (0, secrets_1.fetchSecrets)(projectId, secretsToExport);
42
- for (const [key, value] of secretMaterial) {
43
- localAppHostingConfig.addEnvironmentVariable({
44
- variable: key,
45
- value: value,
46
- availability: ["RUNTIME"],
47
- });
48
- }
49
- localAppHostingConfig.upsertFile(localAppHostingConfigPath);
50
- logger_1.logger.info(`Wrote secrets as environment variables to ${config_1.APPHOSTING_LOCAL_YAML_FILE}.`);
27
+ const projectRoot = (_a = (0, detectProjectRoot_1.detectProjectRoot)({})) !== null && _a !== void 0 ? _a : backendRoot;
28
+ await (0, config_1.exportConfig)(cwd, projectRoot, backendRoot, projectId, environmentConfigFile);
51
29
  });
@@ -172,6 +172,8 @@ function load(client) {
172
172
  client.apphosting.secrets.grantaccess = loadCommand("apphosting-secrets-grantaccess");
173
173
  client.apphosting.secrets.describe = loadCommand("apphosting-secrets-describe");
174
174
  client.apphosting.secrets.access = loadCommand("apphosting-secrets-access");
175
+ client.apphosting.config = {};
176
+ client.apphosting.config.export = loadCommand("apphosting-config-export");
175
177
  if (experiments.isEnabled("internaltesting")) {
176
178
  client.apphosting.builds = {};
177
179
  client.apphosting.builds.get = loadCommand("apphosting-builds-get");
@@ -182,10 +184,6 @@ function load(client) {
182
184
  client.apphosting.rollouts.create = loadCommand("apphosting-rollouts-create");
183
185
  client.apphosting.rollouts.list = loadCommand("apphosting-rollouts-list");
184
186
  }
185
- if (experiments.isEnabled("emulatorapphosting")) {
186
- client.apphosting.config = {};
187
- client.apphosting.config.export = loadCommand("apphosting-config-export");
188
- }
189
187
  }
190
188
  client.login = loadCommand("login");
191
189
  client.login.add = loadCommand("login-add");
@@ -224,7 +224,7 @@ class Fabricator {
224
224
  }
225
225
  }
226
226
  async createV2Function(endpoint, scraper) {
227
- var _a, _b, _c, _d, _e;
227
+ var _a, _b, _c, _d;
228
228
  const storageSource = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
229
229
  if (!storageSource) {
230
230
  logger_1.logger.debug("Precondition failed. Cannot create a GCFv2 function without storage");
@@ -295,8 +295,8 @@ class Fabricator {
295
295
  }
296
296
  });
297
297
  }
298
- endpoint.uri = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.uri;
299
- const serviceName = (_e = resultFunction.serviceConfig) === null || _e === void 0 ? void 0 : _e.service;
298
+ endpoint.uri = resultFunction.url;
299
+ const serviceName = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.service;
300
300
  endpoint.runServiceId = utils.last(serviceName === null || serviceName === void 0 ? void 0 : serviceName.split("/"));
301
301
  if (!serviceName) {
302
302
  logger_1.logger.debug("Result function unexpectedly didn't have a service name.");
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectStartCommand = exports.detectPackageManager = exports.logger = void 0;
4
+ const fs_extra_1 = require("fs-extra");
5
+ const path_1 = require("path");
6
+ const emulatorLogger_1 = require("../emulatorLogger");
7
+ const types_1 = require("../types");
8
+ const error_1 = require("../../error");
9
+ exports.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
10
+ async function detectPackageManager(rootdir) {
11
+ if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "pnpm-lock.yaml"))) {
12
+ return "pnpm";
13
+ }
14
+ if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "yarn.lock"))) {
15
+ return "yarn";
16
+ }
17
+ if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "package-lock.json"))) {
18
+ return "npm";
19
+ }
20
+ throw new error_1.FirebaseError("Unsupported package manager");
21
+ }
22
+ exports.detectPackageManager = detectPackageManager;
23
+ async function detectStartCommand(rootDir) {
24
+ try {
25
+ const packageManager = await detectPackageManager(rootDir);
26
+ return `${packageManager} run dev`;
27
+ }
28
+ catch (e) {
29
+ throw new error_1.FirebaseError("Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommandOverride`");
30
+ }
31
+ }
32
+ exports.detectStartCommand = detectStartCommand;
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AppHostingEmulator = void 0;
4
4
  const types_1 = require("../types");
5
5
  const serve_1 = require("./serve");
6
- const utils_1 = require("./utils");
6
+ const developmentServer_1 = require("./developmentServer");
7
7
  class AppHostingEmulator {
8
8
  constructor(args) {
9
9
  this.args = args;
10
10
  }
11
11
  async start() {
12
12
  const { hostname, port } = await (0, serve_1.start)({
13
+ port: this.args.port,
13
14
  startCommand: this.args.startCommandOverride,
14
15
  rootDirectory: this.args.rootDirectory,
15
16
  });
@@ -17,11 +18,11 @@ class AppHostingEmulator {
17
18
  this.args.options.port = port;
18
19
  }
19
20
  connect() {
20
- utils_1.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "connecting apphosting emulator");
21
+ developmentServer_1.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "connecting apphosting emulator");
21
22
  return Promise.resolve();
22
23
  }
23
24
  stop() {
24
- utils_1.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "stopping apphosting emulator");
25
+ developmentServer_1.logger.logLabeled("INFO", types_1.Emulators.APPHOSTING, "stopping apphosting emulator");
25
26
  return Promise.resolve();
26
27
  }
27
28
  getInfo() {
@@ -3,16 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.start = void 0;
4
4
  const net_1 = require("net");
5
5
  const portUtils_1 = require("../portUtils");
6
- const utils_1 = require("./utils");
6
+ const developmentServer_1 = require("./developmentServer");
7
7
  const constants_1 = require("../constants");
8
8
  const spawn_1 = require("../../init/spawn");
9
- const utils_2 = require("./utils");
9
+ const developmentServer_2 = require("./developmentServer");
10
10
  const types_1 = require("../types");
11
11
  const config_1 = require("./config");
12
12
  const projectPath_1 = require("../../projectPath");
13
13
  async function start(options) {
14
+ var _a;
14
15
  const hostname = constants_1.DEFAULT_HOST;
15
- let port = constants_1.DEFAULT_PORTS.apphosting;
16
+ let port = (_a = options === null || options === void 0 ? void 0 : options.port) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_PORTS.apphosting;
16
17
  while (!(await availablePort(hostname, port))) {
17
18
  port += 1;
18
19
  }
@@ -30,13 +31,13 @@ async function serve(port, startCommand, backendRelativeDir) {
30
31
  }
31
32
  const environmentVariablesToInject = Object.assign(Object.assign({}, environmentVariablesAsRecord), { PORT: port.toString() });
32
33
  if (startCommand) {
33
- utils_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `running custom start command: '${startCommand}'`);
34
+ developmentServer_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `running custom start command: '${startCommand}'`);
34
35
  await (0, spawn_1.spawnWithCommandString)(startCommand, backendRoot, environmentVariablesToInject);
35
36
  return;
36
37
  }
37
- const packageManager = await (0, utils_1.discoverPackageManager)(backendRoot);
38
- utils_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `starting app with: '${packageManager} run dev'`);
39
- await (0, spawn_1.wrapSpawn)(packageManager, ["run", "dev"], backendRoot, environmentVariablesToInject);
38
+ const detectedStartCommand = await (0, developmentServer_1.detectStartCommand)(backendRoot);
39
+ developmentServer_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `starting app with: '${detectedStartCommand}'`);
40
+ await (0, spawn_1.spawnWithCommandString)(detectedStartCommand, backendRoot, environmentVariablesToInject);
40
41
  }
41
42
  function availablePort(host, port) {
42
43
  return (0, portUtils_1.checkListenable)({
@@ -300,8 +300,9 @@ async function emulatorExec(script, options) {
300
300
  exitCode = await runScript(script, extraEnv);
301
301
  await controller.onExit(options);
302
302
  }
303
- catch (_a) {
303
+ catch (err) {
304
304
  await (0, webhook_1.sendVSCodeMessage)({ message: webhook_1.VSCODE_MESSAGE.EMULATORS_START_ERRORED });
305
+ throw err;
305
306
  }
306
307
  finally {
307
308
  await controller.cleanShutdown();
@@ -58,7 +58,7 @@ async function exportOnExit(options) {
58
58
  await exportEmulatorData(exportOnExitDir, options, "exit");
59
59
  }
60
60
  catch (e) {
61
- utils.logWarning(e);
61
+ utils.logWarning(`${e}`);
62
62
  utils.logWarning(`Automatic export to "${exportOnExitDir}" failed, going to exit now...`);
63
63
  }
64
64
  }
@@ -561,19 +561,30 @@ async function startAll(options, showUI = true, runningTestScript = false) {
561
561
  else if (config.length > 1) {
562
562
  logger_1.logger.warn(`TODO: Add support for multiple services in the Data Connect emulator. Currently emulating first service ${config[0].source}`);
563
563
  }
564
- const configDir = config[0].source;
565
- const dataConnectEmulator = new dataconnectEmulator_1.DataConnectEmulator({
564
+ const args = {
566
565
  listen: listenForEmulator.dataconnect,
567
566
  projectId,
568
567
  auto_download: true,
569
- configDir,
568
+ configDir: config[0].source,
570
569
  rc: options.rc,
571
570
  config: options.config,
572
571
  autoconnectToPostgres: true,
573
572
  postgresListen: listenForEmulator["dataconnect.postgres"],
574
573
  enable_output_generated_sdk: true,
575
574
  enable_output_schema_extensions: true,
576
- });
575
+ };
576
+ if (exportMetadata.dataconnect) {
577
+ utils.assertIsString(options.import);
578
+ const importDirAbsPath = path.resolve(options.import);
579
+ const exportMetadataFilePath = path.resolve(importDirAbsPath, exportMetadata.dataconnect.path);
580
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT).logLabeled("BULLET", "dataconnect", `Importing data from ${exportMetadataFilePath}`);
581
+ args.importPath = exportMetadataFilePath;
582
+ void (0, track_1.trackEmulator)("emulator_import", {
583
+ initiated_by: "start",
584
+ emulator_name: types_1.Emulators.DATACONNECT,
585
+ });
586
+ }
587
+ const dataConnectEmulator = new dataconnectEmulator_1.DataConnectEmulator(args);
577
588
  await startEmulator(dataConnectEmulator);
578
589
  }
579
590
  if (listenForEmulator.storage) {
@@ -601,19 +612,17 @@ async function startAll(options, showUI = true, runningTestScript = false) {
601
612
  });
602
613
  await startEmulator(hostingEmulator);
603
614
  }
604
- if (experiments.isEnabled("emulatorapphosting")) {
605
- const apphostingConfig = (_l = options.config.src.emulators) === null || _l === void 0 ? void 0 : _l[types_1.Emulators.APPHOSTING];
606
- if (listenForEmulator.apphosting) {
607
- const apphostingAddr = legacyGetFirstAddr(types_1.Emulators.APPHOSTING);
608
- const apphostingEmulator = new apphosting_1.AppHostingEmulator({
609
- host: apphostingAddr.host,
610
- port: apphostingAddr.port,
611
- startCommandOverride: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride,
612
- rootDirectory: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.rootDirectory,
613
- options,
614
- });
615
- await startEmulator(apphostingEmulator);
616
- }
615
+ const apphostingConfig = (_l = options.config.src.emulators) === null || _l === void 0 ? void 0 : _l[types_1.Emulators.APPHOSTING];
616
+ if (listenForEmulator.apphosting) {
617
+ const apphostingAddr = legacyGetFirstAddr(types_1.Emulators.APPHOSTING);
618
+ const apphostingEmulator = new apphosting_1.AppHostingEmulator({
619
+ host: apphostingAddr.host,
620
+ port: apphostingAddr.port,
621
+ startCommandOverride: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride,
622
+ rootDirectory: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.rootDirectory,
623
+ options,
624
+ });
625
+ await startEmulator(apphostingEmulator);
617
626
  }
618
627
  if (listenForEmulator.logging) {
619
628
  const loggingAddr = legacyGetFirstAddr(types_1.Emulators.LOGGING);
@@ -19,17 +19,28 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
19
19
  function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
20
20
  };
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.PGliteExtendedQueryPatch = exports.PostgresServer = void 0;
22
+ exports.PGliteExtendedQueryPatch = exports.PostgresServer = exports.TRUNCATE_TABLES_SQL = void 0;
23
23
  const pglite_1 = require("@electric-sql/pglite");
24
24
  const { dynamicImport } = require(true && "../../dynamicImport");
25
25
  const net = require("node:net");
26
+ const fs = require("fs");
26
27
  const index_1 = require("./pg-gateway/index");
27
28
  const node_1 = require("./pg-gateway/platforms/node");
28
29
  const logger_1 = require("../../logger");
30
+ exports.TRUNCATE_TABLES_SQL = `
31
+ DO $do$
32
+ BEGIN
33
+ EXECUTE
34
+ (SELECT 'TRUNCATE TABLE ' || string_agg(oid::regclass::text, ', ') || ' CASCADE'
35
+ FROM pg_class
36
+ WHERE relkind = 'r'
37
+ AND relnamespace = 'public'::regnamespace
38
+ );
39
+ END
40
+ $do$;`;
29
41
  class PostgresServer {
30
42
  async createPGServer(host = "127.0.0.1", port) {
31
- const db = await this.getDb();
32
- await db.waitReady;
43
+ const getDb = this.getDb.bind(this);
33
44
  const server = net.createServer(async (socket) => {
34
45
  const connection = await (0, node_1.fromNodeSocket)(socket, {
35
46
  serverVersion: "16.3 (PGlite 0.2.0)",
@@ -38,6 +49,7 @@ class PostgresServer {
38
49
  if (!isAuthenticated) {
39
50
  return;
40
51
  }
52
+ const db = await getDb();
41
53
  const result = await db.execProtocolRaw(data);
42
54
  return extendedQueryPatch.filterResponse(data, result);
43
55
  },
@@ -55,29 +67,50 @@ class PostgresServer {
55
67
  resolve();
56
68
  });
57
69
  });
58
- await db.waitReady;
59
70
  await listeningPromise;
60
71
  return server;
61
72
  }
62
73
  async getDb() {
63
- if (this.db) {
64
- return this.db;
74
+ if (!this.db) {
75
+ const vector = (await dynamicImport("@electric-sql/pglite/vector")).vector;
76
+ const uuidOssp = (await dynamicImport("@electric-sql/pglite/contrib/uuid_ossp")).uuid_ossp;
77
+ const pgliteArgs = {
78
+ username: this.username,
79
+ database: this.database,
80
+ debug: 0,
81
+ extensions: {
82
+ vector,
83
+ uuidOssp,
84
+ },
85
+ dataDir: this.dataDirectory,
86
+ };
87
+ if (this.importPath) {
88
+ logger_1.logger.debug(`Importing from ${this.importPath}`);
89
+ const rf = fs.readFileSync(this.importPath);
90
+ const file = new File([rf], this.importPath);
91
+ pgliteArgs.loadDataDir = file;
92
+ }
93
+ this.db = await pglite_1.PGlite.create(pgliteArgs);
94
+ await this.db.waitReady;
65
95
  }
66
- const vector = (await dynamicImport("@electric-sql/pglite/vector")).vector;
67
- const uuidOssp = (await dynamicImport("@electric-sql/pglite/contrib/uuid_ossp")).uuid_ossp;
68
- return pglite_1.PGlite.create({
69
- username: this.username,
70
- database: this.database,
71
- debug: 0,
72
- extensions: {
73
- vector,
74
- uuidOssp,
75
- },
76
- });
96
+ return this.db;
97
+ }
98
+ async clearDb() {
99
+ const db = await this.getDb();
100
+ await db.query(exports.TRUNCATE_TABLES_SQL);
101
+ }
102
+ async exportData(exportPath) {
103
+ const db = await this.getDb();
104
+ const dump = await db.dumpDataDir();
105
+ const arrayBuff = await dump.arrayBuffer();
106
+ fs.writeFileSync(exportPath, new Uint8Array(arrayBuff));
77
107
  }
78
- constructor(database, username) {
108
+ constructor(database, username, dataDirectory, importPath) {
109
+ this.db = undefined;
79
110
  this.username = username;
80
111
  this.database = database;
112
+ this.dataDirectory = dataDirectory;
113
+ this.importPath = importPath;
81
114
  }
82
115
  }
83
116
  exports.PostgresServer = PostgresServer;
@@ -2,8 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DataConnectEmulatorClient = exports.DataConnectEmulator = exports.dataConnectEmulatorEvents = void 0;
4
4
  const childProcess = require("child_process");
5
+ const pg = require("pg");
5
6
  const events_1 = require("events");
6
7
  const clc = require("colorette");
8
+ const path = require("path");
7
9
  const api_1 = require("../api");
8
10
  const constants_1 = require("./constants");
9
11
  const downloadableEmulators_1 = require("./downloadableEmulators");
@@ -63,8 +65,12 @@ class DataConnectEmulator {
63
65
  this.logger.logLabeled("INFO", "dataconnect", `FIREBASE_DATACONNECT_POSTGRESQL_STRING is set to ${clc.bold(connStr)} - using that instead of starting a new database`);
64
66
  }
65
67
  else if (pgHost && pgPort) {
66
- const pgServer = new pgliteServer_1.PostgresServer(dbId, "postgres");
67
- const server = await pgServer.createPGServer(pgHost, pgPort);
68
+ const dataDirectory = this.args.config.get("emulators.dataconnect.dataDir");
69
+ const postgresDumpPath = this.args.importPath
70
+ ? path.join(this.args.importPath, "postgres.tar.gz")
71
+ : undefined;
72
+ this.postgresServer = new pgliteServer_1.PostgresServer(dbId, "postgres", dataDirectory, postgresDumpPath);
73
+ const server = await this.postgresServer.createPGServer(pgHost, pgPort);
68
74
  const connectableHost = (0, utils_1.connectableHostname)(pgHost);
69
75
  connStr = `postgres://${connectableHost}:${pgPort}/${dbId}?sslmode=disable`;
70
76
  server.on("error", (err) => {
@@ -110,6 +116,24 @@ class DataConnectEmulator {
110
116
  getName() {
111
117
  return types_1.Emulators.DATACONNECT;
112
118
  }
119
+ async clearData() {
120
+ if (this.postgresServer) {
121
+ await this.postgresServer.clearDb();
122
+ }
123
+ else {
124
+ const conn = new pg.Client((0, api_1.dataConnectLocalConnString)());
125
+ await conn.query(pgliteServer_1.TRUNCATE_TABLES_SQL);
126
+ await conn.end();
127
+ }
128
+ }
129
+ async exportData(exportPath) {
130
+ if (this.postgresServer) {
131
+ await this.postgresServer.exportData(path.join(exportPath, "postgres.tar.gz"));
132
+ }
133
+ else {
134
+ throw new error_1.FirebaseError("The Data Connect emulator is currently connected to a separate Postgres instance. Export is not supported.");
135
+ }
136
+ }
113
137
  static async generate(args) {
114
138
  const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
115
139
  const cmd = [
@@ -105,6 +105,21 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
105
105
  await emu.reloadTriggers();
106
106
  res.status(200).json({ enabled: true });
107
107
  });
108
+ app.post(EmulatorHub.PATH_CLEAR_DATA_CONNECT, async (req, res) => {
109
+ if (req.headers.origin) {
110
+ res.status(403).json({
111
+ message: `Clear Data Connect cannot be triggered by external callers.`,
112
+ });
113
+ }
114
+ utils.logLabeledBullet("emulators", `Clearing data from Data Connect data sources.`);
115
+ const instance = registry_1.EmulatorRegistry.get(types_1.Emulators.DATACONNECT);
116
+ if (!instance) {
117
+ res.status(400).json({ error: "The Data Connect emulator is not running." });
118
+ return;
119
+ }
120
+ await instance.clearData();
121
+ res.status(200).send("Data cleared");
122
+ });
108
123
  return app;
109
124
  }
110
125
  async stop() {
@@ -169,3 +184,4 @@ EmulatorHub.PATH_EXPORT = "/_admin/export";
169
184
  EmulatorHub.PATH_DISABLE_FUNCTIONS = "/functions/disableBackgroundTriggers";
170
185
  EmulatorHub.PATH_ENABLE_FUNCTIONS = "/functions/enableBackgroundTriggers";
171
186
  EmulatorHub.PATH_EMULATORS = "/emulators";
187
+ EmulatorHub.PATH_CLEAR_DATA_CONNECT = "/dataconnect/clearData";
@@ -71,6 +71,13 @@ class HubExport {
71
71
  };
72
72
  await this.exportStorage(metadata);
73
73
  }
74
+ if (shouldExport(types_1.Emulators.DATACONNECT)) {
75
+ metadata.dataconnect = {
76
+ version: hub_1.EmulatorHub.CLI_VERSION,
77
+ path: "dataconnect_export",
78
+ };
79
+ await this.exportDataConnect(metadata);
80
+ }
74
81
  if (!fs.existsSync(this.exportPath)) {
75
82
  fs.mkdirSync(this.exportPath);
76
83
  }
@@ -197,6 +204,22 @@ class HubExport {
197
204
  throw new error_1.FirebaseError(`Failed to export storage: ${await res.response.text()}`);
198
205
  }
199
206
  }
207
+ async exportDataConnect(metadata) {
208
+ void (0, track_1.trackEmulator)("emulator_export", {
209
+ initiated_by: this.options.initiatedBy,
210
+ emulator_name: types_1.Emulators.DATACONNECT,
211
+ });
212
+ const instance = registry_1.EmulatorRegistry.get(types_1.Emulators.DATACONNECT);
213
+ if (!instance) {
214
+ throw new error_1.FirebaseError("Unable to export Data Connect emulator data: the Data Connect emulator is not running.");
215
+ }
216
+ const dataconnectExportPath = path.join(this.tmpDir, metadata.dataconnect.path);
217
+ if (fs.existsSync(dataconnectExportPath)) {
218
+ fse.removeSync(dataconnectExportPath);
219
+ }
220
+ fs.mkdirSync(dataconnectExportPath);
221
+ await instance.exportData(dataconnectExportPath);
222
+ }
200
223
  }
201
224
  exports.HubExport = HubExport;
202
225
  HubExport.METADATA_FILE_NAME = "firebase-export-metadata.json";
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AdditionalInitFns = void 0;
4
+ const path_1 = require("path");
5
+ const prompt_1 = require("../prompt");
6
+ const developmentServer_1 = require("./apphosting/developmentServer");
7
+ const emulatorLogger_1 = require("./emulatorLogger");
8
+ const types_1 = require("./types");
9
+ const config_1 = require("../apphosting/config");
10
+ const detectProjectRoot_1 = require("../detectProjectRoot");
11
+ exports.AdditionalInitFns = {
12
+ [types_1.Emulators.APPHOSTING]: async () => {
13
+ var _a;
14
+ const cwd = process.cwd();
15
+ const additionalConfigs = new Map();
16
+ const logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
17
+ logger.logLabeled("INFO", "Initializing Emulator");
18
+ const backendRelativeDir = await (0, prompt_1.promptOnce)({
19
+ name: "rootDir",
20
+ type: "input",
21
+ default: "./",
22
+ message: "Specify your app's root directory relative to your repository",
23
+ });
24
+ additionalConfigs.set("rootDirectory", backendRelativeDir);
25
+ const backendRoot = (0, path_1.join)(cwd, backendRelativeDir);
26
+ try {
27
+ const startCommand = await (0, developmentServer_1.detectStartCommand)(backendRoot);
28
+ additionalConfigs.set("startCommandOverride", startCommand);
29
+ }
30
+ catch (e) {
31
+ logger.log("WARN", "Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommandOverride`");
32
+ }
33
+ try {
34
+ const projectRoot = (_a = (0, detectProjectRoot_1.detectProjectRoot)({})) !== null && _a !== void 0 ? _a : backendRoot;
35
+ await (0, config_1.exportConfig)(cwd, projectRoot, backendRoot);
36
+ }
37
+ catch (e) {
38
+ logger.log("WARN", "failed to export app hosting configs");
39
+ }
40
+ return mapToObject(additionalConfigs);
41
+ },
42
+ };
43
+ function mapToObject(map) {
44
+ const newObject = {};
45
+ for (const [key, value] of map) {
46
+ newObject[key] = value;
47
+ }
48
+ return newObject;
49
+ }
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Severity = exports.EmulatorLog = exports.FunctionsExecutionMode = exports.isEmulator = exports.isDownloadableEmulator = exports.ALL_EMULATORS = exports.EMULATORS_SUPPORTED_BY_USE_EMULATOR = exports.EMULATORS_SUPPORTED_BY_UI = exports.EMULATORS_SUPPORTED_BY_FUNCTIONS = exports.ALL_SERVICE_EMULATORS = exports.IMPORT_EXPORT_EMULATORS = exports.DOWNLOADABLE_EMULATORS = exports.Emulators = void 0;
4
- const experiments = require("../experiments");
5
4
  var Emulators;
6
5
  (function (Emulators) {
7
6
  Emulators["AUTH"] = "auth";
@@ -33,9 +32,10 @@ exports.IMPORT_EXPORT_EMULATORS = [
33
32
  Emulators.DATABASE,
34
33
  Emulators.AUTH,
35
34
  Emulators.STORAGE,
35
+ Emulators.DATACONNECT,
36
36
  ];
37
37
  exports.ALL_SERVICE_EMULATORS = [
38
- ...(experiments.isEnabled("emulatorapphosting") ? [Emulators.APPHOSTING] : []),
38
+ Emulators.APPHOSTING,
39
39
  Emulators.AUTH,
40
40
  Emulators.FUNCTIONS,
41
41
  Emulators.FIRESTORE,
@@ -381,6 +381,7 @@ function endpointFromFunction(gcfFunction) {
381
381
  endpoint.runServiceId = utils.last(serviceName.split("/"));
382
382
  }
383
383
  }
384
+ proto.renameIfPresent(endpoint, gcfFunction, "uri", "url");
384
385
  endpoint.codebase = ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[constants_1.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
385
386
  if ((_f = gcfFunction.labels) === null || _f === void 0 ? void 0 : _f[constants_1.HASH_LABEL]) {
386
387
  endpoint.hash = gcfFunction.labels[constants_1.HASH_LABEL];
@@ -7,6 +7,7 @@ const prompt_1 = require("../../prompt");
7
7
  const types_1 = require("../../emulator/types");
8
8
  const constants_1 = require("../../emulator/constants");
9
9
  const downloadableEmulators_1 = require("../../emulator/downloadableEmulators");
10
+ const initEmulators_1 = require("../../emulator/initEmulators");
10
11
  async function doSetup(setup, config) {
11
12
  var _a, _b, _c;
12
13
  const choices = types_1.ALL_SERVICE_EMULATORS.map((e) => {
@@ -46,6 +47,13 @@ async function doSetup(setup, config) {
46
47
  },
47
48
  ]);
48
49
  }
50
+ const additionalInitFn = initEmulators_1.AdditionalInitFns[selected];
51
+ if (additionalInitFn) {
52
+ const additionalOptions = await additionalInitFn();
53
+ if (additionalOptions) {
54
+ setup.config.emulators[selected] = Object.assign(Object.assign({}, setup.config.emulators[selected]), additionalOptions);
55
+ }
56
+ }
49
57
  }
50
58
  if (selections.emulators.length) {
51
59
  const uiDesc = constants_1.Constants.description(types_1.Emulators.UI);
package/lib/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
4
- exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = exports.openInBrowserPopup = void 0;
4
+ exports.updateOrCreateGitignore = exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = exports.openInBrowserPopup = void 0;
5
5
  const fs = require("fs-extra");
6
6
  const tty = require("tty");
7
7
  const path = require("node:path");
@@ -597,3 +597,18 @@ function readSecretValue(prompt, dataFile) {
597
597
  }
598
598
  }
599
599
  exports.readSecretValue = readSecretValue;
600
+ function updateOrCreateGitignore(dirPath, entries) {
601
+ const gitignorePath = path.join(dirPath, ".gitignore");
602
+ if (!fs.existsSync(gitignorePath)) {
603
+ fs.writeFileSync(gitignorePath, entries.join("\n"));
604
+ return;
605
+ }
606
+ let content = fs.readFileSync(gitignorePath, "utf-8");
607
+ for (const entry of entries) {
608
+ if (!content.includes(entry)) {
609
+ content += `\n${entry}\n`;
610
+ }
611
+ }
612
+ fs.writeFileSync(gitignorePath, content);
613
+ }
614
+ exports.updateOrCreateGitignore = updateOrCreateGitignore;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.26.0",
3
+ "version": "13.27.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -402,6 +402,9 @@
402
402
  "dataconnect": {
403
403
  "additionalProperties": false,
404
404
  "properties": {
405
+ "dataDir": {
406
+ "type": "string"
407
+ },
405
408
  "host": {
406
409
  "type": "string"
407
410
  },
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.discoverPackageManager = exports.logger = void 0;
4
- const fs_extra_1 = require("fs-extra");
5
- const path_1 = require("path");
6
- const emulatorLogger_1 = require("../emulatorLogger");
7
- const types_1 = require("../types");
8
- exports.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
9
- async function discoverPackageManager(rootdir) {
10
- if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "pnpm-lock.yaml"))) {
11
- return "pnpm";
12
- }
13
- if (await (0, fs_extra_1.pathExists)((0, path_1.join)(rootdir, "yarn.lock"))) {
14
- return "yarn";
15
- }
16
- return "npm";
17
- }
18
- exports.discoverPackageManager = discoverPackageManager;