From 6c21b047ab15ebd408cad8e290988c3dca2b4fcc Mon Sep 17 00:00:00 2001 From: Bernie White Date: Wed, 25 Sep 2024 03:12:51 +1000 Subject: [PATCH] Updates for module add upgrade and restore CLI commands #2550 #2551 (#2559) --- docs/CHANGELOG-v3.md | 9 +++ docs/concepts/cli/module.md | 23 ++++++- schemas/PSRule-lock.schema.json | 5 ++ .../Commands/ModuleCommand.cs | 62 ++++++++++++------- .../Models/ModuleOptions.cs | 17 +++-- src/PSRule.Tool/ClientBuilder.cs | 61 +++++++++++------- .../Resources/CmdStrings.Designer.cs | 24 ++++++- src/PSRule.Tool/Resources/CmdStrings.resx | 10 ++- src/PSRule.Types/Data/SemanticVersion.cs | 51 +++++++++------ src/PSRule.Types/Properties/AssemblyInfo.cs | 1 + src/PSRule/Pipeline/Dependencies/LockEntry.cs | 7 +++ .../ModuleConstraintTests.cs | 13 ---- .../PSRule.CommandLine.Tests.csproj | 2 +- .../PSRule.Tool.Tests.csproj | 2 +- .../Data}/SemanticVersionTests.cs | 30 +++++---- 15 files changed, 217 insertions(+), 100 deletions(-) delete mode 100644 tests/PSRule.CommandLine.Tests/ModuleConstraintTests.cs rename tests/{PSRule.Tests => PSRule.Types.Tests/Data}/SemanticVersionTests.cs (92%) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index b0ce89548..b6dc765c3 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -29,6 +29,15 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since pre-release v3.0.0-B0267: +- New features: + - Allow CLI upgrade command to upgrade a single module by @BernieWhite. + [#2551](https://github.com/microsoft/PSRule/issues/2551) + - A single or specific modules can be upgraded by name when using `module upgrade`. + - By default, all modules are upgraded. + - Allow CLI to install pre-release modules by @BernieWhite. + [#2550](https://github.com/microsoft/PSRule/issues/2550) + - Add and upgrade pre-release modules with `--prerelease`. + - Pre-release modules will be restored from the lock file with `module restore`. - General improvements: - **Breaking change**: Empty version comparison only accepts stable versions by default by @BernieWhite. [#2557](https://github.com/microsoft/PSRule/issues/2557) diff --git a/docs/concepts/cli/module.md b/docs/concepts/cli/module.md index 337a71843..b595c4e88 100644 --- a/docs/concepts/cli/module.md +++ b/docs/concepts/cli/module.md @@ -67,6 +67,9 @@ Optional parameters: - `--version` - Specifies a specific version of the module to add. By default, the latest stable version of the module is added. Any required version constraints set by the `Requires` option are taken into consideration. +- `--prerelease` - Accept pre-release versions in addition to stable module versions. + By default, pre-release versions are not included. + A pre-release version may also be accepted when `Requires` includes pre-releases. For example: @@ -80,6 +83,12 @@ For example, a specific version of the module is added: ps-rule module add PSRule.Rules.Azure --version 1.32.1 ``` +For example, include pre-release versions added: + +```bash title="PSRule CLI command-line" +ps-rule module add PSRule.Rules.Azure --prerelease +``` + ## `module remove` Remove one or more modules from the lock file. @@ -112,7 +121,13 @@ ps-rule module restore --force ## `module upgrade` -Upgrade to the latest versions any modules within the lock file. +Upgrade to the latest versions for all or a specific module within the lock file. + +Optional parameters: + +- `--prerelease` - Accept pre-release versions in addition to stable module versions. + By default, pre-release versions are not included. + A pre-release version may also be accepted when `Requires` includes pre-releases. For example: @@ -120,6 +135,12 @@ For example: ps-rule module upgrade ``` +For example, upgrade a specific module and include pre-release versions: + +```bash title="PSRule CLI command-line" +ps-rule module upgrade PSRule.Rules.Azure --prerelease +``` + ## Next steps For more information on the module lock file, see [Lock file](../lockfile.md). diff --git a/schemas/PSRule-lock.schema.json b/schemas/PSRule-lock.schema.json index 543b7f430..edee473fb 100644 --- a/schemas/PSRule-lock.schema.json +++ b/schemas/PSRule-lock.schema.json @@ -23,6 +23,11 @@ "type": "string", "title": "Module version", "description": "The version of the module to use." + }, + "includePrerelease": { + "type": "boolean", + "title": "Include prerelease", + "description": "Accept pre-release versions in addition to stable module versions." } }, "required": [ diff --git a/src/PSRule.CommandLine/Commands/ModuleCommand.cs b/src/PSRule.CommandLine/Commands/ModuleCommand.cs index 26d6cffab..92b3c59bf 100644 --- a/src/PSRule.CommandLine/Commands/ModuleCommand.cs +++ b/src/PSRule.CommandLine/Commands/ModuleCommand.cs @@ -17,7 +17,6 @@ using PSRule.Pipeline.Dependencies; using SemanticVersion = PSRule.Data.SemanticVersion; using NuGet.Packaging; using NuGet.Common; -using PSRule.Pipeline; namespace PSRule.CommandLine.Commands; @@ -51,7 +50,7 @@ public sealed class ModuleCommand var requires = clientContext.Option.Requires.ToDictionary(); var file = LockFile.Read(null); - using var pwsh = PowerShell.Create(); + using var pwsh = CreatePowerShell(); // Restore from the lock file. foreach (var kv in file.Modules) @@ -70,9 +69,11 @@ public sealed class ModuleCommand var idealVersion = await FindVersionAsync(module, null, targetVersion, null, cancellationToken); if (idealVersion != null) - await InstallVersionAsync(clientContext, module, idealVersion.ToString(), cancellationToken); + { + installedVersion = await InstallVersionAsync(clientContext, module, idealVersion, cancellationToken); + } - if (pwsh.HadErrors || (idealVersion == null && installedVersion == null)) + if (pwsh.HadErrors || idealVersion == null || installedVersion == null) { exitCode = ERROR_MODULE_FAILED_TO_INSTALL; clientContext.LogError(Messages.Error_501, module, targetVersion); @@ -109,7 +110,7 @@ public sealed class ModuleCommand var idealVersion = await FindVersionAsync(includeModule, moduleConstraint, null, null, cancellationToken); if (idealVersion != null) { - await InstallVersionAsync(clientContext, includeModule, idealVersion.ToString(), cancellationToken); + await InstallVersionAsync(clientContext, includeModule, idealVersion, cancellationToken); } else if (idealVersion == null) { @@ -143,8 +144,7 @@ public sealed class ModuleCommand var exitCode = 0; var requires = clientContext.Option.Requires.ToDictionary(); var file = !operationOptions.Force ? LockFile.Read(null) : new LockFile(); - - using var pwsh = PowerShell.Create(); + using var pwsh = CreatePowerShell(); // Add for any included modules. if (clientContext.Option?.Include?.Module != null && clientContext.Option.Include.Module.Length > 0) @@ -197,8 +197,7 @@ public sealed class ModuleCommand var exitCode = 0; var requires = clientContext.Option.Requires.ToDictionary(); var file = LockFile.Read(null); - - var pwsh = PowerShell.Create(); + var pwsh = CreatePowerShell(); if (exitCode == 0) { @@ -218,13 +217,13 @@ public sealed class ModuleCommand var requires = clientContext.Option.Requires.ToDictionary(); var file = LockFile.Read(null); - using var pwsh = PowerShell.Create(); + using var pwsh = CreatePowerShell(); foreach (var module in operationOptions.Module) { if (!file.Modules.TryGetValue(module, out var item) || operationOptions.Force) { // Get a constraint if set from options. - var moduleConstraint = requires.TryGetValue(module, out var c) ? c : ModuleConstraint.Any(module, includePrerelease: false); + var moduleConstraint = requires.TryGetValue(module, out var c) ? c : ModuleConstraint.Any(module, includePrerelease: operationOptions.Prerelease); // Get target version if specified in command-line. var targetVersion = !string.IsNullOrEmpty(operationOptions.Version) && SemanticVersion.TryParseVersion(operationOptions.Version, out var v) && v != null ? v : null; @@ -250,7 +249,8 @@ public sealed class ModuleCommand clientContext.LogVerbose(Messages.UsingModule, module, idealVersion.ToString()); item = new LockEntry { - Version = idealVersion + Version = idealVersion, + IncludePrerelease = operationOptions.Prerelease && !idealVersion.Stable ? true : null, }; file.Modules[module] = item; } @@ -281,7 +281,7 @@ public sealed class ModuleCommand var file = LockFile.Read(null); - using var pwsh = PowerShell.Create(); + using var pwsh = CreatePowerShell(); foreach (var module in operationOptions.Module) { if (file.Modules.TryGetValue(module, out var constraint)) @@ -311,12 +311,13 @@ public sealed class ModuleCommand var exitCode = 0; var requires = clientContext.Option.Requires.ToDictionary(); var file = LockFile.Read(null); + var filteredModules = operationOptions.Module != null && operationOptions.Module.Length > 0 ? new HashSet(operationOptions.Module, StringComparer.OrdinalIgnoreCase) : null; - using var pwsh = PowerShell.Create(); - foreach (var kv in file.Modules) + using var pwsh = CreatePowerShell(); + foreach (var kv in file.Modules.Where(m => filteredModules == null || filteredModules.Contains(m.Key))) { // Get a constraint if set from options. - var moduleConstraint = requires.TryGetValue(kv.Key, out var c) ? c : ModuleConstraint.Any(kv.Key, includePrerelease: false); + var moduleConstraint = requires.TryGetValue(kv.Key, out var c) ? c : ModuleConstraint.Any(kv.Key, includePrerelease: kv.Value.IncludePrerelease ?? operationOptions.Prerelease); // Find the ideal version. var idealVersion = await FindVersionAsync(kv.Key, moduleConstraint, null, null, cancellationToken); @@ -332,6 +333,7 @@ public sealed class ModuleCommand clientContext.LogVerbose(Messages.UsingModule, kv.Key, idealVersion.ToString()); kv.Value.Version = idealVersion; + kv.Value.IncludePrerelease = (kv.Value.IncludePrerelease.GetValueOrDefault(false) || operationOptions.Prerelease) && !idealVersion.Stable ? true : null; file.Modules[kv.Key] = kv.Value; } @@ -461,24 +463,26 @@ public sealed class ModuleCommand return result; } - private static async Task InstallVersionAsync([DisallowNull] ClientContext context, [DisallowNull] string name, [DisallowNull] string version, CancellationToken cancellationToken) + private static async Task InstallVersionAsync([DisallowNull] ClientContext context, [DisallowNull] string name, [DisallowNull] SemanticVersion.Version version, CancellationToken cancellationToken) { context.LogVerbose(Messages.RestoringModule, name, version); var cache = new SourceCacheContext(); var logger = new NullLogger(); var resource = await GetSourceRepositoryAsync(); + var stringVersion = version.ToString(); - var packageVersion = new NuGetVersion(version); + var packageVersion = new NuGetVersion(stringVersion); using var packageStream = new MemoryStream(); - await resource.CopyNupkgToStreamAsync( + if (!await resource.CopyNupkgToStreamAsync( name, packageVersion, packageStream, cache, logger, - cancellationToken); + cancellationToken)) + return null; using var packageReader = new PackageArchiveReader(packageStream); var nuspecReader = await packageReader.GetNuspecReaderAsync(cancellationToken); @@ -489,6 +493,7 @@ public sealed class ModuleCommand if (Directory.Exists(modulePath)) Directory.Delete(modulePath, true); + var count = 0; var files = packageReader.GetFiles(); packageReader.CopyFiles(modulePath, files, (name, targetPath, s) => { @@ -496,10 +501,18 @@ public sealed class ModuleCommand return null; s.CopyToFile(targetPath); + count++; return targetPath; }, logger, cancellationToken); + + // Check module path exists. + if (!Directory.Exists(modulePath)) + return null; + + context.LogVerbose("Module saved to: {0} -- {1}", name, modulePath); + return count > 0 ? version : null; } private static async Task GetSourceRepositoryAsync() @@ -509,9 +522,9 @@ public sealed class ModuleCommand return await repository.GetResourceAsync(); } - private static string GetModulePath(ClientContext context, string name, string version) + private static string GetModulePath(ClientContext context, string name, [DisallowNull] SemanticVersion.Version version) { - return Path.Combine(context.CachePath, MODULES_PATH, name, version); + return Path.Combine(context.CachePath, MODULES_PATH, name, version.ToShortString()); } private static bool ShouldIgnorePackageFile(string name) @@ -520,5 +533,10 @@ public sealed class ModuleCommand string.Equals(name, "_rels/.rels", StringComparison.OrdinalIgnoreCase); } + private static PowerShell CreatePowerShell() + { + return PowerShell.Create(); + } + #endregion Helper methods } diff --git a/src/PSRule.CommandLine/Models/ModuleOptions.cs b/src/PSRule.CommandLine/Models/ModuleOptions.cs index 06289d147..0fcbddf1d 100644 --- a/src/PSRule.CommandLine/Models/ModuleOptions.cs +++ b/src/PSRule.CommandLine/Models/ModuleOptions.cs @@ -4,32 +4,37 @@ namespace PSRule.CommandLine.Models; /// -/// +/// Options for the module command. /// public sealed class ModuleOptions { /// - /// + /// A specific path to use for the operation. /// public string[]? Path { get; set; } /// - /// + /// The name of any specified modules. /// public string[]? Module { get; set; } /// - /// + /// Determines if the module is overridden if it already exists. /// public bool Force { get; set; } /// - /// + /// The target module version. /// public string? Version { get; set; } /// - /// + /// Determines if verification that the module exists is skipped. /// public bool SkipVerification { get; set; } + + /// + /// Accept pre-release versions in addition to stable module versions. + /// + public bool Prerelease { get; set; } } diff --git a/src/PSRule.Tool/ClientBuilder.cs b/src/PSRule.Tool/ClientBuilder.cs index 0f2efcab1..72207a80d 100644 --- a/src/PSRule.Tool/ClientBuilder.cs +++ b/src/PSRule.Tool/ClientBuilder.cs @@ -29,6 +29,7 @@ internal sealed class ClientBuilder private readonly Option _Module_Add_Version; private readonly Option _Module_Add_Force; private readonly Option _Module_Add_SkipVerification; + private readonly Option _Module_Prerelease; private readonly Option _Global_Path; private readonly Option _Run_OutputPath; private readonly Option _Run_OutputFormat; @@ -43,70 +44,74 @@ internal sealed class ClientBuilder // Global options. _Global_Option = new Option( - new string[] { "--option" }, + ["--option"], getDefaultValue: () => "ps-rule.yaml", description: CmdStrings.Global_Option_Description ); _Global_Verbose = new Option( - new string[] { "--verbose" }, + ["--verbose"], description: CmdStrings.Global_Verbose_Description ); _Global_Debug = new Option( - new string[] { "--debug" }, + ["--debug"], description: CmdStrings.Global_Debug_Description ); _Global_Path = new Option( - new string[] { "-p", "--path" }, + ["-p", "--path"], description: CmdStrings.Global_Path_Description ); // Options for the run command. _Run_OutputPath = new Option( - new string[] { "--output-path" }, + ["--output-path"], description: CmdStrings.Run_OutputPath_Description ); _Run_OutputFormat = new Option( - new string[] { "-o", "--output" }, + ["-o", "--output"], description: CmdStrings.Run_OutputFormat_Description ); _Run_InputPath = new Option( - new string[] { "-f", "--input-path" }, + ["-f", "--input-path"], description: CmdStrings.Run_InputPath_Description ); _Run_Module = new Option( - new string[] { "-m", "--module" }, + ["-m", "--module"], description: CmdStrings.Run_Module_Description ); _Run_Baseline = new Option( - new string[] { "--baseline" }, + ["--baseline"], description: CmdStrings.Run_Baseline_Description ); _Run_Outcome = new Option( - new string[] { "--outcome" }, + ["--outcome"], description: CmdStrings.Run_Outcome_Description ).FromAmong("Pass", "Fail", "Error", "Processed", "Problem"); _Run_Outcome.Arity = ArgumentArity.ZeroOrMore; // Options for the module command. _Module_Init_Force = new Option( - new string[] { ARG_FORCE }, + [ARG_FORCE], description: CmdStrings.Module_Init_Force_Description ); _Module_Add_Version = new Option ( - new string[] { "--version" }, + ["--version"], description: CmdStrings.Module_Add_Version_Description ); _Module_Add_Force = new Option( - new string[] { ARG_FORCE }, + [ARG_FORCE], description: CmdStrings.Module_Add_Force_Description ); _Module_Add_SkipVerification = new Option( - new string[] { "--skip-verification" }, + ["--skip-verification"], description: CmdStrings.Module_Add_SkipVerification_Description ); + _Module_Prerelease = new Option( + ["--prerelease"], + description: CmdStrings.Module_Prerelease_Description + ); _Module_Restore_Force = new Option( - new string[] { ARG_FORCE }, + [ARG_FORCE], description: CmdStrings.Module_Restore_Force_Description ); @@ -165,12 +170,12 @@ internal sealed class ClientBuilder { var cmd = new Command("module", CmdStrings.Module_Description); - var moduleArg = new Argument + var requiredModuleArg = new Argument ( "module", - CmdStrings.Module_Module_Description + CmdStrings.Module_RequiredModule_Description ); - moduleArg.Arity = ArgumentArity.OneOrMore; + requiredModuleArg.Arity = ArgumentArity.OneOrMore; // Init var init = new Command @@ -219,19 +224,21 @@ internal sealed class ClientBuilder "add", CmdStrings.Module_Add_Description ); - add.AddArgument(moduleArg); + add.AddArgument(requiredModuleArg); add.AddOption(_Module_Add_Version); add.AddOption(_Module_Add_Force); add.AddOption(_Module_Add_SkipVerification); + add.AddOption(_Module_Prerelease); add.SetHandler(async (invocation) => { var option = new ModuleOptions { Path = invocation.ParseResult.GetValueForOption(_Global_Path), - Module = invocation.ParseResult.GetValueForArgument(moduleArg), + Module = invocation.ParseResult.GetValueForArgument(requiredModuleArg), Version = invocation.ParseResult.GetValueForOption(_Module_Add_Version), Force = invocation.ParseResult.GetValueForOption(_Module_Add_Force), SkipVerification = invocation.ParseResult.GetValueForOption(_Module_Add_SkipVerification), + Prerelease = invocation.ParseResult.GetValueForOption(_Module_Prerelease), }; var client = GetClientContext(invocation); @@ -244,13 +251,13 @@ internal sealed class ClientBuilder "remove", CmdStrings.Module_Remove_Description ); - remove.AddArgument(moduleArg); + remove.AddArgument(requiredModuleArg); remove.SetHandler(async (invocation) => { var option = new ModuleOptions { Path = invocation.ParseResult.GetValueForOption(_Global_Path), - Module = invocation.ParseResult.GetValueForArgument(moduleArg), + Module = invocation.ParseResult.GetValueForArgument(requiredModuleArg), }; var client = GetClientContext(invocation); @@ -263,11 +270,21 @@ internal sealed class ClientBuilder "upgrade", CmdStrings.Module_Upgrade_Description ); + var optionalModuleArg = new Argument + ( + "module", + CmdStrings.Module_OptionalModule_Description + ); + optionalModuleArg.Arity = ArgumentArity.ZeroOrMore; + upgrade.AddArgument(optionalModuleArg); + upgrade.AddOption(_Module_Prerelease); upgrade.SetHandler(async (invocation) => { var option = new ModuleOptions { Path = invocation.ParseResult.GetValueForOption(_Global_Path), + Module = invocation.ParseResult.GetValueForArgument(requiredModuleArg), + Prerelease = invocation.ParseResult.GetValueForOption(_Module_Prerelease), }; var client = GetClientContext(invocation); diff --git a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs index 2ef61e47f..b468a88bf 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs +++ b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs @@ -178,11 +178,20 @@ namespace PSRule.Tool.Resources { } /// - /// Looks up a localized string similar to The name of one or more modules.. + /// Looks up a localized string similar to Optionally specifies one or more modules to apply to.. /// - internal static string Module_Module_Description { + internal static string Module_OptionalModule_Description { get { - return ResourceManager.GetString("Module_Module_Description", resourceCulture); + return ResourceManager.GetString("Module_OptionalModule_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Accept pre-release versions in addition to stable module versions.. + /// + internal static string Module_Prerelease_Description { + get { + return ResourceManager.GetString("Module_Prerelease_Description", resourceCulture); } } @@ -195,6 +204,15 @@ namespace PSRule.Tool.Resources { } } + /// + /// Looks up a localized string similar to The name of one or more modules.. + /// + internal static string Module_RequiredModule_Description { + get { + return ResourceManager.GetString("Module_RequiredModule_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Restore from the module lock file and configured options.. /// diff --git a/src/PSRule.Tool/Resources/CmdStrings.resx b/src/PSRule.Tool/Resources/CmdStrings.resx index 4df7b89b9..17470763d 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.resx +++ b/src/PSRule.Tool/Resources/CmdStrings.resx @@ -141,9 +141,12 @@ Manage or restore modules tracked by the module lock file and configured options. - + The name of one or more modules. + + Optionally specifies one or more modules to apply to. + Remove one or more modules from the lock file. @@ -192,4 +195,7 @@ Specifies a path to write results to. - \ No newline at end of file + + Accept pre-release versions in addition to stable module versions. + + diff --git a/src/PSRule.Types/Data/SemanticVersion.cs b/src/PSRule.Types/Data/SemanticVersion.cs index 0dddd1a35..0f0138bfb 100644 --- a/src/PSRule.Types/Data/SemanticVersion.cs +++ b/src/PSRule.Types/Data/SemanticVersion.cs @@ -346,7 +346,8 @@ public static class SemanticVersion [DebuggerDisplay("{_VersionString}")] public sealed class Version : IComparable, IEquatable { - private readonly string _VersionString; + private string? _VersionString; + private string? _ShortVersionString; /// /// The major part of the version. @@ -380,8 +381,6 @@ public static class SemanticVersion Patch = patch; Prerelease = prerelease; Build = build; - - _VersionString = GetVersionString(); } /// @@ -397,10 +396,20 @@ public static class SemanticVersion return TryParseVersion(value, out version); } - /// + /// + /// Get the version as a string. + /// public override string ToString() { - return _VersionString; + return _VersionString ??= GetVersionString(simple: false); + } + + /// + /// Get the version as a string returning only the major.minor.patch part of the version. + /// + public string ToShortString() + { + return _ShortVersionString ??= GetVersionString(simple: true); } /// @@ -480,10 +489,14 @@ public static class SemanticVersion return Prerelease != other.Prerelease ? PR.Compare(Prerelease, other.Prerelease) : 0; } - private string GetVersionString() + /// + /// Returns a version string. + /// + /// When true, only return the major.minor.patch version. + private string GetVersionString(bool simple = false) { - var count = 5 + (Prerelease != null && !Prerelease.Stable ? 2 : 0) + (Build != null && Build.Length > 0 ? 2 : 0); - var parts = new object[count]; + var size = 5 + (!simple && Prerelease != null && !Prerelease.Stable ? 2 : 0) + (!simple && Build != null && Build.Length > 0 ? 2 : 0); + var parts = new object[size]; parts[0] = Major; parts[1] = DOT; @@ -491,19 +504,21 @@ public static class SemanticVersion parts[3] = DOT; parts[4] = Patch; - var next = 5; - if (Prerelease != null && !Prerelease.Stable) + if (size > 5) { - parts[next++] = DASH; - parts[next++] = Prerelease.Value; - } + var next = 5; + if (Prerelease != null && !Prerelease.Stable) + { + parts[next++] = DASH; + parts[next++] = Prerelease.Value; + } - if (Build != null && Build.Length > 0) - { - parts[next++] = PLUS; - parts[next++] = Build; + if (Build != null && Build.Length > 0) + { + parts[next++] = PLUS; + parts[next++] = Build; + } } - return string.Concat(parts); } } diff --git a/src/PSRule.Types/Properties/AssemblyInfo.cs b/src/PSRule.Types/Properties/AssemblyInfo.cs index d08402939..bbd0530f6 100644 --- a/src/PSRule.Types/Properties/AssemblyInfo.cs +++ b/src/PSRule.Types/Properties/AssemblyInfo.cs @@ -6,3 +6,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.PSRule.Core")] [assembly: InternalsVisibleTo("Microsoft.PSRule.Tool")] [assembly: InternalsVisibleTo("PSRule.Tests")] +[assembly: InternalsVisibleTo("PSRule.Types.Tests")] diff --git a/src/PSRule/Pipeline/Dependencies/LockEntry.cs b/src/PSRule/Pipeline/Dependencies/LockEntry.cs index b7952671f..e0afdf7d4 100644 --- a/src/PSRule/Pipeline/Dependencies/LockEntry.cs +++ b/src/PSRule/Pipeline/Dependencies/LockEntry.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.ComponentModel; using Newtonsoft.Json; using PSRule.Data; @@ -16,4 +17,10 @@ public sealed class LockEntry /// [JsonProperty("version", NullValueHandling = NullValueHandling.Include)] public SemanticVersion.Version Version { get; set; } + + /// + /// Accept pre-release versions in addition to stable module versions. + /// + [DefaultValue(null), JsonProperty("includePrerelease", NullValueHandling = NullValueHandling.Ignore)] + public bool? IncludePrerelease { get; set; } } diff --git a/tests/PSRule.CommandLine.Tests/ModuleConstraintTests.cs b/tests/PSRule.CommandLine.Tests/ModuleConstraintTests.cs deleted file mode 100644 index a7fb4a980..000000000 --- a/tests/PSRule.CommandLine.Tests/ModuleConstraintTests.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using PSRule.Data; - -namespace PSRule.CommandLine; - -/// -/// Tests for . -/// -public sealed class ModuleConstraintTests -{ -} diff --git a/tests/PSRule.CommandLine.Tests/PSRule.CommandLine.Tests.csproj b/tests/PSRule.CommandLine.Tests/PSRule.CommandLine.Tests.csproj index 2e92acd2e..a37df408b 100644 --- a/tests/PSRule.CommandLine.Tests/PSRule.CommandLine.Tests.csproj +++ b/tests/PSRule.CommandLine.Tests/PSRule.CommandLine.Tests.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 60cf8eaa6..887ba5613 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tests/SemanticVersionTests.cs b/tests/PSRule.Types.Tests/Data/SemanticVersionTests.cs similarity index 92% rename from tests/PSRule.Tests/SemanticVersionTests.cs rename to tests/PSRule.Types.Tests/Data/SemanticVersionTests.cs index fc44cee22..ad9340173 100644 --- a/tests/PSRule.Tests/SemanticVersionTests.cs +++ b/tests/PSRule.Types.Tests/Data/SemanticVersionTests.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.PowerShell; -using PSRule.Data; - -namespace PSRule; +namespace PSRule.Data; /// /// Tests for semantic version comparison. @@ -18,20 +15,20 @@ public sealed class SemanticVersionTests public void Version() { Assert.True(SemanticVersion.TryParseVersion("1.2.3-alpha.3+7223b39", out var actual1)); - Assert.Equal(1, actual1.Major); + Assert.Equal(1, actual1!.Major); Assert.Equal(2, actual1.Minor); Assert.Equal(3, actual1.Patch); Assert.Equal("alpha.3", actual1.Prerelease.Value); Assert.Equal("7223b39", actual1.Build); Assert.True(SemanticVersion.TryParseVersion("v1.2.3-alpha.3", out var actual2)); - Assert.Equal(1, actual2.Major); + Assert.Equal(1, actual2!.Major); Assert.Equal(2, actual2.Minor); Assert.Equal(3, actual2.Patch); Assert.Equal("alpha.3", actual2.Prerelease.Value); Assert.True(SemanticVersion.TryParseVersion("v1.2.3+7223b39", out var actual3)); - Assert.Equal(1, actual3.Major); + Assert.Equal(1, actual3!.Major); Assert.Equal(2, actual3.Minor); Assert.Equal(3, actual3.Patch); Assert.Equal("7223b39", actual3.Build); @@ -48,11 +45,11 @@ public sealed class SemanticVersionTests Assert.True(SemanticVersion.TryParseVersion("10.0.0", out var actual3)); Assert.True(SemanticVersion.TryParseVersion("1.0.2", out var actual4)); - Assert.True(actual1.CompareTo(actual1) == 0); + Assert.Equal(0, actual1!.CompareTo(actual1)); Assert.True(actual1.CompareTo(actual2) < 0); Assert.True(actual1.CompareTo(actual3) < 0); Assert.True(actual1.CompareTo(actual4) < 0); - Assert.True(actual2.CompareTo(actual2) == 0); + Assert.Equal(0, actual2!.CompareTo(actual2)); Assert.True(actual2.CompareTo(actual1) > 0); Assert.True(actual2.CompareTo(actual3) < 0); Assert.True(actual2.CompareTo(actual4) > 0); @@ -205,7 +202,7 @@ public sealed class SemanticVersionTests var actual7 = new SemanticVersion.PR("beta.11"); var actual8 = new SemanticVersion.PR("rc.1"); - Assert.True(actual1.CompareTo(actual1) == 0); + Assert.Equal(0, actual1.CompareTo(actual1)); Assert.True(actual1.CompareTo(actual2) > 0); Assert.True(actual1.CompareTo(actual6) > 0); Assert.True(actual2.CompareTo(actual3) < 0); @@ -226,6 +223,17 @@ public sealed class SemanticVersionTests public void ToString_WhenValid_ShouldReturnString(string version) { Assert.True(SemanticVersion.TryParseVersion(version, out var actual)); - Assert.Equal(version, actual.ToString()); + Assert.Equal(version, actual!.ToString()); + } + + [Theory] + [InlineData("1.2.3")] + [InlineData("1.2.3-alpha.3+7223b39")] + [InlineData("3.4.5-alpha.9")] + [InlineData("3.4.5+7223b39")] + public void ToShortString_WhenValid_ShouldReturnString(string version) + { + Assert.True(SemanticVersion.TryParseVersion(version, out var actual)); + Assert.Equal(string.Join(".", actual!.Major, actual.Minor, actual.Patch), actual!.ToShortString()); } }