-
Notifications
You must be signed in to change notification settings - Fork 0
feat(version): add CLI version check on extension startup #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| -- Version checker for shelltime | ||
|
|
||
| local config = require('shelltime.config') | ||
|
|
||
| local M = {} | ||
|
|
||
| -- Track if warning has been shown this session | ||
| local has_shown_warning = false | ||
|
|
||
| -- Version check API endpoint path | ||
| local VERSION_CHECK_ENDPOINT = '/api/v1/cli/version-check' | ||
|
|
||
| --- URL encode a string | ||
| ---@param str string String to encode | ||
| ---@return string Encoded string | ||
| local function url_encode(str) | ||
| if str then | ||
| str = string.gsub(str, '\n', '\r\n') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The handling of newline characters ( |
||
| str = string.gsub(str, '([^%w _%%%-%.~])', function(c) | ||
| return string.format('%%%02X', string.byte(c)) | ||
| end) | ||
| str = string.gsub(str, ' ', '+') | ||
| end | ||
| return str | ||
| end | ||
|
|
||
| --- Parse JSON response using Neovim's built-in JSON decoder | ||
| ---@param json_str string JSON string | ||
| ---@return table|nil Parsed table or nil on error | ||
| local function parse_json(json_str) | ||
| local ok, result = pcall(vim.json.decode, json_str) | ||
| if not ok or type(result) ~= 'table' then | ||
| return nil | ||
| end | ||
| return result | ||
| end | ||
|
Comment on lines
30
to
36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The custom JSON parser implemented here is very fragile as it relies on string matching with regular expressions. This can easily break if the API response format changes in ways not anticipated by the regex (e.g., different value types, number formats). Neovim provides a robust built-in JSON decoder, |
||
|
|
||
| --- Check CLI version against server | ||
| ---@param daemon_version string Current daemon version | ||
| ---@param callback function|nil Optional callback(result, error) | ||
| function M.check_version(daemon_version, callback) | ||
| local api_endpoint = config.get('api_endpoint') | ||
| local web_endpoint = config.get('web_endpoint') | ||
|
|
||
| if not api_endpoint or not web_endpoint then | ||
| if config.get('debug') then | ||
| vim.notify('[shelltime] No API/web endpoint configured, skipping version check', vim.log.levels.DEBUG) | ||
| end | ||
| if callback then | ||
| callback(nil, 'No endpoint configured') | ||
| end | ||
| return | ||
| end | ||
|
|
||
| if has_shown_warning then | ||
| if config.get('debug') then | ||
| vim.notify('[shelltime] Version warning already shown this session', vim.log.levels.DEBUG) | ||
| end | ||
| if callback then | ||
| callback(nil, 'Already shown') | ||
| end | ||
| return | ||
| end | ||
|
|
||
| local url = api_endpoint .. VERSION_CHECK_ENDPOINT .. '?version=' .. url_encode(daemon_version) | ||
|
|
||
| if config.get('debug') then | ||
| vim.notify('[shelltime] Checking version at: ' .. url, vim.log.levels.DEBUG) | ||
| end | ||
|
|
||
| -- Use curl asynchronously via vim.fn.jobstart | ||
| local stdout_data = {} | ||
|
|
||
| vim.fn.jobstart({ 'curl', '-sSL', '-m', '5', '-H', 'Accept: application/json', url }, { | ||
| stdout_buffered = true, | ||
| on_stdout = function(_, data) | ||
| if data then | ||
| for _, line in ipairs(data) do | ||
| if line ~= '' then | ||
| table.insert(stdout_data, line) | ||
| end | ||
| end | ||
| end | ||
| end, | ||
| on_exit = function(_, exit_code) | ||
| vim.schedule(function() | ||
| if exit_code ~= 0 then | ||
| if config.get('debug') then | ||
| vim.notify('[shelltime] Version check failed with exit code: ' .. exit_code, vim.log.levels.DEBUG) | ||
| end | ||
| if callback then | ||
| callback(nil, 'curl failed') | ||
| end | ||
| return | ||
| end | ||
|
|
||
| local response = table.concat(stdout_data, '') | ||
| local result = parse_json(response) | ||
|
|
||
| if result then | ||
| if not result.isLatest then | ||
| has_shown_warning = true | ||
| M.show_update_warning(daemon_version, result.latestVersion, web_endpoint) | ||
| elseif config.get('debug') then | ||
| vim.notify('[shelltime] CLI version ' .. daemon_version .. ' is up to date', vim.log.levels.DEBUG) | ||
| end | ||
|
|
||
| if callback then | ||
| callback(result, nil) | ||
| end | ||
| else | ||
| if config.get('debug') then | ||
| vim.notify('[shelltime] Failed to parse version check response', vim.log.levels.DEBUG) | ||
| end | ||
| if callback then | ||
| callback(nil, 'Parse error') | ||
| end | ||
| end | ||
| end) | ||
| end, | ||
| }) | ||
| end | ||
|
|
||
| --- Show update warning notification | ||
| ---@param current_version string Current version | ||
| ---@param latest_version string Latest available version | ||
| ---@param web_endpoint string Web endpoint for update command | ||
| function M.show_update_warning(current_version, latest_version, web_endpoint) | ||
| local update_command = 'curl -sSL ' .. web_endpoint .. '/i | bash' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Piping the output of |
||
| local message = string.format( | ||
| '[shelltime] CLI update available: %s -> %s\n\nRun: %s', | ||
| current_version, | ||
| latest_version, | ||
| update_command | ||
| ) | ||
|
|
||
| vim.notify(message, vim.log.levels.WARN) | ||
|
|
||
| -- Also copy to clipboard if available | ||
| if vim.fn.has('clipboard') == 1 then | ||
| vim.fn.setreg('+', update_command) | ||
| vim.notify('[shelltime] Update command copied to clipboard', vim.log.levels.INFO) | ||
| end | ||
| end | ||
|
|
||
| return M | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error returned from
socket.get_statusis not handled. If the call fails, the version check is silently skipped. While this prevents a crash, it would be beneficial to log the error when in debug mode to aid in troubleshooting connection issues. This would make the behavior consistent with other error handling in the codebase.