Skip to content

Commit 925f56d

Browse files
HeyItsGilbertclaude
andcommitted
fix: FileDownload cross-platform support and target-path handling (#98, #49)
- Expand PSDependMap.psd1 Supports to include core/macos/linux; no Windows-only code was blocking cross-platform use (#98) - Root relative Target paths against $PWD before any path operations so callers are not burned by Split-Path against a relative string (#49) - Replace parent-exists heuristic with file-extension check to distinguish file targets from container targets; directory targets are now created when they do not yet exist rather than erroring (#49) - Fix hardcoded ";" path separator in Write-Verbose to use [IO.Path]::PathSeparator for correctness on non-Windows - Add four tests: existing-dir target, new-dir creation, extension-based file-path target, and relative target rooted against $PWD Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent aa28e3e commit 925f56d

4 files changed

Lines changed: 73 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- `FileDownload` is now supported on all platforms (`windows`, `core`, `macos`, `linux`); there was no Windows-only code blocking this (#98).
13+
- `FileDownload` relative `Target` paths are now rooted against `$PWD` before resolution, matching the intuitive expectation of callers (#49).
14+
1015
### Fixed
1116

1217
- `Get-Dependency -InputObject` no longer mutates the caller's hashtable:
1318
`PSDependOptions` is now preserved after the call, so a second invocation
1419
with the same object still honors global options such as `Target` (#35).
20+
- `FileDownload` no longer misidentifies a directory-like `Target` (no file extension, or trailing slash) as a full file path when its parent happens to exist; the handler now uses a file-extension heuristic to distinguish file targets from container targets and creates the directory when it does not yet exist (#49).
1521

1622
## [0.4.1] - 2026-06-12
1723

PSDepend/PSDependMap.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
FileDownload = @{
2828
Script = 'FileDownload.ps1'
2929
Description = 'Download a file'
30-
Supports = 'windows'
30+
Supports = 'windows', 'core', 'macos', 'linux'
3131
}
3232

3333
FileSystem = @{

PSDepend/PSDependScripts/FileDownload.ps1

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,16 @@ Write-Verbose "Using URL: $URL"
7676

7777
# Act on target path....
7878
$ToInstall = $False # Anti pattern
79+
80+
# Normalize relative paths against $PWD so callers don't get burned by cwd-dependent splits
81+
if (-not [IO.Path]::IsPathRooted($Target)) {
82+
$Target = Join-Path $PWD $Target
83+
}
84+
7985
$TargetParent = Split-Path $Target -Parent
8086
$PathToAdd = $Target
81-
if ( (Test-Path $TargetParent) -and -not (Test-Path $Target)) {
82-
# They gave us a full path, don't parse the file name, use this!
83-
$Path = $Target
84-
$ToInstall = $True
85-
Write-Verbose "Found parent [$TargetParent], not target [$Target], assuming this is target file path"
86-
}
87-
elseif (Test-Path $Target -PathType Leaf) {
87+
88+
if (Test-Path $Target -PathType Leaf) {
8889
# File exists. We should download to temp spot, compare hashes, take action as appropriate.
8990
# For now, skip the file.
9091
Write-Verbose "Skipping existing file [$Target]"
@@ -93,16 +94,26 @@ elseif (Test-Path $Target -PathType Leaf) {
9394
}
9495
$PathToAdd = Split-Path $Target -Parent
9596
}
96-
elseif (-not (Test-Path $Target)) {
97-
# They gave us something that doesn't look like a new container for a new or existing file. Wat?
98-
Write-Error "Could not find target path [$Target]"
99-
if ($PSDependAction -contains 'Test') {
100-
return $False
97+
elseif ([IO.Path]::GetExtension($Target) -and -not (Test-Path $Target -PathType Container)) {
98+
# Target has a file extension — treat as a full destination file path
99+
if (-not (Test-Path $TargetParent)) {
100+
Write-Error "Could not find parent path [$TargetParent] for target [$Target]"
101+
if ($PSDependAction -contains 'Test') {
102+
return $False
103+
}
104+
}
105+
else {
106+
$Path = $Target
107+
$ToInstall = $True
108+
Write-Verbose "Target has extension, treating as file path [$Target]"
101109
}
102110
}
103111
else {
112+
# No extension (or already a container) — treat target as a directory
104113
Write-Verbose "[$Target] is a container, creating path to file"
105-
# We have a target container, now find the name
114+
if (-not (Test-Path $Target)) {
115+
New-Item -ItemType Directory -Path $Target -Force | Out-Null
116+
}
106117
If ($Name) {
107118
# explicit name
108119
$FileName = $Name
@@ -138,6 +149,6 @@ if ($PSDependAction -contains 'Install' -and $ToInstall) {
138149
}
139150

140151
if ($Dependency.AddToPath) {
141-
Write-Verbose "Setting PATH to`n$($PathToAdd, $env:PATH -join ';' | Out-String)"
152+
Write-Verbose "Setting PATH to`n$($PathToAdd, $env:PATH -join [IO.Path]::PathSeparator | Out-String)"
142153
Add-ToItemCollection -Reference Env:\Path -Item $PathToAdd
143154
}

Tests/FileDownload.Type.Tests.ps1

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,45 @@ Describe 'FileDownload script' -Skip:$SkipUnsupported {
7171
}
7272
$result | Should -Be $true
7373
}
74+
75+
It 'Creates a new directory and downloads into it when Target has no extension and does not exist' {
76+
$newDir = Join-Path (New-Item 'TestDrive:/dl5base' -ItemType Directory -Force).FullName 'newcontainer'
77+
$dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target $newDir
78+
InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath; T = $newDir } {
79+
& $ScriptPath -Dependency $Dep
80+
}
81+
Should -Invoke -CommandName Get-WebFile -ModuleName PSDepend -Times 1 -Exactly -ParameterFilter {
82+
$URL -eq 'https://example.com/sample.dll' -and ($Path -like "*newcontainer*sample.dll")
83+
}
84+
Test-Path $newDir -PathType Container | Should -Be $true
85+
}
86+
87+
It 'Treats Target as a full file path when it has a file extension and parent exists' {
88+
$targetDir = (New-Item 'TestDrive:/dl6' -ItemType Directory -Force).FullName
89+
$targetFile = Join-Path $targetDir 'out.dll'
90+
$dep = New-PSDependFixture -DependencyName 'https://example.com/other.dll' -DependencyType 'FileDownload' -Target $targetFile
91+
InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } {
92+
& $ScriptPath -Dependency $Dep
93+
}
94+
Should -Invoke -CommandName Get-WebFile -ModuleName PSDepend -Times 1 -Exactly -ParameterFilter {
95+
$URL -eq 'https://example.com/other.dll' -and $Path -eq $targetFile
96+
}
97+
}
98+
99+
It 'Roots a relative Target against $PWD and downloads to it' {
100+
$baseDir = (New-Item 'TestDrive:/relbase' -ItemType Directory -Force).FullName
101+
Push-Location $baseDir
102+
try {
103+
$dep = New-PSDependFixture -DependencyName 'https://example.com/sample.dll' -DependencyType 'FileDownload' -Target 'subdir'
104+
InModuleScope PSDepend -Parameters @{ Dep = $dep; ScriptPath = $script:ScriptPath } {
105+
& $ScriptPath -Dependency $Dep
106+
}
107+
Should -Invoke -CommandName Get-WebFile -ModuleName PSDepend -Times 1 -Exactly -ParameterFilter {
108+
$Path -like "*subdir*sample.dll"
109+
}
110+
}
111+
finally {
112+
Pop-Location
113+
}
114+
}
74115
}

0 commit comments

Comments
 (0)