@boost/debug
Version:
Lightweight debugging and crash reporting.
195 lines (181 loc) • 5.04 kB
JavaScript
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execaSync } from 'execa';
import glob from 'fast-glob';
import { toArray, json } from '@boost/common';
import { debug } from './debug.mjs';
/* eslint-disable no-magic-numbers */
function run(command, args) {
let cmd = command;
if (command === 'where' && os.platform() === 'win32') {
cmd += '.exe';
}
return String(execaSync(cmd, args, {
preferLocal: true
}).stdout);
}
function resolveHome(filePath) {
return filePath.replace(process.env.HOME, '~');
}
function extractVersion(value) {
const match = value.match(/\d+\.\d+\.\d+([.-a-z0-9])?/u);
return match ? match[0] : '';
}
class CrashReporter {
constructor() {
this.contents = '';
}
/**
* Add a label with a value, or multiple values, to the last added section.
*/
add(label, ...messages) {
this.contents += `${label}:\n`;
this.contents += ` ${messages.map(String).join(' - ')}\n`;
return this;
}
/**
* Start a new section with a title.
*/
addSection(title) {
this.contents += `\n${title.toUpperCase()}\n`;
this.contents += `${'='.repeat(title.length)}\n\n`;
debug('Reporting crash with %s', title);
return this;
}
/**
* Report Node.js related binary versions and paths.
*/
reportBinaries() {
this.addSection('Binaries');
const bins = {
node: 'Node',
npm: 'npm',
yarn: 'Yarn'
};
Object.keys(bins).forEach(bin => {
try {
this.add(bins[bin], extractVersion(run(bin, ['--version'])), resolveHome(run('where', [bin])));
} catch {
// Ignore
}
});
return this;
}
/**
* Report all environment variables.
*/
reportEnvVars() {
this.addSection('Environment');
const keys = Object.keys(process.env).sort();
keys.forEach(key => {
this.add(key, process.env[key]);
});
return this;
}
/**
* Report common programming language versions and paths
*/
reportLanguages() {
this.addSection('Languages');
const languages = {
bash: 'Bash',
go: 'Go',
javac: 'Java',
perl: 'Perl',
php: 'PHP',
python: 'Python',
ruby: 'Ruby',
rustup: 'Rust'
};
// When running on OSX and Java is not installed,
// OSX will interrupt the process with a prompt to install Java.
// This is super annoying, so let's not disrupt consumers.
// istanbul ignore next
if (os.platform() === 'darwin') {
delete languages.javac;
}
Object.keys(languages).forEach(bin => {
let version;
try {
version = extractVersion(run(bin, ['--version']));
if (!version) {
version = extractVersion(run(bin, ['version']));
}
if (version) {
this.add(languages[bin], version, resolveHome(run('where', [bin])));
}
} catch {
// Ignore
}
});
return this;
}
/**
* Report information about the current `process`.
*/
reportProcess() {
this.addSection('Process');
this.add('ID', process.pid);
this.add('Title', process.title);
this.add('Timestamp', new Date().toISOString());
this.add('CWD', process.cwd());
this.add('ARGV', process.argv.map(v => `- ${v}`).join('\n '));
return this;
}
/**
* Report the stack trace for a defined `Error`.
*/
reportStackTrace(error) {
this.addSection('Stack Trace');
this.contents += String(error.stack);
this.contents += '\n';
return this;
}
/**
* Report information about the platform and operating system.
*/
reportSystem() {
this.addSection('System');
this.add('OS', os.platform());
this.add('Architecture', os.arch());
this.add('CPUs', os.cpus().length);
this.add('Uptime (sec)', os.uptime());
this.add('Memory usage', `${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`);
// istanbul ignore next
if (process.platform !== 'win32') {
this.add('Group ID', process.getgid());
this.add('User ID', process.getuid());
}
return this;
}
/**
* Report npm package versions for all that match the defined pattern.
* Only searches in the root node modules folder and _will not_ work with PnP.
*/
reportPackageVersions(patterns, title = 'Packages') {
this.addSection(title);
const map = new Map();
glob.sync(toArray(patterns).map(pattern => path.join('./node_modules', pattern)), {
absolute: true,
onlyDirectories: true,
onlyFiles: false
}).forEach(pkgPath => {
const pkg = json.load(path.join(pkgPath, 'package.json'));
map.set(pkg.name, pkg.version);
});
map.forEach((version, name) => {
this.add(name, version);
});
return this;
}
/**
* Write the reported content to the defined file path.
*/
write(filePath) {
fs.writeFileSync(String(filePath), this.contents.trim(), 'utf8');
return this;
}
}
export { CrashReporter };
//# sourceMappingURL=CrashReporter.mjs.map