--- @meta --- Framework-specific tool configurations and installation utilities for Neovim. --- This module provides automatic installation of language servers and tools --- based on the current development framework environment. --- It reads the FRAMEWORK environment variable to determine which tools to install. --- @alias FrameworkName "python"|"vue"|string --- @class FrameworkTools --- @field python string[] Python development tools and LSP servers including formatters, linters, and type checkers --- @field vue string[] Vue.js development tools and LSP servers including JavaScript/TypeScript tools and CSS utilities --- Framework-specific tool configurations --- Maps framework names to their respective tool lists --- @type FrameworkTools local FRAMEWORK_TOOLS = { python = { "black", "python-lsp-server", "mypy", "pylint", "flake8", "pylama", "bandit", "pydocstyle", "pyproject-flake8", "pyproject-fmt", "reorder-python-imports", }, vue = { "eslint-lsp", "stylelint-lsp", "tailwindcss-language-server", "typescript-language-server", "stylelint", "prettier", }, } --- Retrieves the appropriate tool set based on the current framework environment. --- This function checks the FRAMEWORK environment variable and returns the corresponding --- tool list from FRAMEWORK_TOOLS. If no framework is set or the framework is not recognized, --- returns an empty table. --- @return string[] tools List of tools to install for the detected framework, or empty table if no framework is set local function get_framework_tools() local current_framework = os.getenv("FRAMEWORK") if current_framework then local tools = FRAMEWORK_TOOLS[current_framework] return tools or {} else return {} end end --- Validates if a package name is safe to install. --- This function performs basic security validation to prevent command injection --- and ensures package names follow expected patterns. --- @param package_name string The package name to validate --- @return boolean is_valid True if the package name is valid, false otherwise local function is_valid_package_name(package_name) -- Basic validation: package names should only contain alphanumeric characters, hyphens, and dots -- This prevents command injection attacks if type(package_name) ~= "string" or package_name == "" then return false end -- Additional security: check for potentially dangerous patterns local dangerous_patterns = { "%.%.%/", -- Path traversal "%;", -- Command separator "%|", -- Pipe "%&", -- Background process "%`", -- Command substitution "%$", -- Variable expansion "%!" -- History expansion } for _, pattern in ipairs(dangerous_patterns) do if string.match(package_name, pattern) then return false end end -- Check for valid characters: alphanumeric, hyphen, dot, underscore local valid_pattern = "^[%w%-%.%_]+$" return string.match(package_name, valid_pattern) ~= nil end --- Checks if Mason is available and ready for package installation. --- @return boolean is_available True if Mason is available, false otherwise local function is_mason_available() local mason_ok, _ = pcall(require, "mason") return mason_ok end --- Installs Mason packages based on the current framework environment. --- This function retrieves the appropriate tool set and installs them one by one --- to handle individual failures gracefully. Each package installation is wrapped --- in a protected call to prevent one failure from stopping the entire process. --- @return nil function InstallMyMasonPackages() -- Check if Mason is available first if not is_mason_available() then vim.notify("Mason is not available. Please install Mason first.", vim.log.levels.ERROR) return end local packages = get_framework_tools() if #packages > 0 then local installed_count = 0 local failed_count = 0 -- Install packages one by one to handle individual failures for _, package in ipairs(packages) do -- Validate package name for security if not is_valid_package_name(package) then vim.notify("Skipping invalid package name: " .. package, vim.log.levels.WARN) failed_count = failed_count + 1 else local success, result = pcall(function() vim.cmd("MasonInstall " .. package) end) if success then installed_count = installed_count + 1 vim.notify("Successfully installed: " .. package, vim.log.levels.INFO) else failed_count = failed_count + 1 vim.notify("Failed to install " .. package .. ": " .. tostring(result), vim.log.levels.WARN) end end end -- Provide summary if failed_count == 0 then vim.notify("Framework-specific packages installation completed successfully. Installed: " .. installed_count .. " packages", vim.log.levels.INFO) else vim.notify("Framework-specific packages installation completed with " .. failed_count .. " failures. Successfully installed: " .. installed_count .. " packages", vim.log.levels.WARN) end else vim.notify("No framework detected or no packages to install", vim.log.levels.INFO) end end -- Create user command for Mason installation vim.api.nvim_create_user_command( "MasonInstallR", -- Command name InstallMyMasonPackages, -- Function to call { nargs = 0 } -- Command takes no arguments ) -- Module exports return { FRAMEWORK_TOOLS = FRAMEWORK_TOOLS, get_framework_tools = get_framework_tools, InstallMyMasonPackages = InstallMyMasonPackages, }