nativescript
Version:
Command-line interface for building NativeScript projects
256 lines • 12.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandsService = void 0;
const jaroWinklerDistance = require("../vendor/jaro-winkler_distance");
const helpers = require("../helpers");
const os_1 = require("os");
const _ = require("lodash");
const yok_1 = require("../yok");
class CommandArgumentsValidationHelper {
constructor(isValid, _remainingArguments) {
this.isValid = isValid;
this.remainingArguments = _remainingArguments.slice();
}
}
class CommandsService {
get currentCommandData() {
return _.last(this.commands);
}
constructor($errors, $hooksService, $injector, $logger, $options, $staticConfig, $extensibilityService, $optionsTracker) {
this.$errors = $errors;
this.$hooksService = $hooksService;
this.$injector = $injector;
this.$logger = $logger;
this.$options = $options;
this.$staticConfig = $staticConfig;
this.$extensibilityService = $extensibilityService;
this.$optionsTracker = $optionsTracker;
this.commands = [];
}
allCommands(opts) {
const commands = this.$injector.getRegisteredCommandsNames(opts.includeDevCommands);
return _.reject(commands, (command) => _.includes(command, "|"));
}
async executeCommandUnchecked(commandName, commandArguments) {
this.commands.push({ commandName, commandArguments });
const command = this.$injector.resolveCommand(commandName);
if (command) {
if (!this.$staticConfig.disableAnalytics &&
!command.disableAnalytics &&
!this.$options.disableAnalytics) {
const analyticsService = this.$injector.resolve("analyticsService"); // This should be resolved here due to cyclic dependency
await analyticsService.checkConsent();
const beautifiedCommandName = this.beautifyCommandName(commandName).replace(/\|/g, " ");
const googleAnalyticsPageData = {
googleAnalyticsDataType: "pageview" /* GoogleAnalyticsDataType.Page */,
path: beautifiedCommandName,
title: beautifiedCommandName,
};
await analyticsService.trackInGoogleAnalytics(googleAnalyticsPageData);
await this.$optionsTracker.trackOptions(this.$options);
}
const shouldExecuteHooks = !this.$staticConfig.disableCommandHooks &&
(command.enableHooks === undefined || command.enableHooks === true);
if (shouldExecuteHooks) {
// Handle correctly hierarchical commands
const hierarchicalCommandName = this.$injector.buildHierarchicalCommand(commandName, commandArguments);
if (hierarchicalCommandName) {
commandName = helpers.stringReplaceAll(hierarchicalCommandName.commandName, "|*" /* CommandsDelimiters.DefaultHierarchicalCommand */, "-" /* CommandsDelimiters.HooksCommand */);
commandName = helpers.stringReplaceAll(commandName, "|" /* CommandsDelimiters.HierarchicalCommand */, "-" /* CommandsDelimiters.HooksCommand */);
}
await this.$hooksService.executeBeforeHooks(commandName);
}
await command.execute(commandArguments);
if (command.postCommandAction) {
await command.postCommandAction(commandArguments);
}
if (shouldExecuteHooks) {
await this.$hooksService.executeAfterHooks(commandName);
}
this.commands.pop();
return true;
}
this.commands.pop();
return false;
}
printHelpSuggestion(commandName) {
const command = commandName
? helpers.stringReplaceAll(this.beautifyCommandName(commandName), "|", " ") + " "
: "";
const commandHelp = `ns ${command}--help`;
this.$logger.printMarkdown(`__Run \`${commandHelp}\` for more information.__`);
return;
}
async executeCommandAction(commandName, commandArguments, action) {
return this.$errors.beginCommand(() => action.apply(this, [commandName, commandArguments]), () => this.printHelpSuggestion(commandName));
}
async tryExecuteCommandAction(commandName, commandArguments) {
const command = this.$injector.resolveCommand(commandName);
if (!command || !command.isHierarchicalCommand) {
const dashedOptions = command ? command.dashedOptions : null;
this.$options.validateOptions(dashedOptions);
}
return this.canExecuteCommand(commandName, commandArguments);
}
async tryExecuteCommand(commandName, commandArguments) {
const canExecuteResult = await this.executeCommandAction(commandName, commandArguments, this.tryExecuteCommandAction);
const canExecute = typeof canExecuteResult === "object"
? canExecuteResult.canExecute
: canExecuteResult;
if (canExecute) {
await this.executeCommandAction(commandName, commandArguments, this.executeCommandUnchecked);
}
else {
// If canExecuteCommand returns false, the command cannot be executed or there's no such command at all.
const command = this.$injector.resolveCommand(commandName);
if (command) {
let commandWithArgs = commandName;
if (commandArguments && commandArguments.length) {
commandWithArgs += ` ${commandArguments.join(" ")}`;
}
this.$logger.error(`Command '${commandWithArgs}' cannot be executed.`);
await this.printHelpSuggestion(commandName);
}
}
}
async canExecuteCommand(commandName, commandArguments, isDynamicCommand) {
const command = this.$injector.resolveCommand(commandName);
const beautifiedName = helpers.stringReplaceAll(commandName, "|", " ");
if (command) {
// Verify command is enabled
if (command.isDisabled) {
this.$errors.fail("This command is not applicable to your environment.");
}
// If command wants to handle canExecute logic on its own.
if (command.canExecute) {
return await command.canExecute(commandArguments);
}
// First part of hierarchical commands should be validated in specific way.
if (await this.$injector.isValidHierarchicalCommand(commandName, commandArguments)) {
return true;
}
if (await this.validateCommandArguments(command, commandArguments)) {
return true;
}
this.$errors.fail(`Unable to execute command '${beautifiedName}'.`);
return false;
}
const commandInfo = {
inputStrings: [commandName, ...commandArguments],
commandDelimiter: "|" /* CommandsDelimiters.HierarchicalCommand */,
defaultCommandDelimiter: "|*" /* CommandsDelimiters.DefaultHierarchicalCommand */,
};
const extensionData = await this.$extensibilityService.getExtensionNameWhereCommandIsRegistered(commandInfo);
if (extensionData) {
this.$logger.warn(extensionData.installationMessage);
}
else {
this.$logger.error("Unknown command '%s'.", beautifiedName);
await this.printHelpSuggestion();
this.tryMatchCommand(commandName);
}
return false;
}
async validateMandatoryParams(commandArguments, mandatoryParams) {
const commandArgsHelper = new CommandArgumentsValidationHelper(true, commandArguments);
if (mandatoryParams.length > 0) {
// If command has more mandatory params than the passed ones, we shouldn't execute it
if (mandatoryParams.length > commandArguments.length) {
const customErrorMessages = _.map(mandatoryParams, (mp) => mp.errorMessage);
customErrorMessages.splice(0, 0, "You need to provide all the required parameters.");
this.$errors.failWithHelp(customErrorMessages.join(os_1.EOL));
}
// If we reach here, the commandArguments are at least as much as mandatoryParams. Now we should verify that we have each of them.
for (let mandatoryParamIndex = 0; mandatoryParamIndex < mandatoryParams.length; ++mandatoryParamIndex) {
const mandatoryParam = mandatoryParams[mandatoryParamIndex];
let argument = null;
for (let remainingArgsIndex = 0; remainingArgsIndex < commandArgsHelper.remainingArguments.length; ++remainingArgsIndex) {
const c = commandArgsHelper.remainingArguments[remainingArgsIndex];
if (await mandatoryParam.validate(c)) {
argument = c;
break;
}
}
if (argument) {
helpers.remove(commandArgsHelper.remainingArguments, (arg) => arg === argument);
}
else {
this.$errors.failWithHelp("Missing mandatory parameter.");
}
}
}
return commandArgsHelper;
}
async validateCommandArguments(command, commandArguments) {
const mandatoryParams = _.filter(command.allowedParameters, (param) => param.mandatory);
const commandArgsHelper = await this.validateMandatoryParams(commandArguments, mandatoryParams);
if (!commandArgsHelper.isValid) {
return false;
}
// Command doesn't have any allowedParameters
if (!command.allowedParameters || command.allowedParameters.length === 0) {
if (commandArguments.length > 0) {
this.$errors.failWithHelp("This command doesn't accept parameters.");
}
}
else {
// Exclude mandatory params, we've already checked them
const unverifiedAllowedParams = command.allowedParameters.filter((param) => !param.mandatory);
for (let remainingArgsIndex = 0; remainingArgsIndex < commandArgsHelper.remainingArguments.length; ++remainingArgsIndex) {
const argument = commandArgsHelper.remainingArguments[remainingArgsIndex];
let parameter = null;
for (let unverifiedIndex = 0; unverifiedIndex < unverifiedAllowedParams.length; ++unverifiedIndex) {
const c = unverifiedAllowedParams[unverifiedIndex];
if (await c.validate(argument)) {
parameter = c;
break;
}
}
if (parameter) {
const index = unverifiedAllowedParams.indexOf(parameter);
// Remove the matched parameter from unverifiedAllowedParams collection, so it will not be used to verify another argument.
unverifiedAllowedParams.splice(index, 1);
}
else {
this.$errors.failWithHelp(`The parameter ${argument} is not valid for this command.`);
}
}
}
return true;
}
tryMatchCommand(commandName) {
const allCommands = this.allCommands({ includeDevCommands: false });
let similarCommands = [];
_.each(allCommands, (command) => {
if (!this.$injector.isDefaultCommand(command)) {
command = helpers.stringReplaceAll(command, "|", " ");
const distance = jaroWinklerDistance(commandName, command);
if (commandName.length > 3 && command.indexOf(commandName) !== -1) {
similarCommands.push({ rating: 1, name: command });
}
else if (distance >= 0.65) {
similarCommands.push({ rating: distance, name: command });
}
}
});
similarCommands = _.sortBy(similarCommands, (command) => {
return -command.rating;
}).slice(0, 5);
if (similarCommands.length > 0) {
const message = ["Did you mean?"];
_.each(similarCommands, (command) => {
message.push("\t" + command.name);
});
this.$logger.fatal(message.join("\n"));
}
}
beautifyCommandName(commandName) {
if (commandName.indexOf("*") > 0) {
return commandName.substring(0, commandName.indexOf("|"));
}
return commandName;
}
}
exports.CommandsService = CommandsService;
yok_1.injector.register("commandsService", CommandsService);
//# sourceMappingURL=commands-service.js.map