|
1 | 1 | #!/usr/bin/env crystal |
2 | 2 | # noir/scripts/check_version_consistency.cr |
3 | | -# Check version consistency across multiple files using shard.yml as source of truth |
| 3 | +# Check version consistency across multiple files using shard.yml as source of truth. |
4 | 4 | # |
5 | 5 | # Usage: |
6 | 6 | # crystal run scripts/check_version_consistency.cr |
|
9 | 9 | # Exit codes: |
10 | 10 | # 0 - All versions match |
11 | 11 | # 1 - Version mismatches detected |
12 | | -# 2 - Error reading files or invalid format |
13 | 12 |
|
14 | | -require "yaml" |
| 13 | +require "./version_common" |
15 | 14 |
|
16 | | -class VersionChecker |
17 | | - getter shard_version : String |
18 | | - getter? quiet : Bool |
| 15 | +entries = collect_versions |
19 | 16 |
|
20 | | - struct CheckResult |
21 | | - getter file_path : String |
22 | | - getter pattern : String |
23 | | - getter expected : String |
24 | | - getter actual : String? |
25 | | - getter? matches : Bool |
| 17 | +label_width = entries.map { |label, _, _| label.size }.max |
26 | 18 |
|
27 | | - def initialize(@file_path : String, @pattern : String, @expected : String, @actual : String?, @matches : Bool) |
28 | | - end |
29 | | - end |
30 | | - |
31 | | - def initialize(@quiet : Bool = false) |
32 | | - @shard_version = read_shard_version |
33 | | - end |
34 | | - |
35 | | - def run : Int32 |
36 | | - puts "Checking version consistency across files..." unless quiet? |
37 | | - puts "Source of truth: shard.yml version = #{@shard_version}" unless quiet? |
38 | | - puts unless quiet? |
39 | | - |
40 | | - results = [] of CheckResult |
41 | | - |
42 | | - # Check each file |
43 | | - results << check_flake_nix |
44 | | - results << check_dockerfile |
45 | | - results << check_noir_cr |
46 | | - results << check_sarif_cr |
47 | | - results << check_snapcraft_yaml |
48 | | - results << check_docs_index_md |
49 | | - results << check_docs_index_ko_md |
50 | | - results << check_github_action_dockerfile |
51 | | - results << check_github_action_readme |
52 | | - results << check_sarif_spec |
53 | | - results << check_copilot_instructions |
54 | | - results << check_how_to_release_md |
55 | | - results << check_how_to_release_ko_md |
56 | | - |
57 | | - # Print results |
58 | | - mismatches = [] of CheckResult |
59 | | - results.each do |result| |
60 | | - if result.matches? |
61 | | - puts "✅ #{result.file_path}" unless quiet? |
62 | | - else |
63 | | - puts "❌ #{result.file_path}" unless quiet? |
64 | | - puts " Expected: #{result.expected}" unless quiet? |
65 | | - puts " Found: #{result.actual || "NOT FOUND"}" unless quiet? |
66 | | - mismatches << result |
67 | | - end |
68 | | - end |
69 | | - |
70 | | - puts unless quiet? |
71 | | - if mismatches.empty? |
72 | | - puts "🎉 All versions match! (#{@shard_version})" unless quiet? |
73 | | - 0 |
74 | | - else |
75 | | - puts "❌ Version mismatch detected in #{mismatches.size} file(s):" unless quiet? |
76 | | - mismatches.each do |r| |
77 | | - puts " - #{r.file_path}" unless quiet? |
78 | | - end |
79 | | - 1 |
80 | | - end |
81 | | - end |
| 19 | +puts "Current versions:" |
| 20 | +entries.each do |label, _, version| |
| 21 | + puts " #{label.ljust(label_width)} #{version || "Not found"}" |
| 22 | +end |
| 23 | +puts |
82 | 24 |
|
83 | | - private def read_shard_version : String |
84 | | - shard_yml = YAML.parse(File.read("shard.yml")) |
85 | | - shard_yml["version"].as_s |
86 | | - rescue ex |
87 | | - STDERR.puts "Error reading version from shard.yml: #{ex.message}" |
88 | | - exit 2 |
89 | | - end |
| 25 | +versions = entries.map { |_, _, v| v }.compact |
| 26 | +if versions.empty? |
| 27 | + puts "No versions found!" |
| 28 | + exit 1 |
| 29 | +end |
90 | 30 |
|
91 | | - private def check_file(file_path : String, pattern : Regex, expected_version : String) : CheckResult |
92 | | - if File.exists?(file_path) |
93 | | - content = File.read(file_path) |
94 | | - if match = content.match(pattern) |
95 | | - actual = match[1] |
96 | | - CheckResult.new(file_path, pattern.source, expected_version, actual, actual == expected_version) |
97 | | - else |
98 | | - CheckResult.new(file_path, pattern.source, expected_version, nil, false) |
| 31 | +unique = versions.uniq |
| 32 | +if unique.size == 1 |
| 33 | + puts "✅ All versions match: #{unique.first}" |
| 34 | + exit 0 |
| 35 | +else |
| 36 | + puts "❌ Versions do not match!" |
| 37 | + puts " Unique versions found: #{unique.join(", ")}" |
| 38 | + puts |
| 39 | + shard_v = get_shard_version |
| 40 | + if shard_v |
| 41 | + puts " Source of truth (shard.yml): #{shard_v}" |
| 42 | + mismatches = entries.reject { |_, _, v| v.nil? || v == shard_v } |
| 43 | + unless mismatches.empty? |
| 44 | + puts " Mismatched files:" |
| 45 | + mismatches.each do |label, _, v| |
| 46 | + puts " - #{label} (#{v})" |
99 | 47 | end |
100 | | - else |
101 | | - CheckResult.new(file_path, pattern.source, expected_version, nil, false) |
102 | 48 | end |
103 | | - rescue ex |
104 | | - STDERR.puts "Error checking #{file_path}: #{ex.message}" |
105 | | - CheckResult.new(file_path, pattern.source, expected_version, nil, false) |
106 | | - end |
107 | | - |
108 | | - private def check_flake_nix : CheckResult |
109 | | - check_file("flake.nix", /version\s*=\s*"([^"]+)"/, @shard_version) |
110 | | - end |
111 | | - |
112 | | - private def check_dockerfile : CheckResult |
113 | | - check_file("Dockerfile", /org\.opencontainers\.image\.version="([^"]+)"/, @shard_version) |
114 | | - end |
115 | | - |
116 | | - private def check_noir_cr : CheckResult |
117 | | - check_file("src/noir.cr", /VERSION\s*=\s*"([^"]+)"/, @shard_version) |
118 | | - end |
119 | | - |
120 | | - private def check_sarif_cr : CheckResult |
121 | | - # SARIF output now uses Noir::VERSION constant, no hardcoded version to check. |
122 | | - # Version consistency is ensured by check_noir_cr via src/noir/version.cr. |
123 | | - CheckResult.new("src/output_builder/sarif.cr", "Noir::VERSION (indirect)", @shard_version, @shard_version, true) |
124 | 49 | end |
125 | | - |
126 | | - private def check_snapcraft_yaml : CheckResult |
127 | | - check_file("snap/snapcraft.yaml", /^version:\s*([\d.]+)\s*$/m, @shard_version) |
128 | | - end |
129 | | - |
130 | | - private def check_docs_index_file(file_path : String) : CheckResult |
131 | | - # Check hero-badge version in documentation index files |
132 | | - check_file(file_path, /class="hero-badge">v([\d.]+)</, @shard_version) |
133 | | - end |
134 | | - |
135 | | - private def check_docs_index_md : CheckResult |
136 | | - check_docs_index_file("docs/content/_index.md") |
137 | | - end |
138 | | - |
139 | | - private def check_docs_index_ko_md : CheckResult |
140 | | - check_docs_index_file("docs/content/_index.ko.md") |
141 | | - end |
142 | | - |
143 | | - private def check_github_action_dockerfile : CheckResult |
144 | | - check_file("github-action/Dockerfile", /FROM\s+ghcr\.io\/owasp-noir\/noir:v([^\s]+)/, @shard_version) |
145 | | - end |
146 | | - |
147 | | - private def check_github_action_readme : CheckResult |
148 | | - check_file("github-action/README.md", /uses:\s+owasp-noir\/noir@v([\d.]+)/, @shard_version) |
149 | | - end |
150 | | - |
151 | | - private def check_sarif_spec : CheckResult |
152 | | - # SARIF spec now uses Noir::VERSION constant, no hardcoded version to check. |
153 | | - # Version consistency is ensured by check_noir_cr via src/noir/version.cr. |
154 | | - CheckResult.new("spec/unit_test/output_builder/sarif_spec.cr", "Noir::VERSION (indirect)", @shard_version, @shard_version, true) |
155 | | - end |
156 | | - |
157 | | - private def check_copilot_instructions : CheckResult |
158 | | - # AGENTS.md no longer contains a hardcoded version string. |
159 | | - # Version consistency is maintained through shard.yml only. |
160 | | - CheckResult.new("AGENTS.md", "N/A (no version)", @shard_version, @shard_version, true) |
161 | | - end |
162 | | - |
163 | | - private def check_how_to_release_md : CheckResult |
164 | | - # Check for example version in brew command |
165 | | - check_file("docs/content/development/how_to_release/index.md", /brew bump-formula-pr --strict --version\s+([\d.]+)\s+noir/, @shard_version) |
166 | | - end |
167 | | - |
168 | | - private def check_how_to_release_ko_md : CheckResult |
169 | | - # Check for example version in brew command |
170 | | - check_file("docs/content/development/how_to_release/index.ko.md", /brew bump-formula-pr --strict --version\s+([\d.]+)\s+noir/, @shard_version) |
171 | | - end |
172 | | -end |
173 | | - |
174 | | -# Entry point |
175 | | -quiet = ARGV.includes?("-q") || ARGV.includes?("--quiet") |
176 | | -show_help = ARGV.includes?("-h") || ARGV.includes?("--help") |
177 | | - |
178 | | -if show_help |
179 | | - puts "Usage: crystal run scripts/check_version_consistency.cr [options]" |
180 | | - puts "" |
181 | | - puts "Options:" |
182 | | - puts " -q, --quiet Suppress detailed output" |
183 | | - puts " -h, --help Show this help message" |
184 | | - puts "" |
185 | | - puts "Description:" |
186 | | - puts " Checks version consistency across all files using shard.yml as source of truth." |
187 | | - puts "" |
188 | | - puts "Exit codes:" |
189 | | - puts " 0 - All versions match" |
190 | | - puts " 1 - Version mismatches detected" |
191 | | - puts " 2 - Error reading files" |
192 | | - exit 0 |
| 50 | + exit 1 |
193 | 51 | end |
194 | | - |
195 | | -checker = VersionChecker.new(quiet) |
196 | | -exit_code = checker.run |
197 | | -exit(exit_code) |
0 commit comments