Skip to content

Commit 0dd7b05

Browse files
committed
Refactor JSON validation and synchronization scripts
1 parent d7e2e91 commit 0dd7b05

File tree

7 files changed

+555
-5
lines changed

7 files changed

+555
-5
lines changed

.github/workflows/json-validation.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ jobs:
1515

1616
- name: Validate JSON files
1717
shell: pwsh
18-
run: ./scripts/validate-json.ps1
18+
run: ./scripts/checks/validate-json.ps1
1919

2020
- name: Check JSON Name alignment
2121
shell: pwsh
22-
run: ./scripts/check-name-alignment.ps1
22+
run: ./scripts/checks/check-name-alignment.ps1
23+
24+
- name: Check filename style
25+
shell: pwsh
26+
run: ./scripts/checks/check-filename-style.ps1
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
param(
2+
[switch]$Changed,
3+
[switch]$IncludeNonMaps,
4+
[ValidateRange(0, 8)]
5+
[int]$Precision = 3
6+
)
7+
8+
Set-StrictMode -Version Latest
9+
$ErrorActionPreference = "Stop"
10+
11+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
12+
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..")).Path
13+
14+
function Get-RepoRelativePath {
15+
param(
16+
[Parameter(Mandatory = $true)]
17+
[string]$Path
18+
)
19+
20+
$fullPath = (Resolve-Path $Path).Path
21+
if ($fullPath.StartsWith($repoRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
22+
return $fullPath.Substring($repoRoot.Length).TrimStart('\', '/')
23+
}
24+
25+
return $fullPath
26+
}
27+
28+
function Get-JsonFiles {
29+
param(
30+
[switch]$OnlyChanged
31+
)
32+
33+
Push-Location $repoRoot
34+
try {
35+
if (-not $OnlyChanged) {
36+
return @(Get-ChildItem -Path . -Recurse -File |
37+
Where-Object { $_.Extension -match '^\.(?i:json)$' } |
38+
Select-Object -ExpandProperty FullName)
39+
}
40+
41+
$changed = @()
42+
43+
$diffOutput = @(git diff --name-only --diff-filter=ACMR HEAD -- '*.json' 2>$null)
44+
if ($LASTEXITCODE -eq 0 -and $diffOutput) {
45+
$changed += $diffOutput
46+
}
47+
48+
$untrackedOutput = @(git ls-files --others --exclude-standard -- '*.json' 2>$null)
49+
if ($LASTEXITCODE -eq 0 -and $untrackedOutput) {
50+
$changed += $untrackedOutput
51+
}
52+
53+
return @($changed |
54+
Where-Object { $_ -and (Test-Path $_) } |
55+
Sort-Object -Unique |
56+
ForEach-Object { (Resolve-Path $_).Path })
57+
}
58+
finally {
59+
Pop-Location
60+
}
61+
}
62+
63+
function Test-NumericValue {
64+
param(
65+
[Parameter(Mandatory = $false)]
66+
$Value
67+
)
68+
69+
if ($null -eq $Value) {
70+
return $false
71+
}
72+
73+
if (
74+
$Value -is [byte] -or
75+
$Value -is [sbyte] -or
76+
$Value -is [int16] -or
77+
$Value -is [int32] -or
78+
$Value -is [int64] -or
79+
$Value -is [uint16] -or
80+
$Value -is [uint32] -or
81+
$Value -is [uint64] -or
82+
$Value -is [single] -or
83+
$Value -is [double] -or
84+
$Value -is [decimal]
85+
) {
86+
return $true
87+
}
88+
89+
$parsed = 0.0
90+
return [double]::TryParse(
91+
[string]$Value,
92+
[System.Globalization.NumberStyles]::Float,
93+
[System.Globalization.CultureInfo]::InvariantCulture,
94+
[ref]$parsed
95+
)
96+
}
97+
98+
function Normalize-CoordinateValue {
99+
param(
100+
[Parameter(Mandatory = $true)]
101+
[double]$Value,
102+
[Parameter(Mandatory = $true)]
103+
[int]$Digits
104+
)
105+
106+
$rounded = [Math]::Round($Value, $Digits, [MidpointRounding]::AwayFromZero)
107+
return $rounded.ToString("F$Digits", [System.Globalization.CultureInfo]::InvariantCulture)
108+
}
109+
110+
$files = @(Get-JsonFiles -OnlyChanged:$Changed)
111+
112+
if (-not $IncludeNonMaps) {
113+
$files = @($files | Where-Object {
114+
$rel = Get-RepoRelativePath -Path $_
115+
$rel -like "Maps\*" -or $rel -like "Maps/*"
116+
})
117+
}
118+
119+
if ($files.Count -eq 0) {
120+
if ($Changed) {
121+
Write-Host "No changed JSON files found."
122+
}
123+
else {
124+
Write-Host "No JSON files found."
125+
}
126+
exit 0
127+
}
128+
129+
$errorCount = 0
130+
$signatureGroups = @{}
131+
132+
foreach ($file in $files) {
133+
$relativePath = Get-RepoRelativePath -Path $file
134+
$json = $null
135+
136+
try {
137+
$json = Get-Content -Path $file -Raw -Encoding UTF8 | ConvertFrom-Json
138+
}
139+
catch {
140+
Write-Host "[ERROR] $relativePath : invalid JSON syntax. $($_.Exception.Message)"
141+
$errorCount++
142+
continue
143+
}
144+
145+
if ($json -isnot [pscustomobject]) {
146+
Write-Host "[ERROR] $relativePath : root must be a JSON object."
147+
$errorCount++
148+
continue
149+
}
150+
151+
if (-not ($json.PSObject.Properties.Name -contains "Coordinates") -or $null -eq $json.Coordinates) {
152+
Write-Host "[ERROR] $relativePath : missing 'Coordinates' (must be an array)."
153+
$errorCount++
154+
continue
155+
}
156+
157+
if ($json.Coordinates -is [string] -or $json.Coordinates -isnot [System.Collections.IEnumerable]) {
158+
Write-Host "[ERROR] $relativePath : 'Coordinates' must be an array."
159+
$errorCount++
160+
continue
161+
}
162+
163+
$normalizedPoints = New-Object System.Collections.Generic.List[string]
164+
$index = 0
165+
166+
foreach ($coordinate in $json.Coordinates) {
167+
if ($coordinate -isnot [pscustomobject]) {
168+
Write-Host "[ERROR] $relativePath : Coordinates[$index] must be an object."
169+
$errorCount++
170+
$index++
171+
continue
172+
}
173+
174+
foreach ($axis in @("X", "Y", "Z")) {
175+
if (-not ($coordinate.PSObject.Properties.Name -contains $axis) -or -not (Test-NumericValue -Value $coordinate.$axis)) {
176+
Write-Host "[ERROR] $relativePath : Coordinates[$index].$axis must be numeric."
177+
$errorCount++
178+
}
179+
}
180+
181+
if (
182+
(Test-NumericValue -Value $coordinate.X) -and
183+
(Test-NumericValue -Value $coordinate.Y) -and
184+
(Test-NumericValue -Value $coordinate.Z)
185+
) {
186+
$x = Normalize-CoordinateValue -Value ([double]$coordinate.X) -Digits $Precision
187+
$y = Normalize-CoordinateValue -Value ([double]$coordinate.Y) -Digits $Precision
188+
$z = Normalize-CoordinateValue -Value ([double]$coordinate.Z) -Digits $Precision
189+
$normalizedPoints.Add("$x|$y|$z")
190+
}
191+
192+
$index++
193+
}
194+
195+
if ($normalizedPoints.Count -eq 0) {
196+
continue
197+
}
198+
199+
$signatureText = ($normalizedPoints | Sort-Object -Unique) -join ";"
200+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($signatureText)
201+
$hashBytes = [System.Security.Cryptography.SHA256]::HashData($bytes)
202+
$hash = ([System.BitConverter]::ToString($hashBytes)).Replace("-", "")
203+
204+
if (-not $signatureGroups.ContainsKey($hash)) {
205+
$signatureGroups[$hash] = New-Object System.Collections.Generic.List[string]
206+
}
207+
208+
$signatureGroups[$hash].Add($relativePath)
209+
}
210+
211+
$duplicateGroups = @($signatureGroups.GetEnumerator() | Where-Object { $_.Value.Count -gt 1 })
212+
$duplicateFileCount = 0
213+
214+
foreach ($group in $duplicateGroups) {
215+
$paths = @($group.Value | Sort-Object)
216+
$duplicateFileCount += $paths.Count
217+
Write-Host "[DUPLICATE] Signature=$($group.Key) Count=$($paths.Count)"
218+
foreach ($path in $paths) {
219+
$folder = Split-Path -Path $path -Parent
220+
Write-Host " - $path"
221+
Write-Host " Folder: $folder"
222+
}
223+
}
224+
225+
Write-Host ""
226+
Write-Host "Scanned files: $($files.Count)"
227+
Write-Host "Duplicate groups: $($duplicateGroups.Count)"
228+
Write-Host "Files in duplicate groups: $duplicateFileCount"
229+
Write-Host "Errors: $errorCount"
230+
231+
if ($duplicateGroups.Count -gt 0 -or $errorCount -gt 0) {
232+
exit 1
233+
}
234+
235+
exit 0
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
param(
2+
[switch]$Changed,
3+
[switch]$IncludeNonMaps,
4+
[ValidateRange(1, 50)]
5+
[int]$TinyThreshold = 2,
6+
[switch]$FailOnTiny
7+
)
8+
9+
Set-StrictMode -Version Latest
10+
$ErrorActionPreference = "Stop"
11+
12+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
13+
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..")).Path
14+
15+
function Get-RepoRelativePath {
16+
param(
17+
[Parameter(Mandatory = $true)]
18+
[string]$Path
19+
)
20+
21+
$fullPath = (Resolve-Path $Path).Path
22+
if ($fullPath.StartsWith($repoRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
23+
return $fullPath.Substring($repoRoot.Length).TrimStart('\', '/')
24+
}
25+
26+
return $fullPath
27+
}
28+
29+
function Get-JsonFiles {
30+
param(
31+
[switch]$OnlyChanged
32+
)
33+
34+
Push-Location $repoRoot
35+
try {
36+
if (-not $OnlyChanged) {
37+
return @(Get-ChildItem -Path . -Recurse -File |
38+
Where-Object { $_.Extension -match '^\.(?i:json)$' } |
39+
Select-Object -ExpandProperty FullName)
40+
}
41+
42+
$changed = @()
43+
44+
$diffOutput = @(git diff --name-only --diff-filter=ACMR HEAD -- '*.json' 2>$null)
45+
if ($LASTEXITCODE -eq 0 -and $diffOutput) {
46+
$changed += $diffOutput
47+
}
48+
49+
$untrackedOutput = @(git ls-files --others --exclude-standard -- '*.json' 2>$null)
50+
if ($LASTEXITCODE -eq 0 -and $untrackedOutput) {
51+
$changed += $untrackedOutput
52+
}
53+
54+
return @($changed |
55+
Where-Object { $_ -and (Test-Path $_) } |
56+
Sort-Object -Unique |
57+
ForEach-Object { (Resolve-Path $_).Path })
58+
}
59+
finally {
60+
Pop-Location
61+
}
62+
}
63+
64+
$files = @(Get-JsonFiles -OnlyChanged:$Changed)
65+
66+
if (-not $IncludeNonMaps) {
67+
$files = @($files | Where-Object {
68+
$rel = Get-RepoRelativePath -Path $_
69+
$rel -like "Maps\*" -or $rel -like "Maps/*"
70+
})
71+
}
72+
73+
if ($files.Count -eq 0) {
74+
if ($Changed) {
75+
Write-Host "No changed JSON files found."
76+
}
77+
else {
78+
Write-Host "No JSON files found."
79+
}
80+
exit 0
81+
}
82+
83+
$errorCount = 0
84+
$emptyCount = 0
85+
$tinyCount = 0
86+
87+
foreach ($file in $files) {
88+
$relativePath = Get-RepoRelativePath -Path $file
89+
$json = $null
90+
91+
try {
92+
$json = Get-Content -Path $file -Raw -Encoding UTF8 | ConvertFrom-Json
93+
}
94+
catch {
95+
Write-Host "[ERROR] $relativePath : invalid JSON syntax. $($_.Exception.Message)"
96+
$errorCount++
97+
continue
98+
}
99+
100+
if ($json -isnot [pscustomobject]) {
101+
Write-Host "[ERROR] $relativePath : root must be a JSON object."
102+
$errorCount++
103+
continue
104+
}
105+
106+
if (-not ($json.PSObject.Properties.Name -contains "Coordinates") -or $null -eq $json.Coordinates) {
107+
Write-Host "[ERROR] $relativePath : missing 'Coordinates' (must be an array)."
108+
$errorCount++
109+
continue
110+
}
111+
112+
if ($json.Coordinates -is [string] -or $json.Coordinates -isnot [System.Collections.IEnumerable]) {
113+
Write-Host "[ERROR] $relativePath : 'Coordinates' must be an array."
114+
$errorCount++
115+
continue
116+
}
117+
118+
$coordinates = @($json.Coordinates)
119+
$count = $coordinates.Count
120+
121+
if ($count -eq 0) {
122+
Write-Host "[EMPTY] $relativePath : Coordinates count is 0."
123+
$emptyCount++
124+
continue
125+
}
126+
127+
if ($count -le $TinyThreshold) {
128+
Write-Host "[TINY] $relativePath : Coordinates count is $count (threshold=$TinyThreshold)."
129+
$tinyCount++
130+
}
131+
}
132+
133+
Write-Host ""
134+
Write-Host "Scanned files: $($files.Count)"
135+
Write-Host "Empty routes: $emptyCount"
136+
Write-Host "Tiny routes: $tinyCount (threshold=$TinyThreshold)"
137+
Write-Host "Errors: $errorCount"
138+
139+
if ($errorCount -gt 0 -or $emptyCount -gt 0) {
140+
exit 1
141+
}
142+
143+
if ($FailOnTiny -and $tinyCount -gt 0) {
144+
exit 1
145+
}
146+
147+
exit 0

0 commit comments

Comments
 (0)