From 79df1bf791b1aa15cc324039d08235a047b68874 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Sat, 13 Jun 2026 23:13:04 -0700 Subject: [PATCH] feat: auto-register repositories declared in PSDependOptions.Repositories PSGalleryModule, PSResourceGet, and Package DependencyScripts now check PSDependOptions.Repositories when a named repository is not registered on the current machine. If a URL is declared there the repository is registered persistently as Trusted before installation proceeds, making DependencyFiles portable without manual setup. Closes #50 Co-Authored-By: Claude Sonnet 4.6 --- CONTEXT.md | 4 +++ PSDepend/PSDependScripts/PSGalleryModule.ps1 | 25 +++++++++++++++-- PSDepend/PSDependScripts/PSResourceGet.ps1 | 25 +++++++++++++++-- PSDepend/PSDependScripts/Package.ps1 | 28 +++++++++++++++++-- ...-repository-registry-in-psdependoptions.md | 16 +++++++++++ 5 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 docs/adr/0001-repository-registry-in-psdependoptions.md diff --git a/CONTEXT.md b/CONTEXT.md index 841d6b3..f539ce0 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -60,6 +60,10 @@ _Avoid_: filter, category, label > **Dev:** "I need psake to install before PowerShellBuild. How do I express that?" > **Domain expert:** "Declare a **Prerequisite** on PowerShellBuild: `DependsOn = 'psake'`. The engine resolves all **Prerequisites** into topological order before dispatching any **DependencyScript**." +**RepositoryRegistry**: +A `Repositories` hashtable nested inside `PSDependOptions` that maps repository names to their source URLs. Used by DependencyScripts to auto-register a repository when it is not already present on the machine. +_Avoid_: repository map, source list, feed declarations + ## Flagged ambiguities - "dependency" collides with **Prerequisite** in natural speech ("psake is a dependency of PowerShellBuild") — resolved: use **Prerequisite** for the ordering relationship, **Dependency** only for a declared entry in a DependencyFile. diff --git a/PSDepend/PSDependScripts/PSGalleryModule.ps1 b/PSDepend/PSDependScripts/PSGalleryModule.ps1 index d0eb680..d195e42 100644 --- a/PSDepend/PSDependScripts/PSGalleryModule.ps1 +++ b/PSDepend/PSDependScripts/PSGalleryModule.ps1 @@ -167,8 +167,29 @@ Write-Verbose -Message "Getting dependency [$name] from PowerShell repository [$ if ($Repository) { $validRepo = Get-PSRepository -Name $Repository -Verbose:$false -ErrorAction SilentlyContinue if (-not $validRepo) { - Write-Error "[$Repository] has not been setup as a valid PowerShell repository." - return + $repoRegistry = $Dependency.PSDependOptions.Repositories + if (-not $repoRegistry -or -not $repoRegistry.ContainsKey($Repository)) { + Write-Error "[$Repository] is not registered and no URL was found in PSDependOptions.Repositories. Add an entry to register it automatically." + return + } + $repoUrl = $repoRegistry[$Repository] + if ($repoUrl -isnot [string]) { + Write-Error "PSDependOptions.Repositories entry for [$Repository] must be a string URL for PSGalleryModule dependencies." + return + } + $registerSplat = @{ + Name = $Repository + SourceLocation = $repoUrl + InstallationPolicy = 'Trusted' + } + if ($Credential) { $registerSplat.Credential = $Credential } + Write-Verbose "Registering PowerShell repository [$Repository] at [$repoUrl]" + Register-PSRepository @registerSplat + } elseif ($Dependency.PSDependOptions.Repositories -and $Dependency.PSDependOptions.Repositories.ContainsKey($Repository)) { + $declaredUrl = $Dependency.PSDependOptions.Repositories[$Repository] + if ($declaredUrl -is [string] -and $validRepo.SourceLocation -ne $declaredUrl) { + Write-Warning "Repository [$Repository] is already registered at [$($validRepo.SourceLocation)] but PSDependOptions.Repositories declares [$declaredUrl]. Using existing registration." + } } } diff --git a/PSDepend/PSDependScripts/PSResourceGet.ps1 b/PSDepend/PSDependScripts/PSResourceGet.ps1 index 84d09cd..bb60c96 100644 --- a/PSDepend/PSDependScripts/PSResourceGet.ps1 +++ b/PSDepend/PSDependScripts/PSResourceGet.ps1 @@ -178,8 +178,29 @@ Write-Verbose -Message "Getting dependency [$Name] from PowerShell repository [$ if ($Repository) { $validRepo = Get-PSResourceRepository -Name $Repository -Verbose:$false -ErrorAction SilentlyContinue if (-not $validRepo) { - Write-Error "[$Repository] has not been set up as a valid PowerShell repository." - return + $repoRegistry = $Dependency.PSDependOptions.Repositories + if (-not $repoRegistry -or -not $repoRegistry.ContainsKey($Repository)) { + Write-Error "[$Repository] is not registered and no URL was found in PSDependOptions.Repositories. Add an entry to register it automatically." + return + } + $repoUrl = $repoRegistry[$Repository] + if ($repoUrl -isnot [string]) { + Write-Error "PSDependOptions.Repositories entry for [$Repository] must be a string URL for PSResourceGet dependencies." + return + } + $registerSplat = @{ + Name = $Repository + Uri = $repoUrl + Trusted = $true + } + if ($Credential) { $registerSplat.Credential = $Credential } + Write-Verbose "Registering PSResource repository [$Repository] at [$repoUrl]" + Register-PSResourceRepository @registerSplat + } elseif ($Dependency.PSDependOptions.Repositories -and $Dependency.PSDependOptions.Repositories.ContainsKey($Repository)) { + $declaredUrl = $Dependency.PSDependOptions.Repositories[$Repository] + if ($declaredUrl -is [string] -and $validRepo.Uri -ne $declaredUrl) { + Write-Warning "Repository [$Repository] is already registered at [$($validRepo.Uri)] but PSDependOptions.Repositories declares [$declaredUrl]. Using existing registration." + } } } diff --git a/PSDepend/PSDependScripts/Package.ps1 b/PSDepend/PSDependScripts/Package.ps1 index 4332ec6..6016e95 100644 --- a/PSDepend/PSDependScripts/Package.ps1 +++ b/PSDepend/PSDependScripts/Package.ps1 @@ -76,8 +76,32 @@ if (-not $Version) { $PackageSources = @( Get-PackageSource ) if ($PackageSources.Name -notcontains $Source -and -not $PSBoundParameters.ContainsKey('ProviderName')) { - Write-Error "PackageSource [$Source] is not valid. Valid sources:`n$($PackageSources.ProviderName | Out-String)" - return + $repoRegistry = $Dependency.PSDependOptions.Repositories + if (-not $repoRegistry -or -not $repoRegistry.ContainsKey($Source)) { + Write-Error "PackageSource [$Source] is not registered and no entry was found in PSDependOptions.Repositories. Add an entry with Url and ProviderName to register it automatically." + return + } + $repoEntry = $repoRegistry[$Source] + if ($repoEntry -isnot [hashtable] -or -not $repoEntry.ContainsKey('Url') -or -not $repoEntry.ContainsKey('ProviderName')) { + Write-Error "PSDependOptions.Repositories entry for [$Source] must be a hashtable with 'Url' and 'ProviderName' keys for Package dependencies." + return + } + $registerSplat = @{ + Name = $Source + Location = $repoEntry.Url + ProviderName = $repoEntry.ProviderName + Trusted = $true + } + if ($Dependency.Credential) { $registerSplat.Credential = $Dependency.Credential } + Write-Verbose "Registering PackageSource [$Source] at [$($repoEntry.Url)] with provider [$($repoEntry.ProviderName)]" + Register-PackageSource @registerSplat + $PackageSources = @( Get-PackageSource ) +} elseif ($repoRegistry -and $repoRegistry.ContainsKey($Source)) { + $repoEntry = $repoRegistry[$Source] + $existingSource = $PackageSources | Where-Object { $_.Name -eq $Source } + if ($existingSource -and $repoEntry -is [hashtable] -and $repoEntry.ContainsKey('Url') -and $existingSource.Location -ne $repoEntry.Url) { + Write-Warning "PackageSource [$Source] is already registered at [$($existingSource.Location)] but PSDependOptions.Repositories declares [$($repoEntry.Url)]. Using existing registration." + } } $PackageProviders = @( Get-PackageProvider ) diff --git a/docs/adr/0001-repository-registry-in-psdependoptions.md b/docs/adr/0001-repository-registry-in-psdependoptions.md new file mode 100644 index 0000000..35179d3 --- /dev/null +++ b/docs/adr/0001-repository-registry-in-psdependoptions.md @@ -0,0 +1,16 @@ +# Repository auto-registration via PSDependOptions.Repositories + +Private repository URLs belong in a file-level `Repositories` block inside `PSDependOptions`, not repeated per-Dependency. When a DependencyScript encounters an unregistered repository name, it looks up that name in `PSDependOptions.Repositories`, registers it persistently as Trusted, and proceeds — making the DependencyFile portable to any machine without manual setup. + +## Considered Options + +**Per-Dependency `RepositoryUrl` parameter** — the URL travels with each Dependency that uses it. Simpler for single-module cases but forces repetition when multiple Dependencies share a source, and scatters URL maintenance across the file. + +**Ephemeral registration** (register before install, unregister after) — leaves no trace on the machine. Rejected because it re-registers on every run when many Dependencies share a source, and teardown on error paths is difficult to make reliable across three different registration APIs. + +## Consequences + +- `PSDependOptions.Repositories` is a new public key in the DependencyFile schema. Existing files are unaffected (the key is optional). +- Registration is persistent: the repository stays registered after PSDepend runs. This is intentional — machines in CI or shared environments accumulate registrations over time rather than re-registering on each run. +- `Test` action never registers anything; registration is gated on `Install`. +- If a repository name is already registered with a different URL, PSDepend warns but proceeds using the existing registration.