Skip to content

Commit 86ee3b3

Browse files
committed
Enhance: Add version management and standardize badge display
- Remove redundant text version in README header - Keep standardized version badge style - Add version checking scripts for project consistency - Integrate with version management system - Update luacheckrc to allow globals in scripts - Fix variable shadowing in version_bump.lua script 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3bf053f commit 86ee3b3

4 files changed

Lines changed: 505 additions & 7 deletions

File tree

.luacheckrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ exclude_files = {
3131
"tests/plenary/*",
3232
}
3333

34+
-- Special configuration for scripts
35+
files["scripts/**/*.lua"] = {
36+
globals = {
37+
"print", "arg",
38+
},
39+
}
40+
3441
-- Special configuration for test files
3542
files["tests/**/*.lua"] = {
3643
-- Allow common globals used in testing

lua/claude-code/version.lua

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@
1010
--- @field patch number Patch version (bug fixes)
1111
--- @field string function Returns formatted version string
1212

13-
local M = {
14-
major = 0,
15-
minor = 4,
16-
patch = 2,
17-
}
13+
local M = {}
1814

19-
--- Returns the formatted version string
15+
-- Individual version components
16+
M.major = 0
17+
M.minor = 4
18+
M.patch = 2
19+
20+
-- Combined semantic version
21+
M.version = string.format('%d.%d.%d', M.major, M.minor, M.patch)
22+
23+
--- Returns the formatted version string (for backward compatibility)
2024
--- @return string Version string in format "major.minor.patch"
2125
function M.string()
22-
return string.format('%d.%d.%d', M.major, M.minor, M.patch)
26+
return M.version
2327
end
2428

2529
--- Prints the current version of the plugin

scripts/version_bump.lua

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
#!/usr/bin/env lua
2+
-- Version Bump Script
3+
-- Updates version across all project files
4+
5+
-- Configuration
6+
local config = {
7+
-- Known files that should contain version information
8+
version_files = {
9+
-- Main source of truth
10+
{
11+
path = 'lua/%s/version.lua',
12+
pattern = 'M.major = (%d+).-M.minor = (%d+).-M.patch = (%d+)',
13+
replacement = function(new_version)
14+
local major, minor, patch = new_version:match('(%d+)%.(%d+)%.(%d+)')
15+
return string.format('M.major = %s\nM.minor = %s\nM.patch = %s', major, minor, patch)
16+
end,
17+
complex = true,
18+
},
19+
-- Documentation files
20+
{ path = 'README.md', pattern = 'Version: v([%d%.]+)', replacement = 'Version: v%s' },
21+
{
22+
path = 'CHANGELOG.md',
23+
pattern = '## %[Unreleased%]',
24+
replacement = '## [Unreleased]\n\n## [%s] - %s',
25+
},
26+
-- Optional source files
27+
{
28+
path = 'lua/%s/init.lua',
29+
pattern = 'version = "([%d%.]+)"',
30+
replacement = 'version = "%s"',
31+
},
32+
{ path = 'lua/%s.lua', pattern = 'version = "([%d%.]+)"', replacement = 'version = "%s"' },
33+
-- Package files
34+
{ path = '%s.rockspec', pattern = 'version = "([%d%.]+)"', replacement = 'version = "%s"' },
35+
{
36+
path = 'package.json',
37+
pattern = '"version": "([%d%.]+)"',
38+
replacement = '"version": "%s"',
39+
},
40+
},
41+
}
42+
43+
-- Get the project name from the script argument or from the current directory
44+
local project_name = arg[1]
45+
if not project_name then
46+
local current_dir = io.popen('basename `pwd`'):read('*l')
47+
project_name = current_dir:gsub('%-', '_')
48+
end
49+
50+
-- Get the new version from the command line
51+
local new_version = arg[2]
52+
if not new_version then
53+
print('Usage: lua version_bump.lua [project_name] <new_version>')
54+
print('Example: lua version_bump.lua 1.2.3')
55+
os.exit(1)
56+
end
57+
58+
-- Validate version format
59+
if not new_version:match('^%d+%.%d+%.%d+$') then
60+
print('ERROR: Version must be in the format X.Y.Z (e.g., 1.2.3)')
61+
os.exit(1)
62+
end
63+
64+
-- Get the current date for CHANGELOG updates
65+
local current_date = os.date('%Y-%m-%d')
66+
67+
-- Function to read a file's content
68+
local function read_file(path)
69+
local file, err = io.open(path, 'r')
70+
if not file then
71+
return nil, err
72+
end
73+
local content = file:read('*a')
74+
file:close()
75+
return content
76+
end
77+
78+
-- Function to write content to a file
79+
local function write_file(path, content)
80+
local file, err = io.open(path, 'w')
81+
if not file then
82+
return false, err
83+
end
84+
file:write(content)
85+
file:close()
86+
return true
87+
end
88+
89+
-- Function to extract version from file using pattern
90+
local function extract_version(path, pattern)
91+
local content, err = read_file(path)
92+
if not content then
93+
return nil, 'Could not read ' .. path .. ': ' .. tostring(err)
94+
end
95+
96+
-- Handle patterns that return multiple captures (like the structured version.lua)
97+
local major, minor, patch = content:match(pattern)
98+
if major and minor and patch then
99+
-- This is a structured version with multiple components
100+
return major .. '.' .. minor .. '.' .. patch
101+
end
102+
103+
-- Regular single capture pattern
104+
local version = content:match(pattern)
105+
return version
106+
end
107+
108+
-- Format path with project name
109+
local function format_path(path_template)
110+
return path_template:format(project_name)
111+
end
112+
113+
-- Check if a file exists
114+
local function file_exists(path)
115+
local file = io.open(path, 'r')
116+
if file then
117+
file:close()
118+
return true
119+
end
120+
return false
121+
end
122+
123+
-- Update the version in a file
124+
local function update_version_in_file(file_config, version_string)
125+
local path = format_path(file_config.path)
126+
127+
if not file_exists(path) then
128+
print('⚠️ File not found, skipping: ' .. path)
129+
return true
130+
end
131+
132+
local content, err = read_file(path)
133+
if not content then
134+
print('❌ Error reading file: ' .. path .. ' - ' .. tostring(err))
135+
return false
136+
end
137+
138+
-- Special handling for CHANGELOG.md
139+
if path:match('CHANGELOG.md$') then
140+
-- Check if [Unreleased] section exists
141+
if not content:match('## %[Unreleased%]') then
142+
print('❌ CHANGELOG.md does not have an [Unreleased] section. Please add one.')
143+
return false
144+
end
145+
146+
-- Ensure [Unreleased] has content for the new version
147+
if content:match('## %[Unreleased%]%s*\n\n## ') then
148+
print('⚠️ Warning: [Unreleased] section in CHANGELOG.md appears to be empty.')
149+
end
150+
151+
-- Replace the Unreleased section header to add the new version
152+
local new_content = content:gsub(
153+
'## %[Unreleased%]',
154+
string.format('## [Unreleased]\n\n## [%s] - %s', new_version, current_date)
155+
)
156+
157+
-- Update comparison links at the bottom
158+
local old_version = extract_version(path, '## %[([%d%.]+)%]')
159+
if old_version then
160+
-- Ensure the template URL exists
161+
if content:match('%[Unreleased%]: .+/compare/v[%d%.]+%.%.%.HEAD') then
162+
-- Update existing comparison links
163+
new_content = new_content:gsub(
164+
'%[Unreleased%]: (.+)/compare/v[%d%.]+%.%.%.HEAD',
165+
string.format('[Unreleased]: %%1/compare/v%s...HEAD', new_version)
166+
)
167+
new_content = new_content:gsub(
168+
'%[' .. old_version .. '%]: .+/compare/v.-%.%.%.v' .. old_version,
169+
string.format(
170+
'[%s]: %%1/compare/v%s...v%s',
171+
old_version,
172+
old_version:match('^%d+%.%d+%.%d+'),
173+
old_version
174+
)
175+
)
176+
177+
-- Add new version comparison link
178+
new_content = new_content:gsub(
179+
'%[Unreleased%]: (.+)/compare/v' .. new_version .. '%.%.%.HEAD',
180+
string.format(
181+
'[Unreleased]: %%1/compare/v%s...HEAD\n[%s]: %%1/compare/v%s...v%s',
182+
new_version,
183+
new_version,
184+
old_version,
185+
new_version
186+
)
187+
)
188+
end
189+
end
190+
191+
local success, write_err = write_file(path, new_content)
192+
if not success then
193+
print('❌ Error writing file: ' .. path .. ' - ' .. tostring(write_err))
194+
return false
195+
end
196+
197+
print('✅ Updated version in: ' .. path)
198+
return true
199+
else
200+
-- Standard replacement for other files
201+
local old_version = extract_version(path, file_config.pattern)
202+
if not old_version then
203+
print('⚠️ Could not find version pattern in: ' .. path)
204+
return true -- Not a fatal error
205+
end
206+
207+
local new_content
208+
if file_config.complex then
209+
-- Use a function-based replacement for complex patterns
210+
if type(file_config.replacement) == 'function' then
211+
-- For structured version files like version.lua
212+
local replacement_text = file_config.replacement(new_version)
213+
new_content = content:gsub(file_config.pattern, replacement_text)
214+
else
215+
print('❌ Complex replacement specified but no function provided for: ' .. path)
216+
return false
217+
end
218+
else
219+
-- Simple string replacement
220+
local replacement = string.format(file_config.replacement, new_version)
221+
local pattern_escaped =
222+
file_config.pattern:gsub('%(', '%%('):gsub('%)', '%%)'):gsub('%%', '%%%%')
223+
new_content = content:gsub(pattern_escaped, replacement)
224+
end
225+
226+
if new_content == content then
227+
print('⚠️ No changes made to: ' .. path)
228+
return true
229+
end
230+
231+
local success, write_err = write_file(path, new_content)
232+
if not success then
233+
print('❌ Error writing file: ' .. path .. ' - ' .. tostring(write_err))
234+
return false
235+
end
236+
237+
print('✅ Updated version ' .. old_version .. '' .. new_version .. ' in: ' .. path)
238+
return true
239+
end
240+
end
241+
242+
-- Main function to update all versions
243+
local function bump_version(version_to_apply)
244+
print('Bumping version to: ' .. version_to_apply)
245+
246+
local all_success = true
247+
248+
-- First, update the canonical version
249+
local version_file_config = config.version_files[1]
250+
local version_file_path = format_path(version_file_config.path)
251+
252+
if not file_exists(version_file_path) then
253+
print('❌ Canonical version file not found: ' .. version_file_path)
254+
255+
-- Ask if we should create it
256+
io.write('Would you like to create it? (y/n): ')
257+
local answer = io.read()
258+
if answer:lower() == 'y' or answer:lower() == 'yes' then
259+
-- Get the directory path
260+
local dir_path = version_file_path:match('(.+)/[^/]+$')
261+
if dir_path then
262+
os.execute('mkdir -p ' .. dir_path)
263+
write_file(version_file_path, string.format('return "%s"', version_to_apply))
264+
print('✅ Created version file: ' .. version_file_path)
265+
else
266+
print('❌ Could not determine directory path for: ' .. version_file_path)
267+
return false
268+
end
269+
else
270+
return false
271+
end
272+
end
273+
274+
-- Update each file
275+
for _, file_config in ipairs(config.version_files) do
276+
local success = update_version_in_file(file_config, version_to_apply)
277+
if not success then
278+
all_success = false
279+
end
280+
end
281+
282+
if all_success then
283+
print('\n🎉 Version bumped to ' .. new_version .. ' successfully!')
284+
print('\nRemember to:')
285+
print('1. Review the changes, especially in CHANGELOG.md')
286+
print('2. Commit the changes: git commit -m "Release: Version ' .. new_version .. '"')
287+
print('3. Create a tag: git tag -a v' .. new_version .. ' -m "Version ' .. new_version .. '"')
288+
print('4. Push the changes: git push && git push --tags')
289+
return true
290+
else
291+
print('\n⚠️ Version bump completed with some errors.')
292+
return false
293+
end
294+
end
295+
296+
-- Run the version bump
297+
local success = bump_version(new_version)
298+
if not success then
299+
os.exit(1)
300+
end

0 commit comments

Comments
 (0)