Skip to content

Commit 6186cfa

Browse files
Claudeclaude
andcommitted
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 <[email protected]>
1 parent 68d704d commit 6186cfa

File tree

4 files changed

+480
-7
lines changed

4 files changed

+480
-7
lines changed

.luacheckrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ exclude_files = {
3535
"tests/plenary/*",
3636
}
3737

38+
-- Special configuration for scripts
39+
files["scripts/**/*.lua"] = {
40+
globals = {
41+
"print", "arg",
42+
},
43+
}
44+
3845
-- Special configuration for test files
3946
files["tests/**/*.lua"] = {
4047
-- Allow common globals used in testing

lua/laravel-helper/version.lua

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

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

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

2630
return M

scripts/version_bump.lua

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

0 commit comments

Comments
 (0)