UNPKG

@elsikora/commitizen-plugin-commitlint-ai

Version:
155 lines (151 loc) 6.92 kB
'use strict'; var node_child_process = require('node:child_process'); var node_util = require('node:util'); var chalk = require('chalk'); /** * Implementation of the command service using Node.js child_process. * Provides functionality to execute shell commands. */ class NodeCommandService { /** CLI interface service for user interaction */ CLI_INTERFACE_SERVICE; /** * Promisified version of the exec function from child_process. * Allows for async/await usage of command execution. */ EXEC_ASYNC = node_util.promisify(node_child_process.exec); /** * @param {ICliInterfaceService} cliInterfaceService - The CLI interface service for user interactions */ constructor(cliInterfaceService) { this.CLI_INTERFACE_SERVICE = cliInterfaceService; } /** * Executes a shell command. * @param {string} command - The shell command to execute * @returns {Promise<void>} Promise that resolves when the command completes successfully * @throws Will throw an error if the command execution fails, except for npm install which offers retry options */ async execute(command) { try { await this.EXEC_ASYNC(command); } catch (error) { // Check if the failed command is npm if (command.trim().startsWith("npm install") || command.trim().startsWith("npm ci") || command.trim().startsWith("npm update") || command.trim().startsWith("npm uninstall")) { this.formatAndParseNpmError(command, error); await this.handleNpmInstallFailure(command); } else { // For non-npm commands, throw the error as before throw error; } } } /** * Execute a command and return its output * @param {string} command - The command to execute * @returns {Promise<string>} Promise that resolves to the command output */ async executeWithOutput(command) { try { const { stdout } = await this.EXEC_ASYNC(command); return stdout.trim(); } catch (error) { this.CLI_INTERFACE_SERVICE.handleError(`Failed to execute command: ${command}`, error); throw error; } } /** * Format and parse npm error to readable format * @param {string} command - The original npm command that failed * @param {INodeError} error - Error npm object */ formatAndParseNpmError(command, error) { // Форматируем и выводим ошибку console.error(chalk.red.bold("🚨 NPM Command Failed")); console.error(chalk.gray(`Command: ${command}`)); console.error(chalk.red("Error Details:")); // Парсим stderr для структурированного вывода if (error.stderr) { const lines = error.stderr.split("\n").filter((line) => line.trim()); let errorCode = null; const conflictDetails = []; let resolutionAdvice = null; let logFile = null; for (const line of lines) { if (line.includes("npm error code")) { errorCode = line.replace("npm error code", "").trim(); } else if (line.includes("While resolving") || line.includes("Found") || line.includes("Could not resolve dependency") || line.includes("Conflicting peer dependency")) { conflictDetails.push(line.replace("npm error", "").trim()); } else if (line.includes("Fix the upstream dependency conflict") || line.includes("--force") || line.includes("--legacy-peer-deps")) { resolutionAdvice = line.replace("npm error", "").trim(); } else if (line.includes("A complete log of this run can be found in")) { logFile = line.replace("npm error", "").trim(); } } // Выводим структурированную ошибку if (errorCode) { console.error(chalk.red(` Code: ${errorCode}`)); } if (conflictDetails.length > 0) { console.error(chalk.yellow(" Dependency Conflict:")); for (const detail of conflictDetails) console.error(chalk.yellow(` - ${detail}`)); } if (resolutionAdvice) { console.error(chalk.cyan(" Resolution:")); console.error(chalk.cyan(` ${resolutionAdvice}`)); } if (logFile) { console.error(chalk.gray(` Log File: ${logFile}`)); } } else { // Если stderr пустой, выводим общую информацию об ошибке console.error(chalk.red("Unknown error occurred")); } } /** * Handles npm install command failures by offering retry options to the user. * @param {string} originalCommand - The original npm command that failed * @returns {Promise<void>} Promise that resolves when the chosen action completes * @throws Will throw an error if the user chooses to cancel or if retried command still fails */ async handleNpmInstallFailure(originalCommand) { this.CLI_INTERFACE_SERVICE.warn("npm command exection failed."); const options = [ { label: "Retry with --force", value: "force" }, { label: "Retry with --legacy-peer-deps", value: "legacy-peer-deps" }, { label: "Cancel command execution", value: "cancel" }, ]; const choice = await this.CLI_INTERFACE_SERVICE.select("How would you like to proceed?", options); switch (choice) { case "force": { this.CLI_INTERFACE_SERVICE.info("Retrying with --force flag..."); await this.EXEC_ASYNC(`${originalCommand} --force`); this.CLI_INTERFACE_SERVICE.success("Execution completed with --force flag."); break; } case "legacy-peer-deps": { this.CLI_INTERFACE_SERVICE.info("Retrying with --legacy-peer-deps flag..."); await this.EXEC_ASYNC(`${originalCommand} --legacy-peer-deps`); this.CLI_INTERFACE_SERVICE.success("Execution completed with --legacy-peer-deps flag."); break; } case "cancel": { this.CLI_INTERFACE_SERVICE.info("Execution cancelled by user."); throw new Error("npm command execution was cancelled by user."); } default: { throw new Error("Invalid option selected."); } } } } exports.NodeCommandService = NodeCommandService; //# sourceMappingURL=node-command.service.js.map