Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `Add-PSDepend` appends a dependency to a DependencyFile, creating
`requirements.psd1` if none exists. It parses the target file with the
PowerShell AST and splices the new entry in as text, leaving every other
entry and any comments untouched. By default it re-runs
`Update-PSDependLock` afterward, rolling back the edit if resolution
fails; `-NoLock` opts out. `-Force` overwrites an existing entry for the
same Name, replacing it entirely.

## [0.7.0-beta1] - 2026-10-07

### Added
Expand Down
1 change: 1 addition & 0 deletions PSDepend/PSDepend.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
# Functions to export from this module
# Generated by the build process
FunctionsToExport = @(
'Add-PSDepend',
'Get-Dependency',
'Get-PSDependScript',
'Get-PSDependType',
Expand Down
60 changes: 60 additions & 0 deletions PSDepend/Private/ConvertTo-PSDependLiteral.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
function ConvertTo-PSDependLiteral {
<#
.SYNOPSIS
Render a value as PowerShell source text suitable for a DependencyFile entry.

.DESCRIPTION
Used by Add-PSDepend to serialize field values (Tags, Parameters, ...) when
writing a new hashtable-form entry. Handles strings, booleans, numbers,
arrays, and nested hashtables.

.PARAMETER Value
The value to render.

.PARAMETER IndentLevel
Indentation depth, in 4-space units, for a nested hashtable's members and
its closing brace.

.EXAMPLE
ConvertTo-PSDependLiteral -Value @{ SkipPublisherCheck = $true }

Returns "@{`r`n SkipPublisherCheck = `$true`r`n }"
#>
[CmdletBinding()]
[OutputType([string])]
param(
$Value,

[int]$IndentLevel = 2
)

if ($null -eq $Value) {
return '$null'
}
if ($Value -is [bool]) {
return $(if ($Value) { '$true' } else { '$false' })
}
if ($Value -is [System.Collections.IDictionary]) {
$ChildIndent = ' ' * ($IndentLevel + 1)
$ClosingIndent = ' ' * $IndentLevel
$Entries = foreach ($Key in $Value.Keys) {
$QuotedKey = "'$($Key.ToString() -replace "'", "''")'"
"$ChildIndent$QuotedKey = $(ConvertTo-PSDependLiteral -Value $Value[$Key] -IndentLevel ($IndentLevel + 1))"
}
Comment thread
HeyItsGilbert marked this conversation as resolved.
return "@{`r`n$($Entries -join "`r`n")`r`n$ClosingIndent}"
}
if ($Value -is [string]) {
return "'$($Value -replace "'", "''")'"
}
if ($Value -is [int] -or $Value -is [long] -or $Value -is [double]) {
return "$Value"
}
if ($Value -is [System.Collections.IEnumerable]) {
$Items = @($Value | ForEach-Object { ConvertTo-PSDependLiteral -Value $_ -IndentLevel $IndentLevel })
if ($Items.Count -eq 0) {
return '@()'
}
return ($Items -join ', ')
}
Comment thread
HeyItsGilbert marked this conversation as resolved.
return "'$($Value.ToString() -replace "'", "''")'"
}
61 changes: 61 additions & 0 deletions PSDepend/Private/Resolve-PSDependFileTarget.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
function Resolve-PSDependFileTarget {
<#
.SYNOPSIS
Resolve the DependencyFile that Add-PSDepend should write to.

.DESCRIPTION
If Path points to an existing file, it is used as-is. If Path points to a
nonexistent path with a .psd1 extension, it is treated as the file to
create. Otherwise Path is treated as a directory: it is searched
(optionally recursively) for *.depend.psd1 and requirements.psd1 files,
the same way Invoke-PSDepend and Update-PSDependLock discover
DependencyFiles. Exactly one match is returned as-is. No matches falls
back to <Path>\requirements.psd1, to be created by the caller. More than
one match is ambiguous and the caller must narrow it with -Path.

.PARAMETER Path
A DependencyFile, a new DependencyFile to create, or a directory to search.

.PARAMETER Recurse
Whether to recurse into subdirectories when Path is a directory.

.EXAMPLE
Resolve-PSDependFileTarget -Path . -Recurse $true

Finds the single DependencyFile under the current directory tree, or
falls back to .\requirements.psd1 if none exist yet.
#>
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(Mandatory)]
[string]$Path,

[bool]$Recurse = $true
)

if (Test-Path -LiteralPath $Path -PathType Leaf) {
$Resolved = (Resolve-Path -LiteralPath $Path).ProviderPath
if ([System.IO.Path]::GetExtension($Resolved) -ine '.psd1') {
throw "Path '$Resolved' is not a .psd1 file"
}
return $Resolved
}
Comment thread
HeyItsGilbert marked this conversation as resolved.

if (-not (Test-Path -LiteralPath $Path)) {
if ([System.IO.Path]::GetExtension($Path) -eq '.psd1') {
return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
throw "Path '$Path' does not exist"
}

$DirectoryPath = (Resolve-Path -LiteralPath $Path).ProviderPath
$Found = @(Resolve-DependScripts -Path $DirectoryPath -Recurse $Recurse)
switch ($Found.Count) {
0 { Join-Path $DirectoryPath 'requirements.psd1' }
1 { $Found[0] }
default {
throw "Multiple DependencyFiles found under '$DirectoryPath': $($Found -join ', '). Specify -Path to target one of them."
}
}
}
Loading
Loading