Skip to content

Commit aa06dee

Browse files
authored
🎉 Incomplete Module Handling
ModuleFast will now detect if a module was potentially not fully installed and reinstall it. This is done by placing a .incomplete file in install-in-progress folders and checking for it before initiating installations and clearing the folder if it is found.
1 parent dc352e3 commit aa06dee

File tree

3 files changed

+43
-8
lines changed

3 files changed

+43
-8
lines changed

ModuleFast.psm1

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -418,13 +418,13 @@ function Get-ModuleFastPlan {
418418
foreach ($candidate in $inlinedVersions.Reverse()) {
419419
#Skip Prereleases unless explicitly requested
420420
if (($candidate.IsPrerelease -or $candidate.HasMetadata) -and -not ($currentModuleSpec.PreRelease -or $Prerelease)) {
421-
Write-Debug "Skipping candidate $candidate because it is a prerelease and prerelease was not specified either with the -Prerelease parameter or with a ! on the module name."
421+
Write-Debug "${moduleSpec}: skipping candidate $candidate because it is a prerelease and prerelease was not specified either with the -Prerelease parameter, by specifying a prerelease version in the spec, or adding a ! on the module name spec to indicate prerelease is acceptable."
422422
continue
423423
}
424424

425425
if ($currentModuleSpec.SatisfiedBy($candidate)) {
426426
#TODO: If the found version still matches an installed version, we should skip it
427-
Write-Debug "$currentModuleSpec`: Found satisfying version $candidate in the inlined index."
427+
Write-Debug "${ModuleSpec}: Found satisfying version $candidate in the inlined index."
428428
$matchingEntry = $entries | Where-Object version -EQ $candidate
429429
if ($matchingEntry.count -gt 1) { throw 'Multiple matching Entries found for a specific version. This is a bug and should not happen' }
430430
$matchingEntry
@@ -638,12 +638,15 @@ function Install-ModuleFastHelper {
638638

639639
#Used to keep track of context with Tasks, because we dont have "await" style syntax like C#
640640
[Dictionary[Task, hashtable]]$taskMap = @{}
641-
642641
[List[Task[Stream]]]$streamTasks = foreach ($module in $ModuleToInstall) {
643-
644642
$installPath = Join-Path $Destination $module.Name (Resolve-FolderVersion $module.ModuleVersion)
645-
646643
#TODO: Do a get-localmodule check here
644+
$installIndicatorPath = Join-Path $installPath '.incomplete'
645+
if (Test-Path $installIndicatorPath) {
646+
Write-Warning "${module}: Incomplete installation found at $installPath. Will delete and retry."
647+
Remove-Item $installPath -Recurse -Force
648+
}
649+
647650
if (Test-Path $installPath) {
648651
$existingManifestPath = try {
649652
Resolve-Path (Join-Path $installPath "$($module.Name).psd1") -ErrorAction Stop
@@ -661,7 +664,7 @@ function Install-ModuleFastHelper {
661664
#Do a prerelease evaluation
662665
if ($module.ModuleVersion -eq $existingVersion) {
663666
if ($Update) {
664-
Write-Verbose "${module}: Existing module found at $installPath and its version $existingVersion is the same as the requested version. -Update was specified so we are assuming that the discovered online version is the same as the local version and skipping this module."
667+
Write-Verbose "${module}: Existing module found at $installPath and its version $existingVersion is the same as the requested version. -Update was specified so we are assuming that the discovered online version is the same as the local version and skipping this module installation."
665668
continue
666669
} else {
667670
throw [NotImplementedException]"${module}: Existing module found at $installPath and its version $existingVersion is the same as the requested version. This is probably a bug because it should have been detected by localmodule detection. Use -Update to override..."
@@ -709,7 +712,19 @@ function Install-ModuleFastHelper {
709712
[ValidateNotNullOrEmpty()]$context = $USING:context
710713
)
711714
$installPath = $context.InstallPath
712-
#TODO: Add a ".incomplete" marker file to the folder and remove it when done. This will allow us to detect failed installations
715+
$installIndicatorPath = Join-Path $installPath '.incomplete'
716+
717+
if (Test-Path $installIndicatorPath) {
718+
#FIXME: Output inside a threadjob is not surfaced to the user.
719+
Write-Warning "$($context.Module): Incomplete installation found at $installPath. Will delete and retry."
720+
Remove-Item $installPath -Recurse -Force
721+
}
722+
723+
if (-not (Test-Path $context.InstallPath)) {
724+
New-Item -Path $context.InstallPath -ItemType Directory -Force | Out-Null
725+
}
726+
727+
New-Item -ItemType File -Path $installIndicatorPath -Force | Out-Null
713728

714729
$zip = [IO.Compression.ZipArchive]::new($stream, 'Read')
715730
[IO.Compression.ZipFileExtensions]::ExtractToDirectory($zip, $installPath)
@@ -722,6 +737,7 @@ function Install-ModuleFastHelper {
722737
} | Remove-Item -Force -Recurse
723738
($zip).Dispose()
724739
($stream).Dispose()
740+
Remove-Item $installIndicatorPath -Force
725741
return $context
726742
}
727743
$installJob
@@ -1037,7 +1053,7 @@ class ModuleFastSpec {
10371053
$guid = $this._Guid -ne [Guid]::Empty ? " [$($this._Guid)]" : ''
10381054
$versionRange = $this._VersionRange.ToString() -eq '(, )' ? '' : " $($this._VersionRange)"
10391055
if ($this._VersionRange.MaxVersion -eq $this._VersionRange.MinVersion) {
1040-
$versionRange = "=$($this._VersionRange.MinVersion)"
1056+
$versionRange = "($($this._VersionRange.MinVersion))"
10411057
}
10421058
return "$($this._Name)$guid$versionRange"
10431059
}
@@ -1348,6 +1364,13 @@ function Find-LocalModule {
13481364
[version]$version = $candidateItem.Item1
13491365
[string]$folder = $candidateItem.Item2
13501366

1367+
#Make sure this isn't an incomplete installation
1368+
if (Test-Path (Join-Path $folder '.incomplete')) {
1369+
Write-Warning "${ModuleSpec}: Incomplete installation detected at $folder. Deleting and ignoring."
1370+
Remove-Item $folder -Recurse -Force
1371+
continue
1372+
}
1373+
13511374
#Read the module manifest to check for prerelease versions.
13521375
$manifestName = "$($ModuleSpec.Name).psd1"
13531376
$versionedManifestPath = [Directory]::GetFiles($folder, $manifestName, [EnumerationOptions]@{MatchCasing = 'CaseInsensitive' })

ModuleFast.tests.ps1

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,4 +605,13 @@ Describe 'Install-ModuleFast' -Tag 'E2E' {
605605
| Should -Be 'prerelease' -Because 'CI lock file should have 0.1 prerelease even if 0.2 is available'
606606
#TODO: CI Content
607607
}
608+
609+
It 'Handles an incomplete installation' {
610+
$incompleteItemPath = "$installTempPath\PreReleaseTest\0.0.1\.incomplete"
611+
Install-ModuleFast @imfParams -Specification 'PreReleaseTest=0.0.1'
612+
New-Item -ItemType File -Path $incompleteItemPath
613+
Install-ModuleFast @imfParams -Specification 'PreReleaseTest=0.0.1' -Update -WarningVariable actual 3>$null
614+
$actual | Should -BeLike '*incomplete installation detected*'
615+
Test-Path $incompleteItemPath | Should -BeFalse
616+
}
608617
}

requires.lock.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"PrereleaseTest": "0.0.1"
3+
}

0 commit comments

Comments
 (0)