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.

downloadableEmulators.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = exports.DownloadDetails = void 0;
  4. const types_1 = require("./types");
  5. const constants_1 = require("./constants");
  6. const error_1 = require("../error");
  7. const childProcess = require("child_process");
  8. const utils = require("../utils");
  9. const emulatorLogger_1 = require("./emulatorLogger");
  10. const clc = require("colorette");
  11. const fs = require("fs-extra");
  12. const path = require("path");
  13. const os = require("os");
  14. const registry_1 = require("./registry");
  15. const download_1 = require("../emulator/download");
  16. const experiments = require("../experiments");
  17. const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000;
  18. const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators");
  19. const EMULATOR_UPDATE_DETAILS = {
  20. database: {
  21. version: "4.11.0",
  22. expectedSize: 34318940,
  23. expectedChecksum: "311609538bd65666eb724ef47c2e6466",
  24. },
  25. firestore: {
  26. version: "1.15.1",
  27. expectedSize: 61475851,
  28. expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee",
  29. },
  30. storage: {
  31. version: "1.1.3",
  32. expectedSize: 52892936,
  33. expectedChecksum: "2ca11ec1193003bea89f806cc085fa25",
  34. },
  35. ui: experiments.isEnabled("emulatoruisnapshot")
  36. ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" }
  37. : {
  38. version: "1.11.2",
  39. expectedSize: 3062873,
  40. expectedChecksum: "fe7f668437d0e3c3b92677aaaade78bf",
  41. },
  42. pubsub: {
  43. version: "0.7.1",
  44. expectedSize: 65137179,
  45. expectedChecksum: "b59a6e705031a54a69e5e1dced7ca9bf",
  46. },
  47. };
  48. exports.DownloadDetails = {
  49. database: {
  50. downloadPath: path.join(CACHE_DIR, `firebase-database-emulator-v${EMULATOR_UPDATE_DETAILS.database.version}.jar`),
  51. version: EMULATOR_UPDATE_DETAILS.database.version,
  52. opts: {
  53. cacheDir: CACHE_DIR,
  54. remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v${EMULATOR_UPDATE_DETAILS.database.version}.jar`,
  55. expectedSize: EMULATOR_UPDATE_DETAILS.database.expectedSize,
  56. expectedChecksum: EMULATOR_UPDATE_DETAILS.database.expectedChecksum,
  57. namePrefix: "firebase-database-emulator",
  58. },
  59. },
  60. firestore: {
  61. downloadPath: path.join(CACHE_DIR, `cloud-firestore-emulator-v${EMULATOR_UPDATE_DETAILS.firestore.version}.jar`),
  62. version: EMULATOR_UPDATE_DETAILS.firestore.version,
  63. opts: {
  64. cacheDir: CACHE_DIR,
  65. remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v${EMULATOR_UPDATE_DETAILS.firestore.version}.jar`,
  66. expectedSize: EMULATOR_UPDATE_DETAILS.firestore.expectedSize,
  67. expectedChecksum: EMULATOR_UPDATE_DETAILS.firestore.expectedChecksum,
  68. namePrefix: "cloud-firestore-emulator",
  69. },
  70. },
  71. storage: {
  72. downloadPath: path.join(CACHE_DIR, `cloud-storage-rules-runtime-v${EMULATOR_UPDATE_DETAILS.storage.version}.jar`),
  73. version: EMULATOR_UPDATE_DETAILS.storage.version,
  74. opts: {
  75. cacheDir: CACHE_DIR,
  76. remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-storage-rules-runtime-v${EMULATOR_UPDATE_DETAILS.storage.version}.jar`,
  77. expectedSize: EMULATOR_UPDATE_DETAILS.storage.expectedSize,
  78. expectedChecksum: EMULATOR_UPDATE_DETAILS.storage.expectedChecksum,
  79. namePrefix: "cloud-storage-rules-emulator",
  80. },
  81. },
  82. ui: {
  83. version: EMULATOR_UPDATE_DETAILS.ui.version,
  84. downloadPath: path.join(CACHE_DIR, `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}.zip`),
  85. unzipDir: path.join(CACHE_DIR, `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}`),
  86. binaryPath: path.join(CACHE_DIR, `ui-v${EMULATOR_UPDATE_DETAILS.ui.version}`, "server", "server.js"),
  87. opts: {
  88. cacheDir: CACHE_DIR,
  89. remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v${EMULATOR_UPDATE_DETAILS.ui.version}.zip`,
  90. expectedSize: EMULATOR_UPDATE_DETAILS.ui.expectedSize,
  91. expectedChecksum: EMULATOR_UPDATE_DETAILS.ui.expectedChecksum,
  92. skipCache: experiments.isEnabled("emulatoruisnapshot"),
  93. skipChecksumAndSize: experiments.isEnabled("emulatoruisnapshot"),
  94. namePrefix: "ui",
  95. },
  96. },
  97. pubsub: {
  98. downloadPath: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}.zip`),
  99. version: EMULATOR_UPDATE_DETAILS.pubsub.version,
  100. unzipDir: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`),
  101. binaryPath: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`, `pubsub-emulator/bin/cloud-pubsub-emulator${process.platform === "win32" ? ".bat" : ""}`),
  102. opts: {
  103. cacheDir: CACHE_DIR,
  104. remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}.zip`,
  105. expectedSize: EMULATOR_UPDATE_DETAILS.pubsub.expectedSize,
  106. expectedChecksum: EMULATOR_UPDATE_DETAILS.pubsub.expectedChecksum,
  107. namePrefix: "pubsub-emulator",
  108. },
  109. },
  110. };
  111. const EmulatorDetails = {
  112. database: {
  113. name: types_1.Emulators.DATABASE,
  114. instance: null,
  115. stdout: null,
  116. },
  117. firestore: {
  118. name: types_1.Emulators.FIRESTORE,
  119. instance: null,
  120. stdout: null,
  121. },
  122. storage: {
  123. name: types_1.Emulators.STORAGE,
  124. instance: null,
  125. stdout: null,
  126. },
  127. pubsub: {
  128. name: types_1.Emulators.PUBSUB,
  129. instance: null,
  130. stdout: null,
  131. },
  132. ui: {
  133. name: types_1.Emulators.UI,
  134. instance: null,
  135. stdout: null,
  136. },
  137. };
  138. const Commands = {
  139. database: {
  140. binary: "java",
  141. args: ["-Duser.language=en", "-jar", getExecPath(types_1.Emulators.DATABASE)],
  142. optionalArgs: [
  143. "port",
  144. "host",
  145. "functions_emulator_port",
  146. "functions_emulator_host",
  147. "single_project_mode",
  148. ],
  149. joinArgs: false,
  150. },
  151. firestore: {
  152. binary: "java",
  153. args: [
  154. "-Dgoogle.cloud_firestore.debug_log_level=FINE",
  155. "-Duser.language=en",
  156. "-jar",
  157. getExecPath(types_1.Emulators.FIRESTORE),
  158. ],
  159. optionalArgs: [
  160. "port",
  161. "webchannel_port",
  162. "host",
  163. "rules",
  164. "websocket_port",
  165. "functions_emulator",
  166. "seed_from_export",
  167. "project_id",
  168. "single_project_mode",
  169. ],
  170. joinArgs: false,
  171. },
  172. storage: {
  173. binary: "java",
  174. args: [
  175. "-Duser.language=en",
  176. "-jar",
  177. getExecPath(types_1.Emulators.STORAGE),
  178. "serve",
  179. ],
  180. optionalArgs: [],
  181. joinArgs: false,
  182. },
  183. pubsub: {
  184. binary: getExecPath(types_1.Emulators.PUBSUB),
  185. args: [],
  186. optionalArgs: ["port", "host"],
  187. joinArgs: true,
  188. },
  189. ui: {
  190. binary: "node",
  191. args: [getExecPath(types_1.Emulators.UI)],
  192. optionalArgs: [],
  193. joinArgs: false,
  194. },
  195. };
  196. function getExecPath(name) {
  197. const details = getDownloadDetails(name);
  198. return details.binaryPath || details.downloadPath;
  199. }
  200. function getLogFileName(name) {
  201. return `${name}-debug.log`;
  202. }
  203. exports.getLogFileName = getLogFileName;
  204. function _getCommand(emulator, args) {
  205. const baseCmd = Commands[emulator];
  206. const defaultPort = constants_1.Constants.getDefaultPort(emulator);
  207. if (!args.port) {
  208. args.port = defaultPort;
  209. }
  210. const cmdLineArgs = baseCmd.args.slice();
  211. if (baseCmd.binary === "java" &&
  212. utils.isRunningInWSL() &&
  213. (!args.host || !args.host.includes(":"))) {
  214. cmdLineArgs.unshift("-Djava.net.preferIPv4Stack=true");
  215. }
  216. const logger = emulatorLogger_1.EmulatorLogger.forEmulator(emulator);
  217. Object.keys(args).forEach((key) => {
  218. if (!baseCmd.optionalArgs.includes(key)) {
  219. logger.log("DEBUG", `Ignoring unsupported arg: ${key}`);
  220. return;
  221. }
  222. const argKey = "--" + key;
  223. const argVal = args[key];
  224. if (argVal === undefined) {
  225. logger.log("DEBUG", `Ignoring empty arg for key: ${key}`);
  226. return;
  227. }
  228. if (baseCmd.joinArgs) {
  229. cmdLineArgs.push(`${argKey}=${argVal}`);
  230. }
  231. else {
  232. cmdLineArgs.push(argKey, argVal);
  233. }
  234. });
  235. return {
  236. binary: baseCmd.binary,
  237. args: cmdLineArgs,
  238. optionalArgs: baseCmd.optionalArgs,
  239. joinArgs: baseCmd.joinArgs,
  240. };
  241. }
  242. exports._getCommand = _getCommand;
  243. async function _fatal(emulator, errorMsg) {
  244. try {
  245. const logger = emulatorLogger_1.EmulatorLogger.forEmulator(emulator);
  246. logger.logLabeled("WARN", emulator, `Fatal error occurred: \n ${errorMsg}, \n stopping all running emulators`);
  247. await registry_1.EmulatorRegistry.stopAll();
  248. }
  249. finally {
  250. process.exit(1);
  251. }
  252. }
  253. async function handleEmulatorProcessError(emulator, err) {
  254. const description = constants_1.Constants.description(emulator);
  255. if (err.path === "java" && err.code === "ENOENT") {
  256. await _fatal(emulator, `${description} has exited because java is not installed, you can install it from https://openjdk.java.net/install/`);
  257. }
  258. else {
  259. await _fatal(emulator, `${description} has exited: ${err}`);
  260. }
  261. }
  262. exports.handleEmulatorProcessError = handleEmulatorProcessError;
  263. function requiresJava(emulator) {
  264. if (emulator in Commands) {
  265. return Commands[emulator].binary === "java";
  266. }
  267. return false;
  268. }
  269. exports.requiresJava = requiresJava;
  270. async function _runBinary(emulator, command, extraEnv) {
  271. return new Promise((resolve) => {
  272. var _a, _b;
  273. const logger = emulatorLogger_1.EmulatorLogger.forEmulator(emulator.name);
  274. emulator.stdout = fs.createWriteStream(getLogFileName(emulator.name));
  275. try {
  276. emulator.instance = childProcess.spawn(command.binary, command.args, {
  277. env: Object.assign(Object.assign({}, process.env), extraEnv),
  278. detached: true,
  279. stdio: ["inherit", "pipe", "pipe"],
  280. });
  281. }
  282. catch (e) {
  283. if (e.code === "EACCES") {
  284. logger.logLabeled("WARN", emulator.name, `Could not spawn child process for emulator, check that java is installed and on your $PATH.`);
  285. }
  286. _fatal(emulator.name, e);
  287. }
  288. const description = constants_1.Constants.description(emulator.name);
  289. if (emulator.instance == null) {
  290. logger.logLabeled("WARN", emulator.name, `Could not spawn child process for ${description}.`);
  291. return;
  292. }
  293. logger.logLabeled("BULLET", emulator.name, `${description} logging to ${clc.bold(getLogFileName(emulator.name))}`);
  294. (_a = emulator.instance.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (data) => {
  295. logger.log("DEBUG", data.toString());
  296. emulator.stdout.write(data);
  297. });
  298. (_b = emulator.instance.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (data) => {
  299. logger.log("DEBUG", data.toString());
  300. emulator.stdout.write(data);
  301. if (data.toString().includes("java.lang.UnsupportedClassVersionError")) {
  302. logger.logLabeled("WARN", emulator.name, "Unsupported java version, make sure java --version reports 1.8 or higher.");
  303. }
  304. });
  305. emulator.instance.on("error", (err) => {
  306. handleEmulatorProcessError(emulator.name, err);
  307. });
  308. emulator.instance.once("exit", async (code, signal) => {
  309. if (signal) {
  310. utils.logWarning(`${description} has exited upon receiving signal: ${signal}`);
  311. }
  312. else if (code && code !== 0 && code !== 130) {
  313. await _fatal(emulator.name, `${description} has exited with code: ${code}`);
  314. }
  315. });
  316. resolve();
  317. });
  318. }
  319. function getDownloadDetails(emulator) {
  320. return exports.DownloadDetails[emulator];
  321. }
  322. exports.getDownloadDetails = getDownloadDetails;
  323. function get(emulator) {
  324. return EmulatorDetails[emulator];
  325. }
  326. exports.get = get;
  327. function getPID(emulator) {
  328. const emulatorInstance = get(emulator).instance;
  329. return emulatorInstance && emulatorInstance.pid ? emulatorInstance.pid : 0;
  330. }
  331. exports.getPID = getPID;
  332. async function stop(targetName) {
  333. const emulator = get(targetName);
  334. return new Promise((resolve, reject) => {
  335. const logger = emulatorLogger_1.EmulatorLogger.forEmulator(emulator.name);
  336. if (emulator.instance) {
  337. const killTimeout = setTimeout(() => {
  338. const pid = emulator.instance ? emulator.instance.pid : -1;
  339. const errorMsg = constants_1.Constants.description(emulator.name) + ": Unable to terminate process (PID=" + pid + ")";
  340. logger.log("DEBUG", errorMsg);
  341. reject(new error_1.FirebaseError(emulator.name + ": " + errorMsg));
  342. }, EMULATOR_INSTANCE_KILL_TIMEOUT);
  343. emulator.instance.once("exit", () => {
  344. clearTimeout(killTimeout);
  345. resolve();
  346. });
  347. emulator.instance.kill("SIGINT");
  348. }
  349. else {
  350. resolve();
  351. }
  352. });
  353. }
  354. exports.stop = stop;
  355. async function downloadIfNecessary(targetName) {
  356. const hasEmulator = fs.existsSync(getExecPath(targetName));
  357. if (hasEmulator) {
  358. return;
  359. }
  360. await (0, download_1.downloadEmulator)(targetName);
  361. }
  362. exports.downloadIfNecessary = downloadIfNecessary;
  363. async function start(targetName, args, extraEnv = {}) {
  364. const downloadDetails = exports.DownloadDetails[targetName];
  365. const emulator = get(targetName);
  366. const hasEmulator = fs.existsSync(getExecPath(targetName));
  367. const logger = emulatorLogger_1.EmulatorLogger.forEmulator(targetName);
  368. if (!hasEmulator || downloadDetails.opts.skipCache) {
  369. if (args.auto_download) {
  370. if (process.env.CI) {
  371. utils.logWarning(`It appears you are running in a CI environment. You can avoid downloading the ${constants_1.Constants.description(targetName)} repeatedly by caching the ${downloadDetails.opts.cacheDir} directory.`);
  372. }
  373. await (0, download_1.downloadEmulator)(targetName);
  374. }
  375. else {
  376. utils.logWarning("Setup required, please run: firebase setup:emulators:" + targetName);
  377. throw new error_1.FirebaseError("emulator not found");
  378. }
  379. }
  380. const command = _getCommand(targetName, args);
  381. logger.log("DEBUG", `Starting ${constants_1.Constants.description(targetName)} with command ${JSON.stringify(command)}`);
  382. return _runBinary(emulator, command, extraEnv);
  383. }
  384. exports.start = start;