UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

256 lines • 12.9 kB
"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