├── .github
├── FUNDING.yml
└── workflows
│ └── nuget-publish.yaml
├── .gitignore
├── LICENSE
├── README.md
├── appveyor.yml
└── src
├── DotNetSortRefs.sln
└── DotNetSortRefs
├── CommandBase.cs
├── DotNetSortRefs.csproj
├── Program.cs
└── Sort.xsl
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | ko_fi: 'babuannamalai'
3 | custom: ['https://www.buymeacoffee.com/babuannamalai']
4 |
--------------------------------------------------------------------------------
/.github/workflows/nuget-publish.yaml:
--------------------------------------------------------------------------------
1 | name: NuGet Manual Publish
2 |
3 | on: [workflow_dispatch]
4 |
5 | env:
6 | config: Release
7 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
8 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
9 |
10 | jobs:
11 | publish_job:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Checkout code
16 | uses: actions/checkout@v4
17 |
18 | - name: Install .NET 8.0.x
19 | uses: actions/setup-dotnet@v4
20 | with:
21 | dotnet-version: 9.0.x
22 |
23 | - name: Run Pack
24 | run: dotnet pack src/DotNetSortRefs/DotNetSortRefs.csproj -c Release
25 | shell: bash
26 |
27 | - name: Publish to NuGet
28 | run: |
29 | find . -name '*.nupkg' -exec dotnet nuget push "{}" -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} --skip-duplicate \;
30 | # find . -name '*.snupkg' -exec dotnet nuget push "{}" -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} \;
31 | shell: bash
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | obj
3 | bin
4 | bin/*
5 | deploy
6 | deploy/*
7 | _ReSharper.*
8 | *.user
9 | *.suo
10 | *.cache
11 | *.Cache
12 | Thumbs.db
13 | **/packages
14 | .idea
15 | nupkg
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Babu Annamalai
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dotnet-sort-refs
2 | [](https://ci.appveyor.com/project/BabuAnnamalai/dotnet-sort-refs/branch/master) [](https://www.nuget.org/packages/dotnet-sort-refs/)
3 |
4 | A .NET Core global tool to alphabetically sort package references in your .NET Core and .NET Standard projects.
5 |
6 | If you have benefitted from this library and has saved you a bunch of time, please feel free to buy me a coffee!
7 |
8 |
9 | ## Why use this tool?
10 | References and package references in a project file are the most updated parts. Sorting the references helps with the following:
11 | - Easier merges on source control (git). Without sorting the package references in the project file, you may end up with more merge conflicts to fix.
12 | - It will be easier to go through the list of package references if you manually edit the file or view changes using a diff tool.
13 |
14 | ## Installation
15 | ```bash
16 | dotnet tool install --global dotnet-sort-refs
17 | ```
18 |
19 | ## Usage
20 | ```text
21 | dotnet sort-refs [arguments] [options]
22 |
23 | Arguments:
24 | Path The path to a .csproj, .fsproj or directory. If a directory is specified, all .csproj and .fsproj files within folder tree will be processed. If none specified, it will use the current directory.
25 |
26 | Options:
27 | --version Show version information
28 | -?|-h|--help Show help information
29 | -i|--inspect Specifies whether to inspect and return a non-zero exit code if one or more projects have non-sorted package references.
30 | ```
31 |
32 | Note: `dotnet sort-refs` and `dotnet-sort-refs` are valid usages to run the tool.
33 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: build-{build}
2 |
3 | configuration: Release
4 |
5 | image: Ubuntu2204
6 |
7 | environment:
8 | DOTNET_CLI_TELEMETRY_OPTOUT: true
9 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
10 | NUGET_DEPLOY_KEY:
11 | secure: bfIsyEMzYM9AJCFQwxLjAC48zi8/pNgUokbWcEcEo4hzLvrGCKEN6pNYkQIx/w2J
12 |
13 | nuget:
14 | disable_publish_on_pr: true
15 |
16 | before_build:
17 | - dotnet --info
18 | - dotnet restore .\src\DotNetSortRefs.sln
19 |
20 | build_script:
21 | - dotnet build .\src\DotNetSortRefs.sln --configuration Release
22 |
23 | # after_test:
24 | # - dotnet pack .\src\DotNetSortRefs --configuration Release
25 |
26 | # artifacts:
27 | # - path: '**\dotnet-sort-refs.*.*nupkg' # find all NuGet packages recursively
28 |
29 | # deploy:
30 | # provider: NuGet
31 | # api_key: $(NUGET_DEPLOY_KEY)
32 | # artifact: /.*\.nupkg/
33 | # on:
34 | # APPVEYOR_REPO_TAG: true
35 |
36 |
--------------------------------------------------------------------------------
/src/DotNetSortRefs.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.136
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetSortRefs", "DotNetSortRefs\DotNetSortRefs.csproj", "{18F8C228-6832-4F8F-B629-79A368B23339}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {18F8C228-6832-4F8F-B629-79A368B23339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {18F8C228-6832-4F8F-B629-79A368B23339}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {18F8C228-6832-4F8F-B629-79A368B23339}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {18F8C228-6832-4F8F-B629-79A368B23339}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {08399582-339B-4DBF-847C-2603C9AE098C}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/src/DotNetSortRefs/CommandBase.cs:
--------------------------------------------------------------------------------
1 | using McMaster.Extensions.CommandLineUtils;
2 |
3 | namespace DotNetSortRefs
4 | {
5 | [HelpOption]
6 | internal abstract class CommandBase
7 | {
8 | }
9 | }
--------------------------------------------------------------------------------
/src/DotNetSortRefs/DotNetSortRefs.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | A .NET Core global tool to alphabetically sort package references in csproj or fsproj
4 | 2.1.0
5 | Babu Annamalai
6 | Exe
7 | netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0
8 | dotnet-sort-refs
9 | dotnet-sort-refs
10 | ./nupkg
11 | https://github.com/mysticmind/dotnet-sort-refs
12 | MIT
13 | true
14 | true
15 | dotnet-sort-refs
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/DotNetSortRefs/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Abstractions;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Threading.Tasks;
8 | using System.Xml;
9 | using System.Xml.Linq;
10 | using System.Xml.XPath;
11 | using System.Xml.Xsl;
12 | using McMaster.Extensions.CommandLineUtils;
13 | using Microsoft.Extensions.DependencyInjection;
14 |
15 | namespace DotNetSortRefs
16 | {
17 | [Command(
18 | Name = "dotnet sort-refs",
19 | FullName = "A .NET Core global tool to alphabetically sort package references in csproj or fsproj.")]
20 | [VersionOptionFromMember(MemberName = nameof(GetVersion))]
21 | internal class Program : CommandBase
22 | {
23 | private readonly IFileSystem _fileSystem;
24 | private readonly IReporter _reporter;
25 |
26 | public Program(IFileSystem fileSystem, IReporter reporter)
27 | {
28 | _fileSystem = fileSystem;
29 | _reporter = reporter;
30 | }
31 |
32 | static int Main(string[] args)
33 | {
34 | using var services = new ServiceCollection()
35 | .AddSingleton(PhysicalConsole.Singleton)
36 | .AddSingleton(provider => new ConsoleReporter(provider.GetService()!))
37 | .AddSingleton()
38 | .BuildServiceProvider();
39 | var app = new CommandLineApplication
40 | {
41 | UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.Throw
42 | };
43 |
44 | app.Conventions
45 | .UseDefaultConventions()
46 | .UseConstructorInjection(services);
47 |
48 | try
49 | {
50 | return app.Execute(args);
51 | }
52 | catch (UnrecognizedCommandParsingException)
53 | {
54 | app.ShowHelp();
55 | return 1;
56 | }
57 | }
58 |
59 | [Argument(0, Description =
60 | "The path to a .csproj, .fsproj or directory. If a directory is specified, all .csproj and .fsproj files within folder tree will be processed. If none specified, it will use the current directory.")]
61 | private string Path { get; set; }
62 |
63 | [Option(CommandOptionType.NoValue, Description = "Specifies whether to inspect and return a non-zero exit code if one or more projects have non-sorted package references.",
64 | ShortName = "i", LongName = "inspect")]
65 | private bool IsInspect { get; set; } = false;
66 |
67 | private static string GetVersion() => typeof(Program)
68 | .Assembly
69 | .GetCustomAttribute()
70 | ?.InformationalVersion;
71 |
72 | private async Task OnExecute(CommandLineApplication app, IConsole console)
73 | {
74 | try
75 | {
76 | if (string.IsNullOrEmpty(Path))
77 | Path = _fileSystem.Directory.GetCurrentDirectory();
78 |
79 | if (!(_fileSystem.File.Exists(Path) || _fileSystem.Directory.Exists(Path)))
80 | {
81 | _reporter.Error("Directory or file does not exist.");
82 | return 1;
83 | }
84 |
85 | var projFiles = new List();
86 | var extensions = new[] { ".csproj", ".fsproj", ".props" };
87 |
88 | if (_fileSystem.File.Exists(Path))
89 | {
90 | projFiles.Add(Path);
91 | }
92 | else
93 | {
94 | projFiles = extensions
95 | .SelectMany(ext => _fileSystem.Directory.GetFiles(Path, $"*{ext}", SearchOption.AllDirectories))
96 | .ToList();
97 | }
98 |
99 | if (projFiles.Count == 0)
100 | {
101 | _reporter.Error($"no '{string.Join(", ", extensions)}'' files found.");
102 | return 1;
103 | }
104 |
105 | var projFilesWithNonSortedReferences = await Inspect(projFiles);
106 |
107 | if (IsInspect)
108 | {
109 | Console.WriteLine("Running inspection...");
110 | PrintInspectionResults(projFiles, projFilesWithNonSortedReferences);
111 | return projFilesWithNonSortedReferences.Count > 0 ? 1 : 0;
112 | }
113 | else
114 | {
115 | Console.WriteLine("Running sort package references...");
116 | return await SortReferences(projFilesWithNonSortedReferences);
117 | }
118 | }
119 | catch (Exception e)
120 | {
121 | _reporter.Error(e.StackTrace!);
122 | return 1;
123 | }
124 | }
125 |
126 | private static async Task> Inspect(IEnumerable projFiles)
127 | {
128 | var projFilesWithNonSortedReferences = new List();
129 |
130 | foreach (var proj in projFiles)
131 | {
132 | using (var sw = new StringWriter())
133 | {
134 | var doc = XDocument.Parse(System.IO.File.ReadAllText(proj));
135 |
136 | const string elementTypes = "PackageReference|Reference|PackageVersion";
137 | var itemGroups = doc.XPathSelectElements($"//ItemGroup[{elementTypes}]");
138 |
139 | foreach (var itemGroup in itemGroups)
140 | {
141 | var references = itemGroup.XPathSelectElements(elementTypes)
142 | .Select(x => x.Attribute("Include")?.Value.ToLowerInvariant()).ToList();
143 |
144 | if (references.Count <= 1) continue;
145 |
146 | var sortedReferences = references.OrderBy(x => x).ToList();
147 |
148 | var result = references.SequenceEqual(sortedReferences);
149 |
150 | if (!result && !projFilesWithNonSortedReferences.Contains(proj))
151 | {
152 | projFilesWithNonSortedReferences.Add(proj);
153 | }
154 | }
155 | }
156 | }
157 |
158 | return await Task.FromResult(projFilesWithNonSortedReferences);
159 | }
160 |
161 | private void PrintInspectionResults(IEnumerable projFiles,
162 | ICollection projFilesWithNonSortedReferences)
163 | {
164 | foreach (var proj in projFiles)
165 | {
166 | if (projFilesWithNonSortedReferences.Contains(proj))
167 | {
168 | _reporter.Error($"» {proj} X");
169 | }
170 | else
171 | {
172 | _reporter.Output($"» {proj} ✓");
173 | }
174 | }
175 | }
176 |
177 | private async Task SortReferences(IEnumerable projFiles)
178 | {
179 | var xslt = GetXslTransform();
180 |
181 | foreach (var proj in projFiles)
182 | {
183 | _reporter.Output($"» {proj}");
184 |
185 | await using var sw = new StringWriter();
186 | var doc = XDocument.Parse(await System.IO.File.ReadAllTextAsync(proj));
187 | xslt.Transform(doc.CreateNavigator(), null, sw);
188 | await File.WriteAllTextAsync(proj, sw.ToString());
189 | }
190 |
191 | return await Task.FromResult(0);
192 | }
193 |
194 | private static XslCompiledTransform GetXslTransform()
195 | {
196 | var assembly = Assembly.GetExecutingAssembly();
197 | using var stream = assembly.GetManifestResourceStream("DotNetSortRefs.Sort.xsl");
198 | using var reader = XmlReader.Create(stream!);
199 | var xslt = new XslCompiledTransform();
200 | xslt.Load(reader);
201 | return xslt;
202 | }
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/DotNetSortRefs/Sort.xsl:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------