"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JAVA_DEPRECATION_WARNING = exports.MIN_SUPPORTED_JAVA_MAJOR_VERSION = exports.checkJavaMajorVersion = exports.emulatorExec = exports.getListenOverview = 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; const clc = require("colorette"); const childProcess = require("child_process"); const controller = require("../emulator/controller"); const config_1 = require("../config"); const utils = require("../utils"); const logger_1 = require("../logger"); const path = require("path"); const constants_1 = require("./constants"); const requireAuth_1 = require("../requireAuth"); const requireConfig_1 = require("../requireConfig"); const types_1 = require("./types"); const error_1 = require("../error"); const registry_1 = require("./registry"); const projectUtils_1 = require("../projectUtils"); const prompt_1 = require("../prompt"); const fsutils = require("../fsutils"); const Table = require("cli-table"); const track_1 = require("../track"); const env_1 = require("./env"); exports.FLAG_ONLY = "--only "; exports.DESC_ONLY = "only specific emulators. " + "This is a comma separated list of emulator names. " + "Valid options are: " + JSON.stringify(types_1.ALL_SERVICE_EMULATORS); exports.FLAG_INSPECT_FUNCTIONS = "--inspect-functions [port]"; exports.DESC_INSPECT_FUNCTIONS = "emulate Cloud Functions in debug mode with the node inspector on the given port (9229 if not specified)"; exports.FLAG_IMPORT = "--import [dir]"; exports.DESC_IMPORT = "import emulator data from a previous export (see emulators:export)"; exports.FLAG_EXPORT_ON_EXIT_NAME = "--export-on-exit"; exports.FLAG_EXPORT_ON_EXIT = `${exports.FLAG_EXPORT_ON_EXIT_NAME} [dir]`; exports.DESC_EXPORT_ON_EXIT = "automatically export emulator data (emulators:export) " + "when the emulators make a clean exit (SIGINT), " + `when no dir is provided the location of ${exports.FLAG_IMPORT} is used`; exports.EXPORT_ON_EXIT_USAGE_ERROR = `"${exports.FLAG_EXPORT_ON_EXIT_NAME}" must be used with "${exports.FLAG_IMPORT}"` + ` or provide a dir directly to "${exports.FLAG_EXPORT_ON_EXIT}"`; 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.`; exports.FLAG_UI = "--ui"; exports.DESC_UI = "run the Emulator UI"; exports.FLAG_TEST_CONFIG = "--test-config "; exports.DESC_TEST_CONFIG = "A firebase.json style file. Used to configure the Firestore and Realtime Database emulators."; exports.FLAG_TEST_PARAMS = "--test-params "; exports.DESC_TEST_PARAMS = "A .env file containing test param values for your emulated extension."; const DEFAULT_CONFIG = new config_1.Config({ eventarc: {}, database: {}, firestore: {}, functions: {}, hosting: {}, emulators: { auth: {}, pubsub: {} }, }, {}); function printNoticeIfEmulated(options, emulator) { if (emulator !== types_1.Emulators.DATABASE && emulator !== types_1.Emulators.FIRESTORE) { return; } const emuName = constants_1.Constants.description(emulator); const envKey = emulator === types_1.Emulators.DATABASE ? constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST : constants_1.Constants.FIRESTORE_EMULATOR_HOST; const envVal = process.env[envKey]; if (envVal) { utils.logBullet(`You have set ${clc.bold(`${envKey}=${envVal}`)}, this command will execute against the ${emuName} running at that address.`); } } exports.printNoticeIfEmulated = printNoticeIfEmulated; function warnEmulatorNotSupported(options, emulator) { if (emulator !== types_1.Emulators.DATABASE && emulator !== types_1.Emulators.FIRESTORE) { return; } const emuName = constants_1.Constants.description(emulator); const envKey = emulator === types_1.Emulators.DATABASE ? constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST : constants_1.Constants.FIRESTORE_EMULATOR_HOST; const envVal = process.env[envKey]; if (envVal) { utils.logWarning(`You have set ${clc.bold(`${envKey}=${envVal}`)}, however this command does not support running against the ${emuName} so this action will affect production.`); const opts = { confirm: undefined, }; return (0, prompt_1.promptOnce)({ type: "confirm", default: false, message: "Do you want to continue?", }).then(() => { if (!opts.confirm) { return utils.reject("Command aborted.", { exit: 1 }); } }); } } exports.warnEmulatorNotSupported = warnEmulatorNotSupported; async function beforeEmulatorCommand(options) { const optionsWithDefaultConfig = Object.assign(Object.assign({}, options), { config: DEFAULT_CONFIG }); const optionsWithConfig = options.config ? options : optionsWithDefaultConfig; const canStartWithoutConfig = options.only && !controller.shouldStart(optionsWithConfig, types_1.Emulators.FUNCTIONS) && !controller.shouldStart(optionsWithConfig, types_1.Emulators.HOSTING); try { await (0, requireAuth_1.requireAuth)(options); } catch (e) { logger_1.logger.debug(e); utils.logLabeledWarning("emulators", `You are not currently authenticated so some features may not work correctly. Please run ${clc.bold("firebase login")} to authenticate the CLI.`); } if (canStartWithoutConfig && !options.config) { utils.logWarning("Could not find config (firebase.json) so using defaults."); options.config = DEFAULT_CONFIG; } else { await (0, requireConfig_1.requireConfig)(options); } } exports.beforeEmulatorCommand = beforeEmulatorCommand; function parseInspectionPort(options) { let port = options.inspectFunctions; if (port === true) { port = "9229"; } const parsed = Number(port); if (isNaN(parsed) || parsed < 1024 || parsed > 65535) { throw new error_1.FirebaseError(`"${port}" is not a valid port for debugging, please pass an integer between 1024 and 65535.`); } return parsed; } exports.parseInspectionPort = parseInspectionPort; function setExportOnExitOptions(options) { if (options.exportOnExit || typeof options.exportOnExit === "string") { if (options.import) { options.exportOnExit = typeof options.exportOnExit === "string" ? options.exportOnExit : options.import; const importPath = path.resolve(options.import); if (!fsutils.dirExistsSync(importPath) && options.import === options.exportOnExit) { options.exportOnExit = options.import; delete options.import; } } if (options.exportOnExit === true || !options.exportOnExit) { throw new error_1.FirebaseError(exports.EXPORT_ON_EXIT_USAGE_ERROR); } if (path.resolve(".").startsWith(path.resolve(options.exportOnExit))) { throw new error_1.FirebaseError(exports.EXPORT_ON_EXIT_CWD_DANGER); } } return; } exports.setExportOnExitOptions = setExportOnExitOptions; function processKillSignal(signal, res, rej, options) { let lastSignal = new Date().getTime(); let signalCount = 0; return async () => { var _a; try { const now = new Date().getTime(); const diff = now - lastSignal; if (diff < 100) { logger_1.logger.debug(`Ignoring signal ${signal} due to short delay of ${diff}ms`); return; } signalCount = signalCount + 1; lastSignal = now; const signalDisplay = signal === "SIGINT" ? `SIGINT (Ctrl-C)` : signal; logger_1.logger.debug(`Received signal ${signalDisplay} ${signalCount}`); logger_1.logger.info(" "); if (signalCount === 1) { utils.logLabeledBullet("emulators", `Received ${signalDisplay} for the first time. Starting a clean shutdown.`); utils.logLabeledBullet("emulators", `Please wait for a clean shutdown or send the ${signalDisplay} signal again to stop right now.`); await controller.onExit(options); await controller.cleanShutdown(); } else { logger_1.logger.debug(`Skipping clean onExit() and cleanShutdown()`); const runningEmulatorsInfosWithPid = registry_1.EmulatorRegistry.listRunningWithInfo().filter((i) => Boolean(i.pid)); utils.logLabeledWarning("emulators", `Received ${signalDisplay} ${signalCount} times. You have forced the Emulator Suite to exit without waiting for ${runningEmulatorsInfosWithPid.length} subprocess${runningEmulatorsInfosWithPid.length > 1 ? "es" : ""} to finish. These processes ${clc.bold("may")} still be running on your machine: `); const pids = []; const emulatorsTable = new Table({ head: ["Emulator", "Host:Port", "PID"], style: { head: ["yellow"], }, }); for (const emulatorInfo of runningEmulatorsInfosWithPid) { pids.push(emulatorInfo.pid); emulatorsTable.push([ constants_1.Constants.description(emulatorInfo.name), (_a = getListenOverview(emulatorInfo.name)) !== null && _a !== void 0 ? _a : "unknown", emulatorInfo.pid, ]); } logger_1.logger.info(`\n${emulatorsTable}\n\nTo force them to exit run:\n`); if (process.platform === "win32") { logger_1.logger.info(clc.bold(`TASKKILL ${pids.map((pid) => "/PID " + pid).join(" ")} /T\n`)); } else { logger_1.logger.info(clc.bold(`kill ${pids.join(" ")}\n`)); } } res(); } catch (e) { logger_1.logger.debug(e); rej(); } }; } function shutdownWhenKilled(options) { return new Promise((res, rej) => { ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"].forEach((signal) => { process.on(signal, processKillSignal(signal, res, rej, options)); }); }).catch((e) => { logger_1.logger.debug(e); utils.logLabeledWarning("emulators", "emulators failed to shut down cleanly, see firebase-debug.log for details."); throw e; }); } exports.shutdownWhenKilled = shutdownWhenKilled; async function runScript(script, extraEnv) { utils.logBullet(`Running script: ${clc.bold(script)}`); const env = Object.assign(Object.assign({}, process.env), extraEnv); const emulatorInfos = registry_1.EmulatorRegistry.listRunningWithInfo(); (0, env_1.setEnvVarsForEmulators)(env, emulatorInfos); const proc = childProcess.spawn(script, { stdio: ["inherit", "inherit", "inherit"], shell: true, windowsHide: true, env, }); logger_1.logger.debug(`Running ${script} with environment ${JSON.stringify(env)}`); return new Promise((resolve, reject) => { proc.on("error", (err) => { utils.logWarning(`There was an error running the script: ${JSON.stringify(err)}`); reject(); }); const exitDelayMs = 500; proc.once("exit", (code, signal) => { if (signal) { utils.logWarning(`Script exited with signal: ${signal}`); setTimeout(reject, exitDelayMs); return; } const exitCode = code || 0; if (code === 0) { utils.logSuccess(`Script exited successfully (code 0)`); } else { utils.logWarning(`Script exited unsuccessfully (code ${code})`); } setTimeout(() => { resolve(exitCode); }, exitDelayMs); }); }); } function getListenOverview(emulator) { var _a; const info = (_a = registry_1.EmulatorRegistry.get(emulator)) === null || _a === void 0 ? void 0 : _a.getInfo(); if (!info) { return undefined; } if (info.host.includes(":")) { return `[${info.host}]:${info.port}`; } else { return `${info.host}:${info.port}`; } } exports.getListenOverview = getListenOverview; async function emulatorExec(script, options) { const projectId = (0, projectUtils_1.getProjectId)(options); const extraEnv = {}; if (projectId) { extraEnv.GCLOUD_PROJECT = projectId; } const session = (0, track_1.emulatorSession)(); if (session && session.debugMode) { extraEnv[constants_1.Constants.FIREBASE_GA_SESSION] = JSON.stringify(session); } let exitCode = 0; let deprecationNotices; try { const showUI = !!options.ui; ({ deprecationNotices } = await controller.startAll(options, showUI)); exitCode = await runScript(script, extraEnv); await controller.onExit(options); } finally { await controller.cleanShutdown(); } for (const notice of deprecationNotices) { utils.logLabeledWarning("emulators", notice, "warn"); } if (exitCode !== 0) { throw new error_1.FirebaseError(`Script "${clc.bold(script)}" exited with code ${exitCode}`, { exit: exitCode, }); } } exports.emulatorExec = emulatorExec; const JAVA_VERSION_REGEX = /version "([1-9][0-9]*)/; const JAVA_HINT = "Please make sure Java is installed and on your system PATH."; async function checkJavaMajorVersion() { return new Promise((resolve, reject) => { var _a, _b; let child; try { child = childProcess.spawn("java", ["-Duser.language=en", "-Dfile.encoding=UTF-8", "-version"], { stdio: ["inherit", "pipe", "pipe"], }); } catch (err) { return reject(new error_1.FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err })); } let output = ""; let error = ""; (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (data) => { const str = data.toString("utf8"); logger_1.logger.debug(str); output += str; }); (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (data) => { const str = data.toString("utf8"); logger_1.logger.debug(str); error += str; }); child.once("error", (err) => { reject(new error_1.FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err })); }); child.once("exit", (code, signal) => { if (signal) { reject(new error_1.FirebaseError(`Process \`java -version\` was killed by signal ${signal}.`)); } else if (code && code !== 0) { reject(new error_1.FirebaseError(`Process \`java -version\` has exited with code ${code}. ${JAVA_HINT}\n` + `-----Original stdout-----\n${output}` + `-----Original stderr-----\n${error}`)); } else { resolve(`${output}\n${error}`); } }); }).then((output) => { let versionInt = -1; const match = output.match(JAVA_VERSION_REGEX); if (match) { const version = match[1]; versionInt = parseInt(version, 10); if (!versionInt) { utils.logLabeledWarning("emulators", `Failed to parse Java version. Got "${match[0]}".`, "warn"); } else { logger_1.logger.debug(`Parsed Java major version: ${versionInt}`); } } else { logger_1.logger.debug("java -version outputs:", output); logger_1.logger.warn(`Failed to parse Java version.`); } const session = (0, track_1.emulatorSession)(); if (session) { session.javaMajorVersion = versionInt; } return versionInt; }); } exports.checkJavaMajorVersion = checkJavaMajorVersion; exports.MIN_SUPPORTED_JAVA_MAJOR_VERSION = 11; exports.JAVA_DEPRECATION_WARNING = "firebase-tools no longer supports Java version before 11. " + "Please upgrade to Java version 11 or above to continue using the emulators.";