Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions docs/commands-implementation-details.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
9 changes: 9 additions & 0 deletions src/SolarWinds.Changesets/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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**:
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -40,7 +40,7 @@ public override async Task<int> 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<string> changedCsharpProjectNames = processOutput.Output.Where(x => x.Contains(".csproj", StringComparison.Ordinal)).ToList();

if (changedCsharpProjectNames.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using SolarWinds.Changesets.Shared;

namespace SolarWinds.Changesets.Services;
namespace SolarWinds.Changesets.Commands.Publish.Services;

/// <inheritdoc />
public sealed class DotnetService : IDotnetService
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using SolarWinds.Changesets.Shared;

namespace SolarWinds.Changesets.Services;
namespace SolarWinds.Changesets.Commands.Publish.Services;

/// <inheritdoc />
public sealed class GitService : IGitService
Expand All @@ -19,12 +19,12 @@ public GitService(IProcessExecutor processExecutor)
}

/// <inheritdoc />
public async Task<ProcessOutput> GetDiff(string sourcePath)
public async Task<ProcessOutput> GetDiff(string workingDirectory, string sourcePath)
{
return await _processExecutor.Execute(
"git",
$"diff --name-only {sourcePath}",
Constants.WorkingDirectoryFullPath
$"diff --name-only HEAD~1 HEAD {sourcePath}",
workingDirectory
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using SolarWinds.Changesets.Shared;

namespace SolarWinds.Changesets.Services;
namespace SolarWinds.Changesets.Commands.Publish.Services;

/// <summary>
/// Provides functionality to interact with the .NET CLI for operations such as packing and publishing NuGet packages.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using SolarWinds.Changesets.Shared;

namespace SolarWinds.Changesets.Services;
namespace SolarWinds.Changesets.Commands.Publish.Services;

/// <summary>
/// Provides Git-related services, such as retrieving file differences.
Expand All @@ -10,7 +10,8 @@ public interface IGitService
/// <summary>
/// Retrieves a list of file names that have changed in the specified source path.
/// </summary>
/// <param name="workingDirectory">Working directory.</param>
/// <param name="sourcePath">The path to check for file differences.</param>
/// <returns>Process output and exit code.</returns>
Task<ProcessOutput> GetDiff(string sourcePath);
Task<ProcessOutput> GetDiff(string workingDirectory, string sourcePath);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -49,7 +43,6 @@ public CsProject[] GetCsProjects(ChangesetConfig changesetConfig)
.ToArray();

csProjects.Add(new(projectName, projectVersion, projectReferences, csprojFilePath));

}

return csProjects.ToArray();
Expand Down Expand Up @@ -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(@"(<Version>)(.*?)(</Version>)")]
private static partial Regex VersionRegex();
}
2 changes: 1 addition & 1 deletion src/SolarWinds.Changesets/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
23 changes: 19 additions & 4 deletions src/SolarWinds.Changesets/Shared/Semver.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using SystemVersion = System.Version;

namespace SolarWinds.Changesets.Shared;
Expand Down Expand Up @@ -45,13 +46,27 @@ public override string ToString()
return $"{Major}.{Minor}.{Patch}";
}

public static Semver? FromString(string version)
/// <summary>
/// Attempts to parse a version string into a <see cref="Semver"/> instance.
/// </summary>
/// <param name="version">The version string to parse (e.g., "1.2.3").</param>
/// <param name="semver">When this method returns, contains the parsed <see cref="Semver"/> if parsing succeeded, or null if parsing failed.</param>
/// <returns>True if the version string was successfully parsed; otherwise, false.</returns>
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;
}

/// <summary>
/// Gets an empty <see cref="Semver"/> instance with version 0.0.0.
/// </summary>
/// <value>A <see cref="Semver"/> instance representing version 0.0.0.</value>
public static Semver Empty { get; } = new(0, 0, 0);
}
2 changes: 1 addition & 1 deletion src/SolarWinds.Changesets/SolarWinds.Changesets.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<PackageReleaseNotes>$(RepositoryUrl)/releases</PackageReleaseNotes>
<PackAsTool>true</PackAsTool>
<OutputType>Exe</OutputType>
<Version>0.1.3</Version>
<Version>0.1.4</Version>

<DebugType>embedded</DebugType>
<EnablePackageValidation>true</EnablePackageValidation>
Expand Down
Loading
Loading