diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..f52c73d05 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,16 @@ +# List of whitespace-only commits to ignore in the Git blame; +# to improve tracking changes and avoid noise +58db4e3419bf1e5cc1bb61fcd7ce2ebbca89243a +efb3338f5cf0eec21e8a75abc62ee14965cb4a7e +3859f6ffc088b2ae78748abc84986f4adcadcd41 +d6569192fc91167f555c3eff58402ff01f1197ea +67de97a492c9389f95499db38f9474a1c20ec585 +a0d085f93eaa69c22449d0217e8daf9eaea2b180 +1cfba25beb46c74bb1debca2bcfe7ac470e96172 +f6bc623284914489e891bbac923feb774c862b99 +abbab3f8b477e917d0a175d0de23cce121096631 +126347025f9cade241beff182738b2527da7535e +4740b836f300658b27e6ad4d79efac63c9c24c24 +be44bac95670b1cbbc91bd657882d985989846f9 +f67e5704eda60526d495be758572181f01a6cac8 +928bdfc3b80675e745a1a6642c3636e66d1c6071 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b13099618..f7ceac8ce 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,6 +42,10 @@ jobs: with: fetch-depth: 0 + - name: Check trailing whitespace + shell: pwsh + run: .\scripts\check-trailing-whitespace.ps1 + - name: Summary - Test execution started shell: pwsh run: | diff --git a/scripts/build.ps1 b/scripts/build.ps1 index d4ccb6711..cfce5fa8d 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -115,7 +115,7 @@ if (-not $noVendor) { # Kill ssh-agent.exe if it is running from the $env:cmder_root we are building foreach ($ssh_agent in $(Get-Process ssh-agent -ErrorAction SilentlyContinue)) { - if ([string]$($ssh_agent.path) -Match [string]$cmder_root.replace('\', '\\')) { + if ([string]$($ssh_agent.path) -match [string]$cmder_root.replace('\', '\\')) { Write-Verbose $("Stopping " + $ssh_agent.path + "!") Stop-Process $ssh_agent.id } @@ -178,4 +178,4 @@ if ( $Env:GITHUB_ACTIONS -eq 'true' ) { Write-Output "::notice title=Build Complete::Building Cmder v$version was successful." } -Write-Host -ForegroundColor green "All good and done!" +Write-ColorOutput -ForegroundColor Green -Message "All good and done!" diff --git a/scripts/check-trailing-whitespace.ps1 b/scripts/check-trailing-whitespace.ps1 new file mode 100644 index 000000000..efda28e1e --- /dev/null +++ b/scripts/check-trailing-whitespace.ps1 @@ -0,0 +1,56 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$extensions = @( + '.ps1', + '.psm1', + '.psd1', + '.cmd', + '.bat', + '.sh', + '.lua', + '.yml', + '.yaml', + '.json', + '.xml', + '.ini', + '.rc', + '.vcxproj', + '.filters', + '.props', + '.sln', + '.cpp', + '.c', + '.h', + '.hpp' +) + +$files = @( + git ls-files | + Where-Object { $extensions -contains [System.IO.Path]::GetExtension($_).ToLowerInvariant() } +) + +$findings = foreach ($file in $files) { + $lineNumber = 0 + foreach ($line in Get-Content -LiteralPath $file) { + $lineNumber++ + if ($line -match '[ \t]+$') { + [pscustomobject]@{ + File = $file + Line = $lineNumber + } + } + } +} + +if ($findings) { + Write-Error ( + "Trailing whitespace found:`n" + + (($findings | ForEach-Object { "$($_.File):$($_.Line)" }) -join "`n") + ) +} + +Write-Output "No trailing whitespace found in checked files." diff --git a/scripts/update.ps1 b/scripts/update.ps1 index 7da623a92..b511bf03e 100644 --- a/scripts/update.ps1 +++ b/scripts/update.ps1 @@ -39,23 +39,24 @@ Param( [switch]$IncludePrerelease = $false ) -# Get the root directory of the cmder project. -$cmder_root = Resolve-Path "$PSScriptRoot\.." - # Dot source util functions into this scope . "$PSScriptRoot\utils.ps1" $ErrorActionPreference = "Stop" # Attempts to match the current link with the new link, returning the count of matching characters. -function Match-Filenames { +function Compare-Filename { param ( - $url, - $downloadUrl, - $fromEnd + [Parameter(Mandatory = $true)] + [uri]$Url, + + [Parameter(Mandatory = $true)] + [string]$DownloadUrl, + + [bool]$FromEnd = $false ) - $filename = [System.IO.Path]::GetFileName($url) - $filenameDownload = [System.IO.Path]::GetFileName($downloadUrl) + $filename = [System.IO.Path]::GetFileName($Url) + $filenameDownload = [System.IO.Path]::GetFileName($DownloadUrl) $position = 0 @@ -63,7 +64,7 @@ function Match-Filenames { throw "Either one or both filenames are empty!" } - if ($fromEnd) { + if ($FromEnd) { $arr = $filename -split "" [array]::Reverse($arr) $filename = $arr -join '' @@ -229,14 +230,14 @@ function Fetch-DownloadUrl { return '' } - $temp = $downloadLinks | Where-Object { (Match-Filenames $url $_) -eq $charCount } + $temp = $downloadLinks | Where-Object { (Compare-Filename -Url $url -DownloadUrl $_) -eq $charCount } $downloadLinks = (New-Object System.Collections.Generic.List[System.Object]) $charCount = 0 foreach ($l in $temp) { - $score = Match-Filenames $url $l true + $score = Compare-Filename -Url $url -DownloadUrl $l -FromEnd $true if ( ($score -eq 0) -or ($score -lt $charCount) ) { continue @@ -245,7 +246,7 @@ function Fetch-DownloadUrl { $charCount = $score } - $downloadLinks = $temp | Where-Object { (Match-Filenames $url $_ true) -eq $charCount } + $downloadLinks = $temp | Where-Object { (Compare-Filename -Url $url -DownloadUrl $_ -FromEnd $true) -eq $charCount } if (($null -eq $downloadLinks) -or (-not $downloadLinks)) { throw "No suitable download links matched for the url!" @@ -307,7 +308,7 @@ foreach ($s in $sources) { # Analyze version change type using shared function $result = Get-VersionChangeType -OldVersion $s.version -NewVersion $version $changeType = $result.ChangeType - + # Determine if this is a breaking change if ($changeType -eq "downgrade" -or $changeType -eq "major") { $hasBreakingChanges = $true @@ -332,7 +333,7 @@ foreach ($s in $sources) { $sources | ConvertTo-Json | Set-Content $sourcesPath if ($count -eq 0) { - Write-Host -ForegroundColor yellow "No new releases were found." + Write-ColorOutput -ForegroundColor Yellow -Message "No new releases were found." return } @@ -348,4 +349,4 @@ if ($Env:APPVEYOR -eq 'True') { Add-AppveyorMessage -Message "Successfully updated $count dependencies." -Category Information } -Write-Host -ForegroundColor green "Successfully updated $count dependencies." +Write-ColorOutput -ForegroundColor Green -Message "Successfully updated $count dependencies." diff --git a/scripts/utils.ps1 b/scripts/utils.ps1 index 7c34b47c3..e0c107b92 100644 --- a/scripts/utils.ps1 +++ b/scripts/utils.ps1 @@ -1,4 +1,4 @@ -function Ensure-Exists($path) { +๏ปฟfunction Ensure-Exists($path) { if (-not (Test-Path $path)) { Write-Error "Missing required $path! Ensure it is installed" exit 1 @@ -22,6 +22,45 @@ function Ensure-Executable($command) { } } +function Write-ColorOutput { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', + '', + Justification = 'This helper intentionally writes user-facing host UI when scripts need colored console status messages.' + )] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyString()] + [string]$Message, + + [System.ConsoleColor]$ForegroundColor, + + [System.ConsoleColor]$BackgroundColor, + + [switch]$NoNewline + ) + + process { + $parameters = @{ + Object = $Message + } + + if ($PSBoundParameters.ContainsKey('ForegroundColor')) { + $parameters.ForegroundColor = $ForegroundColor + } + + if ($PSBoundParameters.ContainsKey('BackgroundColor')) { + $parameters.BackgroundColor = $BackgroundColor + } + + if ($NoNewline) { + $parameters.NoNewline = $true + } + + Microsoft.PowerShell.Utility\Write-Host @parameters + } +} + function Delete-Existing($path) { if (Test-Path $path) { Write-Verbose "Remove existing $path" @@ -70,6 +109,11 @@ function Digest-Hash($path) { } function Set-GHVariable { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', + '', + Justification = 'Writes GitHub Actions environment files in non-interactive CI; WhatIf/Confirm would not be meaningful.' + )] param( [Parameter(Mandatory = $true)] [string]$Name, @@ -255,17 +299,17 @@ function Format-FileSize { <# .SYNOPSIS Formats a file size in bytes to a human-readable string using binary units. - + .DESCRIPTION Converts file sizes to appropriate binary units (B, KiB, MiB, GiB) for better readability. - + .PARAMETER Bytes The file size in bytes to format. - + .EXAMPLE Format-FileSize -Bytes 1024 Returns "1.00 KiB" - + .EXAMPLE Format-FileSize -Bytes 15728640 Returns "15.00 MiB" @@ -274,7 +318,7 @@ function Format-FileSize { [Parameter(Mandatory = $true)] [double]$Bytes ) - + if ($Bytes -ge 1GB) { return "{0:N2} GiB" -f ($Bytes / 1GB) } elseif ($Bytes -ge 1MB) { @@ -290,27 +334,27 @@ function Get-VersionChangeType { <# .SYNOPSIS Analyzes version changes using semantic versioning to determine the type of update and appropriate emoji. - + .DESCRIPTION Parses old and new version strings, compares them using semantic versioning rules, and returns information about the change type, emoji indicator, and whether it's a major update. - + .PARAMETER OldVersion The previous version string (e.g., "1.2.3" or "2.52.0.windows.1"). - + .PARAMETER NewVersion The new version string (e.g., "1.3.0" or "3.0.0.windows.1"). - + .OUTPUTS Returns a hashtable with the following keys: - ChangeType: "major", "minor", "patch", "downgrade", or "unknown" - Emoji: The corresponding emoji indicator (๐Ÿ”ฅ, ๐Ÿš€, โฌ†๏ธ, or ๐Ÿ”„) - IsMajor: Boolean indicating if this is a major version update - + .EXAMPLE $result = Get-VersionChangeType -OldVersion "1.2.3" -NewVersion "2.0.0" # Returns: @{ ChangeType = "major"; Emoji = "๐Ÿ”ฅ"; IsMajor = $true } - + .EXAMPLE $result = Get-VersionChangeType -OldVersion "1.2.3" -NewVersion "1.3.0" # Returns: @{ ChangeType = "minor"; Emoji = "๐Ÿš€"; IsMajor = $false } @@ -318,32 +362,32 @@ function Get-VersionChangeType { param( [Parameter(Mandatory = $true)] [string]$OldVersion, - + [Parameter(Mandatory = $true)] [string]$NewVersion ) - + $changeType = "unknown" $emoji = "๐Ÿ”„" $isMajor = $false - + try { # Handle versions with more than 4 parts and strip pre-release identifiers $oldVerStr = $OldVersion.Split('-')[0] $newVerStr = $NewVersion.Split('-')[0] - + # Split by dots and take only numeric parts, first 4 max $oldParts = $oldVerStr.Split('.') | Where-Object { $_ -match '^\d+$' } | Select-Object -First 4 $newParts = $newVerStr.Split('.') | Where-Object { $_ -match '^\d+$' } | Select-Object -First 4 - + # Ensure we have at least 2 parts (major.minor) for semantic versioning if ($oldParts.Count -ge 2 -and $newParts.Count -ge 2) { $oldVerParseable = $oldParts -join '.' $newVerParseable = $newParts -join '.' - + $oldVer = [System.Version]::Parse($oldVerParseable) $newVer = [System.Version]::Parse($newVerParseable) - + if ($newVer -lt $oldVer) { $changeType = "downgrade" # Don't set emoji for downgrades in this function - caller handles it @@ -372,7 +416,7 @@ function Get-VersionChangeType { $emoji = "๐Ÿ”„" $isMajor = $false } - + return @{ ChangeType = $changeType Emoji = $emoji @@ -384,51 +428,51 @@ function Get-ArtifactDownloadUrl { <# .SYNOPSIS Retrieves the download URL for a GitHub Actions artifact with retry logic. - + .DESCRIPTION Uses the GitHub CLI to fetch artifact information from the GitHub API with automatic retries. Falls back to returning $null if all attempts fail. - + .PARAMETER ArtifactName The name of the artifact to retrieve the download URL for. - + .PARAMETER Repository The GitHub repository in the format "owner/repo". - + .PARAMETER RunId The GitHub Actions workflow run ID. - + .PARAMETER MaxRetries Maximum number of retry attempts. Default is 3. - + .PARAMETER DelaySeconds Delay in seconds between retry attempts. Default is 2. - + .EXAMPLE Get-ArtifactDownloadUrl -ArtifactName "cmder.zip" -Repository "cmderdev/cmder" -RunId "123456789" - + .EXAMPLE Get-ArtifactDownloadUrl -ArtifactName "build-output" -Repository "owner/repo" -RunId "987654321" -MaxRetries 5 -DelaySeconds 3 #> param( [Parameter(Mandatory = $true)] [string]$ArtifactName, - + [Parameter(Mandatory = $true)] [string]$Repository, - + [Parameter(Mandatory = $true)] [string]$RunId, - + [int]$MaxRetries = 3, [int]$DelaySeconds = 2 ) - + for ($i = 0; $i -lt $MaxRetries; $i++) { try { # Use GitHub CLI to get artifact information $artifactsJson = gh api "repos/$Repository/actions/runs/$RunId/artifacts" --jq ".artifacts[] | select(.name == `"$ArtifactName`")" - + if ($artifactsJson) { $artifact = $artifactsJson | ConvertFrom-Json if ($artifact.id) { @@ -438,13 +482,13 @@ function Get-ArtifactDownloadUrl { } } } catch { - Write-Host "Attempt $($i + 1) failed to get artifact URL for $ArtifactName : $_" + Write-Warning "Attempt $($i + 1) failed to get artifact URL for $ArtifactName : $_" } - + if ($i -lt ($MaxRetries - 1)) { Start-Sleep -Seconds $DelaySeconds } } - + return $null } diff --git a/vendor/bin/cexec.cmd b/vendor/bin/cexec.cmd index 755cfae07..2d9247b33 100644 --- a/vendor/bin/cexec.cmd +++ b/vendor/bin/cexec.cmd @@ -100,7 +100,7 @@ echo use a pair of double quotation marks to echo wrap your command to avoid exceed expectation. echo. echo parameters These are the parameters passed to the command/program. -echo It's recommended to use a pair of double quotation marks +echo It's recommended to use a pair of double quotation marks echo to wrap your flag name to avoid exceed expectation. echo. echo Examples: diff --git a/vendor/bin/cmder_diag.ps1 b/vendor/bin/cmder_diag.ps1 index e4ddb16af..c211c6412 100644 --- a/vendor/bin/cmder_diag.ps1 +++ b/vendor/bin/cmder_diag.ps1 @@ -1,5 +1,5 @@ -if (test-path $env:temp\cmder_diag_ps.log) { - remove-item $env:temp\cmder_diag_ps.log +if (Test-Path $env:temp\cmder_diag_ps.log) { + Remove-Item $env:temp\cmder_diag_ps.log } $cmder_diag = { @@ -7,19 +7,19 @@ $cmder_diag = { "------------------------------------" "get-childitem env:" "------------------------------------" -get-childitem env: | ft -autosize -wrap 2>&1 +Get-ChildItem env: | Format-Table -AutoSize -Wrap 2>&1 "" "------------------------------------" "get-command git -all -ErrorAction SilentlyContinue" "------------------------------------" -get-command git -all -ErrorAction SilentlyContinue +Get-Command git -All -ErrorAction SilentlyContinue "" "------------------------------------" "get-command clink -all -ErrorAction SilentlyContinue" "------------------------------------" -get-command clink -all -ErrorAction SilentlyContinue +Get-Command clink -All -ErrorAction SilentlyContinue "" "------------------------------------" @@ -30,25 +30,25 @@ systeminfo 2>&1 "------------------------------------" "get-childitem '$env:CMDER_ROOT'" "------------------------------------" -get-childitem "$env:CMDER_ROOT" |ft LastWriteTime,mode,length,FullName +Get-ChildItem "$env:CMDER_ROOT" | Format-Table LastWriteTime, mode, length, FullName "" "------------------------------------" "get-childitem '$env:CMDER_ROOT/vendor'" "------------------------------------" -get-childitem "$env:CMDER_ROOT/vendor" |ft LastWriteTime,mode,length,FullName +Get-ChildItem "$env:CMDER_ROOT/vendor" | Format-Table LastWriteTime, mode, length, FullName "" "------------------------------------" "get-childitem -s '$env:CMDER_ROOT/bin'" "------------------------------------" -get-childitem -s "$env:CMDER_ROOT/bin" |ft LastWriteTime,mode,length,FullName +Get-ChildItem -Recurse "$env:CMDER_ROOT/bin" | Format-Table LastWriteTime, mode, length, FullName "" "------------------------------------" "get-childitem -s '$env:CMDER_ROOT/config'" "------------------------------------" -get-childitem -s "$env:CMDER_ROOT/config" |ft LastWriteTime,mode,length,FullName +Get-ChildItem -Recurse "$env:CMDER_ROOT/config" | Format-Table LastWriteTime, mode, length, FullName "" "------------------------------------" @@ -56,9 +56,9 @@ get-childitem -s "$env:CMDER_ROOT/config" |ft LastWriteTime,mode,length,FullName "------------------------------------" } -& $cmder_diag | out-file -filePath $env:temp\cmder_diag_ps.log +& $cmder_diag | Out-File -FilePath $env:temp\cmder_diag_ps.log -get-content "$env:temp\cmder_diag_ps.log" +Get-Content "$env:temp\cmder_diag_ps.log" -write-host "" -write-host Above output was saved in "$env:temp\cmder_diag_ps.log" +Write-Output "" +Write-Output "Above output was saved in $env:temp\cmder_diag_ps.log" diff --git a/vendor/init.bat b/vendor/init.bat index 2eabb6ea8..bd5242f7b 100644 --- a/vendor/init.bat +++ b/vendor/init.bat @@ -222,7 +222,7 @@ goto :SKIP_CLINK :: Revert back to plain cmd.exe prompt without clink prompt $E[1;32;49m$P$S$_$E[1;30;49mฮป$S$E[0m - + :: Add Windows Terminal shell integration support (OSC 133 sequences) if defined WT_SESSION (prompt $e]133;D$e\$e]133;A$e\$e]9;9;$P$e\%PROMPT%$e]133;B$e\) diff --git a/vendor/profile.ps1 b/vendor/profile.ps1 index 7c49587f7..72d5e503d 100644 --- a/vendor/profile.ps1 +++ b/vendor/profile.ps1 @@ -125,7 +125,7 @@ if (Get-Module PSReadline -ErrorAction "SilentlyContinue") { # Emit OSC 133;C to mark start of command output # This is written directly to the console after the command is accepted - [Console]::Write("$([char]0x1B)]133;C$([char]7)") + $Host.UI.Write("$([char]0x1B)]133;C$([char]7)") } } } @@ -210,8 +210,7 @@ if ($ENV:CMDER_USER_CONFIG) { if (-not (Test-Path $CmderUserProfilePath)) { $CmderUserProfilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($CmderUserProfilePath) - Write-Host -NoNewline "`r" - Write-Host -BackgroundColor Green -ForegroundColor Black "First Run: Creating user startup file: $CmderUserProfilePath" + Write-Information -InformationAction Continue -MessageData "`rFirst Run: Creating user startup file: $CmderUserProfilePath" Copy-Item "$env:CMDER_ROOT\vendor\user_profile.ps1.default" -Destination $CmderUserProfilePath }