No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

commandUtils.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. 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;
  4. const clc = require("colorette");
  5. const childProcess = require("child_process");
  6. const controller = require("../emulator/controller");
  7. const config_1 = require("../config");
  8. const utils = require("../utils");
  9. const logger_1 = require("../logger");
  10. const path = require("path");
  11. const constants_1 = require("./constants");
  12. const requireAuth_1 = require("../requireAuth");
  13. const requireConfig_1 = require("../requireConfig");
  14. const types_1 = require("./types");
  15. const error_1 = require("../error");
  16. const registry_1 = require("./registry");
  17. const projectUtils_1 = require("../projectUtils");
  18. const prompt_1 = require("../prompt");
  19. const fsutils = require("../fsutils");
  20. const Table = require("cli-table");
  21. const track_1 = require("../track");
  22. const env_1 = require("./env");
  23. exports.FLAG_ONLY = "--only <emulators>";
  24. exports.DESC_ONLY = "only specific emulators. " +
  25. "This is a comma separated list of emulator names. " +
  26. "Valid options are: " +
  27. JSON.stringify(types_1.ALL_SERVICE_EMULATORS);
  28. exports.FLAG_INSPECT_FUNCTIONS = "--inspect-functions [port]";
  29. exports.DESC_INSPECT_FUNCTIONS = "emulate Cloud Functions in debug mode with the node inspector on the given port (9229 if not specified)";
  30. exports.FLAG_IMPORT = "--import [dir]";
  31. exports.DESC_IMPORT = "import emulator data from a previous export (see emulators:export)";
  32. exports.FLAG_EXPORT_ON_EXIT_NAME = "--export-on-exit";
  33. exports.FLAG_EXPORT_ON_EXIT = `${exports.FLAG_EXPORT_ON_EXIT_NAME} [dir]`;
  34. exports.DESC_EXPORT_ON_EXIT = "automatically export emulator data (emulators:export) " +
  35. "when the emulators make a clean exit (SIGINT), " +
  36. `when no dir is provided the location of ${exports.FLAG_IMPORT} is used`;
  37. exports.EXPORT_ON_EXIT_USAGE_ERROR = `"${exports.FLAG_EXPORT_ON_EXIT_NAME}" must be used with "${exports.FLAG_IMPORT}"` +
  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.`;
  40. exports.FLAG_UI = "--ui";
  41. exports.DESC_UI = "run the Emulator UI";
  42. exports.FLAG_TEST_CONFIG = "--test-config <firebase.json file>";
  43. exports.DESC_TEST_CONFIG = "A firebase.json style file. Used to configure the Firestore and Realtime Database emulators.";
  44. exports.FLAG_TEST_PARAMS = "--test-params <params.env file>";
  45. exports.DESC_TEST_PARAMS = "A .env file containing test param values for your emulated extension.";
  46. const DEFAULT_CONFIG = new config_1.Config({
  47. eventarc: {},
  48. database: {},
  49. firestore: {},
  50. functions: {},
  51. hosting: {},
  52. emulators: { auth: {}, pubsub: {} },
  53. }, {});
  54. function printNoticeIfEmulated(options, emulator) {
  55. if (emulator !== types_1.Emulators.DATABASE && emulator !== types_1.Emulators.FIRESTORE) {
  56. return;
  57. }
  58. const emuName = constants_1.Constants.description(emulator);
  59. const envKey = emulator === types_1.Emulators.DATABASE
  60. ? constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST
  61. : constants_1.Constants.FIRESTORE_EMULATOR_HOST;
  62. const envVal = process.env[envKey];
  63. if (envVal) {
  64. utils.logBullet(`You have set ${clc.bold(`${envKey}=${envVal}`)}, this command will execute against the ${emuName} running at that address.`);
  65. }
  66. }
  67. exports.printNoticeIfEmulated = printNoticeIfEmulated;
  68. function warnEmulatorNotSupported(options, emulator) {
  69. if (emulator !== types_1.Emulators.DATABASE && emulator !== types_1.Emulators.FIRESTORE) {
  70. return;
  71. }
  72. const emuName = constants_1.Constants.description(emulator);
  73. const envKey = emulator === types_1.Emulators.DATABASE
  74. ? constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST
  75. : constants_1.Constants.FIRESTORE_EMULATOR_HOST;
  76. const envVal = process.env[envKey];
  77. if (envVal) {
  78. 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.`);
  79. const opts = {
  80. confirm: undefined,
  81. };
  82. return (0, prompt_1.promptOnce)({
  83. type: "confirm",
  84. default: false,
  85. message: "Do you want to continue?",
  86. }).then(() => {
  87. if (!opts.confirm) {
  88. return utils.reject("Command aborted.", { exit: 1 });
  89. }
  90. });
  91. }
  92. }
  93. exports.warnEmulatorNotSupported = warnEmulatorNotSupported;
  94. async function beforeEmulatorCommand(options) {
  95. const optionsWithDefaultConfig = Object.assign(Object.assign({}, options), { config: DEFAULT_CONFIG });
  96. const optionsWithConfig = options.config ? options : optionsWithDefaultConfig;
  97. const canStartWithoutConfig = options.only &&
  98. !controller.shouldStart(optionsWithConfig, types_1.Emulators.FUNCTIONS) &&
  99. !controller.shouldStart(optionsWithConfig, types_1.Emulators.HOSTING);
  100. try {
  101. await (0, requireAuth_1.requireAuth)(options);
  102. }
  103. catch (e) {
  104. logger_1.logger.debug(e);
  105. 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.`);
  106. }
  107. if (canStartWithoutConfig && !options.config) {
  108. utils.logWarning("Could not find config (firebase.json) so using defaults.");
  109. options.config = DEFAULT_CONFIG;
  110. }
  111. else {
  112. await (0, requireConfig_1.requireConfig)(options);
  113. }
  114. }
  115. exports.beforeEmulatorCommand = beforeEmulatorCommand;
  116. function parseInspectionPort(options) {
  117. let port = options.inspectFunctions;
  118. if (port === true) {
  119. port = "9229";
  120. }
  121. const parsed = Number(port);
  122. if (isNaN(parsed) || parsed < 1024 || parsed > 65535) {
  123. throw new error_1.FirebaseError(`"${port}" is not a valid port for debugging, please pass an integer between 1024 and 65535.`);
  124. }
  125. return parsed;
  126. }
  127. exports.parseInspectionPort = parseInspectionPort;
  128. function setExportOnExitOptions(options) {
  129. if (options.exportOnExit || typeof options.exportOnExit === "string") {
  130. if (options.import) {
  131. options.exportOnExit =
  132. typeof options.exportOnExit === "string" ? options.exportOnExit : options.import;
  133. const importPath = path.resolve(options.import);
  134. if (!fsutils.dirExistsSync(importPath) && options.import === options.exportOnExit) {
  135. options.exportOnExit = options.import;
  136. delete options.import;
  137. }
  138. }
  139. if (options.exportOnExit === true || !options.exportOnExit) {
  140. throw new error_1.FirebaseError(exports.EXPORT_ON_EXIT_USAGE_ERROR);
  141. }
  142. if (path.resolve(".").startsWith(path.resolve(options.exportOnExit))) {
  143. throw new error_1.FirebaseError(exports.EXPORT_ON_EXIT_CWD_DANGER);
  144. }
  145. }
  146. return;
  147. }
  148. exports.setExportOnExitOptions = setExportOnExitOptions;
  149. function processKillSignal(signal, res, rej, options) {
  150. let lastSignal = new Date().getTime();
  151. let signalCount = 0;
  152. return async () => {
  153. var _a;
  154. try {
  155. const now = new Date().getTime();
  156. const diff = now - lastSignal;
  157. if (diff < 100) {
  158. logger_1.logger.debug(`Ignoring signal ${signal} due to short delay of ${diff}ms`);
  159. return;
  160. }
  161. signalCount = signalCount + 1;
  162. lastSignal = now;
  163. const signalDisplay = signal === "SIGINT" ? `SIGINT (Ctrl-C)` : signal;
  164. logger_1.logger.debug(`Received signal ${signalDisplay} ${signalCount}`);
  165. logger_1.logger.info(" ");
  166. if (signalCount === 1) {
  167. utils.logLabeledBullet("emulators", `Received ${signalDisplay} for the first time. Starting a clean shutdown.`);
  168. utils.logLabeledBullet("emulators", `Please wait for a clean shutdown or send the ${signalDisplay} signal again to stop right now.`);
  169. await controller.onExit(options);
  170. await controller.cleanShutdown();
  171. }
  172. else {
  173. logger_1.logger.debug(`Skipping clean onExit() and cleanShutdown()`);
  174. const runningEmulatorsInfosWithPid = registry_1.EmulatorRegistry.listRunningWithInfo().filter((i) => Boolean(i.pid));
  175. 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: `);
  176. const pids = [];
  177. const emulatorsTable = new Table({
  178. head: ["Emulator", "Host:Port", "PID"],
  179. style: {
  180. head: ["yellow"],
  181. },
  182. });
  183. for (const emulatorInfo of runningEmulatorsInfosWithPid) {
  184. pids.push(emulatorInfo.pid);
  185. emulatorsTable.push([
  186. constants_1.Constants.description(emulatorInfo.name),
  187. (_a = getListenOverview(emulatorInfo.name)) !== null && _a !== void 0 ? _a : "unknown",
  188. emulatorInfo.pid,
  189. ]);
  190. }
  191. logger_1.logger.info(`\n${emulatorsTable}\n\nTo force them to exit run:\n`);
  192. if (process.platform === "win32") {
  193. logger_1.logger.info(clc.bold(`TASKKILL ${pids.map((pid) => "/PID " + pid).join(" ")} /T\n`));
  194. }
  195. else {
  196. logger_1.logger.info(clc.bold(`kill ${pids.join(" ")}\n`));
  197. }
  198. }
  199. res();
  200. }
  201. catch (e) {
  202. logger_1.logger.debug(e);
  203. rej();
  204. }
  205. };
  206. }
  207. function shutdownWhenKilled(options) {
  208. return new Promise((res, rej) => {
  209. ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"].forEach((signal) => {
  210. process.on(signal, processKillSignal(signal, res, rej, options));
  211. });
  212. }).catch((e) => {
  213. logger_1.logger.debug(e);
  214. utils.logLabeledWarning("emulators", "emulators failed to shut down cleanly, see firebase-debug.log for details.");
  215. throw e;
  216. });
  217. }
  218. exports.shutdownWhenKilled = shutdownWhenKilled;
  219. async function runScript(script, extraEnv) {
  220. utils.logBullet(`Running script: ${clc.bold(script)}`);
  221. const env = Object.assign(Object.assign({}, process.env), extraEnv);
  222. const emulatorInfos = registry_1.EmulatorRegistry.listRunningWithInfo();
  223. (0, env_1.setEnvVarsForEmulators)(env, emulatorInfos);
  224. const proc = childProcess.spawn(script, {
  225. stdio: ["inherit", "inherit", "inherit"],
  226. shell: true,
  227. windowsHide: true,
  228. env,
  229. });
  230. logger_1.logger.debug(`Running ${script} with environment ${JSON.stringify(env)}`);
  231. return new Promise((resolve, reject) => {
  232. proc.on("error", (err) => {
  233. utils.logWarning(`There was an error running the script: ${JSON.stringify(err)}`);
  234. reject();
  235. });
  236. const exitDelayMs = 500;
  237. proc.once("exit", (code, signal) => {
  238. if (signal) {
  239. utils.logWarning(`Script exited with signal: ${signal}`);
  240. setTimeout(reject, exitDelayMs);
  241. return;
  242. }
  243. const exitCode = code || 0;
  244. if (code === 0) {
  245. utils.logSuccess(`Script exited successfully (code 0)`);
  246. }
  247. else {
  248. utils.logWarning(`Script exited unsuccessfully (code ${code})`);
  249. }
  250. setTimeout(() => {
  251. resolve(exitCode);
  252. }, exitDelayMs);
  253. });
  254. });
  255. }
  256. function getListenOverview(emulator) {
  257. var _a;
  258. const info = (_a = registry_1.EmulatorRegistry.get(emulator)) === null || _a === void 0 ? void 0 : _a.getInfo();
  259. if (!info) {
  260. return undefined;
  261. }
  262. if (info.host.includes(":")) {
  263. return `[${info.host}]:${info.port}`;
  264. }
  265. else {
  266. return `${info.host}:${info.port}`;
  267. }
  268. }
  269. exports.getListenOverview = getListenOverview;
  270. async function emulatorExec(script, options) {
  271. const projectId = (0, projectUtils_1.getProjectId)(options);
  272. const extraEnv = {};
  273. if (projectId) {
  274. extraEnv.GCLOUD_PROJECT = projectId;
  275. }
  276. const session = (0, track_1.emulatorSession)();
  277. if (session && session.debugMode) {
  278. extraEnv[constants_1.Constants.FIREBASE_GA_SESSION] = JSON.stringify(session);
  279. }
  280. let exitCode = 0;
  281. let deprecationNotices;
  282. try {
  283. const showUI = !!options.ui;
  284. ({ deprecationNotices } = await controller.startAll(options, showUI));
  285. exitCode = await runScript(script, extraEnv);
  286. await controller.onExit(options);
  287. }
  288. finally {
  289. await controller.cleanShutdown();
  290. }
  291. for (const notice of deprecationNotices) {
  292. utils.logLabeledWarning("emulators", notice, "warn");
  293. }
  294. if (exitCode !== 0) {
  295. throw new error_1.FirebaseError(`Script "${clc.bold(script)}" exited with code ${exitCode}`, {
  296. exit: exitCode,
  297. });
  298. }
  299. }
  300. exports.emulatorExec = emulatorExec;
  301. const JAVA_VERSION_REGEX = /version "([1-9][0-9]*)/;
  302. const JAVA_HINT = "Please make sure Java is installed and on your system PATH.";
  303. async function checkJavaMajorVersion() {
  304. return new Promise((resolve, reject) => {
  305. var _a, _b;
  306. let child;
  307. try {
  308. child = childProcess.spawn("java", ["-Duser.language=en", "-Dfile.encoding=UTF-8", "-version"], {
  309. stdio: ["inherit", "pipe", "pipe"],
  310. });
  311. }
  312. catch (err) {
  313. return reject(new error_1.FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err }));
  314. }
  315. let output = "";
  316. let error = "";
  317. (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (data) => {
  318. const str = data.toString("utf8");
  319. logger_1.logger.debug(str);
  320. output += str;
  321. });
  322. (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (data) => {
  323. const str = data.toString("utf8");
  324. logger_1.logger.debug(str);
  325. error += str;
  326. });
  327. child.once("error", (err) => {
  328. reject(new error_1.FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err }));
  329. });
  330. child.once("exit", (code, signal) => {
  331. if (signal) {
  332. reject(new error_1.FirebaseError(`Process \`java -version\` was killed by signal ${signal}.`));
  333. }
  334. else if (code && code !== 0) {
  335. reject(new error_1.FirebaseError(`Process \`java -version\` has exited with code ${code}. ${JAVA_HINT}\n` +
  336. `-----Original stdout-----\n${output}` +
  337. `-----Original stderr-----\n${error}`));
  338. }
  339. else {
  340. resolve(`${output}\n${error}`);
  341. }
  342. });
  343. }).then((output) => {
  344. let versionInt = -1;
  345. const match = output.match(JAVA_VERSION_REGEX);
  346. if (match) {
  347. const version = match[1];
  348. versionInt = parseInt(version, 10);
  349. if (!versionInt) {
  350. utils.logLabeledWarning("emulators", `Failed to parse Java version. Got "${match[0]}".`, "warn");
  351. }
  352. else {
  353. logger_1.logger.debug(`Parsed Java major version: ${versionInt}`);
  354. }
  355. }
  356. else {
  357. logger_1.logger.debug("java -version outputs:", output);
  358. logger_1.logger.warn(`Failed to parse Java version.`);
  359. }
  360. const session = (0, track_1.emulatorSession)();
  361. if (session) {
  362. session.javaMajorVersion = versionInt;
  363. }
  364. return versionInt;
  365. });
  366. }
  367. exports.checkJavaMajorVersion = checkJavaMajorVersion;
  368. exports.MIN_SUPPORTED_JAVA_MAJOR_VERSION = 11;
  369. exports.JAVA_DEPRECATION_WARNING = "firebase-tools no longer supports Java version before 11. " +
  370. "Please upgrade to Java version 11 or above to continue using the emulators.";