From c3e4e476d83a1f08189faba444c5dcb6e300bbd6 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 4 May 2025 16:41:48 +1000 Subject: [PATCH 1/4] Fixed module path not found with pre-release #2889 --- docs/changelog.md | 3 ++ src/PSRule/Pipeline/SourcePipelineBuilder.cs | 28 +++++++++++++++---- tests/PSRule.Tests/PSRule.Tests.csproj | 19 +++++++++++++ .../SourcePipelineBuilderTests.cs | 21 ++++++++++++-- .../PSRule.Tests/TestModule8/TestModule8.psd1 | 8 +++--- 5 files changed, 66 insertions(+), 13 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 9d162c7bca..a681f43034 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -39,6 +39,9 @@ What's changed since pre-release v3.0.0-B0453: [#2824](https://github.com/microsoft/PSRule/issues/2824) - Bump vscode engine to v1.99.1. [#2858](https://github.com/microsoft/PSRule/pull/2858) +- Bug fixes: + - Fixed module path not found with pre-release by @BernieWhite. + [#2889](https://github.com/microsoft/PSRule/issues/2889) ## v3.0.0-B0453 (pre-release) diff --git a/src/PSRule/Pipeline/SourcePipelineBuilder.cs b/src/PSRule/Pipeline/SourcePipelineBuilder.cs index 32279dfe1b..67bc8aa858 100644 --- a/src/PSRule/Pipeline/SourcePipelineBuilder.cs +++ b/src/PSRule/Pipeline/SourcePipelineBuilder.cs @@ -162,7 +162,7 @@ public void ModuleByName(string name, string? version = null) Source(new Source(info, files, dependency: false)); - // Import dependencies + // TODO: Import dependencies //for (var i = 0; module.RequiredModules != null && i < module.RequiredModules.Count; i++) // Module(module.RequiredModules[i], dependency: true); } @@ -182,15 +182,17 @@ private bool TryPackagedModuleFromCache(string name, string? version, out string if (_CachePath == null) return false; - Log($"[PSRule][S] -- Searching for module in: {_CachePath}"); - if (!string.IsNullOrEmpty(version)) + Log($"[PSRule][S] -- Searching for module in cache: {_CachePath}"); + if (!string.IsNullOrEmpty(version) && version != null) { - path = Environment.GetRootedBasePath(Path.Combine(_CachePath, "Modules", name, version)); + path = Environment.GetRootedBasePath(Path.Combine(_CachePath, "Modules", name, GetVersionPath(version))); + Log($"[PSRule][S] -- Search for module by version in: {path}"); if (File.Exists(Path.Combine(path, GetManifestName(name)))) return true; } path = Environment.GetRootedBasePath(Path.Combine(_CachePath, "Modules", name)); + Log($"[PSRule][S] -- Search for module as version-less in: {path}"); return File.Exists(Path.Combine(path, GetManifestName(name))); } @@ -211,9 +213,9 @@ private bool TryInstalledModule(string name, string? version, out string? path) var searchPath = Environment.GetRootedBasePath(Path.Combine(searchPaths[i], name)); // Try a specific version. - if (!string.IsNullOrEmpty(version)) + if (!string.IsNullOrEmpty(version) && version != null) { - var versionPath = Path.Combine(searchPath, version); + var versionPath = Path.Combine(searchPath, GetVersionPath(version)); var manifestPath = Path.Combine(versionPath, GetManifestName(name)); if (File.Exists(manifestPath)) { @@ -247,6 +249,20 @@ private bool TryInstalledModule(string name, string? version, out string? path) return sorted.Length > 0; } + /// + /// Get the path for version lookups which ignore pre-release version and build segments. + /// If version is pre-release, use major.minor.patch. + /// + /// Return a version string just containing major.minor.patch. + private static string GetVersionPath(string version) + { + if (version.Contains('-') || version.Contains('+')) + { + version = version.Split('-', '+')[0]; + } + return version; + } + private static string[] SortModulePath(IEnumerable values) { var results = values.ToArray(); diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 0c408ee216..40763e8308 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -184,4 +184,23 @@ + + + + + + + + + + + + + diff --git a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs index 675c5dd440..f7c236d2d6 100644 --- a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs +++ b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs @@ -30,7 +30,7 @@ public void Add_directory() var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(25, sources[0].File.Length); + Assert.Equal(27, sources[0].File.Length); } [Fact] @@ -93,7 +93,7 @@ public void Add_directory_with_disable_powershell() var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(20, sources[0].File.Length); + Assert.Equal(21, sources[0].File.Length); } [Fact] @@ -106,6 +106,21 @@ public void Add_directory_with_module_only() var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(20, sources[0].File.Length); + Assert.Equal(21, sources[0].File.Length); + } + + [Theory] + [InlineData("TestModule7", "0.0.1")] + [InlineData("TestModule8", "0.0.1-Alpha")] + public void ModuleByName_WithNameAndVersion_ShouldAddModuleFiles(string name, string version) + { + var builder = new SourcePipelineBuilder(null, null, cachePath: GetSourcePath("")); + builder.ModuleByName(name: name, version: version); + var sources = builder.Build(); + + Assert.Single(sources); + Assert.Equal(name, sources[0].Module.Name); + Assert.Equal(version, sources[0].Module.FullVersion); + Assert.NotEmpty(sources[0].File); } } diff --git a/tests/PSRule.Tests/TestModule8/TestModule8.psd1 b/tests/PSRule.Tests/TestModule8/TestModule8.psd1 index 45bcf7699e..35cfd3993b 100644 --- a/tests/PSRule.Tests/TestModule8/TestModule8.psd1 +++ b/tests/PSRule.Tests/TestModule8/TestModule8.psd1 @@ -20,13 +20,13 @@ ModuleVersion = '0.0.1' GUID = 'ff869216-0ca8-42e8-930f-5728719d4f5d' # Author of this module -Author = 'Armaan Mcleod' +Author = 'Bernie White' # Company or vendor of this module -CompanyName = 'Armaan Mcleod' +CompanyName = 'Bernie White' # Copyright statement for this module -Copyright = '(c) Armaan Mcleod. All rights reserved.' +Copyright = '(c) Bernie White. All rights reserved.' # Description of the functionality provided by this module Description = 'A module for Pester testing for PSRule.' @@ -109,7 +109,7 @@ PrivateData = @{ # ReleaseNotes = '' # Prerelease string of this module - # Prerelease = '' + Prerelease = 'Alpha' # Flag to indicate whether the module requires explicit user acceptance for install/update/save # RequireLicenseAcceptance = $false From bd91253b4a7e3ddaf49032e902d2a19098b121de Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 4 May 2025 08:55:39 +0000 Subject: [PATCH 2/4] Additional updates --- .../SourcePipelineBuilderTests.cs | 85 +++++++++++-------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs index f7c236d2d6..ad1a8f8c05 100644 --- a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs +++ b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs @@ -11,108 +11,119 @@ namespace PSRule; /// public sealed class SourcePipelineBuilderTests : BaseTests { - [Fact] - public void Add_single_file() + [Theory] + [InlineData("FromFile.Rule.ps1")] + [InlineData("John's Documents/FromFileBaseline.Rule.ps1")] + public void Directory_WithSingleFile_ShouldFindCount(string path) { var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); Assert.Single(sources[0].File); + Assert.Equal(GetSourcePath(path), sources[0].File[0].Path); } - [Fact] - public void Add_directory() + [Theory] + [InlineData("FromFile.Rule.yaml", 1)] + public void Directory_WithSingleFileAndDisablePowerShell_ShouldFindCount(string path, int count) { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("")); + var option = GetOption(); + option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; + var builder = new SourcePipelineBuilder(null, option); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(27, sources[0].File.Length); + Assert.Equal(count, sources[0].File.Length); } - [Fact] - public void Add_script_file_with_disable_powershell() + [Theory] + [InlineData("FromFile.Rule.ps1")] + public void Directory_WithScriptFileAndDisablePowerShell_ShouldNotFindAny(string path) { var option = GetOption(); option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Empty(sources); } - [Fact] - public void Add_script_file_with_module_only() + [Theory] + [InlineData("FromFile.Rule.yaml", 1)] + public void Directory_WithSingleFileAndModuleOnly_ShouldFindCount(string path, int count) { var option = GetOption(); option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); - Assert.Empty(sources); + Assert.Single(sources); + Assert.Equal(count, sources[0].File.Length); } - [Fact] - public void Add_yaml_file_with_disable_powershell() + [Theory] + [InlineData("FromFile.Rule.ps1")] + public void Directory_WithScriptFileAndModuleOnly_ShouldNotFindAny(string path) { var option = GetOption(); - option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; + option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("FromFile.Rule.yaml")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); - Assert.Single(sources); - Assert.Single(sources[0].File); + Assert.Empty(sources); } - [Fact] - public void Add_yaml_file_with_module_only() + [Theory] + [InlineData("", 28)] + [InlineData("John's Documents", 1)] + public void Directory_WithDirectory_ShouldFindCount(string path, int count) { - var option = GetOption(); - option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; - var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("FromFile.Rule.yaml")); + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); - Assert.Single(sources[0].File); + Assert.Equal(count, sources[0].File.Length); } - [Fact] - public void Add_directory_with_disable_powershell() + [Theory] + [InlineData("", 21)] + public void Directory_WithDirectoryAndDisablePowerShell_ShouldFindCount(string path, int count) { var option = GetOption(); option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(21, sources[0].File.Length); + Assert.Equal(count, sources[0].File.Length); } - [Fact] - public void Add_directory_with_module_only() + [Theory] + [InlineData("", 21)] + public void Directory_WithDirectoryAndModuleOnly_ShouldFindCount(string path, int count) { var option = GetOption(); option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(21, sources[0].File.Length); + Assert.Equal(count, sources[0].File.Length); } [Theory] [InlineData("TestModule7", "0.0.1")] [InlineData("TestModule8", "0.0.1-Alpha")] - public void ModuleByName_WithNameAndVersion_ShouldAddModuleFiles(string name, string version) + public void ModuleByName_WithNameAndVersion_ShouldFindModuleFiles(string name, string version) { var builder = new SourcePipelineBuilder(null, null, cachePath: GetSourcePath("")); builder.ModuleByName(name: name, version: version); From 3fbf3cdc410df2387b9523cd7294ae604a3d6314 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 4 May 2025 22:06:26 +1000 Subject: [PATCH 3/4] Fix --- tests/PSRule.Tests/SourcePipelineBuilderTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs index ad1a8f8c05..eb117101e0 100644 --- a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs +++ b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs @@ -13,7 +13,6 @@ public sealed class SourcePipelineBuilderTests : BaseTests { [Theory] [InlineData("FromFile.Rule.ps1")] - [InlineData("John's Documents/FromFileBaseline.Rule.ps1")] public void Directory_WithSingleFile_ShouldFindCount(string path) { var builder = new SourcePipelineBuilder(null, null); @@ -80,8 +79,7 @@ public void Directory_WithScriptFileAndModuleOnly_ShouldNotFindAny(string path) } [Theory] - [InlineData("", 28)] - [InlineData("John's Documents", 1)] + [InlineData("", 27)] public void Directory_WithDirectory_ShouldFindCount(string path, int count) { var builder = new SourcePipelineBuilder(null, null); From e44a5a867f249f1f19073dc2d00662f9803889c0 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 5 May 2025 12:28:53 +0000 Subject: [PATCH 4/4] Fixes --- tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tests/SourcePipelineBuilderTests.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 979910137d..d277066453 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -183,7 +183,7 @@ PreserveNewest - +