├── .gitignore ├── LICENSE ├── README.md └── src ├── Extensions.fs ├── ListReferences.fs ├── Program.fs ├── ReferenceManager.fsproj ├── ReferenceManager.sln ├── ReplaceReference.fs ├── Solution.fs └── Utils.fs /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | .idea/ 7 | *.sln.iml 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gustavo Mauricio de Barros 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 | # ReferenceManager 🛠️ 2 | A F# console app to replace PackageReferences with ProjectReferences in an entire solution. 3 | 4 | ![image](https://user-images.githubusercontent.com/52143624/194674263-811512d7-00f3-4417-a701-7eb632d81cba.png) 5 | 6 | # Why? 7 | Imagine the following situation, you have several NuGet libraries and projects that depend on them. When you need to debug the NuGet library in multiple projects of a solution, you will need to manually remove and add the reference for each project. The purpose of this application is to automate this process. 8 | # Limitations 9 | - Only solutions with projects with the new SDK style are supported. (Sad for me too, because I created this project thinking about non-SDK projects 😢) 10 | - For now, only SDK-style .csprojs are supported for the ProjectReference input. If you need .vbproj and .fsproj support, you can ask me for this improvement or make a pull request 11 | # Installation 12 | 13 | ## Building from source 14 | > .NET 6 required. 15 | ``` 16 | git clone https://github.com/gumbarros/ReferenceManager . 17 | dotnet build 18 | dotnet run 19 | ``` 20 | ## Downloading and running the cross-platform binary 21 | 1. Download from the [releases](https://github.com/gumbarros/ReferenceManager/releases/tag/v1.0.0) page. 22 | 2. Run from your OS terminal. 23 | ``` 24 | ./ReferenceManager 25 | ``` 26 | 27 | If you want a single file binary, build from the source for your platform. 28 | -------------------------------------------------------------------------------- /src/Extensions.fs: -------------------------------------------------------------------------------- 1 | module ReferenceManager.Extensions 2 | 3 | open System 4 | open System.Text.RegularExpressions 5 | open Spectre.Console 6 | 7 | type String with 8 | member string.ReplaceWholeWord(find : String, replace: String) = 9 | let textToFind = String.Format(@"\b{0}\b", find) 10 | Regex.Replace(string, textToFind,replace) 11 | 12 | type AnsiConsole with 13 | static member EmptyLine = 14 | AnsiConsole.WriteLine String.Empty -------------------------------------------------------------------------------- /src/ListReferences.fs: -------------------------------------------------------------------------------- 1 | module ReferenceManager.ListReferences 2 | 3 | open System.Collections.Generic 4 | open Spectre.Console 5 | open net.r_eg.MvsSln 6 | open System.Linq 7 | open ReferenceManager.Solution 8 | open ReferenceManager.Extensions 9 | open System 10 | 11 | let promptProject (solution: SolutionData) = 12 | let selectionPrompt = 13 | SelectionPrompt() 14 | 15 | selectionPrompt.Title <- "Select a project to list the references." 16 | 17 | AnsiConsole.Prompt 18 | <| selectionPrompt.AddChoices(getSolutionProjectNames solution) 19 | 20 | let writePackageReferences (projectName: string, references: IEnumerable) = 21 | AnsiConsole.Markup $"[purple]Package References[/] in {projectName}:" 22 | AnsiConsole.EmptyLine 23 | 24 | for reference in references do 25 | AnsiConsole.Markup $"[green]-[/] {reference.evaluatedInclude}" 26 | 27 | let versionMetadata = 28 | reference.meta["Version"].evaluated 29 | 30 | match versionMetadata with 31 | | null -> AnsiConsole.MarkupLine String.Empty 32 | | _ -> AnsiConsole.MarkupLine $" {versionMetadata}" 33 | 34 | AnsiConsole.EmptyLine 35 | 36 | let listReferencesFromProject (solution: SolutionData) = 37 | let projectName = promptProject solution 38 | 39 | let selectedProject = 40 | solution.Projects.First(fun p -> p.Name = projectName) 41 | 42 | let references = 43 | selectedProject.PackageReferences 44 | 45 | writePackageReferences(selectedProject.Name,references) -------------------------------------------------------------------------------- /src/Program.fs: -------------------------------------------------------------------------------- 1 | module ReferenceManager.Program 2 | 3 | open Microsoft.Build.Locator 4 | open ReferenceManager.Utils 5 | open ReferenceManager.Solution 6 | open ReferenceManager.ListReferences 7 | open ReferenceManager.ReplaceReference 8 | 9 | MSBuildLocator.RegisterDefaults() |> ignore 10 | 11 | writeAppTitle() 12 | 13 | let rec loadSolution() = 14 | 15 | let solution : SolutionData = promptSolution() 16 | 17 | writeSolutionProjects(solution) 18 | 19 | while true do 20 | let selectedChoice = promptChoice() 21 | 22 | match selectedChoice with 23 | | 0 -> listReferencesFromProject solution 24 | | 1 -> replacePackageByProjectReference solution 25 | | 2 -> loadSolution() 26 | | _ -> exit(1) 27 | 28 | loadSolution() -------------------------------------------------------------------------------- /src/ReferenceManager.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/ReferenceManager.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ReferenceManager", "ReferenceManager.fsproj", "{26A06358-9C95-43F2-BDA8-6885FC749778}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {26A06358-9C95-43F2-BDA8-6885FC749778}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {26A06358-9C95-43F2-BDA8-6885FC749778}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {26A06358-9C95-43F2-BDA8-6885FC749778}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {26A06358-9C95-43F2-BDA8-6885FC749778}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /src/ReplaceReference.fs: -------------------------------------------------------------------------------- 1 | module ReferenceManager.ReplaceReference 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.IO 6 | open Spectre.Console 7 | open net.r_eg.MvsSln 8 | open ReferenceManager.Utils 9 | open ReferenceManager.Solution 10 | open System.Linq 11 | open net.r_eg.MvsSln.Core 12 | open net.r_eg.MvsSln.Core.ObjHandlers 13 | open net.r_eg.MvsSln.Core.SlnHandlers 14 | open ReferenceManager.Extensions 15 | open net.r_eg.MvsSln.Extensions 16 | 17 | let validateIfReferenceExists (solution: SolutionData, reference: string) = 18 | 19 | let projects = solution.Projects 20 | 21 | if projects 22 | .SelectMany(fun p -> p.PackageReferences) 23 | .Any(fun r -> r.evaluatedInclude = reference) then 24 | ValidationResult.Success() 25 | else 26 | ValidationResult.Error() 27 | 28 | let promptReference (solution: SolutionData) = 29 | 30 | let prompt = 31 | TextPrompt( 32 | "Enter the [Green]PackageReference[/] name:", 33 | ValidationErrorMessage = "PackageReference does not exist.", 34 | Validator = fun d -> validateIfReferenceExists (solution, d) 35 | ) 36 | 37 | AnsiConsole.Prompt(prompt) 38 | 39 | let getValidProjects (projects: IEnumerable, packageReferenceName: string) = 40 | projects 41 | .Where(fun p -> 42 | p 43 | .GetPackageReferences() 44 | .Any(fun r -> r.evaluatedInclude = packageReferenceName)) 45 | .DistinctBy(fun p -> p.ProjectName) 46 | 47 | let addProjectReference(project:IXProject, path: string) = 48 | project.AddItem("ProjectReference",project.ProjectFullPath.MakeRelativePath path) 49 | 50 | let setReferenceAtProjectFiles 51 | ( 52 | projects: IEnumerable, 53 | packageReferenceName: string, 54 | projectReferencePath: string 55 | ) = 56 | 57 | let validProjects = 58 | getValidProjects (projects, packageReferenceName) 59 | 60 | for project in validProjects do 61 | let isRemoved = 62 | project.RemovePackageReference packageReferenceName 63 | 64 | if isRemoved then 65 | AnsiConsole.MarkupLine "[Red]PackageReference removed.[/]" 66 | 67 | let isAdded = addProjectReference(project, projectReferencePath) 68 | 69 | if isAdded then 70 | AnsiConsole.MarkupLine "[Green]ProjectReference added.[/]" 71 | 72 | project.Save() 73 | 74 | let fixSolutionFile(path: string) = 75 | let mutable text = File.ReadAllText(path) 76 | let text = text.ReplaceWholeWord("EndProjec", "EndProject") 77 | File.WriteAllText(path, text) 78 | 79 | let saveProjectAtSolution (sln: Sln, solutionPath: string, projectName: string, projectPath: string) = 80 | let projectItem = 81 | ProjectItem(sln.Result.ProjectConfigs.First().PGuid, projectName, ProjectType.CsSdk, projectPath) 82 | 83 | let projects = 84 | sln.Result.ProjectItems.Append(projectItem) 85 | 86 | let handlers = 87 | Dictionary() 88 | 89 | handlers.Add(typedefof, HandlerValue(WProject(projects, sln.Result.ProjectDependencies))) 90 | use writer = new SlnWriter(solutionPath, handlers) 91 | 92 | writer.Write(sln.Result.Map) 93 | 94 | writer.Dispose() 95 | sln.Dispose() 96 | 97 | //Workaround MvsSln issue. 98 | fixSolutionFile(solutionPath) 99 | 100 | AnsiConsole.MarkupLine $"[Green]Added {projectName} to the solution.[/]" 101 | AnsiConsole.EmptyLine 102 | 103 | let replacePackageByProjectReference (solution: SolutionData) = 104 | let packageReferenceName: string = 105 | promptReference solution 106 | 107 | let projectReferencePath = 108 | promptPath "ProjectReference" 109 | 110 | AnsiConsole.EmptyLine 111 | 112 | use sln = new Sln(solution.Path, SlnItems.All) 113 | 114 | let projects = 115 | sln.Result.Env.LoadMinimalProjects() 116 | 117 | setReferenceAtProjectFiles (projects, packageReferenceName, projectReferencePath) 118 | 119 | saveProjectAtSolution (sln, solution.Path, packageReferenceName, projectReferencePath) -------------------------------------------------------------------------------- /src/Solution.fs: -------------------------------------------------------------------------------- 1 | module ReferenceManager.Solution 2 | 3 | open System.Collections.Generic 4 | open ReferenceManager.Extensions 5 | open ReferenceManager.Utils 6 | open System.IO 7 | open System.Linq 8 | open Spectre.Console 9 | open net.r_eg.MvsSln 10 | open net.r_eg.MvsSln.Projects 11 | 12 | type SolutionProject = 13 | { Name: string 14 | FullPath: string 15 | PackageReferences: IEnumerable } 16 | 17 | type SolutionData = 18 | { Projects: IEnumerable 19 | Name: string 20 | Path: string } 21 | 22 | let getSolutionProjectNames (solution: SolutionData) = 23 | solution.Projects.Select(fun p -> p.Name) 24 | 25 | 26 | let writeSolutionProjects (solution: SolutionData) = 27 | let solutionTree = 28 | Tree($"[bold]{solution.Name}[/]") 29 | 30 | solutionTree.Style <- Style(Color.Green) 31 | solutionTree.AddNodes(getSolutionProjectNames solution) 32 | 33 | AnsiConsole.EmptyLine 34 | AnsiConsole.Write solutionTree 35 | AnsiConsole.EmptyLine 36 | 37 | let promptSolution () : SolutionData = 38 | let path = promptPath ".sln" 39 | 40 | use sln = 41 | new Sln(path, SlnItems.All) 42 | 43 | let name = Path.GetFileNameWithoutExtension(path) 44 | 45 | let projects: IEnumerable = 46 | query { 47 | for project in sln.Result.Env.UniqueByGuidProjects do 48 | select 49 | { Name = project.ProjectName 50 | FullPath = project.ProjectFullPath 51 | PackageReferences = project.GetPackageReferences().ToList() } 52 | } 53 | 54 | { Name = name 55 | Projects = projects.ToList() 56 | Path = path } -------------------------------------------------------------------------------- /src/Utils.fs: -------------------------------------------------------------------------------- 1 | module ReferenceManager.Utils 2 | 3 | open ReferenceManager.Extensions 4 | open System.IO 5 | open Spectre.Console 6 | 7 | let writeAppTitle () = 8 | AnsiConsole.Write(Rule()) 9 | AnsiConsole.WriteLine("Welcome to Reference Manager !") 10 | AnsiConsole.Write(Rule()) 11 | AnsiConsole.EmptyLine 12 | 13 | let pathExists (path: string) = 14 | if File.Exists(path) then 15 | ValidationResult.Success() 16 | else 17 | ValidationResult.Error("[red]Invalid path![/]") 18 | 19 | let promptPath (subject: string) = 20 | let prompt = 21 | TextPrompt($"What's your [green]{subject}[/] file path?", PromptStyle = "green", Validator = pathExists) 22 | 23 | AnsiConsole.Prompt(prompt) 24 | 25 | type Choice = { Id: int; Description: string } 26 | 27 | let promptChoice () = 28 | AnsiConsole 29 | .Prompt( 30 | SelectionPrompt() 31 | .AddChoices( 32 | { Id = 0 33 | Description = "List PackageReferences from a project" }, 34 | { Id = 1 35 | Description = "Replace a PackageReference with ProjectReference" }, 36 | { Id = 2 37 | Description = "Load another [Green].sln[/]" }, 38 | { Id = -1; Description = "Exit" } 39 | ) 40 | .UseConverter(fun c -> c.Description) 41 | ) 42 | .Id 43 | 44 | --------------------------------------------------------------------------------