@elsikora/commitizen-plugin-commitlint-ai
Version:
AI-powered Commitizen adapter with Commitlint integration
155 lines (151 loc) • 6.92 kB
JavaScript
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
;