From 869cc90099e3533f7316778d7dd10a238b2c596e Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Sat, 13 Jun 2026 13:20:09 -0700 Subject: [PATCH 1/3] fix: resolve PSDrive paths via GetUnresolvedProviderPathFromPSPath Co-Authored-By: Claude Sonnet 4.6 --- PSDepend/PSDependScripts/FileDownload.ps1 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/PSDepend/PSDependScripts/FileDownload.ps1 b/PSDepend/PSDependScripts/FileDownload.ps1 index a413954..34d383d 100644 --- a/PSDepend/PSDependScripts/FileDownload.ps1 +++ b/PSDepend/PSDependScripts/FileDownload.ps1 @@ -77,10 +77,8 @@ Write-Verbose "Using URL: $URL" # Act on target path.... $ToInstall = $False # Anti pattern -# Normalize relative paths against $PWD so callers don't get burned by cwd-dependent splits -if (-not [IO.Path]::IsPathRooted($Target)) { - $Target = Join-Path $PWD $Target -} +# Resolve PSDrive paths (e.g. TestDrive:) and relative paths to absolute filesystem paths +$Target = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Target) $TargetParent = Split-Path $Target -Parent $PathToAdd = $Target From d1da0dbe905625eb1e2cafecc5a7459967345898 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Sat, 13 Jun 2026 17:13:48 -0700 Subject: [PATCH 2/3] fix: address PR review comments - Set PathToAdd = TargetParent in the file-extension branch so AddToPath prepends the directory, not the file itself - Return early after Write-Error when the parent directory is missing so AddToPath and subsequent steps are not reached with Path unset - Guard New-Item directory creation behind Install action so Test runs do not mutate the filesystem - Fix CHANGELOG wording to include 'windows' in the platform list Co-Authored-By: Claude Sonnet 4.6 --- PSDepend/PSDependScripts/FileDownload.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PSDepend/PSDependScripts/FileDownload.ps1 b/PSDepend/PSDependScripts/FileDownload.ps1 index 34d383d..9e241ac 100644 --- a/PSDepend/PSDependScripts/FileDownload.ps1 +++ b/PSDepend/PSDependScripts/FileDownload.ps1 @@ -94,22 +94,22 @@ if (Test-Path $Target -PathType Leaf) { } elseif ([IO.Path]::GetExtension($Target) -and -not (Test-Path $Target -PathType Container)) { # Target has a file extension — treat as a full destination file path + $PathToAdd = $TargetParent if (-not (Test-Path $TargetParent)) { Write-Error "Could not find parent path [$TargetParent] for target [$Target]" if ($PSDependAction -contains 'Test') { return $False } + return } - else { - $Path = $Target - $ToInstall = $True - Write-Verbose "Target has extension, treating as file path [$Target]" - } + $Path = $Target + $ToInstall = $True + Write-Verbose "Target has extension, treating as file path [$Target]" } else { # No extension (or already a container) — treat target as a directory Write-Verbose "[$Target] is a container, creating path to file" - if (-not (Test-Path $Target)) { + if (-not (Test-Path $Target) -and $PSDependAction -contains 'Install') { New-Item -ItemType Directory -Path $Target -Force | Out-Null } If ($Name) { From 0c9435b08588432fa57abb6016e59c3dd342b39d Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Sat, 13 Jun 2026 17:32:33 -0700 Subject: [PATCH 3/3] fix: support extensionless file targets; use trailing separator for containers Copilot review identified that the extension-only heuristic broke extensionless file targets common on macOS/Linux (e.g. /usr/local/bin/terraform). New branching logic: - Trailing separator on Target => always a container (captured before GetUnresolvedProviderPathFromPSPath normalizes it away) - No trailing separator + (has extension OR parent already exists) => file target - Otherwise => container (created on Install) This restores backward-compatible handling of extensionless file paths while preserving the trailing-slash escape hatch for callers who want an extensionless container target. Update tests to use trailing slashes where container semantics are intended. Co-Authored-By: Claude Sonnet 4.6 --- PSDepend/PSDependScripts/FileDownload.ps1 | 20 ++++++++++++++++---- Tests/FileDownload.Type.Tests.ps1 | 9 ++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/PSDepend/PSDependScripts/FileDownload.ps1 b/PSDepend/PSDependScripts/FileDownload.ps1 index 9e241ac..b4e8ea5 100644 --- a/PSDepend/PSDependScripts/FileDownload.ps1 +++ b/PSDepend/PSDependScripts/FileDownload.ps1 @@ -77,6 +77,11 @@ Write-Verbose "Using URL: $URL" # Act on target path.... $ToInstall = $False # Anti pattern +# Capture trailing-separator intent before GetUnresolvedProviderPathFromPSPath normalizes it away; +# a trailing separator is an explicit signal that the target is a container, not a file. +$endsWithSeparator = $Target -and + $Target[-1] -in @([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) + # Resolve PSDrive paths (e.g. TestDrive:) and relative paths to absolute filesystem paths $Target = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Target) @@ -92,8 +97,15 @@ if (Test-Path $Target -PathType Leaf) { } $PathToAdd = Split-Path $Target -Parent } -elseif ([IO.Path]::GetExtension($Target) -and -not (Test-Path $Target -PathType Container)) { - # Target has a file extension — treat as a full destination file path +elseif ( + -not $endsWithSeparator -and + -not (Test-Path $Target -PathType Container) -and + ([IO.Path]::GetExtension($Target) -or (Test-Path $TargetParent)) +) { + # Treat as a full destination file path. + # Triggered when: has a file extension, OR parent already exists and caller gave no trailing separator. + # The trailing-separator check preserves extensionless binary targets (e.g. /usr/local/bin/terraform) + # while still allowing directory-like targets to be signalled with a trailing slash. $PathToAdd = $TargetParent if (-not (Test-Path $TargetParent)) { Write-Error "Could not find parent path [$TargetParent] for target [$Target]" @@ -104,10 +116,10 @@ elseif ([IO.Path]::GetExtension($Target) -and -not (Test-Path $Target -PathType } $Path = $Target $ToInstall = $True - Write-Verbose "Target has extension, treating as file path [$Target]" + Write-Verbose "Treating as destination file path [$Target]" } else { - # No extension (or already a container) — treat target as a directory + # No extension (or already a container, or explicit trailing separator) — treat target as a directory Write-Verbose "[$Target] is a container, creating path to file" if (-not (Test-Path $Target) -and $PSDependAction -contains 'Install') { New-Item -ItemType Directory -Path $Target -Force | Out-Null diff --git a/Tests/FileDownload.Type.Tests.ps1 b/Tests/FileDownload.Type.Tests.ps1 index 6b64d9b..3777a54 100644 --- a/Tests/FileDownload.Type.Tests.ps1 +++ b/Tests/FileDownload.Type.Tests.ps1 @@ -73,8 +73,10 @@ Describe 'FileDownload script' -Skip:$SkipUnsupported { } It 'Creates a new directory and downloads into it when Target has no extension and does not exist' { - $newDir = Join-Path (New-Item 'TestDrive:/dl5base' -ItemType Directory -Force).FullName 'newcontainer' - $dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target $newDir + $base = (New-Item 'TestDrive:/dl5base' -ItemType Directory -Force).FullName + $newDir = Join-Path $base 'newcontainer' + # Trailing separator signals "this is a container, not an extensionless file" + $dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target "$newDir/" InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath; T = $newDir } { & $ScriptPath -Dependency $Dep } @@ -100,7 +102,8 @@ Describe 'FileDownload script' -Skip:$SkipUnsupported { $baseDir = (New-Item 'TestDrive:/relbase' -ItemType Directory -Force).FullName Push-Location $baseDir try { - $dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target 'subdir' + # Trailing separator signals "this is a container, not an extensionless file" + $dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target 'subdir/' InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } { & $ScriptPath -Dependency $Dep }