UNPKG

@oclif/plugin-plugins

Version:
127 lines (126 loc) 5.48 kB
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; } }