pjv
Version:
package.json visualizer
306 lines (300 loc) • 12.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/*=============================================================================
pjv - package.json visualization tool
- an alternative of cat and bat tools providing formatted view of config files:
.env: aligned table
package.json: especially the "scripts" field
- Installation
$ yarn global add pjv
- Option arguments
s - print scripts only in package.json and .env
https://docs.npmjs.com/cli/v7/configuring-npm/package-json
TODO: When the element of array is an object, formatting is required. [object Object]
- test case: ~/.config/yarn/global/node_modules/yaml/
(C) 2022 SpacetimeQ INC
=============================================================================*/
const fs_1 = __importDefault(require("fs"));
const dotenv_1 = __importDefault(require("dotenv"));
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk")); // for TypeScript use version ^4
const semver_1 = __importDefault(require("semver"));
const child_process_1 = require("child_process");
const SEP = chalk_1.default.gray(': '); // separator between key and value
const SEP0 = chalk_1.default.gray(':');
const BULLET = '▫️ ';
const MIN_PAD_VER = 6; // minimum padding for version strings
const SEMVERSIGN = new Map([
['major', '❌'],
['minor', '✅'],
['patch', '✔️ '],
]);
;
;
;
;
const packVers = [];
let packageName = '';
const dirBase = path_1.default.basename(process.cwd());
const isObject = (o) => (typeof (o) === 'object') && !Array.isArray(o); // array also is an 'object'
let rootPath = './';
const PACKAGE = 'package.json';
const DOTENV = '.env';
const HELP = [
['v', "version", "package version"],
['e', "env", ".env only"],
['s', "scripts", "scripts (in package.json) only"],
['x', "xcheck", "skip checking npm outdated packages that takes time"],
['h', "help", "help"],
];
const [, , ...args] = process.argv;
let vmode = 'all';
if (args[0]) {
const arg = args[0];
const { name, version, description } = require('../' + PACKAGE); // own package
const option = (arg[0] === '-') // allow up to two '-' for the option prefix
? (arg[1] === '-')
? arg[2] // arg.substring(2) // long
: arg[1] // short
: '';
if (option) {
switch (option.toLowerCase()[0]) {
case 'e':
vmode = 'dotenv';
break;
case 's':
vmode = 'scripts';
break;
case 'x':
vmode = 'xoutdated';
break;
case 'v':
console.log(version);
// console.log(process.env.npm_package_version);
process.exit(0);
default:
console.log(chalk_1.default.yellowBright(name), chalk_1.default.greenBright('v' + version), description, '-', chalk_1.default.yellowBright('node'), chalk_1.default.greenBright(process.version));
console.log(' ', chalk_1.default.gray('by'), chalk_1.default.blue("SPACE") + chalk_1.default.green('TIME') + chalk_1.default.red('Q'));
console.log(chalk_1.default.yellow("OPTIONS:"));
let pad = 7; // set the longest length in HELP's long option
HELP.forEach(arr => {
if (arr[1].length > pad)
pad = arr[1].length;
});
HELP.forEach(arr => console.log(' ', chalk_1.default.green('-' + arr[0]) + ', ' +
chalk_1.default.green('--' + arr[1].padEnd(pad)), arr[2]));
process.exit(0);
}
}
else {
rootPath = arg; // TODO: REGEX check required
if (rootPath[rootPath.length - 1] !== '/')
rootPath += '/';
}
}
// arg: a | A - all
const SCRIPT_HL = [
'start',
'serve',
'build',
];
// pad should be the length of the longest key + 1
const maxKeyLen = (obj) => {
let len = 0;
(Array.isArray(obj) ? obj : Object.keys(obj)).forEach((key) => {
if (key.length > len)
len = key.length;
});
return len;
};
// log filename
const logFilename = (name) => console.log(chalk_1.default.white.bgGray(` ${name}`.padEnd(80)));
const logField = (field) => console.log(chalk_1.default.black.bgCyan(` ◀︎ ${field} ▶︎ `));
const logNumbering = (n) => chalk_1.default.gray(`${(n + 1).toString().padStart(3)}`);
const logSubObj = (obj, key) => {
console.log(BULLET, chalk_1.default.cyanBright(key));
Object.entries(obj).forEach(([kv, vv], iv) => {
if (isObject(vv)) {
logSubObj(vv, kv); // recursively call
}
else {
const pk = ` ${kv.padEnd(maxKeyLen(obj))} `;
console.log(logNumbering(iv), chalk_1.default.yellow(pk) + SEP + chalk_1.default.green(vv));
}
});
};
// iterate object
const logKeyValueObj = (obj, key) => {
const o = obj[key];
const isScript = key === 'scripts';
const isDep = key.toLowerCase().includes('dependencies');
if (!isScript && vmode === 'scripts')
return;
if (isDep && vmode !== 'xoutdated') {
const dev = key === 'devDependencies';
Object.entries(o).forEach(([name, ver]) => packVers.push({ dep: dirBase, name, dev, ver: ver }));
return; // just keep in packVers to print when outdated versions are fetched
}
logField(key);
Object.entries(o).forEach(([k, val], i) => {
if (isObject(val)) {
logSubObj(val, k);
}
else {
const pk = ` ${k.padEnd(maxKeyLen(o))} `; // padded key
console.log(logNumbering(i), isScript
? SCRIPT_HL.includes(k)
? chalk_1.default.black.bgYellow(pk) + SEP + chalk_1.default.yellow(val)
: chalk_1.default.yellow(pk) + SEP + chalk_1.default.white(val)
: (isDep && k.includes('@'))
? chalk_1.default.redBright(pk) + SEP + chalk_1.default.blue(val)
: chalk_1.default.yellow(pk) + SEP + chalk_1.default.cyan(val));
}
});
};
const logKeyValue = (obj, key, pad) => {
let content = obj[key];
if (key === 'name' && packageName !== dirBase) {
content += '🚨' + chalk_1.default.redBright(' ≠../' + dirBase);
}
console.log(BULLET, chalk_1.default.cyanBright(`${key.padEnd(pad)}`), SEP0, content);
};
try {
fs_1.default.access(rootPath + DOTENV, fs_1.default.constants.F_OK, (err) => {
if (err) {
// console.log("No .env");
return;
}
logFilename('.env');
const env = dotenv_1.default.parse(fs_1.default.readFileSync(rootPath + DOTENV, 'utf8'));
const pad = maxKeyLen(env);
Object.entries(env).forEach(([key, val], i) => {
console.log(logNumbering(i), key.padEnd(pad) + SEP + chalk_1.default.greenBright(val));
});
});
}
catch (err) {
console.error(err);
}
if (vmode !== 'dotenv') {
try {
fs_1.default.access(rootPath + PACKAGE, fs_1.default.constants.F_OK, (err) => {
if (err) { // file not found
console.log("⛔️ No", rootPath + PACKAGE);
process.exit(1);
}
const json = JSON.parse(fs_1.default.readFileSync(rootPath + PACKAGE, 'utf8'));
packageName = json.name;
console.log(chalk_1.default.white.bgGray(` ${PACKAGE} `) +
chalk_1.default.black.bgWhite(` ${packageName} `) +
chalk_1.default.black.bgGreen(` ${json.version || 'no version'} `) +
chalk_1.default.green.bgBlack(` ${json.description || ''} `));
// Partition keys array by its content if it is of pure object type or not
const [ob, no] = Object.keys(json).reduce(([ob, no], k) => (isObject(json[k])
? [[...ob, k], no]
: [ob, [...no, k]]), [[], []]);
if (vmode !== 'scripts') {
const pad = maxKeyLen(no);
no.forEach(key => logKeyValue(json, key, pad));
}
ob.forEach(key => logKeyValueObj(json, key));
});
}
catch (err) {
console.error(err);
}
}
/**
* package versions
* @param pack - package versions object
* @param pd - padding 0: package nanme, 1: package version, 2: Current, 3: Wanted, 4: Latest
*/
const logPackVers = (pack, pd) => {
if (!pack.length)
return;
const field = (pack[0].dev ? 'devDependencies' : 'dependencies').padEnd(pd[0] + pd[1] + 2);
console.log(chalk_1.default.black.bgCyan(` ◀︎ ${field.padEnd(pd[0] + pd[1] + 2)} ▶︎ `), chalk_1.default.black.bgWhite((pd[2] > MIN_PAD_VER ? 'Current' : 'Cur').padStart(pd[2])), chalk_1.default.black.bgGreen('Wanted'.padStart(pd[3])), chalk_1.default.black.bgMagenta('Latest'.padStart(pd[4])));
pack.forEach((p, i) => {
const nk = ` ${p.name.padEnd(pd[0])} `;
let sn = ''; // sign
if (p.outd) {
const df = semver_1.default.diff(p.outd.current || p.outd.wanted, p.outd.latest) || '';
const svsn = df && SEMVERSIGN.get(df);
sn = svsn || df;
if (p.dep !== dirBase)
sn += ' ' + chalk_1.default.blueBright(p.dep);
}
console.log(logNumbering(i), p.name.includes('@')
? (p.name.includes('@types/')
? chalk_1.default.blueBright.italic(nk)
: chalk_1.default.redBright(nk)) + SEP + chalk_1.default.blue(p.ver.padStart(pd[1]))
: chalk_1.default.yellow(nk) + SEP + chalk_1.default.cyan(p.ver.padStart(pd[1])), p.outd
? chalk_1.default.white((p.outd.current || '-').padStart(pd[2])) + ' ' +
chalk_1.default.greenBright((p.outd.wanted || ' ').padStart(pd[3])) + ' ' +
chalk_1.default.magentaBright((p.outd.latest || ' ').padStart(pd[4])) + ' ' + sn
: '✨');
});
};
/**
* assumption that packVers was already built by reading package.json
* depent name from 'npm outdated' is the current folder name
*/
if (vmode !== 'xoutdated' &&
vmode !== 'dotenv' &&
vmode !== 'scripts') {
// --------------------------------------------------------------------------------
// takes time to run the external command
(0, child_process_1.exec)('npm outdated --json', (error, stdout) => {
// --------------------------------------------------------------------------------
if (stdout) {
// console.log(stdout);
const pd = Array(5).fill(MIN_PAD_VER, 1); // minimum padding
pd[0] = 0; // minimum padding for package name
const json = JSON.parse(stdout);
Object.entries(json).forEach(([key, val]) => {
const { current, wanted, latest } = val;
if ((current === null || current === void 0 ? void 0 : current.length) > pd[2])
pd[2] = current.length;
if ((wanted === null || wanted === void 0 ? void 0 : wanted.length) > pd[3])
pd[3] = wanted.length;
if ((latest === null || latest === void 0 ? void 0 : latest.length) > pd[4])
pd[4] = latest.length;
if (val.dependent === dirBase) {
const fo = packVers.find(x => x.name === key);
if (fo) {
fo.outd = { current, wanted, latest };
}
else {
console.error(key, "not found!");
}
}
else { // dependent is another workspace
packVers.push({
dep: val.dependent,
name: key,
dev: false,
ver: '',
outd: { current, wanted, latest }
});
}
});
packVers.forEach(p => {
if (p.name.length > pd[0])
pd[0] = p.name.length;
if (p.ver.length > pd[1])
pd[1] = p.ver.length;
});
// partition array into prod dependencies and devDependencies
const [pro, dev] = packVers.reduce(([pro, dev], k) => (k.dev ? [pro, [...dev, k]] : [[...pro, k], dev]), [[], []]);
logPackVers(pro, pd);
logPackVers(dev, pd);
}
if (!error) {
console.log(packVers.length ? '✨No outdated modules!' : "⚠️ No dependencies.");
}
});
}