nvim-pack-lock-sync.lua
1 -- Sync nvim-pack-lock.json revisions to full SHAs from installed plugin dirs. 2 -- Run headlessly: nvim -l scripts/nvim-pack-lock-sync.lua 3 4 local pack_dir = (os.getenv('XDG_DATA_HOME') or (os.getenv('HOME') .. '/.local/share')) 5 .. '/nvim/site/pack/core/opt' 6 7 local script_dir = debug.getinfo(1, 'S').source:match('^@(.+)/[^/]+$') or '.' 8 local lockfile = script_dir .. '/../nvim/nvim-pack-lock.json' 9 10 local f = io.open(lockfile, 'r') 11 if not f then 12 io.stderr:write('Lockfile not found: ' .. lockfile .. '\n') 13 os.exit(1) 14 end 15 local raw = f:read('*a') 16 f:close() 17 18 local data = vim.fn.json_decode(raw) 19 if type(data) ~= 'table' or type(data.plugins) ~= 'table' then 20 io.stderr:write('Invalid lockfile format\n') 21 os.exit(1) 22 end 23 24 -- Normalize a remote URL to SSH (preferred), stripping .git suffix. 25 local function normalize_url(url) 26 url = url:gsub('%s+$', '') 27 url = url:gsub('^https://github%.com/', 'ssh://git@github.com/') 28 url = url:gsub('^git@github%.com:', 'ssh://git@github.com/') 29 url = url:gsub('%.git$', '') 30 return url 31 end 32 33 local updated = 0 34 for name, entry in pairs(data.plugins) do 35 local plugin_dir = pack_dir .. '/' .. name 36 37 local sha = vim.fn.system({ 'git', '-C', plugin_dir, 'rev-parse', 'HEAD' }) 38 sha = sha:gsub('%s+$', '') 39 if vim.v.shell_error == 0 and sha ~= '' and entry.rev ~= sha then 40 entry.rev = sha 41 updated = updated + 1 42 end 43 44 local remote = vim.fn.system({ 'git', '-C', plugin_dir, 'remote', 'get-url', 'origin' }) 45 if vim.v.shell_error == 0 then 46 local normalized = normalize_url(remote) 47 if normalized ~= '' and entry.src ~= normalized then 48 entry.src = normalized 49 updated = updated + 1 50 end 51 end 52 end 53 54 local out = vim.fn.json_encode(data) 55 -- json_encode produces compact JSON; pretty-print with 2-space indent via gsub is fragile, 56 -- so write then reformat with a second pass using vim.fn. 57 -- Simpler: write compact and let the file be compact (lockfiles don't need to be pretty). 58 -- But to keep the existing style, use a basic pretty-printer. 59 local function pretty(val, indent) 60 indent = indent or 0 61 local pad = string.rep(' ', indent) 62 local t = type(val) 63 if t == 'string' then 64 return vim.fn.json_encode(val) 65 elseif t == 'number' or t == 'boolean' then 66 return tostring(val) 67 elseif t == 'table' then 68 local is_array = #val > 0 69 local items = {} 70 if is_array then 71 for _, v in ipairs(val) do 72 table.insert(items, pad .. ' ' .. pretty(v, indent + 1)) 73 end 74 return '[\n' .. table.concat(items, ',\n') .. '\n' .. pad .. ']' 75 else 76 local keys = {} 77 for k in pairs(val) do table.insert(keys, k) end 78 table.sort(keys) 79 for _, k in ipairs(keys) do 80 table.insert(items, pad .. ' ' .. vim.fn.json_encode(k) .. ': ' .. pretty(val[k], indent + 1)) 81 end 82 return '{\n' .. table.concat(items, ',\n') .. '\n' .. pad .. '}' 83 end 84 end 85 return 'null' 86 end 87 88 local result = pretty(data) .. '\n' 89 local wf = io.open(lockfile, 'w') 90 if not wf then 91 io.stderr:write('Cannot write lockfile: ' .. lockfile .. '\n') 92 os.exit(1) 93 end 94 wf:write(result) 95 wf:close() 96 97 print(string.format('Done. Updated %d plugin revisions in %s', updated, lockfile))