Skip to content

Commit 87e69ae

Browse files
committed
chore(workflow): add changelog and lint automation (2026.6.4.0-F383)
1 parent 3f6f2aa commit 87e69ae

43 files changed

Lines changed: 2169 additions & 955 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.clang-format

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
BasedOnStyle: LLVM
2+
IndentWidth: 4
3+
ColumnLimit: 100
4+
UseTab: Never
5+
SortIncludes: Never
6+
PointerAlignment: Right
7+
DerivePointerAlignment: false
8+
AllowShortFunctionsOnASingleLine: Empty
9+
AllowShortIfStatementsOnASingleLine: Never
10+
AllowShortLoopsOnASingleLine: false
11+
BreakBeforeBraces: Attach

.editorconfig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 4
9+
trim_trailing_whitespace = true
10+
11+
[*.{yml,yaml}]
12+
indent_size = 2
13+
14+
[*.md]
15+
trim_trailing_whitespace = false
16+
17+
[*.ps1]
18+
end_of_line = crlf
19+
indent_size = 4
20+
21+
[*.rs]
22+
indent_size = 4
23+
24+
[*.{c,h}]
25+
indent_size = 4

.gitattributes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
.gitattributes text eol=lf
44
.gitignore text eol=lf
5+
.clang-format text eol=lf
6+
.editorconfig text eol=lf
57
LICENSE text eol=lf
68
NOTICE text eol=lf
79
CMakeLists.txt text eol=lf
@@ -21,6 +23,8 @@ CMakeLists.txt text eol=lf
2123
*.cmake text eol=lf
2224
*.lock text eol=lf
2325

26+
.githooks/* text eol=lf
27+
2428
*.uf2 binary
2529
*.exe binary
2630
*.zip binary

.githooks/commit-msg

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
# Reject commit subjects with more than one build-version stamp.
3+
4+
set -euo pipefail
5+
6+
msg_file="$1"
7+
[ -f "$msg_file" ] || exit 0
8+
9+
subject="$(grep -m1 -vE '^(#|$)' "$msg_file" || true)"
10+
[ -n "$subject" ] || exit 0
11+
12+
count=$( ( printf '%s\n' "$subject" |
13+
grep -oE '\([0-9]{4}\.[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9]{4})?\)' ||
14+
true ) | grep -c . || true )
15+
count=${count//[^0-9]/}
16+
[ -n "$count" ] || count=0
17+
18+
if [ "${count:-0}" -gt 1 ]; then
19+
echo "" >&2
20+
echo "commit-msg: subject contains $count build-version stamps; expected at most one." >&2
21+
echo "" >&2
22+
echo " subject: $subject" >&2
23+
echo "" >&2
24+
echo "Drop stale (YYYY.M.D.N-XXXX) text from the subject and commit again." >&2
25+
echo "" >&2
26+
exit 1
27+
fi
28+
29+
exit 0

.githooks/prepare-commit-msg

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env bash
2+
# Append the current build version to new commit subjects.
3+
4+
set -eu
5+
6+
msg_file="$1"
7+
source="${2:-}"
8+
9+
case "$source" in
10+
merge|squash) exit 0 ;;
11+
esac
12+
13+
stamp_pattern='\([0-9]{4}\.[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9]{4})?\)'
14+
15+
if [ "$source" != "commit" ]; then
16+
subject="$(grep -m1 -vE '^(#|$)' "$msg_file" || true)"
17+
if printf '%s\n' "$subject" | grep -qE "$stamp_pattern"; then
18+
echo "" >&2
19+
echo "prepare-commit-msg: subject already contains a build-version stamp." >&2
20+
echo "" >&2
21+
echo " subject: $subject" >&2
22+
echo "" >&2
23+
echo "Let this hook append the current version from version.txt instead of copying" >&2
24+
echo "a stamp from an older commit subject." >&2
25+
echo "" >&2
26+
exit 1
27+
fi
28+
fi
29+
30+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
31+
[ -n "$repo_root" ] || exit 0
32+
33+
version_file="$repo_root/version.txt"
34+
version=""
35+
if [ -f "$version_file" ]; then
36+
version="$(tr -d '\r' < "$version_file" | sed 's/[^[:print:]]//g' | xargs)"
37+
fi
38+
39+
today="$(date +%Y.%-m.%-d)"
40+
if ! printf '%s\n' "$version" | grep -qE "^${today}\."; then
41+
short_head="$(git rev-parse --short=4 HEAD 2>/dev/null | tr 'a-z' 'A-Z')"
42+
[ -n "$short_head" ] || short_head="0000"
43+
version="${today}.0-${short_head}"
44+
printf '%s' "$version" > "$version_file"
45+
fi
46+
47+
[ -n "$version" ] || exit 0
48+
49+
if ! grep -qE "$stamp_pattern" "$msg_file"; then
50+
sed -i "1s/\$/ ($version)/" "$msg_file"
51+
fi
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env pwsh
2+
[CmdletBinding()]
3+
param()
4+
5+
$ErrorActionPreference = "Stop"
6+
7+
$repo = Join-Path ([System.IO.Path]::GetTempPath()) ("changelog-test-" + [System.Guid]::NewGuid().ToString("N"))
8+
$script = Join-Path $PSScriptRoot "Update-Changelog.ps1"
9+
10+
function Invoke-Git {
11+
param([Parameter(Mandatory = $true)][string[]]$Arguments)
12+
& git @Arguments | Out-Null
13+
if ($LASTEXITCODE -ne 0) {
14+
throw "git $($Arguments -join ' ') failed with exit code $LASTEXITCODE"
15+
}
16+
}
17+
18+
try {
19+
New-Item -ItemType Directory -Path $repo -Force | Out-Null
20+
Push-Location $repo
21+
22+
Invoke-Git @("init", "-q")
23+
Invoke-Git @("config", "user.email", "dev@example.com")
24+
Invoke-Git @("config", "user.name", "Dev")
25+
26+
$seedChangelog = @(
27+
"# Changelog",
28+
"",
29+
"## Unreleased",
30+
"",
31+
"_No notable changes since the last release._"
32+
) -join "`n"
33+
Set-Content -LiteralPath "CHANGELOG.md" -Value $seedChangelog -Encoding ASCII
34+
35+
Invoke-Git @("add", ".")
36+
Invoke-Git @("commit", "-m", "chore: base")
37+
38+
Set-Content -LiteralPath "sample.txt" -Value "one" -Encoding ASCII
39+
Invoke-Git @("add", ".")
40+
Invoke-Git @("commit", "-m", "feat(cli): add setup menu (2026.6.4.0-ABCD)")
41+
$first = (& git rev-parse HEAD).Trim()
42+
43+
Set-Content -LiteralPath "sample.txt" -Value "two" -Encoding ASCII
44+
Invoke-Git @("add", ".")
45+
Invoke-Git @("commit", "-m", "docs: update readme")
46+
$second = (& git rev-parse HEAD).Trim()
47+
48+
& $script -Mode Append -Range "$first^..$second" -RepoRoot $repo
49+
if ($LASTEXITCODE -ne 0) { throw "Append failed." }
50+
51+
$text = Get-Content -LiteralPath "CHANGELOG.md" -Raw
52+
if ($text -notmatch "### Added" -or $text -notmatch "\*\*cli:\*\* Add setup menu") {
53+
throw "Append did not add the expected feature entry."
54+
}
55+
if ($text -match "update readme") {
56+
throw "Append included a docs-only commit."
57+
}
58+
59+
& $script -Mode Promote -Version "v2026.6.4.0" -Repo "owner/repo" -RepoRoot $repo
60+
if ($LASTEXITCODE -ne 0) { throw "Promote failed." }
61+
62+
$text = Get-Content -LiteralPath "CHANGELOG.md" -Raw
63+
if ($text -notmatch "## \[v2026\.6\.4\.0\]") {
64+
throw "Promote did not create a versioned section."
65+
}
66+
67+
$notes = & $script -Mode Notes -ForVersion -Version "v2026.6.4.0" -RepoRoot $repo
68+
if ($notes -notmatch "Add setup menu") {
69+
throw "Notes did not return the promoted section."
70+
}
71+
72+
Write-Host "Update-Changelog tests passed."
73+
} finally {
74+
Pop-Location
75+
if (Test-Path -LiteralPath $repo) {
76+
Remove-Item -LiteralPath $repo -Recurse -Force
77+
}
78+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env pwsh
2+
[CmdletBinding()]
3+
param(
4+
[string]$Root = (Get-Location).Path
5+
)
6+
7+
$ErrorActionPreference = "Stop"
8+
9+
$errors = [System.Collections.Generic.List[string]]::new()
10+
11+
function Add-ParserErrors {
12+
param(
13+
[Parameter(Mandatory = $true)][string]$Source,
14+
[Parameter(Mandatory = $true)]$ParseErrors
15+
)
16+
17+
foreach ($parseError in $ParseErrors) {
18+
$line = $parseError.Extent.StartLineNumber
19+
$col = $parseError.Extent.StartColumnNumber
20+
$errors.Add("$Source (line ${line}:${col}): $($parseError.Message)") | Out-Null
21+
}
22+
}
23+
24+
Get-ChildItem -LiteralPath $Root -Filter "*.ps1" -Recurse |
25+
Where-Object {
26+
$_.FullName -notmatch "\\bridge\\target\\" -and
27+
$_.FullName -notmatch "\\dist\\" -and
28+
$_.FullName -notmatch "\\pico-bridge\\build"
29+
} |
30+
ForEach-Object {
31+
$parseErrors = $null
32+
[void][System.Management.Automation.Language.Parser]::ParseFile(
33+
$_.FullName,
34+
[ref]$null,
35+
[ref]$parseErrors
36+
)
37+
if ($parseErrors) {
38+
Add-ParserErrors -Source $_.FullName -ParseErrors $parseErrors
39+
}
40+
}
41+
42+
$workflowDir = Join-Path $Root ".github\workflows"
43+
if (Test-Path -LiteralPath $workflowDir) {
44+
$ghaPattern = [regex]"\$\{\{[^}]*\}\}"
45+
Get-ChildItem -LiteralPath $workflowDir -Filter "*.yml" | ForEach-Object {
46+
$path = $_.FullName
47+
$lines = Get-Content -LiteralPath $path
48+
$stepName = "<unnamed>"
49+
$isPwsh = $false
50+
$inRun = $false
51+
$runIndent = -1
52+
$runStart = 0
53+
$block = [System.Collections.Generic.List[string]]::new()
54+
55+
$flush = {
56+
if ($block.Count -eq 0) { return }
57+
$baseline = -1
58+
foreach ($line in $block) {
59+
if ($line.Trim().Length -eq 0) { continue }
60+
$baseline = $line.Length - $line.TrimStart(" ").Length
61+
break
62+
}
63+
if ($baseline -lt 0) { return }
64+
65+
$body = ($block | ForEach-Object {
66+
if ($_.Length -gt $baseline) { $_.Substring($baseline) } else { "" }
67+
}) -join "`n"
68+
$body = $ghaPattern.Replace($body, "__GHA_EXPR__")
69+
70+
$parseErrors = $null
71+
[void][System.Management.Automation.Language.Parser]::ParseInput(
72+
$body,
73+
[ref]$null,
74+
[ref]$parseErrors
75+
)
76+
if ($parseErrors) {
77+
Add-ParserErrors -Source "$path step '$stepName' (run: line $runStart)" -ParseErrors $parseErrors
78+
}
79+
}
80+
81+
for ($i = 0; $i -lt $lines.Count; $i++) {
82+
$line = $lines[$i]
83+
$indent = $line.Length - $line.TrimStart(" ").Length
84+
$trimmed = $line.Trim()
85+
86+
if ($inRun) {
87+
if ($trimmed.Length -gt 0 -and $indent -le $runIndent) {
88+
& $flush
89+
$block.Clear()
90+
$inRun = $false
91+
} else {
92+
$block.Add($line) | Out-Null
93+
continue
94+
}
95+
}
96+
97+
if ($trimmed -match "^- name:\s*(.+?)\s*$") {
98+
if ($block.Count -gt 0) {
99+
& $flush
100+
$block.Clear()
101+
$inRun = $false
102+
}
103+
$stepName = $matches[1]
104+
$isPwsh = $false
105+
continue
106+
}
107+
108+
if ($trimmed -match "^shell:\s*(.+?)\s*$") {
109+
$isPwsh = ($matches[1] -eq "pwsh")
110+
continue
111+
}
112+
113+
if ($isPwsh -and $trimmed -match "^run:\s*\|\s*$") {
114+
$inRun = $true
115+
$runIndent = $indent
116+
$runStart = $i + 1
117+
$block.Clear()
118+
continue
119+
}
120+
}
121+
if ($inRun) { & $flush }
122+
}
123+
}
124+
125+
if ($errors.Count -gt 0) {
126+
Write-Host "PowerShell syntax errors:"
127+
foreach ($message in $errors) {
128+
Write-Host " $message"
129+
}
130+
throw "Found $($errors.Count) PowerShell syntax error(s)."
131+
}
132+
133+
Write-Host "PowerShell scripts and inline workflow blocks parsed cleanly."

0 commit comments

Comments
 (0)