index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. 'use strict';
  2. const destr = require('destr');
  3. const nanoid = require('nanoid');
  4. const rc9 = require('rc9');
  5. const fetch = require('node-fetch');
  6. const path = require('path');
  7. const fs = require('fs');
  8. const createRequire = require('create-require');
  9. const os = require('os');
  10. const gitUrlParse = require('git-url-parse');
  11. const parseGitConfig = require('parse-git-config');
  12. const isDocker = require('is-docker');
  13. const ci = require('ci-info');
  14. const fs$1 = require('fs-extra');
  15. const crypto = require('crypto');
  16. const consola = require('consola');
  17. const c = require('chalk');
  18. const inquirer = require('inquirer');
  19. const stdEnv = require('std-env');
  20. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  21. const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
  22. const fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
  23. const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
  24. const createRequire__default = /*#__PURE__*/_interopDefaultLegacy(createRequire);
  25. const os__default = /*#__PURE__*/_interopDefaultLegacy(os);
  26. const gitUrlParse__default = /*#__PURE__*/_interopDefaultLegacy(gitUrlParse);
  27. const parseGitConfig__default = /*#__PURE__*/_interopDefaultLegacy(parseGitConfig);
  28. const isDocker__default = /*#__PURE__*/_interopDefaultLegacy(isDocker);
  29. const ci__default = /*#__PURE__*/_interopDefaultLegacy(ci);
  30. const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs$1);
  31. const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
  32. const c__default = /*#__PURE__*/_interopDefaultLegacy(c);
  33. const inquirer__default = /*#__PURE__*/_interopDefaultLegacy(inquirer);
  34. const stdEnv__default = /*#__PURE__*/_interopDefaultLegacy(stdEnv);
  35. var name = "@nuxt/telemetry";
  36. var version = "1.3.6";
  37. function updateUserNuxtRc(key, val) {
  38. rc9.updateUser({[key]: val}, ".nuxtrc");
  39. }
  40. const consentVersion = 1;
  41. async function postEvent(endpoint, body) {
  42. const res = await fetch__default['default'](endpoint, {
  43. method: "POST",
  44. body: JSON.stringify(body),
  45. headers: {
  46. "content-type": "application/json",
  47. "user-agent": "Nuxt Telemetry " + version
  48. },
  49. timeout: 4e3
  50. });
  51. if (!res.ok) {
  52. throw new Error(res.statusText);
  53. }
  54. }
  55. const build = function({nuxt}, payload) {
  56. const duration = {build: payload.duration.build};
  57. let isSuccess = true;
  58. for (const [name, stat] of Object.entries(payload.stats)) {
  59. duration[name] = stat.duration;
  60. if (!stat.success) {
  61. isSuccess = false;
  62. }
  63. }
  64. return {
  65. name: "build",
  66. isSuccess,
  67. isDev: nuxt.options.dev || false,
  68. duration
  69. };
  70. };
  71. const command = function({nuxt}) {
  72. let command2 = "unknown";
  73. const flagMap = {
  74. dev: "dev",
  75. _generate: "generate",
  76. _export: "export",
  77. _build: "build",
  78. _serve: "serve",
  79. _start: "start"
  80. };
  81. for (const flag in flagMap) {
  82. if (nuxt.options[flag]) {
  83. command2 = flagMap[flag];
  84. break;
  85. }
  86. }
  87. return {
  88. name: "command",
  89. command: command2
  90. };
  91. };
  92. const generate = function generate2({nuxt}, payload) {
  93. return {
  94. name: "generate",
  95. isExport: !!nuxt.options._export,
  96. routesCount: payload.routesCount,
  97. duration: {
  98. generate: payload.duration.generate
  99. }
  100. };
  101. };
  102. const dependency = function({nuxt: {options}}) {
  103. const events = [];
  104. const projectDeps = getDependencies(options.rootDir);
  105. const modules = normalizeModules(options.modules);
  106. const buildModules = normalizeModules(options.buildModules);
  107. const relatedDeps = [...modules, ...buildModules];
  108. for (const dep of projectDeps) {
  109. if (!relatedDeps.includes(dep.name)) {
  110. continue;
  111. }
  112. events.push({
  113. name: "dependency",
  114. packageName: dep.name,
  115. version: dep.version,
  116. isDevDependency: dep.dev,
  117. isModule: modules.includes(dep.name),
  118. isBuildModule: buildModules.includes(dep.name)
  119. });
  120. }
  121. return events;
  122. };
  123. function normalizeModules(modules) {
  124. return modules.map((m) => {
  125. if (typeof m === "string") {
  126. return m;
  127. }
  128. if (Array.isArray(m) && typeof m[0] === "string") {
  129. return m[0];
  130. }
  131. return null;
  132. }).filter(Boolean);
  133. }
  134. function getDependencies(rootDir) {
  135. const pkgPath = path.join(rootDir, "package.json");
  136. if (!fs.existsSync(pkgPath)) {
  137. return [];
  138. }
  139. const _require = createRequire__default['default'](rootDir);
  140. const pkg = _require(pkgPath);
  141. const mapDeps = (depsObj, dev = false) => {
  142. const _deps = [];
  143. for (const name in depsObj) {
  144. try {
  145. const pkg2 = _require(path.join(name, "package.json"));
  146. _deps.push({name, version: pkg2.version, dev});
  147. } catch (_e) {
  148. _deps.push({name, version: depsObj[name], dev});
  149. }
  150. }
  151. return _deps;
  152. };
  153. const deps = [];
  154. if (pkg.dependencies) {
  155. deps.push(...mapDeps(pkg.dependencies));
  156. }
  157. if (pkg.devDependencies) {
  158. deps.push(...mapDeps(pkg.dependencies, true));
  159. }
  160. return deps;
  161. }
  162. const project = function(context) {
  163. const {options} = context.nuxt;
  164. return {
  165. name: "project",
  166. type: context.git && context.git.url ? "git" : "local",
  167. isSSR: options.mode === "universal" || options.ssr === true,
  168. target: options._generate ? "static" : "server",
  169. packageManager: context.packageManager
  170. };
  171. };
  172. const session = function({seed}) {
  173. return {
  174. name: "session",
  175. id: seed
  176. };
  177. };
  178. const events = /*#__PURE__*/Object.freeze({
  179. __proto__: null,
  180. build: build,
  181. command: command,
  182. generate: generate,
  183. dependency: dependency,
  184. getDependencies: getDependencies,
  185. project: project,
  186. session: session
  187. });
  188. const FILE2PM = {
  189. "yarn.lock": "yarn",
  190. "package-lock.json": "npm",
  191. "shrinkwrap.json": "npm"
  192. };
  193. async function detectPackageManager(rootDir) {
  194. for (const file in FILE2PM) {
  195. if (await fs__default['default'].pathExists(path__default['default'].resolve(rootDir, file))) {
  196. return FILE2PM[file];
  197. }
  198. }
  199. return "unknown";
  200. }
  201. function hash(str) {
  202. return crypto.createHash("sha256").update(str).digest("hex").substr(0, 16);
  203. }
  204. async function createContext(nuxt, options) {
  205. const rootDir = nuxt.options.rootDir || process.cwd();
  206. const git = await getGit(rootDir);
  207. const packageManager = await detectPackageManager(rootDir);
  208. const {seed} = options;
  209. const projectHash = await getProjectHash(rootDir, git, seed);
  210. const projectSession = getProjectSession(projectHash, seed);
  211. const nuxtVersion = (nuxt.constructor.version || "").replace("v", "");
  212. const nodeVersion = process.version.replace("v", "");
  213. const isEdge = nuxtVersion.includes("-");
  214. return {
  215. nuxt,
  216. seed,
  217. git,
  218. projectHash,
  219. projectSession,
  220. nuxtVersion,
  221. isEdge,
  222. cli: getCLI(),
  223. nodeVersion,
  224. os: os__default['default'].type().toLocaleLowerCase(),
  225. environment: getEnv(),
  226. packageManager,
  227. concent: options.consent
  228. };
  229. }
  230. function getEnv() {
  231. if (process.env.CODESANDBOX_SSE) {
  232. return "CSB";
  233. }
  234. if (ci__default['default'].isCI) {
  235. return ci__default['default'].name;
  236. }
  237. if (isDocker__default['default']()) {
  238. return "Docker";
  239. }
  240. return "unknown";
  241. }
  242. function getCLI() {
  243. let entry;
  244. if (typeof require !== "undefined" && require.main && require.main.filename) {
  245. entry = require.main.filename;
  246. } else {
  247. entry = process.argv[1];
  248. }
  249. const knownCLIs = {
  250. "nuxt-ts.js": "nuxt-ts",
  251. "nuxt-start.js": "nuxt-start",
  252. "nuxt.js": "nuxt"
  253. };
  254. for (const key in knownCLIs) {
  255. if (entry.includes(key)) {
  256. const edge = entry.includes("-edge") ? "-edge" : "";
  257. return knownCLIs[key] + edge;
  258. }
  259. }
  260. return "programmatic";
  261. }
  262. function getProjectSession(projectHash, sessionId) {
  263. return hash(`${projectHash}#${sessionId}`);
  264. }
  265. function getProjectHash(rootDir, git, seed) {
  266. let id;
  267. if (git && git.url) {
  268. id = `${git.source}#${git.owner}#${git.name}`;
  269. } else {
  270. id = `${rootDir}#${seed}`;
  271. }
  272. return hash(id);
  273. }
  274. async function getGitRemote(rootDir) {
  275. try {
  276. const parsed = await parseGitConfig__default['default']({cwd: rootDir});
  277. if (parsed) {
  278. const gitRemote = parsed['remote "origin"'].url;
  279. return gitRemote;
  280. }
  281. return null;
  282. } catch (err) {
  283. return null;
  284. }
  285. }
  286. async function getGit(rootDir) {
  287. const gitRemote = await getGitRemote(rootDir);
  288. if (!gitRemote) {
  289. return;
  290. }
  291. const meta = gitUrlParse__default['default'](gitRemote);
  292. const url = meta.toString("https");
  293. return {
  294. url,
  295. gitRemote,
  296. source: meta.source,
  297. owner: meta.owner,
  298. name: meta.name
  299. };
  300. }
  301. const log = consola__default['default'].withScope("@nuxt/telemetry");
  302. class Telemetry {
  303. constructor(nuxt, options) {
  304. this.events = [];
  305. this.nuxt = nuxt;
  306. this.options = options;
  307. }
  308. getContext() {
  309. if (!this._contextPromise) {
  310. this._contextPromise = createContext(this.nuxt, this.options);
  311. }
  312. return this._contextPromise;
  313. }
  314. createEvent(name, payload) {
  315. const eventFactory = events[name];
  316. if (typeof eventFactory !== "function") {
  317. log.warn("Unknown event:", name);
  318. return;
  319. }
  320. const eventPromise = this._invokeEvent(name, eventFactory, payload);
  321. this.events.push(eventPromise);
  322. }
  323. async _invokeEvent(name, eventFactory, payload) {
  324. try {
  325. const context = await this.getContext();
  326. const event = await eventFactory(context, payload);
  327. event.name = name;
  328. return event;
  329. } catch (err) {
  330. log.error("Error while running event:", err);
  331. }
  332. }
  333. async getPublicContext() {
  334. const context = await this.getContext();
  335. const eventContext = {};
  336. for (const key of [
  337. "nuxtVersion",
  338. "isEdge",
  339. "nodeVersion",
  340. "cli",
  341. "os",
  342. "environment",
  343. "projectHash",
  344. "projectSession"
  345. ]) {
  346. eventContext[key] = context[key];
  347. }
  348. return eventContext;
  349. }
  350. async sendEvents() {
  351. const events2 = [].concat(...(await Promise.all(this.events)).filter(Boolean));
  352. this.events = [];
  353. const context = await this.getPublicContext();
  354. const body = {
  355. timestamp: Date.now(),
  356. context,
  357. events: events2
  358. };
  359. if (this.options.endpoint) {
  360. const start = Date.now();
  361. try {
  362. log.info("Sending events:", JSON.stringify(body, null, 2));
  363. await postEvent(this.options.endpoint, body);
  364. log.success(`Events sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)`);
  365. } catch (err) {
  366. log.error(`Error sending sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)
  367. `, err);
  368. }
  369. }
  370. }
  371. }
  372. function getStats(stats) {
  373. const duration = stats.endTime - stats.startTime;
  374. return {
  375. duration,
  376. success: stats.compilation.errors.length === 0,
  377. size: 0,
  378. fullHash: stats.compilation.fullHash
  379. };
  380. }
  381. async function ensureUserconsent(options) {
  382. if (options.consent >= consentVersion) {
  383. return true;
  384. }
  385. if (stdEnv__default['default'].minimal || process.env.CODESANDBOX_SSE || process.env.NEXT_TELEMETRY_DISABLED || isDocker__default['default']()) {
  386. return false;
  387. }
  388. process.stdout.write("\n");
  389. consola__default['default'].info(`${c__default['default'].green("NuxtJS")} collects completely anonymous data about usage.
  390. This will help us improve Nuxt developer experience over time.
  391. Read more on ${c__default['default'].cyan.underline("https://git.io/nuxt-telemetry")}
  392. `);
  393. const {accept} = await inquirer__default['default'].prompt({
  394. type: "confirm",
  395. name: "accept",
  396. message: "Are you interested in participating?"
  397. });
  398. process.stdout.write("\n");
  399. if (accept) {
  400. updateUserNuxtRc("telemetry.consent", consentVersion);
  401. updateUserNuxtRc("telemetry.enabled", true);
  402. return true;
  403. }
  404. updateUserNuxtRc("telemetry.enabled", false);
  405. return false;
  406. }
  407. async function _telemetryModule(nuxt) {
  408. const toptions = {
  409. endpoint: destr__default['default'](process.env.NUXT_TELEMETRY_ENDPOINT) || "https://telemetry.nuxtjs.com",
  410. debug: destr__default['default'](process.env.NUXT_TELEMETRY_DEBUG),
  411. ...nuxt.options.telemetry
  412. };
  413. if (!toptions.debug) {
  414. log.level = -Infinity;
  415. }
  416. if (nuxt.options.telemetry !== true) {
  417. if (toptions.enabled === false || nuxt.options.telemetry === false || !await ensureUserconsent(toptions)) {
  418. log.info("Telemetry disabled");
  419. return;
  420. }
  421. }
  422. log.info("Telemetry enabled");
  423. if (!toptions.seed) {
  424. toptions.seed = hash(nanoid.nanoid());
  425. updateUserNuxtRc("telemetry.seed", toptions.seed);
  426. log.info("Seed generated:", toptions.seed);
  427. }
  428. const t = new Telemetry(nuxt, toptions);
  429. if (nuxt.options._start) {
  430. nuxt.hook("listen", () => {
  431. t.createEvent("project");
  432. t.createEvent("session");
  433. t.createEvent("command");
  434. t.sendEvents();
  435. });
  436. }
  437. nuxt.hook("build:before", () => {
  438. t.createEvent("project");
  439. t.createEvent("session");
  440. t.createEvent("command");
  441. t.createEvent("dependency");
  442. });
  443. profile(nuxt, t);
  444. }
  445. const telemetryModule = async function() {
  446. try {
  447. await _telemetryModule(this.nuxt);
  448. } catch (err) {
  449. log.error(err);
  450. }
  451. };
  452. function profile(nuxt, t) {
  453. const startT = {};
  454. const duration = {};
  455. const stats = {};
  456. let routesCount = 0;
  457. const timeStart = (name2) => {
  458. startT[name2] = Date.now();
  459. };
  460. const timeEnd = (name2) => {
  461. duration[name2] = Date.now() - startT[name2];
  462. };
  463. nuxt.hook("build:before", () => {
  464. timeStart("build");
  465. });
  466. nuxt.hook("build:done", () => {
  467. timeEnd("build");
  468. });
  469. nuxt.hook("build:compiled", ({name: name2, stats: _stats}) => {
  470. stats[name2] = getStats(_stats);
  471. });
  472. nuxt.hook("generate:extendRoutes", () => timeStart("generate"));
  473. nuxt.hook("generate:routeCreated", () => {
  474. routesCount++;
  475. });
  476. nuxt.hook("generate:done", () => {
  477. timeEnd("generate");
  478. t.createEvent("generate", {duration, stats, routesCount});
  479. t.sendEvents();
  480. });
  481. nuxt.hook("build:done", () => {
  482. t.createEvent("build", {duration, stats});
  483. t.sendEvents();
  484. });
  485. }
  486. telemetryModule.meta = {name, version};
  487. module.exports = telemetryModule;