UNPKG

@elsikora/setup-wizard

Version:

Setup Wizard - CLI scaffolding utility

211 lines (208 loc) 9.94 kB
#!/usr/bin/env node import { LINT_STAGED_FEATURE_CONFIG } from '../../domain/constant/lint-staged-feature-config.constant.js'; import { ELintStagedFeature } from '../../domain/enum/lint-staged-feature.enum.js'; import { EModule } from '../../domain/enum/module.enum.js'; import { EPackageJsonDependencyType } from '../../domain/enum/package-json-dependency-type.enum.js'; import { NodeCommandService } from '../../infrastructure/service/node-command.service.js'; import { LINT_STAGED_CONFIG_FILE_NAMES } from '../constant/lint-staged-config-file-names.constant.js'; import { LINT_STAGED_CONFIG_HUSKY_PRE_COMMIT_SCRIPT } from '../constant/lint-staged-config-husky-pre-commit-script.constant.js'; import { LINT_STAGED_CONFIG } from '../constant/lint-staged-config.constant.js'; import { LINT_STAGED_CORE_DEPENDENCIES } from '../constant/lint-staged-core-dependencies.constant.js'; import { PackageJsonService } from './package-json.service.js'; /** * Service for setting up and managing lint-staged configuration. * Provides functionality to run linters on git staged files, * ensuring only properly formatted code is committed. */ class LintStagedModuleService { /** CLI interface service for user interaction */ CLI_INTERFACE_SERVICE; /** Command service for executing shell commands */ COMMAND_SERVICE; /** Configuration service for managing app configuration */ CONFIG_SERVICE; /** File system service for file operations */ FILE_SYSTEM_SERVICE; /** Service for managing package.json */ PACKAGE_JSON_SERVICE; /** Cached lint-staged configuration */ config = null; /** Selected lint-staged features to configure */ selectedFeatures = []; /** * Initializes a new instance of the LintStagedModuleService. * @param cliInterfaceService - Service for CLI user interactions * @param fileSystemService - Service for file system operations * @param configService - Service for managing app configuration */ constructor(cliInterfaceService, fileSystemService, configService) { this.CLI_INTERFACE_SERVICE = cliInterfaceService; this.FILE_SYSTEM_SERVICE = fileSystemService; this.COMMAND_SERVICE = new NodeCommandService(cliInterfaceService); this.PACKAGE_JSON_SERVICE = new PackageJsonService(fileSystemService, this.COMMAND_SERVICE); this.CONFIG_SERVICE = configService; } /** * Handles existing lint-staged setup. * Checks for existing configuration files and asks if user wants to remove them. * @returns Promise resolving to true if setup should proceed, false otherwise */ async handleExistingSetup() { const existingFiles = await this.findExistingConfigFiles(); const packageJson = await this.PACKAGE_JSON_SERVICE.get(); if (packageJson["lint-staged"]) { existingFiles.push("package.json (lint-staged config)"); } if (existingFiles.length > 0) { const messageLines = ["Existing lint-staged configuration files detected:"]; messageLines.push(""); for (const file of existingFiles) { messageLines.push(`- ${file}`); } messageLines.push("", "Do you want to delete them?"); const shouldDelete = await this.CLI_INTERFACE_SERVICE.confirm(messageLines.join("\n"), true); if (shouldDelete) { await Promise.all(existingFiles.filter((file) => file !== "package.json (lint-staged config)").map((file) => this.FILE_SYSTEM_SERVICE.deleteFile(file))); if (packageJson["lint-staged"]) { delete packageJson["lint-staged"]; await this.PACKAGE_JSON_SERVICE.set(packageJson); } } else { this.CLI_INTERFACE_SERVICE.warn("Existing lint-staged configuration files detected. Setup aborted."); return false; } } return true; } /** * Installs and configures lint-staged. * Guides the user through selecting linting tools and setting up git hooks. * @returns Promise resolving to the module setup result */ async install() { try { this.config = await this.CONFIG_SERVICE.getModuleConfig(EModule.LINT_STAGED); if (!(await this.shouldInstall())) { return { wasInstalled: false }; } if (!(await this.handleExistingSetup())) { return { wasInstalled: false }; } const savedFeatures = this.config?.features ?? []; await this.setupLintStaged(savedFeatures); return { customProperties: { features: this.selectedFeatures, }, wasInstalled: true, }; } catch (error) { this.CLI_INTERFACE_SERVICE.handleError("Failed to complete lint-staged setup", error); throw error; } } /** * Determines if lint-staged should be installed. * Asks the user if they want to set up lint-staged with Husky pre-commit hooks. * Uses the saved config value as default if it exists. * @returns Promise resolving to true if the module should be installed, false otherwise */ async shouldInstall() { try { return await this.CLI_INTERFACE_SERVICE.confirm("Do you want to set up lint-staged with Husky pre-commit hooks?", await this.CONFIG_SERVICE.isModuleEnabled(EModule.LINT_STAGED)); } catch (error) { this.CLI_INTERFACE_SERVICE.handleError("Failed to get user confirmation", error); return false; } } /** * Creates the lint-staged configuration file. * @param selectedFeatures - Array of selected lint-staged features */ async createConfigs(selectedFeatures) { const config = LINT_STAGED_CONFIG.template(selectedFeatures); await this.FILE_SYSTEM_SERVICE.writeFile("lint-staged.config.js", config, "utf8"); } /** * Displays a summary of the lint-staged setup results. * Lists selected linting tools and required packages. * @param selectedFeatures - Array of selected lint-staged features */ displaySetupSummary(selectedFeatures) { const requiredPackages = selectedFeatures.flatMap((feature) => LINT_STAGED_FEATURE_CONFIG[feature].requiredPackages); const summary = [ "lint-staged configuration has been created.", "", "Configuration files:", "- lint-staged.config.js", "- .husky/pre-commit", "", "Selected linting tools:", ...selectedFeatures.map((feature) => `- ${LINT_STAGED_FEATURE_CONFIG[feature].label}`), "", "Required packages (please ensure these are installed):", ...requiredPackages.map((packageName) => `- ${packageName}`), "", "Husky git hooks have been set up to run lint-staged before commits.", ]; this.CLI_INTERFACE_SERVICE.note("lint-staged Setup", summary.join("\n")); } /** * Finds existing lint-staged configuration files. * @returns Promise resolving to an array of file paths for existing configuration files */ async findExistingConfigFiles() { const existingFiles = []; for (const file of LINT_STAGED_CONFIG_FILE_NAMES) { if (await this.FILE_SYSTEM_SERVICE.isPathExists(file)) { existingFiles.push(file); } } if (await this.FILE_SYSTEM_SERVICE.isPathExists(".husky/pre-commit")) { existingFiles.push(".husky/pre-commit"); } return existingFiles; } /** * Sets up Husky git hooks. * Initializes Husky, adds prepare script, and creates pre-commit hook. */ async setupHusky() { await this.COMMAND_SERVICE.execute("npx husky"); await this.PACKAGE_JSON_SERVICE.addScript("prepare", "husky"); await this.COMMAND_SERVICE.execute("mkdir -p .husky"); await this.FILE_SYSTEM_SERVICE.writeFile(".husky/pre-commit", LINT_STAGED_CONFIG_HUSKY_PRE_COMMIT_SCRIPT, "utf8"); await this.COMMAND_SERVICE.execute("chmod +x .husky/pre-commit"); } /** * Sets up lint-staged configuration. * Guides the user through selecting linting tools and creates necessary config files. * @param savedFeatures - Previously saved lint-staged features */ async setupLintStaged(savedFeatures = []) { try { const options = Object.entries(LINT_STAGED_FEATURE_CONFIG).map(([value, config]) => ({ label: config.label, value, })); const hasValidSavedFeatures = savedFeatures.length > 0 && savedFeatures.every((feature) => Object.values(ELintStagedFeature).includes(feature)); const initialValues = hasValidSavedFeatures ? savedFeatures : []; this.selectedFeatures = await this.CLI_INTERFACE_SERVICE.multiselect("Select which linting tools to include:", options, true, initialValues); this.CLI_INTERFACE_SERVICE.startSpinner("Setting up lint-staged configuration..."); await this.PACKAGE_JSON_SERVICE.installPackages(LINT_STAGED_CORE_DEPENDENCIES, "latest", EPackageJsonDependencyType.DEV); await this.createConfigs(this.selectedFeatures); await this.setupHusky(); this.CLI_INTERFACE_SERVICE.stopSpinner("lint-staged configuration completed successfully!"); this.displaySetupSummary(this.selectedFeatures); } catch (error) { this.CLI_INTERFACE_SERVICE.stopSpinner("Failed to setup lint-staged configuration"); throw error; } } } export { LintStagedModuleService }; //# sourceMappingURL=lint-staged-module.service.js.map