├── sample-output.png ├── DependenSee ├── Properties │ └── launchSettings.json ├── globalusings.cs ├── Models.cs ├── LICENSE.txt ├── DependenSee.csproj ├── GraphVizSerializer.cs ├── Program.cs ├── ResultWriter.cs ├── PowerArgsProgram.cs └── ReferenceDiscoveryService.cs ├── .github ├── workflows │ └── ci.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug-report.md ├── azure-pipelines.yml ├── LICENSE ├── DependenSee.sln ├── docs ├── Troubleshooting.md ├── Version3.md └── CommandLine.md ├── README.md └── .gitignore /sample-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madushans/DependenSee/HEAD/sample-output.png -------------------------------------------------------------------------------- /DependenSee/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DependenSee": { 4 | "commandName": "Project", 5 | "commandLineArgs": "C:\\Users\\Madushan\\source\\repos\\ConsoleApp5 C:\\temp\\test.html -P" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /DependenSee/globalusings.cs: -------------------------------------------------------------------------------- 1 | global using System.CodeDom.Compiler; 2 | global using System.IO; 3 | global using System.Collections.Generic; 4 | global using System; 5 | global using System.Net; 6 | global using System.Reflection; 7 | global using System.Xml.Serialization; 8 | global using PowerArgs; 9 | global using System.Linq; 10 | global using System.Xml; 11 | global using System.Text.Json; 12 | -------------------------------------------------------------------------------- /DependenSee/Models.cs: -------------------------------------------------------------------------------- 1 | namespace DependenSee; 2 | 3 | public class DiscoveryResult 4 | { 5 | public List Projects { get; set; } 6 | public List Packages { get; set; } 7 | public List References { get; set; } 8 | } 9 | 10 | public class Project 11 | { 12 | public string Id { get; set; } 13 | public string Name { get; set; } 14 | } 15 | public class Package 16 | { 17 | public string Id { get; set; } 18 | public string Name { get; set; } 19 | } 20 | 21 | public class Reference 22 | { 23 | public string From { get; set; } 24 | public string To { get; set; } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | DOTNET_VERSION: '8.0.x' 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@v3 21 | with: 22 | dotnet-version: ${{ env.DOTNET_VERSION }} 23 | 24 | - name: Restore 25 | run: dotnet restore DependenSee.sln 26 | 27 | - name: Build 28 | run: dotnet build DependenSee.sln --configuration Release --no-restore 29 | 30 | - name: Test 31 | run: dotnet test DependenSee.sln --configuration Release --no-build -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # ASP.NET 2 | # Build and test ASP.NET projects. 3 | # Add steps that publish symbols, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4 5 | 6 | trigger: 7 | - main 8 | 9 | pool: 10 | vmImage: 'windows-latest' 11 | 12 | variables: 13 | solution: '**/*.sln' 14 | buildPlatform: 'Any CPU' 15 | buildConfiguration: 'Release' 16 | 17 | steps: 18 | 19 | - task: UseDotNet@2 20 | inputs: 21 | packageType: 'sdk' 22 | version: '6.0.x' 23 | 24 | - task: DotNetCoreCLI@2 25 | inputs: 26 | command: 'pack' 27 | packagesToPack: '**/*.csproj' 28 | includesymbols: true 29 | versioningScheme: 'off' 30 | 31 | - task: PublishBuildArtifacts@1 32 | inputs: 33 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 34 | ArtifactName: 'drop' 35 | publishLocation: 'Container' -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | Hey there, 11 | 12 | Pull Requests are welcome. If you know how to add the feature to the code and you're sure it fits well with the scope of DependenSee, feel free to branch off of `dev` and put a PR. 13 | 14 | This template is here only if you're not sure if the feature is in scope or you like the feature to be there but not looking to contribute code/not know how to start. 15 | 16 | --- 17 | 18 | **Is your feature request related to a problem? Please describe.** 19 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 20 | 21 | **Describe the solution you'd like** 22 | A clear and concise description of what you want to happen. 23 | 24 | **Describe alternatives you've considered** 25 | A clear and concise description of any alternative solutions or features you've considered. 26 | 27 | **Additional context** 28 | Add any other context or screenshots about the feature request here. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Madushan Siriwardena 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 | -------------------------------------------------------------------------------- /DependenSee/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) Madushan Siriwardena 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /DependenSee.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependenSee", "DependenSee\DependenSee.csproj", "{6C4DA9BF-3DEC-4794-8E63-3F066A0C7361}" 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 | {6C4DA9BF-3DEC-4794-8E63-3F066A0C7361}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6C4DA9BF-3DEC-4794-8E63-3F066A0C7361}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6C4DA9BF-3DEC-4794-8E63-3F066A0C7361}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6C4DA9BF-3DEC-4794-8E63-3F066A0C7361}.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 = {8BB29E98-3985-4D36-A65D-6520522B4E68} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /DependenSee/DependenSee.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | true 8 | DependenSee 9 | ./bin/nupkg 10 | 2.2.0 11 | 12 | 13 | 14 | 15 | Always 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | LICENSE.txt 25 | Madushan 26 | Dotnet Tool to visualize or export project and package dependencies. 27 | dependency, visualizer, dependensee 28 | https://github.com/madushans/DependenSee 29 | https://github.com/madushans/DependenSee 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help improve DependenSee 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Hey there, 11 | 12 | Pull Requests are welcome. If you know how to add the bugfix to code and you're sure it fits well with the scope of DependenSee, feel free to branch off of `dev` and put a PR. 13 | 14 | This template is here only if you're not sure if it really is a bug, if is in scope or you're not looking to contribute code/not know how to start. 15 | 16 | --- 17 | 18 | **Describe the bug** 19 | A clear and concise description of what the bug is. 20 | 21 | **To Reproduce** 22 | Steps to reproduce the behavior: 23 | 24 | **Example VS Solution** 25 | If possible attach a zip of a solution directory that exepriences this issue. 26 | DependenSee only looks at csproj files, so you don't have to include your code. But if you do, make sure you **Clean** your solution to prevent any anti malware software from flagging the upload. 27 | 28 | **DependenSee Command** 29 | *Command you ran to get this issue* 30 | 31 | **DependenSee Output** 32 | *output of DependenSee* 33 | 34 | 35 | **DependenSee Version** 36 | *Run `dotnet tool list --global` to get the installed version details* 37 | -------------------------------------------------------------------------------- /DependenSee/GraphVizSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace DependenSee; 2 | public static class GraphvizSerializer 3 | { 4 | const string ProjectBackgroundColor = "#00cc22"; 5 | const string PackageBackgroundColor = "#22aaee"; 6 | 7 | public static string ToString(DiscoveryResult discoveryResult) 8 | { 9 | using var stringWriter = new StringWriter(); 10 | using var writer = new IndentedTextWriter(stringWriter); 11 | 12 | writer.WriteLine("digraph dependencies {"); 13 | 14 | writer.Indent++; 15 | 16 | foreach (var project in discoveryResult.Projects) 17 | { 18 | writer.WriteLine($"\"{project.Id}\" [label=\"{project.Name}\", fillcolor=\"{ProjectBackgroundColor}\", style=filled];"); 19 | } 20 | 21 | foreach (var package in discoveryResult.Packages) 22 | { 23 | writer.WriteLine($"\"{package.Id}\" [label=\"{package.Name}\", fillcolor=\"{PackageBackgroundColor}\", style=filled];"); 24 | } 25 | 26 | foreach (var reference in discoveryResult.References) 27 | { 28 | writer.WriteLine($"\"{reference.From}\" -> \"{reference.To}\";"); 29 | } 30 | 31 | writer.Indent--; 32 | 33 | writer.WriteLine("}"); 34 | 35 | writer.Flush(); 36 | 37 | return stringWriter.ToString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/Troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | ## Missing projects or duplicates 4 | Make sure you're running DependenSee on the root of your projects. 5 | 6 | ## Exclude stderr errors and warnings 7 | DependenSee may log errors and warnings to `stderr` stream. If you're relying on the console output of the tool and would like to exclude `stderr` you can exclude them as below. 8 | 9 | cmd 10 | ``` 11 | Dependensee 2> nul 12 | ``` 13 | 14 | powershell 15 | ``` 16 | Dependensee 2> $null 17 | ``` 18 | 19 | bash 20 | ``` 21 | Dependensee 2> /dev/null 22 | ``` 23 | 24 | 25 | ## Symlinks, Hard links, Reparse points, NTFS junctions 26 | If your solution has these or are depending on them, below applies. 27 | 28 | - Reparse points are skipped by default. You can force them to be followed by using `-FollowReparsePoints` flag. They will not be followed if the target does not exists. 29 | 30 | - If you want to follow some reparse points, but not others, enable following using `-FollowReparsePoints` and exclude the ones you don't want to follow using `-ExcludeFolders` flag. 31 | 32 | - If you have symlinks that contain loops, you have to exclude them as DependenSee will not check for filesystem loops. 33 | 34 | ## Access denied or missing folder errors 35 | DependenSee assumes it can access all files and folders below the specified folder. If this is not true, you may get access denied errors or DependenSee may assume some folders do not exist. Make sure all folders are accessible on the user account DependenSee is running under. 36 | -------------------------------------------------------------------------------- /DependenSee/Program.cs: -------------------------------------------------------------------------------- 1 | namespace DependenSee; 2 | public static class Program 3 | { 4 | public static int Main(string[] args) 5 | { 6 | if (args is null || args.Length == 0) 7 | { 8 | // if no arguments specified, instead of showing an error, 9 | // pretend as help command 10 | Args.InvokeMain(new[] { $"-{nameof(PowerArgsProgram.Help)}" }); 11 | return 0; 12 | } 13 | try 14 | { 15 | Args.InvokeMain(args); 16 | return 0; 17 | } 18 | catch (Exception ex) 19 | { 20 | WriteUnexpectedException(ex); 21 | return 1; 22 | } 23 | } 24 | 25 | private static void WriteUnexpectedException(Exception ex) 26 | { 27 | var currentColor = Console.ForegroundColor; 28 | Console.ForegroundColor = ConsoleColor.Yellow; 29 | 30 | Console.Error.WriteLine($"{ex.GetType()}:{ex.Message}\r\n{ex.StackTrace}"); 31 | 32 | var inner = ex.InnerException; 33 | 34 | while (inner != null) 35 | { 36 | Console.Error.WriteLine($"-----Inner Exception----"); 37 | Console.Error.WriteLine($"{inner.GetType()}:{inner.Message}\r\n{inner.StackTrace}"); 38 | inner = inner.InnerException; 39 | } 40 | 41 | Console.ForegroundColor = ConsoleColor.Red; 42 | 43 | Console.Error.WriteLine(@" 44 | 45 | ============================================================= 46 | | | 47 | | Well that didn't work. | 48 | | | 49 | | If this keeps happening, | 50 | | - try updating to the latest version with | 51 | | - dotnet tool update DependenSee --global | 52 | | | 53 | | - check current version with | 54 | | - dotnet tool list --global | 55 | | | 56 | | - Please open an issue at | 57 | | - https://www.github.com/madushans/DependenSee | 58 | | | 59 | | ♥ Thank you for your support ♥ | 60 | | | 61 | ============================================================= 62 | "); 63 | Console.ForegroundColor = currentColor; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DependenSee 2 | Dotnet project and package dependency visualizer. 3 | 4 | --- 5 | 6 | ## V3 Is Coming! 🔜 7 | 8 | I have started work on the next major version of DependenSee. See [what's coming!](/docs/Version3.md) 9 | 10 | --- 11 | 12 | # Install 13 | 14 | ***Requires .NET 6 Runtime*** 15 | 16 | `dotnet tool install DependenSee --global` 17 | 18 | available on [Nuget](https://www.nuget.org/packages/DependenSee) 19 | 20 | ![Nuget Badge](https://buildstats.info/nuget/dependensee) 21 | 22 | ## Uninstall 23 | `dotnet tool uninstall DependenSee --global` 24 | 25 | # Basic Usage 26 | 27 | `DependenSee root/of/projects path/to/output.html` 28 | 29 | See [full CLI documentation](/docs/CommandLine.md) 30 | 31 | ## Sample Output (HTML, interactive) 32 | 33 | Download [this](https://raw.githubusercontent.com/madushans/DependenSee/main/sample-html-output.html) as html and open in a browser for a demo. 34 | 35 | ![DependenSee sample html output](https://raw.githubusercontent.com/madushans/DependenSee/main/sample-output.png) 36 | 37 | 38 | # [Command Line Arguments > 🔗](/docs/CommandLine.md) 39 | 40 | # [Troubleshooting > 🔗](/docs/Troubleshooting.md) 41 | 42 | 43 | # Why `DependenSee` over 'X' 44 | 45 | Current popular options are to either use NDepend, VS Architecture Explorer or Code Map. While these options are feature rich, they also has a licensing cost. If all you want is to see the dependency graph, and nothing else, it may be hard to justify the licensing cost. Then `DependenSee` is for you. 46 | 47 | If you need to see the Type structure, relationships between your methods or types .etc. then you should use one of the options above instead. DependenSee is mean to be very simple, easy, straight forward to use and FREE! `DependenSee` does not intend to compete with the above. See [Limitations](#Limitations) 48 | 49 | # Features 50 | 51 | - Creates the dependency graph for your solution. 52 | - Can only include or exclude certain namespaces so the result is not overwhelming or filled with noise. 53 | - Can create HTML, XML, JSON and Graphviz outputs 54 | - Can return output to `STDOUT` for further processing by other command line tools 55 | - Returns errors and warnings to `STDERR` 56 | 57 | For full docs run without any arguments 58 | `DependenSee` 59 | 60 | 61 | # Privacy and Security Note 62 | 63 | In the output, the full path to project files is used as the unique identifier. So your file structure is exposed in the generated output. It attempts to only use the subdirectory structure, so an attempt is made to hide the full path, however it is possible these paths may include your username for example, if your project was located in the default VS path/repo clone location of `C:\Users\\Repos\...`. 64 | 65 | Keep this in mind and inspect the output if you're distributing the outputs from this tool. 66 | 67 | # Limitations 68 | 69 | - Currently only traverses `csproj` and `vbproj` files. No other file types are supported. 70 | - No compile results are inspected. Only the project structure is used. 71 | 72 | # License 73 | [MIT License](https://github.com/madushans/DependenSee/blob/main/LICENSE) 74 | 75 | # Support 76 | If you are experiencing issues, please [open an issue](https://github.com/madushans/DependenSee/issues) with details and reproduction steps. 77 | 78 | # Contributions 79 | 80 | Pull requests welcome. ♥ 81 | 82 | Please branch off of `dev` branch and put a PR to `dev` for your changes. 83 | If you have a contribution you're not sure about, please feel free to [open an issue](https://github.com/madushans/DependenSee/issues). However a prior approval is not necessary for a PR to be merged. 84 | 85 | Once approved, all pending changes (possibly multiple PRs) will be merged to `main` for a release to be distributed via NuGet. 86 | 87 | # Powered by (Thanks) 88 | 89 | - [Your Community Contributions 🙏](https://github.com/madushans/DependenSee/pulls?q=is%3Apr+is%3Aclosed) 90 | - [PowerArgs](https://github.com/adamabdelhamed/PowerArgs) 91 | - [Vis.JS](https://visjs.org/) 92 | 93 | -------------------------------------------------------------------------------- /DependenSee/ResultWriter.cs: -------------------------------------------------------------------------------- 1 | namespace DependenSee; 2 | 3 | internal class ResultWriter 4 | { 5 | private const string HtmlTemplateToken = "'{#SOURCE_TOKEN#}'"; 6 | private const string HtmlTitleToken = "{#TITLE_TOKEN#}"; 7 | private const string HtmlTemplateName = "HtmlResultTemplate.html"; 8 | private JsonSerializerOptions _JsonSerializerOptions = new JsonSerializerOptions() { WriteIndented = true }; 9 | 10 | internal void Write(DiscoveryResult result, OutputTypes type, string outputPath, string htmlTitle) 11 | { 12 | switch (type) 13 | { 14 | case OutputTypes.Html: 15 | case OutputTypes.Xml: 16 | case OutputTypes.Json: 17 | case OutputTypes.Graphviz: 18 | if (string.IsNullOrWhiteSpace(outputPath)) 19 | { 20 | Console.Error.WriteLine($"output type {type} require specifying {nameof(outputPath)}"); 21 | return; 22 | } 23 | break; 24 | } 25 | 26 | switch (type) 27 | { 28 | case OutputTypes.Html: 29 | WriteAsHtmlToFile(result, outputPath, htmlTitle); 30 | break; 31 | case OutputTypes.Xml: 32 | WriteAsXmlToFile(result, outputPath); 33 | break; 34 | case OutputTypes.Json: 35 | WriteAsJsonToFile(result, outputPath); 36 | break; 37 | case OutputTypes.Graphviz: 38 | WriteAsGraphvizToFile(result, outputPath); 39 | break; 40 | case OutputTypes.ConsoleJson: 41 | WriteAsJsonToConsole(result); 42 | break; 43 | case OutputTypes.ConsoleXml: 44 | WriteAsXmlToConsole(result); 45 | break; 46 | case OutputTypes.ConsoleGraphviz: 47 | WriteAsGraphvizToConsole(result); 48 | break; 49 | default: 50 | throw new Exception($"Unknown {nameof(type)} '{type}'"); 51 | } 52 | } 53 | 54 | private void WriteAsHtmlToFile(DiscoveryResult result, string outputPath, string title) 55 | { 56 | var templatePath = Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName, HtmlTemplateName); 57 | var template = File.ReadAllText(templatePath); 58 | var html = template 59 | .Replace(HtmlTemplateToken, JsonSerializer.Serialize(result, _JsonSerializerOptions)) 60 | .Replace(HtmlTitleToken, WebUtility.HtmlEncode(title)); 61 | 62 | File.WriteAllText(outputPath, html); 63 | Console.WriteLine($"HTML output written to {outputPath}"); 64 | } 65 | 66 | private void WriteAsXmlToConsole(DiscoveryResult result) 67 | { 68 | var serializer = new XmlSerializer(typeof(DiscoveryResult)); 69 | using var writeStream = Console.Out; 70 | serializer.Serialize(writeStream, result); 71 | } 72 | 73 | private void WriteAsXmlToFile(DiscoveryResult result, string outputPath) 74 | { 75 | var serializer = new XmlSerializer(typeof(DiscoveryResult)); 76 | using var writeStream = File.OpenWrite(outputPath); 77 | serializer.Serialize(writeStream, result); 78 | Console.WriteLine($"XML output written to {outputPath}"); 79 | } 80 | 81 | private void WriteAsJsonToFile(DiscoveryResult result, string outputPath) 82 | { 83 | File.WriteAllText(outputPath, JsonSerializer.Serialize(result, _JsonSerializerOptions)); 84 | Console.WriteLine($"JSON output written to {outputPath}"); 85 | } 86 | private void WriteAsJsonToConsole(DiscoveryResult result) => 87 | Console.WriteLine(JsonSerializer.Serialize(result, _JsonSerializerOptions)); 88 | 89 | private static void WriteAsGraphvizToConsole(DiscoveryResult result) 90 | => Console.WriteLine(GraphvizSerializer.ToString(result)); 91 | 92 | private static void WriteAsGraphvizToFile(DiscoveryResult result, string outputPath) 93 | { 94 | File.WriteAllText(outputPath, GraphvizSerializer.ToString(result)); 95 | Console.WriteLine($"GraphViz output written to {outputPath}"); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /docs/Version3.md: -------------------------------------------------------------------------------- 1 | 2 | # DependenSee Version 3 3 | 4 | I am planning an overhaul of the project for the next major release. Below is what I have in mind. Note that some of this will unfortunately be breaking changes. 5 | 6 | If you like to follow the progress, checkout the [`/v3` branch](https://github.com/madushans/DependenSee/tree/v3). 7 | 8 | If you have any questions, come say hi 👋 in [Discussions](https://github.com/madushans/DependenSee/discussions) 9 | 10 | ## Overall Design 11 | 12 | DepedenSee will be organized into 3 parts as below. 13 | 14 | ### **DependenSee Core** 15 | 16 | As Requested in [Issue #28](https://github.com/madushans/DependenSee/issues/28) the core functonality will be available as a Nuget package. This will be a new andseparate nuget package (as opposed to a nuget tool) which will allow any custom scenarios the existing CLI does not support and/or is beyond the scope of DependenSee. 17 | 18 | This is not a replacement for the existing CLI. Though most of the functionality will be moved to the Core package, and the CLI will serve as a shell for the Core. 19 | 20 | ## DependenSee (CLI) 21 | 22 | This will be the evolution of the existing CLI tool and will be available as it is, just with a 3.x version number. However the CLI will not be backwards compatible as the philosophy has changed. 23 | 24 | ## DependenSee Visualizer 25 | 26 | Currently the most used part of the project is the html based visualizer. This was originally built as a bare bones html page, and not much though was put into it. 27 | 28 | However it has clearly shown to be useful, and requires an overhaul. Going forward this is planned to be a React app, though still self-contained in a single html file. Moving to React allows to make a better interface than what we have now. For ore information see [Visualizer](https://github.com/madushans/DependenSee/blob/v3/docs/Visualizer.md) 29 | 30 | ## Changes to Philosophy 31 | 32 | The v2 DependenSee has grown to have an overwhelming amount of CLI options which made it depart from the original "easy-to-use" motto. Most of this is due to requirements of filtering the dependency graph in different ways. 33 | 34 | With these switches, DependenSee can be instructed to customize the discovery run, by including or excluding projects, namespaces, and packages. However if you want a different filter, you have to run DependenSee again which means you have to have access to the source. This may not be possible if the html output was distributed to individuals, who may not have access to the source. 35 | 36 | The new v3 is being updated to have a minimal amount of filtering on the CLI. Filtering is only present to get around any issues the discovery may encounter, and not as a means of customizing the output. Instead the discovery will always emit a complete (as possible) representation of your projects. This will include more information than what the current tool is collecting, like Solution files and Solution folders. 37 | 38 | Along with the upcoming new Visualizer, all the filtering are to be moved to the Visualizer frontend. This allows DependenSee to be run on a repository only once, collect all the information it sees, and let the Visualizer handle filtering, even without having the source at hand. Since all the information will be presented to the visualizer, it will be able to provide filtering on folders, projects, project names, packages names, solution files, and solution folders, all without having to run the CLI or having to have the source at hand. 39 | 40 | Unfortunately as you may have guessed, this means breaking many of the existing CLI switches. Since we would still like to generate a visualization with some preconfigured filters, ideally we will be able to point DependenSee to a file that has a "default filter" which can be sent to the visualizer. I plan to make this file extractable from the visualizer as well. My plan is to allow the following workflow 41 | 42 | - Generate a visualization 43 | - Open visualizer, setup filters (and possibly customizations like colors .etc.) 44 | - Extract the configuration form the visualizer and save as some JSON file. 45 | - Run the CLI again, but additionally providing the path to the JSON file. 46 | - CLI will generate a new visualization with the specified data as the default filter. 47 | 48 | This visualization can be distributed or stored in documentation where it will show the filtered and customized output as default, but allowing to change the filter if necessary. 49 | 50 | # Progress 51 | 52 | This is still very much in the early stages, so stay tuned. 53 | 54 | So far, I think I have the Core and the CLI work mostly done. (Checkout whats already in the [/v3 branch](https://github.com/madushans/DependenSee/tree/v3)). 55 | 56 | I'll move to start working on the Visualizer soon. (no timeline) 57 | 58 | If you have any questions, come say hi 👋 in [Discussions](https://github.com/madushans/DependenSee/discussions) 59 | 60 | 61 | -------------------------------------------------------------------------------- /DependenSee/PowerArgsProgram.cs: -------------------------------------------------------------------------------- 1 | namespace DependenSee; 2 | 3 | public enum OutputTypes 4 | { 5 | [ArgDescription("Creates an html document.")] Html, 6 | [ArgDescription("Creates a JSON file.")] Json, 7 | [ArgDescription("Creates a XML file.")] Xml, 8 | [ArgDescription("Creates a Graphviz/DOT file.")] Graphviz, 9 | [ArgDescription("Writes JSON output to stdout")] ConsoleJson, 10 | [ArgDescription("Writes XML output to stdout")] ConsoleXml, 11 | [ArgDescription("Writes Graphviz output to stdout")] ConsoleGraphviz, 12 | } 13 | 14 | [ArgExceptionBehavior(ArgExceptionPolicy.StandardExceptionHandling)] 15 | [ArgDescription(@" 16 | 17 | ================================================================== 18 | | | 19 | | For full documentation, support and reporting bugs | 20 | | Please visit https://www.github.com/madushans/DependenSee | 21 | | | 22 | ================================================================== 23 | ")] 24 | public class PowerArgsProgram 25 | { 26 | [HelpHook] 27 | [ArgDescription("Shows help descriptions.")] 28 | public bool Help { get; set; } 29 | [ArgRequired] 30 | [ArgPosition(0)] 31 | [ArgDescription("Root folder (usually solution folder) to look for csproj files recursively.")] 32 | [ArgExistingDirectory] 33 | public string SourceFolder { get; set; } 34 | 35 | [ArgDescription("Path to write the result. Not required if writing the output to stdout")] 36 | [ArgPosition(1)] 37 | public string OutputPath { get; set; } 38 | 39 | [ArgDefaultValue(false)] 40 | [ArgDescription("Whether to include external (Nuget) packages in the result.")] 41 | [ArgShortcut("P")] 42 | public bool IncludePackages { get; set; } 43 | 44 | [ArgDefaultValue(OutputTypes.Html)] 45 | [ArgDescription("Type of the output. Warnings are written to stderr. If you're piping the stdout, you may also want to check the stderr.")] 46 | [ArgShortcut("T")] 47 | public OutputTypes OutputType { get; set; } 48 | 49 | [ArgDefaultValue("DependenSee")] 50 | [ArgDescription("Document title for Html output. Ignored for other output types.")] 51 | [ArgShortcut("HT")] 52 | public string HtmlTitle { get; set; } 53 | 54 | [ArgDefaultValue("")] 55 | [ArgDescription("Comma separated list of project file prefixes to include. Wildcards not allowed. Only the filename is considered, case insensitive. Ex:'MyApp.Core, MyApp.Extensions' Includes only projects starting with MyApp.Core and projects starting with MyApp.Extensions")] 56 | [ArgShortcut("IPrN")] 57 | public string IncludeProjectNamespaces { get; set; } 58 | 59 | [ArgDescription("Comma separated list of project file prefixes to exclude. Wildcards not allowed. Only the filename is considered, case insensitive. This must be a subset of includes to be useful. Ex: 'MyApp.Extensions, MyApp.Helpers' Excludes projects starting with MyApp.Extensions and projects starting with MyApp.Helpers")] 60 | [ArgShortcut("EPrN")] 61 | public string ExcludeProjectNamespaces { get; set; } 62 | 63 | [ArgDefaultValue("")] 64 | [ArgDescription("Comma separated list of package name prefixes to include. Wildcards not allowed. Only the package name is considered, case insensitive. If specified, 'IncludePackages' is overridden to True. Ex: 'Xamarin, Microsoft' includes only packages starting with Xamarin and packages starting with Microsoft")] 65 | [ArgShortcut("IPaN")] 66 | public string IncludePackageNamespaces { get; set; } 67 | 68 | [ArgDescription("Comma separated list of package name prefixes to exclude. Wildcards not allowed. Only the filename is considered, case insensitive. If specified, 'IncludePackages' is overridden to True. This must be a subset of includes to be useful. Ex: 'Microsoft.Logging, Azure' Excludes packages starting with Microsoft.Logging and packages starting with Azure")] 69 | [ArgShortcut("EPaN")] 70 | public string ExcludePackageNamespaces { get; set; } 71 | [ArgDefaultValue("")] 72 | [ArgDescription("Comma Separated list of folders (either absolute paths or relative to SourceFolder) to skip during scan, even if there are references to them from your projects.")] 73 | [ArgShortcut("EFol")] 74 | public string ExcludeFolders { get; set; } 75 | 76 | [ArgDefaultValue(false)] 77 | [ArgDescription("Set if you want the scan to follow valid reparse points. This is helpful if your project references are relying on symlinks, NTFS junction points .etc.")] 78 | [ArgShortcut("FReP")] 79 | public bool FollowReparsePoints { get; set; } 80 | 81 | 82 | public void Main() 83 | { 84 | var service = new ReferenceDiscoveryService 85 | { 86 | ExcludePackageNamespaces = ExcludePackageNamespaces, 87 | ExcludeProjectNamespaces = ExcludeProjectNamespaces, 88 | IncludePackageNamespaces = IncludePackageNamespaces, 89 | IncludeProjectNamespaces = IncludeProjectNamespaces, 90 | 91 | IncludePackages = IncludePackages, 92 | 93 | ExcludeFolders = ExcludeFolders, 94 | FollowReparsePoints = FollowReparsePoints, 95 | 96 | OutputPath = OutputPath, 97 | OutputType = OutputType, 98 | 99 | SourceFolder = SourceFolder, 100 | 101 | }; 102 | var result = service.Discover(); 103 | new ResultWriter().Write(result, OutputType, OutputPath, HtmlTitle); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Coverlet is a free, cross platform Code Coverage Tool 145 | coverage*.json 146 | coverage*.xml 147 | coverage*.info 148 | 149 | # Visual Studio code coverage results 150 | *.coverage 151 | *.coveragexml 152 | 153 | # NCrunch 154 | _NCrunch_* 155 | .*crunch*.local.xml 156 | nCrunchTemp_* 157 | 158 | # MightyMoose 159 | *.mm.* 160 | AutoTest.Net/ 161 | 162 | # Web workbench (sass) 163 | .sass-cache/ 164 | 165 | # Installshield output folder 166 | [Ee]xpress/ 167 | 168 | # DocProject is a documentation generator add-in 169 | DocProject/buildhelp/ 170 | DocProject/Help/*.HxT 171 | DocProject/Help/*.HxC 172 | DocProject/Help/*.hhc 173 | DocProject/Help/*.hhk 174 | DocProject/Help/*.hhp 175 | DocProject/Help/Html2 176 | DocProject/Help/html 177 | 178 | # Click-Once directory 179 | publish/ 180 | 181 | # Publish Web Output 182 | *.[Pp]ublish.xml 183 | *.azurePubxml 184 | # Note: Comment the next line if you want to checkin your web deploy settings, 185 | # but database connection strings (with potential passwords) will be unencrypted 186 | *.pubxml 187 | *.publishproj 188 | 189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 190 | # checkin your Azure Web App publish settings, but sensitive information contained 191 | # in these scripts will be unencrypted 192 | PublishScripts/ 193 | 194 | # NuGet Packages 195 | *.nupkg 196 | # NuGet Symbol Packages 197 | *.snupkg 198 | # The packages folder can be ignored because of Package Restore 199 | **/[Pp]ackages/* 200 | # except build/, which is used as an MSBuild target. 201 | !**/[Pp]ackages/build/ 202 | # Uncomment if necessary however generally it will be regenerated when needed 203 | #!**/[Pp]ackages/repositories.config 204 | # NuGet v3's project.json files produces more ignorable files 205 | *.nuget.props 206 | *.nuget.targets 207 | 208 | # Microsoft Azure Build Output 209 | csx/ 210 | *.build.csdef 211 | 212 | # Microsoft Azure Emulator 213 | ecf/ 214 | rcf/ 215 | 216 | # Windows Store app package directories and files 217 | AppPackages/ 218 | BundleArtifacts/ 219 | Package.StoreAssociation.xml 220 | _pkginfo.txt 221 | *.appx 222 | *.appxbundle 223 | *.appxupload 224 | 225 | # Visual Studio cache files 226 | # files ending in .cache can be ignored 227 | *.[Cc]ache 228 | # but keep track of directories ending in .cache 229 | !?*.[Cc]ache/ 230 | 231 | # Others 232 | ClientBin/ 233 | ~$* 234 | *~ 235 | *.dbmdl 236 | *.dbproj.schemaview 237 | *.jfm 238 | *.pfx 239 | *.publishsettings 240 | orleans.codegen.cs 241 | 242 | # Including strong name files can present a security risk 243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 244 | #*.snk 245 | 246 | # Since there are multiple workflows, uncomment next line to ignore bower_components 247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 248 | #bower_components/ 249 | 250 | # RIA/Silverlight projects 251 | Generated_Code/ 252 | 253 | # Backup & report files from converting an old project file 254 | # to a newer Visual Studio version. Backup files are not needed, 255 | # because we have git ;-) 256 | _UpgradeReport_Files/ 257 | Backup*/ 258 | UpgradeLog*.XML 259 | UpgradeLog*.htm 260 | ServiceFabricBackup/ 261 | *.rptproj.bak 262 | 263 | # SQL Server files 264 | *.mdf 265 | *.ldf 266 | *.ndf 267 | 268 | # Business Intelligence projects 269 | *.rdl.data 270 | *.bim.layout 271 | *.bim_*.settings 272 | *.rptproj.rsuser 273 | *- [Bb]ackup.rdl 274 | *- [Bb]ackup ([0-9]).rdl 275 | *- [Bb]ackup ([0-9][0-9]).rdl 276 | 277 | # Microsoft Fakes 278 | FakesAssemblies/ 279 | 280 | # GhostDoc plugin setting file 281 | *.GhostDoc.xml 282 | 283 | # Node.js Tools for Visual Studio 284 | .ntvs_analysis.dat 285 | node_modules/ 286 | 287 | # Visual Studio 6 build log 288 | *.plg 289 | 290 | # Visual Studio 6 workspace options file 291 | *.opt 292 | 293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 294 | *.vbw 295 | 296 | # Visual Studio LightSwitch build output 297 | **/*.HTMLClient/GeneratedArtifacts 298 | **/*.DesktopClient/GeneratedArtifacts 299 | **/*.DesktopClient/ModelManifest.xml 300 | **/*.Server/GeneratedArtifacts 301 | **/*.Server/ModelManifest.xml 302 | _Pvt_Extensions 303 | 304 | # Paket dependency manager 305 | .paket/paket.exe 306 | paket-files/ 307 | 308 | # FAKE - F# Make 309 | .fake/ 310 | 311 | # CodeRush personal settings 312 | .cr/personal 313 | 314 | # Python Tools for Visual Studio (PTVS) 315 | __pycache__/ 316 | *.pyc 317 | 318 | # Cake - Uncomment if you are using it 319 | # tools/** 320 | # !tools/packages.config 321 | 322 | # Tabs Studio 323 | *.tss 324 | 325 | # Telerik's JustMock configuration file 326 | *.jmconfig 327 | 328 | # BizTalk build output 329 | *.btp.cs 330 | *.btm.cs 331 | *.odx.cs 332 | *.xsd.cs 333 | 334 | # OpenCover UI analysis results 335 | OpenCover/ 336 | 337 | # Azure Stream Analytics local run output 338 | ASALocalRun/ 339 | 340 | # MSBuild Binary and Structured Log 341 | *.binlog 342 | 343 | # NVidia Nsight GPU debugger configuration file 344 | *.nvuser 345 | 346 | # MFractors (Xamarin productivity tool) working folder 347 | .mfractor/ 348 | 349 | # Local History for Visual Studio 350 | .localhistory/ 351 | 352 | # BeatPulse healthcheck temp database 353 | healthchecksdb 354 | 355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 356 | MigrationBackup/ 357 | 358 | # Ionide (cross platform F# VS Code tools) working folder 359 | .ionide/ 360 | 361 | # Fody - auto-generated XML schema 362 | FodyWeavers.xsd 363 | 364 | */Properties/launchSettings.json 365 | -------------------------------------------------------------------------------- /DependenSee/ReferenceDiscoveryService.cs: -------------------------------------------------------------------------------- 1 | namespace DependenSee; 2 | 3 | public class ReferenceDiscoveryService 4 | { 5 | public string SourceFolder { get; set; } 6 | public string OutputPath { get; set; } 7 | public bool IncludePackages { get; set; } 8 | public OutputTypes OutputType { get; set; } 9 | public string IncludeProjectNamespaces { get; set; } 10 | public string ExcludeProjectNamespaces { get; set; } 11 | public string IncludePackageNamespaces { get; set; } 12 | public string ExcludePackageNamespaces { get; set; } 13 | public bool FollowReparsePoints { get; set; } 14 | public string ExcludeFolders { get; set; } 15 | 16 | private string[] _includeProjectNamespaces { get; set; } 17 | private string[] _excludeProjectNamespaces { get; set; } 18 | private string[] _includePackageNamespaces { get; set; } 19 | private string[] _excludePackageNamespaces { get; set; } 20 | 21 | private bool _shouldIncludePackages { get; set; } 22 | 23 | public DiscoveryResult Discover() 24 | { 25 | var result = new DiscoveryResult 26 | { 27 | Packages = new List(), 28 | Projects = new List(), 29 | References = new List() 30 | }; 31 | 32 | _includeProjectNamespaces = ParseStringToLowercaseStringArray(IncludeProjectNamespaces); 33 | _excludeProjectNamespaces = ParseStringToLowercaseStringArray(ExcludeProjectNamespaces); 34 | 35 | _includePackageNamespaces = ParseStringToLowercaseStringArray(IncludePackageNamespaces); 36 | _excludePackageNamespaces = ParseStringToLowercaseStringArray(ExcludePackageNamespaces); 37 | 38 | if (!_includeProjectNamespaces.Any()) _includeProjectNamespaces = new[] { "" }; 39 | if (!_includePackageNamespaces.Any()) _includePackageNamespaces = new[] { "" }; 40 | _shouldIncludePackages = IncludePackages 41 | || !string.IsNullOrWhiteSpace(IncludePackageNamespaces) 42 | || !string.IsNullOrWhiteSpace(ExcludePackageNamespaces); 43 | 44 | Discover(SourceFolder, result); 45 | return result; 46 | } 47 | 48 | private static string[] ParseStringToLowercaseStringArray(string list) => 49 | string.IsNullOrWhiteSpace(list) 50 | ? Array.Empty() 51 | : list.Split(',', StringSplitOptions.RemoveEmptyEntries) 52 | .Select(e => e.Trim().ToLower()) 53 | .ToArray(); 54 | 55 | private void Discover(string folder, DiscoveryResult result) 56 | { 57 | var info = new DirectoryInfo(folder); 58 | 59 | var excludedByRule = GetFolderExclusionFor(info.FullName); 60 | if (excludedByRule != null) 61 | { 62 | Console.Error.WriteLine($"Skipping folder '{folder}' excluded by rule '{excludedByRule}'\r\n\r\n"); 63 | return; 64 | } 65 | 66 | if (!info.Exists) 67 | { 68 | Console.Error.WriteLine($"Skipping scan for missing folder '{folder}'\r\n\r\n"); 69 | return; 70 | } 71 | if (info.Attributes.HasFlag(FileAttributes.ReparsePoint) && !FollowReparsePoints) 72 | { 73 | Console.Error.WriteLine($"Skipping scan for reparse point '{folder}'. Set {nameof(PowerArgsProgram.FollowReparsePoints)} flag to follow.\r\n\r\n"); 74 | return; 75 | } 76 | 77 | var projectFiles = Directory.EnumerateFiles(folder, "*.csproj") 78 | .Concat(Directory.EnumerateFiles(folder, "*.vbproj")); 79 | foreach (var file in projectFiles) 80 | { 81 | var id = file.Replace(SourceFolder, ""); 82 | var name = Path.GetFileNameWithoutExtension(file); 83 | if (!_includeProjectNamespaces.Any(i => name.ToLower().StartsWith(i))) continue; 84 | if (_excludeProjectNamespaces.Any(i => name.ToLower().StartsWith(i))) continue; 85 | 86 | // add this project. 87 | if (!result.Projects.Any(p => p.Id == id)) 88 | result.Projects.Add(new Project 89 | { 90 | Id = id, 91 | Name = name 92 | }); 93 | 94 | var (projects, packages) = DiscoverFileReferences(file); 95 | 96 | projects = projects 97 | .Where(p => 98 | _includeProjectNamespaces.Any(i => p.Name.ToLower().StartsWith(i)) 99 | && !_excludeProjectNamespaces.Any(i => p.Name.ToLower().StartsWith(i))) 100 | .ToList(); 101 | 102 | foreach (var project in projects) 103 | { 104 | if (!result.Projects.Any(p => p.Id == project.Id)) result.Projects.Add(project); 105 | 106 | result.References.Add(new Reference 107 | { 108 | From = id, 109 | To = project.Id 110 | }); 111 | } 112 | 113 | if (!_shouldIncludePackages) continue; 114 | 115 | packages = packages.Where(p => 116 | { 117 | return _includePackageNamespaces.Any(i => p.Name.ToLower().StartsWith(i)) 118 | && !_excludePackageNamespaces.Any(i => p.Name.ToLower().StartsWith(i)); 119 | }).ToList(); 120 | 121 | foreach (var package in packages) 122 | { 123 | if (!result.Packages.Any(p => p.Id == package.Id)) result.Packages.Add(package); 124 | 125 | result.References.Add(new Reference 126 | { 127 | From = id, 128 | To = package.Id 129 | }); 130 | } 131 | } 132 | var directories = Directory.EnumerateDirectories(folder); 133 | foreach (var directory in directories) 134 | { 135 | Discover(directory, result); 136 | } 137 | } 138 | 139 | private (List projects, List packages) DiscoverFileReferences(string path) 140 | { 141 | var xml = new XmlDocument(); 142 | xml.Load(path); 143 | var basePath = new FileInfo(path).Directory.FullName; 144 | 145 | var projects = DiscoverProjectRefrences(xml, basePath); 146 | var packages = _shouldIncludePackages 147 | ? DiscoverPackageReferences(xml) 148 | : null; 149 | 150 | return (projects, packages); 151 | } 152 | 153 | private List DiscoverPackageReferences(XmlDocument xml) 154 | { 155 | // PackageReference = Nuget package 156 | // Reference = COM/DLL reference. These can have a child relative path to dll' 157 | // Reference also present for .NET Framework projects when they reference BCL assemblies, but these 158 | // do not include a HintPath 159 | var packageReferenceNodes = xml.SelectNodes("//*[local-name() = 'PackageReference' or local-name() = 'Reference']"); 160 | var packages = new List(); 161 | foreach (XmlNode node in packageReferenceNodes) 162 | { 163 | var packageName = node.Attributes["Include"]?.Value 164 | ?? node.Attributes["Update"].Value; 165 | 166 | packages.Add(new Package 167 | { 168 | Id = packageName, 169 | Name = packageName 170 | }); 171 | } 172 | return packages; 173 | } 174 | 175 | private string GetFolderExclusionFor(string fullFolderPath) 176 | { 177 | if (string.IsNullOrWhiteSpace(ExcludeFolders)) return null; 178 | 179 | var allRules = ExcludeFolders 180 | .Split(',') 181 | .Select(r => Path.IsPathRooted(r) ? r : Path.GetFullPath(r, SourceFolder)) 182 | .Select(r => r.ToLower().Trim()) 183 | .ToList(); 184 | 185 | fullFolderPath = fullFolderPath.ToLower(); 186 | return allRules.FirstOrDefault(r => fullFolderPath.StartsWith(r)); 187 | } 188 | 189 | private List DiscoverProjectRefrences(XmlDocument xml, string basePath) 190 | { 191 | var projectReferenceNodes = xml.SelectNodes("//*[local-name() = 'ProjectReference']"); 192 | var projects = new List(); 193 | 194 | foreach (XmlNode node in projectReferenceNodes) 195 | { 196 | var referencePath = node.Attributes["Include"].Value; 197 | var fullPath = Path.GetFullPath(referencePath, basePath); 198 | 199 | string filename = Path.GetFileNameWithoutExtension(fullPath); 200 | 201 | if (!fullPath.ToLower().StartsWith(SourceFolder.ToLower())) 202 | { 203 | Console.Error.WriteLine($"Found referenced project '{fullPath}' outside of provided source folder. Run DependenSee on the parent folder of all your projects to prevent duplicates and/or missing projects from the output.\r\n\r\n"); 204 | } 205 | 206 | projects.Add(new Project 207 | { 208 | Id = fullPath.Replace(SourceFolder, ""), 209 | Name = filename 210 | }); 211 | } 212 | return projects; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /docs/CommandLine.md: -------------------------------------------------------------------------------- 1 | 2 | # Command Line Arguments 3 | 4 | `Usage - DependenSee [] -options` 5 | 6 | For full docs in the command line, run without any arguments. 7 | 8 | To see the version installed, use `dotnet tool list` instead. (if you installed globally, `-g` option is required.) 9 | 10 | ## Exit code 11 | DependenSee will exit with 0 if there were no errors (may have warnings) and with 1 if the command failed. 12 | 13 | See `stderr` stream for warnings. If you're running in a standard command line, these will be included in the output, but will not be included if you're redirecting/piping the `stdout` to a file or another program. 14 | 15 | ## Help 16 | 17 | Shows help descriptions. Help is also displayed if no arguments were provided. 18 | 19 | **Shorthand: `-H`** 20 | 21 | ### Examples 22 | - `DependenSee -Help` 23 | - `DependenSee -H` 24 | - `DependenSee` 25 | 26 | ## SourceFolder [Required] 27 | 28 | Root folder (usually solution folder) to look for csproj files recursively. If no explicit options are specified, the first argument is assumed to be this option. 29 | 30 | If your path has spaces, you will need to enclose it in double quotes. 31 | 32 | Note that if a project inside this folder has a reference to a project that is outside of this folder, DependenSee WILL enumerate those folders. However this is not reccomended. Always run DependenSee on the root folder of your projects for consistent and correct results. 33 | 34 | ### Examples 35 | 36 | - `DependenSee \Source\MySolutionFolder` 37 | - `DependenSee -SourceFolder \Source\MySolutionFolder` 38 | - `DependenSee -S \Source\MySolutionFolder` 39 | 40 | ## OutputPath 41 | 42 | Path to write the result. Not required if writing the output to stdout. 43 | If no explicit options are specified, the second argument is assumed to be this option. 44 | 45 | If your path has spaces, you will need to enclose it in double quotes. 46 | 47 | **Shorthand: `-O`** 48 | 49 | ### Examples 50 | 51 | - `DependenSee \Source\SolutionFolder \Test\MyOutput.html` 52 | - `DependenSee \Source\SolutionFolder -O \Test\MyOutput.html` 53 | - `DependenSee \Source\SolutionFolder -OutputPath \Test\MyOutput.html` 54 | 55 | ## IncludePackages 56 | 57 | Whether to include external (Nuget) packages in the result. 58 | 59 | **Shorthand: `-P`** 60 | 61 | **Default: `False`** 62 | 63 | 64 | ### Examples 65 | 66 | - `DependenSee \Source\SolutionFolder \Test\MyOutput.html -P` 67 | - `DependenSee \Source\SolutionFolder \Test\MyOutput.html -IncludePackages` 68 | 69 | ## OutputType 70 | 71 | Type of output to produce. Following types are available. 72 | 73 | - `Html` - Creates an html document. 74 | - `Json` - Creates a JSON file. 75 | - `Xml` - Creates a XML file. 76 | - `Graphviz` - Creates a Graphviz/DOT file. 77 | - `ConsoleJson` - Writes JSON output to stdout 78 | - `ConsoleXml` - Writes XML output to stdout 79 | - `GonsoleGraphviz` - Writes Graphviz output to stdout 80 | 81 | When a `Console...` type output is specified, the `-OutputPath` can be ommitted. 82 | `Console...` output types may still write warings to `stderr` stream. If you're piping just the `stderr` into another program, consider checking the `stderr` for warnings as well. 83 | 84 | if you'd like to hide `stderr` output from your console output, see `Troubleshooting` section below. 85 | 86 | To visualize Graphviz output, either use 87 | - Use an online visualizer such as [https://dreampuf.github.io/GraphvizOnline](https://dreampuf.github.io/GraphvizOnline) 88 | 89 | - or use tooling from [https://graphviz.org](https://graphviz.org) 90 | - once installed you can use `.\dot.exe DependenSee-OutputFile.dot -oTargetFile.Svg -Tsvg` 91 | - `dot.exe` is located at `your-graphvis-installation/bin` 92 | - see Graphviz docs for [command line usage](https://graphviz.org/doc/info/command.html) and supported [output types](https://graphviz.org/doc/info/output.html) 93 | 94 | **Shorthand: `-T`** 95 | 96 | **Default: `Html`** 97 | 98 | ### Examples 99 | 100 | - `DependenSee \Source\SolutionFolder \Test\MyOutput.html -T Xml` 101 | - `DependenSee \Source\SolutionFolder \Test\MyOutput.html -OutputType Json` 102 | - `DependenSee \Source\SolutionFolder -T ConsoleJson` 103 | 104 | ## HtmlTitle 105 | 106 | Document title for Html output. Ignored when creating output types other than `Html`. 107 | 108 | If your title has spaces, you will need to enclose it in double quotes. The title should not be HTML-encoded ahead of time as DependenSee will do this automatically. 109 | 110 | **Shorthand: `-HT`** 111 | 112 | **Default: `DependenSee`** 113 | 114 | ### Examples 115 | 116 | - `DependenSee \Source\SolutionFolder \Test\MyOutput.html -HtmlTitle "My Graph Title"` 117 | - `DependenSee \Source\SolutionFolder \Test\MyOutput.html -T Html -HtmlTitle my-graph-title` 118 | - `DependenSee \Source\SolutionFolder \Test\MyOutput.html -HT "My Graph Title"` 119 | 120 | ## IncludeProjectNamespaces 121 | Comma separated list of project file prefixes to include. Wildcards not allowed. Only the filename is considered, case insensitive. 122 | 123 | If you want to include spaces between items, make sure you enclose the parameter value in double quotes. 124 | 125 | **Shorthand: `-IPrN`** 126 | 127 | **Default: ``** 128 | 129 | ### Examples 130 | 131 | - `DependenSee \Source\SolutionFolder -O ConsoleJson -IncludeProjectNamespaces MyApp.Extensions,MyApp.Core` 132 | - Includes only projects starting with MyApp.Core and projects starting with MyApp.Extensions 133 | - `DependenSee \Source\SolutionFolder -O ConsoleJson -IPrN MyApp.Extensions,MyApp.Core` 134 | - Includes only projects starting with MyApp.Core and projects starting with MyApp.Extensions 135 | 136 | ## ExcludeProjectNamespaces 137 | 138 | Comma separated list of project file prefixes to exclude. Wildcards not allowed. Only the filename is considered, case insensitive. This must be a subset of includes to be useful. 139 | 140 | If you want to include spaces between items, make sure you enclose the parameter value in double quotes. 141 | 142 | **Shorthand: `-EPrN`** 143 | 144 | **Default: ``** 145 | 146 | ### Examples 147 | 148 | - `DependenSee \Source\SolutionFolder -O ConsoleJson -ExcludeProjectNamespaces MyApp.Extensions, MyApp.Helpers` 149 | - Excludes projects starting with MyApp.Extensions and projects starting with MyApp.Helpers 150 | - `DependenSee \Source\SolutionFolder -O ConsoleJson -EPrN MyApp.Extensions, MyApp.Helpers` 151 | - Excludes projects starting with MyApp.Extensions and projects starting with MyApp.Helpers 152 | 153 | ## IncludePackageNamespaces 154 | Comma separated list of package name prefixes to include. Wildcards not allowed. Only the package name is considered, case insensitive. If specified, `-IncludePackages` is overridden to `True`. 155 | 156 | If you want to include spaces between items, make sure you enclose the parameter value in double quotes. 157 | 158 | **Shorthand: `-IPaN`** 159 | 160 | **Default: ``** 161 | 162 | ### Examples 163 | 164 | - `DependenSee \Source\SolutionFolder -O ConsoleJson -IncludePackageNamespaces Xamarin,Microsoft` 165 | - includes **only** packages starting with Xamarin and packages starting with Microsoft 166 | - `DependenSee \Source\SolutionFolder -O ConsoleJson -IPaN Xamarin,Microsoft` 167 | - includes **only** packages starting with Xamarin and packages starting with Microsoft 168 | 169 | ## ExcludePackageNamespaces 170 | 171 | Comma separated list of package name prefixes to exclude. Wildcards not allowed. Only the filename is considered, case insensitive. If specified, `-IncludePackages` is overridden to `True`. This must be a subset of includes to be useful. 172 | 173 | If you want to include spaces between items, make sure you enclose the parameter value in double quotes. 174 | 175 | **Shorthand: `-EPaN`** 176 | 177 | **Default: ``** 178 | 179 | ## FollowReparsePoints 180 | 181 | Set if you want the scan to follow valid reparse points. This is helpful if your project references are relying on symlinks, NTFS junction points .etc. 182 | 183 | If set to `True` and a junction/reparse point points to a folder that does not exist (`DirectoryInfo.Exists == false`) it is still skipped (with a message to `stderr`). 184 | 185 | If set to `False` and a reparse point is encountered, a message is also emitted with the full path showing which reparse point is skipped. 186 | 187 | **Shorthand: `-FReP`** 188 | 189 | **Default: `False`** 190 | 191 | ### Examples 192 | - `DependenSee \Source\SolutionFolder -FollowReparsePoints` 193 | - `DependenSee \Source\SolutionFolder -FReP` 194 | 195 | ## ExcludeFolders 196 | 197 | Comma Separated list of folders (either absolute paths or relative to `SourceFolder`) to skip during scan, even if there are references to them from your projects. Wildcards not allowed. When a folder is matched and skipped by a rule specified this way, will emit a warning into the `stderr` stream. **Paths here are case insensitive**. 198 | 199 | While you can specify to exclude a path outside of the source folder, and it will be considered if a project has a reference to a project outside of the source folder, this is not recommended. Always run DependenSee against the root folder of your projects to get consitent and correct results. 200 | 201 | **Shorthand: `-EFol`** 202 | 203 | **Default: ``** 204 | 205 | ### Examples 206 | 207 | - `DependenSee \Source\SolutionFolder -ExcludeFolders "\Source\SolutionFolder\docs, Source\SolutionFolder\clientapp\node-modules"` 208 | - `DependenSee \Source\SolutionFolder -EFol "C:\Source\SolutionFolder\docs, \Source\SolutionFolder\clientapp\node-modules"` 209 | --------------------------------------------------------------------------------