diff --git a/docs/commands-implementation-details.md b/docs/commands-implementation-details.md index 2f8da1f..dcc0230 100644 --- a/docs/commands-implementation-details.md +++ b/docs/commands-implementation-details.md @@ -53,13 +53,36 @@ If there are any existing changesets the command generates changelogs for every changeset publish ``` -Creates nuget packages of affected projects and publishes them to the predefined package source. The command: +Creates NuGet packages of affected projects and publishes them to the predefined package source. -1. Gets all modified `.csproj` files from the predefined working directory using `git diff --name-only`. -1. Creates nuget package using `nuget pack` for every csproj file. -1. Pushes the created nuget packages to the predefined nuget source using `nuget push`. +### How it works (current .NET implementation) -The package source can be configured via `config.json` file. +Currently, the `publish` command assumes that the changes made by the `version` command have **already been committed**. The intended flow is: + +1. Run `changeset version` to bump versions, update changelogs, and delete processed changesets. +2. Commit the changes produced by the `version` command. +3. Run `changeset publish` to create and push NuGet packages based on the committed changes. + +The `publish` command does following: + +1. Gets all modified `.csproj` files from the predefined source directory by comparing the last two commits `git diff --name-only HEAD~1 HEAD {sourcePath}` +2. For each changed `.csproj` file, creates a NuGet package using `dotnet pack`. +3. Pushes the created NuGet packages to the predefined NuGet source using `dotnet nuget push`. + +The package source can be configured via the `.changeset/config.json` file (see `docs/config-file-options.md`). + +This approach relies on the fact that the `version` command only performs three types of changes: + +- Deleting processed changeset files from the `.changeset` folder +- Modifying or creating `CHANGELOG.md` files +- Bumping versions in `.csproj` files + +By looking at the diff between `HEAD~1` and `HEAD`, `publish` can safely identify which projects had their versions bumped and therefore need to be published. + +### Comparison with original Node.js changesets implementation + +The original `@changesets/cli` implementation for Node.js works differently. Instead of relying on a Git diff, +it checks whether a package with the **current version** from `package.json` already exists in the package registry; if it does not, the package is published. ## `status` diff --git a/src/SolarWinds.Changesets/CHANGELOG.md b/src/SolarWinds.Changesets/CHANGELOG.md index ef9a9b2..eb4b04a 100644 --- a/src/SolarWinds.Changesets/CHANGELOG.md +++ b/src/SolarWinds.Changesets/CHANGELOG.md @@ -1,5 +1,14 @@ # SolarWinds.Changesets +## 0.1.4 + +**Patch Changes**: + +- [[#19](https://github.com/solarwinds/net-changesets/issues/19)] Fix publish command ([PR #14](https://github.com/solarwinds/net-changesets/pull/14)) + - The `publish` command now compares the last two commits (HEAD~1 and HEAD) instead of comparing the last commit with the working tree to determine which projects were published. + This change is required because publishing must be done after version bumps and generated files are committed. Since there is no other reliable way to detect whether the version command was run before publish, + the comparison is based on committed changes in `.csproj` files. This is still error-prone if users make manual changes to `.csproj` files and run `publish` command, but it is a reasonable compromise for now. + ## 0.1.3 **Patch Changes**: diff --git a/src/SolarWinds.Changesets/Commands/Publish/PublishChangesetCommand.cs b/src/SolarWinds.Changesets/Commands/Publish/PublishChangesetCommand.cs index b0b2b53..9e1eaa6 100644 --- a/src/SolarWinds.Changesets/Commands/Publish/PublishChangesetCommand.cs +++ b/src/SolarWinds.Changesets/Commands/Publish/PublishChangesetCommand.cs @@ -1,4 +1,4 @@ -using SolarWinds.Changesets.Services; +using SolarWinds.Changesets.Commands.Publish.Services; using SolarWinds.Changesets.Shared; using Spectre.Console; using Spectre.Console.Cli; @@ -40,7 +40,7 @@ public override async Task ExecuteCommandAsync(CommandContext context) _console.WriteLine("Determining projects to publish ..."); _console.WriteLine(); - ProcessOutput processOutput = await _gitService.GetDiff(ChangesetConfig.SourcePath); + ProcessOutput processOutput = await _gitService.GetDiff(Constants.WorkingDirectoryFullPath, ChangesetConfig.SourcePath); List changedCsharpProjectNames = processOutput.Output.Where(x => x.Contains(".csproj", StringComparison.Ordinal)).ToList(); if (changedCsharpProjectNames.Count == 0) diff --git a/src/SolarWinds.Changesets/Services/DotnetService.cs b/src/SolarWinds.Changesets/Commands/Publish/Services/DotnetService.cs similarity index 95% rename from src/SolarWinds.Changesets/Services/DotnetService.cs rename to src/SolarWinds.Changesets/Commands/Publish/Services/DotnetService.cs index af63dae..3a318e2 100644 --- a/src/SolarWinds.Changesets/Services/DotnetService.cs +++ b/src/SolarWinds.Changesets/Commands/Publish/Services/DotnetService.cs @@ -1,6 +1,6 @@ using SolarWinds.Changesets.Shared; -namespace SolarWinds.Changesets.Services; +namespace SolarWinds.Changesets.Commands.Publish.Services; /// public sealed class DotnetService : IDotnetService diff --git a/src/SolarWinds.Changesets/Services/GitService.cs b/src/SolarWinds.Changesets/Commands/Publish/Services/GitService.cs similarity index 74% rename from src/SolarWinds.Changesets/Services/GitService.cs rename to src/SolarWinds.Changesets/Commands/Publish/Services/GitService.cs index d0fcbd6..afb837e 100644 --- a/src/SolarWinds.Changesets/Services/GitService.cs +++ b/src/SolarWinds.Changesets/Commands/Publish/Services/GitService.cs @@ -1,6 +1,6 @@ using SolarWinds.Changesets.Shared; -namespace SolarWinds.Changesets.Services; +namespace SolarWinds.Changesets.Commands.Publish.Services; /// public sealed class GitService : IGitService @@ -19,12 +19,12 @@ public GitService(IProcessExecutor processExecutor) } /// - public async Task GetDiff(string sourcePath) + public async Task GetDiff(string workingDirectory, string sourcePath) { return await _processExecutor.Execute( "git", - $"diff --name-only {sourcePath}", - Constants.WorkingDirectoryFullPath + $"diff --name-only HEAD~1 HEAD {sourcePath}", + workingDirectory ); } } diff --git a/src/SolarWinds.Changesets/Services/IDotnetService.cs b/src/SolarWinds.Changesets/Commands/Publish/Services/IDotnetService.cs similarity index 94% rename from src/SolarWinds.Changesets/Services/IDotnetService.cs rename to src/SolarWinds.Changesets/Commands/Publish/Services/IDotnetService.cs index 2fbc473..4d5e495 100644 --- a/src/SolarWinds.Changesets/Services/IDotnetService.cs +++ b/src/SolarWinds.Changesets/Commands/Publish/Services/IDotnetService.cs @@ -1,6 +1,6 @@ using SolarWinds.Changesets.Shared; -namespace SolarWinds.Changesets.Services; +namespace SolarWinds.Changesets.Commands.Publish.Services; /// /// Provides functionality to interact with the .NET CLI for operations such as packing and publishing NuGet packages. diff --git a/src/SolarWinds.Changesets/Services/IGitService.cs b/src/SolarWinds.Changesets/Commands/Publish/Services/IGitService.cs similarity index 68% rename from src/SolarWinds.Changesets/Services/IGitService.cs rename to src/SolarWinds.Changesets/Commands/Publish/Services/IGitService.cs index 7d13ebd..1b3e79a 100644 --- a/src/SolarWinds.Changesets/Services/IGitService.cs +++ b/src/SolarWinds.Changesets/Commands/Publish/Services/IGitService.cs @@ -1,6 +1,6 @@ using SolarWinds.Changesets.Shared; -namespace SolarWinds.Changesets.Services; +namespace SolarWinds.Changesets.Commands.Publish.Services; /// /// Provides Git-related services, such as retrieving file differences. @@ -10,7 +10,8 @@ public interface IGitService /// /// Retrieves a list of file names that have changed in the specified source path. /// + /// Working directory. /// The path to check for file differences. /// Process output and exit code. - Task GetDiff(string sourcePath); + Task GetDiff(string workingDirectory, string sourcePath); } diff --git a/src/SolarWinds.Changesets/Commands/Version/Helpers/CsProjectsRepository.cs b/src/SolarWinds.Changesets/Commands/Version/Helpers/CsProjectsRepository.cs index e7c5f2b..04a5506 100644 --- a/src/SolarWinds.Changesets/Commands/Version/Helpers/CsProjectsRepository.cs +++ b/src/SolarWinds.Changesets/Commands/Version/Helpers/CsProjectsRepository.cs @@ -29,15 +29,9 @@ public CsProject[] GetCsProjects(ChangesetConfig changesetConfig) XDocument csprojXDocument = XDocument.Load(csprojFilePath); string projectName = Path.GetFileNameWithoutExtension(csprojFilePath); - XElement? projectVersionXElement = csprojXDocument.Descendants().SingleOrDefault(d => d.Name.LocalName == "Version"); - Semver? projectVersion = projectVersionXElement is not null - ? Semver.FromString(projectVersionXElement.Value) - : new(0, 0, 0); - - if (projectVersion is null) + Semver projectVersion = GetProjectVersion(csprojXDocument, projectName); + if (projectVersion == Semver.Empty) { - _console.MarkupLine($"[yellow]Version {projectVersionXElement?.Value} could not be parsed " + - $"for project {projectName}. This may have unexpected consequences on the 'version' command![/]"); continue; } @@ -49,7 +43,6 @@ public CsProject[] GetCsProjects(ChangesetConfig changesetConfig) .ToArray(); csProjects.Add(new(projectName, projectVersion, projectReferences, csprojFilePath)); - } return csProjects.ToArray(); @@ -85,6 +78,21 @@ private async Task UpdateCsProjectVersionAsync(string moduleCsProjFilePath, stri await File.WriteAllTextAsync(moduleCsProjFilePath, newVersionContent); } + private Semver GetProjectVersion(XDocument csprojXDocument, string projectName) + { + XElement? projectVersionXElement = csprojXDocument.Descendants().SingleOrDefault(d => d.Name.LocalName == "Version"); + + if (projectVersionXElement is not null && Semver.TryParse(projectVersionXElement.Value, out Semver? parsedVersion)) + { + return parsedVersion; + } + + _console.MarkupLine($"[yellow]Version {projectVersionXElement?.Value} could not be parsed " + + $"for project {projectName}. This may have unexpected consequences on the 'version' command![/]"); + + return Semver.Empty; + } + [GeneratedRegex(@"()(.*?)()")] private static partial Regex VersionRegex(); } diff --git a/src/SolarWinds.Changesets/Program.cs b/src/SolarWinds.Changesets/Program.cs index c0143d6..eda75c1 100644 --- a/src/SolarWinds.Changesets/Program.cs +++ b/src/SolarWinds.Changesets/Program.cs @@ -2,11 +2,11 @@ using SolarWinds.Changesets.Commands.Add; using SolarWinds.Changesets.Commands.Init; using SolarWinds.Changesets.Commands.Publish; +using SolarWinds.Changesets.Commands.Publish.Services; using SolarWinds.Changesets.Commands.Status; using SolarWinds.Changesets.Commands.Version; using SolarWinds.Changesets.Commands.Version.Helpers; using SolarWinds.Changesets.Infrastructure; -using SolarWinds.Changesets.Services; using SolarWinds.Changesets.Shared; using Spectre.Console.Cli; diff --git a/src/SolarWinds.Changesets/Shared/Semver.cs b/src/SolarWinds.Changesets/Shared/Semver.cs index cfffb38..7169df6 100644 --- a/src/SolarWinds.Changesets/Shared/Semver.cs +++ b/src/SolarWinds.Changesets/Shared/Semver.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using SystemVersion = System.Version; namespace SolarWinds.Changesets.Shared; @@ -45,13 +46,27 @@ public override string ToString() return $"{Major}.{Minor}.{Patch}"; } - public static Semver? FromString(string version) + /// + /// Attempts to parse a version string into a instance. + /// + /// The version string to parse (e.g., "1.2.3"). + /// When this method returns, contains the parsed if parsing succeeded, or null if parsing failed. + /// True if the version string was successfully parsed; otherwise, false. + public static bool TryParse(string version, [NotNullWhen(true)] out Semver? semver) { - if (!SystemVersion.TryParse(version, out SystemVersion? result)) + if (SystemVersion.TryParse(version, out SystemVersion? result)) { - return null; + semver = new(result.Major, result.Minor, result.Build); + return true; } - return new(result.Major, result.Minor, result.Build); + semver = null; + return false; } + + /// + /// Gets an empty instance with version 0.0.0. + /// + /// A instance representing version 0.0.0. + public static Semver Empty { get; } = new(0, 0, 0); } diff --git a/src/SolarWinds.Changesets/SolarWinds.Changesets.csproj b/src/SolarWinds.Changesets/SolarWinds.Changesets.csproj index 68b08ae..1a47387 100644 --- a/src/SolarWinds.Changesets/SolarWinds.Changesets.csproj +++ b/src/SolarWinds.Changesets/SolarWinds.Changesets.csproj @@ -17,7 +17,7 @@ $(RepositoryUrl)/releases true Exe - 0.1.3 + 0.1.4 embedded true diff --git a/tests/SolarWinds.Changesets.Tests/Publish/GitServiceTests.cs b/tests/SolarWinds.Changesets.Tests/Publish/GitServiceTests.cs new file mode 100644 index 0000000..267ecfc --- /dev/null +++ b/tests/SolarWinds.Changesets.Tests/Publish/GitServiceTests.cs @@ -0,0 +1,184 @@ +using AwesomeAssertions; +using SolarWinds.Changesets.Commands.Publish.Services; +using SolarWinds.Changesets.Shared; + +namespace SolarWinds.Changesets.Tests.Publish; + +[TestFixture] +internal sealed class GitServiceTests +{ + private string _tempRepositoryAbsolutePath = string.Empty; + private ProcessExecutor _processExecutor = null!; + private GitService _gitService = null!; + + [SetUp] + public async Task SetUp() + { + string testDirectory = TestContext.CurrentContext.TestDirectory; + _tempRepositoryAbsolutePath = Path.Join(testDirectory, $"git-service-test-{Guid.NewGuid()}"); + Directory.CreateDirectory(_tempRepositoryAbsolutePath); + + _processExecutor = new ProcessExecutor(); + await ExecuteGitCommand("init"); + await ExecuteGitCommand("config --local user.email \"test@example.com\""); + await ExecuteGitCommand("config --local user.name \"Test User\""); + await ExecuteGitCommand("config --local commit.gpgSign false"); + + _gitService = new GitService(_processExecutor); + } + + [TearDown] + public void TearDown() + { + if (Directory.Exists(_tempRepositoryAbsolutePath)) + { + DeleteDirectoryWithRetry(_tempRepositoryAbsolutePath); + } + } + + [Test] + public async Task GetDiff_WithModifiedCsprojFile_ReturnsModifiedProject() + { + // Arrange + string projectName = "TestProject"; + string csprojAbsolutePath = Path.Join(_tempRepositoryAbsolutePath, $"{projectName}.csproj"); + + // Create initial .csproj file + await File.WriteAllTextAsync(csprojAbsolutePath, """ + + + net8.0 + 1.0.0 + + + """ + ); + + await ExecuteGitCommand("add ."); + await ExecuteGitCommand("commit -m \"Initial commit\""); + + // Modify .csproj file + await File.WriteAllTextAsync(csprojAbsolutePath, """ + + + net8.0 + 1.1.0 + + + """ + ); + + await ExecuteGitCommand("add ."); + await ExecuteGitCommand("commit -m \"Update version\""); + + // Act + ProcessOutput result = await _gitService.GetDiff(_tempRepositoryAbsolutePath, string.Empty); + + // Assert + result.ExitCode.Should().Be(0); + result.Output.Should().HaveCount(1); + result.Output.First().Should().EndWith($"{projectName}.csproj"); + } + + [Test] + public async Task GetDiff_WithNonCsprojChanges_ReturnsEmpty() + { + // Arrange + string readmePath = Path.Join(_tempRepositoryAbsolutePath, "README.md"); + + await File.WriteAllTextAsync(readmePath, "# Test"); + await ExecuteGitCommand("add ."); + await ExecuteGitCommand("commit -m \"Initial commit\""); + + await File.WriteAllTextAsync(readmePath, "# Test Updated"); + await ExecuteGitCommand("add ."); + await ExecuteGitCommand("commit -m \"Update README\""); + + // Act + ProcessOutput result = await _gitService.GetDiff(_tempRepositoryAbsolutePath, string.Empty); + + // Assert + result.ExitCode.Should().Be(0); + result.Output.Should().HaveCount(1); + result.Output.First().Should().EndWith($".md"); + } + + [Test] + public async Task GetDiff_WithMultipleCsprojFiles_ReturnsAllModified() + { + // Arrange + string project1Path = Path.Join(_tempRepositoryAbsolutePath, "Project1.csproj"); + string project2Path = Path.Join(_tempRepositoryAbsolutePath, "Project2.csproj"); + + await File.WriteAllTextAsync(project1Path, ""); + await File.WriteAllTextAsync(project2Path, ""); + await ExecuteGitCommand("add ."); + await ExecuteGitCommand("commit -m \"Initial commit\""); + + await File.WriteAllTextAsync(project1Path, ""); + await File.WriteAllTextAsync(project2Path, ""); + await ExecuteGitCommand("add ."); + await ExecuteGitCommand("commit -m \"Update projects\""); + + // Act + ProcessOutput result = await _gitService.GetDiff(_tempRepositoryAbsolutePath, string.Empty); + + // Assert + result.ExitCode.Should().Be(0); + result.Output.Should().HaveCount(2); + result.Output.Any(p => p.EndsWith("Project1.csproj", StringComparison.InvariantCulture)).Should().BeTrue(); + result.Output.Any(p => p.EndsWith("Project2.csproj", StringComparison.InvariantCulture)).Should().BeTrue(); + } + + /// + /// Helper method to execute git commands in the test repository using ProcessExecutor. + /// + private async Task ExecuteGitCommand(string arguments) + { + ProcessOutput result = await _processExecutor.Execute("git", arguments, _tempRepositoryAbsolutePath); + + if (result.ExitCode != 0) + { + throw new InvalidOperationException($"Git command failed: {arguments}. {result.Output}"); + } + } + + /// + /// Deletes a directory with retry logic to handle file locks from Git processes. + /// + private static void DeleteDirectoryWithRetry(string path, int maxRetries = 3) + { + for (int i = 0; i < maxRetries; i++) + { + try + { + Directory.Delete(path, recursive: true); + return; + } + catch (IOException) when (i < maxRetries - 1) + { + // Wait for file handles to be released + Thread.Sleep(100); + } + catch (UnauthorizedAccessException) when (i < maxRetries - 1) + { + // Remove read-only attributes and retry + RemoveReadOnlyAttributes(path); + Thread.Sleep(100); + } + } + } + + /// + /// Removes read-only attributes from all files in a directory recursively. + /// + private static void RemoveReadOnlyAttributes(string path) + { + DirectoryInfo directory = new(path); + + foreach (FileInfo file in directory.GetFiles("*", SearchOption.AllDirectories)) + { + file.Attributes &= ~FileAttributes.ReadOnly; + } + } +} diff --git a/tests/SolarWinds.Changesets.Tests/Publish/PublishChangesetCommandTests.cs b/tests/SolarWinds.Changesets.Tests/Publish/PublishChangesetCommandTests.cs index 43ae625..94722b8 100644 --- a/tests/SolarWinds.Changesets.Tests/Publish/PublishChangesetCommandTests.cs +++ b/tests/SolarWinds.Changesets.Tests/Publish/PublishChangesetCommandTests.cs @@ -2,8 +2,8 @@ using Moq; using SolarWinds.Changesets.Commands.Init; using SolarWinds.Changesets.Commands.Publish; +using SolarWinds.Changesets.Commands.Publish.Services; using SolarWinds.Changesets.Infrastructure; -using SolarWinds.Changesets.Services; using SolarWinds.Changesets.Shared; using Spectre.Console.Testing; @@ -46,7 +46,7 @@ public void TearDown() [Test] public void PublishChangesetCommand_CompletesSuccesfully_HappyPath() { - _gitServiceMock.Setup(x => x.GetDiff(It.IsAny())).Returns(Task.FromResult(new ProcessOutput(["A.csproj", "B.csproj"], 0))); + _gitServiceMock.Setup(x => x.GetDiff(It.IsAny(), It.IsAny())).Returns(Task.FromResult(new ProcessOutput(["A.csproj", "B.csproj"], 0))); _dotnetServiceMock.Setup(x => x.Pack(It.IsAny())).Returns(Task.FromResult(new ProcessOutput(["Project A packed", "Project B packed"], 0))); _dotnetServiceMock.Setup(x => x.Publish(It.IsAny())).Returns(Task.FromResult(new ProcessOutput(["Project A published", "Project B published"], 0))); @@ -58,7 +58,7 @@ public void PublishChangesetCommand_CompletesSuccesfully_HappyPath() [Test] public void PublishChangesetCommand_WhenNoCsprojToPublish_FinishesWithError() { - _gitServiceMock.Setup(x => x.GetDiff(It.IsAny())).Returns(Task.FromResult(new ProcessOutput([], 0))); + _gitServiceMock.Setup(x => x.GetDiff(It.IsAny(), It.IsAny())).Returns(Task.FromResult(new ProcessOutput([], 0))); CommandAppResult result = _app.Run(); @@ -68,7 +68,7 @@ public void PublishChangesetCommand_WhenNoCsprojToPublish_FinishesWithError() [Test] public void PublishChangesetCommand_WhenPublishFails_FinishesWithError() { - _gitServiceMock.Setup(x => x.GetDiff(It.IsAny())).Returns(Task.FromResult(new ProcessOutput(["A.csproj", "B.csproj"], 0))); + _gitServiceMock.Setup(x => x.GetDiff(It.IsAny(), It.IsAny())).Returns(Task.FromResult(new ProcessOutput(["A.csproj", "B.csproj"], 0))); _dotnetServiceMock.Setup(x => x.Pack(It.IsAny())).Returns(Task.FromResult(new ProcessOutput(["Project A packed", "Project B packed"], 0))); _dotnetServiceMock.Setup(x => x.Publish(It.IsAny())).Returns(Task.FromResult(new ProcessOutput(["Some error ..."], 1))); diff --git a/tests/SolarWinds.Changesets.Tests/Version/CsProjectsRepositoryTests.cs b/tests/SolarWinds.Changesets.Tests/Version/CsProjectsRepositoryTests.cs index 324d586..54eb16a 100644 --- a/tests/SolarWinds.Changesets.Tests/Version/CsProjectsRepositoryTests.cs +++ b/tests/SolarWinds.Changesets.Tests/Version/CsProjectsRepositoryTests.cs @@ -36,7 +36,7 @@ public async Task UpdateModuleCsProjsAsync_IncreasesVersion_WhenVersionInProject ModuleCsProjFilePath = s_testFilePath, Changes = [("abc", BumpType.Minor)] }]; - TestConsole testConsole = new(); + using TestConsole testConsole = new(); CsProjectsRepository csProjFileHelper = new(testConsole); await csProjFileHelper.UpdateCsProjectsVersionAsync(changes); @@ -44,7 +44,6 @@ public async Task UpdateModuleCsProjsAsync_IncreasesVersion_WhenVersionInProject Semver? versionFromFile = LoadVersionFromProjectFile(s_testFilePath); versionFromFile?.Should().BeEquivalentTo(new Semver(1, 1, 0)); - testConsole.Dispose(); } [Test] @@ -65,7 +64,7 @@ public async Task UpdateModuleCsProjsAsync_DoesNotTouchProjectFile_WhenNoVersion Changes = [("abc", BumpType.Minor)] }]; - TestConsole testConsole = new(); + using TestConsole testConsole = new(); CsProjectsRepository csProjFileHelper = new(testConsole); await csProjFileHelper.UpdateCsProjectsVersionAsync(changes); @@ -73,33 +72,29 @@ public async Task UpdateModuleCsProjsAsync_DoesNotTouchProjectFile_WhenNoVersion LoadVersionFromProjectFile(s_testFilePath).Should().BeNull(); ComputeFileHash(s_testFilePath).Should().Be(hashOriginal); - testConsole.Dispose(); } [Test] - public void GetCsProjects_OneVersionOneWithoutVersionProjects_ReturnsTwoProjects() + public void GetCsProjects_OnlyOneProjectWithValidVersion_ReturnsSingleProject() { ChangesetConfig config = new() { SourcePath = "TestData" }; - TestConsole testConsole = new(); + using TestConsole testConsole = new(); CsProjectsRepository csProjFileHelper = new(testConsole); CsProject[] csProjects = csProjFileHelper.GetCsProjects(config); - csProjects.Length.Should().Be(2); + csProjects.Length.Should().Be(1); csProjects - .Where(x => x.Name == "TestProjectWithVersion") - .First() + .Single() .ReferencedProjectNames .Length .Should() .Be(2) ; - - testConsole.Dispose(); } private static void DeleteFile(string path) @@ -116,8 +111,12 @@ private static void DeleteFile(string path) doc.Load(path); XmlNode? versionNode = doc.DocumentElement?.SelectSingleNode("/Project/PropertyGroup/Version"); + if (versionNode != null && Semver.TryParse(versionNode.InnerText, out Semver? parsedVersion)) + { + return parsedVersion; + } - return versionNode != null ? Semver.FromString(versionNode.InnerText) : null; + return null; } private static string ComputeFileHash(string path) diff --git a/tests/SolarWinds.Changesets.Tests/Version/VersionChangesetCommandTests.cs b/tests/SolarWinds.Changesets.Tests/Version/VersionChangesetCommandTests.cs index 2ae67e6..fa5c89f 100644 --- a/tests/SolarWinds.Changesets.Tests/Version/VersionChangesetCommandTests.cs +++ b/tests/SolarWinds.Changesets.Tests/Version/VersionChangesetCommandTests.cs @@ -57,6 +57,8 @@ public void VersionCommand_HappyPath_FolderAndFilesAreCreated() CommandAppResult result = app.Run(); + result.ExitCode.Should().Be(ResultCodes.Success, result.Output); + AssertVersionOutput(); } @@ -80,8 +82,12 @@ private static void AssertVersionOutput() doc.Load(path); XmlNode? versionNode = doc.DocumentElement?.SelectSingleNode("/Project/PropertyGroup/Version"); + if (versionNode != null && Semver.TryParse(versionNode.InnerText, out Semver? parsedVersion)) + { + return parsedVersion; + } - return versionNode != null ? Semver.FromString(versionNode.InnerText) : null; + return null; } private static void CopyDirectory(string sourceDirectory, string destinationDirectory, bool recursive, bool firstRun)