@absolunet/ioc2.1.0

View on GitHub

console/services/CommandRegistrar.js

//--------------------------------------------------------
//-- Node IoC - Console - Services - Command Registrar
//--------------------------------------------------------

import __ from '@absolunet/private-registry';


/**
 * Class that registers the command and bootstraps all the needed instances in order to make the CLI engine works.
 *
 * @memberof console.services
 * @hideconstructor
 */
class CommandRegistrar {

	/**
	 * Class dependencies: <code>['app', 'command', 'command.runner', 'file', 'terminal', 'yargs']</code>.
	 *
	 * @type {Array<string>}
	 */
	static get dependencies() {
		return ['app', 'command', 'command.runner', 'file', 'terminal', 'yargs'];
	}

	/**
	 * @inheritdoc
	 * @private
	 */
	init() {
		__(this).set('handlers', __(this).get('handlers') || []);
	}

	/**
	 * Add a command in the repository.
	 *
	 * @param {Function} command - A command to add.
	 */
	add(command) {
		this.commandRepository.add(command);
	}

	/**
	 * Add all commands in the given folder.
	 *
	 * @param {string} folder - A folder from which load commands.
	 */
	addFromFolder(folder) {
		this.file.scandir(folder, 'file', { recursive: true, pattern: '*.js', fullPath: true }).forEach((file) => {
			this.add(this.file.load(file));
		});
	}

	/**
	 * Set default command to be executed of nothing is provided.
	 *
	 * @param {console.Command|Function} Command - The default command.
	 */
	setDefault(Command) {
		const command = this.app.make(Command.constructor === Function ? Command : Command.constructor);

		const properties = {
			name: '*',
			description: `Default command [${command.name}]`
		};

		Object.entries(properties).forEach(([key, value]) => {
			Object.defineProperty(command, key, {
				value,
				enumerable:   false,
				writable:     false,
				configurable: false
			});
		});

		this.add(command);
	}

	/**
	 * Retrieve default command from the command repository.
	 *
	 * @returns {console.Command|null} The default command instance.
	 */
	getDefault() {
		return this.commandRepository.get('*');
	}

	/**
	 * Resolve given console command by getting the related Command
	 * instance and make it handle the request.
	 *
	 * @param {string|Array<string>} command - The command to resolve.
	 * @param {boolean} [internal] - Specify if the command should be processed as an internal process. If if should check the policies restrictions, set to false.
	 * @returns {Promise} The async process promise.
	 */
	async resolve(command, internal = false) {
		const yargs = this.yargs(command);

		this.bootstrap(yargs, !internal);

		yargs.parse();

		await this.handlers.shift();
	}

	/**
	 * Bootstrap the commands in Yargs.
	 *
	 * @param {yargs} yargs - The Yargs instance.
	 * @param {boolean} [withPolicies] - Indicates that the policies should be checked before bootstrapping them.
	 */
	bootstrap(yargs, withPolicies = true) {
		const { app } = this;

		const translate = (key, defaultValue) => {
			if (!app.isBound('translator')) {
				return defaultValue;
			}

			return app.make('translator').translate(key);
		};

		const translations = Object.create({}, Object.fromEntries(Object.entries({
			options: 'Options:',
			positionals: 'Positionals:'
		})
			.map(([key, original]) => {
				return [original, {
					configurable: true,
					enumerable:   true,
					get() {
						return translate(`commands.yargs.${key}`, original);
					}
				}];
			})));

		yargs
			.option('v', {
				alias: 'verbose',
				describe: translate('commands.list.flags.verbose', 'Adjust the verbosity of the command.'),
				type: 'boolean'
			})
			.version('version', translate('commands.list.flags.version', 'Show version number.'), app.version)
			.help('help', translate('commands.list.flags.help', 'Show help.'))
			.updateStrings(translations)
			.count('v')
			.exitProcess(false)
			.showHelpOnFail(false)
			.locale('en')
			.strict()
			.wrap(Math.min(120, yargs.terminalWidth()));

		this.commandRepository.all(withPolicies).forEach((command) => {
			yargs.command(Object.assign({
				handler: (argv) => {
					const handler = this.commandRunner.run(command, argv, yargs);

					this.handlers.push(handler);

					return handler;
				}
			}, command.yargsModel));
		});
	}

	/**
	 * Currently running command handlers.
	 *
	 * @type {Array<Promise<*>>}
	 */
	get handlers() {
		return __(this).get('handlers');
	}

	/**
	 * The command repository.
	 *
	 * @type {console.repositories.CommandRepository}
	 */
	get commandRepository() {
		return __(this).get('command');
	}

}


export default CommandRegistrar;