2025-09-05 14:59:21 +08:00

378 lines
9.7 KiB
JavaScript

'use strict';
const destr = require('destr');
const kit = require('@nuxt/kit');
const consent = require('./shared/telemetry.BS0NLRIM.cjs');
const ofetch = require('ofetch');
const os = require('node:os');
const node_child_process = require('node:child_process');
const gitUrlParse = require('git-url-parse');
const isDocker = require('is-docker');
const stdEnv = require('std-env');
const packageManagerDetector = require('package-manager-detector');
const node_crypto = require('node:crypto');
const fs = require('node:fs');
const pathe = require('pathe');
require('consola/utils');
require('consola');
require('rc9');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const os__default = /*#__PURE__*/_interopDefaultCompat(os);
const gitUrlParse__default = /*#__PURE__*/_interopDefaultCompat(gitUrlParse);
const isDocker__default = /*#__PURE__*/_interopDefaultCompat(isDocker);
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
async function postEvent(endpoint, body) {
const res = await ofetch.fetch(endpoint, {
method: "POST",
body: JSON.stringify(body),
headers: {
"content-type": "application/json",
"user-agent": "Nuxt Telemetry " + consent.version
}
});
if (!res.ok) {
throw new Error(res.statusText);
}
}
function hash(str) {
return node_crypto.createHash("sha256").update(str).digest("hex").substr(0, 16);
}
function randomSeed() {
return hash(node_crypto.randomUUID());
}
async function createContext(nuxt, options) {
const rootDir = nuxt.options.rootDir || process.cwd();
const git = await getGit(rootDir);
const packageManager = await packageManagerDetector.detect({ cwd: rootDir });
const { seed } = options;
const projectHash = await getProjectHash(rootDir, git, seed);
const projectSession = getProjectSession(projectHash, seed);
const nuxtVersion = kit.getNuxtVersion(nuxt);
const nuxtMajorVersion = kit.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__default.type().toLocaleLowerCase(),
environment: getEnv(),
packageManager: packageManager?.name || "unknown",
concent: options.consent
};
}
function getEnv() {
if (stdEnv.provider) {
return stdEnv.provider;
}
if (isDocker__default()) {
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 = node_child_process.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__default(gitRemote);
const url = meta.toString("https");
return {
url,
gitRemote,
source: meta.source,
owner: meta.owner,
name: meta.name
};
}
const logger = kit.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$2 = 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__default.existsSync(pathe.resolve(options.rootDir, ".nuxtignore"));
const nuxtRc = fs__default.existsSync(pathe.resolve(options.rootDir, ".nuxtrc"));
const appConfig = fs__default.existsSync(await kit.resolvePath("~/app.config"));
return {
name: "files",
nuxtIgnore,
nuxtRc,
appConfig
};
};
class Telemetry {
nuxt;
options;
storage;
// TODO
_contextPromise;
events = [];
eventFactories = {
build,
command,
generate,
module: module$2,
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$1 = kit.defineNuxtModule({
meta: {
name: "@nuxt/telemetry",
configKey: "telemetry"
},
defaults: {
endpoint: process.env.NUXT_TELEMETRY_ENDPOINT || "https://telemetry.nuxt.com",
debug: destr.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 consent.ensureUserconsent(toptions)) {
logger.info("Telemetry disabled");
return;
}
}
logger.info("Telemetry enabled");
if (!toptions.seed || typeof toptions.seed !== "string") {
toptions.seed = randomSeed();
consent.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);
});
}
});
module.exports = module$1;