Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# {{ if eq .chezmoi.os "windows" }}
#!/usr/bin/env pwsh
# Start the Windows Terminal PowerShell profile quietly.
#
# Two lines of noise appear before anything useful:
Expand All @@ -18,9 +18,25 @@
# As with the sibling default-profile script, Windows Terminal owns
# settings.json and every machine has a different, hand-tuned config, so only
# this one profile's commandline is touched.
#
# Deliberately NOT a .tmpl: the signing workflow skips *.ps1.tmpl (a rendered
# template no longer matches its signature), and this script needs no template
# input. The OS guard the sibling templates carry is redundant here because
# .chezmoiignore already drops .chezmoiscripts/windows/** on non-Windows, and
# the module path comes from CHEZMOI_SOURCE_DIR, which chezmoi exports to every
# script it runs.
$ErrorActionPreference = "Stop"

$modulePath = "{{ .chezmoi.sourceDir }}\dot_config\powershell\modules\DotfilesHelpers"
# CHEZMOI_SOURCE_DIR arrives with forward slashes (and sometimes in 8.3 form),
# so build the path with Join-Path rather than string concatenation. Falling
# back to the applied tree keeps the script usable when run by hand.
if ($env:CHEZMOI_SOURCE_DIR) {
$modulePath = Join-Path $env:CHEZMOI_SOURCE_DIR "dot_config/powershell/modules/DotfilesHelpers"
}
else {
$modulePath = Join-Path $HOME ".config/powershell/modules/DotfilesHelpers"
}

if (Test-Path $modulePath) {
Import-Module $modulePath -Force -DisableNameChecking
}
Expand All @@ -40,4 +56,3 @@ if (-not $results) {
elseif (-not ($results | Where-Object { $_.Status -in @('Updated', 'AlreadySet') })) {
Write-Host "[SKIP] PowerShell Core profile not found; leaving commandline unchanged" -ForegroundColor Yellow
}
# {{ end }}
12 changes: 9 additions & 3 deletions home/dot_config/fastfetch/status.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,16 @@ function Write-StatusSection {

# UTF-8 without BOM: `cmd /c type` copies the bytes straight through to
# fastfetch, so a BOM would show up as stray characters in the banner.
# The temp name is unique so a concurrent writer cannot claim it.
$encoding = New-Object System.Text.UTF8Encoding($false)
$tmp = "$file.tmp"
[System.IO.File]::WriteAllText($tmp, ($Value + "`r`n"), $encoding)
Move-Item -LiteralPath $tmp -Destination $file -Force
$tmp = '{0}.{1}.tmp' -f $file, [System.IO.Path]::GetRandomFileName()
try {
[System.IO.File]::WriteAllText($tmp, ($Value + "`r`n"), $encoding)
Move-Item -LiteralPath $tmp -Destination $file -Force
}
finally {
Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue
}
}

function Invoke-StatusRender {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,14 @@ function Update-FastfetchStatusCache {
}

# Drop rendered files whose source is gone (section no longer applies).
# Scoped deliberately narrowly: '.tmp' files belong to an in-flight write in
# this or another shell, and deleting one would break its Move-Item.
foreach ($stale in @(Get-ChildItem -LiteralPath $CacheDir -File -ErrorAction SilentlyContinue |
Where-Object { $_.Name -notlike '*.src' -and $_.Name -notlike '.*' })) {
Where-Object {
$_.Name -notlike '*.src' -and
$_.Name -notlike '*.tmp' -and
$_.Name -notlike '.*'
})) {
if (-not (Test-Path -LiteralPath "$($stale.FullName).src")) {
if ($PSCmdlet.ShouldProcess($stale.FullName, 'Remove orphaned fastfetch status section')) {
Remove-Item -LiteralPath $stale.FullName -Force -ErrorAction SilentlyContinue
Expand Down Expand Up @@ -344,9 +350,17 @@ function Write-FastfetchStatusFile {

$encoding = New-Object System.Text.UTF8Encoding($false)
$content = ($Lines -join "`r`n") + "`r`n"
$tmp = "$Path.tmp"
[System.IO.File]::WriteAllText($tmp, $content, $encoding)
Move-Item -LiteralPath $tmp -Destination $Path -Force
# Unique temp name: two shells starting at the same moment both render the
# same section, and a shared '<name>.tmp' would let one process's Move-Item
# pull the file out from under the other's.
$tmp = '{0}.{1}.tmp' -f $Path, [System.IO.Path]::GetRandomFileName()
try {
[System.IO.File]::WriteAllText($tmp, $content, $encoding)
Move-Item -LiteralPath $tmp -Destination $Path -Force
}
finally {
Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue
}
}

# SIG # Begin signature block
Expand Down
5 changes: 4 additions & 1 deletion home/dot_config/powershell/profile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ if ([Environment]::UserInteractive -and -not $env:CHEZMOI_SOURCE_DIR) {
if (-not $fastfetchProc.WaitForExit($fastfetchTimeoutMs)) {
try { $fastfetchProc.Kill() } catch { }
Write-Host "`n(fastfetch timed out after $([math]::Round($fastfetchTimeoutMs / 1000, 1))s; skipping)" -ForegroundColor DarkYellow
} else {
} elseif ($fastfetchProc.ExitCode -eq 0) {
# Only a clean run actually painted a banner. A non-zero exit
# (bad config, for instance) leaves the screen empty, so the
# welcome lines below stay as the one sign of life.
$script:_fastfetchShown = $true
}
} catch {
Expand Down
23 changes: 23 additions & 0 deletions tests/powershell/FastfetchStatus.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,29 @@ Describe "fastfetch status module helpers" {
Join-Path $script:TestCacheDir 'updates' | Should -Not -Exist
}

It "should not delete an in-flight temp file from a concurrent writer" {
# Cleanup used to remove any non-.src file, which could yank a
# '<name>.<random>.tmp' out from under another shell's Move-Item.
$inFlight = Join-Path $script:TestCacheDir 'updates.abc12345.tmp'
Set-Content -LiteralPath $inFlight -Value 'half-written'
Set-Content -LiteralPath (Join-Path $script:TestCacheDir 'updates.src') -Value 'line'

Update-FastfetchStatusCache -SkipRefresh

$inFlight | Should -Exist
}

It "should give each write a unique temp name" {
# Two shells starting together both render the same section; a
# shared '<name>.tmp' would make one of the writes fail.
$target = Join-Path $script:TestCacheDir 'concurrent'
Write-FastfetchStatusFile -Path $target -Lines 'one'
Write-FastfetchStatusFile -Path $target -Lines 'two'

(Get-Content -LiteralPath $target -Raw).Trim() | Should -Be 'two'
@(Get-ChildItem -LiteralPath $script:TestCacheDir -Filter '*.tmp') | Should -BeNullOrEmpty
}

It "should leave the stamp alone" {
Set-Content -LiteralPath (Join-Path $script:TestCacheDir 'updates.src') -Value 'line'
Update-FastfetchStatusCache -SkipRefresh
Expand Down
6 changes: 6 additions & 0 deletions tests/powershell/Profile.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,12 @@ Describe "Profile startup noise" {
$script:ProfileContent | Should -Match 'if \(-not \$fastfetchProc\.WaitForExit'
}

It "Profile should not mark fastfetch as shown when it exited non-zero" {
# Exiting quickly is not the same as succeeding: a bad config leaves the
# screen blank, and suppressing the welcome lines would look like a hang.
$script:ProfileContent | Should -Match '\$fastfetchProc\.ExitCode -eq 0'
}

It "Profile should keep the welcome lines for installs without fastfetch" {
$script:ProfileContent | Should -Match 'PowerShell Profile Loaded'
$script:ProfileContent | Should -Match "Type 'aliases' to see available aliases"
Expand Down
40 changes: 40 additions & 0 deletions tests/powershell/WindowsTerminal.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,46 @@ Describe "Set-WindowsTerminalProfileCommandLine Function" -Tag "Unit" {
}
}

Describe "Windows Terminal PowerShell args script" -Tag "Unit" {
BeforeAll {
$script:ArgsScript = Join-Path $script:RepoRoot `
"home\.chezmoiscripts\windows\run_onchange_set-windows-terminal-powershell-args.ps1"
}

It "Should exist as a plain .ps1 so the signing workflow can sign it" {
# sign-powershell.yml signs **.ps1 but excludes **.ps1.tmpl, because a
# rendered template no longer matches its signature.
$script:ArgsScript | Should -Exist
"$script:ArgsScript.tmpl" | Should -Not -Exist
}

It "Should have valid PowerShell syntax" {
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
$script:ArgsScript, [ref]$null, [ref]$errors) | Out-Null
$errors | Should -BeNullOrEmpty
}

It "Should not contain chezmoi template syntax" {
$content = Get-Content $script:ArgsScript -Raw
$content | Should -Not -Match '\{\{'
}

It "Should resolve the module through CHEZMOI_SOURCE_DIR" {
$content = Get-Content $script:ArgsScript -Raw
$content | Should -Match 'CHEZMOI_SOURCE_DIR'
# Forward slashes and 8.3 paths arrive in that variable, so the path has
# to be composed rather than concatenated.
$content | Should -Match 'Join-Path'
}

It "Should quieten both pwsh startup lines" {
$content = Get-Content $script:ArgsScript -Raw
$content | Should -Match '-NoLogo'
$content | Should -Match '-NoProfileLoadTime'
}
}

Describe "Set-WindowsTerminalCopilotProfile Function" -Tag "Unit" {
BeforeEach {
$script:settingsPath = Join-Path $script:TestDir.FullName "settings-$(Get-Random).json"
Expand Down