Skip to content

Commit

Permalink
Added support for converting projects within sln files on the command…
Browse files Browse the repository at this point in the history
… line
  • Loading branch information
icnocop committed Nov 4, 2024
1 parent c939764 commit 90570e7
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 93 deletions.
17 changes: 10 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ on:
description: 'Publish extension to the Visual Studio Marketplace?'
required: true
default: 'false'
publishNupkg:
description: 'Publish NuGet package to nuget.org?'
skipPublishNupkg:
description: 'Skip publish NuGet package?'
required: true
default: 'false'
stableRelease:
description: 'Is NuGet package a stable release?'
required: true
default: 'false'

Expand Down Expand Up @@ -54,7 +58,7 @@ jobs:
echo "SEM_VERSION=$majorVersion.$minorVersion.$buildVersion" >> $env:GITHUB_ENV
- name: Set version number for pre-release
if: ${{ github.event.inputs.publishNupkg == '' || github.event.inputs.publishNupkg == 'false' }}
if: ${{ github.event.inputs.stableRelease == '' || github.event.inputs.stableRelease == 'false' }}
run: |
echo "SEM_VERSION=${{ env.SEM_VERSION }}-build-${{ github.RUN_NUMBER }}" >> $env:GITHUB_ENV
Expand Down Expand Up @@ -153,7 +157,7 @@ jobs:
if-no-files-found: error

- name: Publish GitHub Release
if: ${{ matrix.Configuration == 'Release' && (github.event.inputs.publishNupkg == 'true' || github.event.inputs.publishVsix == 'true') }}
if: ${{ matrix.Configuration == 'Release' && github.event.inputs.publishVsix == 'true' }}
uses: softprops/action-gh-release@v2.0.8
with:
name: v${{ env.VERSION }}
Expand All @@ -164,14 +168,13 @@ jobs:
fail_on_unmatched_files: true
files: |
./src/PackageReferenceVersionToAttributeExtension/bin/${{matrix.Configuration}}/PackageReferenceVersionToAttributeExtension.vsix
./src/PackageReferenceVersionToAttributeTool/bin/${{matrix.Configuration}}/PackageReferenceVersionToAttribute.Tool.${{ env.SEM_VERSION }}.nupkg
- name: Publish NuGet Package
if: ${{ matrix.Configuration == 'Release' && github.event_name != 'pull_request' && github.event.inputs.publishNupkg == 'true' }}
if: ${{ matrix.Configuration == 'Release' && github.event_name != 'pull_request' && (github.event.inputs.skipPublishNupkg == '' || github.event.inputs.skipPublishNupkg == 'false') }}
run: dotnet nuget push .\src\PackageReferenceVersionToAttributeTool\bin\${{matrix.Configuration}}\PackageReferenceVersionToAttribute.Tool.${{ env.SEM_VERSION }}.nupkg --api-key ${{ secrets.NUGET_KEY }} --source https://api.nuget.org/v3/index.json

- name: Publish to Open VSIX
if: ${{ matrix.Configuration == 'Release' && github.event_name != 'pull_request' && github.event.inputs.publishNupkg == 'true' }}
if: ${{ matrix.Configuration == 'Release' && github.event_name != 'pull_request' && (github.event.inputs.skipPublishNupkg == '' || github.event.inputs.skipPublishNupkg == 'false') }}
run: |
[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
$vsixFile = ".\src\PackageReferenceVersionToAttributeExtension\bin\${{matrix.Configuration}}\PackageReferenceVersionToAttributeExtension.vsix"
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## TBD

- Added support for converting projects within sln files on the command line

## v1.0.1104.33 (November 4<sup>th</sup>, 2024)

- Added input validation for the mutually exclusive --backup and --dry-run command line options
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# PackageReference Version to Attribute

This Visual Studio extension and dotnet tool converts PackageReference Version child elements to attributes across your projects.
This Visual Studio extension and dotnet tool converts `PackageReference` `Version` child elements to attributes in C# project files (`csproj`).

It works with C# csproj Visual Studio project files.
It can also convert all projects in a Visual Studio solution file (`sln`).

## Getting started with the Visual Studio Extension

1. [Download and install the extension from the Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=RamiAbughazaleh.PackageReferenceVersionToAttributeExtension).
2. Right-click on a project and select `Convert PackageReference Version elements to attributes...`.
2. Right-click on the root `Solution` node, or one or more projects, and select `Convert PackageReference Version elements to attributes...`.

![Preview](Preview.png)

Expand All @@ -31,7 +31,7 @@ It works with C# csproj Visual Studio project files.

## Technical details

The extension will first create a backup of the project file.
The extension will create a backup of each project file.
For example, `MyProject.csproj` will be copied to `MyProject.csproj.bak`.

If the project file is source controlled, it will be checked out for modification.
Expand Down
1 change: 0 additions & 1 deletion src/PackageReferenceVersionToAttribute/ProjectConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace PackageReferenceVersionToAttribute
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="SlnParser" Version="4.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
86 changes: 5 additions & 81 deletions src/PackageReferenceVersionToAttributeTool/ProgramCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@

namespace PackageReferenceVersionToAttributeTool
{
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PackageReferenceVersionToAttribute;

/// <summary>
/// Program command.
Expand Down Expand Up @@ -51,82 +44,13 @@ public ProgramCommand()
this.Add(forceOption);
this.Add(dryRunOption);

// Add validation
this.AddValidator(result =>
{
bool backup = result.GetValueForOption(backupOption);
bool force = result.GetValueForOption(forceOption);
bool dryRun = result.GetValueForOption(dryRunOption);

var options = new ProjectConverterOptions
{
Backup = backup,
Force = force,
DryRun = dryRun,
};

var validator = new ProjectConverterOptionsValidator();
var validationResult = validator.Validate(nameof(ProjectConverterOptions), options);
if (validationResult.Failed)
{
result.ErrorMessage = validationResult.FailureMessage;
}
});
// validate
var commandValidator = new ProgramCommandLineOptionsValidator(backupOption, forceOption, dryRunOption);
this.AddValidator(commandValidator.Validate);

// Set the handler for the command
this.Handler = CommandHandler.Create<ProgramCommandLineOptions>(async (options) =>
{
if (options.Version)
{
var version = Assembly.GetExecutingAssembly().GetName().Version;
Console.WriteLine($"Version: {version}");
return;
}

foreach (var input in options.Inputs)
{
await ConvertPackageReferencesAsync(input, options);
}
});
}

private static async Task ConvertPackageReferencesAsync(
string input, ProjectConverterOptions options)
{
FilePatternMatcher filePatternMatcher = new();
List<string> matchingFiles = filePatternMatcher.GetMatchingFiles(input);
if (matchingFiles.Count == 0)
{
Console.WriteLine($"No matching files found for pattern: {input}");
return;
}

if (options.Backup)
{
Console.WriteLine("Backup option is enabled.");
}

if (options.Force)
{
Console.WriteLine("Force option is enabled.");
}

if (options.DryRun)
{
Console.WriteLine("Dry run mode is enabled. No changes will be made.");
}

using var serviceProvider = new ServiceCollection()
.AddSingleton(Microsoft.Extensions.Options.Options.Create(options))
.AddLogging(configure => configure.AddConsole())
.AddSingleton<ProjectConverter>()
.AddSingleton<IFileService, FileService>()
.AddSingleton<ISourceControlService, NullSourceControlService>()
.BuildServiceProvider();

var projectConverter = serviceProvider.GetRequiredService<ProjectConverter>();

await projectConverter.ConvertAsync(matchingFiles);
this.Handler = CommandHandler.Create<ProgramCommandLineOptions>(
ProgramCommandHandler.HandleAsync);
}
}
}
120 changes: 120 additions & 0 deletions src/PackageReferenceVersionToAttributeTool/ProgramCommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// <copyright file="ProgramCommandHandler.cs" company="Rami Abughazaleh">
// Copyright (c) Rami Abughazaleh. All rights reserved.
// </copyright>

namespace PackageReferenceVersionToAttributeTool
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PackageReferenceVersionToAttribute;
using SlnParser;
using SlnParser.Contracts;

/// <summary>
/// Program command handler.
/// </summary>
internal class ProgramCommandHandler
{
/// <summary>
/// Executes the command logic asynchronously based on the provided command line options.
/// </summary>
/// <param name="options">An instance of <see cref="ProgramCommandLineOptions"/> containing the parsed options from the command line.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static async Task HandleAsync(ProgramCommandLineOptions options)
{
if (options.Version)
{
var version = Assembly.GetExecutingAssembly().GetName().Version;
Console.WriteLine($"Version: {version}");
return;
}

foreach (var input in options.Inputs)
{
await ConvertPackageReferencesAsync(input, options);
}
}

private static async Task ConvertPackageReferencesAsync(
string input, ProjectConverterOptions options)
{
FilePatternMatcher filePatternMatcher = new();

// get matching csproj and sln files
List<string> matchingFiles = filePatternMatcher.GetMatchingFiles(input)
.Where(x => x.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)
|| x.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
.ToList();

// parse sln files to get only csproj files
List<string> projectFiles = GetCsprojFiles(matchingFiles);
if (projectFiles.Count == 0)
{
Console.WriteLine($"No matching project files found for pattern: {input}");
return;
}

if (options.Backup)
{
Console.WriteLine("Backup option is enabled.");
}

if (options.Force)
{
Console.WriteLine("Force option is enabled.");
}

if (options.DryRun)
{
Console.WriteLine("Dry run mode is enabled. No changes will be made.");
}

using var serviceProvider = new ServiceCollection()
.AddSingleton(Microsoft.Extensions.Options.Options.Create(options))
.AddLogging(configure => configure.AddConsole())
.AddSingleton<ProjectConverter>()
.AddSingleton<IFileService, FileService>()
.AddSingleton<ISourceControlService, NullSourceControlService>()
.BuildServiceProvider();

var projectConverter = serviceProvider.GetRequiredService<ProjectConverter>();

await projectConverter.ConvertAsync(projectFiles);
}

private static List<string> GetCsprojFiles(List<string> files)
{
var csprojFiles = new List<string>();

foreach (var file in files)
{
if (file.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
{
csprojFiles.Add(file);
}
else if (file.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
{
csprojFiles.AddRange(GetCsprojFiles(file));
}
}

return csprojFiles;
}

private static IEnumerable<string> GetCsprojFiles(string solutionFilePath)
{
SolutionParser solutionParser = new SolutionParser();
ISolution solution = solutionParser.Parse(solutionFilePath);

return solution.AllProjects
.OfType<SolutionProject>()
.Where(x => x.File.FullName.EndsWith(".csproj"))
.Select(x => x.File.FullName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// <copyright file="ProgramCommandLineOptionsValidator.cs" company="Rami Abughazaleh">
// Copyright (c) Rami Abughazaleh. All rights reserved.
// </copyright>

namespace PackageReferenceVersionToAttributeTool
{
using System.CommandLine;
using System.CommandLine.Parsing;
using PackageReferenceVersionToAttribute;

/// <summary>
/// Validates command-line options for the program.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="ProgramCommandLineOptionsValidator"/> class.
/// </remarks>
/// <param name="backupOption">The backup option.</param>
/// <param name="forceOption">The force option.</param>
/// <param name="dryRunOption">The dry run option.</param>
internal class ProgramCommandLineOptionsValidator(
Option<bool> backupOption,
Option<bool> forceOption,
Option<bool> dryRunOption)
{
private readonly Option<bool> backupOption = backupOption;
private readonly Option<bool> forceOption = forceOption;
private readonly Option<bool> dryRunOption = dryRunOption;

/// <summary>
/// Validates the specified <see cref="CommandResult"/>.
/// </summary>
/// <param name="result">The <see cref="CommandResult"/> containing the parsed command-line options.</param>
public void Validate(CommandResult result)
{
// Extract option values
bool backup = result.GetValueForOption(this.backupOption);
bool force = result.GetValueForOption(this.forceOption);
bool dryRun = result.GetValueForOption(this.dryRunOption);

var options = new ProjectConverterOptions
{
Backup = backup,
Force = force,
DryRun = dryRun,
};

var validator = new ProjectConverterOptionsValidator();
var validationResult = validator.Validate(nameof(ProjectConverterOptions), options);
if (validationResult.Failed)
{
result.ErrorMessage = validationResult.FailureMessage;
}
}
}
}
Loading

0 comments on commit 90570e7

Please sign in to comment.