@oclif/plugin-commands
Version:
plugin to show the list of all the commands
136 lines (135 loc) • 5.72 kB
JavaScript
import { Command, Flags, toConfiguredId, ux } from '@oclif/core';
import pickBy from 'lodash.pickby';
import sortBy from 'lodash.sortby';
import template from 'lodash.template';
import uniqBy from 'lodash.uniqby';
import { EOL } from 'node:os';
import createCommandTree from '../utils/tree.js';
export default class Commands extends Command {
static description = 'list all the commands';
static enableJsonFlag = true;
static flags = {
help: Flags.help({ char: 'h' }),
hidden: Flags.boolean({ description: 'show hidden commands' }),
tree: Flags.boolean({ description: 'show tree of commands' }),
...ux.table.flags(),
};
async run() {
const { flags } = await this.parse(Commands);
let commands = this.getCommands();
if (!flags.hidden) {
commands = commands.filter((c) => !c.hidden);
}
const { config } = this;
commands = sortBy(commands, 'id').map((command) => {
// Template supported fields.
command.description =
(typeof command.description === 'string' && template(command.description)({ command, config })) || undefined;
command.summary =
(typeof command.summary === 'string' && template(command.summary)({ command, config })) || undefined;
command.usage = (typeof command.usage === 'string' && template(command.usage)({ command, config })) || undefined;
command.id = toConfiguredId(command.id, config);
return command;
});
if (this.jsonEnabled() && !flags.tree) {
const formatted = await Promise.all(commands.map(async (cmd) => {
let commandClass;
try {
commandClass = await cmd.load();
}
catch (error) {
this.debug(error);
}
const obj = { ...cmd, ...commandClass };
// Load all properties on all extending classes.
while (commandClass !== undefined) {
commandClass = Object.getPrototypeOf(commandClass) ?? undefined;
// ES2022 will return all unset static properties on the prototype as undefined. This is different from ES2021
// which only returns the static properties that are set by defaults. In order to prevent
// Object.assign from overwriting the properties on the object, we need to filter out the undefined values.
Object.assign(obj, pickBy(commandClass, (v) => v !== undefined));
}
// The plugin property on the loaded class contains a LOT of information including all the commands again. Remove it.
delete obj.plugin;
// If Command classes have circular references, don't break the commands command.
return this.removeCycles(obj);
}));
return uniqBy(formatted, 'id');
}
if (flags.tree) {
const tree = createCommandTree(commands, config.topicSeparator);
if (!this.jsonEnabled()) {
tree.display();
}
return tree;
}
ux.table(commands.map((command) => {
// Massage some fields so it looks good in the table
command.description = (command.description ?? '').split(EOL)[0];
command.summary = command.summary ?? (command.description ?? '').split(EOL)[0];
command.hidden = Boolean(command.hidden);
command.usage ??= '';
return command;
}), {
description: {
extended: true,
},
hidden: {
extended: true,
},
id: {
header: 'Command',
},
pluginName: {
extended: true,
header: 'Plugin',
},
pluginType: {
extended: true,
header: 'Type',
},
summary: {},
usage: {
extended: true,
},
}, {
// to-do: investigate this oclif/core error when printLine is enabled
// printLine: this.log,
...flags, // parsed flags
});
}
getCommands() {
return this.config.commands;
}
removeCycles(object) {
// Keep track of seen objects.
const seenObjects = new WeakMap();
const _removeCycles = (obj) => {
// Use object prototype to get around type and null checks
if (Object.prototype.toString.call(obj) === '[object Object]') {
// We know it is a "Dictionary" because of the conditional
const dictionary = obj;
// Seen, return undefined to remove.
if (seenObjects.has(dictionary))
return;
seenObjects.set(dictionary, undefined);
for (const key in dictionary) {
// Delete the duplicate object if cycle found.
if (_removeCycles(dictionary[key]) === undefined) {
delete dictionary[key];
}
}
}
else if (Array.isArray(obj)) {
for (const i in obj) {
if (_removeCycles(obj[i]) === undefined) {
// We don't want to delete the array, but we can replace the element with null.
obj[i] = null;
}
}
}
return obj;
};
return _removeCycles(object);
}
}