import { destr } from 'destr'; import { getNuxtVersion, isNuxt3, useLogger, resolvePath, defineNuxtModule } from '@nuxt/kit'; import { v as version, e as ensureUserconsent, u as updateUserNuxtRc } from './shared/telemetry.CpL9G3he.mjs'; import { fetch } from 'ofetch'; import os from 'node:os'; import { execSync } from 'node:child_process'; import gitUrlParse from 'git-url-parse'; import isDocker from 'is-docker'; import { provider } from 'std-env'; import { detect } from 'package-manager-detector'; import { randomUUID, createHash } from 'node:crypto'; import fs from 'node:fs'; import { resolve } from 'pathe'; import 'consola/utils'; import 'consola'; import 'rc9'; async function postEvent(endpoint, body) { const res = await fetch(endpoint, { method: "POST", body: JSON.stringify(body), headers: { "content-type": "application/json", "user-agent": "Nuxt Telemetry " + version } }); if (!res.ok) { throw new Error(res.statusText); } } function hash(str) { return createHash("sha256").update(str).digest("hex").substr(0, 16); } function randomSeed() { return hash(randomUUID()); } async function createContext(nuxt, options) { const rootDir = nuxt.options.rootDir || process.cwd(); const git = await getGit(rootDir); const packageManager = await detect({ cwd: rootDir }); const { seed } = options; const projectHash = await getProjectHash(rootDir, git, seed); const projectSession = getProjectSession(projectHash, seed); const nuxtVersion = getNuxtVersion(nuxt); const nuxtMajorVersion = isNuxt3(nuxt) ? 3 : 2; const nodeVersion = process.version.replace("v", ""); const isEdge = nuxtVersion.includes("edge"); return { nuxt, seed, git, projectHash, projectSession, nuxtVersion, nuxtMajorVersion, isEdge, cli: getCLI(), nodeVersion, os: os.type().toLocaleLowerCase(), environment: getEnv(), packageManager: packageManager?.name || "unknown", concent: options.consent }; } function getEnv() { if (provider) { return provider; } if (isDocker()) { return "Docker"; } return "unknown"; } function getCLI() { const entry = process.argv[1]; const knownCLIs = { "nuxt-ts.js": "nuxt-ts", "nuxt-start.js": "nuxt-start", "nuxt.js": "nuxt", "nuxi": "nuxi" }; for (const _key in knownCLIs) { const key = _key; if (entry.includes(key)) { const edge = entry.includes("-edge") ? "-edge" : entry.includes("-nightly") ? "-nightly" : ""; 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(cwd) { let gitRemoteUrl = null; try { gitRemoteUrl = execSync("git config --get remote.origin.url ", { encoding: "utf8", cwd }).trim() || null; } catch { } return gitRemoteUrl; } async function getGit(rootDir) { const gitRemote = await getGitRemote(rootDir); if (!gitRemote) { return; } const meta = gitUrlParse(gitRemote); const url = meta.toString("https"); return { url, gitRemote, source: meta.source, owner: meta.owner, name: meta.name }; } const logger = useLogger("@nuxt/telemetry"); 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 // size }; }; const command = function({ nuxt }) { let command2 = process.argv[2] || "unknown"; const flagMap = { dev: "dev", _generate: "generate", _export: "export", _build: "build", _serve: "serve", _start: "start" }; for (const _flag in flagMap) { const flag = _flag; if (nuxt.options[flag]) { command2 = flagMap[flag]; break; } } return { name: "command", command: command2 }; }; const generate = function generate2({ nuxt }, payload) { return { name: "generate", // @ts-expect-error Legacy type from Nuxt 2 isExport: !!nuxt.options._export, routesCount: payload.routesCount, duration: { generate: payload.duration.generate } }; }; const module$1 = function({ nuxt: { options } }) { const events = []; const modules = (options._installedModules || []).filter((m) => m.meta?.version).map((m) => ({ name: m.meta.name, version: m.meta.version, timing: m.timings?.setup || 0 })); for (const m of modules) { events.push({ name: "module", moduleName: m.name, version: m.version, timing: m.timing }); } return events; }; const project = function(context) { const { options } = context.nuxt; return { name: "project", type: context.git && context.git.url ? "git" : "local", isSSR: options.ssr !== false, target: options._generate ? "static" : "server", packageManager: context.packageManager }; }; const session = function({ seed }) { return { name: "session", id: seed }; }; const files = async function(context) { const { options } = context.nuxt; const nuxtIgnore = fs.existsSync(resolve(options.rootDir, ".nuxtignore")); const nuxtRc = fs.existsSync(resolve(options.rootDir, ".nuxtrc")); const appConfig = fs.existsSync(await resolvePath("~/app.config")); return { name: "files", nuxtIgnore, nuxtRc, appConfig }; }; class Telemetry { nuxt; options; storage; // TODO _contextPromise; events = []; eventFactories = { build, command, generate, module: module$1, project, session, files }; constructor(nuxt, options) { 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 = this.eventFactories[name]; if (typeof eventFactory !== "function") { logger.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) { logger.error("Error while running event:", err); } } async getPublicContext() { const context = await this.getContext(); const eventContext = {}; for (const key of [ "nuxtVersion", "nuxtMajorVersion", "isEdge", "nodeVersion", "cli", "os", "environment", "projectHash", "projectSession" ]) { eventContext[key] = context[key]; } return eventContext; } async sendEvents(debug) { const events = [].concat(...(await Promise.all(this.events)).filter(Boolean)); this.events = []; const context = await this.getPublicContext(); const body = { timestamp: Date.now(), context, events }; if (this.options.endpoint) { const start = Date.now(); try { if (debug) { logger.info("Sending events:", JSON.stringify(body, null, 2)); } await postEvent(this.options.endpoint, body); if (debug) { logger.success(`Events sent to \`${this.options.endpoint}\` (${Date.now() - start} ms)`); } } catch (err) { if (debug) { logger.error(`Error sending sent to \`${this.options.endpoint}\` (${Date.now() - start} ms) `, err); } } } } } const module = defineNuxtModule({ meta: { name: "@nuxt/telemetry", configKey: "telemetry" }, defaults: { endpoint: process.env.NUXT_TELEMETRY_ENDPOINT || "https://telemetry.nuxt.com", debug: destr(process.env.NUXT_TELEMETRY_DEBUG), enabled: void 0, seed: void 0 }, async setup(toptions, nuxt) { if (!toptions.debug) { logger.level = 0; } const _topLevelTelemetry = nuxt.options.telemetry; if (_topLevelTelemetry !== true) { if (toptions.enabled === false || _topLevelTelemetry === false || !await ensureUserconsent(toptions)) { logger.info("Telemetry disabled"); return; } } logger.info("Telemetry enabled"); if (!toptions.seed || typeof toptions.seed !== "string") { toptions.seed = randomSeed(); updateUserNuxtRc("telemetry.seed", toptions.seed); logger.info("Seed generated:", toptions.seed); } const t = new Telemetry(nuxt, toptions); nuxt.hook("modules:done", async () => { t.createEvent("project"); if (nuxt.options.dev) { t.createEvent("session"); t.createEvent("files"); } t.createEvent("command"); t.createEvent("module"); await nuxt.callHook("telemetry:setup", t); t.sendEvents(toptions.debug); }); } }); export { module as default };