From e0f367725fc36de324eecdd562db34ee99884710 Mon Sep 17 00:00:00 2001 From: Danish Bhatti Date: Sun, 2 Nov 2025 17:33:29 +0100 Subject: [PATCH 1/2] fix: removed whitespace lines --- lua/deployment/commands.lua | 504 +++++++-------- lua/deployment/config.lua | 31 +- lua/deployment/init.lua | 1211 +++++++++++++++++++---------------- lua/deployment/parser.lua | 377 +++++------ lua/deployment/rsync.lua | 185 +++--- 5 files changed, 1219 insertions(+), 1089 deletions(-) diff --git a/lua/deployment/commands.lua b/lua/deployment/commands.lua index e306f9a..fbdd9c4 100644 --- a/lua/deployment/commands.lua +++ b/lua/deployment/commands.lua @@ -5,277 +5,281 @@ local deployment = require("deployment") -- Get server completion local function get_server_completion() - local project_root = deployment.get_project_root() - local config, _ = deployment.get_current_config(project_root) - - if not config or #config.servers == 0 then - return {} - end - - local server_names = {} - for _, server in ipairs(config.servers) do - table.insert(server_names, server.name) - end - - return server_names + local project_root = deployment.get_project_root() + local config, _ = deployment.get_current_config(project_root) + + if not config or #config.servers == 0 then + return {} + end + + local server_names = {} + for _, server in ipairs(config.servers) do + table.insert(server_names, server.name) + end + + return server_names end -- Get configuration completion local function get_config_completion() - local project_root = deployment.get_project_root() - local parser = require("deployment.parser") - local multi_config, _ = parser.parse_multi_config_deployment( - project_root .. "/" .. require("deployment.config").get().deployment_file - ) - - if not multi_config or not multi_config.configurations then - return {} - end - - local config_names = {} - for config_name, _ in pairs(multi_config.configurations) do - table.insert(config_names, config_name) - end - - return config_names + local project_root = deployment.get_project_root() + local parser = require("deployment.parser") + local multi_config, _ = + parser.parse_multi_config_deployment(project_root .. "/" .. require("deployment.config").get().deployment_file) + + if not multi_config or not multi_config.configurations then + return {} + end + + local config_names = {} + for config_name, _ in pairs(multi_config.configurations) do + table.insert(config_names, config_name) + end + + return config_names end -- Get file completion local function get_file_completion() - local project_root = deployment.get_project_root() - - -- Get all files in project (excluding common build/temp directories) - local files = {} - local handle = io.popen("find '" .. project_root .. "' -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/build/*' -not -path '*/target/*' 2>/dev/null") - - if handle then - for file in handle:lines() do - -- Make path relative to project root - local relative = file:sub(#project_root + 2) - if relative and relative ~= "" then - table.insert(files, relative) - end + local project_root = deployment.get_project_root() + + -- Get all files in project (excluding common build/temp directories) + local files = {} + local handle = io.popen( + "find '" + .. project_root + .. "' -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/build/*' -not -path '*/target/*' 2>/dev/null" + ) + + if handle then + for file in handle:lines() do + -- Make path relative to project root + local relative = file:sub(#project_root + 2) + if relative and relative ~= "" then + table.insert(files, relative) + end + end + handle:close() end - handle:close() - end - - return files + + return files end -- Setup user commands function M.setup_commands() - vim.api.nvim_create_user_command("DeployAll", function() - deployment.deploy_all(true) - end, { desc = "Deploy to all configured servers" }) - - vim.api.nvim_create_user_command("Deploy", function(opts) - if opts.args == "" then - deployment.deploy_all(true) - else - deployment.deploy_to_specific_server(opts.args) - end - end, { - desc = "Deploy to specific server or all servers", - nargs = "?", - complete = get_server_completion - }) - - vim.api.nvim_create_user_command("DeployList", function() - deployment.list_servers() - end, { desc = "List configured deployment servers" }) - - vim.api.nvim_create_user_command("DeployInit", function(opts) - local use_multi_config = opts.args == "multi" or opts.args == "m" - deployment.create_example_deployment_file(use_multi_config) - end, { - desc = "Create example .deployment file (add 'multi' for multi-config)", - nargs = "?" - }) - - vim.api.nvim_create_user_command("DeployDebug", function() - local config = require("deployment.config") - config.set("debug", not config.get().debug) - vim.notify("Deployment debug mode: " .. (config.get().debug and "ON" or "OFF"), vim.log.levels.INFO) - end, { desc = "Toggle deployment debug mode" }) - - vim.api.nvim_create_user_command("DeployFile", function(opts) - if opts.args == "" then - deployment.deploy_current_file_to_all(true) - else - deployment.deploy_current_file_to_server(opts.args) - end - end, { - desc = "Deploy current file to specific server or all servers", - nargs = "?", - complete = get_server_completion - }) - - vim.api.nvim_create_user_command("DeployFileAll", function() - deployment.deploy_current_file_to_all(true) - end, { desc = "Deploy current file to all servers" }) - - vim.api.nvim_create_user_command("DeployFilePath", function(opts) - local args = vim.split(opts.args, "%s+") - if #args == 0 or args[1] == "" then - vim.notify("Please specify a file path", vim.log.levels.WARN) - return - end - - local file_path = args[1] - local server_name = args[2] - - if server_name then - deployment.deploy_file_by_path_to_server(file_path, server_name) - else - deployment.deploy_file_by_path_to_all(file_path, true) - end - end, { - desc = "Deploy file by path to all servers or specific server", - nargs = "+", - complete = function(arg_lead, cmd_line, cursor_pos) - local args = vim.split(cmd_line, "%s+") - local arg_count = #args - - -- First argument: file path - if arg_count <= 2 then - local files = get_file_completion() - local matches = {} - for _, file in ipairs(files) do - if file:find(arg_lead, 1, true) == 1 then - table.insert(matches, file) - end + vim.api.nvim_create_user_command("DeployAll", function() + deployment.deploy_all(true) + end, { desc = "Deploy to all configured servers" }) + + vim.api.nvim_create_user_command("Deploy", function(opts) + if opts.args == "" then + deployment.deploy_all(true) + else + deployment.deploy_to_specific_server(opts.args) end - return matches - -- Second argument: server name - elseif arg_count == 3 then - local servers = get_server_completion() - local matches = {} - for _, server in ipairs(servers) do - if server:find(arg_lead, 1, true) == 1 then - table.insert(matches, server) - end + end, { + desc = "Deploy to specific server or all servers", + nargs = "?", + complete = get_server_completion, + }) + + vim.api.nvim_create_user_command("DeployList", function() + deployment.list_servers() + end, { desc = "List configured deployment servers" }) + + vim.api.nvim_create_user_command("DeployInit", function(opts) + local use_multi_config = opts.args == "multi" or opts.args == "m" + deployment.create_example_deployment_file(use_multi_config) + end, { + desc = "Create example .deployment file (add 'multi' for multi-config)", + nargs = "?", + }) + + vim.api.nvim_create_user_command("DeployDebug", function() + local config = require("deployment.config") + config.set("debug", not config.get().debug) + vim.notify("Deployment debug mode: " .. (config.get().debug and "ON" or "OFF"), vim.log.levels.INFO) + end, { desc = "Toggle deployment debug mode" }) + + vim.api.nvim_create_user_command("DeployFile", function(opts) + if opts.args == "" then + deployment.deploy_current_file_to_all(true) + else + deployment.deploy_current_file_to_server(opts.args) end - return matches - end - - return {} - end - }) + end, { + desc = "Deploy current file to specific server or all servers", + nargs = "?", + complete = get_server_completion, + }) - vim.api.nvim_create_user_command("DeployConfigs", function() - deployment.list_configurations() - end, { desc = "List all deployment configurations" }) + vim.api.nvim_create_user_command("DeployFileAll", function() + deployment.deploy_current_file_to_all(true) + end, { desc = "Deploy current file to all servers" }) - vim.api.nvim_create_user_command("DeploySetActive", function(opts) - if opts.args == "" then - vim.notify("Please specify a configuration name", vim.log.levels.WARN) - return - end - deployment.set_active_configuration(opts.args) - end, { - desc = "Set active deployment configuration", - nargs = 1, - complete = get_config_completion - }) - - vim.api.nvim_create_user_command("DeployDebugParse", function() - deployment.debug_parse() - end, { desc = "Debug deployment file parsing" }) + vim.api.nvim_create_user_command("DeployFilePath", function(opts) + local args = vim.split(opts.args, "%s+") + if #args == 0 or args[1] == "" then + vim.notify("Please specify a file path", vim.log.levels.WARN) + return + end + + local file_path = args[1] + local server_name = args[2] + + if server_name then + deployment.deploy_file_by_path_to_server(file_path, server_name) + else + deployment.deploy_file_by_path_to_all(file_path, true) + end + end, { + desc = "Deploy file by path to all servers or specific server", + nargs = "+", + complete = function(arg_lead, cmd_line, cursor_pos) + local args = vim.split(cmd_line, "%s+") + local arg_count = #args + + -- First argument: file path + if arg_count <= 2 then + local files = get_file_completion() + local matches = {} + for _, file in ipairs(files) do + if file:find(arg_lead, 1, true) == 1 then + table.insert(matches, file) + end + end + return matches + -- Second argument: server name + elseif arg_count == 3 then + local servers = get_server_completion() + local matches = {} + for _, server in ipairs(servers) do + if server:find(arg_lead, 1, true) == 1 then + table.insert(matches, server) + end + end + return matches + end + + return {} + end, + }) + + vim.api.nvim_create_user_command("DeployConfigs", function() + deployment.list_configurations() + end, { desc = "List all deployment configurations" }) + + vim.api.nvim_create_user_command("DeploySetActive", function(opts) + if opts.args == "" then + vim.notify("Please specify a configuration name", vim.log.levels.WARN) + return + end + deployment.set_active_configuration(opts.args) + end, { + desc = "Set active deployment configuration", + nargs = 1, + complete = get_config_completion, + }) + + vim.api.nvim_create_user_command("DeployDebugParse", function() + deployment.debug_parse() + end, { desc = "Debug deployment file parsing" }) end -- Setup keymaps (optional, only if which-key is available) function M.setup_keymaps() - -- Check if which-key is available - local ok, wk = pcall(require, "which-key") - if ok then - wk.add({ - { "t", group = "deployment", icon = "󰒋" }, - { "ta", desc = "Deploy to all servers", icon = "󰒋" }, - { "ts", desc = "Deploy to specific server", icon = "󰒓" }, - { "tl", desc = "List deployment servers", icon = "󰒉" }, - { "ti", desc = "Create .deployment file", icon = "󰒅" }, - { "tf", desc = "Deploy current file to all servers", icon = "󰈙" }, - { "tF", desc = "Deploy current file to specific server", icon = "󰈚" }, - { "tc", desc = "List configurations", icon = "󰒉" }, - { "tC", desc = "Set active configuration", icon = "󰒓" }, - }) - end - - -- Setup actual keymaps - vim.keymap.set("n", "ta", function() - deployment.deploy_all(true) - end, { desc = "Deploy to all servers" }) - - vim.keymap.set("n", "ts", function() - local servers = get_server_completion() - if #servers == 0 then - vim.notify("No servers configured", vim.log.levels.WARN) - return - end - - vim.ui.select(servers, { - prompt = "Select server to deploy to:", - }, function(choice) - if choice then - deployment.deploy_to_specific_server(choice) - end - end) - end, { desc = "Deploy to specific server" }) - - vim.keymap.set("n", "tl", function() - deployment.list_servers() - end, { desc = "List deployment servers" }) - - vim.keymap.set("n", "ti", function() - vim.ui.select({ "Single configuration", "Multi-configuration" }, { - prompt = "Select deployment file type:", - }, function(choice) - if choice then - local use_multi_config = choice == "Multi-configuration" - deployment.create_example_deployment_file(use_multi_config) - end - end) - end, { desc = "Create .deployment file" }) - - vim.keymap.set("n", "tf", function() - deployment.deploy_current_file_to_all(true) - end, { desc = "Deploy current file to all servers" }) - - vim.keymap.set("n", "tF", function() - local servers = get_server_completion() - if #servers == 0 then - vim.notify("No servers configured", vim.log.levels.WARN) - return - end - - vim.ui.select(servers, { - prompt = "Select server to deploy current file to:", - }, function(choice) - if choice then - deployment.deploy_current_file_to_server(choice) - end - end) - end, { desc = "Deploy current file to specific server" }) - - vim.keymap.set("n", "tc", function() - deployment.list_configurations() - end, { desc = "List configurations" }) - - vim.keymap.set("n", "tC", function() - local configs = get_config_completion() - if #configs == 0 then - vim.notify("No configurations found", vim.log.levels.WARN) - return + -- Check if which-key is available + local ok, wk = pcall(require, "which-key") + if ok then + wk.add({ + { "t", group = "deployment", icon = "󰒋" }, + { "ta", desc = "Deploy to all servers", icon = "󰒋" }, + { "ts", desc = "Deploy to specific server", icon = "󰒓" }, + { "tl", desc = "List deployment servers", icon = "󰒉" }, + { "ti", desc = "Create .deployment file", icon = "󰒅" }, + { "tf", desc = "Deploy current file to all servers", icon = "󰈙" }, + { "tF", desc = "Deploy current file to specific server", icon = "󰈚" }, + { "tc", desc = "List configurations", icon = "󰒉" }, + { "tC", desc = "Set active configuration", icon = "󰒓" }, + }) end - - vim.ui.select(configs, { - prompt = "Select active configuration:", - }, function(choice) - if choice then - deployment.set_active_configuration(choice) - end - end) - end, { desc = "Set active configuration" }) + + -- Setup actual keymaps + vim.keymap.set("n", "ta", function() + deployment.deploy_all(true) + end, { desc = "Deploy to all servers" }) + + vim.keymap.set("n", "ts", function() + local servers = get_server_completion() + if #servers == 0 then + vim.notify("No servers configured", vim.log.levels.WARN) + return + end + + vim.ui.select(servers, { + prompt = "Select server to deploy to:", + }, function(choice) + if choice then + deployment.deploy_to_specific_server(choice) + end + end) + end, { desc = "Deploy to specific server" }) + + vim.keymap.set("n", "tl", function() + deployment.list_servers() + end, { desc = "List deployment servers" }) + + vim.keymap.set("n", "ti", function() + vim.ui.select({ "Single configuration", "Multi-configuration" }, { + prompt = "Select deployment file type:", + }, function(choice) + if choice then + local use_multi_config = choice == "Multi-configuration" + deployment.create_example_deployment_file(use_multi_config) + end + end) + end, { desc = "Create .deployment file" }) + + vim.keymap.set("n", "tf", function() + deployment.deploy_current_file_to_all(true) + end, { desc = "Deploy current file to all servers" }) + + vim.keymap.set("n", "tF", function() + local servers = get_server_completion() + if #servers == 0 then + vim.notify("No servers configured", vim.log.levels.WARN) + return + end + + vim.ui.select(servers, { + prompt = "Select server to deploy current file to:", + }, function(choice) + if choice then + deployment.deploy_current_file_to_server(choice) + end + end) + end, { desc = "Deploy current file to specific server" }) + + vim.keymap.set("n", "tc", function() + deployment.list_configurations() + end, { desc = "List configurations" }) + + vim.keymap.set("n", "tC", function() + local configs = get_config_completion() + if #configs == 0 then + vim.notify("No configurations found", vim.log.levels.WARN) + return + end + + vim.ui.select(configs, { + prompt = "Select active configuration:", + }, function(choice) + if choice then + deployment.set_active_configuration(choice) + end + end) + end, { desc = "Set active configuration" }) end -return M \ No newline at end of file +return M + diff --git a/lua/deployment/config.lua b/lua/deployment/config.lua index fc3849a..67294cc 100644 --- a/lua/deployment/config.lua +++ b/lua/deployment/config.lua @@ -3,17 +3,17 @@ local M = {} -- Default configuration M.defaults = { - deployment_file = ".deployment", - rsync_options = { - "-avz", - "--exclude=.git/", - "--exclude=.DS_Store", - "--exclude=node_modules/", - }, - parallel_jobs = 5, - timeout = 30000, -- 30 seconds - debug = false, - delete_remote_files = false, + deployment_file = ".deployment", + rsync_options = { + "-avz", + "--exclude=.git/", + "--exclude=.DS_Store", + "--exclude=node_modules/", + }, + parallel_jobs = 5, + timeout = 30000, -- 30 seconds + debug = false, + delete_remote_files = false, } -- Current configuration (will be merged with user options) @@ -21,17 +21,18 @@ M.config = {} -- Setup function to merge user config with defaults function M.setup(user_config) - M.config = vim.tbl_deep_extend("force", M.defaults, user_config or {}) + M.config = vim.tbl_deep_extend("force", M.defaults, user_config or {}) end -- Get current config function M.get() - return M.config + return M.config end -- Update a specific config value function M.set(key, value) - M.config[key] = value + M.config[key] = value end -return M \ No newline at end of file +return M + diff --git a/lua/deployment/init.lua b/lua/deployment/init.lua index a5ab4ec..bc60c2f 100644 --- a/lua/deployment/init.lua +++ b/lua/deployment/init.lua @@ -5,467 +5,579 @@ local Path = require("plenary.path") -- Get project root directory function M.get_project_root() - local current_file = vim.api.nvim_buf_get_name(0) - local current_dir = vim.fn.fnamemodify(current_file, ":h") - - -- Look for .deployment file up the directory tree - local path = Path:new(current_dir) - while path.filename ~= "/" do - local deployment_file = Path:new(path.filename, require("deployment.config").get().deployment_file) - if deployment_file:exists() then - return path.filename + local current_file = vim.api.nvim_buf_get_name(0) + local current_dir = vim.fn.fnamemodify(current_file, ":h") + + -- Look for .deployment file up the directory tree + local path = Path:new(current_dir) + while path.filename ~= "/" do + local deployment_file = Path:new(path.filename, require("deployment.config").get().deployment_file) + if deployment_file:exists() then + return path.filename + end + path = path:parent() end - path = path:parent() - end - - -- Fallback to git root if available - local git_root = vim.fn.systemlist("git rev-parse --show-toplevel")[1] - if git_root and git_root ~= "" then - return git_root - end - - return vim.fn.getcwd() + + -- Fallback to git root if available + local git_root = vim.fn.systemlist("git rev-parse --show-toplevel")[1] + if git_root and git_root ~= "" then + return git_root + end + + return vim.fn.getcwd() end -- Get current deployment configuration (supports both single and multi-config) function M.get_current_config(project_root) - local parser = require("deployment.parser") - local deployment_file_path = project_root .. "/" .. require("deployment.config").get().deployment_file - - -- First try to parse as multi-config - local multi_config, multi_err = parser.parse_multi_config_deployment(deployment_file_path) - if multi_config and multi_config.configurations then - if not multi_config.active then - return nil, "No active configuration specified. Use :DeploySetActive " - end - - local active_config = multi_config.configurations[multi_config.active] - if not active_config then - return nil, "Active configuration '" .. multi_config.active .. "' not found" - end - - return active_config, nil, multi_config.active, multi_config.configurations - end - - -- Fallback to single config format - local single_config, single_err = parser.parse_deployment_config(deployment_file_path) - if single_config then - return single_config, nil, "default", { default = single_config } - end - - return nil, single_err or multi_err + local parser = require("deployment.parser") + local deployment_file_path = project_root .. "/" .. require("deployment.config").get().deployment_file + + -- First try to parse as multi-config + local multi_config, multi_err = parser.parse_multi_config_deployment(deployment_file_path) + if multi_config and multi_config.configurations then + if not multi_config.active then + return nil, "No active configuration specified. Use :DeploySetActive " + end + + local active_config = multi_config.configurations[multi_config.active] + if not active_config then + return nil, "Active configuration '" .. multi_config.active .. "' not found" + end + + return active_config, nil, multi_config.active, multi_config.configurations + end + + -- Fallback to single config format + local single_config, single_err = parser.parse_deployment_config(deployment_file_path) + if single_config then + return single_config, nil, "default", { default = single_config } + end + + return nil, single_err or multi_err end -- Deploy to all servers in parallel function M.deploy_all(show_progress) - local project_root = M.get_project_root() - local config, err = M.get_current_config(project_root) - - if not config then - vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) - return - end - - if #config.servers == 0 then - vim.notify("No servers configured in .deployment file", vim.log.levels.WARN) - return - end - - local results = {} - local completed = 0 - local total = #config.servers - - if show_progress then - vim.notify(string.format("Starting deployment to %d server(s)...", total), vim.log.levels.INFO) - end + local project_root = M.get_project_root() + local config, err = M.get_current_config(project_root) - local rsync = require("deployment.rsync") - for _, server in ipairs(config.servers) do - rsync.deploy_to_server(config, server, project_root, function(srv, success, error_msg, duration) - completed = completed + 1 - - table.insert(results, { - server = srv, - success = success, - error = error_msg, - duration = duration - }) - - if show_progress then - if success then - vim.notify(string.format("✓ %s (%dms) [%d/%d]", srv.name, duration, completed, total), vim.log.levels.INFO) - else - vim.notify(string.format("✗ %s failed: %s [%d/%d]", srv.name, error_msg or "Unknown error", completed, total), vim.log.levels.ERROR) - end - end - - -- All deployments completed - if completed == total then - M.show_deployment_summary(results, show_progress) - end - end) - end + if not config then + vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) + return + end + + if #config.servers == 0 then + vim.notify("No servers configured in .deployment file", vim.log.levels.WARN) + return + end + + local results = {} + local completed = 0 + local total = #config.servers + + if show_progress then + vim.notify(string.format("Starting deployment to %d server(s)...", total), vim.log.levels.INFO) + end + + local rsync = require("deployment.rsync") + for _, server in ipairs(config.servers) do + rsync.deploy_to_server(config, server, project_root, function(srv, success, error_msg, duration) + completed = completed + 1 + + table.insert(results, { + server = srv, + success = success, + error = error_msg, + duration = duration, + }) + + if show_progress then + if success then + vim.notify( + string.format("✓ %s (%dms) [%d/%d]", srv.name, duration, completed, total), + vim.log.levels.INFO + ) + else + vim.notify( + string.format( + "✗ %s failed: %s [%d/%d]", + srv.name, + error_msg or "Unknown error", + completed, + total + ), + vim.log.levels.ERROR + ) + end + end + + -- All deployments completed + if completed == total then + M.show_deployment_summary(results, show_progress) + end + end) + end end -- Deploy to specific server function M.deploy_to_specific_server(server_name) - local project_root = M.get_project_root() - local config, err = M.get_current_config(project_root) - - if not config then - vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) - return - end - - local server = nil - for _, srv in ipairs(config.servers) do - if srv.name == server_name then - server = srv - break + local project_root = M.get_project_root() + local config, err = M.get_current_config(project_root) + + if not config then + vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) + return end - end - - if not server then - vim.notify("Server '" .. server_name .. "' not found in .deployment file", vim.log.levels.ERROR) - return - end - - vim.notify("Deploying to " .. server_name .. "...", vim.log.levels.INFO) - - local rsync = require("deployment.rsync") - rsync.deploy_to_server(config, server, project_root, function(srv, success, error_msg, duration) - if success then - vim.notify(string.format("✓ %s deployed successfully (%dms)", srv.name, duration), vim.log.levels.INFO) - else - vim.notify(string.format("✗ %s deployment failed: %s", srv.name, error_msg or "Unknown error"), vim.log.levels.ERROR) + + local server = nil + for _, srv in ipairs(config.servers) do + if srv.name == server_name then + server = srv + break + end end - end) -end --- Deploy current file to all servers -function M.deploy_current_file_to_all(show_progress) - local current_file = vim.api.nvim_buf_get_name(0) - if current_file == "" then - vim.notify("No file is currently open", vim.log.levels.WARN) - return - end - - local project_root = M.get_project_root() - local config, err = M.get_current_config(project_root) - - if not config then - vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) - return - end - - if #config.servers == 0 then - vim.notify("No servers configured in .deployment file", vim.log.levels.WARN) - return - end - - -- Get relative path from project root to current file - local relative_path = vim.fn.fnamemodify(current_file, ":.") - local filename = vim.fn.fnamemodify(current_file, ":t") - - local results = {} - local completed = 0 - local total = #config.servers - - if show_progress then - vim.notify(string.format("Starting deployment of %s to %d server(s)...", filename, total), vim.log.levels.INFO) - end - - local rsync = require("deployment.rsync") - for _, server in ipairs(config.servers) do - rsync.deploy_single_file_to_server(config, server, project_root, relative_path, function(srv, success, error_msg, duration) - completed = completed + 1 - - table.insert(results, { - server = srv, - success = success, - error = error_msg, - duration = duration, - filename = filename - }) - - if show_progress then + if not server then + vim.notify("Server '" .. server_name .. "' not found in .deployment file", vim.log.levels.ERROR) + return + end + + vim.notify("Deploying to " .. server_name .. "...", vim.log.levels.INFO) + + local rsync = require("deployment.rsync") + rsync.deploy_to_server(config, server, project_root, function(srv, success, error_msg, duration) if success then - vim.notify(string.format("✓ %s deployed %s (%dms) [%d/%d]", srv.name, filename, duration, completed, total), vim.log.levels.INFO) + vim.notify(string.format("✓ %s deployed successfully (%dms)", srv.name, duration), vim.log.levels.INFO) else - vim.notify(string.format("✗ %s failed to deploy %s: %s [%d/%d]", srv.name, filename, error_msg or "Unknown error", completed, total), vim.log.levels.ERROR) + vim.notify( + string.format("✗ %s deployment failed: %s", srv.name, error_msg or "Unknown error"), + vim.log.levels.ERROR + ) end - end - - -- All deployments completed - if completed == total then - M.show_file_deployment_summary(results, filename, show_progress) - end end) - end +end + +-- Deploy current file to all servers +function M.deploy_current_file_to_all(show_progress) + local current_file = vim.api.nvim_buf_get_name(0) + if current_file == "" then + vim.notify("No file is currently open", vim.log.levels.WARN) + return + end + + local project_root = M.get_project_root() + local config, err = M.get_current_config(project_root) + + if not config then + vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) + return + end + + if #config.servers == 0 then + vim.notify("No servers configured in .deployment file", vim.log.levels.WARN) + return + end + + -- Get relative path from project root to current file + local relative_path = vim.fn.fnamemodify(current_file, ":.") + local filename = vim.fn.fnamemodify(current_file, ":t") + + local results = {} + local completed = 0 + local total = #config.servers + + if show_progress then + vim.notify(string.format("Starting deployment of %s to %d server(s)...", filename, total), vim.log.levels.INFO) + end + + local rsync = require("deployment.rsync") + for _, server in ipairs(config.servers) do + rsync.deploy_single_file_to_server( + config, + server, + project_root, + relative_path, + function(srv, success, error_msg, duration) + completed = completed + 1 + + table.insert(results, { + server = srv, + success = success, + error = error_msg, + duration = duration, + filename = filename, + }) + + if show_progress then + if success then + vim.notify( + string.format( + "✓ %s deployed %s (%dms) [%d/%d]", + srv.name, + filename, + duration, + completed, + total + ), + vim.log.levels.INFO + ) + else + vim.notify( + string.format( + "✗ %s failed to deploy %s: %s [%d/%d]", + srv.name, + filename, + error_msg or "Unknown error", + completed, + total + ), + vim.log.levels.ERROR + ) + end + end + + -- All deployments completed + if completed == total then + M.show_file_deployment_summary(results, filename, show_progress) + end + end + ) + end end -- Deploy current file to specific server function M.deploy_current_file_to_server(server_name) - local current_file = vim.api.nvim_buf_get_name(0) - if current_file == "" then - vim.notify("No file is currently open", vim.log.levels.WARN) - return - end - - local project_root = M.get_project_root() - local config, err = M.get_current_config(project_root) - - if not config then - vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) - return - end - - local server = nil - for _, srv in ipairs(config.servers) do - if srv.name == server_name then - server = srv - break + local current_file = vim.api.nvim_buf_get_name(0) + if current_file == "" then + vim.notify("No file is currently open", vim.log.levels.WARN) + return end - end - - if not server then - vim.notify("Server '" .. server_name .. "' not found in .deployment file", vim.log.levels.ERROR) - return - end - - -- Get relative path from project root to current file - local relative_path = vim.fn.fnamemodify(current_file, ":.") - local filename = vim.fn.fnamemodify(current_file, ":t") - - vim.notify("Deploying " .. filename .. " to " .. server_name .. "...", vim.log.levels.INFO) - - local rsync = require("deployment.rsync") - rsync.deploy_single_file_to_server(config, server, project_root, relative_path, function(srv, success, error_msg, duration) - if success then - vim.notify(string.format("✓ %s deployed %s successfully (%dms)", srv.name, filename, duration), vim.log.levels.INFO) - else - vim.notify(string.format("✗ %s deployment of %s failed: %s", srv.name, filename, error_msg or "Unknown error"), vim.log.levels.ERROR) + + local project_root = M.get_project_root() + local config, err = M.get_current_config(project_root) + + if not config then + vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) + return + end + + local server = nil + for _, srv in ipairs(config.servers) do + if srv.name == server_name then + server = srv + break + end end - end) + + if not server then + vim.notify("Server '" .. server_name .. "' not found in .deployment file", vim.log.levels.ERROR) + return + end + + -- Get relative path from project root to current file + local relative_path = vim.fn.fnamemodify(current_file, ":.") + local filename = vim.fn.fnamemodify(current_file, ":t") + + vim.notify("Deploying " .. filename .. " to " .. server_name .. "...", vim.log.levels.INFO) + + local rsync = require("deployment.rsync") + rsync.deploy_single_file_to_server( + config, + server, + project_root, + relative_path, + function(srv, success, error_msg, duration) + if success then + vim.notify( + string.format("✓ %s deployed %s successfully (%dms)", srv.name, filename, duration), + vim.log.levels.INFO + ) + else + vim.notify( + string.format( + "✗ %s deployment of %s failed: %s", + srv.name, + filename, + error_msg or "Unknown error" + ), + vim.log.levels.ERROR + ) + end + end + ) end -- Deploy file by path to all servers function M.deploy_file_by_path_to_all(file_path, show_progress) - local project_root = M.get_project_root() - - -- Resolve and validate file path - local full_path = Path:new(file_path):absolute() - if not Path:new(full_path):exists() then - vim.notify("File does not exist: " .. file_path, vim.log.levels.ERROR) - return - end - - -- Check if file is within project root - local project_path = Path:new(project_root):absolute() - if not full_path:find(project_path, 1, true) then - vim.notify("File must be within project root: " .. file_path, vim.log.levels.ERROR) - return - end - - -- Calculate relative path from project root - local relative_path = full_path:sub(#project_path + 2) -- +2 to skip trailing slash - local filename = vim.fn.fnamemodify(full_path, ":t") - - local config, err = M.get_current_config(project_root) - - if not config then - vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) - return - end - - if #config.servers == 0 then - vim.notify("No servers configured in .deployment file", vim.log.levels.WARN) - return - end - - local results = {} - local completed = 0 - local total = #config.servers - - if show_progress then - vim.notify(string.format("Starting deployment of %s to %d server(s)...", filename, total), vim.log.levels.INFO) - end - - local rsync = require("deployment.rsync") - for _, server in ipairs(config.servers) do - rsync.deploy_single_file_to_server(config, server, project_root, relative_path, function(srv, success, error_msg, duration) - completed = completed + 1 - - table.insert(results, { - server = srv, - success = success, - error = error_msg, - duration = duration, - filename = filename - }) - - if show_progress then - if success then - vim.notify(string.format("✓ %s deployed %s (%dms) [%d/%d]", srv.name, filename, duration, completed, total), vim.log.levels.INFO) - else - vim.notify(string.format("✗ %s failed to deploy %s: %s [%d/%d]", srv.name, filename, error_msg or "Unknown error", completed, total), vim.log.levels.ERROR) - end - end - - -- All deployments completed - if completed == total then - M.show_file_deployment_summary(results, filename, show_progress) - end - end) - end + local project_root = M.get_project_root() + + -- Resolve and validate file path + local full_path = Path:new(file_path):absolute() + if not Path:new(full_path):exists() then + vim.notify("File does not exist: " .. file_path, vim.log.levels.ERROR) + return + end + + -- Check if file is within project root + local project_path = Path:new(project_root):absolute() + if not full_path:find(project_path, 1, true) then + vim.notify("File must be within project root: " .. file_path, vim.log.levels.ERROR) + return + end + + -- Calculate relative path from project root + local relative_path = full_path:sub(#project_path + 2) -- +2 to skip trailing slash + local filename = vim.fn.fnamemodify(full_path, ":t") + + local config, err = M.get_current_config(project_root) + + if not config then + vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) + return + end + + if #config.servers == 0 then + vim.notify("No servers configured in .deployment file", vim.log.levels.WARN) + return + end + + local results = {} + local completed = 0 + local total = #config.servers + + if show_progress then + vim.notify(string.format("Starting deployment of %s to %d server(s)...", filename, total), vim.log.levels.INFO) + end + + local rsync = require("deployment.rsync") + for _, server in ipairs(config.servers) do + rsync.deploy_single_file_to_server( + config, + server, + project_root, + relative_path, + function(srv, success, error_msg, duration) + completed = completed + 1 + + table.insert(results, { + server = srv, + success = success, + error = error_msg, + duration = duration, + filename = filename, + }) + + if show_progress then + if success then + vim.notify( + string.format( + "✓ %s deployed %s (%dms) [%d/%d]", + srv.name, + filename, + duration, + completed, + total + ), + vim.log.levels.INFO + ) + else + vim.notify( + string.format( + "✗ %s failed to deploy %s: %s [%d/%d]", + srv.name, + filename, + error_msg or "Unknown error", + completed, + total + ), + vim.log.levels.ERROR + ) + end + end + + -- All deployments completed + if completed == total then + M.show_file_deployment_summary(results, filename, show_progress) + end + end + ) + end end -- Deploy file by path to specific server function M.deploy_file_by_path_to_server(file_path, server_name) - local project_root = M.get_project_root() - - -- Resolve and validate file path - local full_path = Path:new(file_path):absolute() - if not Path:new(full_path):exists() then - vim.notify("File does not exist: " .. file_path, vim.log.levels.ERROR) - return - end - - -- Check if file is within project root - local project_path = Path:new(project_root):absolute() - if not full_path:find(project_path, 1, true) then - vim.notify("File must be within project root: " .. file_path, vim.log.levels.ERROR) - return - end - - -- Calculate relative path from project root - local relative_path = full_path:sub(#project_path + 2) -- +2 to skip trailing slash - local filename = vim.fn.fnamemodify(full_path, ":t") - - local config, err = M.get_current_config(project_root) - - if not config then - vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) - return - end - - local server = nil - for _, srv in ipairs(config.servers) do - if srv.name == server_name then - server = srv - break + local project_root = M.get_project_root() + + -- Resolve and validate file path + local full_path = Path:new(file_path):absolute() + if not Path:new(full_path):exists() then + vim.notify("File does not exist: " .. file_path, vim.log.levels.ERROR) + return end - end - - if not server then - vim.notify("Server '" .. server_name .. "' not found in .deployment file", vim.log.levels.ERROR) - return - end - - vim.notify("Deploying " .. filename .. " to " .. server_name .. "...", vim.log.levels.INFO) - - local rsync = require("deployment.rsync") - rsync.deploy_single_file_to_server(config, server, project_root, relative_path, function(srv, success, error_msg, duration) - if success then - vim.notify(string.format("✓ %s deployed %s successfully (%dms)", srv.name, filename, duration), vim.log.levels.INFO) - else - vim.notify(string.format("✗ %s deployment of %s failed: %s", srv.name, filename, error_msg or "Unknown error"), vim.log.levels.ERROR) + + -- Check if file is within project root + local project_path = Path:new(project_root):absolute() + if not full_path:find(project_path, 1, true) then + vim.notify("File must be within project root: " .. file_path, vim.log.levels.ERROR) + return + end + + -- Calculate relative path from project root + local relative_path = full_path:sub(#project_path + 2) -- +2 to skip trailing slash + local filename = vim.fn.fnamemodify(full_path, ":t") + + local config, err = M.get_current_config(project_root) + + if not config then + vim.notify("Deployment failed: " .. err, vim.log.levels.ERROR) + return + end + + local server = nil + for _, srv in ipairs(config.servers) do + if srv.name == server_name then + server = srv + break + end end - end) + + if not server then + vim.notify("Server '" .. server_name .. "' not found in .deployment file", vim.log.levels.ERROR) + return + end + + vim.notify("Deploying " .. filename .. " to " .. server_name .. "...", vim.log.levels.INFO) + + local rsync = require("deployment.rsync") + rsync.deploy_single_file_to_server( + config, + server, + project_root, + relative_path, + function(srv, success, error_msg, duration) + if success then + vim.notify( + string.format("✓ %s deployed %s successfully (%dms)", srv.name, filename, duration), + vim.log.levels.INFO + ) + else + vim.notify( + string.format( + "✗ %s deployment of %s failed: %s", + srv.name, + filename, + error_msg or "Unknown error" + ), + vim.log.levels.ERROR + ) + end + end + ) end -- Show deployment summary function M.show_deployment_summary(results, show_summary) - if not show_summary then return end - - local successful = 0 - local failed = 0 - local total_time = 0 - - for _, result in ipairs(results) do - if result.success then - successful = successful + 1 + if not show_summary then + return + end + + local successful = 0 + local failed = 0 + local total_time = 0 + + for _, result in ipairs(results) do + if result.success then + successful = successful + 1 + else + failed = failed + 1 + end + total_time = total_time + result.duration + end + + local avg_time = math.floor(total_time / #results) + local summary = + string.format("Deployment complete: %d successful, %d failed (avg: %dms)", successful, failed, avg_time) + + if failed == 0 then + vim.notify("✓ " .. summary, vim.log.levels.INFO) else - failed = failed + 1 + vim.notify("⚠ " .. summary, vim.log.levels.WARN) end - total_time = total_time + result.duration - end - - local avg_time = math.floor(total_time / #results) - local summary = string.format("Deployment complete: %d successful, %d failed (avg: %dms)", successful, failed, avg_time) - - if failed == 0 then - vim.notify("✓ " .. summary, vim.log.levels.INFO) - else - vim.notify("⚠ " .. summary, vim.log.levels.WARN) - end end -- Show file deployment summary function M.show_file_deployment_summary(results, filename, show_summary) - if not show_summary then return end - - local successful = 0 - local failed = 0 - local total_time = 0 - - for _, result in ipairs(results) do - if result.success then - successful = successful + 1 + if not show_summary then + return + end + + local successful = 0 + local failed = 0 + local total_time = 0 + + for _, result in ipairs(results) do + if result.success then + successful = successful + 1 + else + failed = failed + 1 + end + total_time = total_time + result.duration + end + + local avg_time = math.floor(total_time / #results) + local summary = string.format( + "File %s deployment: %d successful, %d failed (avg: %dms)", + filename, + successful, + failed, + avg_time + ) + + if failed == 0 then + vim.notify("✓ " .. summary, vim.log.levels.INFO) else - failed = failed + 1 + vim.notify("⚠ " .. summary, vim.log.levels.WARN) end - total_time = total_time + result.duration - end - - local avg_time = math.floor(total_time / #results) - local summary = string.format("File %s deployment: %d successful, %d failed (avg: %dms)", filename, successful, failed, avg_time) - - if failed == 0 then - vim.notify("✓ " .. summary, vim.log.levels.INFO) - else - vim.notify("⚠ " .. summary, vim.log.levels.WARN) - end end -- List configured servers function M.list_servers() - local project_root = M.get_project_root() - local config, err, active_name = M.get_current_config(project_root) - - if not config then - vim.notify("Cannot list servers: " .. err, vim.log.levels.ERROR) - return - end - - if #config.servers == 0 then - vim.notify("No servers configured", vim.log.levels.INFO) - return - end - - local lines = { "Configured deployment servers (" .. (active_name or "default") .. "):" } - for _, server in ipairs(config.servers) do - local host = server.host or "unknown_host" - local remote_path = server.remote_path or "unknown_path" - local local_path = server.local_path or "." - local name = server.name or "unnamed" - table.insert(lines, string.format(" • %s: %s -> %s:%s", name, local_path, host, remote_path)) - end - - vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO) + local project_root = M.get_project_root() + local config, err, active_name = M.get_current_config(project_root) + + if not config then + vim.notify("Cannot list servers: " .. err, vim.log.levels.ERROR) + return + end + + if #config.servers == 0 then + vim.notify("No servers configured", vim.log.levels.INFO) + return + end + + local lines = { "Configured deployment servers (" .. (active_name or "default") .. "):" } + for _, server in ipairs(config.servers) do + local host = server.host or "unknown_host" + local remote_path = server.remote_path or "unknown_path" + local local_path = server.local_path or "." + local name = server.name or "unnamed" + table.insert(lines, string.format(" • %s: %s -> %s:%s", name, local_path, host, remote_path)) + end + + vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO) end -- Create example .deployment file function M.create_example_deployment_file(use_multi_config) - local project_root = M.get_project_root() - local deployment_file = Path:new(project_root, require("deployment.config").get().deployment_file) - - if deployment_file:exists() then - vim.notify(".deployment file already exists", vim.log.levels.WARN) - return - end - - local example_content = [[# Deployment configuration (YAML format) + local project_root = M.get_project_root() + local deployment_file = Path:new(project_root, require("deployment.config").get().deployment_file) + + if deployment_file:exists() then + vim.notify(".deployment file already exists", vim.log.levels.WARN) + return + end + + local example_content = [[# Deployment configuration (YAML format) # Deploy files to multiple servers using rsync servers: @@ -503,171 +615,174 @@ options: - "--partial" - "--progress" ]] - - deployment_file:write(example_content, "w") - local config_type = use_multi_config and "multi-configuration" or "single configuration" - vim.notify("Created example " .. config_type .. " .deployment file at: " .. deployment_file.filename, vim.log.levels.INFO) + + deployment_file:write(example_content, "w") + local config_type = use_multi_config and "multi-configuration" or "single configuration" + vim.notify( + "Created example " .. config_type .. " .deployment file at: " .. deployment_file.filename, + vim.log.levels.INFO + ) end -- List all available configurations function M.list_configurations() - local project_root = M.get_project_root() - local parser = require("deployment.parser") - local multi_config, err = parser.parse_multi_config_deployment( - project_root .. "/" .. require("deployment.config").get().deployment_file - ) - - if not multi_config then - vim.notify("Cannot list configurations: " .. err, vim.log.levels.ERROR) - return - end - - if not multi_config.configurations or vim.tbl_isempty(multi_config.configurations) then - vim.notify("No configurations found. This appears to be a single-config deployment file.", vim.log.levels.INFO) - return - end - - local lines = { "Available deployment configurations:" } - for config_name, config in pairs(multi_config.configurations) do - local active_marker = (config_name == multi_config.active) and " (active)" or "" - local server_count = #config.servers - table.insert(lines, string.format(" • %s: %d server(s)%s", config_name, server_count, active_marker)) - end - - vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO) + local project_root = M.get_project_root() + local parser = require("deployment.parser") + local multi_config, err = + parser.parse_multi_config_deployment(project_root .. "/" .. require("deployment.config").get().deployment_file) + + if not multi_config then + vim.notify("Cannot list configurations: " .. err, vim.log.levels.ERROR) + return + end + + if not multi_config.configurations or vim.tbl_isempty(multi_config.configurations) then + vim.notify("No configurations found. This appears to be a single-config deployment file.", vim.log.levels.INFO) + return + end + + local lines = { "Available deployment configurations:" } + for config_name, config in pairs(multi_config.configurations) do + local active_marker = (config_name == multi_config.active) and " (active)" or "" + local server_count = #config.servers + table.insert(lines, string.format(" • %s: %d server(s)%s", config_name, server_count, active_marker)) + end + + vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO) end -- Set active configuration function M.set_active_configuration(config_name) - local project_root = M.get_project_root() - local deployment_file = Path:new(project_root, require("deployment.config").get().deployment_file) - - if not deployment_file:exists() then - vim.notify("No .deployment file found", vim.log.levels.ERROR) - return - end - - local parser = require("deployment.parser") - local multi_config, err = parser.parse_multi_config_deployment(deployment_file.filename) - if not multi_config then - vim.notify("Cannot parse configurations: " .. err, vim.log.levels.ERROR) - return - end - - if not multi_config.configurations or not multi_config.configurations[config_name] then - vim.notify("Configuration '" .. config_name .. "' not found", vim.log.levels.ERROR) - return - end - - -- Read the file and update the active line - local content = deployment_file:read() - local lines = vim.split(content, "\n") - local updated = false - - for i, line in ipairs(lines) do - if line:match("^active:%s*") then - lines[i] = "active: " .. config_name - updated = true - break + local project_root = M.get_project_root() + local deployment_file = Path:new(project_root, require("deployment.config").get().deployment_file) + + if not deployment_file:exists() then + vim.notify("No .deployment file found", vim.log.levels.ERROR) + return end - end - - if not updated then - -- Insert active line after first comment block or at the beginning - local insert_pos = 1 + + local parser = require("deployment.parser") + local multi_config, err = parser.parse_multi_config_deployment(deployment_file.filename) + if not multi_config then + vim.notify("Cannot parse configurations: " .. err, vim.log.levels.ERROR) + return + end + + if not multi_config.configurations or not multi_config.configurations[config_name] then + vim.notify("Configuration '" .. config_name .. "' not found", vim.log.levels.ERROR) + return + end + + -- Read the file and update the active line + local content = deployment_file:read() + local lines = vim.split(content, "\n") + local updated = false + for i, line in ipairs(lines) do - if not line:match("^#") and vim.trim(line) ~= "" then - insert_pos = i - break - end - end - table.insert(lines, insert_pos, "active: " .. config_name) - table.insert(lines, insert_pos + 1, "") - end - - deployment_file:write(table.concat(lines, "\n"), "w") - vim.notify("Active configuration set to: " .. config_name, vim.log.levels.INFO) + if line:match("^active:%s*") then + lines[i] = "active: " .. config_name + updated = true + break + end + end + + if not updated then + -- Insert active line after first comment block or at the beginning + local insert_pos = 1 + for i, line in ipairs(lines) do + if not line:match("^#") and vim.trim(line) ~= "" then + insert_pos = i + break + end + end + table.insert(lines, insert_pos, "active: " .. config_name) + table.insert(lines, insert_pos + 1, "") + end + + deployment_file:write(table.concat(lines, "\n"), "w") + vim.notify("Active configuration set to: " .. config_name, vim.log.levels.INFO) end -- Debug deployment file parsing function M.debug_parse() - local project_root = M.get_project_root() - vim.notify("Project root: " .. project_root, vim.log.levels.INFO) - - local parser = require("deployment.parser") - local deployment_file_path = project_root .. "/" .. require("deployment.config").get().deployment_file - - -- Test multi-config parsing - local multi_config, multi_err = parser.parse_multi_config_deployment(deployment_file_path) - if multi_config then - vim.notify("Multi-config parsing SUCCESS", vim.log.levels.INFO) - vim.notify("Active: " .. (multi_config.active or "nil"), vim.log.levels.INFO) - local config_count = multi_config.configurations and vim.tbl_count(multi_config.configurations) or 0 - vim.notify("Found " .. config_count .. " configurations", vim.log.levels.INFO) - - if multi_config.configurations then - for name, config in pairs(multi_config.configurations) do - local server_count = config.servers and #config.servers or 0 - vim.notify(" - " .. name .. ": " .. server_count .. " servers", vim.log.levels.INFO) - end - end - else - vim.notify("Multi-config parsing FAILED: " .. (multi_err or "unknown"), vim.log.levels.ERROR) - end - - -- Test current config - local config, err, active_name = M.get_current_config(project_root) - if config then - vim.notify("Current config SUCCESS: " .. (active_name or "default"), vim.log.levels.INFO) - vim.notify("Servers in current config: " .. #config.servers, vim.log.levels.INFO) - else - vim.notify("Current config FAILED: " .. (err or "unknown"), vim.log.levels.ERROR) - end + local project_root = M.get_project_root() + vim.notify("Project root: " .. project_root, vim.log.levels.INFO) + + local parser = require("deployment.parser") + local deployment_file_path = project_root .. "/" .. require("deployment.config").get().deployment_file + + -- Test multi-config parsing + local multi_config, multi_err = parser.parse_multi_config_deployment(deployment_file_path) + if multi_config then + vim.notify("Multi-config parsing SUCCESS", vim.log.levels.INFO) + vim.notify("Active: " .. (multi_config.active or "nil"), vim.log.levels.INFO) + local config_count = multi_config.configurations and vim.tbl_count(multi_config.configurations) or 0 + vim.notify("Found " .. config_count .. " configurations", vim.log.levels.INFO) + + if multi_config.configurations then + for name, config in pairs(multi_config.configurations) do + local server_count = config.servers and #config.servers or 0 + vim.notify(" - " .. name .. ": " .. server_count .. " servers", vim.log.levels.INFO) + end + end + else + vim.notify("Multi-config parsing FAILED: " .. (multi_err or "unknown"), vim.log.levels.ERROR) + end + + -- Test current config + local config, err, active_name = M.get_current_config(project_root) + if config then + vim.notify("Current config SUCCESS: " .. (active_name or "default"), vim.log.levels.INFO) + vim.notify("Servers in current config: " .. #config.servers, vim.log.levels.INFO) + else + vim.notify("Current config FAILED: " .. (err or "unknown"), vim.log.levels.ERROR) + end end -- Lualine component for deployment status function M.lualine_component() - return { - function() - local project_root = M.get_project_root() - local parser = require("deployment.parser") - local multi_config, _ = parser.parse_multi_config_deployment( - project_root .. "/" .. require("deployment.config").get().deployment_file - ) - - if multi_config and multi_config.active then - return "󰒋 " .. multi_config.active - end - - -- Fallback: check if there's a single config deployment - local single_config, _ = parser.parse_deployment_config( - project_root .. "/" .. require("deployment.config").get().deployment_file - ) - if single_config and single_config.servers and #single_config.servers > 0 then - return "󰒋 deploy" - end - - return "" - end, - color = { fg = "#7aa2f7" }, - } + return { + function() + local project_root = M.get_project_root() + local parser = require("deployment.parser") + local multi_config, _ = parser.parse_multi_config_deployment( + project_root .. "/" .. require("deployment.config").get().deployment_file + ) + + if multi_config and multi_config.active then + return "󰒋 " .. multi_config.active + end + + -- Fallback: check if there's a single config deployment + local single_config, _ = parser.parse_deployment_config( + project_root .. "/" .. require("deployment.config").get().deployment_file + ) + if single_config and single_config.servers and #single_config.servers > 0 then + return "󰒋 deploy" + end + + return "" + end, + color = { fg = "#7aa2f7" }, + } end -- Setup function for the plugin function M.setup(opts) - -- Setup configuration - require("deployment.config").setup(opts) - - -- Setup commands - require("deployment.commands").setup_commands() - - -- Setup keymaps (optional) - if opts and opts.keymaps ~= false then - require("deployment.commands").setup_keymaps() - end - - -- Make module globally available for backwards compatibility - _G.Deployment = M + -- Setup configuration + require("deployment.config").setup(opts) + + -- Setup commands + require("deployment.commands").setup_commands() + + -- Setup keymaps (optional) + if opts and opts.keymaps ~= false then + require("deployment.commands").setup_keymaps() + end + + -- Make module globally available for backwards compatibility + _G.Deployment = M end -return M \ No newline at end of file +return M + diff --git a/lua/deployment/parser.lua b/lua/deployment/parser.lua index eac6a87..a53b543 100644 --- a/lua/deployment/parser.lua +++ b/lua/deployment/parser.lua @@ -3,207 +3,216 @@ local M = {} -- Simple YAML parser for .deployment file local function parse_yaml_value(value) - if not value then return nil end - - local trimmed = vim.trim(value) - - -- Handle quoted strings - if trimmed:match('^".*"$') or trimmed:match("^'.*'$") then - return trimmed:sub(2, -2) - end - - -- Handle boolean values - if trimmed:lower() == "true" then return true end - if trimmed:lower() == "false" then return false end - - -- Handle numbers - local num = tonumber(trimmed) - if num then return num end - - return trimmed + if not value then + return nil + end + + local trimmed = vim.trim(value) + + -- Handle quoted strings + if trimmed:match('^".*"$') or trimmed:match("^'.*'$") then + return trimmed:sub(2, -2) + end + + -- Handle boolean values + if trimmed:lower() == "true" then + return true + end + if trimmed:lower() == "false" then + return false + end + + -- Handle numbers + local num = tonumber(trimmed) + if num then + return num + end + + return trimmed end -- Simple YAML-like parser for deployment configuration local function parse_simple_yaml(content) - local result = {} - local lines = vim.split(content, "\n") - local stack = {{table = result, indent = -1}} - - for _, line in ipairs(lines) do - if line:match("^%s*$") or line:match("^%s*#") then - -- Skip empty lines and comments - else - local indent = #line:match("^%s*") - local trimmed = vim.trim(line) - - -- Pop stack to correct level - while #stack > 1 and stack[#stack].indent >= indent do - table.remove(stack) - end - - local current = stack[#stack].table - - if trimmed:match("^(.-):%s*$") then - -- Key without value (starts a new table) - local key = trimmed:match("^(.-):%s*$") - current[key] = {} - table.insert(stack, {table = current[key], indent = indent}) - elseif trimmed:match("^(.-):%s*(.+)$") then - -- Key with value - local key, value = trimmed:match("^(.-):%s*(.+)$") - current[key] = parse_yaml_value(value) - elseif trimmed:match("^%-%s*(.+)$") then - -- Array item - local value = trimmed:match("^%-%s*(.+)$") - table.insert(current, parse_yaml_value(value)) - end + local result = {} + local lines = vim.split(content, "\n") + local stack = { { table = result, indent = -1 } } + + for _, line in ipairs(lines) do + if line:match("^%s*$") or line:match("^%s*#") then + -- Skip empty lines and comments + else + local indent = #line:match("^%s*") + local trimmed = vim.trim(line) + + -- Pop stack to correct level + while #stack > 1 and stack[#stack].indent >= indent do + table.remove(stack) + end + + local current = stack[#stack].table + + if trimmed:match("^(.-):%s*$") then + -- Key without value (starts a new table) + local key = trimmed:match("^(.-):%s*$") + current[key] = {} + table.insert(stack, { table = current[key], indent = indent }) + elseif trimmed:match("^(.-):%s*(.+)$") then + -- Key with value + local key, value = trimmed:match("^(.-):%s*(.+)$") + current[key] = parse_yaml_value(value) + elseif trimmed:match("^%-%s*(.+)$") then + -- Array item + local value = trimmed:match("^%-%s*(.+)$") + table.insert(current, parse_yaml_value(value)) + end + end end - end - - return result + + return result end -- Parse multi-configuration deployment file function M.parse_multi_config_deployment(deployment_file_path) - local Path = require("plenary.path") - local deployment_file = Path:new(deployment_file_path) - - if not deployment_file:exists() then - return nil, "No .deployment file found at: " .. deployment_file_path - end - - local content = deployment_file:read() - if not content then - return nil, "Could not read .deployment file" - end - - local parsed = parse_simple_yaml(content) - - if not parsed.configurations then - return nil, "No configurations found in deployment file" - end - - -- Convert servers from hash to array format - for config_name, config in pairs(parsed.configurations) do - if config.servers then - local servers_array = {} - for server_name, server_props in pairs(config.servers) do - local server = { - name = server_name, - host = server_props.host, - remote_path = server_props.remote_path, - local_path = server_props.local_path or "." - } - table.insert(servers_array, server) - end - config.servers = servers_array + local Path = require("plenary.path") + local deployment_file = Path:new(deployment_file_path) + + if not deployment_file:exists() then + return nil, "No .deployment file found at: " .. deployment_file_path + end + + local content = deployment_file:read() + if not content then + return nil, "Could not read .deployment file" + end + + local parsed = parse_simple_yaml(content) + + if not parsed.configurations then + return nil, "No configurations found in deployment file" end - - -- Ensure other sections exist - config.exclude = config.exclude or {} - config.include = config.include or {} - config.options = config.options or {} - end - - return { - configurations = parsed.configurations, - active = parsed.active - }, nil + + -- Convert servers from hash to array format + for config_name, config in pairs(parsed.configurations) do + if config.servers then + local servers_array = {} + for server_name, server_props in pairs(config.servers) do + local server = { + name = server_name, + host = server_props.host, + remote_path = server_props.remote_path, + local_path = server_props.local_path or ".", + } + table.insert(servers_array, server) + end + config.servers = servers_array + end + + -- Ensure other sections exist + config.exclude = config.exclude or {} + config.include = config.include or {} + config.options = config.options or {} + end + + return { + configurations = parsed.configurations, + active = parsed.active, + }, nil end -- Parse deployment configuration file (legacy single config support) function M.parse_deployment_config(deployment_file_path) - local Path = require("plenary.path") - local deployment_file = Path:new(deployment_file_path) - local config = require("deployment.config") - - if not deployment_file:exists() then - return nil, "No .deployment file found at: " .. deployment_file_path - end - - local content = deployment_file:read() - if not content then - return nil, "Could not read .deployment file" - end - - local result = { - servers = {}, - exclude = {}, - include = {}, - options = {} - } - - local current_section = nil - local current_server = nil - - for line in content:gmatch("[^\n]+") do - local trimmed = vim.trim(line) - - -- Skip empty lines and comments - if trimmed ~= "" and not trimmed:match("^#") then - -- Check for main sections - if trimmed == "servers:" then - current_section = "servers" - elseif trimmed == "exclude:" then - current_section = "exclude" - elseif trimmed == "include:" then - current_section = "include" - elseif trimmed == "options:" then - current_section = "options" - elseif current_section == "servers" then - -- Handle server entries - local server_name = trimmed:match("^%s*([%w_%-]+):$") - if server_name then - current_server = { - name = server_name, - host = nil, - remote_path = nil, - local_path = "." - } - table.insert(result.servers, current_server) - elseif current_server then - -- Parse server properties - if config.get().debug then - print(string.format("DEBUG: Parsing line: '%s'", trimmed)) - end - - local key, value = trimmed:match("^%s+([%w_]+):%s*(.*)") - if key and value then - if config.get().debug then - print(string.format("DEBUG: Found property '%s' = '%s'", key, value)) - end - current_server[key] = parse_yaml_value(value) - else - -- Try simpler pattern - local simple_key, simple_value = trimmed:match("([%w_]+):%s*(.*)") - if simple_key and simple_value then - current_server[simple_key] = parse_yaml_value(simple_value) + local Path = require("plenary.path") + local deployment_file = Path:new(deployment_file_path) + local config = require("deployment.config") + + if not deployment_file:exists() then + return nil, "No .deployment file found at: " .. deployment_file_path + end + + local content = deployment_file:read() + if not content then + return nil, "Could not read .deployment file" + end + + local result = { + servers = {}, + exclude = {}, + include = {}, + options = {}, + } + + local current_section = nil + local current_server = nil + + for line in content:gmatch("[^\n]+") do + local trimmed = vim.trim(line) + + -- Skip empty lines and comments + if trimmed ~= "" and not trimmed:match("^#") then + -- Check for main sections + if trimmed == "servers:" then + current_section = "servers" + elseif trimmed == "exclude:" then + current_section = "exclude" + elseif trimmed == "include:" then + current_section = "include" + elseif trimmed == "options:" then + current_section = "options" + elseif current_section == "servers" then + -- Handle server entries + local server_name = trimmed:match("^%s*([%w_%-]+):$") + if server_name then + current_server = { + name = server_name, + host = nil, + remote_path = nil, + local_path = ".", + } + table.insert(result.servers, current_server) + elseif current_server then + -- Parse server properties + if config.get().debug then + print(string.format("DEBUG: Parsing line: '%s'", trimmed)) + end + + local key, value = trimmed:match("^%s+([%w_]+):%s*(.*)") + if key and value then + if config.get().debug then + print(string.format("DEBUG: Found property '%s' = '%s'", key, value)) + end + current_server[key] = parse_yaml_value(value) + else + -- Try simpler pattern + local simple_key, simple_value = trimmed:match("([%w_]+):%s*(.*)") + if simple_key and simple_value then + current_server[simple_key] = parse_yaml_value(simple_value) + end + end + end + elseif current_section == "exclude" then + -- Handle exclude patterns + local pattern = trimmed:match("^%s*-%s*(.+)") + if pattern then + table.insert(result.exclude, parse_yaml_value(pattern)) + end + elseif current_section == "include" then + -- Handle include patterns + local pattern = trimmed:match("^%s*-%s*(.+)") + if pattern then + table.insert(result.include, parse_yaml_value(pattern)) + end + elseif current_section == "options" then + -- Handle rsync options + local option = trimmed:match("^%s*-%s*(.+)") + if option then + table.insert(result.options, parse_yaml_value(option)) + end end - end - end - elseif current_section == "exclude" then - -- Handle exclude patterns - local pattern = trimmed:match("^%s*-%s*(.+)") - if pattern then - table.insert(result.exclude, parse_yaml_value(pattern)) - end - elseif current_section == "include" then - -- Handle include patterns - local pattern = trimmed:match("^%s*-%s*(.+)") - if pattern then - table.insert(result.include, parse_yaml_value(pattern)) end - elseif current_section == "options" then - -- Handle rsync options - local option = trimmed:match("^%s*-%s*(.+)") - if option then - table.insert(result.options, parse_yaml_value(option)) - end - end end - end - return result, nil + return result, nil end -return M \ No newline at end of file +return M + diff --git a/lua/deployment/rsync.lua b/lua/deployment/rsync.lua index 75260af..8aa4971 100644 --- a/lua/deployment/rsync.lua +++ b/lua/deployment/rsync.lua @@ -6,113 +6,114 @@ local Path = require("plenary.path") -- Build rsync command for full deployment function M.build_rsync_command(config, server, project_root) - local deployment_config = require("deployment.config") - local cmd = { "rsync" } - - -- Add default options - vim.list_extend(cmd, deployment_config.get().rsync_options) - - -- Conditionally add --delete flag - if deployment_config.get().delete_remote_files then - table.insert(cmd, "--delete") - end - - -- Add custom options from config - vim.list_extend(cmd, config.options) - - -- Add excludes - for _, exclude in ipairs(config.exclude) do - table.insert(cmd, "--exclude=" .. exclude) - end - - -- Add includes (rsync processes includes before excludes) - for _, include in ipairs(config.include) do - table.insert(cmd, "--include=" .. include) - end - - -- Source path - local source_path = Path:new(project_root, server.local_path):absolute() - if not source_path:match("/$") then - source_path = source_path .. "/" - end - table.insert(cmd, source_path) - - -- Destination - local dest = server.host .. ":" .. server.remote_path - if not server.remote_path:match("/$") then - dest = dest .. "/" - end - table.insert(cmd, dest) - - return cmd + local deployment_config = require("deployment.config") + local cmd = { "rsync" } + + -- Add default options + vim.list_extend(cmd, deployment_config.get().rsync_options) + + -- Conditionally add --delete flag + if deployment_config.get().delete_remote_files then + table.insert(cmd, "--delete") + end + + -- Add custom options from config + vim.list_extend(cmd, config.options) + + -- Add excludes + for _, exclude in ipairs(config.exclude) do + table.insert(cmd, "--exclude=" .. exclude) + end + + -- Add includes (rsync processes includes before excludes) + for _, include in ipairs(config.include) do + table.insert(cmd, "--include=" .. include) + end + + -- Source path + local source_path = Path:new(project_root, server.local_path):absolute() + if not source_path:match("/$") then + source_path = source_path .. "/" + end + table.insert(cmd, source_path) + + -- Destination + local dest = server.host .. ":" .. server.remote_path + if not server.remote_path:match("/$") then + dest = dest .. "/" + end + table.insert(cmd, dest) + + return cmd end -- Build rsync command for single file deployment function M.build_file_rsync_command(config, server, project_root, relative_path) - local cmd = { "rsync" } - - -- Add basic options for single file (no --delete for safety) - local file_options = { - "-avz", - "--exclude=.git/", - "--exclude=.DS_Store", - } - vim.list_extend(cmd, file_options) - - -- Add custom options from config - vim.list_extend(cmd, config.options) - - -- Source file (full path) - local source_file = Path:new(project_root, relative_path):absolute() - table.insert(cmd, source_file) - - -- Destination (preserve directory structure) - local dest = server.host .. ":" .. server.remote_path .. "/" .. relative_path - table.insert(cmd, dest) - - return cmd + local cmd = { "rsync" } + + -- Add basic options for single file (no --delete for safety) + local file_options = { + "-avz", + "--exclude=.git/", + "--exclude=.DS_Store", + } + vim.list_extend(cmd, file_options) + + -- Add custom options from config + vim.list_extend(cmd, config.options) + + -- Source file (full path) + local source_file = Path:new(project_root, relative_path):absolute() + table.insert(cmd, source_file) + + -- Destination (preserve directory structure) + local dest = server.host .. ":" .. server.remote_path .. "/" .. relative_path + table.insert(cmd, dest) + + return cmd end -- Execute rsync command function M.execute_rsync(cmd, callback) - local start_time = vim.loop.hrtime() - - Job:new({ - command = cmd[1], - args = vim.list_slice(cmd, 2), - on_exit = function(j, return_val) - local end_time = vim.loop.hrtime() - local duration = math.floor((end_time - start_time) / 1000000) -- Convert to milliseconds - - if return_val == 0 then - callback(true, nil, duration) - else - local stderr = table.concat(j:stderr_result(), "\n") - callback(false, stderr, duration) - end - end, - on_stderr = function(_, data) - -- Store stderr for error reporting - end, - }):start() + local start_time = vim.loop.hrtime() + + Job:new({ + command = cmd[1], + args = vim.list_slice(cmd, 2), + on_exit = function(j, return_val) + local end_time = vim.loop.hrtime() + local duration = math.floor((end_time - start_time) / 1000000) -- Convert to milliseconds + + if return_val == 0 then + callback(true, nil, duration) + else + local stderr = table.concat(j:stderr_result(), "\n") + callback(false, stderr, duration) + end + end, + on_stderr = function(_, data) + -- Store stderr for error reporting + end, + }):start() end -- Deploy to single server (full deployment) function M.deploy_to_server(config, server, project_root, callback) - local cmd = M.build_rsync_command(config, server, project_root) - - M.execute_rsync(cmd, function(success, error_msg, duration) - callback(server, success, error_msg, duration) - end) + local cmd = M.build_rsync_command(config, server, project_root) + + M.execute_rsync(cmd, function(success, error_msg, duration) + callback(server, success, error_msg, duration) + end) end -- Deploy single file to server function M.deploy_single_file_to_server(config, server, project_root, relative_path, callback) - local cmd = M.build_file_rsync_command(config, server, project_root, relative_path) - - M.execute_rsync(cmd, function(success, error_msg, duration) - callback(server, success, error_msg, duration) - end) + local cmd = M.build_file_rsync_command(config, server, project_root, relative_path) + + M.execute_rsync(cmd, function(success, error_msg, duration) + callback(server, success, error_msg, duration) + end) end -return M \ No newline at end of file +return M + From 62c873a0988de0032c06f2ed76e33f64e46ab199 Mon Sep 17 00:00:00 2001 From: Danish Bhatti Date: Sun, 2 Nov 2025 17:26:57 +0100 Subject: [PATCH 2/2] ci: fix ci failures --- .github/workflows/ci.yml | 5 +++-- .luacheckrc | 1 + lua/deployment/commands.lua | 15 +++++++++------ lua/deployment/init.lua | 4 ++-- lua/deployment/parser.lua | 5 ++--- lua/deployment/rsync.lua | 2 +- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d1d1ae..6206696 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,9 +17,10 @@ jobs: - uses: actions/checkout@v4 - name: Install Neovim - uses: rhymond/setup-neovim@v1 + uses: rhysd/action-setup-vim@v1 with: - neovim-version: ${{ matrix.neovim_version }} + neovim: true + version: ${{ matrix.neovim_version }} - name: Install Lua uses: leafo/gh-actions-lua@v10 diff --git a/.luacheckrc b/.luacheckrc index 602f97d..2912c93 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -15,6 +15,7 @@ globals = { -- Ignore some common warnings ignore = { "212/_.*", -- unused argument, for vars with "_" prefix + "213/_.*", -- unused loop variable, for vars with "_" prefix "214", -- unused variable "121", -- setting read-only global variable "122", -- setting read-only field of global variable diff --git a/lua/deployment/commands.lua b/lua/deployment/commands.lua index fbdd9c4..8218628 100644 --- a/lua/deployment/commands.lua +++ b/lua/deployment/commands.lua @@ -45,11 +45,14 @@ local function get_file_completion() -- Get all files in project (excluding common build/temp directories) local files = {} - local handle = io.popen( - "find '" - .. project_root - .. "' -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/build/*' -not -path '*/target/*' 2>/dev/null" - ) + local find_cmd = "find '" .. project_root .. "' -type f" + .. " -not -path '*/node_modules/*'" + .. " -not -path '*/.git/*'" + .. " -not -path '*/dist/*'" + .. " -not -path '*/build/*'" + .. " -not -path '*/target/*'" + .. " 2>/dev/null" + local handle = io.popen(find_cmd) if handle then for file in handle:lines() do @@ -135,7 +138,7 @@ function M.setup_commands() end, { desc = "Deploy file by path to all servers or specific server", nargs = "+", - complete = function(arg_lead, cmd_line, cursor_pos) + complete = function(arg_lead, cmd_line, _cursor_pos) local args = vim.split(cmd_line, "%s+") local arg_count = #args diff --git a/lua/deployment/init.lua b/lua/deployment/init.lua index bc60c2f..798481a 100644 --- a/lua/deployment/init.lua +++ b/lua/deployment/init.lua @@ -585,12 +585,12 @@ servers: host: user@staging.example.com remote_path: /var/www/html local_path: . - + production: host: deploy@prod.example.com remote_path: /var/www/html local_path: ./dist - + backup: host: backup@backup.server.com remote_path: /backups/myproject diff --git a/lua/deployment/parser.lua b/lua/deployment/parser.lua index a53b543..3436499 100644 --- a/lua/deployment/parser.lua +++ b/lua/deployment/parser.lua @@ -38,9 +38,8 @@ local function parse_simple_yaml(content) local stack = { { table = result, indent = -1 } } for _, line in ipairs(lines) do - if line:match("^%s*$") or line:match("^%s*#") then -- Skip empty lines and comments - else + if not (line:match("^%s*$") or line:match("^%s*#")) then local indent = #line:match("^%s*") local trimmed = vim.trim(line) @@ -92,7 +91,7 @@ function M.parse_multi_config_deployment(deployment_file_path) end -- Convert servers from hash to array format - for config_name, config in pairs(parsed.configurations) do + for _config_name, config in pairs(parsed.configurations) do if config.servers then local servers_array = {} for server_name, server_props in pairs(config.servers) do diff --git a/lua/deployment/rsync.lua b/lua/deployment/rsync.lua index 8aa4971..f3bcfce 100644 --- a/lua/deployment/rsync.lua +++ b/lua/deployment/rsync.lua @@ -91,7 +91,7 @@ function M.execute_rsync(cmd, callback) callback(false, stderr, duration) end end, - on_stderr = function(_, data) + on_stderr = function(_, _data) -- Store stderr for error reporting end, }):start()