From 2d89954ae20f980209508aea1a702f3f634dca49 Mon Sep 17 00:00:00 2001 From: Pedro Pinho Date: Thu, 23 Jul 2026 13:32:31 +0000 Subject: [PATCH 1/7] feat: add support for pre-releases --- cmf-cli/Commands/init/InitCommand.cs | 6 +- cmf-cli/Commands/new/HTMLCommand.cs | 4 +- cmf-cli/Commands/new/HelpCommand.cs | 2 +- cmf-cli/Commands/new/IoTCommand.cs | 4 +- .../new/iot/GenerateDotnetDriverCommand.cs | 2 +- .../GenerateDotnetFrameworkDriverCommand.cs | 2 +- .../Commands/new/iot/GenerateDriverCommand.cs | 2 +- .../new/iot/GenerateTaskLibraryCommand.cs | 2 +- .../Commands/upgrade/UpgradeBaseCommand.cs | 2 +- cmf-cli/Utilities/UpgradeBaseUtilities.cs | 6 +- core/Objects/ProjectConfig/ProjectConfigV1.cs | 31 +++++ core/Utilities/GenericUtilities.cs | 88 ++++++++++++ tests/Specs/GenericUtilitiesTests.cs | 126 ++++++++++++++++++ tests/Specs/Init.cs | 87 ++++++++++++ tests/Specs/MESVersionValidation.cs | 19 +++ 15 files changed, 368 insertions(+), 15 deletions(-) create mode 100644 tests/Specs/GenericUtilitiesTests.cs diff --git a/cmf-cli/Commands/init/InitCommand.cs b/cmf-cli/Commands/init/InitCommand.cs index d1c082452..949cc0e7c 100644 --- a/cmf-cli/Commands/init/InitCommand.cs +++ b/cmf-cli/Commands/init/InitCommand.cs @@ -514,8 +514,8 @@ internal void Execute(InitArguments x) if (string.IsNullOrWhiteSpace(x.ngxSchematicsVersion)) { - var mesVer = Version.Parse(x.BaseVersion); - x.ngxSchematicsVersion = $"release-{mesVer.Major}{mesVer.Minor}{mesVer.Build}"; + var mesVer = GenericUtilities.ParseVersion(x.BaseVersion); + x.ngxSchematicsVersion = GenericUtilities.GetNpmDistTag(mesVer); } Log.Information($"Using ngx-schematics version: {x.ngxSchematicsVersion}"); @@ -578,7 +578,7 @@ internal void Execute(InitArguments x) #region version-specific bits - var version = Version.Parse(x.BaseVersion); + var version = GenericUtilities.ToVersion(GenericUtilities.ParseVersion(x.BaseVersion)); args.AddRange(new []{ "--dotnetSDKVersion", ExecutionContext.ServiceProvider.GetService().DotNetSdk(version) }); if(version < new Version(10,0)) diff --git a/cmf-cli/Commands/new/HTMLCommand.cs b/cmf-cli/Commands/new/HTMLCommand.cs index 4760fd856..934df4fd4 100644 --- a/cmf-cli/Commands/new/HTMLCommand.cs +++ b/cmf-cli/Commands/new/HTMLCommand.cs @@ -104,7 +104,7 @@ public void ExecuteV10(IDirectoryInfo workingDir, string version) var packageName = base.GeneratePackageName(workingDir)!.Value.Item1; var packageDir = workingDir.GetDirectories(packageName).First(); - var schematicsVersion = !string.IsNullOrEmpty(ngxSchematicsVersion) ? ngxSchematicsVersion : $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}"; + var schematicsVersion = !string.IsNullOrEmpty(ngxSchematicsVersion) ? ngxSchematicsVersion : GenericUtilities.GetNpmDistTag(mesVersion); //After v11 we use Angular default routing var routing = mesVersion.Major >= 11 ? "true" : "false"; @@ -143,7 +143,7 @@ public void ExecuteV10(IDirectoryInfo workingDir, string version) "add", "--registry", ExecutionContext.Instance.ProjectConfig.NPMRegistry.OriginalString, "--skip-confirmation", $"@criticalmanufacturing/ngx-schematics@{schematicsVersion}", "--eslint", "--application", baseLayer.ToString(), - "--version", $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}" + "--version", GenericUtilities.GetNpmDistTag(mesVersion) ], WorkingDirectory = packageDir, ForceColorOutput = false diff --git a/cmf-cli/Commands/new/HelpCommand.cs b/cmf-cli/Commands/new/HelpCommand.cs index f461ecb5d..2779aad5e 100644 --- a/cmf-cli/Commands/new/HelpCommand.cs +++ b/cmf-cli/Commands/new/HelpCommand.cs @@ -102,7 +102,7 @@ public void Execute(IDirectoryInfo workingDir, string version) var mesVersion = ExecutionContext.Instance.ProjectConfig.MESVersion; - this.schematicsVersion = ngxSchematicsVersion ?? $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}"; + this.schematicsVersion = ngxSchematicsVersion ?? GenericUtilities.GetNpmDistTag(mesVersion); //Switch between v10 and v11 template switch (majorVersion) diff --git a/cmf-cli/Commands/new/IoTCommand.cs b/cmf-cli/Commands/new/IoTCommand.cs index a7d189f96..8324a1b9d 100644 --- a/cmf-cli/Commands/new/IoTCommand.cs +++ b/cmf-cli/Commands/new/IoTCommand.cs @@ -212,7 +212,7 @@ public void ExecuteV10AngularPackage(IDirectoryInfo workingDir, string version, var mesVersion = ExecutionContext.Instance.ProjectConfig.MESVersion; - var schematicsVersion = ngxSchematicsVersion ?? $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}"; + var schematicsVersion = ngxSchematicsVersion ?? GenericUtilities.GetNpmDistTag(mesVersion); Log.Debug($"Creating new IoT Workspace {packageName}"); @@ -256,7 +256,7 @@ public void ExecuteV10AngularPackage(IDirectoryInfo workingDir, string version, "--skip-confirmation", $"@criticalmanufacturing/ngx-iot-schematics@{schematicsVersion}", "--lint", "--base-app", baseLayer.ToString(), - "--version", $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}" }, + "--version", GenericUtilities.GetNpmDistTag(mesVersion) }, WorkingDirectory = iotCustomPackageWorkDir, ForceColorOutput = false }.Exec(); diff --git a/cmf-cli/Commands/new/iot/GenerateDotnetDriverCommand.cs b/cmf-cli/Commands/new/iot/GenerateDotnetDriverCommand.cs index 83cf0791f..faebe19a2 100644 --- a/cmf-cli/Commands/new/iot/GenerateDotnetDriverCommand.cs +++ b/cmf-cli/Commands/new/iot/GenerateDotnetDriverCommand.cs @@ -128,7 +128,7 @@ private List GenerateArgs( var args = new List(); args.AddRange(new[] { - "--targetSystemVersionProcessed", $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}", + "--targetSystemVersionProcessed", GenericUtilities.GetNpmDistTag(mesVersion), "--directoryName", directoryName, "--identifier", identifier, "--identifierCamel", identifierCamel, diff --git a/cmf-cli/Commands/new/iot/GenerateDotnetFrameworkDriverCommand.cs b/cmf-cli/Commands/new/iot/GenerateDotnetFrameworkDriverCommand.cs index 4570a9f68..7bd9ce561 100644 --- a/cmf-cli/Commands/new/iot/GenerateDotnetFrameworkDriverCommand.cs +++ b/cmf-cli/Commands/new/iot/GenerateDotnetFrameworkDriverCommand.cs @@ -123,7 +123,7 @@ private List GenerateArgs( var args = new List(); args.AddRange(new[] { - "--targetSystemVersionProcessed", $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}", + "--targetSystemVersionProcessed", GenericUtilities.GetNpmDistTag(mesVersion), "--directoryName", directoryName, "--identifier", identifier, "--identifierCamel", identifierCamel, diff --git a/cmf-cli/Commands/new/iot/GenerateDriverCommand.cs b/cmf-cli/Commands/new/iot/GenerateDriverCommand.cs index 085fa75cc..b2504fd25 100644 --- a/cmf-cli/Commands/new/iot/GenerateDriverCommand.cs +++ b/cmf-cli/Commands/new/iot/GenerateDriverCommand.cs @@ -143,7 +143,7 @@ private List GenerateArgs( var args = new List(); args.AddRange(new[] { - "--targetSystemVersionProcessed", $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}", + "--targetSystemVersionProcessed", GenericUtilities.GetNpmDistTag(mesVersion), "--directoryName", directoryName, "--identifier", identifier, "--identifierCamel", identifierCamel, diff --git a/cmf-cli/Commands/new/iot/GenerateTaskLibraryCommand.cs b/cmf-cli/Commands/new/iot/GenerateTaskLibraryCommand.cs index 6dff59428..09a45ce45 100644 --- a/cmf-cli/Commands/new/iot/GenerateTaskLibraryCommand.cs +++ b/cmf-cli/Commands/new/iot/GenerateTaskLibraryCommand.cs @@ -130,7 +130,7 @@ private List GenerateArgs( "--identifierLower", identifier.Replace(" ", "").ToLower().Trim(), "--packageName", fullPackageName, "--packageVersion", packageVersion, - "--targetSystemVersionProcessed", $"release-{mesVersion.Major}{mesVersion.Minor}{mesVersion.Build}", + "--targetSystemVersionProcessed", GenericUtilities.GetNpmDistTag(mesVersion), "--dependsOnScope", JsonConvert.SerializeObject(dependsOnScope), "--mandatoryForScope", JsonConvert.SerializeObject(mandatoryForScope), "--dependsOnProtocol", JsonConvert.SerializeObject(dependsOnProtocol), diff --git a/cmf-cli/Commands/upgrade/UpgradeBaseCommand.cs b/cmf-cli/Commands/upgrade/UpgradeBaseCommand.cs index eff96cb4c..2f6ccb2d1 100644 --- a/cmf-cli/Commands/upgrade/UpgradeBaseCommand.cs +++ b/cmf-cli/Commands/upgrade/UpgradeBaseCommand.cs @@ -144,7 +144,7 @@ private void UpdateProjectConfig(IDirectoryInfo packagePath, string baseVersion) text = UpgradeBaseUtilities.UpdateJsonValue(text, key, baseVersion); } - if (new Version(baseVersion).Major >= 11) + if (GenericUtilities.ParseVersion(baseVersion).Major >= 11) { // TODO: find a more elegant way to apply these changes to files/packages when this command is executed. // For the moment, sneaking this if-statement in will do the job but long-term we'll need an approach that diff --git a/cmf-cli/Utilities/UpgradeBaseUtilities.cs b/cmf-cli/Utilities/UpgradeBaseUtilities.cs index 41838a2af..c813a1d5d 100644 --- a/cmf-cli/Utilities/UpgradeBaseUtilities.cs +++ b/cmf-cli/Utilities/UpgradeBaseUtilities.cs @@ -54,12 +54,14 @@ public static void UpdateNPMProject(IFileSystem fileSystem, CmfPackage cmfPackag { // package.json files string[] filesToUpdate = fileSystem.Directory.GetFiles(cmfPackage.GetFileInfo().DirectoryName, "package.json", SearchOption.AllDirectories); - string pattern = @"release-\d+"; + // matches any npm dist-tag in the "{label}-{version}" convention (e.g. "release-1200", "alpha-1200", "next-1200") + string pattern = @"(?:release|alpha|beta|rc|next|canary|preview)-\d+"; + string newDistTag = GenericUtilities.GetNpmDistTag(GenericUtilities.ParseVersion(version)); foreach (string filePath in filesToUpdate.Where(path => !path.Contains("node_modules") && !path.Contains("dist"))) { string text = fileSystem.File.ReadAllText(filePath); - text = Regex.Replace(text, pattern, $"release-{version.Replace(".", "")}", RegexOptions.IgnoreCase); + text = Regex.Replace(text, pattern, newDistTag, RegexOptions.IgnoreCase); fileSystem.File.WriteAllText(filePath, text); } diff --git a/core/Objects/ProjectConfig/ProjectConfigV1.cs b/core/Objects/ProjectConfig/ProjectConfigV1.cs index 7e0ec5892..00900397e 100644 --- a/core/Objects/ProjectConfig/ProjectConfigV1.cs +++ b/core/Objects/ProjectConfig/ProjectConfigV1.cs @@ -1,6 +1,7 @@ using System; using System.Text.Json.Serialization; using Cmf.CLI.Core.Enums; +using Cmf.CLI.Utilities; using Newtonsoft.Json; using NuGet.Versioning; @@ -26,12 +27,16 @@ public class ProjectConfigV1 [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public int? RESTPort { get; set; } public string Tenant { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(LenientVersionConverter))] public Version MESVersion { get; set; } public SemanticVersion DevTasksVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(LenientVersionConverter))] public Version HTMLStarterVersion { get; set; } public SemanticVersion YoGeneratorVersion { get; set; } public string NGXSchematicsVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(LenientVersionConverter))] public Version NugetVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(LenientVersionConverter))] public Version TestScenariosNugetVersion { get; set; } [Newtonsoft.Json.JsonConverter(typeof(BooleanJsonConverter))] public bool IsSslEnabled { get; set; } @@ -104,4 +109,30 @@ public override void WriteJson( JsonWriter writer, object value, JsonSerializer { } +} + +/// +/// Converts a version string to/from a , tolerating semantic versioning +/// pre-release labels and/or build metadata (e.g. "12.0.0-alpha.1+build"). Only the numeric +/// release components (Major.Minor.Build[.Revision]) are kept, since has +/// no concept of pre-release/build metadata. This keeps backward compatibility with existing +/// project configs that only have plain "Major.Minor.Build" versions. +/// +public class LenientVersionConverter : Newtonsoft.Json.JsonConverter +{ + public override Version ReadJson(JsonReader reader, Type objectType, Version existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + + var value = reader.Value?.ToString(); + return string.IsNullOrWhiteSpace(value) ? null : GenericUtilities.ToVersion(GenericUtilities.ParseVersion(value)); + } + + public override void WriteJson(JsonWriter writer, Version value, JsonSerializer serializer) + { + writer.WriteValue(value?.ToString()); + } } \ No newline at end of file diff --git a/core/Utilities/GenericUtilities.cs b/core/Utilities/GenericUtilities.cs index ba9f162b0..bc0f7b150 100644 --- a/core/Utilities/GenericUtilities.cs +++ b/core/Utilities/GenericUtilities.cs @@ -7,6 +7,7 @@ using Cmf.CLI.Core.Enums; using Cmf.CLI.Core.Objects; using Cmf.CLI.Core.Repository.Credentials; +using NuGet.Versioning; using Spectre.Console; namespace Cmf.CLI.Utilities @@ -183,6 +184,93 @@ public static IEnumerable Flatten( { return string.IsNullOrEmpty(value?.Value) ? null : new Uri(value!.Value); } +#nullable disable + + /// + /// Parses a version string into a , supporting semantic versioning + /// pre-release labels and/or build metadata (e.g. "12.0.0-alpha.1+build"), unlike . + /// + /// the version string to parse + /// a representing + /// thrown when is null or empty + /// thrown when is not a valid version + public static NuGetVersion ParseVersion(string version) + { + if (string.IsNullOrWhiteSpace(version)) + { + throw new ArgumentException("Version cannot be null or empty", nameof(version)); + } + + if (NuGetVersion.TryParse(version, out var nuGetVersion)) + { + return nuGetVersion; + } + + // fallback to the standard parser, so a meaningful FormatException is still thrown for truly invalid values + return new NuGetVersion(Version.Parse(version)); + } + + /// + /// Same as but returns instead of throwing + /// when cannot be parsed. + /// + /// the version string to parse + /// the parsed , or if parsing failed + /// if was successfully parsed + public static bool TryParseVersion(string version, out NuGetVersion result) + { + if (string.IsNullOrWhiteSpace(version)) + { + result = null; + return false; + } + + return NuGetVersion.TryParse(version, out result); + } + + /// + /// Converts a to a , keeping the traditional + /// 3-part "Major.Minor.Build" formatting (as opposed to , which + /// always includes a 4th "Revision" component, e.g. "11.0.0.0" instead of "11.0.0"). Any + /// pre-release label and/or build metadata is discarded, since cannot + /// represent it. + /// + /// the version to convert + /// a built from the numeric release components of + public static Version ToVersion(NuGetVersion version) + { + return version.Version.Revision > 0 + ? version.Version + : new Version(version.Major, version.Minor, version.Patch); + } + + /// + /// Computes the npm dist-tag conventionally used by CM packages (e.g. "@criticalmanufacturing/ngx-schematics") + /// for a given product/MES version, e.g.: + /// + /// "12.0.0" -> "release-1200" + /// "12.0.0-alpha.1" -> "alpha-1200" + /// "12.0.0-next.2" -> "next-1200" + /// + /// + /// the version to compute the dist-tag for + /// the npm dist-tag for + public static string GetNpmDistTag(NuGetVersion version) + { + var label = version.IsPrerelease ? version.ReleaseLabels.FirstOrDefault() : null; + return $"{(string.IsNullOrWhiteSpace(label) ? "release" : label)}-{version.Major}{version.Minor}{version.Patch}"; + } + + /// + /// Same as , but for a plain (which + /// cannot carry a pre-release label, so the result always uses the "release" dist-tag). + /// + /// the version to compute the dist-tag for + /// the npm dist-tag for + public static string GetNpmDistTag(Version version) + { + return GetNpmDistTag(new NuGetVersion(version.Major, version.Minor, version.Build)); + } /// /// Builds a tree representation of a CmfPackage dependency tree diff --git a/tests/Specs/GenericUtilitiesTests.cs b/tests/Specs/GenericUtilitiesTests.cs new file mode 100644 index 000000000..ec678b55b --- /dev/null +++ b/tests/Specs/GenericUtilitiesTests.cs @@ -0,0 +1,126 @@ +using System; +using Cmf.CLI.Utilities; +using FluentAssertions; +using Xunit; + +namespace tests.Specs +{ + public class GenericUtilitiesTests + { + [Theory] + [InlineData("12.0.0", 12, 0, 0)] + [InlineData("11.1.5", 11, 1, 5)] + [InlineData("10.0.0.0", 10, 0, 0)] + public void ParseVersion_PlainVersion_ParsesCorrectly(string version, int major, int minor, int patch) + { + var result = GenericUtilities.ParseVersion(version); + + result.Major.Should().Be(major); + result.Minor.Should().Be(minor); + result.Patch.Should().Be(patch); + result.IsPrerelease.Should().BeFalse(); + } + + [Theory] + [InlineData("12.0.0-alpha.1", 12, 0, 0, "alpha.1")] + [InlineData("12.0.0-beta", 12, 0, 0, "beta")] + [InlineData("11.1.5-rc.2+build.123", 11, 1, 5, "rc.2")] + public void ParseVersion_PreReleaseVersion_PreservesPreReleaseLabel(string version, int major, int minor, int patch, string expectedRelease) + { + var result = GenericUtilities.ParseVersion(version); + + result.Major.Should().Be(major); + result.Minor.Should().Be(minor); + result.Patch.Should().Be(patch); + result.IsPrerelease.Should().BeTrue(); + result.Release.Should().Be(expectedRelease); + } + + [Fact] + public void ParseVersion_NullOrEmpty_Throws() + { + ((Action)(() => GenericUtilities.ParseVersion(null))).Should().Throw(); + ((Action)(() => GenericUtilities.ParseVersion(""))).Should().Throw(); + ((Action)(() => GenericUtilities.ParseVersion(" "))).Should().Throw(); + } + + [Fact] + public void ParseVersion_Invalid_Throws() + { + ((Action)(() => GenericUtilities.ParseVersion("not-a-version"))).Should().Throw(); + } + + [Theory] + [InlineData("12.0.0-alpha.1")] + [InlineData("12.0.0")] + public void TryParseVersion_ValidVersions_ReturnsTrue(string version) + { + var success = GenericUtilities.TryParseVersion(version, out var result); + + success.Should().BeTrue(); + result.Major.Should().Be(12); + result.Minor.Should().Be(0); + result.Patch.Should().Be(0); + } + + [Fact] + public void TryParseVersion_Invalid_ReturnsFalse() + { + var success = GenericUtilities.TryParseVersion("not-a-version", out var result); + + success.Should().BeFalse(); + result.Should().BeNull(); + } + + [Fact] + public void ParseVersion_PreReleaseAndPlainVersions_AreComparableAndOrdered() + { + // this mirrors how MESVersion is used across the CLI: comparisons must keep working + // regardless of a pre-release label being present in the original string + var preRelease = GenericUtilities.ParseVersion("12.0.0-alpha.1"); + var release = GenericUtilities.ParseVersion("12.0.0"); + var older = GenericUtilities.ParseVersion("11.0.0"); + + // a pre-release version sorts before its associated release version, per semver rules + (preRelease < release).Should().BeTrue(); + (preRelease > older).Should().BeTrue(); + } + + [Theory] + [InlineData("12.0.0", "12.0.0")] + [InlineData("11.1.5", "11.1.5")] + [InlineData("12.0.0-alpha.1", "12.0.0")] + [InlineData("10.0.0.0", "10.0.0")] + public void ToVersion_CollapsesToThreePartVersion_WhenRevisionIsZero(string version, string expected) + { + var nuGetVersion = GenericUtilities.ParseVersion(version); + + var result = GenericUtilities.ToVersion(nuGetVersion); + + result.ToString().Should().Be(expected); + } + + [Theory] + [InlineData("12.0.0", "release-1200")] + [InlineData("11.1.5", "release-1115")] + [InlineData("12.0.0-alpha.1", "alpha-1200")] + [InlineData("12.0.0-next.2", "next-1200")] + [InlineData("11.1.5-beta.2", "beta-1115")] + public void GetNpmDistTag_NuGetVersion_ComputesExpectedDistTag(string version, string expectedDistTag) + { + var nuGetVersion = GenericUtilities.ParseVersion(version); + + var result = GenericUtilities.GetNpmDistTag(nuGetVersion); + + result.Should().Be(expectedDistTag); + } + + [Fact] + public void GetNpmDistTag_PlainVersion_AlwaysUsesReleaseTag() + { + var result = GenericUtilities.GetNpmDistTag(new Version(12, 0, 0)); + + result.Should().Be("release-1200"); + } + } +} diff --git a/tests/Specs/Init.cs b/tests/Specs/Init.cs index 2faf685bb..8d76b127c 100644 --- a/tests/Specs/Init.cs +++ b/tests/Specs/Init.cs @@ -250,6 +250,51 @@ public void Init_NgxSchematicsVersionUsesProvidedValueOverDefault() } } + [Theory] + [InlineData("12.0.0-alpha.1", "alpha-1200")] + [InlineData("11.1.5-beta.2", "beta-1115")] + public void Init_PreReleaseMESVersion_Succeeds(string mesVersion, string expectedNgxSchematicsVersion) + { + var tmp = TestUtilities.GetTmpDirectory(); + var projectName = Convert.ToHexString(Guid.NewGuid().ToByteArray()).Substring(0, 8); + var deploymentDir = "\\\\share\\deployment_dir"; + + var cur = Directory.GetCurrentDirectory(); + try + { + var console = new TestConsole(); + Directory.SetCurrentDirectory(tmp); + + var initCommand = new InitCommand(); + var cmd = new Command("x"); + initCommand.Configure(cmd); + + TestUtilities.GetParser(cmd).Invoke(new[] + { + projectName, + "--infra", TestUtilities.GetFixturePath("init", "infrastructure.json"), + "-c", TestUtilities.GetFixturePath("init", "config.json"), + "--MESVersion", mesVersion, + // --nugetVersion, --testScenariosNugetVersion and --ngxSchematicsVersion are intentionally omitted + "--deploymentDir", deploymentDir, + }, console); + + console.Error.ToString().Should().BeEmpty("command should succeed with a pre-release MES version"); + Assert.True(File.Exists("cmfpackage.json"), "root cmfpackage is missing"); + + var projectConfig = File.ReadAllText(Path.Join(tmp, ".project-config.json")); + projectConfig.Should().Contain($@"""MESVersion"": ""{mesVersion}""", "MESVersion should retain the original pre-release string"); + projectConfig.Should().Contain($@"""NugetVersion"": ""{mesVersion}""", "NugetVersion should default to the MES version"); + projectConfig.Should().Contain($@"""TestScenariosNugetVersion"": ""{mesVersion}""", "TestScenariosNugetVersion should default to the MES version"); + projectConfig.Should().Contain($@"""NGXSchematicsVersion"": ""{expectedNgxSchematicsVersion}""", "NGXSchematicsVersion should be derived from the MES version's pre-release label and numeric components"); + } + finally + { + Directory.SetCurrentDirectory(cur); + Directory.Delete(tmp, true); + } + } + [Fact] public void Init_Fail_ForLTv10() { @@ -291,6 +336,48 @@ public void Init_Fail_ForLTv10() } } + [Fact] + public void Init_Fail_ForLTv10_WithPreReleaseVersion() + { + var console = new TestConsole(); + var tmp = TestUtilities.GetTmpDirectory(); + + var projectName = Convert.ToHexString(Guid.NewGuid().ToByteArray()).Substring(0, 8); + var deploymentDir = "\\\\share\\deployment_dir"; + + var cur = Directory.GetCurrentDirectory(); + try + { + Directory.SetCurrentDirectory(tmp); + + var initCommand = new InitCommand(); + var cmd = new Command("x"); // this is the command name used in help text + initCommand.Configure(cmd); + + TestUtilities.GetParser(cmd).Invoke(new[] + { + projectName, + "-c", TestUtilities.GetFixturePath("init", "config.json"), + "--MESVersion", "8.2.0-alpha.1", + "--nugetVersion", "8.2.0-alpha.1", + "--testScenariosNugetVersion", "8.2.0-alpha.1", + "--nugetRegistry", "http://nuget.example/feed", + "--npmRegistry", "http://npm.example/feed", + "--ISOLocation", "dummy", + "--ngxSchematicsVersion", "1.3.7", + "--deploymentDir", deploymentDir, + }, console); + + // the pre-release label should not stop the numeric comparison from being evaluated correctly + console.Error.ToString().Should().Contain("MES Versions under 10 are no longer supported with the newest version of the CLI. Please use cmf-cli 5.8.0 or lower."); + } + finally + { + Directory.SetCurrentDirectory(cur); + Directory.Delete(tmp, true); + } + } + [Fact] public void Init_Fail_MissingOptionsForGTv10() { diff --git a/tests/Specs/MESVersionValidation.cs b/tests/Specs/MESVersionValidation.cs index 0281f03cc..f991b1b15 100644 --- a/tests/Specs/MESVersionValidation.cs +++ b/tests/Specs/MESVersionValidation.cs @@ -164,6 +164,25 @@ public void IsVersionCompatible_NoExecutionContext_ShouldReturnFalse() result.Should().BeFalse(); } + [Theory] + [InlineData("12.0.0-alpha.1", "11.0.0", true)] // pre-release, current version higher + [InlineData("12.0.0-alpha.1", "12.0.0", true)] // pre-release, exact numeric match + [InlineData("12.0.0-alpha.1", "13.0.0", false)] // pre-release, current version lower + public void ProjectConfig_WithPreReleaseMESVersion_LoadsAndComparesCorrectly(string currentVersion, string minimumVersion, bool expectedResult) + { + // Arrange - this exercises loading a .project-config.json (as produced by `cmf init`) whose + // MESVersion contains a semantic versioning pre-release label (e.g. "12.0.0-alpha.1") + SetupExecutionContext(currentVersion); + var service = new MESVersionValidationService(); + + // Act + var result = service.IsVersionCompatible(minimumVersion); + + // Assert + result.Should().Be(expectedResult); + ExecutionContext.Instance.ProjectConfig.MESVersion.Should().Be(new Version(12, 0, 0)); + } + private void SetupExecutionContext(string mesVersion) { var projConfig = projCfgTemplate.Replace("{MES_VERSION}", mesVersion); From c3f654bb62b8b4032f924e17c8332f30281b0a8a Mon Sep 17 00:00:00 2001 From: pedroapap Date: Thu, 13 Aug 2026 15:46:20 +0000 Subject: [PATCH 2/7] fix: update SharpCompress package version and improve TAR.GZ handling in CmfPackageController --- core/Services/CmfPackageController.cs | 14 +- core/core.csproj | 2 +- tests/Specs/CmfPackageController_FromTgz.cs | 149 ++++++++++++++++++++ 3 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 tests/Specs/CmfPackageController_FromTgz.cs diff --git a/core/Services/CmfPackageController.cs b/core/Services/CmfPackageController.cs index 67b148d96..d6493c771 100644 --- a/core/Services/CmfPackageController.cs +++ b/core/Services/CmfPackageController.cs @@ -158,9 +158,12 @@ public CmfPackageController(IFileInfo file, IFileSystem fileSystem = null, bool Log.Debug("File is a DF package in TAR.GZ format"); using Stream zipToOpen = file.OpenRead(); using GZipStream gzipStream = new GZipStream(zipToOpen, CompressionMode.Decompress); + using var decompressedStream = new MemoryStream(); + gzipStream.CopyTo(decompressedStream); + decompressedStream.Position = 0; var foundManifest = false; - using var tarReader = SharpCompress.Readers.Tar.TarReader.Open(gzipStream, new SharpCompress.Readers.ReaderOptions { }); + using var tarReader = SharpCompress.Readers.Tar.TarReader.OpenReader(decompressedStream, new SharpCompress.Readers.ReaderOptions { }); while (tarReader.MoveToNextEntry()) { var entry = tarReader.Entry; @@ -183,7 +186,10 @@ public CmfPackageController(IFileInfo file, IFileSystem fileSystem = null, bool { using Stream zipToOpen2 = file.OpenRead(); using GZipStream gzipStream2 = new GZipStream(zipToOpen2, CompressionMode.Decompress); - using var tarReader2 = SharpCompress.Readers.Tar.TarReader.Open(gzipStream2, new SharpCompress.Readers.ReaderOptions { }); + using var decompressedStream2 = new MemoryStream(); + gzipStream2.CopyTo(decompressedStream2); + decompressedStream2.Position = 0; + using var tarReader2 = SharpCompress.Readers.Tar.TarReader.OpenReader(decompressedStream2, new SharpCompress.Readers.ReaderOptions { }); while (tarReader2.MoveToNextEntry()) { @@ -782,9 +788,9 @@ public static CmfPackageV1 FromJson(JObject json) var auxArr = (JObject)rootNode.Value; var steps = new List(); - if (auxArr.Property("steps").Value.Type == JTokenType.Array) + var stepsProperty = auxArr.Property("steps"); + if (stepsProperty?.Value is JArray stepsEl) { - var stepsEl = (JArray)auxArr.Property("steps").Value; if (stepsEl != null) { diff --git a/core/core.csproj b/core/core.csproj index 6875b304e..97ebcdc3d 100644 --- a/core/core.csproj +++ b/core/core.csproj @@ -25,7 +25,7 @@ - + diff --git a/tests/Specs/CmfPackageController_FromTgz.cs b/tests/Specs/CmfPackageController_FromTgz.cs new file mode 100644 index 000000000..c77378501 --- /dev/null +++ b/tests/Specs/CmfPackageController_FromTgz.cs @@ -0,0 +1,149 @@ +using System.IO.Abstractions.TestingHelpers; +using Cmf.CLI.Core.Constants; +using Cmf.CLI.Core.Services; +using FluentAssertions; +using tests.Objects; +using Xunit; + +namespace tests.Specs; + +public class CmfPackageController_FromTgz +{ + [Fact] + public void Constructor_ShouldReadXmlManifest_FromPackageFolderInTgz() + { + var fileSystem = new MockFileSystem(); + fileSystem.Directory.CreateDirectory("/repo"); + var packageFile = fileSystem.FileInfo.New("/repo/package.tgz"); + var manifestXml = $""" + + + Cmf.Custom.Data + 1.2.3 + + """; + + var archiveBytes = new DFTGZPackageBuilder() + .CreateEntry($"package/{CoreConstants.DeploymentFrameworkManifestFileName}", manifestXml) + .ToByteArray(); + + using (var stream = packageFile.Create()) + { + stream.Write(archiveBytes, 0, archiveBytes.Length); + stream.Flush(); + } + + var controller = new CmfPackageController(packageFile, fileSystem); + + controller.CmfPackage.Should().NotBeNull(); + controller.CmfPackage.PackageId.Should().Be("Cmf.Custom.Data"); + controller.CmfPackage.Version.Should().Be("1.2.3"); + } + + [Fact] + public void Constructor_ShouldReadJsonManifest_FromPackageFolderInTgz() + { + var fileSystem = new MockFileSystem(); + fileSystem.Directory.CreateDirectory("/repo"); + var packageFile = fileSystem.FileInfo.New("/repo/package.tgz"); + var manifestJson = """ + { + "name": "Cmf.Custom.Data", + "version": "1.2.3", + "packageName": "Cmf.Custom.Data", + "description": "Sample package", + "packageType": "Generic", + "keywords": ["cmf-deployment-package"], + "deployment": { + "packageId": "Cmf.Custom.Data", + "version": "1.2.3" + } + } + """; + + var archiveBytes = new DFTGZPackageBuilder() + .CreateEntry($"package/{CoreConstants.PackageJson}", manifestJson) + .ToByteArray(); + + using (var stream = packageFile.Create()) + { + stream.Write(archiveBytes, 0, archiveBytes.Length); + stream.Flush(); + } + + var controller = new CmfPackageController(packageFile, fileSystem); + + controller.CmfPackage.Should().NotBeNull(); + controller.CmfPackage.PackageId.Should().Be("Cmf.Custom.Data"); + controller.CmfPackage.Version.Should().Be("1.2.3"); + } + + [Fact] + public void Constructor_ShouldReadXmlManifest_FromRootOfTgz() + { + var fileSystem = new MockFileSystem(); + fileSystem.Directory.CreateDirectory("/repo"); + var packageFile = fileSystem.FileInfo.New("/repo/package.tgz"); + var manifestXml = $""" + + + Cmf.Custom.Root + 2.0.0 + + """; + + var archiveBytes = new DFTGZPackageBuilder() + .CreateEntry(CoreConstants.DeploymentFrameworkManifestFileName, manifestXml) + .ToByteArray(); + + using (var stream = packageFile.Create()) + { + stream.Write(archiveBytes, 0, archiveBytes.Length); + stream.Flush(); + } + + var controller = new CmfPackageController(packageFile, fileSystem); + + controller.CmfPackage.Should().NotBeNull(); + controller.CmfPackage.PackageId.Should().Be("Cmf.Custom.Root"); + controller.CmfPackage.Version.Should().Be("2.0.0"); + } + + [Fact] + public void Constructor_ShouldReadJsonManifest_WithoutSteps_FromRootOfTgz() + { + var fileSystem = new MockFileSystem(); + fileSystem.Directory.CreateDirectory("/repo"); + var packageFile = fileSystem.FileInfo.New("/repo/package.tgz"); + var manifestJson = """ + { + "name": "Cmf.Custom.Root", + "version": "2.0.0", + "packageName": "Cmf.Custom.Root", + "description": "Sample package without steps", + "keywords": ["cmf-deployment-package"], + "deployment": { + "packageType": "Generic", + "packageId": "Cmf.Custom.Root", + "version": "2.0.0" + } + } + """; + + var archiveBytes = new DFTGZPackageBuilder() + .CreateEntry(CoreConstants.PackageJson, manifestJson) + .ToByteArray(); + + using (var stream = packageFile.Create()) + { + stream.Write(archiveBytes, 0, archiveBytes.Length); + stream.Flush(); + } + + var controller = new CmfPackageController(packageFile, fileSystem); + + controller.CmfPackage.Should().NotBeNull(); + controller.CmfPackage.PackageId.Should().Be("Cmf.Custom.Root"); + controller.CmfPackage.Version.Should().Be("2.0.0"); + } +} From 7893e749d07c83a60498c513ce9bf91aa8d3aabf Mon Sep 17 00:00:00 2001 From: pedroapap Date: Fri, 14 Aug 2026 17:09:32 +0000 Subject: [PATCH 3/7] refactor: replace Version with MesVersion for improved semantic version handling across the CLI --- cmf-cli/Builders/NPXCommand.cs | 5 +- cmf-cli/Commands/UILayerTemplateCommand.cs | 8 +- .../Commands/build/html/ExtractI18nCommand.cs | 4 +- .../Commands/build/html/LocalizeCommand.cs | 4 +- cmf-cli/Commands/init/InitCommand.cs | 7 +- cmf-cli/Commands/new/DataCommand.cs | 2 +- cmf-cli/Commands/new/HTMLCommand.cs | 2 +- cmf-cli/Commands/new/HelpCommand.cs | 9 +- cmf-cli/Commands/new/IoTCommand.cs | 17 ++-- cmf-cli/Commands/new/TestCommand.cs | 4 +- .../Commands/upgrade/UpgradeBaseCommand.cs | 4 +- .../PackageType/IoTPackageTypeHandler.cs | 9 +- cmf-cli/Program.cs | 2 +- cmf-cli/Utilities/NodeVersionUtilities.cs | 7 +- cmf-cli/Utilities/UpgradeBaseUtilities.cs | 5 +- cmf-cli/services/DependencyVersionService.cs | 21 ++-- core/Objects/MESVersionValidationService.cs | 8 +- core/Objects/ProjectConfig/ProjectConfigV1.cs | 89 ++++++++++------- core/Utilities/GenericUtilities.cs | 99 +++---------------- tests/Objects/DFTGZPackageBuilder.cs | 3 +- tests/Specs/GenericUtilitiesTests.cs | 76 +++++++++----- tests/Specs/Init.cs | 5 +- tests/Specs/MESVersionValidation.cs | 51 +++++++++- tests/Specs/New.cs | 7 +- tests/Specs/NodeVersionUtilities.cs | 13 ++- 25 files changed, 238 insertions(+), 223 deletions(-) diff --git a/cmf-cli/Builders/NPXCommand.cs b/cmf-cli/Builders/NPXCommand.cs index 15bd956b8..3faeedf1d 100644 --- a/cmf-cli/Builders/NPXCommand.cs +++ b/cmf-cli/Builders/NPXCommand.cs @@ -1,8 +1,7 @@ -using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using Cmf.CLI.Core.Objects; +using NuGet.Versioning; namespace Cmf.CLI.Builders { @@ -48,7 +47,7 @@ public override ProcessBuildStep[] GetSteps() // So we get the MES version of the project, and we assume that the user must be running the supported // node version for that MES project var mesVersion = ExecutionContext.Instance?.ProjectConfig?.MESVersion; - if (mesVersion != null && mesVersion < new Version(10, 0)) + if (mesVersion != null && mesVersion < new NuGetVersion(10, 0, 0)) { args.Add("true"); } diff --git a/cmf-cli/Commands/UILayerTemplateCommand.cs b/cmf-cli/Commands/UILayerTemplateCommand.cs index d8fd0f22e..be70ce980 100644 --- a/cmf-cli/Commands/UILayerTemplateCommand.cs +++ b/cmf-cli/Commands/UILayerTemplateCommand.cs @@ -41,13 +41,13 @@ protected void CloneHTMLStarter(Version versionTag, IDirectoryInfo target) target.GetFiles(".gitkeep").FirstOrDefault()?.Delete(); Log.Verbose("cloning html starter"); // git init - (new GitCommand() { Command = "init", WorkingDirectory = target }).Exec(); + new GitCommand() { Command = "init", WorkingDirectory = target }.Exec(); // git remote add origin https://github.com/criticalmanufacturing/html-starter - (new GitCommand() { Command = "remote", WorkingDirectory = target, Args = new[] { "add", "origin", "https://github.com/criticalmanufacturing/html-starter" } }).Exec(); + new GitCommand() { Command = "remote", WorkingDirectory = target, Args = ["add", "origin", "https://github.com/criticalmanufacturing/html-starter"] }.Exec(); // git fetch - (new GitCommand() { Command = "fetch", WorkingDirectory = target }).Exec(); + new GitCommand() { Command = "fetch", WorkingDirectory = target }.Exec(); // git pull origin $vars['HTMLStarterVersion'] - (new GitCommand() { Command = "pull", WorkingDirectory = target, Args = new[] { "origin", versionTag.ToString() } }).Exec(); + new GitCommand() { Command = "pull", WorkingDirectory = target, Args = ["origin", versionTag.ToString()] }.Exec(); Log.Debug("delete .git folder"); this.DeleteFolderWithReadOnlyFiles(target.GetDirectories(".git").FirstOrDefault()); // delete apps/.gitkeep diff --git a/cmf-cli/Commands/build/html/ExtractI18nCommand.cs b/cmf-cli/Commands/build/html/ExtractI18nCommand.cs index c678e5cb2..898d4d2a8 100644 --- a/cmf-cli/Commands/build/html/ExtractI18nCommand.cs +++ b/cmf-cli/Commands/build/html/ExtractI18nCommand.cs @@ -10,10 +10,10 @@ using Cmf.CLI.Utilities; using System.Xml.Linq; using System.Linq; -using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Collections.Generic; +using NuGet.Versioning; namespace Cmf.CLI.Commands.html; @@ -26,7 +26,7 @@ public class ExtractI18nCommand : BaseCommand /// /// The minimum MES Version that supports this command /// - private readonly Version MIN_MES_VERSION = new Version(11, 2, 0); + private readonly NuGetVersion MIN_MES_VERSION = new NuGetVersion(11, 2, 0); /// /// constructor diff --git a/cmf-cli/Commands/build/html/LocalizeCommand.cs b/cmf-cli/Commands/build/html/LocalizeCommand.cs index f69e9c010..086d65fda 100644 --- a/cmf-cli/Commands/build/html/LocalizeCommand.cs +++ b/cmf-cli/Commands/build/html/LocalizeCommand.cs @@ -8,7 +8,7 @@ using Cmf.CLI.Core.Objects; using Cmf.CLI.Utilities; using System.Linq; -using System; +using NuGet.Versioning; namespace Cmf.CLI.Commands.html; @@ -21,7 +21,7 @@ public class LocalizeCommand : BaseCommand /// /// The minimum MES Version that supports this command /// - private readonly Version MIN_MES_VERSION = new Version(11, 2, 0); + private readonly NuGetVersion MIN_MES_VERSION = new NuGetVersion(11, 2, 0); public override void Configure(Command cmd) { diff --git a/cmf-cli/Commands/init/InitCommand.cs b/cmf-cli/Commands/init/InitCommand.cs index 949cc0e7c..a6343d97d 100644 --- a/cmf-cli/Commands/init/InitCommand.cs +++ b/cmf-cli/Commands/init/InitCommand.cs @@ -17,6 +17,7 @@ using Cmf.CLI.Utilities; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; +using NuGet.Versioning; namespace Cmf.CLI.Commands { @@ -514,7 +515,7 @@ internal void Execute(InitArguments x) if (string.IsNullOrWhiteSpace(x.ngxSchematicsVersion)) { - var mesVer = GenericUtilities.ParseVersion(x.BaseVersion); + var mesVer = NuGetVersion.Parse(x.BaseVersion); x.ngxSchematicsVersion = GenericUtilities.GetNpmDistTag(mesVer); } @@ -578,10 +579,10 @@ internal void Execute(InitArguments x) #region version-specific bits - var version = GenericUtilities.ToVersion(GenericUtilities.ParseVersion(x.BaseVersion)); + var version = new NuGetVersion(x.BaseVersion); args.AddRange(new []{ "--dotnetSDKVersion", ExecutionContext.ServiceProvider.GetService().DotNetSdk(version) }); - if(version < new Version(10,0)) + if(version < new NuGetVersion(10, 0, 0)) { throw new CliException("MES Versions under 10 are no longer supported with the newest version of the CLI. Please use cmf-cli 5.8.0 or lower."); } diff --git a/cmf-cli/Commands/new/DataCommand.cs b/cmf-cli/Commands/new/DataCommand.cs index 4118184a3..591b2616e 100644 --- a/cmf-cli/Commands/new/DataCommand.cs +++ b/cmf-cli/Commands/new/DataCommand.cs @@ -58,7 +58,7 @@ public override void Configure(Command cmd) protected override List GenerateArgs(IDirectoryInfo projectRoot, IDirectoryInfo workingDir, List args) { var repoType = ExecutionContext.Instance.ProjectConfig.RepositoryType ?? CliConstants.DefaultRepositoryType; - Version version = ExecutionContext.Instance.ProjectConfig.MESVersion; + var version = ExecutionContext.Instance.ProjectConfig.MESVersion; var relativePathToRoot = this.fileSystem.Path.Join("..", //always one level deeper diff --git a/cmf-cli/Commands/new/HTMLCommand.cs b/cmf-cli/Commands/new/HTMLCommand.cs index 934df4fd4..a068d311a 100644 --- a/cmf-cli/Commands/new/HTMLCommand.cs +++ b/cmf-cli/Commands/new/HTMLCommand.cs @@ -104,7 +104,7 @@ public void ExecuteV10(IDirectoryInfo workingDir, string version) var packageName = base.GeneratePackageName(workingDir)!.Value.Item1; var packageDir = workingDir.GetDirectories(packageName).First(); - var schematicsVersion = !string.IsNullOrEmpty(ngxSchematicsVersion) ? ngxSchematicsVersion : GenericUtilities.GetNpmDistTag(mesVersion); + var schematicsVersion = !string.IsNullOrEmpty(ngxSchematicsVersion?.ToString()) ? ngxSchematicsVersion.ToString() : GenericUtilities.GetNpmDistTag(mesVersion); //After v11 we use Angular default routing var routing = mesVersion.Major >= 11 ? "true" : "false"; diff --git a/cmf-cli/Commands/new/HelpCommand.cs b/cmf-cli/Commands/new/HelpCommand.cs index 2779aad5e..896575c9a 100644 --- a/cmf-cli/Commands/new/HelpCommand.cs +++ b/cmf-cli/Commands/new/HelpCommand.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.CommandLine; using System.IO.Abstractions; @@ -16,6 +15,7 @@ using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NuGet.Versioning; namespace Cmf.CLI.Commands.New { @@ -64,9 +64,10 @@ protected override List GenerateArgs(IDirectoryInfo projectRoot, IDirect projectRoot.FullName) ).Replace("\\", "/"); + var mesVersion = ExecutionContext.Instance.ProjectConfig.MESVersion; var requireModuleSES = - ExecutionContext.Instance.ProjectConfig.MESVersion >= new Version(11, 0, 0) && - ExecutionContext.Instance.ProjectConfig.MESVersion < new Version(11, 2, 0); + mesVersion >= new NuGetVersion(11, 0, 0) && + mesVersion < new NuGetVersion(11, 2, 0); var angularDeps = ExecutionContext.ServiceProvider.GetService().Angular(ExecutionContext.Instance.ProjectConfig.MESVersion); @@ -102,7 +103,7 @@ public void Execute(IDirectoryInfo workingDir, string version) var mesVersion = ExecutionContext.Instance.ProjectConfig.MESVersion; - this.schematicsVersion = ngxSchematicsVersion ?? GenericUtilities.GetNpmDistTag(mesVersion); + this.schematicsVersion = !string.IsNullOrEmpty(ngxSchematicsVersion?.ToString()) ? ngxSchematicsVersion.ToString() : GenericUtilities.GetNpmDistTag(mesVersion); //Switch between v10 and v11 template switch (majorVersion) diff --git a/cmf-cli/Commands/new/IoTCommand.cs b/cmf-cli/Commands/new/IoTCommand.cs index 8324a1b9d..2546445da 100644 --- a/cmf-cli/Commands/new/IoTCommand.cs +++ b/cmf-cli/Commands/new/IoTCommand.cs @@ -7,7 +7,7 @@ using Cmf.CLI.Services; using Cmf.CLI.Utilities; using Microsoft.Extensions.DependencyInjection; -using System; +using NuGet.Versioning; using System.Collections.Generic; using System.CommandLine; using System.IO.Abstractions; @@ -114,10 +114,10 @@ public void Execute(IDirectoryInfo workingDir, string version, string htmlPackag var mesVersion = ExecutionContext.Instance.ProjectConfig.MESVersion; // (ATL) Automation Task Library Package // only introduced in v10.2.7 - var executeV10ATL = !isAngularPackage && mesVersion >= new Version(10, 2, 7) && mesVersion < new Version(11, 0, 0); + var executeV10ATL = !isAngularPackage && mesVersion >= new NuGetVersion(10, 2, 7) && mesVersion < new NuGetVersion(11, 0, 0); // only introduced in v11 - var executeV11ATL = !isAngularPackage && mesVersion >= new Version(11, 0, 0); + var executeV11ATL = !isAngularPackage && mesVersion >= new NuGetVersion(11, 0, 0); if (executeV10ATL) { @@ -175,8 +175,10 @@ public void ExecuteV10AngularPackage(IDirectoryInfo workingDir, string version, { throw new CliException(CliMessages.IoTV10HTMLPackageMustBeProvided); } - + + var mesVersion = ExecutionContext.Instance.ProjectConfig.MESVersion; var ngxSchematicsVersion = ExecutionContext.Instance.ProjectConfig.NGXSchematicsVersion; + var schematicsVersion = !string.IsNullOrEmpty(ngxSchematicsVersion?.ToString()) ? ngxSchematicsVersion.ToString() : GenericUtilities.GetNpmDistTag(mesVersion); IDirectoryInfo htmlPackageDir = fileSystem.DirectoryInfo.New(htmlPackageLocation); @@ -208,11 +210,6 @@ public void ExecuteV10AngularPackage(IDirectoryInfo workingDir, string version, } var iotCustomPackageWorkDir = iotCustomPackage.GetFileInfo().Directory; - var iotCustomPackageName = base.GeneratePackageName(iotCustomPackageWorkDir)!.Value.Item1; - - var mesVersion = ExecutionContext.Instance.ProjectConfig.MESVersion; - - var schematicsVersion = ngxSchematicsVersion ?? GenericUtilities.GetNpmDistTag(mesVersion); Log.Debug($"Creating new IoT Workspace {packageName}"); @@ -276,7 +273,7 @@ public void ExecuteV10AngularPackage(IDirectoryInfo workingDir, string version, #endregion Link To HTML Package } - private static void InstallYoeman(IDirectoryInfo iotCustomPackageWorkDir, Version mesVersion) + private static void InstallYoeman(IDirectoryInfo iotCustomPackageWorkDir, NuGetVersion mesVersion) { Log.Debug($"Installing Yeoman"); diff --git a/cmf-cli/Commands/new/TestCommand.cs b/cmf-cli/Commands/new/TestCommand.cs index 49a8fbc01..f19c37929 100644 --- a/cmf-cli/Commands/new/TestCommand.cs +++ b/cmf-cli/Commands/new/TestCommand.cs @@ -6,10 +6,10 @@ using System.Collections.Generic; using System.CommandLine; using System.IO.Abstractions; -using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Cmf.CLI.Services; +using NuGet.Versioning; namespace Cmf.CLI.Commands.New { @@ -108,7 +108,7 @@ public void Execute(string version) #region version-specific bits args.AddRange(new []{ "--targetFramework", ExecutionContext.ServiceProvider.GetService().DotNetTargetFramework(mesVersion) }); - if (mesVersion >= new Version(11, 2, 3)) + if (mesVersion >= new NuGetVersion(11, 2, 3)) { args.Add("--hostPerformanceTests"); } diff --git a/cmf-cli/Commands/upgrade/UpgradeBaseCommand.cs b/cmf-cli/Commands/upgrade/UpgradeBaseCommand.cs index 2f6ccb2d1..609ef8f43 100644 --- a/cmf-cli/Commands/upgrade/UpgradeBaseCommand.cs +++ b/cmf-cli/Commands/upgrade/UpgradeBaseCommand.cs @@ -6,7 +6,7 @@ using Cmf.CLI.Factories; using Cmf.CLI.Utilities; using Microsoft.Extensions.DependencyInjection; -using System; +using NuGet.Versioning; using System.Collections.Generic; using System.CommandLine; using System.IO; @@ -144,7 +144,7 @@ private void UpdateProjectConfig(IDirectoryInfo packagePath, string baseVersion) text = UpgradeBaseUtilities.UpdateJsonValue(text, key, baseVersion); } - if (GenericUtilities.ParseVersion(baseVersion).Major >= 11) + if (NuGetVersion.Parse(baseVersion).Major >= 11) { // TODO: find a more elegant way to apply these changes to files/packages when this command is executed. // For the moment, sneaking this if-statement in will do the job but long-term we'll need an approach that diff --git a/cmf-cli/Handlers/PackageType/IoTPackageTypeHandler.cs b/cmf-cli/Handlers/PackageType/IoTPackageTypeHandler.cs index 640ece42e..b29874a9a 100644 --- a/cmf-cli/Handlers/PackageType/IoTPackageTypeHandler.cs +++ b/cmf-cli/Handlers/PackageType/IoTPackageTypeHandler.cs @@ -9,6 +9,7 @@ using Cmf.CLI.Utilities; using Microsoft.CodeAnalysis; using Newtonsoft.Json; +using NuGet.Versioning; using System; using System.Collections.Generic; using System.IO; @@ -479,12 +480,12 @@ private bool IsAngularProject(string path) "angular.json")); } - private List AddAutomationTaskLibrariesStep(Version targetVersion, CmfPackage cmfPackage, List defaultSteps, string packageLocation = "src") + private List AddAutomationTaskLibrariesStep(NuGetVersion targetVersion, CmfPackage cmfPackage, List defaultSteps, string packageLocation = "src") { // Introduced in version 10.2.x if ((targetVersion.Major > 10 || (targetVersion.Major == 10 && targetVersion.Minor >= 2 && - targetVersion.Build >= 7)) && !this.IsAngularProject(cmfPackage.GetFileInfo().Directory.FullName)) + targetVersion.Patch >= 7)) && !this.IsAngularProject(cmfPackage.GetFileInfo().Directory.FullName)) { var packages = string.Join(",", this.GetPackagesWithTaskLibraries(this.GetPackageJsons(cmfPackage, packageLocation))); @@ -499,10 +500,10 @@ private List AddAutomationTaskLibrariesStep(Version targetVersion, CmfPack return defaultSteps; } - private List AddAutomationBusinessScenarioStep(Version targetVersion, CmfPackage cmfPackage, List defaultSteps, string packageLocation = "src") + private List AddAutomationBusinessScenarioStep(NuGetVersion targetVersion, CmfPackage cmfPackage, List defaultSteps, string packageLocation = "src") { // Introduced in version 11.1.x - if ((targetVersion.Major > 11 || (targetVersion.Major == 11 && targetVersion.Minor >= 1))) + if (targetVersion.Major > 11 || (targetVersion.Major == 11 && targetVersion.Minor >= 1)) { var packages = string.Join(",", this.GetPackagesWithBusinessScenarios(this.GetPackageJsons(cmfPackage, packageLocation))); diff --git a/cmf-cli/Program.cs b/cmf-cli/Program.cs index 9cc94fd72..fdfbe36a0 100644 --- a/cmf-cli/Program.cs +++ b/cmf-cli/Program.cs @@ -78,7 +78,7 @@ public static async Task Main(string[] args) .InitializeClientsForRepositories(ExecutionContext.Instance.FileSystem); // Global validation for all CLI core commands - ValidateMesVersion(ExecutionContext.Instance.ProjectConfig?.MESVersion?.Major); + ValidateMesVersion(ExecutionContext.Instance.ProjectConfig?.MESVersion.Major); // Parse and invoke using beta5 pattern var parseResult = rootCommand.Parse(args); diff --git a/cmf-cli/Utilities/NodeVersionUtilities.cs b/cmf-cli/Utilities/NodeVersionUtilities.cs index c9808cd6e..10b8760f7 100644 --- a/cmf-cli/Utilities/NodeVersionUtilities.cs +++ b/cmf-cli/Utilities/NodeVersionUtilities.cs @@ -1,4 +1,5 @@ using Cmf.CLI.Core; +using NuGet.Versioning; using System; using System.Diagnostics; using System.Runtime.InteropServices; @@ -15,7 +16,7 @@ public static class NodeVersionUtilities /// Gets the installed Node.js version /// /// The installed Node.js version, or null if Node.js is not installed - public static Version GetInstalledNodeVersion() + public static NuGetVersion GetInstalledNodeVersion() { try { @@ -51,7 +52,7 @@ public static Version GetInstalledNodeVersion() int major = int.Parse(match.Groups[1].Value); int minor = int.Parse(match.Groups[2].Value); int patch = int.Parse(match.Groups[3].Value); - return new Version(major, minor, patch); + return new NuGetVersion(major, minor, patch); } return null; @@ -69,7 +70,7 @@ public static Version GetInstalledNodeVersion() /// The target MES version /// The required Node.js major version /// Thrown when Node.js is not installed or the version is incompatible - public static void ValidateNodeVersion(Version mesVersion, string requiredNodeMajorVersion) + public static void ValidateNodeVersion(SemanticVersion mesVersion, string requiredNodeMajorVersion) { var installedVersion = GetInstalledNodeVersion(); diff --git a/cmf-cli/Utilities/UpgradeBaseUtilities.cs b/cmf-cli/Utilities/UpgradeBaseUtilities.cs index c813a1d5d..421b571d6 100644 --- a/cmf-cli/Utilities/UpgradeBaseUtilities.cs +++ b/cmf-cli/Utilities/UpgradeBaseUtilities.cs @@ -2,15 +2,14 @@ using System.IO; using System.IO.Abstractions; using System.Linq; -using System.Reflection; using System.Text.RegularExpressions; using Cmf.CLI.Core; using Cmf.CLI.Core.Objects; using Newtonsoft.Json.Linq; using Newtonsoft.Json; using Cmf.CLI.Core.Enums; -using System.Text.Json.Nodes; using System.Runtime.CompilerServices; +using NuGet.Versioning; [assembly: InternalsVisibleTo("tests")] namespace Cmf.CLI.Utilities @@ -56,7 +55,7 @@ public static void UpdateNPMProject(IFileSystem fileSystem, CmfPackage cmfPackag string[] filesToUpdate = fileSystem.Directory.GetFiles(cmfPackage.GetFileInfo().DirectoryName, "package.json", SearchOption.AllDirectories); // matches any npm dist-tag in the "{label}-{version}" convention (e.g. "release-1200", "alpha-1200", "next-1200") string pattern = @"(?:release|alpha|beta|rc|next|canary|preview)-\d+"; - string newDistTag = GenericUtilities.GetNpmDistTag(GenericUtilities.ParseVersion(version)); + string newDistTag = GenericUtilities.GetNpmDistTag(NuGetVersion.Parse(version)); foreach (string filePath in filesToUpdate.Where(path => !path.Contains("node_modules") && !path.Contains("dist"))) { diff --git a/cmf-cli/services/DependencyVersionService.cs b/cmf-cli/services/DependencyVersionService.cs index bf408f460..c11c70409 100644 --- a/cmf-cli/services/DependencyVersionService.cs +++ b/cmf-cli/services/DependencyVersionService.cs @@ -1,4 +1,5 @@ using System; +using NuGet.Versioning; namespace Cmf.CLI.Services; @@ -21,35 +22,35 @@ public interface IDependencyVersionService /// /// The product version to use /// String representing the .NET SDK version - string DotNetSdk(Version productVersion); + string DotNetSdk(NuGetVersion productVersion); /// /// Returns the expected .NET Target Framework for the given product version /// /// /// String representing the .NET Target Framework - string DotNetTargetFramework(Version productVersion); + string DotNetTargetFramework(NuGetVersion productVersion); /// /// Returns the expected Node.js version for the given product version /// /// The product version to use /// String representing the Node.js version - string Node(Version productVersion); + string Node(SemanticVersion productVersion); /// /// Returns the expected Angular CLI major version for the given product version /// /// The product version to use /// String representing the Angular CLI major version - string AngularCLI(Version productVersion); + string AngularCLI(SemanticVersion productVersion); /// /// Returns the expected Angular dependencies for the given product version /// /// The product version to use /// AngularDeps representing the Angular dependencies - AngularDeps Angular(Version productVersion); + AngularDeps Angular(SemanticVersion productVersion); } /// @@ -79,11 +80,11 @@ public class DependencyVersionService : IDependencyVersionService public const string NG17_TS = "5.3.3"; public const string NG21_TS = "5.9.3"; - public string DotNetSdk(Version productVersion) => productVersion.Major >= 11 ? NET8SDK : NET6SDK; - public string DotNetTargetFramework(Version productVersion) => productVersion.Major >= 11 ? NET8TARGETFRAMEWORK : NET6TARGETFRAMEWORK; - public string Node(Version productVersion) => productVersion.Major >= 11 ? NODE20 : NODE18; + public string DotNetSdk(NuGetVersion productVersion) => productVersion.Major >= 11 ? NET8SDK : NET6SDK; + public string DotNetTargetFramework(NuGetVersion productVersion) => productVersion.Major >= 11 ? NET8TARGETFRAMEWORK : NET6TARGETFRAMEWORK; + public string Node(SemanticVersion productVersion) => productVersion.Major >= 11 ? NODE20 : NODE18; - public AngularDeps Angular(Version productVersion) => + public AngularDeps Angular(SemanticVersion productVersion) => productVersion.Major switch { <= 10 => new AngularDeps() @@ -113,5 +114,5 @@ public AngularDeps Angular(Version productVersion) => _ => throw new NotSupportedException($"No Angular dependencies defined for MES version {productVersion}") }; - public string AngularCLI(Version productVersion) => this.Angular(productVersion).CLI.Major.ToString(); + public string AngularCLI(SemanticVersion productVersion) => this.Angular(productVersion).CLI.Major.ToString(); } \ No newline at end of file diff --git a/core/Objects/MESVersionValidationService.cs b/core/Objects/MESVersionValidationService.cs index e00c5505c..5bc140725 100644 --- a/core/Objects/MESVersionValidationService.cs +++ b/core/Objects/MESVersionValidationService.cs @@ -1,4 +1,6 @@ using System; +using Cmf.CLI.Utilities; +using NuGet.Versioning; namespace Cmf.CLI.Core.Objects { @@ -43,9 +45,9 @@ public void ValidateMinimumVersion(string minimumVersion) return; // No validation needed if no minimum version is specified } - if (!Version.TryParse(minimumVersion, out Version minVersion)) + if (!NuGetVersion.TryParse(minimumVersion, out NuGetVersion minVersion)) { - throw new ArgumentException($"Invalid minimum version format: {minimumVersion}. Expected format: 'Major.Minor.Build' (e.g., '11.0.0')"); + throw new ArgumentException($"Invalid minimum version format: {minimumVersion}."); } var currentVersion = ExecutionContext.Instance?.ProjectConfig?.MESVersion; @@ -68,7 +70,7 @@ public bool IsVersionCompatible(string minimumVersion) return true; // No minimum version requirement } - if (!Version.TryParse(minimumVersion, out Version minVersion)) + if (!NuGetVersion.TryParse(minimumVersion, out var minVersion)) { return false; } diff --git a/core/Objects/ProjectConfig/ProjectConfigV1.cs b/core/Objects/ProjectConfig/ProjectConfigV1.cs index 00900397e..7fe45cd8f 100644 --- a/core/Objects/ProjectConfig/ProjectConfigV1.cs +++ b/core/Objects/ProjectConfig/ProjectConfigV1.cs @@ -27,17 +27,20 @@ public class ProjectConfigV1 [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public int? RESTPort { get; set; } public string Tenant { get; set; } - [Newtonsoft.Json.JsonConverter(typeof(LenientVersionConverter))] - public Version MESVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(VersionStringConverter))] + public NuGetVersion MESVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(VersionStringConverter))] public SemanticVersion DevTasksVersion { get; set; } - [Newtonsoft.Json.JsonConverter(typeof(LenientVersionConverter))] - public Version HTMLStarterVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(VersionStringConverter))] + public SemanticVersion HTMLStarterVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(VersionStringConverter))] public SemanticVersion YoGeneratorVersion { get; set; } - public string NGXSchematicsVersion { get; set; } - [Newtonsoft.Json.JsonConverter(typeof(LenientVersionConverter))] - public Version NugetVersion { get; set; } - [Newtonsoft.Json.JsonConverter(typeof(LenientVersionConverter))] - public Version TestScenariosNugetVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(VersionStringConverter))] + public SemanticVersion NGXSchematicsVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(VersionStringConverter))] + public NuGetVersion NugetVersion { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(VersionStringConverter))] + public NuGetVersion TestScenariosNugetVersion { get; set; } [Newtonsoft.Json.JsonConverter(typeof(BooleanJsonConverter))] public bool IsSslEnabled { get; set; } public string vmHostname { get; set; } @@ -74,6 +77,48 @@ public class ProjectConfigV1 public string Product { get; set; } } +/*** + * Converts project-config version strings into the concrete CLR types expected by the model. + * This is required because .project-config.json stores values as strings while fields such as + * MESVersion and NugetVersion are typed as NuGetVersion/SemanticVersion instances. + * Without this conversion, Json.NET can try to assign a SemanticVersion to a NuGetVersion and throw + * an invalid cast when prerelease values such as "12.0.0-beta.1" are deserialized. + */ +public class VersionStringConverter : Newtonsoft.Json.JsonConverter + where T : class +{ + public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null || reader.Value is null) + { + return null; + } + + var value = reader.Value?.ToString(); + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } + + if (typeof(T) == typeof(NuGetVersion)) + { + return (T)(object)NuGetVersion.Parse(value); + } + + if (typeof(T) == typeof(SemanticVersion)) + { + return (T)(object)SemanticVersion.Parse(value); + } + + throw new NotSupportedException($"Unsupported version type: {typeof(T).FullName}"); + } + + public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) + { + writer.WriteValue(value?.ToString()); + } +} + public class BooleanJsonConverter : Newtonsoft.Json.JsonConverter { public override bool CanRead => true; @@ -109,30 +154,4 @@ public override void WriteJson( JsonWriter writer, object value, JsonSerializer { } -} - -/// -/// Converts a version string to/from a , tolerating semantic versioning -/// pre-release labels and/or build metadata (e.g. "12.0.0-alpha.1+build"). Only the numeric -/// release components (Major.Minor.Build[.Revision]) are kept, since has -/// no concept of pre-release/build metadata. This keeps backward compatibility with existing -/// project configs that only have plain "Major.Minor.Build" versions. -/// -public class LenientVersionConverter : Newtonsoft.Json.JsonConverter -{ - public override Version ReadJson(JsonReader reader, Type objectType, Version existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - { - return null; - } - - var value = reader.Value?.ToString(); - return string.IsNullOrWhiteSpace(value) ? null : GenericUtilities.ToVersion(GenericUtilities.ParseVersion(value)); - } - - public override void WriteJson(JsonWriter writer, Version value, JsonSerializer serializer) - { - writer.WriteValue(value?.ToString()); - } } \ No newline at end of file diff --git a/core/Utilities/GenericUtilities.cs b/core/Utilities/GenericUtilities.cs index bc0f7b150..785a39cf3 100644 --- a/core/Utilities/GenericUtilities.cs +++ b/core/Utilities/GenericUtilities.cs @@ -186,92 +186,6 @@ public static IEnumerable Flatten( } #nullable disable - /// - /// Parses a version string into a , supporting semantic versioning - /// pre-release labels and/or build metadata (e.g. "12.0.0-alpha.1+build"), unlike . - /// - /// the version string to parse - /// a representing - /// thrown when is null or empty - /// thrown when is not a valid version - public static NuGetVersion ParseVersion(string version) - { - if (string.IsNullOrWhiteSpace(version)) - { - throw new ArgumentException("Version cannot be null or empty", nameof(version)); - } - - if (NuGetVersion.TryParse(version, out var nuGetVersion)) - { - return nuGetVersion; - } - - // fallback to the standard parser, so a meaningful FormatException is still thrown for truly invalid values - return new NuGetVersion(Version.Parse(version)); - } - - /// - /// Same as but returns instead of throwing - /// when cannot be parsed. - /// - /// the version string to parse - /// the parsed , or if parsing failed - /// if was successfully parsed - public static bool TryParseVersion(string version, out NuGetVersion result) - { - if (string.IsNullOrWhiteSpace(version)) - { - result = null; - return false; - } - - return NuGetVersion.TryParse(version, out result); - } - - /// - /// Converts a to a , keeping the traditional - /// 3-part "Major.Minor.Build" formatting (as opposed to , which - /// always includes a 4th "Revision" component, e.g. "11.0.0.0" instead of "11.0.0"). Any - /// pre-release label and/or build metadata is discarded, since cannot - /// represent it. - /// - /// the version to convert - /// a built from the numeric release components of - public static Version ToVersion(NuGetVersion version) - { - return version.Version.Revision > 0 - ? version.Version - : new Version(version.Major, version.Minor, version.Patch); - } - - /// - /// Computes the npm dist-tag conventionally used by CM packages (e.g. "@criticalmanufacturing/ngx-schematics") - /// for a given product/MES version, e.g.: - /// - /// "12.0.0" -> "release-1200" - /// "12.0.0-alpha.1" -> "alpha-1200" - /// "12.0.0-next.2" -> "next-1200" - /// - /// - /// the version to compute the dist-tag for - /// the npm dist-tag for - public static string GetNpmDistTag(NuGetVersion version) - { - var label = version.IsPrerelease ? version.ReleaseLabels.FirstOrDefault() : null; - return $"{(string.IsNullOrWhiteSpace(label) ? "release" : label)}-{version.Major}{version.Minor}{version.Patch}"; - } - - /// - /// Same as , but for a plain (which - /// cannot carry a pre-release label, so the result always uses the "release" dist-tag). - /// - /// the version to compute the dist-tag for - /// the npm dist-tag for - public static string GetNpmDistTag(Version version) - { - return GetNpmDistTag(new NuGetVersion(version.Major, version.Minor, version.Build)); - } - /// /// Builds a tree representation of a CmfPackage dependency tree /// @@ -369,6 +283,19 @@ public static string BuildEnvVarPrefix(RepositoryCredentialsType repositoryType, return repositoryType.ToString().ToLower() + "__" + new string(baseUri.Select(ch => strip.Contains(ch) ? '_' : ch).ToArray()); } + /// + /// Computes the npm dist-tag for a full SemVer value, preserving the prerelease label when present. + /// + /// the parsed NuGet version to convert + /// the npm dist-tag for + public static string GetNpmDistTag(NuGetVersion version) + { + // ReleaseLabels are dot separated values from the pre-release part of the version, e.g. "alpha.1" or "beta.2". + // We only want the first label (e.g. "alpha" or "beta") for the dist-tag. + var label = version.IsPrerelease ? version.ReleaseLabels.FirstOrDefault() : null; + return $"{(string.IsNullOrWhiteSpace(label) ? "release" : label)}-{version.Major}{version.Minor}{version.Patch}"; + } + #endregion Public Methods #region Private Methods diff --git a/tests/Objects/DFTGZPackageBuilder.cs b/tests/Objects/DFTGZPackageBuilder.cs index b00d9a20c..e8393c639 100644 --- a/tests/Objects/DFTGZPackageBuilder.cs +++ b/tests/Objects/DFTGZPackageBuilder.cs @@ -83,8 +83,7 @@ public DFTGZPackageBuilder CreateManifest(string id, string version, Dictionary< /// public byte[] ToByteArray() { - // Flush ZipArchive - // gzArchive.Flush(); + tarArchive.Dispose(); gzArchive.Dispose(); byte[] result = memoryStream.ToArray(); diff --git a/tests/Specs/GenericUtilitiesTests.cs b/tests/Specs/GenericUtilitiesTests.cs index ec678b55b..3f6de739a 100644 --- a/tests/Specs/GenericUtilitiesTests.cs +++ b/tests/Specs/GenericUtilitiesTests.cs @@ -1,6 +1,8 @@ using System; +using Cmf.CLI.Core.Objects; using Cmf.CLI.Utilities; using FluentAssertions; +using NuGet.Versioning; using Xunit; namespace tests.Specs @@ -13,7 +15,7 @@ public class GenericUtilitiesTests [InlineData("10.0.0.0", 10, 0, 0)] public void ParseVersion_PlainVersion_ParsesCorrectly(string version, int major, int minor, int patch) { - var result = GenericUtilities.ParseVersion(version); + var result = NuGetVersion.Parse(version); result.Major.Should().Be(major); result.Minor.Should().Be(minor); @@ -27,7 +29,7 @@ public void ParseVersion_PlainVersion_ParsesCorrectly(string version, int major, [InlineData("11.1.5-rc.2+build.123", 11, 1, 5, "rc.2")] public void ParseVersion_PreReleaseVersion_PreservesPreReleaseLabel(string version, int major, int minor, int patch, string expectedRelease) { - var result = GenericUtilities.ParseVersion(version); + var result = NuGetVersion.Parse(version); result.Major.Should().Be(major); result.Minor.Should().Be(minor); @@ -39,15 +41,15 @@ public void ParseVersion_PreReleaseVersion_PreservesPreReleaseLabel(string versi [Fact] public void ParseVersion_NullOrEmpty_Throws() { - ((Action)(() => GenericUtilities.ParseVersion(null))).Should().Throw(); - ((Action)(() => GenericUtilities.ParseVersion(""))).Should().Throw(); - ((Action)(() => GenericUtilities.ParseVersion(" "))).Should().Throw(); + ((Action)(() => NuGetVersion.Parse(null))).Should().Throw(); + ((Action)(() => NuGetVersion.Parse(""))).Should().Throw(); + ((Action)(() => NuGetVersion.Parse(" "))).Should().Throw(); } [Fact] public void ParseVersion_Invalid_Throws() { - ((Action)(() => GenericUtilities.ParseVersion("not-a-version"))).Should().Throw(); + ((Action)(() => NuGetVersion.Parse("not-a-version"))).Should().Throw(); } [Theory] @@ -55,7 +57,7 @@ public void ParseVersion_Invalid_Throws() [InlineData("12.0.0")] public void TryParseVersion_ValidVersions_ReturnsTrue(string version) { - var success = GenericUtilities.TryParseVersion(version, out var result); + var success = NuGetVersion.TryParse(version, out var result); success.Should().BeTrue(); result.Major.Should().Be(12); @@ -66,7 +68,7 @@ public void TryParseVersion_ValidVersions_ReturnsTrue(string version) [Fact] public void TryParseVersion_Invalid_ReturnsFalse() { - var success = GenericUtilities.TryParseVersion("not-a-version", out var result); + var success = NuGetVersion.TryParse("not-a-version", out var result); success.Should().BeFalse(); result.Should().BeNull(); @@ -77,29 +79,15 @@ public void ParseVersion_PreReleaseAndPlainVersions_AreComparableAndOrdered() { // this mirrors how MESVersion is used across the CLI: comparisons must keep working // regardless of a pre-release label being present in the original string - var preRelease = GenericUtilities.ParseVersion("12.0.0-alpha.1"); - var release = GenericUtilities.ParseVersion("12.0.0"); - var older = GenericUtilities.ParseVersion("11.0.0"); + var preRelease = NuGetVersion.Parse("12.0.0-alpha.1"); + var release = NuGetVersion.Parse("12.0.0"); + var older = NuGetVersion.Parse("11.0.0"); // a pre-release version sorts before its associated release version, per semver rules (preRelease < release).Should().BeTrue(); (preRelease > older).Should().BeTrue(); } - [Theory] - [InlineData("12.0.0", "12.0.0")] - [InlineData("11.1.5", "11.1.5")] - [InlineData("12.0.0-alpha.1", "12.0.0")] - [InlineData("10.0.0.0", "10.0.0")] - public void ToVersion_CollapsesToThreePartVersion_WhenRevisionIsZero(string version, string expected) - { - var nuGetVersion = GenericUtilities.ParseVersion(version); - - var result = GenericUtilities.ToVersion(nuGetVersion); - - result.ToString().Should().Be(expected); - } - [Theory] [InlineData("12.0.0", "release-1200")] [InlineData("11.1.5", "release-1115")] @@ -108,7 +96,7 @@ public void ToVersion_CollapsesToThreePartVersion_WhenRevisionIsZero(string vers [InlineData("11.1.5-beta.2", "beta-1115")] public void GetNpmDistTag_NuGetVersion_ComputesExpectedDistTag(string version, string expectedDistTag) { - var nuGetVersion = GenericUtilities.ParseVersion(version); + var nuGetVersion = NuGetVersion.Parse(version); var result = GenericUtilities.GetNpmDistTag(nuGetVersion); @@ -118,9 +106,43 @@ public void GetNpmDistTag_NuGetVersion_ComputesExpectedDistTag(string version, s [Fact] public void GetNpmDistTag_PlainVersion_AlwaysUsesReleaseTag() { - var result = GenericUtilities.GetNpmDistTag(new Version(12, 0, 0)); + var result = GenericUtilities.GetNpmDistTag(new NuGetVersion(12, 0, 0)); result.Should().Be("release-1200"); } + + [Fact] + public void GetNpmDistTag_PreReleaseVersion_ShouldUsePrereleaseDistTag() + { + // Prerelease MES versions should map to the corresponding prerelease npm dist-tag rather than the release tag. + var result = GenericUtilities.GetNpmDistTag(NuGetVersion.Parse("12.0.0-beta.2")); + + result.Should().Be("beta-1200"); + } + + [Theory] + [InlineData("12.0.0", "release-1200")] + [InlineData("12.0.0-beta.2", "beta-1200")] + public void GetNpmDistTag_NuGetVersion_UsesSemVerAwareDistTag(string version, string expectedDistTag) + { + var mesVersion = NuGetVersion.Parse(version); + + var result = GenericUtilities.GetNpmDistTag(mesVersion); + + result.Should().Be(expectedDistTag); + } + + [Theory] + [InlineData("12.0.0", "12.0.0", false)] + [InlineData("12.0.0-beta.1", "12.0.0-beta.2", true)] + [InlineData("12.0.0-beta.2", "12.0.0", true)] + [InlineData("12.0.0", "12.0.0-beta.1", false)] + public void NuGetVersion_Comparison_RespectsSemVerOrdering(string left, string right, bool leftIsLessThanRight) + { + var leftVersion = new NuGetVersion(left); + var rightVersion = new NuGetVersion(right); + + (leftVersion < rightVersion).Should().Be(leftIsLessThanRight); + } } } diff --git a/tests/Specs/Init.cs b/tests/Specs/Init.cs index 8d76b127c..5f8a39c3d 100644 --- a/tests/Specs/Init.cs +++ b/tests/Specs/Init.cs @@ -14,6 +14,7 @@ using Xunit; using Newtonsoft.Json; using Assert = tests.AssertWithMessage; +using NuGet.Versioning; namespace tests.Specs { @@ -82,9 +83,9 @@ public void Init_(string baseVersionStr, string dotnetSDKVersion) var extractFileName = new Func(s => s.Split(Path.DirectorySeparatorChar).LastOrDefault()); // For v10 and above, the devcontainer should be created - var baseVersion = new Version(baseVersionStr); + var baseVersion = new NuGetVersion(baseVersionStr); - if (baseVersion >= new Version(10, 0)) + if (baseVersion >= new NuGetVersion(10, 0, 0)) { var devContainerFile = File.ReadAllText(Path.Join(tmp, ".devcontainer/devcontainer.json")); diff --git a/tests/Specs/MESVersionValidation.cs b/tests/Specs/MESVersionValidation.cs index f991b1b15..6184fa89f 100644 --- a/tests/Specs/MESVersionValidation.cs +++ b/tests/Specs/MESVersionValidation.cs @@ -1,9 +1,10 @@ using System; -using System.Collections.Generic; using System.IO.Abstractions.TestingHelpers; using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; +using NuGet.Versioning; using Xunit; namespace tests.Specs; @@ -166,7 +167,7 @@ public void IsVersionCompatible_NoExecutionContext_ShouldReturnFalse() [Theory] [InlineData("12.0.0-alpha.1", "11.0.0", true)] // pre-release, current version higher - [InlineData("12.0.0-alpha.1", "12.0.0", true)] // pre-release, exact numeric match + [InlineData("12.0.0-alpha.1", "12.0.0", false)] // pre-release is still below the release build [InlineData("12.0.0-alpha.1", "13.0.0", false)] // pre-release, current version lower public void ProjectConfig_WithPreReleaseMESVersion_LoadsAndComparesCorrectly(string currentVersion, string minimumVersion, bool expectedResult) { @@ -180,7 +181,51 @@ public void ProjectConfig_WithPreReleaseMESVersion_LoadsAndComparesCorrectly(str // Assert result.Should().Be(expectedResult); - ExecutionContext.Instance.ProjectConfig.MESVersion.Should().Be(new Version(12, 0, 0)); + ExecutionContext.Instance.ProjectConfig.MESVersion.ToString().Should().Be(currentVersion); + } + + [Fact] + public void IsVersionCompatible_PreReleaseCurrentVersion_BelowReleaseMinimum_ShouldReturnFalse() + { + // SemVer rule: 12.0.0-alpha.1 is lower than 12.0.0. + SetupExecutionContext("12.0.0-alpha.1"); + var service = new MESVersionValidationService(); + + service.IsVersionCompatible("12.0.0").Should().BeFalse(); + } + + [Fact] + public void ValidateMinimumVersion_PrereleaseMinimumVersion_ShouldAcceptSemVerMinimum() + { + // The API should accept prerelease minimums like 12.0.0-beta.1 instead of rejecting them as invalid. + SetupExecutionContext("12.0.0-beta.2"); + var service = new MESVersionValidationService(); + + service.Invoking(x => x.ValidateMinimumVersion("12.0.0-beta.1")) + .Should().NotThrow(); + } + + [Fact] + public void IsVersionCompatible_PrereleaseMinimumVersion_ShouldCompareUsingSemVerRules() + { + // A minimum of 12.0.0-beta.1 should be considered lower than the current 12.0.0-beta.2 version. + SetupExecutionContext("12.0.0-beta.2"); + var service = new MESVersionValidationService(); + + service.IsVersionCompatible("12.0.0-beta.1").Should().BeTrue(); + } + + [Theory] + [InlineData("12.0.0")] + [InlineData("12.0.0-beta.1")] + public void ProjectConfig_WithReleaseOrPrereleaseMESVersion_ShouldRoundTripWithoutLosingSemVer(string mesVersion) + { + // The config must preserve the original SemVer identity, including prerelease labels, + // while still exposing the normalized form used for comparisons. + SetupExecutionContext(mesVersion); + + ExecutionContext.Instance.ProjectConfig.MESVersion.ToString().Should().Be(mesVersion); + ExecutionContext.Instance.ProjectConfig.MESVersion.ToNormalizedString().Should().Be(NuGetVersion.Parse(mesVersion).ToNormalizedString()); } private void SetupExecutionContext(string mesVersion) diff --git a/tests/Specs/New.cs b/tests/Specs/New.cs index 729826b5a..39fd6f9a0 100644 --- a/tests/Specs/New.cs +++ b/tests/Specs/New.cs @@ -29,6 +29,7 @@ using Cmf.CLI.Commands.New.IoT; using TestingConsole = Spectre.Console.Testing; using Xunit.Sdk; +using NuGet.Versioning; namespace tests.Specs { @@ -304,7 +305,7 @@ private void UI_internal(string scaffoldingDir, BaseLayer layer, string mesVersi Assert.True(File.Exists($"Cmf.Custom.HTML/src/index.html"), "Index file is missing or has wrong name"); Assert.True(File.ReadAllText("Cmf.Custom.HTML/src/index.html").Contains("MES"), "Index content is not expected"); Assert.True(File.ReadAllText("Cmf.Custom.HTML/src/index.html").Contains(""), "Index base path was not changed correctly"); - if(new Version(mesVersion) < new Version(11,0,0)) + if(new NuGetVersion(mesVersion) < new NuGetVersion(11,0,0)) { if (layer == BaseLayer.Core) { @@ -374,7 +375,7 @@ public void IoT(string mesVersion, bool htmlPackageLocationFullPath = false, boo string packageFolderData = packageIdData; // Before v10.2.7, all packages were Angular packages, even if the flag was not passed explicitly - bool isAngularPackage = isAngularPackageFlag || Version.Parse(mesVersion) < new Version(10, 2, 7); + bool isAngularPackage = isAngularPackageFlag || NuGetVersion.Parse(mesVersion) < new NuGetVersion(10, 2, 7); CopyNewFixture(dir, mesVersion: mesVersion); if (isAngularPackage) @@ -451,7 +452,7 @@ public void IoTDriverDefaultValues(string mesVersion) string packageFolderPackages = "Cmf.Custom.IoT.Packages"; - bool isGreaterOrEqualThan1120 = Version.Parse(mesVersion) >= new Version(11, 2, 0); + bool isGreaterOrEqualThan1120 = NuGetVersion.Parse(mesVersion) >= new NuGetVersion(11, 2, 0); var cur = Directory.GetCurrentDirectory(); try diff --git a/tests/Specs/NodeVersionUtilities.cs b/tests/Specs/NodeVersionUtilities.cs index 494b43975..ff98e1fc7 100644 --- a/tests/Specs/NodeVersionUtilities.cs +++ b/tests/Specs/NodeVersionUtilities.cs @@ -1,7 +1,6 @@ using Cmf.CLI.Utilities; -using Cmf.CLI.Core; -using System; using Xunit; +using NuGet.Versioning; namespace tests.Specs { @@ -28,7 +27,7 @@ public void GetInstalledNodeVersion_ReturnsVersion_WhenNodeIsInstalled() public void ValidateNodeVersion_ThrowsException_WhenNodeNotInstalled(string mesVersionString, string requiredNodeVersion) { // Arrange - var mesVersion = new Version(mesVersionString); + var mesVersion = new NuGetVersion(mesVersionString); // Act & Assert // This test assumes Node.js is either not installed or the version doesn't match @@ -62,20 +61,20 @@ public void ValidateNodeVersion_DoesNotThrow_WhenNodeVersionMatches() if (installedVersion != null) { // Determine the MES version based on the installed Node.js version - Version mesVersion; + NuGetVersion mesVersion; string requiredNodeVersion = installedVersion.Major.ToString(); switch (installedVersion.Major) { case 12: - mesVersion = new Version("8.0.0"); + mesVersion = new NuGetVersion(8, 0, 0); break; case 18: - mesVersion = new Version("10.0.0"); + mesVersion = new NuGetVersion(10, 0, 0); break; case 20: default: - mesVersion = new Version("11.0.0"); + mesVersion = new NuGetVersion(11, 0, 0); break; } From 005535817bc012a219100a58aca53b1b328c2c46 Mon Sep 17 00:00:00 2001 From: pedroapap Date: Wed, 19 Aug 2026 10:15:08 +0000 Subject: [PATCH 4/7] test: add regex transformation tests for version handling in template --- .../init/.template.config/template.json | 6 ++-- tests/Specs/Init.cs | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/cmf-cli/resources/template_feed/init/.template.config/template.json b/cmf-cli/resources/template_feed/init/.template.config/template.json index af8ebda82..f6b0cd991 100644 --- a/cmf-cli/resources/template_feed/init/.template.config/template.json +++ b/cmf-cli/resources/template_feed/init/.template.config/template.json @@ -608,17 +608,17 @@ "forms": { "versionToMajor": { "identifier": "replace", - "pattern": "(\\d+)\\.(\\d+)\\.(\\d+)", + "pattern": "^(\\d+)\\.\\d+\\.\\d+\\.?\\d*(?:[-+].*)?$", "replacement": "$1" }, "versionToMajorRange": { "identifier": "replace", - "pattern": "(\\d+)\\.(\\d+)\\.(\\d+).*", + "pattern": "^(\\d+)\\.\\d+\\.\\d+\\.?\\d*(?:[-+].*)?$", "replacement": "$1.x.x" }, "versionToFeature": { "identifier": "replace", - "pattern": "(\\d+)\\.(\\d+)\\.(\\d+)", + "pattern": "^(\\d+)\\.(\\d+)\\.\\d+\\.?\\d*(?:[-+].*)?$", "replacement": "$1.$2.x" }, "stripQuotes": { diff --git a/tests/Specs/Init.cs b/tests/Specs/Init.cs index 5f8a39c3d..e109b0659 100644 --- a/tests/Specs/Init.cs +++ b/tests/Specs/Init.cs @@ -13,6 +13,8 @@ using Cmf.CLI.Services; using Xunit; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Text.RegularExpressions; using Assert = tests.AssertWithMessage; using NuGet.Versioning; @@ -38,6 +40,25 @@ public Init() parseResult.Invoke(console); } + [Theory] + [InlineData("10.2.0", "10", "10.x.x", "10.2.x")] + [InlineData("11.1.5", "11", "11.x.x", "11.1.x")] + [InlineData("12.0.0-beta.2", "12", "12.x.x", "12.0.x")] + public void Template_VersionTransforms_MatchTheConfiguredRegexes(string version, string expectedMajor, string expectedMajorRange, string expectedFeature) + { + var templatePath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "cmf-cli", "resources", "template_feed", "init", ".template.config", "template.json")); + var template = JObject.Parse(File.ReadAllText(templatePath)); + var forms = (JObject)template["forms"]!; + + var major = Transform(forms["versionToMajor"]!, version); + var majorRange = Transform(forms["versionToMajorRange"]!, version); + var feature = Transform(forms["versionToFeature"]!, version); + + major.Should().Be(expectedMajor, $"versionToMajor should reduce '{version}' to its major segment"); + majorRange.Should().Be(expectedMajorRange, $"versionToMajorRange should produce a major-only range from '{version}'"); + feature.Should().Be(expectedFeature, $"versionToFeature should produce the feature segment from '{version}'"); + } + [Theory] [InlineData("10.2.0", DependencyVersionService.NET6SDK)] [InlineData("11.0.0", DependencyVersionService.NET8SDK)] @@ -134,6 +155,13 @@ public void Init_(string baseVersionStr, string dotnetSDKVersion) } } + private static string Transform(JToken form, string value) + { + var pattern = (string)form["pattern"]!; + var replacement = (string)form["replacement"]!; + return Regex.Replace(value, pattern, replacement); + } + [Fact] public void Init_Fail_InvalidProjectName_WithDot() { From 3c3019af1202e790770c25240493335c35deb89e Mon Sep 17 00:00:00 2001 From: cmeira Date: Wed, 19 Aug 2026 16:24:34 +0000 Subject: [PATCH 5/7] feat: add dependency version for v12 (dotnet, node, ng22) --- cmf-cli/Commands/new/HTMLCommand.cs | 10 ++++- cmf-cli/services/DependencyVersionService.cs | 41 ++++++++++++++++---- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/cmf-cli/Commands/new/HTMLCommand.cs b/cmf-cli/Commands/new/HTMLCommand.cs index a068d311a..be6ac9895 100644 --- a/cmf-cli/Commands/new/HTMLCommand.cs +++ b/cmf-cli/Commands/new/HTMLCommand.cs @@ -170,7 +170,15 @@ public void ExecuteV10(IDirectoryInfo workingDir, string version) rootPkgJson.devDependencies["cross-env"] = "^10.1.0"; } - rootPkgJson.scripts["serve"] = "cross-env NODE_OPTIONS=--max-old-space-size=8192 npm run start -- --host 0.0.0.0 --disable-host-check --port 7000"; + + if (mesVersion.Major < 12) + { + rootPkgJson.scripts["serve"] = "cross-env NODE_OPTIONS=--max-old-space-size=8192 npm run start -- --host 0.0.0.0 --disable-host-check --port 7000"; + } + else + { + rootPkgJson.scripts["serve"] = "cross-env NODE_OPTIONS=--max-old-space-size=8192 npm run start -- --host 0.0.0.0 --port 7000"; + } if (ExecutionContext.Instance.ProjectConfig.RepositoryType == RepositoryType.App) { diff --git a/cmf-cli/services/DependencyVersionService.cs b/cmf-cli/services/DependencyVersionService.cs index c11c70409..d8f64281a 100644 --- a/cmf-cli/services/DependencyVersionService.cs +++ b/cmf-cli/services/DependencyVersionService.cs @@ -60,29 +60,54 @@ public class DependencyVersionService : IDependencyVersionService { public const string NET6TARGETFRAMEWORK = "net6.0"; public const string NET8TARGETFRAMEWORK = "net8.0"; + public const string NET10TARGETFRAMEWORK = "net10.0"; public const string NET6SDK = "6.0.201"; // avoid >2xx as it requires HTTPS for nuget pulls public const string NET8SDK = "8.0.301"; + public const string NET10SDK = "10.0.301"; + public const string NODE24 = "24"; public const string NODE20 = "20"; public const string NODE18 = "18"; public const string NG15 = "15.2.1"; public const string NG17 = "17.2.1"; public const string NG21 = "21.1.0"; + public const string NG22 = "22.1.2"; public const string NG15_ZONE = "0.12.0"; public const string NG17_ZONE = "0.14.3"; public const string NG21_ZONE = "0.16.0"; + public const string NG22_ZONE = "0.16.2"; public const string NG15_TSESLINT = "5.44.0"; public const string NG17_TSESLINT = "6.10.0"; public const string NG21_TSESLINT = "8.52.0"; + public const string NG22_TSESLINT = "8.67.0"; public const string NG15_ESLINT = "8.28.0"; public const string NG17_ESLINT = "8.53.0"; public const string NG21_ESLINT = "9.39.2"; + public const string NG22_ESLINT = "10.8.1"; public const string NG15_TS = "4.8.4"; public const string NG17_TS = "5.3.3"; public const string NG21_TS = "5.9.3"; + public const string NG22_TS = "6.0.3"; - public string DotNetSdk(NuGetVersion productVersion) => productVersion.Major >= 11 ? NET8SDK : NET6SDK; - public string DotNetTargetFramework(NuGetVersion productVersion) => productVersion.Major >= 11 ? NET8TARGETFRAMEWORK : NET6TARGETFRAMEWORK; - public string Node(SemanticVersion productVersion) => productVersion.Major >= 11 ? NODE20 : NODE18; + public string DotNetSdk(NuGetVersion productVersion) => + productVersion.Major >= 12 + ? NET10SDK + : productVersion.Major >= 11 + ? NET8SDK + : NET6SDK; + + public string DotNetTargetFramework(NuGetVersion productVersion) => + productVersion.Major >= 12 + ? NET10TARGETFRAMEWORK + : productVersion.Major >= 11 + ? NET8TARGETFRAMEWORK + : NET6TARGETFRAMEWORK; + + public string Node(SemanticVersion productVersion) => + productVersion.Major >= 12 + ? NODE24 + : productVersion.Major >= 11 + ? NODE20 + : NODE18; public AngularDeps Angular(SemanticVersion productVersion) => productVersion.Major switch @@ -105,11 +130,11 @@ public AngularDeps Angular(SemanticVersion productVersion) => }, 12 => new AngularDeps() { - CLI = Version.Parse(NG21), - Zone = NG21_ZONE, - Typescript = NG21_TS, - ESLint = NG21_ESLINT, - TSESLint = NG21_TSESLINT + CLI = Version.Parse(NG22), + Zone = NG22_ZONE, + Typescript = NG22_TS, + ESLint = NG22_ESLINT, + TSESLint = NG22_TSESLINT }, _ => throw new NotSupportedException($"No Angular dependencies defined for MES version {productVersion}") }; From 5a75a86b21231a7582d9ccd64ba9b8a0605e511d Mon Sep 17 00:00:00 2001 From: pedroapap Date: Thu, 20 Aug 2026 16:45:34 +0000 Subject: [PATCH 6/7] refactor: remove outdated NPX version handling logic from NPXCommand --- cmf-cli/Builders/NPXCommand.cs | 12 ------------ cmf-cli/Commands/new/IoTCommand.cs | 1 - 2 files changed, 13 deletions(-) diff --git a/cmf-cli/Builders/NPXCommand.cs b/cmf-cli/Builders/NPXCommand.cs index 3faeedf1d..8e7640b61 100644 --- a/cmf-cli/Builders/NPXCommand.cs +++ b/cmf-cli/Builders/NPXCommand.cs @@ -39,18 +39,6 @@ public override ProcessBuildStep[] GetSteps() // Bypass any interactive confirmation prompts that might appear "-y" }; - - // The initial versions of NPX (<= 6) that are used by MES v8 and v9 must be called like this: - // npx -y true - // Later versions of NPX (>= 7) that are used by MES v10 and greater, must be called only like this: - // npx -y - // So we get the MES version of the project, and we assume that the user must be running the supported - // node version for that MES project - var mesVersion = ExecutionContext.Instance?.ProjectConfig?.MESVersion; - if (mesVersion != null && mesVersion < new NuGetVersion(10, 0, 0)) - { - args.Add("true"); - } args.Add(this.Command); if (this.Args != null) diff --git a/cmf-cli/Commands/new/IoTCommand.cs b/cmf-cli/Commands/new/IoTCommand.cs index 2546445da..8922d24f9 100644 --- a/cmf-cli/Commands/new/IoTCommand.cs +++ b/cmf-cli/Commands/new/IoTCommand.cs @@ -160,7 +160,6 @@ public void ExecuteV10ATL(IDirectoryInfo workingDir, string version) } var iotCustomPackageWorkDir = iotCustomPackage.GetFileInfo().Directory; - var iotCustomPackageName = base.GeneratePackageName(iotCustomPackageWorkDir)!.Value.Item1; var mesVersion = ExecutionContext.Instance.ProjectConfig.MESVersion; From ff0209d21b5a49bcd78a06761404bf5a3e64aff5 Mon Sep 17 00:00:00 2001 From: pedroapap Date: Fri, 21 Aug 2026 14:33:23 +0000 Subject: [PATCH 7/7] chore: bump version to 6.0.0-3 --- CHANGES.md | 19 +++++++++++++++++++ cmf-cli/cmf.csproj | 2 +- core/core.csproj | 2 +- npm/package-lock.json | 4 ++-- npm/package.json | 2 +- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cc4c1dd64..88afa89aa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. +## [6.0.0-3](https://github.com/criticalmanufacturing/cli/compare/6.0.0-2...6.0.0-3) (2026-08-21) + + +### Features + +* add dependency version for v12 (dotnet, node, ng22) ([3c3019a](https://github.com/criticalmanufacturing/cli/commit/3c3019af1202e790770c25240493335c35deb89e)) +* add support for pre-releases ([2d89954](https://github.com/criticalmanufacturing/cli/commit/2d89954ae20f980209508aea1a702f3f634dca49)) + + +### Bug Fixes + +* update SharpCompress package version and improve TAR.GZ handling in CmfPackageController ([c3f654b](https://github.com/criticalmanufacturing/cli/commit/c3f654bb62b8b4032f924e17c8332f30281b0a8a)) + + +### Under the hood + +* remove outdated NPX version handling logic from NPXCommand ([5a75a86](https://github.com/criticalmanufacturing/cli/commit/5a75a86b21231a7582d9ccd64ba9b8a0605e511d)) +* replace Version with MesVersion for improved semantic version handling across the CLI ([7893e74](https://github.com/criticalmanufacturing/cli/commit/7893e749d07c83a60498c513ce9bf91aa8d3aabf)) + ## [6.0.0-2](https://github.com/criticalmanufacturing/cli/compare/6.0.0-1...6.0.0-2) (2026-07-22) diff --git a/cmf-cli/cmf.csproj b/cmf-cli/cmf.csproj index 51c4bb68b..a32392f5b 100644 --- a/cmf-cli/cmf.csproj +++ b/cmf-cli/cmf.csproj @@ -15,7 +15,7 @@ Name Namespace Namespaces - 6.0.0-2 + 6.0.0-3 diff --git a/core/core.csproj b/core/core.csproj index 97ebcdc3d..e6ff0d219 100644 --- a/core/core.csproj +++ b/core/core.csproj @@ -4,7 +4,7 @@ net10.0 CriticalManufacturing.CLI.Core Cmf.CLI.Core - 6.0.0-2 + 6.0.0-3 CriticalManufacturing CriticalManufacturing BSD-3-Clause diff --git a/npm/package-lock.json b/npm/package-lock.json index c36e4fa2d..93bf2d647 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -1,12 +1,12 @@ { "name": "@criticalmanufacturing/cli", - "version": "6.0.0-2", + "version": "6.0.0-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@criticalmanufacturing/cli", - "version": "6.0.0-2", + "version": "6.0.0-3", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/npm/package.json b/npm/package.json index 9e07a082b..2bb7b7789 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@criticalmanufacturing/cli", - "version": "6.0.0-2", + "version": "6.0.0-3", "description": "Critical Manufacturing command line client", "bin": { "cmf": "run.js"