/ scripts / nvim-pack-lock-sync.lua
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))