Sin descripción
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.

utils.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = 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.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
  4. const _ = require("lodash");
  5. const url = require("url");
  6. const clc = require("colorette");
  7. const ora = require("ora");
  8. const process = require("process");
  9. const stream_1 = require("stream");
  10. const winston = require("winston");
  11. const triple_beam_1 = require("triple-beam");
  12. const assert_1 = require("assert");
  13. const stripAnsi = require("strip-ansi");
  14. const configstore_1 = require("./configstore");
  15. const error_1 = require("./error");
  16. const logger_1 = require("./logger");
  17. const IS_WINDOWS = process.platform === "win32";
  18. const SUCCESS_CHAR = IS_WINDOWS ? "+" : "✔";
  19. const WARNING_CHAR = IS_WINDOWS ? "!" : "⚠";
  20. const ERROR_CHAR = IS_WINDOWS ? "!!" : "⬢";
  21. const THIRTY_DAYS_IN_MILLISECONDS = 30 * 24 * 60 * 60 * 1000;
  22. exports.envOverrides = [];
  23. function consoleUrl(project, path) {
  24. const api = require("./api");
  25. return `${api.consoleOrigin}/project/${project}${path}`;
  26. }
  27. exports.consoleUrl = consoleUrl;
  28. function getInheritedOption(options, key) {
  29. let target = options;
  30. while (target) {
  31. if (target[key] !== undefined) {
  32. return target[key];
  33. }
  34. target = target.parent;
  35. }
  36. }
  37. exports.getInheritedOption = getInheritedOption;
  38. function envOverride(envname, value, coerce) {
  39. const currentEnvValue = process.env[envname];
  40. if (currentEnvValue && currentEnvValue.length) {
  41. exports.envOverrides.push(envname);
  42. if (coerce) {
  43. try {
  44. return coerce(currentEnvValue, value);
  45. }
  46. catch (e) {
  47. return value;
  48. }
  49. }
  50. return currentEnvValue;
  51. }
  52. return value;
  53. }
  54. exports.envOverride = envOverride;
  55. function getDatabaseUrl(origin, namespace, pathname) {
  56. const withPath = url.resolve(origin, pathname);
  57. return addDatabaseNamespace(withPath, namespace);
  58. }
  59. exports.getDatabaseUrl = getDatabaseUrl;
  60. function getDatabaseViewDataUrl(origin, project, namespace, pathname) {
  61. const urlObj = new url.URL(origin);
  62. if (urlObj.hostname.includes("firebaseio") || urlObj.hostname.includes("firebasedatabase")) {
  63. return consoleUrl(project, `/database/${namespace}/data${pathname}`);
  64. }
  65. return getDatabaseUrl(origin, namespace, pathname + ".json");
  66. }
  67. exports.getDatabaseViewDataUrl = getDatabaseViewDataUrl;
  68. function addDatabaseNamespace(origin, namespace) {
  69. const urlObj = new url.URL(origin);
  70. if (urlObj.hostname.includes(namespace)) {
  71. return urlObj.href;
  72. }
  73. if (urlObj.hostname.includes("firebaseio") || urlObj.hostname.includes("firebasedatabase")) {
  74. return addSubdomain(origin, namespace);
  75. }
  76. urlObj.searchParams.set("ns", namespace);
  77. return urlObj.href;
  78. }
  79. exports.addDatabaseNamespace = addDatabaseNamespace;
  80. function addSubdomain(origin, subdomain) {
  81. return origin.replace("//", `//${subdomain}.`);
  82. }
  83. exports.addSubdomain = addSubdomain;
  84. function logSuccess(message, type = "info", data = undefined) {
  85. logger_1.logger[type](clc.green(clc.bold(`${SUCCESS_CHAR} `)), message, data);
  86. }
  87. exports.logSuccess = logSuccess;
  88. function logLabeledSuccess(label, message, type = "info", data = undefined) {
  89. logger_1.logger[type](clc.green(clc.bold(`${SUCCESS_CHAR} ${label}:`)), message, data);
  90. }
  91. exports.logLabeledSuccess = logLabeledSuccess;
  92. function logBullet(message, type = "info", data = undefined) {
  93. logger_1.logger[type](clc.cyan(clc.bold("i ")), message, data);
  94. }
  95. exports.logBullet = logBullet;
  96. function logLabeledBullet(label, message, type = "info", data = undefined) {
  97. logger_1.logger[type](clc.cyan(clc.bold(`i ${label}:`)), message, data);
  98. }
  99. exports.logLabeledBullet = logLabeledBullet;
  100. function logWarning(message, type = "warn", data = undefined) {
  101. logger_1.logger[type](clc.yellow(clc.bold(`${WARNING_CHAR} `)), message, data);
  102. }
  103. exports.logWarning = logWarning;
  104. function logLabeledWarning(label, message, type = "warn", data = undefined) {
  105. logger_1.logger[type](clc.yellow(clc.bold(`${WARNING_CHAR} ${label}:`)), message, data);
  106. }
  107. exports.logLabeledWarning = logLabeledWarning;
  108. function logLabeledError(label, message, type = "error", data = undefined) {
  109. logger_1.logger[type](clc.red(clc.bold(`${ERROR_CHAR} ${label}:`)), message, data);
  110. }
  111. exports.logLabeledError = logLabeledError;
  112. function reject(message, options) {
  113. return Promise.reject(new error_1.FirebaseError(message, options));
  114. }
  115. exports.reject = reject;
  116. function allSettled(promises) {
  117. if (!promises.length) {
  118. return Promise.resolve([]);
  119. }
  120. return new Promise((resolve) => {
  121. let remaining = promises.length;
  122. const results = [];
  123. for (let i = 0; i < promises.length; i++) {
  124. void Promise.resolve(promises[i])
  125. .then((result) => {
  126. results[i] = {
  127. status: "fulfilled",
  128. value: result,
  129. };
  130. }, (err) => {
  131. results[i] = {
  132. status: "rejected",
  133. reason: err,
  134. };
  135. })
  136. .then(() => {
  137. if (!--remaining) {
  138. resolve(results);
  139. }
  140. });
  141. }
  142. });
  143. }
  144. exports.allSettled = allSettled;
  145. function explainStdin() {
  146. if (IS_WINDOWS) {
  147. throw new error_1.FirebaseError("STDIN input is not available on Windows.", {
  148. exit: 1,
  149. });
  150. }
  151. if (process.stdin.isTTY) {
  152. logger_1.logger.info(clc.bold("Note:"), "Reading STDIN. Type JSON data and then press Ctrl-D");
  153. }
  154. }
  155. exports.explainStdin = explainStdin;
  156. function stringToStream(text) {
  157. if (!text) {
  158. return undefined;
  159. }
  160. const s = new stream_1.Readable();
  161. s.push(text);
  162. s.push(null);
  163. return s;
  164. }
  165. exports.stringToStream = stringToStream;
  166. function streamToString(s) {
  167. return new Promise((resolve, reject) => {
  168. let b = "";
  169. s.on("error", reject);
  170. s.on("data", (d) => (b += `${d}`));
  171. s.once("end", () => resolve(b));
  172. });
  173. }
  174. exports.streamToString = streamToString;
  175. function makeActiveProject(projectDir, newActive) {
  176. const activeProjects = configstore_1.configstore.get("activeProjects") || {};
  177. if (newActive) {
  178. activeProjects[projectDir] = newActive;
  179. }
  180. else {
  181. _.unset(activeProjects, projectDir);
  182. }
  183. configstore_1.configstore.set("activeProjects", activeProjects);
  184. }
  185. exports.makeActiveProject = makeActiveProject;
  186. function endpoint(parts) {
  187. return `/${parts.join("/")}`;
  188. }
  189. exports.endpoint = endpoint;
  190. function getFunctionsEventProvider(eventType) {
  191. const parts = eventType.split("/");
  192. if (parts.length > 1) {
  193. const provider = last(parts[1].split("."));
  194. return _.capitalize(provider);
  195. }
  196. if (/google.pubsub/.exec(eventType)) {
  197. return "PubSub";
  198. }
  199. else if (/google.storage/.exec(eventType)) {
  200. return "Storage";
  201. }
  202. else if (/google.analytics/.exec(eventType)) {
  203. return "Analytics";
  204. }
  205. else if (/google.firebase.database/.exec(eventType)) {
  206. return "Database";
  207. }
  208. else if (/google.firebase.auth/.exec(eventType)) {
  209. return "Auth";
  210. }
  211. else if (/google.firebase.crashlytics/.exec(eventType)) {
  212. return "Crashlytics";
  213. }
  214. else if (/google.firestore/.exec(eventType)) {
  215. return "Firestore";
  216. }
  217. return _.capitalize(eventType.split(".")[1]);
  218. }
  219. exports.getFunctionsEventProvider = getFunctionsEventProvider;
  220. function promiseAllSettled(promises) {
  221. const wrappedPromises = promises.map(async (p) => {
  222. try {
  223. const val = await Promise.resolve(p);
  224. return { state: "fulfilled", value: val };
  225. }
  226. catch (err) {
  227. return { state: "rejected", reason: err };
  228. }
  229. });
  230. return Promise.all(wrappedPromises);
  231. }
  232. exports.promiseAllSettled = promiseAllSettled;
  233. async function promiseWhile(action, check, interval = 2500) {
  234. return new Promise((resolve, promiseReject) => {
  235. const run = async () => {
  236. try {
  237. const res = await action();
  238. if (check(res)) {
  239. return resolve(res);
  240. }
  241. setTimeout(run, interval);
  242. }
  243. catch (err) {
  244. return promiseReject(err);
  245. }
  246. };
  247. run();
  248. });
  249. }
  250. exports.promiseWhile = promiseWhile;
  251. function withTimeout(timeoutMs, promise) {
  252. return new Promise((resolve, reject) => {
  253. const timeout = setTimeout(() => reject(new Error("Timed out.")), timeoutMs);
  254. promise.then((value) => {
  255. clearTimeout(timeout);
  256. resolve(value);
  257. }, (err) => {
  258. clearTimeout(timeout);
  259. reject(err);
  260. });
  261. });
  262. }
  263. exports.withTimeout = withTimeout;
  264. async function promiseProps(obj) {
  265. const resultObj = {};
  266. const promises = Object.keys(obj).map(async (key) => {
  267. const r = await Promise.resolve(obj[key]);
  268. resultObj[key] = r;
  269. });
  270. return Promise.all(promises).then(() => resultObj);
  271. }
  272. exports.promiseProps = promiseProps;
  273. function tryStringify(value) {
  274. if (typeof value === "string") {
  275. return value;
  276. }
  277. try {
  278. return JSON.stringify(value);
  279. }
  280. catch (_a) {
  281. return value;
  282. }
  283. }
  284. exports.tryStringify = tryStringify;
  285. function tryParse(value) {
  286. if (typeof value !== "string") {
  287. return value;
  288. }
  289. try {
  290. return JSON.parse(value);
  291. }
  292. catch (_a) {
  293. return value;
  294. }
  295. }
  296. exports.tryParse = tryParse;
  297. function setupLoggers() {
  298. if (process.env.DEBUG) {
  299. logger_1.logger.add(new winston.transports.Console({
  300. level: "debug",
  301. format: winston.format.printf((info) => {
  302. const segments = [info.message, ...(info[triple_beam_1.SPLAT] || [])].map(tryStringify);
  303. return `${stripAnsi(segments.join(" "))}`;
  304. }),
  305. }));
  306. }
  307. else if (process.env.IS_FIREBASE_CLI) {
  308. logger_1.logger.add(new winston.transports.Console({
  309. level: "info",
  310. format: winston.format.printf((info) => [info.message, ...(info[triple_beam_1.SPLAT] || [])]
  311. .filter((chunk) => typeof chunk === "string")
  312. .join(" ")),
  313. }));
  314. }
  315. }
  316. exports.setupLoggers = setupLoggers;
  317. async function promiseWithSpinner(action, message) {
  318. const spinner = ora(message).start();
  319. let data;
  320. try {
  321. data = await action();
  322. spinner.succeed();
  323. }
  324. catch (err) {
  325. spinner.fail();
  326. throw err;
  327. }
  328. return data;
  329. }
  330. exports.promiseWithSpinner = promiseWithSpinner;
  331. function createDestroyer(server) {
  332. const connections = new Set();
  333. server.on("connection", (conn) => {
  334. connections.add(conn);
  335. conn.once("close", () => connections.delete(conn));
  336. });
  337. let destroyPromise = undefined;
  338. return function destroyer() {
  339. if (!destroyPromise) {
  340. destroyPromise = new Promise((resolve, reject) => {
  341. server.close((err) => {
  342. if (err)
  343. return reject(err);
  344. resolve();
  345. });
  346. connections.forEach((socket) => socket.destroy());
  347. });
  348. }
  349. return destroyPromise;
  350. };
  351. }
  352. exports.createDestroyer = createDestroyer;
  353. function datetimeString(d) {
  354. const day = `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d
  355. .getDate()
  356. .toString()
  357. .padStart(2, "0")}`;
  358. const time = `${d.getHours().toString().padStart(2, "0")}:${d
  359. .getMinutes()
  360. .toString()
  361. .padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}`;
  362. return `${day} ${time}`;
  363. }
  364. exports.datetimeString = datetimeString;
  365. function isCloudEnvironment() {
  366. return !!process.env.CODESPACES || !!process.env.GOOGLE_CLOUD_WORKSTATIONS;
  367. }
  368. exports.isCloudEnvironment = isCloudEnvironment;
  369. function isRunningInWSL() {
  370. return !!process.env.WSL_DISTRO_NAME;
  371. }
  372. exports.isRunningInWSL = isRunningInWSL;
  373. function thirtyDaysFromNow() {
  374. return new Date(Date.now() + THIRTY_DAYS_IN_MILLISECONDS);
  375. }
  376. exports.thirtyDaysFromNow = thirtyDaysFromNow;
  377. function assertDefined(val, message) {
  378. if (val === undefined || val === null) {
  379. throw new assert_1.AssertionError({
  380. message: message || `expected value to be defined but got "${val}"`,
  381. });
  382. }
  383. }
  384. exports.assertDefined = assertDefined;
  385. function assertIsString(val, message) {
  386. if (typeof val !== "string") {
  387. throw new assert_1.AssertionError({
  388. message: message || `expected "string" but got "${typeof val}"`,
  389. });
  390. }
  391. }
  392. exports.assertIsString = assertIsString;
  393. function assertIsNumber(val, message) {
  394. if (typeof val !== "number") {
  395. throw new assert_1.AssertionError({
  396. message: message || `expected "number" but got "${typeof val}"`,
  397. });
  398. }
  399. }
  400. exports.assertIsNumber = assertIsNumber;
  401. function assertIsStringOrUndefined(val, message) {
  402. if (!(val === undefined || typeof val === "string")) {
  403. throw new assert_1.AssertionError({
  404. message: message || `expected "string" or "undefined" but got "${typeof val}"`,
  405. });
  406. }
  407. }
  408. exports.assertIsStringOrUndefined = assertIsStringOrUndefined;
  409. function groupBy(arr, f) {
  410. return arr.reduce((result, item) => {
  411. const key = f(item);
  412. if (result[key]) {
  413. result[key].push(item);
  414. }
  415. else {
  416. result[key] = [item];
  417. }
  418. return result;
  419. }, {});
  420. }
  421. exports.groupBy = groupBy;
  422. function cloneArray(arr) {
  423. return arr.map((e) => cloneDeep(e));
  424. }
  425. function cloneObject(obj) {
  426. const clone = {};
  427. for (const [k, v] of Object.entries(obj)) {
  428. clone[k] = cloneDeep(v);
  429. }
  430. return clone;
  431. }
  432. function cloneDeep(obj) {
  433. if (typeof obj !== "object" || !obj) {
  434. return obj;
  435. }
  436. if (obj instanceof RegExp) {
  437. return RegExp(obj, obj.flags);
  438. }
  439. if (obj instanceof Date) {
  440. return new Date(obj);
  441. }
  442. if (Array.isArray(obj)) {
  443. return cloneArray(obj);
  444. }
  445. if (obj instanceof Map) {
  446. return new Map(obj.entries());
  447. }
  448. return cloneObject(obj);
  449. }
  450. exports.cloneDeep = cloneDeep;
  451. function last(arr) {
  452. if (!Array.isArray(arr)) {
  453. return undefined;
  454. }
  455. return arr[arr.length - 1];
  456. }
  457. exports.last = last;
  458. function debounce(fn, delay, { leading } = {}) {
  459. let timer;
  460. return (...args) => {
  461. if (!timer && leading) {
  462. fn(...args);
  463. }
  464. clearTimeout(timer);
  465. timer = setTimeout(() => fn(...args), delay);
  466. };
  467. }
  468. exports.debounce = debounce;
  469. function randomInt(min, max) {
  470. min = Math.floor(min);
  471. max = Math.ceil(max) + 1;
  472. return Math.floor(Math.random() * (max - min) + min);
  473. }
  474. exports.randomInt = randomInt;
  475. function connectableHostname(hostname) {
  476. if (hostname === "0.0.0.0") {
  477. hostname = "127.0.0.1";
  478. }
  479. else if (hostname === "::") {
  480. hostname = "::1";
  481. }
  482. else if (hostname === "[::]") {
  483. hostname = "[::1]";
  484. }
  485. return hostname;
  486. }
  487. exports.connectableHostname = connectableHostname;