'use strict';

const destr = require('destr');
const nanoid = require('nanoid');
const rc9 = require('rc9');
const fetch = require('node-fetch');
const path = require('path');
const fs = require('fs');
const createRequire = require('create-require');
const os = require('os');
const gitUrlParse = require('git-url-parse');
const parseGitConfig = require('parse-git-config');
const ciInfo = require('ci-info');
const fs$1 = require('fs-extra');
const crypto = require('crypto');
const consola = require('consola');
const c = require('chalk');
const inquirer = require('inquirer');
const stdEnv = require('std-env');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
const fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
const createRequire__default = /*#__PURE__*/_interopDefaultLegacy(createRequire);
const os__default = /*#__PURE__*/_interopDefaultLegacy(os);
const gitUrlParse__default = /*#__PURE__*/_interopDefaultLegacy(gitUrlParse);
const parseGitConfig__default = /*#__PURE__*/_interopDefaultLegacy(parseGitConfig);
const fs__default$1 = /*#__PURE__*/_interopDefaultLegacy(fs$1);
const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
const c__default = /*#__PURE__*/_interopDefaultLegacy(c);
const inquirer__default = /*#__PURE__*/_interopDefaultLegacy(inquirer);

var name = "@nuxt/telemetry";
var version = "1.4.1";

function updateUserNuxtRc(key, val) {
  rc9.updateUser({ [key]: val }, ".nuxtrc");
}

const consentVersion = 1;

async function postEvent(endpoint, body) {
  const res = await fetch__default["default"](endpoint, {
    method: "POST",
    body: JSON.stringify(body),
    headers: {
      "content-type": "application/json",
      "user-agent": "Nuxt Telemetry " + version
    },
    timeout: 4e3
  });
  if (!res.ok) {
    throw new Error(res.statusText);
  }
}

const build = function({ nuxt }, payload) {
  const duration = { build: payload.duration.build };
  let isSuccess = true;
  for (const [name, stat] of Object.entries(payload.stats)) {
    duration[name] = stat.duration;
    if (!stat.success) {
      isSuccess = false;
    }
  }
  return {
    name: "build",
    isSuccess,
    isDev: nuxt.options.dev || false,
    duration
  };
};

const command = function({ nuxt }) {
  let command2 = "unknown";
  const flagMap = {
    dev: "dev",
    _generate: "generate",
    _export: "export",
    _build: "build",
    _serve: "serve",
    _start: "start"
  };
  for (const flag in flagMap) {
    if (nuxt.options[flag]) {
      command2 = flagMap[flag];
      break;
    }
  }
  return {
    name: "command",
    command: command2
  };
};

const generate = function generate2({ nuxt }, payload) {
  return {
    name: "generate",
    isExport: !!nuxt.options._export,
    routesCount: payload.routesCount,
    duration: {
      generate: payload.duration.generate
    }
  };
};

const dependency = function({ nuxt: { options } }) {
  const events = [];
  const projectDeps = getDependencies(options.rootDir);
  const modules = normalizeModules(options.modules);
  const buildModules = normalizeModules(options.buildModules);
  const relatedDeps = [...modules, ...buildModules];
  for (const dep of projectDeps) {
    if (!relatedDeps.includes(dep.name)) {
      continue;
    }
    events.push({
      name: "dependency",
      packageName: dep.name,
      version: dep.version,
      isDevDependency: dep.dev,
      isModule: modules.includes(dep.name),
      isBuildModule: buildModules.includes(dep.name)
    });
  }
  return events;
};
function normalizeModules(modules) {
  return modules.map((m) => {
    if (typeof m === "string") {
      return m;
    }
    if (Array.isArray(m) && typeof m[0] === "string") {
      return m[0];
    }
    return null;
  }).filter(Boolean);
}
function getDependencies(rootDir) {
  const pkgPath = path.join(rootDir, "package.json");
  if (!fs.existsSync(pkgPath)) {
    return [];
  }
  const _require = createRequire__default["default"](rootDir);
  const pkg = _require(pkgPath);
  const mapDeps = (depsObj, dev = false) => {
    const _deps = [];
    for (const name in depsObj) {
      try {
        const pkg2 = _require(path.join(name, "package.json"));
        _deps.push({ name, version: pkg2.version, dev });
      } catch (_e) {
        _deps.push({ name, version: depsObj[name], dev });
      }
    }
    return _deps;
  };
  const deps = [];
  if (pkg.dependencies) {
    deps.push(...mapDeps(pkg.dependencies));
  }
  if (pkg.devDependencies) {
    deps.push(...mapDeps(pkg.dependencies, true));
  }
  return deps;
}

const project = function(context) {
  const { options } = context.nuxt;
  return {
    name: "project",
    type: context.git && context.git.url ? "git" : "local",
    isSSR: options.mode === "universal" || options.ssr === true,
    target: options._generate ? "static" : "server",
    packageManager: context.packageManager
  };
};

const session = function({ seed }) {
  return {
    name: "session",
    id: seed
  };
};

const events = /*#__PURE__*/Object.freeze({
  __proto__: null,
  build: build,
  command: command,
  generate: generate,
  dependency: dependency,
  getDependencies: getDependencies,
  project: project,
  session: session
});

let isDockerCached;
function hasDockerEnv() {
  try {
    fs__default["default"].statSync("/.dockerenv");
    return true;
  } catch (e) {
    return false;
  }
}
function hasDockerCGroup() {
  try {
    return fs__default["default"].readFileSync("/proc/self/cgroup", "utf8").includes("docker");
  } catch (e) {
    return false;
  }
}
function isDocker() {
  if (isDockerCached === void 0) {
    isDockerCached = hasDockerEnv() || hasDockerCGroup();
  }
  return isDockerCached;
}

const FILE2PM = {
  "yarn.lock": "yarn",
  "package-lock.json": "npm",
  "shrinkwrap.json": "npm"
};
async function detectPackageManager(rootDir) {
  for (const file in FILE2PM) {
    if (await fs__default$1["default"].pathExists(path__default["default"].resolve(rootDir, file))) {
      return FILE2PM[file];
    }
  }
  return "unknown";
}

function hash(str) {
  return crypto.createHash("sha256").update(str).digest("hex").substr(0, 16);
}

async function createContext(nuxt, options) {
  const rootDir = nuxt.options.rootDir || process.cwd();
  const git = await getGit(rootDir);
  const packageManager = await detectPackageManager(rootDir);
  const { seed } = options;
  const projectHash = await getProjectHash(rootDir, git, seed);
  const projectSession = getProjectSession(projectHash, seed);
  const nuxtVersion = (nuxt.constructor.version || "").replace("v", "");
  const nodeVersion = process.version.replace("v", "");
  const isEdge = nuxtVersion.includes("-");
  return {
    nuxt,
    seed,
    git,
    projectHash,
    projectSession,
    nuxtVersion,
    isEdge,
    cli: getCLI(),
    nodeVersion,
    os: os__default["default"].type().toLocaleLowerCase(),
    environment: getEnv(),
    packageManager,
    concent: options.consent
  };
}
function getEnv() {
  if (process.env.CODESANDBOX_SSE) {
    return "CSB";
  }
  if (ciInfo.isCI) {
    return ciInfo.name;
  }
  if (isDocker()) {
    return "Docker";
  }
  return "unknown";
}
function getCLI() {
  let entry;
  if (typeof require !== "undefined" && require.main && require.main.filename) {
    entry = require.main.filename;
  } else {
    entry = process.argv[1];
  }
  const knownCLIs = {
    "nuxt-ts.js": "nuxt-ts",
    "nuxt-start.js": "nuxt-start",
    "nuxt.js": "nuxt"
  };
  for (const key in knownCLIs) {
    if (entry.includes(key)) {
      const edge = entry.includes("-edge") ? "-edge" : "";
      return knownCLIs[key] + edge;
    }
  }
  return "programmatic";
}
function getProjectSession(projectHash, sessionId) {
  return hash(`${projectHash}#${sessionId}`);
}
function getProjectHash(rootDir, git, seed) {
  let id;
  if (git && git.url) {
    id = `${git.source}#${git.owner}#${git.name}`;
  } else {
    id = `${rootDir}#${seed}`;
  }
  return hash(id);
}
async function getGitRemote(rootDir) {
  try {
    const parsed = await parseGitConfig__default["default"]({ cwd: rootDir });
    if (parsed) {
      const gitRemote = parsed['remote "origin"'].url;
      return gitRemote;
    }
    return null;
  } catch (err) {
    return null;
  }
}
async function getGit(rootDir) {
  const gitRemote = await getGitRemote(rootDir);
  if (!gitRemote) {
    return;
  }
  const meta = gitUrlParse__default["default"](gitRemote);
  const url = meta.toString("https");
  return {
    url,
    gitRemote,
    source: meta.source,
    owner: meta.owner,
    name: meta.name
  };
}

const log = consola__default["default"].withScope("@nuxt/telemetry");

class Telemetry {
  constructor(nuxt, options) {
    this.events = [];
    this.nuxt = nuxt;
    this.options = options;
  }
  getContext() {
    if (!this._contextPromise) {
      this._contextPromise = createContext(this.nuxt, this.options);
    }
    return this._contextPromise;
  }
  createEvent(name, payload) {
    const eventFactory = events[name];
    if (typeof eventFactory !== "function") {
      log.warn("Unknown event:", name);
      return;
    }
    const eventPromise = this._invokeEvent(name, eventFactory, payload);
    this.events.push(eventPromise);
  }
  async _invokeEvent(name, eventFactory, payload) {
    try {
      const context = await this.getContext();
      const event = await eventFactory(context, payload);
      event.name = name;
      return event;
    } catch (err) {
      log.error("Error while running event:", err);
    }
  }
  async getPublicContext() {
    const context = await this.getContext();
    const eventContext = {};
    for (const key of [
      "nuxtVersion",
      "isEdge",
      "nodeVersion",
      "cli",
      "os",
      "environment",
      "projectHash",
      "projectSession"
    ]) {
      eventContext[key] = context[key];
    }
    return eventContext;
  }
  async sendEvents() {
    const events2 = [].concat(...(await Promise.all(this.events)).filter(Boolean));
    this.events = [];
    const context = await this.getPublicContext();
    const body = {
      timestamp: Date.now(),
      context,
      events: events2
    };
    if (this.options.endpoint) {
      const start = Date.now();
      try {
        log.info("Sending events:", JSON.stringify(body, null, 2));
        await postEvent(this.options.endpoint, body);
        log.success(`Events sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)`);
      } catch (err) {
        log.error(`Error sending sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)
`, err);
      }
    }
  }
}

function getStats(stats) {
  const duration = stats.endTime - stats.startTime;
  return {
    duration,
    success: stats.compilation.errors.length === 0,
    size: 0,
    fullHash: stats.compilation.fullHash
  };
}

async function ensureUserconsent(options) {
  if (options.consent >= consentVersion) {
    return true;
  }
  if (stdEnv.isMinimal || process.env.CODESANDBOX_SSE || process.env.NEXT_TELEMETRY_DISABLED || isDocker()) {
    return false;
  }
  process.stdout.write("\n");
  consola__default["default"].info(`${c__default["default"].green("NuxtJS")} collects completely anonymous data about usage.
  This will help us improve Nuxt developer experience over time.
  Read more on ${c__default["default"].cyan.underline("https://git.io/nuxt-telemetry")}
`);
  const { accept } = await inquirer__default["default"].prompt({
    type: "confirm",
    name: "accept",
    message: "Are you interested in participating?"
  });
  process.stdout.write("\n");
  if (accept) {
    updateUserNuxtRc("telemetry.consent", consentVersion);
    updateUserNuxtRc("telemetry.enabled", true);
    return true;
  }
  updateUserNuxtRc("telemetry.enabled", false);
  return false;
}

async function _telemetryModule(nuxt) {
  const toptions = {
    endpoint: destr__default["default"](process.env.NUXT_TELEMETRY_ENDPOINT) || "https://telemetry.nuxtjs.com",
    debug: destr__default["default"](process.env.NUXT_TELEMETRY_DEBUG),
    ...nuxt.options.telemetry
  };
  if (!toptions.debug) {
    log.level = -Infinity;
  }
  if (nuxt.options.telemetry !== true) {
    if (toptions.enabled === false || nuxt.options.telemetry === false || !await ensureUserconsent(toptions)) {
      log.info("Telemetry disabled");
      return;
    }
  }
  log.info("Telemetry enabled");
  if (!toptions.seed) {
    toptions.seed = hash(nanoid.nanoid());
    updateUserNuxtRc("telemetry.seed", toptions.seed);
    log.info("Seed generated:", toptions.seed);
  }
  const t = new Telemetry(nuxt, toptions);
  if (nuxt.options._start) {
    nuxt.hook("listen", () => {
      t.createEvent("project");
      t.createEvent("session");
      t.createEvent("command");
      t.sendEvents();
    });
  }
  nuxt.hook("build:before", () => {
    t.createEvent("project");
    t.createEvent("session");
    t.createEvent("command");
    t.createEvent("dependency");
  });
  profile(nuxt, t);
}
const telemetryModule = async function() {
  try {
    await _telemetryModule(this.nuxt);
  } catch (err) {
    log.error(err);
  }
};
function profile(nuxt, t) {
  const startT = {};
  const duration = {};
  const stats = {};
  let routesCount = 0;
  const timeStart = (name2) => {
    startT[name2] = Date.now();
  };
  const timeEnd = (name2) => {
    duration[name2] = Date.now() - startT[name2];
  };
  nuxt.hook("build:before", () => {
    timeStart("build");
  });
  nuxt.hook("build:done", () => {
    timeEnd("build");
  });
  nuxt.hook("build:compiled", ({ name: name2, stats: _stats }) => {
    stats[name2] = getStats(_stats);
  });
  nuxt.hook("generate:extendRoutes", () => timeStart("generate"));
  nuxt.hook("generate:routeCreated", () => {
    routesCount++;
  });
  nuxt.hook("generate:done", () => {
    timeEnd("generate");
    t.createEvent("generate", { duration, stats, routesCount });
    t.sendEvents();
  });
  nuxt.hook("build:done", () => {
    t.createEvent("build", { duration, stats });
    t.sendEvents();
  });
}
telemetryModule.meta = { name, version };

module.exports = telemetryModule;
