Skip to content

Commit

Permalink
The nesting limit is actually a scope stack limit, not a file system …
Browse files Browse the repository at this point in the history
…limit so put nested modules back into a directory, with the ability to install directly in the module directory.
  • Loading branch information
splatteredbits committed Dec 2, 2024
1 parent 3a92d2f commit ecc7868
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 53 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@

# Prism Changelog

## 0.9.0

Turns out, the 10 directory nesting limit for nested modules is a scope stack limit, not a directory limit. This version
of Prism now installs nested modules into a "Modules" directory instead of directly in the module directory. You can
preserve the old behavior and install modules directly in the module directory by setting the "PSModulesDirectoryName"
configuration property in the prism.json file to `.`.

## 0.8.1

> Released 19 Nov 2024
Fixed: Prism fails to install new versions of nested modules if old versions are installed.

## 0.8.0
Expand Down
14 changes: 2 additions & 12 deletions Prism/Functions/Install-PrivateModule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,6 @@ function Install-PrivateModule

$installedModules =
& {
if ($Configuration.Nested)
{
Get-ChildItem -Path $Configuration.InstallDirectoryPath -Recurse | Out-String | Write-Debug
Get-ChildItem -Path (Join-Path -Path $Configuration.InstallDirectoryPath -ChildPath '*\*.psd1') |
ForEach-Object { Get-Module -Name $_.FullName -ListAvailable -ErrorAction Ignore }
}
else
{
$origPSModulePath = $env:PSModulePath
$env:PSModulePath = $Configuration.InstallDirectoryPath
try
Expand All @@ -67,7 +59,6 @@ function Install-PrivateModule
{
$env:PSModulePath = $origPSModulePath
}
}
} |
Add-Member -Name 'SemVer' -MemberType ScriptProperty -PassThru -Value {
$prerelease = $this.PrivateData['PSData']['PreRelease']
Expand Down Expand Up @@ -146,9 +137,8 @@ function Install-PrivateModule
-Repository $repoName `
@pkgMgmtPrefs

# PowerShell has a 10 directory limit for nested modules, so reduce the number of nested directories
# when installing a nested module by installing directly in the module root directory and moving
# everything out of the version module directory.
# Windows has a 260 character limit for path length. Reduce paths by removing extraneous version
# directories.
if ($nestedSingleVersion)
{
$modulePath = Join-Path -Path $installDirPath -ChildPath $module.name
Expand Down
22 changes: 14 additions & 8 deletions Prism/Functions/Invoke-Prism.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,23 @@ function Invoke-Prism
$lockExtension = ''
}

$isNested = (Test-Path -Path (Join-Path -Path $prismJsonFile.DirectoryName -ChildPath '*.psd1')) -or `
(Test-Path -Path (Join-Path -Path $prismJsonFile.DirectoryName -ChildPath '*.psm1'))

$defaultInstallDirName = 'PSModules'
if ($isNested)
{
$defaultInstallDirName = 'Modules'
}

$ignore = @{ 'ErrorAction' = 'Ignore' }
# public configuration that users can customize.
# Add-Member doesn't return an object if the member already exists, so these can't be part of one pipeline.
$config | Add-Member -Name 'PSModules' -MemberType NoteProperty -Value @() @ignore
$config | Add-Member -Name 'PSModulesDirectoryName' -MemberType NoteProperty -Value 'PSModules' @ignore
$config | Add-Member -Name 'PSModulesDirectoryName' `
-MemberType NoteProperty `
-Value $defaultInstallDirName `
@ignore

if ($config.PSModulesDirectoryName.Contains('\') -or `
$config.PSModulesDirectoryName.Contains('/') -or `
Expand All @@ -161,15 +173,9 @@ function Invoke-Prism
return
}

$isNested = (Test-Path -Path (Join-Path -Path $prismJsonFile.DirectoryName -ChildPath '*.psd1')) -or `
(Test-Path -Path (Join-Path -Path $prismJsonFile.DirectoryName -ChildPath '*.psm1'))

$installDirPath =
Join-Path -Path $prismJsonFile.DirectoryName -ChildPath $config.PSModulesDirectoryName
if ($isNested -or $config.PSModulesDirectoryName -eq '.')
{
$installDirPath = $prismJsonFile.DirectoryName
}
$installDirPath = [IO.Path]::GetFullPath($installDirPath)

$lockPath =
Join-Path -Path ($prismJsonPath |
Expand Down
2 changes: 1 addition & 1 deletion Prism/Prism.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
RootModule = 'Prism.psm1'

# Version number of this module.
ModuleVersion = '0.8.1'
ModuleVersion = '0.9.0'

# ID used to uniquely identify this module
GUID = '5b244346-40c9-4a50-a098-8758c19f7f25'
Expand Down
31 changes: 13 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,22 @@ a `prism.lock.json` file, with the specific versions of each module to install.
install` will only install the module versions listed in the lock file. To update the versions in the lock file to
newer versions or to reflect changes made to the `prism.json` file, run `prism update`.

### Output Directory
### Where Prism Installs/Saves Modules

Modules will always be saved in the same directory as the "prism.json" file, in a directory named "PSModules". You can
customize this directory name with the `PSModulesDirectoryName` option in your `prism.json` file:
Modules will always be saved in the same directory as the "prism.json" file, in a directory named "PSModules". If
installing nested modules for a module (i.e. the install directory contains a .psd1 or .psm1 file), modules are saved to
a "Modules" directory. You can customize this directory name with the `PSModulesDirectoryName` option in your
`prism.json` file:

```json
{
"PSModules": [],
"PSModulesDirectoryName": "Modules"
"PSModulesDirectoryName": "SomeOtherDirName"
}
```

To install modules in the same directory as the "prism.json" file, use `.` as the "PSModulesDirectoryName" value.

To put the PSModules directory in a *different* directory, put a "prism.json" file in that directory. Use the "prism"
command's `-Recurse` switch to run prism against every prism.json file under the current directory.

Expand All @@ -139,16 +143,7 @@ Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'PSModules\Whiskey
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\PSModules\Whiskey' -Resolve)
```

## Using Nested Modules in a Module

### Installing

PowerShell has a 10 directory nested limit for nested modules. When using nested modules in a module, in order to avoid
errors about too much nesting, Prism will install the modules directly into your module directory and will also *not*
install modules into a version-specific directory. Prism automatically detects when installing into a module directory
by looking for a .psd1 or .psm1 file in the same directory as the prism.json file.

### Importing
### Importing Nested Modules

To import and use a private, nested module installed by Prism, use `Import-Module` and pass the path to the module
instead of a module name. Use `Join-Path` and join the path to your module's directory with the relative path to the
Expand All @@ -168,14 +163,14 @@ Import-Module -Name (Join-Path -Path $script:moduleDirPath -ChildPath 'Whiskey'
***DO*** always use the `Import-Module` cmdlet's `Alias`, `Cmdlet`, and `Function` parameters to explicitly list what
commands your script is importing and using. It makes upgrading easier when you know what commands you're using.

***DO NOT*** depend on PowerShell's automatic module loading. That functionality won't see the private modules in
"PSModules".
***DO NOT*** depend on PowerShell's automatic module loading. That functionality won't see the private modules Prism
installs.

#### When Writing Modules

***DO*** ship your module's dependencies as nested modules. Use Prism to manage these as it structures dependencies to
avoid deep nesting errors. Import dependencies from that private location. A module can have its own version of a module
loaded privately.
avoid long directory paths. Import dependencies from that private location. A module can have its own version of a
module loaded privately.

***DO NOT*** use the `NestedModules` module manifest property. Use an explicit `Import-Module` in your root module to
import dependencies saved inside your module.
Expand Down
89 changes: 75 additions & 14 deletions Tests/install.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,15 @@ BeforeAll {

function ThenInstalled
{
[CmdletBinding(DefaultParameterSetName='NonNested')]
[CmdletBinding()]
param(
[Parameter(Mandatory, Position=0)]
[hashtable] $Module,

[String] $In,

[Parameter(ParameterSetName='NonNested')]
[String] $UsingDirName,

[Parameter(Mandatory, ParameterSetName='Nested')]
[switch] $AsNestedModule
)

Expand All @@ -108,16 +106,16 @@ BeforeAll {
if (-not $UsingDirName)
{
$UsingDirName = 'PSModules'
}

$isNestedModule = $PSCmdlet.ParameterSetName -eq 'Nested'

$savePath = $In
if (-not $isNestedModule)
{
$savePath = Join-Path -Path $In -ChildPath $UsingDirName
if ($AsNestedModule)
{
$UsingDirName = 'Modules'
}
}

$savePath = Join-Path -Path $In -ChildPath $UsingDirName
$savePath = [IO.Path]::GetFullPath($savePath)

# Make sure *only* the modules we requested are installed.
$expectedCount = 0
foreach ($moduleName in $Module.Keys)
Expand All @@ -128,7 +126,7 @@ BeforeAll {
$expectedCount += 1
$version,$prerelease = $semver -split '-'
$manifestPath = Join-Path -Path $modulePath -ChildPath $version
if ($isNestedModule -and ($Module[$moduleName] | Measure-Object).Count -eq 1)
if ($AsNestedModule -and ($Module[$moduleName] | Measure-Object).Count -eq 1)
{
$manifestPath | Should -Not -Exist -Because 'should remove version directory for nested module'
$manifestPath = $manifestPath | Split-Path -Parent
Expand All @@ -154,7 +152,7 @@ BeforeAll {
}

$path = "${savePath}\*\*\*.psd1"
if ($isNestedModule -and ($Module[$moduleName] | Measure-Object).Count -eq 1)
if ($AsNestedModule -and ($Module[$moduleName] | Measure-Object).Count -eq 1)
{
$path = "${savePath}\*\*.psd1"
}
Expand Down Expand Up @@ -296,12 +294,33 @@ Describe 'prism install' {
"Version": "1.0.0"
}
],
"PSModulesDirectoryName": "Modules"
"PSModulesDirectoryName": "PSM1"
}
'@
GivenLockFile $script:latestNoOpLockFile
WhenInstalling
ThenInstalled @{ 'NoOp' = '1.0.0' } -UsingDirName 'Modules'
ThenInstalled @{ 'NoOp' = '1.0.0' } -UsingDirName 'PSM1'
}

It 'does not install in a subdirectory' {
GivenPrismFile @'
{
"PSModulesDirectoryName": "."
}
'@
GivenLockFile @'
{
"PSModules": [
{
"name": "NoOp",
"version": "1.0.0",
"repositorySourceLocation": "https://www.powershellgallery.com/api/v2/"
}
]
}
'@
WhenInstalling
ThenInstalled @{ 'NoOp' = '1.0.0' } -UsingDirName '.'
}

It 'should install prerelease versions' {
Expand Down Expand Up @@ -745,5 +764,47 @@ Describe 'prism install' {
Should -Not -Invoke 'Save-Module' -ModuleName 'Prism'
$Global:Error | Should -Match 'that destination already exists'
}

It 'customizes module directory name' {
GivenPrismFile @'
{
"PSModulesDirectoryName": "PSM1"
}
'@
GivenLockFile @'
{
"PSModules": [
{
"name": "NoOp",
"version": "1.0.0",
"repositorySourceLocation": "https://www.powershellgallery.com/api/v2/"
}
]
}
'@
WhenInstalling
ThenInstalled @{ 'NoOp' = '1.0.0' } -AsNestedModule -UsingDirName 'PSM1'
}

It 'does not install in a subdirectory' {
GivenPrismFile @'
{
"PSModulesDirectoryName": "."
}
'@
GivenLockFile @'
{
"PSModules": [
{
"name": "NoOp",
"version": "1.0.0",
"repositorySourceLocation": "https://www.powershellgallery.com/api/v2/"
}
]
}
'@
WhenInstalling
ThenInstalled @{ 'NoOp' = '1.0.0' } -AsNestedModule -UsingDirName '.'
}
}
}

0 comments on commit ecc7868

Please sign in to comment.