move projects to src folder
This commit is contained in:
8
src/DotNetSolutionTools.App/App.axaml
Normal file
8
src/DotNetSolutionTools.App/App.axaml
Normal file
@@ -0,0 +1,8 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="DotNetSolutionTools.App.App"
|
||||
RequestedThemeVariant="Default">
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
25
src/DotNetSolutionTools.App/App.axaml.cs
Normal file
25
src/DotNetSolutionTools.App/App.axaml.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using DotNetSolutionTools.App.ViewModels;
|
||||
using DotNetSolutionTools.App.Views;
|
||||
|
||||
namespace DotNetSolutionTools.App;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow() { DataContext = new MainWindowViewModel() };
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
BIN
src/DotNetSolutionTools.App/Assets/avalonia-logo.ico
Normal file
BIN
src/DotNetSolutionTools.App/Assets/avalonia-logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
34
src/DotNetSolutionTools.App/DotNetSolutionTools.App.csproj
Normal file
34
src/DotNetSolutionTools.App/DotNetSolutionTools.App.csproj
Normal file
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectCapability Include="Avalonia"/>
|
||||
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.0" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
8
src/DotNetSolutionTools.App/Models/LocalStateDto.cs
Normal file
8
src/DotNetSolutionTools.App/Models/LocalStateDto.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DotNetSolutionTools.App.Models;
|
||||
|
||||
public class LocalStateDto
|
||||
{
|
||||
public string SolutionFolderPath { get; set; } = string.Empty;
|
||||
public string SolutionFilePath { get; set; } = string.Empty;
|
||||
public string CsprojFilePath { get; set; } = string.Empty;
|
||||
}
|
||||
16
src/DotNetSolutionTools.App/Program.cs
Normal file
16
src/DotNetSolutionTools.App/Program.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Avalonia;
|
||||
|
||||
namespace DotNetSolutionTools.App;
|
||||
|
||||
class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp() =>
|
||||
AppBuilder.Configure<App>().UsePlatformDetect().WithInterFont().LogToTrace();
|
||||
}
|
||||
56
src/DotNetSolutionTools.App/Services/FileService.cs
Normal file
56
src/DotNetSolutionTools.App/Services/FileService.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace DotNetSolutionTools.App.Services;
|
||||
|
||||
public class FileService
|
||||
{
|
||||
private readonly FilePickerFileType _csprojFileType = new("C# Project File") { Patterns = ["*.csproj"] };
|
||||
private readonly FilePickerFileType _slnFileType = new("C# Solution File") { Patterns = ["*.sln", "*.slnx"] };
|
||||
|
||||
public async Task<IStorageFile?> DoOpenFilePickerCsprojAsync()
|
||||
{
|
||||
return await DoOpenFilePickerAsync(_csprojFileType);
|
||||
}
|
||||
|
||||
public async Task<IStorageFile?> DoOpenFilePickerSlnAsync()
|
||||
{
|
||||
return await DoOpenFilePickerAsync(_slnFileType);
|
||||
}
|
||||
|
||||
public async Task<IStorageFile?> DoOpenFilePickerAsync(FilePickerFileType fileType)
|
||||
{
|
||||
if (
|
||||
Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop
|
||||
|| desktop.MainWindow?.StorageProvider is not { } provider
|
||||
)
|
||||
throw new NullReferenceException("Missing StorageProvider instance.");
|
||||
|
||||
var files = await provider.OpenFilePickerAsync(
|
||||
new FilePickerOpenOptions()
|
||||
{
|
||||
Title = "Open File",
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = [fileType]
|
||||
}
|
||||
);
|
||||
|
||||
return files?.Count >= 1 ? files[0] : null;
|
||||
}
|
||||
|
||||
public async Task<IStorageFolder?> DoOpenFolderPickerAsync()
|
||||
{
|
||||
if (
|
||||
Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop
|
||||
|| desktop.MainWindow?.StorageProvider is not { } provider
|
||||
)
|
||||
throw new NullReferenceException("Missing StorageProvider instance.");
|
||||
|
||||
var folder = await provider.OpenFolderPickerAsync(
|
||||
new FolderPickerOpenOptions() { Title = "Select Folder", AllowMultiple = false }
|
||||
);
|
||||
|
||||
return folder?.Count >= 1 ? folder[0] : null;
|
||||
}
|
||||
}
|
||||
416
src/DotNetSolutionTools.App/ViewModels/MainWindowViewModel.cs
Normal file
416
src/DotNetSolutionTools.App/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,416 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Immutable;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using DotNetSolutionTools.App.Models;
|
||||
using DotNetSolutionTools.App.Services;
|
||||
using DotNetSolutionTools.Core;
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using NuGet.Packaging;
|
||||
|
||||
namespace DotNetSolutionTools.App.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel : ObservableObject
|
||||
{
|
||||
private readonly FileService _fileService = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _solutionFolderPath = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _solutionFilePath = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _csprojFilePath = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> _operationResults = new() { };
|
||||
|
||||
[ObservableProperty]
|
||||
private string _resultsLabel = "Ready";
|
||||
|
||||
[ObservableProperty]
|
||||
private IImmutableSolidColorBrush _resultsLabelColor = new ImmutableSolidColorBrush(Colors.Black);
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
LoadSavedState().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void SetBeginCommandState()
|
||||
{
|
||||
OperationResults.Clear();
|
||||
ResultsLabel = "Running...";
|
||||
ResultsLabelColor = new ImmutableSolidColorBrush(Colors.Black);
|
||||
}
|
||||
|
||||
private void SetCommandSuccessState(string message, IEnumerable<string>? results = null)
|
||||
{
|
||||
ResultsLabel = message;
|
||||
ResultsLabelColor = new ImmutableSolidColorBrush(Colors.Green);
|
||||
if (results is not null)
|
||||
OperationResults.AddRange(results);
|
||||
}
|
||||
|
||||
private void SetCommandFailureState(string message, Exception e)
|
||||
{
|
||||
ResultsLabel = message;
|
||||
ResultsLabelColor = new ImmutableSolidColorBrush(Colors.DarkRed);
|
||||
OperationResults?.Add(e.Message);
|
||||
OperationResults?.Add(e.ToString());
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ExecuteParityChecker(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var results = SolutionProjectParity.CompareSolutionAndCSharpProjects(
|
||||
SolutionFolderPath,
|
||||
SolutionFilePath
|
||||
);
|
||||
SetCommandSuccessState($"{results.Count} Projects in folder missing from solution", results);
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to compare solution and csharp projects", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task FormatCsProjFile(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
FormatCsproj.FormatCsprojFile(CsprojFilePath);
|
||||
},
|
||||
token
|
||||
);
|
||||
SetCommandSuccessState("Successfully formatted csproj file");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to format csproj file", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task FormatAllCsprojFilesInSolutionFile(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromSolution(SolutionFilePath);
|
||||
foreach (var csproj in csprojList)
|
||||
{
|
||||
FormatCsproj.FormatCsprojFile(csproj);
|
||||
}
|
||||
},
|
||||
token
|
||||
);
|
||||
SetCommandSuccessState("Successfully formatted all csproj files in solution file");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to format all csproj files in solution file", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task FormatAllCsprojFilesInSolutionFolder(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromFolder(SolutionFolderPath);
|
||||
foreach (var csproj in csprojList)
|
||||
{
|
||||
FormatCsproj.FormatCsprojFile(csproj);
|
||||
}
|
||||
},
|
||||
token
|
||||
);
|
||||
SetCommandSuccessState("Successfully formatted all csproj files in folder");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to format all csproj files in folder", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ResolveInconsistentNugetPackageVersionsInSolutionFile(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
PackageVersionConsistency.ResolvePackageVersionsToLatestInstalled(SolutionFilePath);
|
||||
SetCommandSuccessState("Resolved all packages with inconsistent versions");
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to resolve inconsistent package versions in solution file", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CheckForInconsistentNugetPackageVersionsInSolutionFile(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var results = PackageVersionConsistency.FindInconsistentNugetPackageVersions(SolutionFilePath);
|
||||
SetCommandSuccessState($"{results.Count} packages with inconsistent versions", results);
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to check for inconsistent package versions in solution file", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CheckForMissingImplicitUsingsInSolutionFile(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var results = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(SolutionFilePath);
|
||||
SetCommandSuccessState($"{results.Count} Projects missing ImplicitUsings", results);
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to check for missing implicit usings in solution file", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CheckForMissingTreatWarningsAsErrorsInSolutionFile(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var results = WarningsAsErrors.FindCSharpProjectsMissingTreatWarningsAsErrors(SolutionFilePath);
|
||||
SetCommandSuccessState($"{results.Count} Projects missing TreatWarningsAsErrors", results);
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to check for missing treat warnings as errors in solution file", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task DeleteBinAndObjFoldersInFolder(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
CleanFolder.DeleteBinObjAndNodeModulesFoldersInFolder(SolutionFolderPath);
|
||||
},
|
||||
token
|
||||
);
|
||||
SetCommandSuccessState("Successfully deleted bin and obj folders");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to delete bin and obj folders", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task UpdateAllProjectsToNet80(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
async () =>
|
||||
{
|
||||
await DotNetUpgrade.UpdateProjectsInSolutionToNet80(SolutionFilePath);
|
||||
},
|
||||
token
|
||||
);
|
||||
SetCommandSuccessState("Successfully updated all projects in solution to .NET 8");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to update all projects in solution to .NET 8", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task UpdateProjectToNet80(CancellationToken token)
|
||||
{
|
||||
SetBeginCommandState();
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
async () =>
|
||||
{
|
||||
await DotNetUpgrade.UpdateProjectAtPathToNet80(CsprojFilePath);
|
||||
},
|
||||
token
|
||||
);
|
||||
SetCommandSuccessState("Successfully updated project to .NET 8");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetCommandFailureState("Failed to update project to .NET 8", e);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LoadSolutionFile(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = await _fileService.DoOpenFilePickerSlnAsync();
|
||||
if (file is null)
|
||||
return;
|
||||
|
||||
SolutionFilePath = file.Path.AbsolutePath;
|
||||
await SaveLoadedState();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ResultsLabel = "Error";
|
||||
OperationResults?.Add(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ClearSolutionFile(CancellationToken token)
|
||||
{
|
||||
SolutionFilePath = string.Empty;
|
||||
await SaveLoadedState();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LoadSolutionFolder(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var folder = await _fileService.DoOpenFolderPickerAsync();
|
||||
if (folder is null)
|
||||
return;
|
||||
|
||||
SolutionFolderPath = folder.Path.AbsolutePath;
|
||||
await SaveLoadedState();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ResultsLabel = "Error";
|
||||
OperationResults?.Add(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ClearSolutionFolder(CancellationToken token)
|
||||
{
|
||||
SolutionFolderPath = string.Empty;
|
||||
await SaveLoadedState();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LoadCsprojFile(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var folder = await _fileService.DoOpenFilePickerCsprojAsync();
|
||||
if (folder is null)
|
||||
return;
|
||||
|
||||
CsprojFilePath = folder.Path.AbsolutePath;
|
||||
await SaveLoadedState();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ResultsLabel = "Error";
|
||||
OperationResults?.Add(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ClearCsprojFile(CancellationToken token)
|
||||
{
|
||||
CsprojFilePath = string.Empty;
|
||||
await SaveLoadedState();
|
||||
}
|
||||
|
||||
private async Task SaveLoadedState()
|
||||
{
|
||||
var dto = new LocalStateDto
|
||||
{
|
||||
SolutionFolderPath = SolutionFolderPath,
|
||||
SolutionFilePath = SolutionFilePath,
|
||||
CsprojFilePath = CsprojFilePath
|
||||
};
|
||||
var json = JsonSerializer.Serialize(dto);
|
||||
await File.WriteAllTextAsync("./localState.json", json);
|
||||
}
|
||||
|
||||
private async Task LoadSavedState()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await File.ReadAllTextAsync("./localState.json");
|
||||
if (string.IsNullOrEmpty(json))
|
||||
return;
|
||||
var dto = JsonSerializer.Deserialize<LocalStateDto>(json);
|
||||
if (dto is null)
|
||||
return;
|
||||
SolutionFolderPath = dto.SolutionFolderPath;
|
||||
SolutionFilePath = dto.SolutionFilePath;
|
||||
CsprojFilePath = dto.CsprojFilePath;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
146
src/DotNetSolutionTools.App/Views/MainWindow.axaml
Normal file
146
src/DotNetSolutionTools.App/Views/MainWindow.axaml
Normal file
@@ -0,0 +1,146 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="clr-namespace:DotNetSolutionTools.App.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
|
||||
Width="800" Height="650"
|
||||
x:Class="DotNetSolutionTools.App.Views.MainWindow"
|
||||
x:DataType="viewModels:MainWindowViewModel"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
Title="DotNetSolutionTools">
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<viewModels:MainWindowViewModel />
|
||||
</Design.DataContext>
|
||||
<DockPanel Width="{Binding $parent.Bounds.Width}" Height="{Binding $parent.Bounds.Height}">
|
||||
<StackPanel Margin="5 10 5 5" Orientation="Vertical" DockPanel.Dock="Top">
|
||||
<Grid Margin="5 0 5 0" ColumnDefinitions="Auto,*,Auto">
|
||||
<Button Grid.Column="0" HorizontalContentAlignment="Center" Width="200" Command="{Binding LoadSolutionFolderCommand}">Select Solution Folder</Button>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" TextAlignment="Center" Name="SolutionFolderPath2" Text="{Binding SolutionFolderPath}" />
|
||||
<Button Grid.Column="2" HorizontalContentAlignment="Center" Width="60" Command="{Binding ClearSolutionFolderCommand}">Clear</Button>
|
||||
</Grid>
|
||||
<Grid Margin="5 0 5 0" ColumnDefinitions="Auto,*,Auto">
|
||||
<Button Grid.Column="0" Width="200" HorizontalContentAlignment="Center" Command="{Binding LoadSolutionFileCommand}">Select Solution File</Button>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Name="SolutionFilePath" Text="{Binding SolutionFilePath}" />
|
||||
<Button Grid.Column="2" Width="60" HorizontalContentAlignment="Center" Command="{Binding ClearSolutionFileCommand}">Clear</Button>
|
||||
</Grid>
|
||||
<Grid Margin="5 0 5 0" ColumnDefinitions="Auto,*,Auto">
|
||||
<Button Grid.Column="0" Width="200" HorizontalContentAlignment="Center" Command="{Binding LoadCsprojFileCommand}">Select CSharp Project File</Button>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Name="CsprojFilePath" Text="{Binding CsprojFilePath}" />
|
||||
<Button Grid.Column="2" Width="60" HorizontalContentAlignment="Center" Command="{Binding ClearCsprojFileCommand}">Clear</Button>
|
||||
</Grid>
|
||||
<Grid Margin="0 5 0 0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ShowGridLines="False" RowDefinitions="*,*,*,*" ColumnDefinitions="*,*,*">
|
||||
<Button Grid.Row="0" Grid.Column="0" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFolderPath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding ExecuteParityCheckerCommand}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Check Solution Parity
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="0" Grid.Column="1" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding CsprojFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding FormatCsProjFileCommand}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Format CSharp Project File
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="0" Grid.Column="2" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding FormatAllCsprojFilesInSolutionFileCommand}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Format All CSharp Project Files in Solution
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Grid.Column="0" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFolderPath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding FormatAllCsprojFilesInSolutionFolderCommand}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Format All CSharp Project Files in Folder
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Grid.Column="1" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding CheckForMissingImplicitUsingsInSolutionFileCommand}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Check For Missing Implicit Usings
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Grid.Column="2" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding CheckForMissingTreatWarningsAsErrorsInSolutionFileCommand}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Check For Missing Treat Warnings as Errors
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="2" Grid.Column="0" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFolderPath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding DeleteBinAndObjFoldersInFolderCommand}">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Clear bin and obj folders
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
(and node_modules)
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="2" Grid.Column="1" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding UpdateAllProjectsToNet80Command}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Update all projects in Solution to .NET 9.0
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="2" Grid.Column="2" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding CsprojFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding UpdateProjectToNet80Command}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Update C# Project to .NET 9.0
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="3" Grid.Column="0" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding CheckForInconsistentNugetPackageVersionsInSolutionFileCommand}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Check for Inconsistent Nuget Package Versions
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Row="3" Grid.Column="1" MinHeight="100" Padding="10" Margin="5"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
IsEnabled="{Binding SolutionFilePath, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Command="{Binding ResolveInconsistentNugetPackageVersionsInSolutionFileCommand}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Resolve Inconsistent Nuget Package Versions
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Label Width="{Binding $parent.Bounds.Width}" Background="{Binding ResultsLabelColor}" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Name="ResultsLabel" Content="{Binding ResultsLabel}"></Label>
|
||||
</StackPanel>
|
||||
<ScrollViewer Width="{Binding $parent.Bounds.Width}">
|
||||
<ListBox ScrollViewer.VerticalScrollBarVisibility="Visible" Name="Results" ItemsSource="{Binding OperationResults}" />
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
17
src/DotNetSolutionTools.App/Views/MainWindow.axaml.cs
Normal file
17
src/DotNetSolutionTools.App/Views/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Avalonia.Controls;
|
||||
using Microsoft.Build.Locator;
|
||||
|
||||
namespace DotNetSolutionTools.App.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
var instance = MSBuildLocator
|
||||
.QueryVisualStudioInstances()
|
||||
.OrderByDescending(instance => instance.Version)
|
||||
.First();
|
||||
MSBuildLocator.RegisterInstance(instance);
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
18
src/DotNetSolutionTools.App/app.manifest
Normal file
18
src/DotNetSolutionTools.App/app.manifest
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="SolutionParityChecker.App.Desktop"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
29
src/DotNetSolutionTools.CLI/Commands/ClearBinObjCommand.cs
Normal file
29
src/DotNetSolutionTools.CLI/Commands/ClearBinObjCommand.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using DotNetSolutionTools.Core;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace DotNetSolutionTools.CLI.Commands;
|
||||
|
||||
public class ClearBinObjCommand : Command<ClearBinObjCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(1, "<FolderPath>")]
|
||||
public required string FolderPath { get; set; }
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context, Settings settings)
|
||||
{
|
||||
// validate a real folder path was passed in
|
||||
if (!Directory.Exists(settings.FolderPath))
|
||||
{
|
||||
Console.WriteLine("Invalid folder path");
|
||||
return 1;
|
||||
}
|
||||
Console.WriteLine("Deleting bin, obj, and node_modules folders");
|
||||
CleanFolder.DeleteBinObjAndNodeModulesFoldersInFolder(settings.FolderPath);
|
||||
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
69
src/DotNetSolutionTools.CLI/Commands/CompareCommand.cs
Normal file
69
src/DotNetSolutionTools.CLI/Commands/CompareCommand.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.ComponentModel;
|
||||
using DotNetSolutionTools.Core;
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace DotNetSolutionTools.CLI.Commands;
|
||||
|
||||
public class CompareCommand : Command<CompareCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<SolutionFolderPath>")]
|
||||
public required string SolutionFolderPath { get; set; }
|
||||
|
||||
[CommandArgument(1, "<SolutionFilePath>")]
|
||||
public required string SolutionFilePath { get; set; }
|
||||
|
||||
[CommandOption("-l|--logprojectfiles")]
|
||||
[Description("true to enable log output of all project files found in folder. Default is false.")]
|
||||
[DefaultValue(false)]
|
||||
public bool LogAllProjectFileNames { get; set; } = false;
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context, Settings settings)
|
||||
{
|
||||
var folderDirectory = settings.SolutionFolderPath; // Include the trailing slash
|
||||
var pathToSolutionFile = settings.SolutionFilePath;
|
||||
Console.WriteLine($"Retrieving C# Projects from {folderDirectory}");
|
||||
|
||||
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromFolder(folderDirectory);
|
||||
|
||||
if (settings.LogAllProjectFileNames)
|
||||
{
|
||||
foreach (var project in csprojList)
|
||||
{
|
||||
Console.WriteLine(project);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"Retrieved {csprojList.Length} C# Projects");
|
||||
Console.WriteLine("==================================================");
|
||||
|
||||
Console.WriteLine($"Parsing Solution File: {pathToSolutionFile}");
|
||||
// Load the solution file
|
||||
var solutionFile = SlnHelper.ParseSolutionFileFromPath(pathToSolutionFile);
|
||||
if (solutionFile == null)
|
||||
{
|
||||
Console.WriteLine("Failed to parse solution file. The file was either not found or malformed.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the list of projects
|
||||
var projectsMissingFromSolution = SolutionProjectParity.FindProjectsMissingFromSolution(
|
||||
csprojList,
|
||||
solutionFile
|
||||
);
|
||||
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine($"Missing {projectsMissingFromSolution.Count} C# Projects from Solution File");
|
||||
|
||||
foreach (var project in projectsMissingFromSolution)
|
||||
{
|
||||
Console.WriteLine(project);
|
||||
}
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
72
src/DotNetSolutionTools.CLI/Commands/FormatCsprojCommand.cs
Normal file
72
src/DotNetSolutionTools.CLI/Commands/FormatCsprojCommand.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using DotNetSolutionTools.Core;
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace DotNetSolutionTools.CLI.Commands;
|
||||
|
||||
public class FormatCsprojCommand : Command<FormatCsprojCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("--project <CsprojFilePath>")]
|
||||
public string? CsprojFilePath { get; set; }
|
||||
|
||||
[CommandOption("--folder <SolutionFolderPath>")]
|
||||
public string? SolutionFolderPath { get; set; }
|
||||
|
||||
[CommandOption("--sln <SolutionFilePath>")]
|
||||
public string? SolutionFilePath { get; set; }
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context, Settings settings)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(settings.CsprojFilePath))
|
||||
{
|
||||
var pathToCsprojFile = settings.CsprojFilePath;
|
||||
Console.WriteLine($"Retrieving C# Project from {pathToCsprojFile}");
|
||||
FormatCsproj.FormatCsprojFile(pathToCsprojFile);
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(settings.SolutionFilePath))
|
||||
{
|
||||
var test = SlnHelper.ParseSolutionFileFromPath(settings.SolutionFilePath);
|
||||
if (test == null)
|
||||
{
|
||||
Console.WriteLine("Failed to parse solution file. The file was either not found or malformed.");
|
||||
return 1;
|
||||
}
|
||||
var cSharpProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(test);
|
||||
Console.WriteLine($"Found {cSharpProjects.Count} C# Projects");
|
||||
Console.WriteLine("==================================================");
|
||||
foreach (var project in cSharpProjects)
|
||||
{
|
||||
FormatCsproj.FormatCsprojFile(project.FullPath);
|
||||
}
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(settings.SolutionFolderPath))
|
||||
{
|
||||
var folderDirectory = settings.SolutionFolderPath; // Include the trailing slash
|
||||
Console.WriteLine($"Retrieving C# Projects from {folderDirectory}");
|
||||
|
||||
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromFolder(folderDirectory);
|
||||
|
||||
Console.WriteLine($"Retrieved {csprojList.Length} C# Projects");
|
||||
Console.WriteLine("==================================================");
|
||||
|
||||
foreach (var project in csprojList)
|
||||
{
|
||||
FormatCsproj.FormatCsprojFile(project);
|
||||
}
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No arguments were provided. Please provide a csproj, folder, or solution file.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.ComponentModel;
|
||||
using DotNetSolutionTools.Core;
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace DotNetSolutionTools.CLI.Commands;
|
||||
|
||||
public class ImplicitUsingsCommand : Command<ImplicitUsingsCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(1, "<SolutionFilePath>")]
|
||||
public required string SolutionFilePath { get; set; }
|
||||
|
||||
[CommandOption("-m|--add-missing")]
|
||||
[Description("Add Implicit Usings=true to projects missing them. Default is false.")]
|
||||
[DefaultValue(false)]
|
||||
public bool AddMissing { get; set; } = false;
|
||||
|
||||
[CommandOption("-d|--enable-disabled")]
|
||||
[Description("Sets Implicit Usings to true for any projects with it disabled. Default is false.")]
|
||||
[DefaultValue(false)]
|
||||
public bool EnableDisabled { get; set; } = false;
|
||||
|
||||
[CommandOption("-a|--enable-all")]
|
||||
[Description("Enables Implicit Usings for all projects. Default is false.")]
|
||||
[DefaultValue(false)]
|
||||
public bool EnableAll { get; set; } = false;
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context, Settings settings)
|
||||
{
|
||||
var pathToSolutionFile = settings.SolutionFilePath;
|
||||
Console.WriteLine($"Retrieving Solution from {pathToSolutionFile}");
|
||||
|
||||
var solutionFile = SlnHelper.ParseSolutionFileFromPath(pathToSolutionFile);
|
||||
if (solutionFile == null)
|
||||
{
|
||||
Console.WriteLine("Failed to parse solution file. The file was either not found or malformed.");
|
||||
return 1;
|
||||
}
|
||||
var cSharpProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
Console.WriteLine($"Found {cSharpProjects.Count} C# Projects");
|
||||
Console.WriteLine("==================================================");
|
||||
|
||||
// Get the list of projects
|
||||
var projectsMissingImplicitUsings = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(cSharpProjects);
|
||||
|
||||
Console.WriteLine(
|
||||
$"{projectsMissingImplicitUsings.Count} C# Projects have missing or disabled implicit usings"
|
||||
);
|
||||
|
||||
foreach (var project in projectsMissingImplicitUsings)
|
||||
{
|
||||
Console.WriteLine(project.DirectoryPath);
|
||||
}
|
||||
|
||||
if (settings.AddMissing)
|
||||
{
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Adding missing implicit usings");
|
||||
ImplicitUsings.AddMissingImplicitUsings(projectsMissingImplicitUsings);
|
||||
var updatedProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
var projectsWithMissing = ImplicitUsings.FindCSharpProjectsMissingImplicitUsings(updatedProjects);
|
||||
Console.WriteLine(
|
||||
$"There are now {projectsWithMissing.Count} C# Projects missing/disabled implicit usings"
|
||||
);
|
||||
}
|
||||
if (settings.EnableDisabled)
|
||||
{
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Enabling disabled implicit usings");
|
||||
ImplicitUsings.EnableDisabledImplicitUsings(projectsMissingImplicitUsings);
|
||||
}
|
||||
if (settings.EnableAll)
|
||||
{
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Enabling all implicit usings");
|
||||
ImplicitUsings.EnableAllImplicitUsings(projectsMissingImplicitUsings);
|
||||
}
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.ComponentModel;
|
||||
using DotNetSolutionTools.Core;
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace DotNetSolutionTools.CLI.Commands;
|
||||
|
||||
public class TreatWarningsAsErrorsCommand : Command<TreatWarningsAsErrorsCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(1, "<SolutionFilePath>")]
|
||||
public required string SolutionFilePath { get; set; }
|
||||
|
||||
[CommandOption("-m|--add-missing")]
|
||||
[DefaultValue(false)]
|
||||
public bool AddMissing { get; set; } = false;
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context, Settings settings)
|
||||
{
|
||||
var pathToSolutionFile = settings.SolutionFilePath;
|
||||
Console.WriteLine($"Retrieving Solution from {pathToSolutionFile}");
|
||||
|
||||
var solutionFile = SlnHelper.ParseSolutionFileFromPath(pathToSolutionFile);
|
||||
if (solutionFile == null)
|
||||
{
|
||||
Console.WriteLine("Failed to parse solution file. The file was either not found or malformed.");
|
||||
return 1;
|
||||
}
|
||||
var cSharpProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
Console.WriteLine($"Found {cSharpProjects.Count} C# Projects");
|
||||
Console.WriteLine("==================================================");
|
||||
|
||||
// Get the list of projects
|
||||
var projectsMissingTreatWarningsAsErrors = WarningsAsErrors.FindCSharpProjectsMissingTreatWarningsAsErrors(
|
||||
cSharpProjects
|
||||
);
|
||||
|
||||
Console.WriteLine(
|
||||
$"{projectsMissingTreatWarningsAsErrors.Count} C# Projects have missing Treat Warnings As Errors"
|
||||
);
|
||||
|
||||
if (settings.AddMissing)
|
||||
{
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Adding missing Warnings As Errors");
|
||||
WarningsAsErrors.AddMissingTreatWarningsAsErrors(projectsMissingTreatWarningsAsErrors);
|
||||
var updatedProjects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
var projectsWithMissing = WarningsAsErrors.FindCSharpProjectsMissingTreatWarningsAsErrors(updatedProjects);
|
||||
Console.WriteLine(
|
||||
$"There are now {projectsWithMissing.Count} C# Projects missing Treat Warnings As Errors"
|
||||
);
|
||||
}
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using DotNetSolutionTools.Core;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace DotNetSolutionTools.CLI.Commands;
|
||||
|
||||
public class UpdateProjectToNet80Command : AsyncCommand<UpdateProjectToNet80Command.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(1, "<CsprojFilePath>")]
|
||||
public required string CsprojFilePath { get; set; }
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
||||
{
|
||||
if (!Directory.Exists(settings.CsprojFilePath))
|
||||
{
|
||||
Console.WriteLine("Invalid file path. Please pass in a valid file path to a .csproj file.");
|
||||
return 1;
|
||||
}
|
||||
Console.WriteLine("Upgrading project to .NET 9.0");
|
||||
await DotNetUpgrade.UpdateProjectAtPathToNet80(settings.CsprojFilePath);
|
||||
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using DotNetSolutionTools.Core;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace DotNetSolutionTools.CLI.Commands;
|
||||
|
||||
public class UpdateSlnToNet80Command : AsyncCommand<UpdateSlnToNet80Command.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(1, "<SolutionFilePath>")]
|
||||
public required string SolutionFilePath { get; set; }
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
||||
{
|
||||
if (!File.Exists(settings.SolutionFilePath))
|
||||
{
|
||||
Console.WriteLine("Invalid file path. Please pass in a valid file path to a .csproj file.");
|
||||
return 1;
|
||||
}
|
||||
Console.WriteLine("Upgrading project to .NET 9.0");
|
||||
await DotNetUpgrade.UpdateProjectsInSolutionToNet80(settings.SolutionFilePath);
|
||||
|
||||
Console.WriteLine("==================================================");
|
||||
Console.WriteLine("Done!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
20
src/DotNetSolutionTools.CLI/DotNetSolutionTools.CLI.csproj
Normal file
20
src/DotNetSolutionTools.CLI/DotNetSolutionTools.CLI.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Spectre.Console" Version="0.50.0" />
|
||||
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
23
src/DotNetSolutionTools.CLI/Program.cs
Normal file
23
src/DotNetSolutionTools.CLI/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using DotNetSolutionTools.CLI.Commands;
|
||||
using Microsoft.Build.Locator;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
var app = new CommandApp();
|
||||
app.Configure(config =>
|
||||
{
|
||||
config.SetApplicationName("SolutionParityChecker");
|
||||
config.ValidateExamples();
|
||||
|
||||
config.AddCommand<CompareCommand>("compare");
|
||||
config.AddCommand<ImplicitUsingsCommand>("implicit-usings");
|
||||
config.AddCommand<FormatCsprojCommand>("format-csproj");
|
||||
config.AddCommand<TreatWarningsAsErrorsCommand>("warnings-as-errors");
|
||||
config.AddCommand<ClearBinObjCommand>("clear-bin-obj");
|
||||
config.AddCommand<UpdateProjectToNet80Command>("update-csproj-net80");
|
||||
config.AddCommand<UpdateSlnToNet80Command>("update-sln-net80");
|
||||
});
|
||||
|
||||
var instance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(instance => instance.Version).First();
|
||||
MSBuildLocator.RegisterInstance(instance);
|
||||
|
||||
return await app.RunAsync(args);
|
||||
35
src/DotNetSolutionTools.Core/CleanFolder.cs
Normal file
35
src/DotNetSolutionTools.Core/CleanFolder.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public static class CleanFolder
|
||||
{
|
||||
public static void DeleteBinObjAndNodeModulesFoldersInFolder(string folderPath)
|
||||
{
|
||||
var nodeModulesFolders = Directory
|
||||
.GetDirectories(folderPath, "*", SearchOption.AllDirectories)
|
||||
.Where(x => x.EndsWith(Path.DirectorySeparatorChar + "node_modules"))
|
||||
.ToList();
|
||||
|
||||
foreach (var folder in nodeModulesFolders)
|
||||
{
|
||||
if (Directory.Exists(folder))
|
||||
{
|
||||
Directory.Delete(folder, true);
|
||||
}
|
||||
}
|
||||
|
||||
var binAndObjFolders = Directory
|
||||
.GetDirectories(folderPath, "*", SearchOption.AllDirectories)
|
||||
.Where(x =>
|
||||
x.EndsWith(Path.DirectorySeparatorChar + "bin") || x.EndsWith(Path.DirectorySeparatorChar + "obj")
|
||||
)
|
||||
.ToList();
|
||||
|
||||
foreach (var folder in binAndObjFolders)
|
||||
{
|
||||
if (Directory.Exists(folder))
|
||||
{
|
||||
Directory.Delete(folder, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/DotNetSolutionTools.Core/Common/CsprojHelper.cs
Normal file
24
src/DotNetSolutionTools.Core/Common/CsprojHelper.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace DotNetSolutionTools.Core.Common;
|
||||
|
||||
public static class CsprojHelper
|
||||
{
|
||||
public static string[] RetrieveAllCSharpProjectFullPathsFromFolder(string solutionFolderPath)
|
||||
{
|
||||
// if solutionFolderPath does not end with a slash, add one
|
||||
if (solutionFolderPath[^1] != Path.DirectorySeparatorChar)
|
||||
{
|
||||
solutionFolderPath += Path.DirectorySeparatorChar;
|
||||
}
|
||||
var csprojList = Directory.GetFiles(solutionFolderPath, "*.csproj", SearchOption.AllDirectories);
|
||||
return csprojList;
|
||||
}
|
||||
|
||||
public static string[] RetrieveAllCSharpProjectFullPathsFromSolution(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SlnHelper.ParseSolutionFileFromPath(solutionFilePath);
|
||||
ArgumentNullException.ThrowIfNull(solutionFile);
|
||||
var result = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
var csprojList = result.Select(x => x.FullPath).ToArray();
|
||||
return csprojList;
|
||||
}
|
||||
}
|
||||
23
src/DotNetSolutionTools.Core/Common/SlnHelper.cs
Normal file
23
src/DotNetSolutionTools.Core/Common/SlnHelper.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Microsoft.Build.Construction;
|
||||
|
||||
namespace DotNetSolutionTools.Core.Common;
|
||||
|
||||
public static class SlnHelper
|
||||
{
|
||||
public static SolutionFile? ParseSolutionFileFromPath(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SolutionFile.Parse(solutionFilePath);
|
||||
|
||||
return solutionFile;
|
||||
}
|
||||
|
||||
public static List<ProjectRootElement> GetCSharpProjectObjectsFromSolutionFile(SolutionFile solutionFile)
|
||||
{
|
||||
var projectList = solutionFile
|
||||
.ProjectsByGuid.Where(x => x.Value.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat)
|
||||
.Select(s => ProjectRootElement.Open(s.Value.AbsolutePath))
|
||||
.ToList();
|
||||
|
||||
return projectList;
|
||||
}
|
||||
}
|
||||
15
src/DotNetSolutionTools.Core/DotNetSolutionTools.Core.csproj
Normal file
15
src/DotNetSolutionTools.Core/DotNetSolutionTools.Core.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build" Version="17.14.8" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.Build.Locator" Version="1.9.1" />
|
||||
<PackageReference Include="NuGet.Protocol" Version="6.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
96
src/DotNetSolutionTools.Core/DotNetUpgrade.cs
Normal file
96
src/DotNetSolutionTools.Core/DotNetUpgrade.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using DotNetSolutionTools.Core.Infrastructure;
|
||||
using Microsoft.Build.Construction;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public static class DotNetUpgrade
|
||||
{
|
||||
public static async Task UpdateProjectsInSolutionToNet80(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SolutionFile.Parse(solutionFilePath);
|
||||
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
await UpdateProjectsToNet80(csprojList);
|
||||
}
|
||||
|
||||
public static async Task UpdateProjectAtPathToNet80(string csprojFilePath)
|
||||
{
|
||||
var csproj = ProjectRootElement.Open(csprojFilePath);
|
||||
await UpdateProjectToNet80(csproj!);
|
||||
}
|
||||
|
||||
private static async Task UpdateProjectsToNet80(List<ProjectRootElement> projects)
|
||||
{
|
||||
foreach (var project in projects)
|
||||
{
|
||||
await UpdateProjectToNet80(project);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task UpdateProjectToNet80(ProjectRootElement project)
|
||||
{
|
||||
var targetFramework = project
|
||||
.PropertyGroups.SelectMany(x => x.Properties)
|
||||
.FirstOrDefault(x => x.Name == "TargetFramework");
|
||||
if (targetFramework?.Value is "net9.0" or "net8.0" or "net7.0" or "net6.0" or "net5.0")
|
||||
{
|
||||
if (targetFramework.Value is not "net9.0")
|
||||
{
|
||||
targetFramework.Value = "net9.0";
|
||||
project.Save();
|
||||
FormatCsproj.FormatCsprojFile(project.FullPath);
|
||||
}
|
||||
await UpdatePackagesToLatest(project);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task UpdatePackagesToLatest(ProjectRootElement project)
|
||||
{
|
||||
try
|
||||
{
|
||||
var packages = project
|
||||
.Items.Where(x =>
|
||||
x.ItemType == "PackageReference"
|
||||
&& x.Metadata.Any(s => s.Name == "Version")
|
||||
&& x.Include.StartsWith("Microsoft.")
|
||||
)
|
||||
.ToList();
|
||||
|
||||
var packageNameAndVersion = packages
|
||||
.Select(x => new
|
||||
{
|
||||
Package = x,
|
||||
Name = x.Include,
|
||||
NugetVersion = NuGetVersion.Parse(x.Metadata.First(s => s.Name == "Version").Value)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var shouldSave = false;
|
||||
foreach (var package in packageNameAndVersion)
|
||||
{
|
||||
var latestNugetVersion = await NugetLookup.FetchPackageMetadataAsync(
|
||||
package.Name,
|
||||
package.NugetVersion.IsPrerelease
|
||||
);
|
||||
|
||||
if (latestNugetVersion > package.NugetVersion)
|
||||
{
|
||||
shouldSave = true;
|
||||
package.Package.Metadata.First(s => s.Name == "Version").Value = latestNugetVersion.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSave)
|
||||
{
|
||||
project.Save();
|
||||
FormatCsproj.FormatCsprojFile(project.FullPath);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/DotNetSolutionTools.Core/FormatCsproj.cs
Normal file
36
src/DotNetSolutionTools.Core/FormatCsproj.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public static class FormatCsproj
|
||||
{
|
||||
public static void FormatCsprojFile(string csprojFilePath)
|
||||
{
|
||||
using TextReader rd = new StreamReader(csprojFilePath, Encoding.Default);
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(rd);
|
||||
|
||||
if (rd != Console.In)
|
||||
{
|
||||
rd.Close();
|
||||
}
|
||||
|
||||
using var wr = new StreamWriter(csprojFilePath, false, Encoding.Default);
|
||||
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
IndentChars = "\t",
|
||||
NewLineOnAttributes = false,
|
||||
OmitXmlDeclaration = true
|
||||
};
|
||||
|
||||
using (var writer = XmlWriter.Create(wr, settings))
|
||||
{
|
||||
doc.WriteContentTo(writer);
|
||||
writer.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/DotNetSolutionTools.Core/GenerateSln.cs
Normal file
89
src/DotNetSolutionTools.Core/GenerateSln.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Build.Construction;
|
||||
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public static class GenerateSln
|
||||
{
|
||||
private const string BasePath = @"C:\Users\Matthew\Documents\Git\folder";
|
||||
|
||||
public static async Task GenerateSlnWithDependenciesFromProjects(List<string> csprojPathList)
|
||||
{
|
||||
csprojPathList = [$"""{BasePath}/src/project1/project1.csproj"""];
|
||||
// Get csproj objects
|
||||
var projectObjects = csprojPathList.Select(ProjectRootElement.Open).ToList();
|
||||
|
||||
// Initialize dependencies with the root projects
|
||||
var allDependencies = new HashSet<string>(csprojPathList);
|
||||
|
||||
// Recursively find all dependencies
|
||||
FindAllDependenciesAsync(projectObjects, allDependencies);
|
||||
|
||||
// At this point, allDependencies contains paths to all projects and their dependencies
|
||||
// Further processing to generate the solution file can be done here
|
||||
Console.WriteLine(allDependencies.Count);
|
||||
|
||||
// delete the solution file if it exists
|
||||
if (File.Exists(BasePath + "\\Global.sln"))
|
||||
{
|
||||
File.Delete(BasePath + "\\Global.sln");
|
||||
}
|
||||
|
||||
// powershell
|
||||
await RunPowerShellCommandAsync($"dotnet new sln -n Global -o {BasePath}");
|
||||
|
||||
foreach (var project in allDependencies)
|
||||
{
|
||||
await RunPowerShellCommandAsync($"dotnet sln {BasePath}/Global.sln add {project}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void FindAllDependenciesAsync(List<ProjectRootElement> projects, HashSet<string> allDependencies)
|
||||
{
|
||||
var newDependencies = new List<ProjectRootElement>();
|
||||
|
||||
foreach (var project in projects)
|
||||
{
|
||||
var dependencyPaths = project
|
||||
.Items.Where(x => x.ItemType == "ProjectReference")
|
||||
.Select(s => s.Include)
|
||||
.ToList();
|
||||
|
||||
// projectrootelement can't handle paths with ../ so we need to remove them, but resolve them correctly
|
||||
dependencyPaths = dependencyPaths.Select(x => Path.GetFullPath(x, project.DirectoryPath)).ToList();
|
||||
|
||||
foreach (var path in dependencyPaths)
|
||||
{
|
||||
// Add to the HashSet to prevent duplicates and check if the path was already processed
|
||||
if (allDependencies.Add(path))
|
||||
{
|
||||
newDependencies.Add(ProjectRootElement.Open(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newDependencies.Any())
|
||||
{
|
||||
// Recursively process new dependencies found
|
||||
FindAllDependenciesAsync(newDependencies, allDependencies);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task RunPowerShellCommandAsync(string command)
|
||||
{
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell",
|
||||
Arguments = command,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
await process.WaitForExitAsync();
|
||||
Console.WriteLine(await process.StandardOutput.ReadToEndAsync());
|
||||
}
|
||||
}
|
||||
71
src/DotNetSolutionTools.Core/ImplicitUsings.cs
Normal file
71
src/DotNetSolutionTools.Core/ImplicitUsings.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using Microsoft.Build.Construction;
|
||||
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public static class ImplicitUsings
|
||||
{
|
||||
public static List<string> FindCSharpProjectsMissingImplicitUsings(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SolutionFile.Parse(solutionFilePath);
|
||||
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
var projectsMissingImplicitUsings = FindCSharpProjectsMissingImplicitUsings(csprojList);
|
||||
var projectsMissingImplicitUsingsStringList = projectsMissingImplicitUsings.Select(x => x.FullPath).ToList();
|
||||
|
||||
return projectsMissingImplicitUsingsStringList;
|
||||
}
|
||||
|
||||
public static List<ProjectRootElement> FindCSharpProjectsMissingImplicitUsings(List<ProjectRootElement> projectList)
|
||||
{
|
||||
var projectsMissingImplicitUsings = new List<ProjectRootElement>();
|
||||
|
||||
foreach (var project in projectList)
|
||||
{
|
||||
var implicitUsings = project
|
||||
.PropertyGroups.SelectMany(x => x.Properties)
|
||||
.FirstOrDefault(x => x.Name == "ImplicitUsings");
|
||||
if (implicitUsings is null || implicitUsings.Value is not "enable")
|
||||
{
|
||||
projectsMissingImplicitUsings.Add(project);
|
||||
}
|
||||
}
|
||||
|
||||
return projectsMissingImplicitUsings;
|
||||
}
|
||||
|
||||
public static void AddMissingImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
|
||||
{
|
||||
foreach (var project in projectsMissingImplicitUsings)
|
||||
{
|
||||
if (ProjectIsMissingImplicitUsings(project))
|
||||
{
|
||||
project.AddProperty("ImplicitUsings", "enable");
|
||||
project.Save();
|
||||
FormatCsproj.FormatCsprojFile(project.FullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnableDisabledImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void EnableAllImplicitUsings(List<ProjectRootElement> projectsMissingImplicitUsings)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static bool ProjectIsMissingImplicitUsings(ProjectRootElement project)
|
||||
{
|
||||
var implicitUsings = project
|
||||
.PropertyGroups.SelectMany(x => x.Properties)
|
||||
.FirstOrDefault(x => x.Name == "ImplicitUsings");
|
||||
if (implicitUsings is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
66
src/DotNetSolutionTools.Core/Infrastructure/NugetLookup.cs
Normal file
66
src/DotNetSolutionTools.Core/Infrastructure/NugetLookup.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using NuGet.Common;
|
||||
using NuGet.Protocol;
|
||||
using NuGet.Protocol.Core.Types;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace DotNetSolutionTools.Core.Infrastructure;
|
||||
|
||||
public static class NugetLookup
|
||||
{
|
||||
public static async Task<NuGetVersion> FetchPackageMetadataAsync(string packageId, bool isCurrentlyPrerelease)
|
||||
{
|
||||
var cache = new SourceCacheContext();
|
||||
var repositories = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
|
||||
var logger = NullLogger.Instance;
|
||||
|
||||
var resource = await repositories.GetResourceAsync<FindPackageByIdResource>();
|
||||
var versions = await resource.GetAllVersionsAsync(packageId, cache, logger, CancellationToken.None);
|
||||
|
||||
var latestPrereleaseVersion = versions.LastOrDefault(s => s.IsPrerelease == true);
|
||||
var latestStableVersion = versions.LastOrDefault(s => s.IsPrerelease == false);
|
||||
|
||||
if (latestStableVersion is null && latestPrereleaseVersion is null)
|
||||
{
|
||||
Throw(packageId);
|
||||
}
|
||||
|
||||
if (latestStableVersion is null)
|
||||
{
|
||||
if (isCurrentlyPrerelease && latestPrereleaseVersion is not null)
|
||||
{
|
||||
return latestPrereleaseVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw(packageId);
|
||||
}
|
||||
}
|
||||
|
||||
if (latestPrereleaseVersion is null)
|
||||
{
|
||||
return latestStableVersion;
|
||||
}
|
||||
|
||||
if (latestStableVersion > latestPrereleaseVersion)
|
||||
{
|
||||
return latestStableVersion;
|
||||
}
|
||||
|
||||
if (isCurrentlyPrerelease)
|
||||
{
|
||||
return latestPrereleaseVersion;
|
||||
}
|
||||
|
||||
return latestStableVersion;
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void Throw(string packageId)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
"latestVersion",
|
||||
$"No latest stable or prerelease Nuget package version found for {packageId}"
|
||||
);
|
||||
}
|
||||
}
|
||||
8
src/DotNetSolutionTools.Core/Models/Project.cs
Normal file
8
src/DotNetSolutionTools.Core/Models/Project.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DotNetSolutionTools.Core.Models;
|
||||
|
||||
public class Project
|
||||
{
|
||||
public required string FullPath { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public List<Project> DependsOn { get; set; } = [];
|
||||
}
|
||||
93
src/DotNetSolutionTools.Core/PackageVersionConsistency.cs
Normal file
93
src/DotNetSolutionTools.Core/PackageVersionConsistency.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using Microsoft.Build.Construction;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public class PackageVersionConsistency
|
||||
{
|
||||
public static void ResolvePackageVersionsToLatestInstalled(string solutionFilePath)
|
||||
{
|
||||
var result = GetInconsistentNugetPackageVersions(solutionFilePath);
|
||||
var inconsistentPackages = result
|
||||
.GroupBy(s => s.packageName)
|
||||
.Select(s =>
|
||||
(s.Key, s.Select(x => (x.version, x.metadataElement)).OrderByDescending(x => x.version).ToList())
|
||||
)
|
||||
.ToList();
|
||||
|
||||
foreach (var package in inconsistentPackages)
|
||||
{
|
||||
var latestInstalledVersion = package.Item2.First().version;
|
||||
foreach (var (version, metadataElement) in package.Item2.Skip(1))
|
||||
{
|
||||
metadataElement.Value = latestInstalledVersion.ToString();
|
||||
metadataElement.ContainingProject.Save();
|
||||
FormatCsproj.FormatCsprojFile(metadataElement.ContainingProject.FullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> FindInconsistentNugetPackageVersions(string solutionFilePath)
|
||||
{
|
||||
var result = GetInconsistentNugetPackageVersions(solutionFilePath);
|
||||
return result.Select(s => s.packageName).Distinct().ToList();
|
||||
}
|
||||
|
||||
private static List<(
|
||||
string packageName,
|
||||
NuGetVersion version,
|
||||
ProjectMetadataElement metadataElement
|
||||
)> GetInconsistentNugetPackageVersions(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SolutionFile.Parse(solutionFilePath);
|
||||
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
var packageList = new List<(string, NuGetVersion, ProjectMetadataElement)>();
|
||||
var outOfSyncPackages = new List<(string packageName, NuGetVersion version, ProjectMetadataElement element)>();
|
||||
|
||||
foreach (var project in csprojList)
|
||||
{
|
||||
var projectPackages = GetPackageVersions(project);
|
||||
packageList.AddRange(projectPackages);
|
||||
}
|
||||
|
||||
var groupedByPackage = packageList.GroupBy(s => s.Item1).ToList();
|
||||
foreach (var grouping in groupedByPackage)
|
||||
{
|
||||
var versions = grouping.Select(s => (s.Item2, s.Item3)).DistinctBy(s => s.Item1).ToList();
|
||||
if (versions.Count > 1)
|
||||
{
|
||||
//outOfSyncPackages.AddRange(versions.Select(s => (grouping.Key, s.Item1, s.Item2)));
|
||||
outOfSyncPackages.AddRange(grouping.Select(s => (s.Item1, s.Item2, s.Item3)));
|
||||
}
|
||||
}
|
||||
|
||||
return outOfSyncPackages.ToList();
|
||||
}
|
||||
|
||||
private static List<(string, NuGetVersion, ProjectMetadataElement)> GetPackageVersions(ProjectRootElement project)
|
||||
{
|
||||
try
|
||||
{
|
||||
var packages = project
|
||||
.Items.Where(x => x.ItemType == "PackageReference" && x.Metadata.Any(s => s.Name == "Version"))
|
||||
.ToList();
|
||||
|
||||
var packageNameAndVersion = packages
|
||||
.Select(x =>
|
||||
(
|
||||
x.Include,
|
||||
NuGetVersion.Parse(x.Metadata.First(s => s.Name == "Version").Value),
|
||||
x.Metadata.First(s => s.Name == "Version")
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
return packageNameAndVersion;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/DotNetSolutionTools.Core/SolutionBuildOrder.cs
Normal file
52
src/DotNetSolutionTools.Core/SolutionBuildOrder.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using DotNetSolutionTools.Core.Models;
|
||||
using Microsoft.Build.Construction;
|
||||
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public static class SolutionBuildOrder
|
||||
{
|
||||
public static List<ProjectRootElement> Projects { get; set; } = [];
|
||||
|
||||
public static List<Project> GetBuildOrder(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SlnHelper.ParseSolutionFileFromPath(solutionFilePath);
|
||||
ArgumentNullException.ThrowIfNull(solutionFile);
|
||||
var projects = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
Projects = projects;
|
||||
|
||||
List<Project> projects2 = [];
|
||||
foreach (var project in projects)
|
||||
{
|
||||
var projectName = Path.GetFileNameWithoutExtension(project.FullPath);
|
||||
var project2 = new Project { FullPath = project.FullPath, Name = projectName };
|
||||
project2.DependsOn = GetDependencies(project2);
|
||||
projects2.Add(project2);
|
||||
}
|
||||
|
||||
return projects2;
|
||||
}
|
||||
|
||||
public static List<Project> GetDependencies(Project project)
|
||||
{
|
||||
var projectReferences = Projects
|
||||
.Single(s => s.FullPath == project.FullPath)
|
||||
.AllChildren.OfType<ProjectItemElement>()
|
||||
.Where(x => x.ElementName == "ProjectReference")
|
||||
.ToList();
|
||||
|
||||
List<Project> dependencies = [];
|
||||
foreach (var projectReference in projectReferences)
|
||||
{
|
||||
var fullPath = Path.Combine(Path.GetDirectoryName(project.FullPath)!, projectReference.Include);
|
||||
fullPath = Path.GetFullPath(fullPath);
|
||||
var dependency = Projects.Single(s => s.FullPath == fullPath);
|
||||
var projectName = Path.GetFileNameWithoutExtension(dependency.FullPath);
|
||||
var subProject = new Project { FullPath = dependency.FullPath, Name = projectName };
|
||||
subProject.DependsOn = GetDependencies(subProject);
|
||||
dependencies.Add(subProject);
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
}
|
||||
43
src/DotNetSolutionTools.Core/SolutionProjectParity.cs
Normal file
43
src/DotNetSolutionTools.Core/SolutionProjectParity.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using Microsoft.Build.Construction;
|
||||
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public static class SolutionProjectParity
|
||||
{
|
||||
public static List<string> CompareSolutionAndCSharpProjects(string solutionFolderPath, string solutionFilePath)
|
||||
{
|
||||
var csprojList = CsprojHelper.RetrieveAllCSharpProjectFullPathsFromFolder(solutionFolderPath);
|
||||
var solutionFile = SlnHelper.ParseSolutionFileFromPath(solutionFilePath);
|
||||
ArgumentNullException.ThrowIfNull(solutionFile);
|
||||
var projectsMissingFromSolution = FindProjectsMissingFromSolution(csprojList, solutionFile);
|
||||
|
||||
return projectsMissingFromSolution;
|
||||
}
|
||||
|
||||
public static List<string> FindProjectsMissingFromSolution(string[] csprojList, SolutionFile solutionFile)
|
||||
{
|
||||
var projects = solutionFile.ProjectsInOrder;
|
||||
var projectsMissingFromSolution = new List<string>();
|
||||
|
||||
foreach (var project in csprojList)
|
||||
{
|
||||
var projectInSolution = projects.FirstOrDefault(x =>
|
||||
NormalizePath(x.AbsolutePath) == NormalizePath(project)
|
||||
);
|
||||
|
||||
if (projectInSolution == null)
|
||||
{
|
||||
projectsMissingFromSolution.Add(project);
|
||||
}
|
||||
}
|
||||
|
||||
return projectsMissingFromSolution;
|
||||
}
|
||||
|
||||
private static string NormalizePath(string path)
|
||||
{
|
||||
return Path.GetFullPath(new Uri(path).LocalPath)
|
||||
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
}
|
||||
}
|
||||
103
src/DotNetSolutionTools.Core/WarningsAsErrors.cs
Normal file
103
src/DotNetSolutionTools.Core/WarningsAsErrors.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using DotNetSolutionTools.Core.Common;
|
||||
using Microsoft.Build.Construction;
|
||||
|
||||
namespace DotNetSolutionTools.Core;
|
||||
|
||||
public static class WarningsAsErrors
|
||||
{
|
||||
public static List<string> FindCSharpProjectsMissingTreatWarningsAsErrors(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SolutionFile.Parse(solutionFilePath);
|
||||
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
var projectsMissingImplicitUsings = FindCSharpProjectsMissingTreatWarningsAsErrors(csprojList);
|
||||
var projectsMissingImplicitUsingsStringList = projectsMissingImplicitUsings.Select(x => x.FullPath).ToList();
|
||||
|
||||
return projectsMissingImplicitUsingsStringList;
|
||||
}
|
||||
|
||||
public static List<ProjectRootElement> FindCSharpProjectsMissingTreatWarningsAsErrors(
|
||||
List<ProjectRootElement> projectList
|
||||
)
|
||||
{
|
||||
var projectsMissingTreatWarningsAsErrors = new List<ProjectRootElement>();
|
||||
|
||||
foreach (var project in projectList)
|
||||
{
|
||||
var treatWarningsAsErrors = project
|
||||
.PropertyGroups.SelectMany(x => x.Properties)
|
||||
.FirstOrDefault(x => x.Name == "TreatWarningsAsErrors");
|
||||
if (treatWarningsAsErrors is null || treatWarningsAsErrors.Value is not "true")
|
||||
{
|
||||
projectsMissingTreatWarningsAsErrors.Add(project);
|
||||
}
|
||||
}
|
||||
|
||||
return projectsMissingTreatWarningsAsErrors;
|
||||
}
|
||||
|
||||
public static void AddMissingTreatWarningsAsErrorsToSolution(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SolutionFile.Parse(solutionFilePath);
|
||||
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
var projectsMissingImplicitUsings = FindCSharpProjectsMissingTreatWarningsAsErrors(csprojList);
|
||||
AddMissingTreatWarningsAsErrors(projectsMissingImplicitUsings);
|
||||
}
|
||||
|
||||
public static void RemoveAllTreatWarningsAsErrorsInSolution(string solutionFilePath)
|
||||
{
|
||||
var solutionFile = SolutionFile.Parse(solutionFilePath);
|
||||
var csprojList = SlnHelper.GetCSharpProjectObjectsFromSolutionFile(solutionFile);
|
||||
RemoveTreatWarningsAsErrors(csprojList);
|
||||
}
|
||||
|
||||
public static void AddMissingTreatWarningsAsErrors(List<ProjectRootElement> projectsMissingImplicitUsings)
|
||||
{
|
||||
foreach (var project in projectsMissingImplicitUsings)
|
||||
{
|
||||
if (ProjectIsMissingTreatWarningsAsErrors(project))
|
||||
{
|
||||
project.AddProperty("TreatWarningsAsErrors", "true");
|
||||
project.Save();
|
||||
FormatCsproj.FormatCsprojFile(project.FullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveTreatWarningsAsErrors(List<ProjectRootElement> projectList)
|
||||
{
|
||||
foreach (var project in projectList)
|
||||
{
|
||||
var treatWarningsAsErrors = project
|
||||
.PropertyGroups.SelectMany(x => x.Properties)
|
||||
.FirstOrDefault(x => x.Name == "TreatWarningsAsErrors");
|
||||
if (treatWarningsAsErrors is not null)
|
||||
{
|
||||
treatWarningsAsErrors.Parent.RemoveChild(treatWarningsAsErrors);
|
||||
project.Save();
|
||||
FormatCsproj.FormatCsprojFile(project.FullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ProjectIsMissingTreatWarningsAsErrors(ProjectRootElement project)
|
||||
{
|
||||
var implicitUsings = project
|
||||
.PropertyGroups.SelectMany(x => x.Properties)
|
||||
.FirstOrDefault(x => x.Name == "TreatWarningsAsErrors");
|
||||
if (implicitUsings is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ProjectBuildSuccessfully(ProjectRootElement project)
|
||||
{
|
||||
// build the project
|
||||
var buildProject = new Microsoft.Build.Evaluation.Project(project);
|
||||
// retrieve warnings
|
||||
var buildResult = buildProject.Build();
|
||||
return buildResult;
|
||||
}
|
||||
}
|
||||
17
src/DotNetSolutionTools.Photino/App.razor
Normal file
17
src/DotNetSolutionTools.Photino/App.razor
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
<Router AppAssembly="@typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
||||
@code
|
||||
{
|
||||
|
||||
}
|
||||
15
src/DotNetSolutionTools.Photino/AppThemeProvider.cs
Normal file
15
src/DotNetSolutionTools.Photino/AppThemeProvider.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using MudBlazor;
|
||||
|
||||
namespace DotNetSolutionTools.Photino;
|
||||
|
||||
public static class AppThemeProvider
|
||||
{
|
||||
public static MudTheme GetTheme()
|
||||
{
|
||||
var theme = new MudTheme();
|
||||
theme.Typography.H5.FontSize = "1.4rem";
|
||||
theme.Typography.H5.FontWeight = "500";
|
||||
theme.Typography.H6.FontSize = "1.1rem";
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
@using Blazor.Diagrams
|
||||
@using Blazor.Diagrams.Core.Anchors
|
||||
@using Blazor.Diagrams.Core.Geometry
|
||||
@using Blazor.Diagrams.Core.Models
|
||||
@using DotNetSolutionTools.Core.Models
|
||||
|
||||
<h3>BuildOrderDiagram</h3>
|
||||
<div style="width: 100%; height: 400px; border: black">
|
||||
<CascadingValue Value="Diagram" IsFixed="true">
|
||||
<DiagramCanvas />
|
||||
</CascadingValue>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired]
|
||||
public List<Project> Projects { get; set; }
|
||||
|
||||
private BlazorDiagram Diagram { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Diagram = new BlazorDiagram();
|
||||
foreach (var project in Projects)
|
||||
{
|
||||
var node = Diagram.Nodes.Add(new NodeModel()
|
||||
{
|
||||
Title = project.Name,
|
||||
|
||||
});
|
||||
foreach (var dependency in project.DependsOn)
|
||||
{
|
||||
var dependencyNode = Diagram.Nodes.Add(new NodeModel()
|
||||
{
|
||||
Title = dependency.Name
|
||||
});
|
||||
Diagram.Links.Add(new LinkModel(node, dependencyNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void Example()
|
||||
{
|
||||
Diagram = new BlazorDiagram();
|
||||
var firstNode = Diagram.Nodes.Add(new NodeModel(position: new Point(50, 50))
|
||||
{
|
||||
Title = "Node 1"
|
||||
});
|
||||
var secondNode = Diagram.Nodes.Add(new NodeModel(position: new Point(200, 100))
|
||||
{
|
||||
Title = "Node 2"
|
||||
});
|
||||
var leftPort = secondNode.AddPort(PortAlignment.Left);
|
||||
var rightPort = secondNode.AddPort(PortAlignment.Right);
|
||||
// The connection point will be the intersection of
|
||||
// a line going from the target to the center of the source
|
||||
var sourceAnchor = new ShapeIntersectionAnchor(firstNode);
|
||||
// The connection point will be the port's position
|
||||
var targetAnchor = new SinglePortAnchor(leftPort);
|
||||
var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType> <!-- Doesn't actually mean Windows Exe, 'Exe' = console entrypoint, 'WinExe' = application entry point -->
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>false</SelfContained>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SupportedPlatform Include="browser" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ardalis.GuardClauses" Version="5.0.0" />
|
||||
<PackageReference Include="MudBlazor" Version="8.6.0" />
|
||||
<PackageReference Include="Photino.Blazor" Version="4.0.13" />
|
||||
<PackageReference Include="Z.Blazor.Diagrams" Version="3.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="favicon.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotNetSolutionTools.Core\DotNetSolutionTools.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RemoveConflictingAssets" BeforeTargets="ResolveStaticWebAssetsConfiguration">
|
||||
<ItemGroup>
|
||||
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(Extension)' == '.gz'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
34
src/DotNetSolutionTools.Photino/Layout/MainLayout.razor
Normal file
34
src/DotNetSolutionTools.Photino/Layout/MainLayout.razor
Normal file
@@ -0,0 +1,34 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudThemeProvider Theme="@AppThemeProvider.GetTheme()" />
|
||||
<MudPopoverProvider/>
|
||||
<MudDialogProvider/>
|
||||
<MudSnackbarProvider/>
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
|
||||
</MudAppBar>
|
||||
<MudDrawer @bind-Open="@_drawerOpen">
|
||||
<MudDrawerHeader>
|
||||
<MudStack Row="false" Justify="Justify.Center">
|
||||
<MudText Typo="Typo.h5">DotNetSolutionTools.Photino</MudText>
|
||||
</MudStack>
|
||||
</MudDrawerHeader>
|
||||
<NavMenu/>
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
<MudContainer Class="px-8 py-6">
|
||||
@Body
|
||||
</MudContainer>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
|
||||
@code {
|
||||
bool _drawerOpen = true;
|
||||
|
||||
void DrawerToggle()
|
||||
{
|
||||
_drawerOpen = !_drawerOpen;
|
||||
}
|
||||
}
|
||||
3
src/DotNetSolutionTools.Photino/Layout/NavMenu.razor
Normal file
3
src/DotNetSolutionTools.Photino/Layout/NavMenu.razor
Normal file
@@ -0,0 +1,3 @@
|
||||
<MudNavMenu>
|
||||
<MudNavLink Href="/" Icon="@Icons.Material.Filled.Home" Match="NavLinkMatch.All">Home</MudNavLink>
|
||||
</MudNavMenu>
|
||||
8
src/DotNetSolutionTools.Photino/Models/AppState.cs
Normal file
8
src/DotNetSolutionTools.Photino/Models/AppState.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DotNetSolutionTools.Photino.Models;
|
||||
|
||||
public class AppState
|
||||
{
|
||||
public required string? SolutionFolderPath { get; set; }
|
||||
public required string? SolutionFilePath { get; set; }
|
||||
public required string? CsprojFilePath { get; set; }
|
||||
}
|
||||
42
src/DotNetSolutionTools.Photino/Pages/Home.razor
Normal file
42
src/DotNetSolutionTools.Photino/Pages/Home.razor
Normal file
@@ -0,0 +1,42 @@
|
||||
@page "/"
|
||||
@using Ardalis.GuardClauses
|
||||
@using DotNetSolutionTools.Core
|
||||
@using DotNetSolutionTools.Core.Models
|
||||
@inject AppState AppState
|
||||
|
||||
<MudStack>
|
||||
<MudInput T="string" Label="Solution File Path" @bind-Value="_solutionFilePath" @bind-Value:after="SetToAppState" />
|
||||
<MudButton OnClick="@Populate">Populate project dependencies</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@if (_projects.Count > 0)
|
||||
{
|
||||
<BuildOrderDiagram Projects="_projects"/>
|
||||
}
|
||||
@foreach(var project in _projects)
|
||||
{
|
||||
<MudText>@project.Name</MudText>
|
||||
}
|
||||
|
||||
@code {
|
||||
private string? _solutionFilePath;
|
||||
private List<Project> _projects = [];
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_solutionFilePath = AppState.SolutionFilePath;
|
||||
}
|
||||
|
||||
private void Populate()
|
||||
{
|
||||
Guard.Against.Null(_solutionFilePath);
|
||||
var result = SolutionBuildOrder.GetBuildOrder(_solutionFilePath);
|
||||
_projects = result;
|
||||
}
|
||||
|
||||
private void SetToAppState()
|
||||
{
|
||||
AppState.SolutionFilePath = _solutionFilePath;
|
||||
}
|
||||
|
||||
}
|
||||
91
src/DotNetSolutionTools.Photino/Program.cs
Normal file
91
src/DotNetSolutionTools.Photino/Program.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System.Text.Json;
|
||||
using DotNetSolutionTools.Photino.Models;
|
||||
using Microsoft.Build.Locator;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MudBlazor.Services;
|
||||
using Photino.Blazor;
|
||||
|
||||
namespace DotNetSolutionTools.Photino;
|
||||
|
||||
public class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var appBuilder = PhotinoBlazorAppBuilder.CreateDefault(args);
|
||||
|
||||
appBuilder.Services.AddLogging();
|
||||
appBuilder.Services.AddMudServices();
|
||||
appBuilder.Services.AddSingleton<AppState>();
|
||||
|
||||
appBuilder.RootComponents.Add<App>("app");
|
||||
|
||||
var app = appBuilder.Build();
|
||||
|
||||
app.MainWindow.SetSize(1400, 800)
|
||||
.SetDevToolsEnabled(true)
|
||||
.SetLogVerbosity(0)
|
||||
//.SetIconFile("favicon.ico")
|
||||
.SetTitle("DotNetSolutionTools.Photino");
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
|
||||
{
|
||||
app.MainWindow.ShowMessage("Fatal exception", error.ExceptionObject.ToString());
|
||||
};
|
||||
|
||||
var instance = MSBuildLocator
|
||||
.QueryVisualStudioInstances()
|
||||
.OrderByDescending(instance => instance.Version)
|
||||
.First();
|
||||
MSBuildLocator.RegisterInstance(instance);
|
||||
|
||||
var configFilePath = GetConfigFilePath();
|
||||
|
||||
using var scope = app.Services.CreateScope();
|
||||
var appState = scope.ServiceProvider.GetRequiredService<AppState>();
|
||||
|
||||
LoadAppStateFromConfigFile(appState, configFilePath);
|
||||
|
||||
app.MainWindow.RegisterWindowClosingHandler(
|
||||
(sender, eventArgs) =>
|
||||
{
|
||||
using var stream = File.Create(configFilePath);
|
||||
JsonSerializer.Serialize(stream, appState);
|
||||
stream.Flush();
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
app.Run();
|
||||
}
|
||||
|
||||
private static string GetConfigFilePath()
|
||||
{
|
||||
var folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var configFolder = Path.Combine(folder, "DotNetSolutionTools.Photino");
|
||||
Directory.CreateDirectory(configFolder);
|
||||
var configFilePath = Path.Combine(configFolder, "config.json");
|
||||
return configFilePath;
|
||||
}
|
||||
|
||||
private static void LoadAppStateFromConfigFile(AppState appState, string configFilePath)
|
||||
{
|
||||
if (File.Exists(configFilePath) is false)
|
||||
{
|
||||
File.WriteAllText(configFilePath, string.Empty);
|
||||
}
|
||||
|
||||
using var stream = File.OpenRead(configFilePath);
|
||||
if (stream.Length is 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var deserializedAppState = JsonSerializer.Deserialize<AppState>(stream);
|
||||
if (deserializedAppState is not null)
|
||||
{
|
||||
appState.SolutionFolderPath = deserializedAppState.SolutionFolderPath;
|
||||
appState.SolutionFilePath = deserializedAppState.SolutionFilePath;
|
||||
appState.CsprojFilePath = deserializedAppState.CsprojFilePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"Run": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"environmentVariables": {
|
||||
"DOTNET_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"(Watch)": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "dotnet",
|
||||
"workingDirectory": "$(ProjectDir)",
|
||||
"commandLineArgs": "watch run",
|
||||
"environmentVariables": {
|
||||
"DOTNET_ENVIRONMENT": "Development",
|
||||
"DOTNET_WATCH_RESTART_ON_RUDE_EDIT": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/DotNetSolutionTools.Photino/_Imports.razor
Normal file
15
src/DotNetSolutionTools.Photino/_Imports.razor
Normal file
@@ -0,0 +1,15 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using MudBlazor
|
||||
@using DotNetSolutionTools.Photino.Layout
|
||||
@using DotNetSolutionTools.Photino
|
||||
@using DotNetSolutionTools.Photino.Models
|
||||
@using DotNetSolutionTools.Photino.Components
|
||||
@using Blazor.Diagrams.Components
|
||||
BIN
src/DotNetSolutionTools.Photino/favicon.ico
Normal file
BIN
src/DotNetSolutionTools.Photino/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 KiB |
1
src/DotNetSolutionTools.Photino/wwwroot/css/app.css
Normal file
1
src/DotNetSolutionTools.Photino/wwwroot/css/app.css
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
28
src/DotNetSolutionTools.Photino/wwwroot/index.html
Normal file
28
src/DotNetSolutionTools.Photino/wwwroot/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<base href="/" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link href="css/app.css" rel="stylesheet" />
|
||||
<link href="DotNetSolutionTools.Photino.styles.css" rel="stylesheet" />
|
||||
|
||||
<link href="_content/Z.Blazor.Diagrams/style.min.css" rel="stylesheet" />
|
||||
<link href="_content/Z.Blazor.Diagrams/default.styles.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<app>Loading...</app>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="_framework/blazor.webview.js"></script>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="_content/Z.Blazor.Diagrams/script.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user