Updates for module add upgrade and restore CLI commands #2550 #2551 (#2559)

This commit is contained in:
Bernie White 2024-09-25 03:12:51 +10:00 коммит произвёл GitHub
Родитель 56a21369f9
Коммит 6c21b047ab
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
15 изменённых файлов: 217 добавлений и 100 удалений

Просмотреть файл

@ -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: 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: - General improvements:
- **Breaking change**: Empty version comparison only accepts stable versions by default by @BernieWhite. - **Breaking change**: Empty version comparison only accepts stable versions by default by @BernieWhite.
[#2557](https://github.com/microsoft/PSRule/issues/2557) [#2557](https://github.com/microsoft/PSRule/issues/2557)

Просмотреть файл

@ -67,6 +67,9 @@ Optional parameters:
- `--version` - Specifies a specific version of the module to add. - `--version` - Specifies a specific version of the module to add.
By default, the latest stable version of the module is added. By default, the latest stable version of the module is added.
Any required version constraints set by the `Requires` option are taken into consideration. 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: 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 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` ## `module remove`
Remove one or more modules from the lock file. Remove one or more modules from the lock file.
@ -112,7 +121,13 @@ ps-rule module restore --force
## `module upgrade` ## `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: For example:
@ -120,6 +135,12 @@ For example:
ps-rule module upgrade 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 ## Next steps
For more information on the module lock file, see [Lock file](../lockfile.md). For more information on the module lock file, see [Lock file](../lockfile.md).

Просмотреть файл

@ -23,6 +23,11 @@
"type": "string", "type": "string",
"title": "Module version", "title": "Module version",
"description": "The version of the module to use." "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": [ "required": [

Просмотреть файл

@ -17,7 +17,6 @@ using PSRule.Pipeline.Dependencies;
using SemanticVersion = PSRule.Data.SemanticVersion; using SemanticVersion = PSRule.Data.SemanticVersion;
using NuGet.Packaging; using NuGet.Packaging;
using NuGet.Common; using NuGet.Common;
using PSRule.Pipeline;
namespace PSRule.CommandLine.Commands; namespace PSRule.CommandLine.Commands;
@ -51,7 +50,7 @@ public sealed class ModuleCommand
var requires = clientContext.Option.Requires.ToDictionary(); var requires = clientContext.Option.Requires.ToDictionary();
var file = LockFile.Read(null); var file = LockFile.Read(null);
using var pwsh = PowerShell.Create(); using var pwsh = CreatePowerShell();
// Restore from the lock file. // Restore from the lock file.
foreach (var kv in file.Modules) foreach (var kv in file.Modules)
@ -70,9 +69,11 @@ public sealed class ModuleCommand
var idealVersion = await FindVersionAsync(module, null, targetVersion, null, cancellationToken); var idealVersion = await FindVersionAsync(module, null, targetVersion, null, cancellationToken);
if (idealVersion != null) 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; exitCode = ERROR_MODULE_FAILED_TO_INSTALL;
clientContext.LogError(Messages.Error_501, module, targetVersion); clientContext.LogError(Messages.Error_501, module, targetVersion);
@ -109,7 +110,7 @@ public sealed class ModuleCommand
var idealVersion = await FindVersionAsync(includeModule, moduleConstraint, null, null, cancellationToken); var idealVersion = await FindVersionAsync(includeModule, moduleConstraint, null, null, cancellationToken);
if (idealVersion != null) if (idealVersion != null)
{ {
await InstallVersionAsync(clientContext, includeModule, idealVersion.ToString(), cancellationToken); await InstallVersionAsync(clientContext, includeModule, idealVersion, cancellationToken);
} }
else if (idealVersion == null) else if (idealVersion == null)
{ {
@ -143,8 +144,7 @@ public sealed class ModuleCommand
var exitCode = 0; var exitCode = 0;
var requires = clientContext.Option.Requires.ToDictionary(); var requires = clientContext.Option.Requires.ToDictionary();
var file = !operationOptions.Force ? LockFile.Read(null) : new LockFile(); var file = !operationOptions.Force ? LockFile.Read(null) : new LockFile();
using var pwsh = CreatePowerShell();
using var pwsh = PowerShell.Create();
// Add for any included modules. // Add for any included modules.
if (clientContext.Option?.Include?.Module != null && clientContext.Option.Include.Module.Length > 0) if (clientContext.Option?.Include?.Module != null && clientContext.Option.Include.Module.Length > 0)
@ -197,8 +197,7 @@ public sealed class ModuleCommand
var exitCode = 0; var exitCode = 0;
var requires = clientContext.Option.Requires.ToDictionary(); var requires = clientContext.Option.Requires.ToDictionary();
var file = LockFile.Read(null); var file = LockFile.Read(null);
var pwsh = CreatePowerShell();
var pwsh = PowerShell.Create();
if (exitCode == 0) if (exitCode == 0)
{ {
@ -218,13 +217,13 @@ public sealed class ModuleCommand
var requires = clientContext.Option.Requires.ToDictionary(); var requires = clientContext.Option.Requires.ToDictionary();
var file = LockFile.Read(null); var file = LockFile.Read(null);
using var pwsh = PowerShell.Create(); using var pwsh = CreatePowerShell();
foreach (var module in operationOptions.Module) foreach (var module in operationOptions.Module)
{ {
if (!file.Modules.TryGetValue(module, out var item) || operationOptions.Force) if (!file.Modules.TryGetValue(module, out var item) || operationOptions.Force)
{ {
// Get a constraint if set from options. // 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. // 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; 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()); clientContext.LogVerbose(Messages.UsingModule, module, idealVersion.ToString());
item = new LockEntry item = new LockEntry
{ {
Version = idealVersion Version = idealVersion,
IncludePrerelease = operationOptions.Prerelease && !idealVersion.Stable ? true : null,
}; };
file.Modules[module] = item; file.Modules[module] = item;
} }
@ -281,7 +281,7 @@ public sealed class ModuleCommand
var file = LockFile.Read(null); var file = LockFile.Read(null);
using var pwsh = PowerShell.Create(); using var pwsh = CreatePowerShell();
foreach (var module in operationOptions.Module) foreach (var module in operationOptions.Module)
{ {
if (file.Modules.TryGetValue(module, out var constraint)) if (file.Modules.TryGetValue(module, out var constraint))
@ -311,12 +311,13 @@ public sealed class ModuleCommand
var exitCode = 0; var exitCode = 0;
var requires = clientContext.Option.Requires.ToDictionary(); var requires = clientContext.Option.Requires.ToDictionary();
var file = LockFile.Read(null); var file = LockFile.Read(null);
var filteredModules = operationOptions.Module != null && operationOptions.Module.Length > 0 ? new HashSet<string>(operationOptions.Module, StringComparer.OrdinalIgnoreCase) : null;
using var pwsh = PowerShell.Create(); using var pwsh = CreatePowerShell();
foreach (var kv in file.Modules) foreach (var kv in file.Modules.Where(m => filteredModules == null || filteredModules.Contains(m.Key)))
{ {
// Get a constraint if set from options. // 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. // Find the ideal version.
var idealVersion = await FindVersionAsync(kv.Key, moduleConstraint, null, null, cancellationToken); 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()); clientContext.LogVerbose(Messages.UsingModule, kv.Key, idealVersion.ToString());
kv.Value.Version = idealVersion; kv.Value.Version = idealVersion;
kv.Value.IncludePrerelease = (kv.Value.IncludePrerelease.GetValueOrDefault(false) || operationOptions.Prerelease) && !idealVersion.Stable ? true : null;
file.Modules[kv.Key] = kv.Value; file.Modules[kv.Key] = kv.Value;
} }
@ -461,24 +463,26 @@ public sealed class ModuleCommand
return result; return result;
} }
private static async Task InstallVersionAsync([DisallowNull] ClientContext context, [DisallowNull] string name, [DisallowNull] string version, CancellationToken cancellationToken) private static async Task<SemanticVersion.Version?> InstallVersionAsync([DisallowNull] ClientContext context, [DisallowNull] string name, [DisallowNull] SemanticVersion.Version version, CancellationToken cancellationToken)
{ {
context.LogVerbose(Messages.RestoringModule, name, version); context.LogVerbose(Messages.RestoringModule, name, version);
var cache = new SourceCacheContext(); var cache = new SourceCacheContext();
var logger = new NullLogger(); var logger = new NullLogger();
var resource = await GetSourceRepositoryAsync(); var resource = await GetSourceRepositoryAsync();
var stringVersion = version.ToString();
var packageVersion = new NuGetVersion(version); var packageVersion = new NuGetVersion(stringVersion);
using var packageStream = new MemoryStream(); using var packageStream = new MemoryStream();
await resource.CopyNupkgToStreamAsync( if (!await resource.CopyNupkgToStreamAsync(
name, name,
packageVersion, packageVersion,
packageStream, packageStream,
cache, cache,
logger, logger,
cancellationToken); cancellationToken))
return null;
using var packageReader = new PackageArchiveReader(packageStream); using var packageReader = new PackageArchiveReader(packageStream);
var nuspecReader = await packageReader.GetNuspecReaderAsync(cancellationToken); var nuspecReader = await packageReader.GetNuspecReaderAsync(cancellationToken);
@ -489,6 +493,7 @@ public sealed class ModuleCommand
if (Directory.Exists(modulePath)) if (Directory.Exists(modulePath))
Directory.Delete(modulePath, true); Directory.Delete(modulePath, true);
var count = 0;
var files = packageReader.GetFiles(); var files = packageReader.GetFiles();
packageReader.CopyFiles(modulePath, files, (name, targetPath, s) => packageReader.CopyFiles(modulePath, files, (name, targetPath, s) =>
{ {
@ -496,10 +501,18 @@ public sealed class ModuleCommand
return null; return null;
s.CopyToFile(targetPath); s.CopyToFile(targetPath);
count++;
return targetPath; return targetPath;
}, logger, cancellationToken); }, 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<FindPackageByIdResource> GetSourceRepositoryAsync() private static async Task<FindPackageByIdResource> GetSourceRepositoryAsync()
@ -509,9 +522,9 @@ public sealed class ModuleCommand
return await repository.GetResourceAsync<FindPackageByIdResource>(); return await repository.GetResourceAsync<FindPackageByIdResource>();
} }
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) private static bool ShouldIgnorePackageFile(string name)
@ -520,5 +533,10 @@ public sealed class ModuleCommand
string.Equals(name, "_rels/.rels", StringComparison.OrdinalIgnoreCase); string.Equals(name, "_rels/.rels", StringComparison.OrdinalIgnoreCase);
} }
private static PowerShell CreatePowerShell()
{
return PowerShell.Create();
}
#endregion Helper methods #endregion Helper methods
} }

Просмотреть файл

@ -4,32 +4,37 @@
namespace PSRule.CommandLine.Models; namespace PSRule.CommandLine.Models;
/// <summary> /// <summary>
/// /// Options for the module command.
/// </summary> /// </summary>
public sealed class ModuleOptions public sealed class ModuleOptions
{ {
/// <summary> /// <summary>
/// /// A specific path to use for the operation.
/// </summary> /// </summary>
public string[]? Path { get; set; } public string[]? Path { get; set; }
/// <summary> /// <summary>
/// /// The name of any specified modules.
/// </summary> /// </summary>
public string[]? Module { get; set; } public string[]? Module { get; set; }
/// <summary> /// <summary>
/// /// Determines if the module is overridden if it already exists.
/// </summary> /// </summary>
public bool Force { get; set; } public bool Force { get; set; }
/// <summary> /// <summary>
/// /// The target module version.
/// </summary> /// </summary>
public string? Version { get; set; } public string? Version { get; set; }
/// <summary> /// <summary>
/// /// Determines if verification that the module exists is skipped.
/// </summary> /// </summary>
public bool SkipVerification { get; set; } public bool SkipVerification { get; set; }
/// <summary>
/// Accept pre-release versions in addition to stable module versions.
/// </summary>
public bool Prerelease { get; set; }
} }

Просмотреть файл

@ -29,6 +29,7 @@ internal sealed class ClientBuilder
private readonly Option<string> _Module_Add_Version; private readonly Option<string> _Module_Add_Version;
private readonly Option<bool> _Module_Add_Force; private readonly Option<bool> _Module_Add_Force;
private readonly Option<bool> _Module_Add_SkipVerification; private readonly Option<bool> _Module_Add_SkipVerification;
private readonly Option<bool> _Module_Prerelease;
private readonly Option<string[]> _Global_Path; private readonly Option<string[]> _Global_Path;
private readonly Option<DirectoryInfo> _Run_OutputPath; private readonly Option<DirectoryInfo> _Run_OutputPath;
private readonly Option<string> _Run_OutputFormat; private readonly Option<string> _Run_OutputFormat;
@ -43,70 +44,74 @@ internal sealed class ClientBuilder
// Global options. // Global options.
_Global_Option = new Option<string>( _Global_Option = new Option<string>(
new string[] { "--option" }, ["--option"],
getDefaultValue: () => "ps-rule.yaml", getDefaultValue: () => "ps-rule.yaml",
description: CmdStrings.Global_Option_Description description: CmdStrings.Global_Option_Description
); );
_Global_Verbose = new Option<bool>( _Global_Verbose = new Option<bool>(
new string[] { "--verbose" }, ["--verbose"],
description: CmdStrings.Global_Verbose_Description description: CmdStrings.Global_Verbose_Description
); );
_Global_Debug = new Option<bool>( _Global_Debug = new Option<bool>(
new string[] { "--debug" }, ["--debug"],
description: CmdStrings.Global_Debug_Description description: CmdStrings.Global_Debug_Description
); );
_Global_Path = new Option<string[]>( _Global_Path = new Option<string[]>(
new string[] { "-p", "--path" }, ["-p", "--path"],
description: CmdStrings.Global_Path_Description description: CmdStrings.Global_Path_Description
); );
// Options for the run command. // Options for the run command.
_Run_OutputPath = new Option<DirectoryInfo>( _Run_OutputPath = new Option<DirectoryInfo>(
new string[] { "--output-path" }, ["--output-path"],
description: CmdStrings.Run_OutputPath_Description description: CmdStrings.Run_OutputPath_Description
); );
_Run_OutputFormat = new Option<string>( _Run_OutputFormat = new Option<string>(
new string[] { "-o", "--output" }, ["-o", "--output"],
description: CmdStrings.Run_OutputFormat_Description description: CmdStrings.Run_OutputFormat_Description
); );
_Run_InputPath = new Option<string[]>( _Run_InputPath = new Option<string[]>(
new string[] { "-f", "--input-path" }, ["-f", "--input-path"],
description: CmdStrings.Run_InputPath_Description description: CmdStrings.Run_InputPath_Description
); );
_Run_Module = new Option<string[]>( _Run_Module = new Option<string[]>(
new string[] { "-m", "--module" }, ["-m", "--module"],
description: CmdStrings.Run_Module_Description description: CmdStrings.Run_Module_Description
); );
_Run_Baseline = new Option<string>( _Run_Baseline = new Option<string>(
new string[] { "--baseline" }, ["--baseline"],
description: CmdStrings.Run_Baseline_Description description: CmdStrings.Run_Baseline_Description
); );
_Run_Outcome = new Option<string[]>( _Run_Outcome = new Option<string[]>(
new string[] { "--outcome" }, ["--outcome"],
description: CmdStrings.Run_Outcome_Description description: CmdStrings.Run_Outcome_Description
).FromAmong("Pass", "Fail", "Error", "Processed", "Problem"); ).FromAmong("Pass", "Fail", "Error", "Processed", "Problem");
_Run_Outcome.Arity = ArgumentArity.ZeroOrMore; _Run_Outcome.Arity = ArgumentArity.ZeroOrMore;
// Options for the module command. // Options for the module command.
_Module_Init_Force = new Option<bool>( _Module_Init_Force = new Option<bool>(
new string[] { ARG_FORCE }, [ARG_FORCE],
description: CmdStrings.Module_Init_Force_Description description: CmdStrings.Module_Init_Force_Description
); );
_Module_Add_Version = new Option<string> _Module_Add_Version = new Option<string>
( (
new string[] { "--version" }, ["--version"],
description: CmdStrings.Module_Add_Version_Description description: CmdStrings.Module_Add_Version_Description
); );
_Module_Add_Force = new Option<bool>( _Module_Add_Force = new Option<bool>(
new string[] { ARG_FORCE }, [ARG_FORCE],
description: CmdStrings.Module_Add_Force_Description description: CmdStrings.Module_Add_Force_Description
); );
_Module_Add_SkipVerification = new Option<bool>( _Module_Add_SkipVerification = new Option<bool>(
new string[] { "--skip-verification" }, ["--skip-verification"],
description: CmdStrings.Module_Add_SkipVerification_Description description: CmdStrings.Module_Add_SkipVerification_Description
); );
_Module_Prerelease = new Option<bool>(
["--prerelease"],
description: CmdStrings.Module_Prerelease_Description
);
_Module_Restore_Force = new Option<bool>( _Module_Restore_Force = new Option<bool>(
new string[] { ARG_FORCE }, [ARG_FORCE],
description: CmdStrings.Module_Restore_Force_Description description: CmdStrings.Module_Restore_Force_Description
); );
@ -165,12 +170,12 @@ internal sealed class ClientBuilder
{ {
var cmd = new Command("module", CmdStrings.Module_Description); var cmd = new Command("module", CmdStrings.Module_Description);
var moduleArg = new Argument<string[]> var requiredModuleArg = new Argument<string[]>
( (
"module", "module",
CmdStrings.Module_Module_Description CmdStrings.Module_RequiredModule_Description
); );
moduleArg.Arity = ArgumentArity.OneOrMore; requiredModuleArg.Arity = ArgumentArity.OneOrMore;
// Init // Init
var init = new Command var init = new Command
@ -219,19 +224,21 @@ internal sealed class ClientBuilder
"add", "add",
CmdStrings.Module_Add_Description CmdStrings.Module_Add_Description
); );
add.AddArgument(moduleArg); add.AddArgument(requiredModuleArg);
add.AddOption(_Module_Add_Version); add.AddOption(_Module_Add_Version);
add.AddOption(_Module_Add_Force); add.AddOption(_Module_Add_Force);
add.AddOption(_Module_Add_SkipVerification); add.AddOption(_Module_Add_SkipVerification);
add.AddOption(_Module_Prerelease);
add.SetHandler(async (invocation) => add.SetHandler(async (invocation) =>
{ {
var option = new ModuleOptions var option = new ModuleOptions
{ {
Path = invocation.ParseResult.GetValueForOption(_Global_Path), Path = invocation.ParseResult.GetValueForOption(_Global_Path),
Module = invocation.ParseResult.GetValueForArgument(moduleArg), Module = invocation.ParseResult.GetValueForArgument(requiredModuleArg),
Version = invocation.ParseResult.GetValueForOption(_Module_Add_Version), Version = invocation.ParseResult.GetValueForOption(_Module_Add_Version),
Force = invocation.ParseResult.GetValueForOption(_Module_Add_Force), Force = invocation.ParseResult.GetValueForOption(_Module_Add_Force),
SkipVerification = invocation.ParseResult.GetValueForOption(_Module_Add_SkipVerification), SkipVerification = invocation.ParseResult.GetValueForOption(_Module_Add_SkipVerification),
Prerelease = invocation.ParseResult.GetValueForOption(_Module_Prerelease),
}; };
var client = GetClientContext(invocation); var client = GetClientContext(invocation);
@ -244,13 +251,13 @@ internal sealed class ClientBuilder
"remove", "remove",
CmdStrings.Module_Remove_Description CmdStrings.Module_Remove_Description
); );
remove.AddArgument(moduleArg); remove.AddArgument(requiredModuleArg);
remove.SetHandler(async (invocation) => remove.SetHandler(async (invocation) =>
{ {
var option = new ModuleOptions var option = new ModuleOptions
{ {
Path = invocation.ParseResult.GetValueForOption(_Global_Path), Path = invocation.ParseResult.GetValueForOption(_Global_Path),
Module = invocation.ParseResult.GetValueForArgument(moduleArg), Module = invocation.ParseResult.GetValueForArgument(requiredModuleArg),
}; };
var client = GetClientContext(invocation); var client = GetClientContext(invocation);
@ -263,11 +270,21 @@ internal sealed class ClientBuilder
"upgrade", "upgrade",
CmdStrings.Module_Upgrade_Description CmdStrings.Module_Upgrade_Description
); );
var optionalModuleArg = new Argument<string[]>
(
"module",
CmdStrings.Module_OptionalModule_Description
);
optionalModuleArg.Arity = ArgumentArity.ZeroOrMore;
upgrade.AddArgument(optionalModuleArg);
upgrade.AddOption(_Module_Prerelease);
upgrade.SetHandler(async (invocation) => upgrade.SetHandler(async (invocation) =>
{ {
var option = new ModuleOptions var option = new ModuleOptions
{ {
Path = invocation.ParseResult.GetValueForOption(_Global_Path), Path = invocation.ParseResult.GetValueForOption(_Global_Path),
Module = invocation.ParseResult.GetValueForArgument(requiredModuleArg),
Prerelease = invocation.ParseResult.GetValueForOption(_Module_Prerelease),
}; };
var client = GetClientContext(invocation); var client = GetClientContext(invocation);

24
src/PSRule.Tool/Resources/CmdStrings.Designer.cs сгенерированный
Просмотреть файл

@ -178,11 +178,20 @@ namespace PSRule.Tool.Resources {
} }
/// <summary> /// <summary>
/// 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..
/// </summary> /// </summary>
internal static string Module_Module_Description { internal static string Module_OptionalModule_Description {
get { get {
return ResourceManager.GetString("Module_Module_Description", resourceCulture); return ResourceManager.GetString("Module_OptionalModule_Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Accept pre-release versions in addition to stable module versions..
/// </summary>
internal static string Module_Prerelease_Description {
get {
return ResourceManager.GetString("Module_Prerelease_Description", resourceCulture);
} }
} }
@ -195,6 +204,15 @@ namespace PSRule.Tool.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to The name of one or more modules..
/// </summary>
internal static string Module_RequiredModule_Description {
get {
return ResourceManager.GetString("Module_RequiredModule_Description", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Restore from the module lock file and configured options.. /// Looks up a localized string similar to Restore from the module lock file and configured options..
/// </summary> /// </summary>

Просмотреть файл

@ -141,9 +141,12 @@
<data name="Module_Description" xml:space="preserve"> <data name="Module_Description" xml:space="preserve">
<value>Manage or restore modules tracked by the module lock file and configured options.</value> <value>Manage or restore modules tracked by the module lock file and configured options.</value>
</data> </data>
<data name="Module_Module_Description" xml:space="preserve"> <data name="Module_RequiredModule_Description" xml:space="preserve">
<value>The name of one or more modules.</value> <value>The name of one or more modules.</value>
</data> </data>
<data name="Module_OptionalModule_Description" xml:space="preserve">
<value>Optionally specifies one or more modules to apply to.</value>
</data>
<data name="Module_Remove_Description" xml:space="preserve"> <data name="Module_Remove_Description" xml:space="preserve">
<value>Remove one or more modules from the lock file.</value> <value>Remove one or more modules from the lock file.</value>
</data> </data>
@ -192,4 +195,7 @@
<data name="Run_OutputPath_Description" xml:space="preserve"> <data name="Run_OutputPath_Description" xml:space="preserve">
<value>Specifies a path to write results to.</value> <value>Specifies a path to write results to.</value>
</data> </data>
<data name="Module_Prerelease_Description" xml:space="preserve">
<value>Accept pre-release versions in addition to stable module versions.</value>
</data>
</root> </root>

Просмотреть файл

@ -346,7 +346,8 @@ public static class SemanticVersion
[DebuggerDisplay("{_VersionString}")] [DebuggerDisplay("{_VersionString}")]
public sealed class Version : IComparable<Version>, IEquatable<Version> public sealed class Version : IComparable<Version>, IEquatable<Version>
{ {
private readonly string _VersionString; private string? _VersionString;
private string? _ShortVersionString;
/// <summary> /// <summary>
/// The major part of the version. /// The major part of the version.
@ -380,8 +381,6 @@ public static class SemanticVersion
Patch = patch; Patch = patch;
Prerelease = prerelease; Prerelease = prerelease;
Build = build; Build = build;
_VersionString = GetVersionString();
} }
/// <summary> /// <summary>
@ -397,10 +396,20 @@ public static class SemanticVersion
return TryParseVersion(value, out version); return TryParseVersion(value, out version);
} }
/// <inheritdoc/> /// <summary>
/// Get the version as a string.
/// </summary>
public override string ToString() public override string ToString()
{ {
return _VersionString; return _VersionString ??= GetVersionString(simple: false);
}
/// <summary>
/// Get the version as a string returning only the major.minor.patch part of the version.
/// </summary>
public string ToShortString()
{
return _ShortVersionString ??= GetVersionString(simple: true);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -480,10 +489,14 @@ public static class SemanticVersion
return Prerelease != other.Prerelease ? PR.Compare(Prerelease, other.Prerelease) : 0; return Prerelease != other.Prerelease ? PR.Compare(Prerelease, other.Prerelease) : 0;
} }
private string GetVersionString() /// <summary>
/// Returns a version string.
/// </summary>
/// <param name="simple">When <c>true</c>, only return the major.minor.patch version.</param>
private string GetVersionString(bool simple = false)
{ {
var count = 5 + (Prerelease != null && !Prerelease.Stable ? 2 : 0) + (Build != null && Build.Length > 0 ? 2 : 0); var size = 5 + (!simple && Prerelease != null && !Prerelease.Stable ? 2 : 0) + (!simple && Build != null && Build.Length > 0 ? 2 : 0);
var parts = new object[count]; var parts = new object[size];
parts[0] = Major; parts[0] = Major;
parts[1] = DOT; parts[1] = DOT;
@ -491,19 +504,21 @@ public static class SemanticVersion
parts[3] = DOT; parts[3] = DOT;
parts[4] = Patch; parts[4] = Patch;
var next = 5; if (size > 5)
if (Prerelease != null && !Prerelease.Stable)
{ {
parts[next++] = DASH; var next = 5;
parts[next++] = Prerelease.Value; if (Prerelease != null && !Prerelease.Stable)
} {
parts[next++] = DASH;
parts[next++] = Prerelease.Value;
}
if (Build != null && Build.Length > 0) if (Build != null && Build.Length > 0)
{ {
parts[next++] = PLUS; parts[next++] = PLUS;
parts[next++] = Build; parts[next++] = Build;
}
} }
return string.Concat(parts); return string.Concat(parts);
} }
} }

Просмотреть файл

@ -6,3 +6,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.PSRule.Core")] [assembly: InternalsVisibleTo("Microsoft.PSRule.Core")]
[assembly: InternalsVisibleTo("Microsoft.PSRule.Tool")] [assembly: InternalsVisibleTo("Microsoft.PSRule.Tool")]
[assembly: InternalsVisibleTo("PSRule.Tests")] [assembly: InternalsVisibleTo("PSRule.Tests")]
[assembly: InternalsVisibleTo("PSRule.Types.Tests")]

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
using System.ComponentModel;
using Newtonsoft.Json; using Newtonsoft.Json;
using PSRule.Data; using PSRule.Data;
@ -16,4 +17,10 @@ public sealed class LockEntry
/// </summary> /// </summary>
[JsonProperty("version", NullValueHandling = NullValueHandling.Include)] [JsonProperty("version", NullValueHandling = NullValueHandling.Include)]
public SemanticVersion.Version Version { get; set; } public SemanticVersion.Version Version { get; set; }
/// <summary>
/// Accept pre-release versions in addition to stable module versions.
/// </summary>
[DefaultValue(null), JsonProperty("includePrerelease", NullValueHandling = NullValueHandling.Ignore)]
public bool? IncludePrerelease { get; set; }
} }

Просмотреть файл

@ -1,13 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Data;
namespace PSRule.CommandLine;
/// <summary>
/// Tests for <see cref="ModuleConstraint"/>.
/// </summary>
public sealed class ModuleConstraintTests
{
}

Просмотреть файл

@ -14,7 +14,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.0" /> <PackageReference Include="xunit" Version="2.9.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Просмотреть файл

@ -11,7 +11,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.0" /> <PackageReference Include="xunit" Version="2.9.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Просмотреть файл

@ -1,10 +1,7 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
using Microsoft.PowerShell; namespace PSRule.Data;
using PSRule.Data;
namespace PSRule;
/// <summary> /// <summary>
/// Tests for semantic version comparison. /// Tests for semantic version comparison.
@ -18,20 +15,20 @@ public sealed class SemanticVersionTests
public void Version() public void Version()
{ {
Assert.True(SemanticVersion.TryParseVersion("1.2.3-alpha.3+7223b39", out var actual1)); 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(2, actual1.Minor);
Assert.Equal(3, actual1.Patch); Assert.Equal(3, actual1.Patch);
Assert.Equal("alpha.3", actual1.Prerelease.Value); Assert.Equal("alpha.3", actual1.Prerelease.Value);
Assert.Equal("7223b39", actual1.Build); Assert.Equal("7223b39", actual1.Build);
Assert.True(SemanticVersion.TryParseVersion("v1.2.3-alpha.3", out var actual2)); 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(2, actual2.Minor);
Assert.Equal(3, actual2.Patch); Assert.Equal(3, actual2.Patch);
Assert.Equal("alpha.3", actual2.Prerelease.Value); Assert.Equal("alpha.3", actual2.Prerelease.Value);
Assert.True(SemanticVersion.TryParseVersion("v1.2.3+7223b39", out var actual3)); 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(2, actual3.Minor);
Assert.Equal(3, actual3.Patch); Assert.Equal(3, actual3.Patch);
Assert.Equal("7223b39", actual3.Build); 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("10.0.0", out var actual3));
Assert.True(SemanticVersion.TryParseVersion("1.0.2", out var actual4)); 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(actual2) < 0);
Assert.True(actual1.CompareTo(actual3) < 0); Assert.True(actual1.CompareTo(actual3) < 0);
Assert.True(actual1.CompareTo(actual4) < 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(actual1) > 0);
Assert.True(actual2.CompareTo(actual3) < 0); Assert.True(actual2.CompareTo(actual3) < 0);
Assert.True(actual2.CompareTo(actual4) > 0); Assert.True(actual2.CompareTo(actual4) > 0);
@ -205,7 +202,7 @@ public sealed class SemanticVersionTests
var actual7 = new SemanticVersion.PR("beta.11"); var actual7 = new SemanticVersion.PR("beta.11");
var actual8 = new SemanticVersion.PR("rc.1"); 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(actual2) > 0);
Assert.True(actual1.CompareTo(actual6) > 0); Assert.True(actual1.CompareTo(actual6) > 0);
Assert.True(actual2.CompareTo(actual3) < 0); Assert.True(actual2.CompareTo(actual3) < 0);
@ -226,6 +223,17 @@ public sealed class SemanticVersionTests
public void ToString_WhenValid_ShouldReturnString(string version) public void ToString_WhenValid_ShouldReturnString(string version)
{ {
Assert.True(SemanticVersion.TryParseVersion(version, out var actual)); 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());
} }
} }