cord-nvim 2.0.0-beta.13

🚀 The most extensible Discord Rich Presence plugin for Neovim, powered by Rust.
local async = require 'cord.core.async'
local logger = require 'cord.plugin.log'

local M = {}

M.build = async.wrap(function()
  local server = require 'cord.server'
  if server.is_updating then return end
  server.is_updating = true

  if not vim.fn.executable 'cargo' then
    error 'cargo is not installed or not in PATH'
    return
  end

  logger.info 'Building executable...'

  vim.schedule(function()
    local cord = require 'cord.server'
    local function initialize()
      local process = require 'cord.core.uv.process'

      async.run(function()
        process
          .spawn({
            cmd = 'cargo',
            args = {
              'install',
              'cord-nvim',
              '--force',
              '--root',
              require('cord.server.fs').get_data_path(),
            },
          })
          :and_then(function(res)
            if res.code ~= 0 then
              server.is_updating = false
              logger.error 'Failed to build executable'
              if res.stderr then logger.error('cargo\'s stderr: ' .. res.stderr) end
              return
            end
            logger.info 'Successfully built executable. Restarting...'

            async.run(function()
              server.is_updating = false
              cord:initialize()
            end)
          end, function(err)
            server.is_updating = false

            logger.error(err)
          end)
      end)
    end

    if cord.manager then cord.manager:cleanup() end
    if not cord.tx then return initialize() end
    if not cord.client then return initialize() end
    if cord.client:is_closing() then return initialize() end

    if cord.client.on_close then cord.client.on_close() end

    cord.client.on_close = function()
      cord.client.on_close = nil
      initialize()
    end

    cord.tx:shutdown()
  end)
end)

local function get_local_version()
  local process = require 'cord.core.uv.process'
  local executable_path = require('cord.server.fs').get_executable_path()

  return async.wrap(function()
    local res = process
      .spawn({
        cmd = executable_path,
        args = { '-v' },
      })
      :get()

    if not res then return nil end
    if res.code ~= 0 then return nil end
    local version = res.stdout:gsub('^%s*(.-)%s*$', '%1')
    if not version then return nil end

    return version
  end)()
end

local function check_versions()
  local process = require 'cord.core.uv.process'

  local current_version = async.wrap(function() return get_local_version() end)

  local latest_version = async.wrap(function()
    local res = process
      .spawn({
        cmd = 'curl',
        args = {
          'https://raw.githubusercontent.com/vyfor/cord.nvim/refs/heads/client-server/.github/server-version.txt',
          '--fail',
        },
      })
      :await()

    if res.code ~= 0 then
      error('Failed to fetch latest version: ' .. res.stdout, 0)
      return nil
    end

    local version = res.stdout:gsub('^%s*(.-)%s*$', '%1')
    if not version then
      error('Failed to parse latest version', 0)
      return nil
    end

    return version
  end)

  return current_version():await(), latest_version():await()
end

M.check_version = async.wrap(function()
  local server = require 'cord.server'
  if server.is_updating then return end

  if not vim.fn.executable 'curl' then
    error('curl is not installed or not in PATH', 0)
    return
  end

  async.run(function()
    logger.info 'Checking for updates...'
    local current, latest = check_versions()

    if current and latest then
      if latest == current then
        logger.info('You are on the latest server version ' .. latest)
      else
        logger.info('New version available: ' .. latest .. ' (current: ' .. current .. ')')
      end
    end
  end)
end)

M.version = async.wrap(function()
  async.run(function()
    local version = get_local_version():await()
    if version then logger.info('Server version: ' .. version) end
  end)
end)

M.fetch = async.wrap(function()
  local server = require 'cord.server'
  if server.is_updating then return end
  server.is_updating = true

  if not vim.fn.executable 'curl' then
    error('curl is not installed or not in PATH', 0)
    return
  end

  local executable_path = require('cord.server.fs').get_executable_path()
  local process = require 'cord.core.uv.process'

  local fetch_executable = vim.schedule_wrap(function(tag)
    local base_url
    if tag then
      logger.info('Found new version: ' .. tag .. '. Downloading...')
      base_url = 'https://github.com/vyfor/cord.nvim/releases/download/v' .. tag .. '/'
    else
      logger.info 'Downloading latest version...'
      base_url = 'https://github.com/vyfor/cord.nvim/releases/latest/download/'
    end

    local os_info = require('cord.plugin.constants').get_os()
    local url = base_url
      .. os_info.arch
      .. '-'
      .. os_info.name
      .. '-'
      .. (os_info.name == 'windows' and 'cord.exe' or 'cord')

    local cord = require 'cord.server'
    local function initialize()
      async.run(function()
        process
          .spawn({
            cmd = 'curl',
            args = {
              url,
              '--create-dirs',
              '--fail',
              '--location',
              '-o',
              executable_path,
              '-H',
              'Accept: application/octet-stream',
            },
          })
          :and_then(function(res)
            if res.code ~= 0 then
              server.is_updating = false
              logger.error('Failed to download executable; code: ' .. res.code .. ', path: ' .. url)
              if res.stderr then logger.error('curl\'s stderr: ' .. res.stderr) end
              return
            end
            logger.info 'Successfully updated executable. Restarting...'

            async.run(function()
              server.is_updating = false
              require('cord.core.uv.fs').chmod(executable_path, '755'):await()
              cord:initialize()
            end)
          end, function(err)
            server.is_updating = false
            logger.error(err)
          end)
      end)
    end

    if cord.manager then cord.manager:cleanup() end
    if not cord.tx then return initialize() end
    if not cord.client then return initialize() end
    if cord.client:is_closing() then return initialize() end

    if cord.client.on_close then cord.client.on_close() end

    cord.client.on_close = function()
      cord.client.on_close = nil
      initialize()
    end

    cord.tx:shutdown()
  end)

  async.run(function()
    logger.info 'Checking for updates...'
    local current, latest = check_versions()

    if current and latest then
      if latest == current then
        server.is_updating = false
        logger.info('Already on latest server version ' .. latest)
      else
        fetch_executable(latest)
      end
    else
      fetch_executable()
    end
  end)
end)

return M