@oclif/plugin-plugins
Version:
plugins plugin for oclif
127 lines (126 loc) • 5.48 kB
JavaScript
import { Args, Command, Flags } from '@oclif/core';
import { bold, dim } from 'ansis';
import { readFile } from 'node:fs/promises';
import { dirname, join, sep } from 'node:path';
// @ts-expect-error because object-treeify does not have types: https://github.com/blackflux/object-treeify/issues/1077
import treeify from 'object-treeify';
import { determineLogLevel } from '../../log-level.js';
import Plugins from '../../plugins.js';
import { sortBy } from '../../util.js';
function trimUntil(fsPath, part) {
const parts = fsPath.split(sep);
// eslint-disable-next-line unicorn/no-array-reduce
const indices = parts.reduce((result, current, index) => (current === part ? [...result, index] : result), []);
const partIndex = Math.max(...indices);
if (partIndex === -1)
return fsPath;
return parts.slice(0, partIndex + 1).join(sep);
}
export default class PluginsInspect extends Command {
static args = {
plugin: Args.string({
default: '.',
description: 'Plugin to inspect.',
required: true,
}),
};
static description = 'Displays installation properties of a plugin.';
static enableJsonFlag = true;
static examples = ['<%= config.bin %> <%= command.id %> <%- config.pjson.oclif.examplePlugin || "myplugin" %> '];
static flags = {
help: Flags.help({ char: 'h' }),
verbose: Flags.boolean({ char: 'v' }),
};
static strict = false;
static usage = 'plugins:inspect PLUGIN...';
plugins;
async findDep(plugin, dependency) {
const dependencyPath = join(...dependency.split('/'));
let start = join(plugin.root, 'node_modules');
const paths = [start];
while ((start.match(/node_modules/g) ?? []).length > 1) {
start = trimUntil(dirname(start), 'node_modules');
paths.push(start);
}
// NOTE: we cannot parallelize this because we need to check the paths in the order they were found.
// Parallelizing this would be faster, but would make the result non-deterministic.
for (const p of paths) {
try {
const fullPath = join(p, dependencyPath);
const pkgJsonPath = join(fullPath, 'package.json');
// eslint-disable-next-line no-await-in-loop
const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf8'));
return { pkgPath: fullPath, version: pkgJson.version };
}
catch {
// try the next path
}
}
return { pkgPath: null, version: null };
}
findPlugin(pluginName) {
const pluginConfig = this.config.getPluginsList().find((plg) => plg.name === pluginName);
if (pluginConfig)
return pluginConfig;
if (this.config.pjson.oclif.jitPlugins?.[pluginName]) {
this.warn(`Plugin ${pluginName} is a JIT plugin. It will be installed the first time you run one of it's commands.`);
}
throw new Error(`${pluginName} not installed`);
}
async inspect(pluginName, verbose = false) {
const plugin = this.findPlugin(pluginName);
const dependencies = {};
const depsJson = {};
for (const dep of sortBy(Object.keys({ ...plugin.pjson.dependencies }), (d) => d)) {
// eslint-disable-next-line no-await-in-loop
const { pkgPath, version } = await this.findDep(plugin, dep);
if (!version)
continue;
const from = plugin.pjson.dependencies?.[dep];
const versionMsg = dim(from ? `${from} => ${version}` : version);
const msg = verbose ? `${dep} ${versionMsg} ${pkgPath}` : `${dep} ${versionMsg}`;
dependencies[msg] = null;
depsJson[dep] = { from, version };
}
const tree = {
[bold.cyan(plugin.name)]: {
[`version ${plugin.version}`]: null,
...(plugin.tag ? { [`tag ${plugin.tag}`]: null } : {}),
...(plugin.pjson.homepage ? { [`homepage ${plugin.pjson.homepage}`]: null } : {}),
[`location ${plugin.root}`]: null,
commands: Object.fromEntries(sortBy(plugin.commandIDs, (c) => c).map((id) => [id, null])),
dependencies,
},
};
this.log(treeify(tree));
return { ...plugin, deps: depsJson };
}
/* eslint-disable no-await-in-loop */
async run() {
const { argv, flags } = await this.parse(PluginsInspect);
this.plugins = new Plugins({
config: this.config,
logLevel: determineLogLevel(this.config, flags, 'silent'),
});
const aliases = this.config.pjson.oclif.aliases ?? {};
const plugins = [];
for (let name of argv) {
if (name === '.') {
const pkgJson = JSON.parse(await readFile('package.json', 'utf8'));
name = pkgJson.name;
}
if (aliases[name] === null)
this.error(`${name} is blocked`);
name = aliases[name] ?? name;
const pluginName = (await this.plugins.maybeUnfriendlyName(name)) ?? name;
try {
plugins.push(await this.inspect(pluginName, flags.verbose));
}
catch (error) {
this.log(bold.red('failed'));
throw error;
}
}
return plugins;
}
}