nest-commander
Version:
A module for making CLI applications with NestJS. Decorators for running commands and separating out config parsers included. This package works on top of commander.
222 lines • 11.4 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandRunnerService = void 0;
const common_1 = require("@nestjs/common");
const nestjs_discovery_1 = require("@golevelup/nestjs-discovery");
const commander_1 = require("commander");
const constants_1 = require("./constants");
const command_decorators_1 = require("./command.decorators");
let CommandRunnerService = class CommandRunnerService {
constructor(discoveryService, commander, options, logger) {
this.discoveryService = discoveryService;
this.commander = commander;
this.options = options;
this.logger = logger;
}
async onModuleInit() {
var _a;
await this.setUpDefaultCommand();
const providers = await this.discoveryService.providersWithMetaAtKey(constants_1.CommandMeta);
const commands = await this.populateCommandMapInstances(providers);
await this.setUpCommander(commands);
if (this.options.usePlugins) {
this.commander.showHelpAfterError(`
${this.commander.helpInformation()}
${(0, constants_1.cliPluginError)((_a = this.options.cliName) !== null && _a !== void 0 ? _a : 'nest-commander', this.options.pluginsAvailable)}`);
}
if (this.options.helpConfiguration) {
this.commander.configureHelp(this.options.helpConfiguration);
}
if (this.options.errorHandler) {
this.commander.exitOverride(this.options.errorHandler);
}
if (!this.options.serviceErrorHandler) {
this.options.serviceErrorHandler = (err) => {
process.stderr.write(err.toString());
};
}
if (this.options.outputConfiguration) {
this.commander.configureOutput(this.options.outputConfiguration);
}
if (this.options.version) {
this.commander.version(this.options.version);
}
}
/**
* override the initial `commander` instance to be the `@DefaultCommand()`
* This will allow for the -h action on a default call of the command to
* provide the information from the default command and not the overall
* application.
*/
async setUpDefaultCommand() {
const [defaultCommand, ...others] = await this.discoveryService.providersWithMetaAtKey(constants_1.RootCommandMeta);
if (others === null || others === void 0 ? void 0 : others.length) {
throw new Error('You can only have one @RootCommand() in your application');
}
if (!defaultCommand) {
return;
}
const [populatedCommand] = await this.populateCommandMapInstances([
defaultCommand,
]);
const builtDefault = await this.buildCommand(populatedCommand);
this.commander = builtDefault;
}
async populateCommandMapInstances(providers) {
const commands = [];
for (const provider of providers) {
const optionProviders = await this.discoveryService.providerMethodsWithMetaAtKey(constants_1.OptionMeta, (found) => found.name === provider.discoveredClass.name);
const helpProviders = await this.discoveryService.providerMethodsWithMetaAtKey(constants_1.HelpMeta, (found) => found.name === provider.discoveredClass.name);
commands.push({
command: provider.meta,
instance: provider.discoveredClass.instance,
params: optionProviders,
help: helpProviders,
});
}
return commands;
}
async setUpCommander(commands) {
for (const command of commands) {
const newCommand = await this.buildCommand(command);
this.commander.addCommand(newCommand, command.command.options);
}
}
async buildCommand(command) {
var _a, _b, _c, _d, _e, _f, _g;
const newCommand = new commander_1.Command(command.command.name);
command.instance.setCommand(newCommand);
if (this.options.outputConfiguration) {
newCommand.configureOutput(this.options.outputConfiguration);
}
if (this.options.helpConfiguration) {
newCommand.configureHelp(this.options.helpConfiguration);
}
if (command.command.allowUnknownOptions) {
newCommand.allowUnknownOption();
}
if (command.command.allowExcessArgs) {
newCommand.allowExcessArguments();
}
if (command.command.arguments) {
this.mapArgumentDescriptions(newCommand, command.command.arguments, command.command.argsDescription);
}
newCommand.description((_a = command.command.description) !== null && _a !== void 0 ? _a : '');
// Needs to be applied to every command
// Commands created with the constructor do not inherit from the parent
if (this.options.enablePositionalOptions) {
newCommand.enablePositionalOptions(true);
}
if (this.options.enablePassThroughOptions) {
newCommand.passThroughOptions(true);
}
const optionNameMap = {};
for (const option of command.params) {
const { flags, description, defaultValue = undefined, required = false, choices = [], name: optionName = '', env = undefined, } = option.meta;
const handler = option.discoveredMethod.handler.bind(command.instance);
const commandOption = new commander_1.Option(flags, description)
.default(defaultValue)
.preset(defaultValue)
.makeOptionMandatory(required);
// choices can be a true boolean or an array of string options for commander.
// If a boolean, then we know that we are expected to go find the OptionChoiceFOr method.
if (choices === true || (Array.isArray(choices) && choices.length)) {
let optionChoices = [];
if (choices === true) {
const choicesMethods = await this.discoveryService.providerMethodsWithMetaAtKey(constants_1.OptionChoiceMeta, (item) => item.instance === command.instance);
const cMethod = choicesMethods
.filter((choiceMethod) => choiceMethod.meta.name === optionName)
.map((method) => method.discoveredMethod)[0];
optionChoices = cMethod.handler.call(command.instance);
}
else {
optionChoices = choices;
}
commandOption.choices(optionChoices);
}
if (env) {
commandOption.env(env);
}
commandOption.argParser(handler);
newCommand.addOption(commandOption);
optionNameMap[commandOption.attributeName()] =
optionName || commandOption.attributeName();
}
for (const help of (_b = command.help) !== null && _b !== void 0 ? _b : []) {
newCommand.addHelpText(help.meta, help.discoveredMethod.handler.bind(command.instance));
}
(_c = command.command.aliases) === null || _c === void 0 ? void 0 : _c.forEach((alias) => newCommand.alias(alias));
newCommand.action(async () => {
var _a, _b;
try {
command.instance.run.bind(command.instance);
const passedOptions = newCommand.opts();
const trueOptions = {};
for (const opt in passedOptions) {
trueOptions[optionNameMap[opt]] = passedOptions[opt];
}
return await command.instance.run(newCommand.args, trueOptions);
}
catch (err) {
if (err instanceof Error) {
if (err.message.includes('Cannot read properties of undefined')) {
const className = (_b = /\s+at\s(\w+)\.run/.exec((_a = err.stack) !== null && _a !== void 0 ? _a : '')) === null || _b === void 0 ? void 0 : _b[1];
this.logger.error(`A service tried to call a property of "undefined" in the ${className} class. Did you use a request scoped provider without the decorator?\n\n${err.message}`, err.stack, 'CommandRunnerService');
}
}
throw err;
}
});
if ((_d = command.command.subCommands) === null || _d === void 0 ? void 0 : _d.length) {
(_e = this.subCommands) !== null && _e !== void 0 ? _e : (this.subCommands = await this.discoveryService.providersWithMetaAtKey(constants_1.SubCommandMeta));
const subCommandsMetaForCommand = this.subCommands.filter((subMeta) => {
var _a;
return (_a = command.command.subCommands) === null || _a === void 0 ? void 0 : _a.map((subCommand) => subCommand.name).includes(subMeta.discoveredClass.name);
});
const subCommands = await this.populateCommandMapInstances(subCommandsMetaForCommand);
for (const subCommand of subCommands) {
newCommand.addCommand(await this.buildCommand(subCommand), {
isDefault: (_g = (_f = subCommand.command.options) === null || _f === void 0 ? void 0 : _f.isDefault) !== null && _g !== void 0 ? _g : false,
});
}
}
return newCommand;
}
mapArgumentDescriptions(command, args = '', argDescriptions = {}) {
const trueArgDefs = {};
const splitArgs = args.split(' ');
for (const arg of splitArgs) {
let added = false;
for (const key of Object.keys(argDescriptions).filter((key) => arg.includes(key))) {
added = true;
trueArgDefs[arg] = argDescriptions[key];
}
command.argument(arg, added ? trueArgDefs[arg] : '');
}
}
async run(args) {
await this.commander
.parseAsync(args || process.argv)
.catch(this.options.serviceErrorHandler);
}
};
exports.CommandRunnerService = CommandRunnerService;
exports.CommandRunnerService = CommandRunnerService = __decorate([
__param(1, (0, command_decorators_1.InjectCommander)()),
__param(2, (0, common_1.Inject)(constants_1.CommanderOptions)),
__metadata("design:paramtypes", [nestjs_discovery_1.DiscoveryService,
commander_1.Command, Object, common_1.Logger])
], CommandRunnerService);
//# sourceMappingURL=command-runner.service.js.map