forked from daren.hsu/line_push
607 lines
13 KiB
JavaScript
607 lines
13 KiB
JavaScript
'use strict';
|
|
|
|
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
|
|
var destr = _interopDefault(require('destr'));
|
|
var nanoid = require('nanoid');
|
|
var rc = require('rc9');
|
|
var fetch = _interopDefault(require('node-fetch'));
|
|
var meta = require('./meta-73536716.js');
|
|
var path = require('path');
|
|
var path__default = _interopDefault(path);
|
|
var fs = require('fs');
|
|
var createRequire = _interopDefault(require('create-require'));
|
|
var os = _interopDefault(require('os'));
|
|
var gitUrlParse = _interopDefault(require('git-url-parse'));
|
|
var parseGitConfig = _interopDefault(require('parse-git-config'));
|
|
var isDocker = _interopDefault(require('is-docker'));
|
|
var ci = _interopDefault(require('ci-info'));
|
|
var fs$1 = _interopDefault(require('fs-extra'));
|
|
var crypto = require('crypto');
|
|
var consola = _interopDefault(require('consola'));
|
|
var c = _interopDefault(require('chalk'));
|
|
var inquirer = _interopDefault(require('inquirer'));
|
|
var stdEnv = _interopDefault(require('std-env'));
|
|
|
|
var name = "@nuxt/telemetry";
|
|
var version = "1.2.2";
|
|
|
|
function updateUserNuxtRc(key, val) {
|
|
rc.updateUser({
|
|
[key]: val
|
|
}, '.nuxtrc');
|
|
}
|
|
|
|
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
|
|
},
|
|
timeout: 4000 // Important: should be less than 5 seconds for prod
|
|
|
|
});
|
|
|
|
if (!res.ok) {
|
|
throw new Error(res.statusText);
|
|
}
|
|
}
|
|
|
|
const build = function ({
|
|
nuxt
|
|
}, payload) {
|
|
const duration = {
|
|
build: payload.duration.build
|
|
}; // const size = {}
|
|
|
|
let isSuccess = true;
|
|
|
|
for (const [name, stat] of Object.entries(payload.stats)) {
|
|
duration[name] = stat.duration; // size[name] = stat.size
|
|
|
|
if (!stat.success) {
|
|
isSuccess = false;
|
|
}
|
|
}
|
|
|
|
return {
|
|
name: 'build',
|
|
isSuccess,
|
|
isDev: nuxt.options.dev || false,
|
|
duration // size
|
|
|
|
};
|
|
};
|
|
|
|
const command = function ({
|
|
nuxt
|
|
}) {
|
|
let command = 'unknown';
|
|
const flagMap = {
|
|
dev: 'dev',
|
|
_generate: 'generate',
|
|
_export: 'export',
|
|
_build: 'build',
|
|
_serve: 'serve',
|
|
_start: 'start'
|
|
};
|
|
|
|
for (const flag in flagMap) {
|
|
if (nuxt.options[flag]) {
|
|
command = flagMap[flag];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
name: 'command',
|
|
command
|
|
};
|
|
};
|
|
|
|
const generate = function generate({
|
|
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); // Get used modules and buildModules
|
|
|
|
const modules = normalizeModules(options.modules);
|
|
const buildModules = normalizeModules(options.buildModules); // Only send event for relevant dependencies
|
|
|
|
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];
|
|
}
|
|
}).filter(Boolean);
|
|
}
|
|
|
|
function getDependencies(rootDir) {
|
|
const pkgPath = path.join(rootDir, 'package.json');
|
|
|
|
if (!fs.existsSync(pkgPath)) {
|
|
return [];
|
|
}
|
|
|
|
const _require = createRequire(rootDir);
|
|
|
|
const pkg = _require(pkgPath);
|
|
|
|
const mapDeps = (depsObj, dev = false) => {
|
|
const _deps = [];
|
|
|
|
for (const name in depsObj) {
|
|
try {
|
|
const pkg = _require(path.join(name, 'package.json'));
|
|
|
|
_deps.push({
|
|
name,
|
|
version: pkg.version,
|
|
dev
|
|
});
|
|
} catch (_e) {
|
|
// Dependency is not installed
|
|
_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
|
|
};
|
|
};
|
|
|
|
var events = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
build: build,
|
|
command: command,
|
|
generate: generate,
|
|
dependency: dependency,
|
|
getDependencies: getDependencies,
|
|
project: project,
|
|
session: session
|
|
});
|
|
|
|
const FILE2PM = {
|
|
'yarn.lock': 'yarn',
|
|
'package-lock.json': 'npm',
|
|
'shrinkwrap.json': 'npm'
|
|
};
|
|
async function detectPackageManager(rootDir) {
|
|
for (const file in FILE2PM) {
|
|
if (await fs$1.pathExists(path__default.resolve(rootDir, file))) {
|
|
// @ts-ignore
|
|
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.type().toLocaleLowerCase(),
|
|
environment: getEnv(),
|
|
packageManager
|
|
};
|
|
}
|
|
|
|
function getEnv() {
|
|
if (process.env.CODESANDBOX_SSE) {
|
|
return 'CSB';
|
|
}
|
|
|
|
if (ci.isCI) {
|
|
return ci.name;
|
|
}
|
|
|
|
if (isDocker()) {
|
|
return 'Docker';
|
|
}
|
|
|
|
return 'unknown';
|
|
}
|
|
|
|
function getCLI() {
|
|
const entry = require.main.filename;
|
|
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({
|
|
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(gitRemote);
|
|
const url = meta.toString('https');
|
|
return {
|
|
url,
|
|
gitRemote,
|
|
source: meta.source,
|
|
owner: meta.owner,
|
|
name: meta.name
|
|
};
|
|
}
|
|
|
|
var log = consola.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) {
|
|
// @ts-ignore
|
|
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 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 {
|
|
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)\n`, 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 >= meta.consentVersion || stdEnv.minimal || process.env.CODESANDBOX_SSE || isDocker()) {
|
|
return true;
|
|
}
|
|
|
|
process.stdout.write('\n');
|
|
consola.info(`${c.green('NuxtJS')} collects completely anonymous data about usage.
|
|
This will help us improving Nuxt developer experience over the time.
|
|
Read more on ${c.cyan.underline('https://git.io/nuxt-telemetry')}\n`);
|
|
const {
|
|
accept
|
|
} = await inquirer.prompt({
|
|
type: 'confirm',
|
|
name: 'accept',
|
|
message: 'Are you interested in participation?'
|
|
});
|
|
process.stdout.write('\n');
|
|
|
|
if (accept) {
|
|
updateUserNuxtRc('telemetry.consent', meta.consentVersion);
|
|
updateUserNuxtRc('telemetry.enabled', true);
|
|
return true;
|
|
}
|
|
|
|
updateUserNuxtRc('telemetry.enabled', false);
|
|
return false;
|
|
}
|
|
|
|
async function _telemetryModule(nuxt) {
|
|
const toptions = {
|
|
endpoint: destr(process.env.NUXT_TELEMETRY_ENDPOINT) || 'https://telemetry.nuxtjs.com',
|
|
debug: destr(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 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);
|
|
}
|
|
|
|
async function telemetryModule() {
|
|
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 = name => {
|
|
startT[name] = Date.now();
|
|
};
|
|
|
|
const timeEnd = name => {
|
|
duration[name] = Date.now() - startT[name];
|
|
}; // Total build timing
|
|
|
|
|
|
nuxt.hook('build:before', () => {
|
|
timeStart('build');
|
|
});
|
|
nuxt.hook('build:done', () => {
|
|
timeEnd('build');
|
|
});
|
|
nuxt.hook('build:compiled', ({
|
|
name,
|
|
stats: _stats
|
|
}) => {
|
|
// @ts-ignore
|
|
stats[name] = getStats(_stats);
|
|
}); // Generate timing
|
|
// TODO: workaround as generate:before is before build
|
|
|
|
nuxt.hook('generate:extendRoutes', () => timeStart('generate'));
|
|
nuxt.hook('generate:routeCreated', () => {
|
|
routesCount++;
|
|
});
|
|
nuxt.hook('generate:done', () => {
|
|
timeEnd('generate'); // nuxt generate or nuxt export
|
|
|
|
t.createEvent('generate', {
|
|
duration,
|
|
stats,
|
|
routesCount
|
|
});
|
|
t.sendEvents();
|
|
}); // Report build time
|
|
|
|
nuxt.hook('build:done', () => {
|
|
// nuxt build or nuxt dev
|
|
t.createEvent('build', {
|
|
duration,
|
|
stats
|
|
});
|
|
t.sendEvents();
|
|
});
|
|
}
|
|
|
|
telemetryModule.meta = {
|
|
name,
|
|
version
|
|
};
|
|
|
|
module.exports = telemetryModule;
|