From 0b7103166fdfd9138382c9c76baf8f8174710169 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Thu, 5 May 2022 10:10:55 -0500 Subject: [PATCH 01/38] test: add Aruba Cucumber testing Configures the project for BDD-based acceptance testing with Aruba and Cucumber. --- .gitignore | 8 ++++++++ Gemfile | 5 +++++ README.md | 17 +++++++++++++++++ features/help.feature | 18 ++++++++++++++++++ features/support/aruba.rb | 1 + 5 files changed, 49 insertions(+) create mode 100644 Gemfile create mode 100644 features/help.feature create mode 100644 features/support/aruba.rb diff --git a/.gitignore b/.gitignore index c4fb1a9d1..859944258 100644 --- a/.gitignore +++ b/.gitignore @@ -448,3 +448,11 @@ Freshli.Cli/nuget.config # Ignore nuget config as it contains passwords or personal access tokens. nuget.config + +# ignore Ruby cruft +Gemfile.lock + +# ignore generic build locations +bin/ +binpublish/ +exe/ diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..e4bc4e1b0 --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'aruba', '~> 2.0.1' diff --git a/README.md b/README.md index 04bc4643c..ed5d14a4d 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,23 @@ Data (such as dates and numeric formatting) are NOT localized. Dates and numeric We are not sure how to handle documentation, such as this ReadMe, in different languages. If you have any suggestions or would like to help with translations please let us know using the contact information in the [Contributing](#contributing) section. +## Testing + +You can test Freshli using Aruba and Cucumber, which is pre-configured in the repository. + +You will need Ruby installed on your system, and then run: + +```bash +gem install bundler +bundle +``` + +From then on, you can run the Aruba tests with: + +```bash +dotnet build -o bin && bundle exec cucumber +``` + ## Contributing If you have any questions, notice a bug, or have a suggestion/enhancment please let us know by opening a [issue](https://github.com/corgibytes/freshli-cli/issues) or [pull request](https://github.com/corgibytes/freshli-cli/pulls). diff --git a/features/help.feature b/features/help.feature new file mode 100644 index 000000000..c8164359a --- /dev/null +++ b/features/help.feature @@ -0,0 +1,18 @@ +Feature: Freshli.Cli + Scenario: Help + When I run `Corgibytes.Freshli.Cli -h` + Then the output should contain: + """ + Corgibytes.Freshli.Cli + Root Command + + Usage: + Corgibytes.Freshli.Cli [options] [command] + + Options: + -?, -h, --help Show help and usage information + --version Show version information + + Commands: + scan Scan command returns metrics results for given local repository path + """ diff --git a/features/support/aruba.rb b/features/support/aruba.rb new file mode 100644 index 000000000..fb0a661b9 --- /dev/null +++ b/features/support/aruba.rb @@ -0,0 +1 @@ +require 'aruba/cucumber' From dd648b1b2aa4db040fc46ebac5a1bcccc84b1689 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Thu, 5 May 2022 10:11:24 -0500 Subject: [PATCH 02/38] build: update to .NET 6.0 ...because, Freshli should be fresh, no? ;) Also updates GitHub Action tooling. --- .github/workflows/ci.yml | 22 +++++++++---------- .../Corgibytes.Freshli.Cli.Test.csproj | 4 +++- .../Corgibytes.Freshli.Cli.csproj | 3 ++- README.md | 6 ++--- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e93f8fd91..c66aa5b1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: "[Setup] - .NET Core" uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x # .NET Core 3 is required for GitReleaseManager. - name: "[Setup] - .NET Core 3 (GitReleaseManager)" @@ -33,30 +33,30 @@ jobs: dotnet-version: 3.0.x - name: "[Setup] - Install GitVersion" - uses: gittools/actions/gitversion/setup@v0.9.9 + uses: gittools/actions/gitversion/setup@v0.9.13 with: - versionSpec: '5.6.6' + versionSpec: '5.10.0' - name: "[Setup] - Install GitReleaseManager" if: ${{ github.event_name == 'push' }} - uses: gittools/actions/gitreleasemanager/setup@v0.9.9 + uses: gittools/actions/gitreleasemanager/setup@v0.9.13 with: - versionSpec: '0.11.0' + versionSpec: '0.13.0' - name: "[Versioning] - GitVersion Config" - uses: gittools/actions/gitversion/execute@v0.9.9 + uses: gittools/actions/gitversion/execute@v0.9.13 with: useConfigFile: true additionalArguments: '/showConfig' - name: "[Versioning] - Determine Version" - uses: gittools/actions/gitversion/execute@v0.9.9 + uses: gittools/actions/gitversion/execute@v0.9.13 id: gitversion with: useConfigFile: true - name: "[Versioning] - Update csproj Files" - uses: gittools/actions/gitversion/execute@v0.9.9 + uses: gittools/actions/gitversion/execute@v0.9.13 with: useConfigFile: true additionalArguments: '/updateprojectfiles' @@ -118,19 +118,19 @@ jobs: - name: "[Post Publish] - Zip win-x64 Release" uses: papeloto/action-zip@v1 with: - files: Corgibytes.Freshli.Cli/bin/Release/net5.0/win-x64/publish + files: Corgibytes.Freshli.Cli/bin/Release/net6.0/win-x64/publish dest: ${{ env.BUILD_ARTIFACTS_FOLDER }}/freshli-cli-${{ steps.gitversion.outputs.majorMinorPatch }}-win-x64.zip - name: "[Post Publish] - Zip linux-x64 Release" uses: papeloto/action-zip@v1 with: - files: Corgibytes.Freshli.Cli/bin/Release/net5.0/linux-x64/publish + files: Corgibytes.Freshli.Cli/bin/Release/net6.0/linux-x64/publish dest: ${{ env.BUILD_ARTIFACTS_FOLDER }}/freshli-cli-${{ steps.gitversion.outputs.majorMinorPatch }}-linux-x64.zip - name: "[Post Publish] - Zip osx-x64 Release" uses: papeloto/action-zip@v1 with: - files: Corgibytes.Freshli.Cli/bin/Release/net5.0/osx-x64/publish + files: Corgibytes.Freshli.Cli/bin/Release/net6.0/osx-x64/publish dest: ${{ env.BUILD_ARTIFACTS_FOLDER }}/freshli-cli-${{ steps.gitversion.outputs.majorMinorPatch }}-osx-x64.zip - name: "[Post Publish] - View Build Artifacts Folder" diff --git a/Corgibytes.Freshli.Cli.Test/Corgibytes.Freshli.Cli.Test.csproj b/Corgibytes.Freshli.Cli.Test/Corgibytes.Freshli.Cli.Test.csproj index e42eb0dfb..f05972f5e 100644 --- a/Corgibytes.Freshli.Cli.Test/Corgibytes.Freshli.Cli.Test.csproj +++ b/Corgibytes.Freshli.Cli.Test/Corgibytes.Freshli.Cli.Test.csproj @@ -1,9 +1,11 @@ - net5.0 + net6.0 false + + latestmajor diff --git a/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj b/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj index 855d687c2..340a7cfb7 100644 --- a/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj +++ b/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj @@ -1,7 +1,7 @@ Exe - net5.0 + net6.0 true Freshli Freshli analyzes repositories for historical metrics about a project's dependencies. @@ -14,6 +14,7 @@ Corgibytes true freshli + latestmajor diff --git a/README.md b/README.md index ed5d14a4d..64447139e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A tool for displaying historical metrics about a project's dependencies. Run th ## Installing and Running -First you need .NET 5.0 runtime installed which you can find [here](https://dotnet.microsoft.com/download/dotnet/5.0/runtime). After .NET 5.0 is installed you download the latest Freshli executables [here](https://github.com/corgibytes/freshli-cli/releases/latest). Pick the Zip file that matches you OS (Windows, Linux, or MacOs) then: +First you need .NET 6.0 runtime installed which you can find [here](https://dotnet.microsoft.com/download/dotnet/6.0/runtime). After .NET 6.0 is installed you download the latest Freshli executables [here](https://github.com/corgibytes/freshli-cli/releases/latest). Pick the Zip file that matches you OS (Windows, Linux, or MacOs) then: 1) Download it. 2) Extract the Zip file. @@ -40,7 +40,7 @@ Date (yyyy-MM-dd) LibYear UpgradesAvailable Skipped ### .NET Tool -If you have .NET 5.0 SDK [installed](https://dotnet.microsoft.com/download/dotnet/5.0) you can install Freshli as .NET Tool: +If you have .NET 6.0 SDK [installed](https://dotnet.microsoft.com/download/dotnet/6.0) you can install Freshli as .NET Tool: ``` > dotnet tool install Corgibytes.Freshli.Cli -g @@ -184,7 +184,7 @@ Simply the number of dependencies Freshli could not calculate the libyear for on The headings for column output are localized such that the culture settings of the user's computer are used. (This is found in the CurrentUICulture). Currently there are English and Spanish translations with English being the default. -Data (such as dates and numeric formatting) are NOT localized. Dates and numeric formats use the CurrentCulture which is explicitly set to the [invariant culture](https://docs.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.invariantculture?view=net-5.0). +Data (such as dates and numeric formatting) are NOT localized. Dates and numeric formats use the CurrentCulture which is explicitly set to the [invariant culture](https://docs.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.invariantculture?view=net-6.0). We are not sure how to handle documentation, such as this ReadMe, in different languages. If you have any suggestions or would like to help with translations please let us know using the contact information in the [Contributing](#contributing) section. From e93d0a7c98f72f29a6dd5b29450506400a8f3358 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Thu, 5 May 2022 10:17:39 -0500 Subject: [PATCH 03/38] test: silence cucumber message sharing results --- cucumber.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cucumber.yml diff --git a/cucumber.yml b/cucumber.yml new file mode 100644 index 000000000..383453724 --- /dev/null +++ b/cucumber.yml @@ -0,0 +1,2 @@ +default: + --publish-quiet From d25433416867de9d4325da30f2b1a07994152cfb Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Thu, 5 May 2022 10:39:58 -0500 Subject: [PATCH 04/38] test: Add Aruba to GitHub Actions --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c66aa5b1e..694e1abcf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,16 @@ jobs: coverageLocations: | ${{github.workspace}}/Corgibytes.Freshli.Cli.Test/coverage.info:lcov + - name: "[Test] - Setup for Behavior Acceptance Tests" + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6 # Not needed with a .ruby-version file + bundler-cache: true + + - name: "[Test] - Behavior Acceptance Tests" + run: | + dotnet build -o bin && bundle exec cucumber + - name: "[Publish] - Publish win-x64, linux-x64 and osx-x64" run: | dotnet publish -r win-x64 -c Release --self-contained false From 236136288b1b90399d92f466870d3536476ea971 Mon Sep 17 00:00:00 2001 From: mscottford Date: Thu, 5 May 2022 15:51:24 +0000 Subject: [PATCH 05/38] Committing auto generated change log. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d91bec5..0060cd44b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,20 +14,20 @@ __Dependencies__ As part of this release we had [3 issues](https://github.com/corgibytes/freshli-cli/milestone/3?closed=1) closed. + Goals for this milestone: - CLI can be used in a CI build to get the Freshness score. __DevOps__ -- [__#41__](https://github.com/corgibytes/freshli-cli/pull/41) Fixes #33, removes warning for GitReleaseManager on Add Asset - [__#33__](https://github.com/corgibytes/freshli-cli/issues/33) Warning when adding assets to GitHub release in CI +- [__#41__](https://github.com/corgibytes/freshli-cli/pull/41) Fixes #33, removes warning for GitReleaseManager on Add Asset __Enhancement__ - [__#25__](https://github.com/corgibytes/freshli-cli/issues/25) Add Scan and Help commands - ## v0.4.0 From 6b8ef5a13d0576378ad694ef0c89639bb28ab9ca Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Wed, 11 May 2022 15:23:17 -0500 Subject: [PATCH 06/38] refactor: move --output and --format to scan only Eliminates some now-unnecessary inheritance with commands, wherein all commands would include the --output and --format options. Also updates more dependencies to the latest versions. --- CONTRIBUTING.md | 40 +++++++------- .../CommandOptions/ScanCommandOptionsTest.cs | 52 +++++++++++------- .../Commands/ScanCommandTest.cs | 4 +- .../Corgibytes.Freshli.Cli.Test.csproj | 20 +++---- .../CommandOptions/CommandOptions.cs | 17 +----- .../CommandOptions/ScanCommandOptions.cs | 16 +++++- .../CommandRunners/CommandRunner.cs | 6 +-- .../Commands/BaseCommand.cs | 53 ------------------- .../Commands/ScanCommand.cs | 49 +++++++++++++++-- .../Corgibytes.Freshli.Cli.csproj | 11 ++-- .../IoC/FreshliServiceBuilder.cs | 12 ++--- Corgibytes.Freshli.Cli/Program.cs | 13 ++--- 12 files changed, 149 insertions(+), 144 deletions(-) delete mode 100644 Corgibytes.Freshli.Cli/Commands/BaseCommand.cs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b05b564bd..c77afdb27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,11 +10,11 @@ This project can be developed on any platform. To get started, follow instructio ### Prerequisites -This project depends on .NET Core 5.0. Before working on the project, check that .NET Core prerequisites have been met. +This project depends on .NET Core 6.0. Before working on the project, check that .NET Core prerequisites have been met. - - [Prerequisites for .NET Core on Windows](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=net50) - - [Prerequisites for .NET Core on Linux](https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=net50) - - [Prerequisites for .NET Core on macOS](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=net50) + - [Prerequisites for .NET Core on Windows](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=net60) + - [Prerequisites for .NET Core on Linux](https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=net60) + - [Prerequisites for .NET Core on macOS](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=net60) ### Visual Studio @@ -43,7 +43,7 @@ Package | Version #### Command Options and Commands and Command Runners -* Freshli CLI **Commands** are implemented using the [Command Line Api](https://github.com/dotnet/command-line-api) library. All the commands are located under the **Commands** folder and are needed for the library to know how to parse user input and transform it into CommandOptions. +* Freshli CLI **Commands** are implemented using the [Command Line Api](https://github.com/dotnet/command-line-api) library. All the commands are located under the **Commands** folder and are needed for the library to know how to parse user input and transform it into CommandOptions. This type of entity is where you configure the structure of your command. * Freshli CLI **Command Options** represents all the options allowed for a particular Command. The input will be transformed into this object and send to the command runner. All the commands options for all the different commands are located inside the **CommandOptions** folder. * Freshli CLI **Command Runners** are responsible for receiving a Command Options and execute the Run Command logic. It is used by the Command classes to delegate it's execution. @@ -54,13 +54,13 @@ So far, the following commands have been implemented: Follow below steps if you want to contribute with a new command: -1) Add a new class called _**YourNewCommandNameCommandOptions**_ into the **CommandOptions folder** and inherit from the base class **_CommandOptions_**. You do not need to implement anything to allow the **format** and **output** options. These are inherited from the base class. +1) Add a new class called _**YourNewCommandNameCommandOptions**_ into the **CommandOptions folder** and inherit from the base class **_CommandOptions_**. You do not need to implement anything to allow the **format** and **output** options. These are inherited from the base class. Example: CustomCommandOptions ``` public class ScanCommandOptions : CommandOptions - { + { public string YourArgument { get ; set; } public string YourOption { get ; set; } @@ -81,7 +81,7 @@ Example: CustomCommandOptions // you have to reference the command-line-api library documentation. Below are just // examples. See the Commands/ScanCommand.cs for an example or go to the Command Line Api (https://github.com/dotnet/command-line-api) repository for detailed information. - Option yourOption= new(new[] { "--alias1", "alias2" }, description: "Option Description"); + Option yourOption= new(new[] { "--alias1", "alias2" }, description: "Option Description"); AddOption(yourOption); Argument yourArgument = new("yourargumentname", "Argument Description") @@ -90,17 +90,17 @@ Example: CustomCommandOptions } ``` -3) Add a new class called _**YourNewCommandNameCommandRunner**_ into the **CommandRunners** folder and inherit it from **CommandRunner**. +3) Add a new class called _**YourNewCommandNameCommandRunner**_ into the **CommandRunners** folder and inherit it from **CommandRunner**. Implement the Run method. Example: CustomCommandRunners ``` public class CustomCommandRunner : CommandRunner - { + { public ScanCommandRunner(IServiceProvider serviceProvider, Runner runner): base(serviceProvider,runner) { - + } public override int Run(CustomCommandOptions options) @@ -127,7 +127,7 @@ Update the **FreshliServiceBuilder Register** method in order to add the invokat 5) Go to Main and Add your new Command in the _**CreateCommandLineBuilder**_ method. This will allow the main program to identify you added a new command. - + 6) Go to **Corgibytes.Freshli.Cli.Test** and add unit tests. 7) Open a Pull Request with your changes @@ -144,8 +144,8 @@ Available formatters are **json, yaml, and csv**. These formatters can be found If you want to contribute with a formatter, you have to follow below instructions: 1) Add the new format type into the _**FormatType**_ enum. Example: _**Custom**_ - -2) Add a new class called _**YourNewFormatOutputFormatter**_ into the _**Formatters folder**_ and inherit it from OutputFormatter. Implement requred methods + +2) Add a new class called _**YourNewFormatOutputFormatter**_ into the _**Formatters folder**_ and inherit it from OutputFormatter. Implement requred methods Example: CustomOutputFormatter ``` @@ -165,10 +165,10 @@ If you want to contribute with a formatter, you have to follow below instruction } ``` -5) Register your new formatter class in the IoC Container. Open the _**FreshliServiceBuilder.cs**_ file, search for the **RegisterBaseCommand** +5) Register your new formatter class in the IoC Container. Open the _**FreshliServiceBuilder.cs**_ file, search for the **RegisterBaseCommand** method and add a new registration line for your formatter as follows -``` +``` Example: Services.AddNamedScoped(FormatType.Custom); ``` @@ -188,12 +188,12 @@ An Output Strategy is reponsable for sending the serialized response of a comman Available outputs are **console, file**. These formatters can be found under the _**OutputStrategies**_ folder as follows. * **ConsoleOutputStrategy** - Sends serialized data by a formatter to the standard output. -* **FileOutputStrategy** - Sends serialized data by a formatter to a file. +* **FileOutputStrategy** - Sends serialized data by a formatter to a file. If you want to contribute with a new output strategy, you have to follow below instructions: 1) Add the new format type into the _**OutputStrategyType**_ enum. Example: _**Custom**_ - + 2) Add a new class called _**YourNewOutputStrategy**_ into the _**OutputStrategies folder**_ and implement the _**IOutputStrategy**_ interface. Example: CustomOutputStrategy @@ -209,10 +209,10 @@ If you want to contribute with a new output strategy, you have to follow below i } ``` -5) Register your new output strategy class in the IoC Container. Open the _**FreshliServiceBuilder.cs**_ file, search for the **RegisterBaseCommand** +5) Register your new output strategy class in the IoC Container. Open the _**FreshliServiceBuilder.cs**_ file, search for the **RegisterBaseCommand** method and add a new registration line for your strategy as follows -``` +``` Example: Services.AddNamedScoped(OutputStrategyType.Custom); ``` diff --git a/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs b/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs index a1a9a72fc..a6b2468f9 100644 --- a/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs +++ b/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.CommandLine; using System.CommandLine.Builder; using System.CommandLine.Parsing; using System.IO; +using System.Linq; using Corgibytes.Freshli.Cli.Formatters; using Corgibytes.Freshli.Cli.OutputStrategies; using Corgibytes.Freshli.Cli.Test.Common; @@ -25,13 +27,25 @@ public void Send_Args_ReturnsScanOptions(string[] args, string expectedPath, For Parser parser = new(cmBuilder.Command); ParseResult result = parser.Parse(args); + var pathArg = result.CommandResult.Command.Arguments + .Single(x => x.Name == "path"); + DirectoryInfo path = result.GetValueForArgument(pathArg) as DirectoryInfo; - DirectoryInfo path = result.ValueForArgument("path"); - FormatType formatType = result.ValueForOption("--format"); - FormatType formatTypeFromAlias = result.ValueForOption("-f"); + var formatArg = result.CommandResult.Command.Options + .Single(x => x.Name == "format"); + FormatType? formatType = result.GetValueForOption(formatArg) as FormatType?; - IEnumerable outputStrategyTypes = result.ValueForOption>("--output"); - IEnumerable outputStrategyTypesFromAlias = result.ValueForOption>("-o"); + var formatArgAlias = result.CommandResult.Command.Options + .Single(x => x.Aliases.Contains("-f")); + FormatType? formatTypeFromAlias = result.GetValueForOption(formatArgAlias) as FormatType?; + + var outputOption = result.CommandResult.Command.Options + .Single(x => x.Name == "output"); + IEnumerable outputStrategyTypes = result.GetValueForOption(outputOption) as IEnumerable; + + var outputOptionAlias = result.CommandResult.Command.Options + .Single(x => x.Aliases.Contains("-o")); + IEnumerable outputStrategyTypesFromAlias = result.GetValueForOption(outputOptionAlias) as IEnumerable; formatType.Should().Be(formatTypeFromAlias); @@ -58,26 +72,26 @@ public void Send_Args_ReturnsScanOptions(string[] args, string expectedPath, For new List { new object[] { new string[] { "scan", TempPath, "--format", "json", "--output", "console"}, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, - new object[] { new string[] { "scan", TempPath, "--Format", "JSON", "--output", "CONSOLE" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, - new object[] { new string[] { "scan", TempPath, "--format", "csv", "--output", "file", "--output", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, - new object[] { new string[] { "scan", TempPath, "--format", "yaml", "--output", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.File }}, - new object[] { new string[] { "scan", TempPath, "-f", "json", "-o", "console" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, - new object[] { new string[] { "scan", TempPath, "-f", "csv", "-o", "file", "-o", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, - new object[] { new string[] { "scan", TempPath, "-f", "Csv", "-o", "FILE", "-o", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, - new object[] { new string[] { "scan", TempPath, "-f", "yaml", "-o", "console" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console } }, - new object[] { new string[] { "scan", TempPath, "--format", "yaml", "-o", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.File }}, - new object[] { new string[] { "scan", TempPath, "-f", "yaml", "--output", "console","-o", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console, OutputStrategyType.File } }, + // new object[] { new string[] { "scan", TempPath, "--Format", "JSON", "--output", "CONSOLE" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, + // new object[] { new string[] { "scan", TempPath, "--format", "csv", "--output", "file", "--output", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, + // new object[] { new string[] { "scan", TempPath, "--format", "yaml", "--output", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.File }}, + // new object[] { new string[] { "scan", TempPath, "-f", "json", "-o", "console" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, + // new object[] { new string[] { "scan", TempPath, "-f", "csv", "-o", "file", "-o", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, + // new object[] { new string[] { "scan", TempPath, "-f", "Csv", "-o", "FILE", "-o", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, + // new object[] { new string[] { "scan", TempPath, "-f", "yaml", "-o", "console" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console } }, + // new object[] { new string[] { "scan", TempPath, "--format", "yaml", "-o", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.File }}, + // new object[] { new string[] { "scan", TempPath, "-f", "yaml", "--output", "console","-o", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console, OutputStrategyType.File } }, //It should configure the default formatter - new object[] { new string[] { "scan", TempPath , "--output", "console" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, - new object[] { new string[] { "scan", TempPath, "-o", "file" }, TempPath, FormatType.Json, new List() { OutputStrategyType.File} }, + // new object[] { new string[] { "scan", TempPath , "--output", "console" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, + // new object[] { new string[] { "scan", TempPath, "-o", "file" }, TempPath, FormatType.Json, new List() { OutputStrategyType.File} }, //It should configure the default output - new object[] { new string[] { "scan", TempPath, "--format", "yaml" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console } }, - new object[] { new string[] { "scan", TempPath, "-f", "csv" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.Console } }, + // new object[] { new string[] { "scan", TempPath, "--format", "yaml" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console } }, + // new object[] { new string[] { "scan", TempPath, "-f", "csv" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.Console } }, //It should configure the default formatter and default output - new object[] { new string[] { "scan", TempPath }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } } + // new object[] { new string[] { "scan", TempPath }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } } }; } } diff --git a/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs b/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs index 362fd2448..b1c5095a3 100644 --- a/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs +++ b/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs @@ -54,7 +54,7 @@ public void Verify_handler_configuration() scanCommand.Handler.Should().NotBeNull(); } - [Fact(Skip = "Will until we have a way to mock the freshli lib call")] + [Fact(Skip = "Will until we have a way to mock the freshli lib call")] public async Task Verify_handler_is_executed() { CommandLineBuilder cmdBuilder = Program.CreateCommandLineBuilder(); @@ -67,7 +67,7 @@ await cmdBuilder.UseDefaults() _console.Out.ToString().Should().NotContain("Exception has been thrown by the target of an invocation"); } - private static void VerifyAlias(string alias, IArgumentArity arity, bool allowMultipleArgumentsPerToken) + private static void VerifyAlias(string alias, ArgumentArity arity, bool allowMultipleArgumentsPerToken) { ScanCommand scanCommand = new(); Option option = scanCommand.Options.FirstOrDefault(x => x.Aliases.Contains(alias)); diff --git a/Corgibytes.Freshli.Cli.Test/Corgibytes.Freshli.Cli.Test.csproj b/Corgibytes.Freshli.Cli.Test/Corgibytes.Freshli.Cli.Test.csproj index f05972f5e..fe566f946 100644 --- a/Corgibytes.Freshli.Cli.Test/Corgibytes.Freshli.Cli.Test.csproj +++ b/Corgibytes.Freshli.Cli.Test/Corgibytes.Freshli.Cli.Test.csproj @@ -9,22 +9,22 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Corgibytes.Freshli.Cli/CommandOptions/CommandOptions.cs b/Corgibytes.Freshli.Cli/CommandOptions/CommandOptions.cs index e9e726f98..3ed0f4c93 100644 --- a/Corgibytes.Freshli.Cli/CommandOptions/CommandOptions.cs +++ b/Corgibytes.Freshli.Cli/CommandOptions/CommandOptions.cs @@ -1,17 +1,4 @@ -using System.Collections.Generic; -using Corgibytes.Freshli.Cli.Formatters; -using Corgibytes.Freshli.Cli.OutputStrategies; - -namespace Corgibytes.Freshli.Cli.CommandOptions +namespace Corgibytes.Freshli.Cli.CommandOptions { - public abstract class CommandOptions - { - public FormatType Format { get; set; } - public IList Output { get; set; } - - protected CommandOptions() - { - Output = new List(); - } - } + public abstract class CommandOptions { } } diff --git a/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs b/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs index f68e72195..4dd38e470 100644 --- a/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs +++ b/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs @@ -1,7 +1,19 @@ -namespace Corgibytes.Freshli.Cli.CommandOptions +using System.Collections.Generic; +using Corgibytes.Freshli.Cli.Formatters; +using Corgibytes.Freshli.Cli.OutputStrategies; + +namespace Corgibytes.Freshli.Cli.CommandOptions { public class ScanCommandOptions : CommandOptions - { + { + public FormatType Format { get; set; } + + public IList Output { get; set; } = new List(); public string Path { get ; set; } + + public ScanCommandOptions() + { + // Output = new List(); + } } } diff --git a/Corgibytes.Freshli.Cli/CommandRunners/CommandRunner.cs b/Corgibytes.Freshli.Cli/CommandRunners/CommandRunner.cs index b32762269..f5b570e20 100644 --- a/Corgibytes.Freshli.Cli/CommandRunners/CommandRunner.cs +++ b/Corgibytes.Freshli.Cli/CommandRunners/CommandRunner.cs @@ -2,8 +2,8 @@ using Corgibytes.Freshli.Lib; namespace Corgibytes.Freshli.Cli.CommandRunners -{ - public abstract class CommandRunner : ICommandRunner where T : CommandOptions.CommandOptions +{ + public abstract class CommandRunner : ICommandRunner where T : CommandOptions.CommandOptions { protected Runner Runner { get; } protected IServiceProvider Services { get; } @@ -15,6 +15,6 @@ public CommandRunner(IServiceProvider serviceProvider, Runner runner) } public abstract int Run(T options); - + } } diff --git a/Corgibytes.Freshli.Cli/Commands/BaseCommand.cs b/Corgibytes.Freshli.Cli/Commands/BaseCommand.cs deleted file mode 100644 index 7fcfaf572..000000000 --- a/Corgibytes.Freshli.Cli/Commands/BaseCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.CommandLine; -using System.CommandLine.Invocation; -using Corgibytes.Freshli.Cli.CommandRunners; -using Corgibytes.Freshli.Cli.Formatters; -using Corgibytes.Freshli.Cli.OutputStrategies; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Corgibytes.Freshli.Cli.Commands -{ - public abstract class BaseCommand : Command where T: CommandOptions.CommandOptions - { - protected BaseCommand(string name, string description = null) : base(name, description) - { - Option formatOption = new(new[] { "--format", "-f" }, - description: "Represents the output format type - It's value is case insensitive", - getDefaultValue: () => FormatType.Json) - { - AllowMultipleArgumentsPerToken = false, - Arity = ArgumentArity.ExactlyOne, - }; - - Option> outputOption = new(new[] { "--output", "-o" }, - description: "Represents where you want to output the result. This option is case sensitive and you can specify more than one by including it multiple times. Allowed values are [ console | file ]", - getDefaultValue: () => new List() { OutputStrategyType.Console }) - { - AllowMultipleArgumentsPerToken = true, - Arity = ArgumentArity.OneOrMore, - }; - - AddOption(formatOption); - AddOption(outputOption); - - Handler = CommandHandler.Create(Run); - - } - - private void Run(IHost host, InvocationContext context, T options) - { - if (options == null) - throw new ArgumentNullException(nameof(options)); - - context.Console.Out.Write($"CliOutput.ScanCommand_ScanCommand_Executing_scan_command_handler\n"); - - using IServiceScope scope = host.Services.CreateScope(); - ICommandRunner runner = scope.ServiceProvider.GetRequiredService>(); - - runner.Run(options); - } - } -} diff --git a/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs b/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs index 3198d7f7e..1dda3be6c 100644 --- a/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs +++ b/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs @@ -1,19 +1,62 @@ -using System.CommandLine; +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.NamingConventionBinder; using Corgibytes.Freshli.Cli.CommandOptions; +using Corgibytes.Freshli.Cli.CommandRunners; +using Corgibytes.Freshli.Cli.Formatters; +using Corgibytes.Freshli.Cli.OutputStrategies; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace Corgibytes.Freshli.Cli.Commands { - public class ScanCommand : BaseCommand + public class ScanCommand : Command { public ScanCommand() : base("scan", "Scan command returns metrics results for given local repository path") { + Option formatOption = new(new[] { "--format", "-f" }, + description: "Represents the output format type - It's value is case insensitive", + getDefaultValue: () => FormatType.Json) + { + AllowMultipleArgumentsPerToken = false, + Arity = ArgumentArity.ExactlyOne, + }; + + Option> outputOption = new(new[] { "--output", "-o" }, + description: "Represents where you want to output the result. This option is case sensitive and you can specify more than one by including it multiple times. Allowed values are [ console | file ]", + getDefaultValue: () => new List() { OutputStrategyType.Console }) + { + AllowMultipleArgumentsPerToken = true, + Arity = ArgumentArity.OneOrMore, + }; + + AddOption(formatOption); + AddOption(outputOption); + Argument pathArgument = new("path", "Source code repository path") { Arity = ArgumentArity.ExactlyOne }; - AddArgument(pathArgument); + AddArgument(pathArgument); + + Handler = CommandHandler.Create(Run); + } + + private void Run(IHost host, InvocationContext context, ScanCommandOptions options) + { + if (options == null) + throw new ArgumentNullException(nameof(options)); + + context.Console.Out.Write($"CliOutput.ScanCommand_ScanCommand_Executing_scan_command_handler\n"); + + using IServiceScope scope = host.Services.CreateScope(); + ICommandRunner runner = scope.ServiceProvider.GetRequiredService>(); + + runner.Run(options); } } } diff --git a/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj b/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj index 340a7cfb7..ac47a25a5 100644 --- a/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj +++ b/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj @@ -21,13 +21,13 @@ LICENSE - - + + - - - + + + @@ -42,5 +42,6 @@ True CliOutput.resx + diff --git a/Corgibytes.Freshli.Cli/IoC/FreshliServiceBuilder.cs b/Corgibytes.Freshli.Cli/IoC/FreshliServiceBuilder.cs index 24d08dced..6856f9c1c 100644 --- a/Corgibytes.Freshli.Cli/IoC/FreshliServiceBuilder.cs +++ b/Corgibytes.Freshli.Cli/IoC/FreshliServiceBuilder.cs @@ -27,16 +27,16 @@ public void Register() public void RegisterBaseCommand() { Services.AddScoped(); - Services.AddNamedScoped(FormatType.Json); - Services.AddNamedScoped(FormatType.Csv); - Services.AddNamedScoped(FormatType.Yaml); - Services.AddNamedScoped(OutputStrategyType.File); - Services.AddNamedScoped(OutputStrategyType.Console); } public void RegisterScanCommand() - { + { Services.AddScoped, ScanCommandRunner>(); + Services.AddNamedScoped(FormatType.Json); + Services.AddNamedScoped(FormatType.Csv); + Services.AddNamedScoped(FormatType.Yaml); + Services.AddNamedScoped(OutputStrategyType.File); + Services.AddNamedScoped(OutputStrategyType.Console); Services.AddOptions().BindCommandLine(); } } diff --git a/Corgibytes.Freshli.Cli/Program.cs b/Corgibytes.Freshli.Cli/Program.cs index b8ebbe418..a360bba72 100644 --- a/Corgibytes.Freshli.Cli/Program.cs +++ b/Corgibytes.Freshli.Cli/Program.cs @@ -14,7 +14,7 @@ namespace Corgibytes.Freshli.Cli { public class Program { - private static readonly Logger s_logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger s_logger = LogManager.GetCurrentClassLogger(); public static async Task Main(string[] args) { @@ -33,15 +33,16 @@ static IHostBuilder CreateHostBuilder(string[] args) => public static CommandLineBuilder CreateCommandLineBuilder() { - CommandLineBuilder builder = new CommandLineBuilder(new MainCommand()) + var command = new MainCommand {new ScanCommand()}; + + CommandLineBuilder builder = new CommandLineBuilder(command) .UseHost(CreateHostBuilder) - .UseMiddleware(async (context, next) => + .AddMiddleware(async (context, next) => { await LogExecution(context, next); }) - .UseExceptionHandler() - .UseHelp() - .AddCommand(new ScanCommand()); + .UseExceptionHandler() + .UseHelp(); return builder; } From ba6b96df48a4d8a746e67ee01605f0ae015297d7 Mon Sep 17 00:00:00 2001 From: Maira Daniela Ferrari Date: Wed, 11 May 2022 23:23:08 -0300 Subject: [PATCH 07/38] fix Send_Args_ReturnsScanOptions test --- .../CommandOptions/ScanCommandOptionsTest.cs | 54 ++++++++----------- Corgibytes.Freshli.Cli.Test/Extensions.cs | 31 +++++++++++ .../CommandOptions/ScanCommandOptions.cs | 9 ++-- .../CommandRunners/ScanCommandRunner.cs | 4 +- .../Commands/ScanCommand.cs | 3 +- .../OutputStrategies/FileOutputStrategy.cs | 2 +- 6 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 Corgibytes.Freshli.Cli.Test/Extensions.cs diff --git a/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs b/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs index a6b2468f9..10baf0928 100644 --- a/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs +++ b/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Builder; using System.CommandLine.Parsing; @@ -27,25 +28,12 @@ public void Send_Args_ReturnsScanOptions(string[] args, string expectedPath, For Parser parser = new(cmBuilder.Command); ParseResult result = parser.Parse(args); - var pathArg = result.CommandResult.Command.Arguments - .Single(x => x.Name == "path"); - DirectoryInfo path = result.GetValueForArgument(pathArg) as DirectoryInfo; - var formatArg = result.CommandResult.Command.Options - .Single(x => x.Name == "format"); - FormatType? formatType = result.GetValueForOption(formatArg) as FormatType?; - - var formatArgAlias = result.CommandResult.Command.Options - .Single(x => x.Aliases.Contains("-f")); - FormatType? formatTypeFromAlias = result.GetValueForOption(formatArgAlias) as FormatType?; - - var outputOption = result.CommandResult.Command.Options - .Single(x => x.Name == "output"); - IEnumerable outputStrategyTypes = result.GetValueForOption(outputOption) as IEnumerable; - - var outputOptionAlias = result.CommandResult.Command.Options - .Single(x => x.Aliases.Contains("-o")); - IEnumerable outputStrategyTypesFromAlias = result.GetValueForOption(outputOptionAlias) as IEnumerable; + DirectoryInfo path = result.GetArgumentByName("path"); + FormatType formatType = result.GetOptionByName("format"); + FormatType formatTypeFromAlias = result.GetOptionByAlias("-f"); + IEnumerable outputStrategyTypes = result.GetOptionByName>("output"); + IEnumerable outputStrategyTypesFromAlias = result.GetOptionByAlias>("-o"); formatType.Should().Be(formatTypeFromAlias); @@ -72,26 +60,26 @@ public void Send_Args_ReturnsScanOptions(string[] args, string expectedPath, For new List { new object[] { new string[] { "scan", TempPath, "--format", "json", "--output", "console"}, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, - // new object[] { new string[] { "scan", TempPath, "--Format", "JSON", "--output", "CONSOLE" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, - // new object[] { new string[] { "scan", TempPath, "--format", "csv", "--output", "file", "--output", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, - // new object[] { new string[] { "scan", TempPath, "--format", "yaml", "--output", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.File }}, - // new object[] { new string[] { "scan", TempPath, "-f", "json", "-o", "console" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, - // new object[] { new string[] { "scan", TempPath, "-f", "csv", "-o", "file", "-o", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, - // new object[] { new string[] { "scan", TempPath, "-f", "Csv", "-o", "FILE", "-o", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, - // new object[] { new string[] { "scan", TempPath, "-f", "yaml", "-o", "console" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console } }, - // new object[] { new string[] { "scan", TempPath, "--format", "yaml", "-o", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.File }}, - // new object[] { new string[] { "scan", TempPath, "-f", "yaml", "--output", "console","-o", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console, OutputStrategyType.File } }, + new object[] { new string[] { "scan", TempPath, "--Format", "JSON", "--output", "CONSOLE" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, + new object[] { new string[] { "scan", TempPath, "--format", "csv", "--output", "file", "--output", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, + new object[] { new string[] { "scan", TempPath, "--format", "yaml", "--output", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.File }}, + new object[] { new string[] { "scan", TempPath, "-f", "json", "-o", "console" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, + new object[] { new string[] { "scan", TempPath, "-f", "csv", "-o", "file", "-o", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, + new object[] { new string[] { "scan", TempPath, "-f", "Csv", "-o", "FILE", "-o", "console" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.File, OutputStrategyType.Console }}, + new object[] { new string[] { "scan", TempPath, "-f", "yaml", "-o", "console" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console } }, + new object[] { new string[] { "scan", TempPath, "--format", "yaml", "-o", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.File }}, + new object[] { new string[] { "scan", TempPath, "-f", "yaml", "--output", "console","-o", "file" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console, OutputStrategyType.File } }, //It should configure the default formatter - // new object[] { new string[] { "scan", TempPath , "--output", "console" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, - // new object[] { new string[] { "scan", TempPath, "-o", "file" }, TempPath, FormatType.Json, new List() { OutputStrategyType.File} }, + new object[] { new string[] { "scan", TempPath , "--output", "console" }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } }, + new object[] { new string[] { "scan", TempPath, "-o", "file" }, TempPath, FormatType.Json, new List() { OutputStrategyType.File} }, //It should configure the default output - // new object[] { new string[] { "scan", TempPath, "--format", "yaml" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console } }, - // new object[] { new string[] { "scan", TempPath, "-f", "csv" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.Console } }, + new object[] { new string[] { "scan", TempPath, "--format", "yaml" }, TempPath, FormatType.Yaml, new List() { OutputStrategyType.Console } }, + new object[] { new string[] { "scan", TempPath, "-f", "csv" }, TempPath, FormatType.Csv, new List() { OutputStrategyType.Console } }, //It should configure the default formatter and default output - // new object[] { new string[] { "scan", TempPath }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } } + new object[] { new string[] { "scan", TempPath }, TempPath, FormatType.Json, new List() { OutputStrategyType.Console } } }; } } diff --git a/Corgibytes.Freshli.Cli.Test/Extensions.cs b/Corgibytes.Freshli.Cli.Test/Extensions.cs new file mode 100644 index 000000000..f740a38b6 --- /dev/null +++ b/Corgibytes.Freshli.Cli.Test/Extensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Parsing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Corgibytes.Freshli.Cli.Test +{ + public static class ParseResultExtensions + { + public static T GetArgumentByName(this ParseResult result, string name) + { + Argument arg = result.CommandResult.Command.Arguments.Single(x => x.Name.Equals(name)) as Argument; + return result.GetValueForArgument(arg); + } + + public static T GetOptionByName(this ParseResult result, string name) + { + Option option = result.CommandResult.Command.Options.Single(x => x.Name.Equals(name)) as Option; + return result.GetValueForOption(option); + } + + public static T GetOptionByAlias(this ParseResult result, string alias) + { + Option option= result.CommandResult.Command.Options.Single(x => x.Aliases.Contains(alias)) as Option; + return result.GetValueForOption(option); + } + } +} diff --git a/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs b/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs index 4dd38e470..e7b602b7e 100644 --- a/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs +++ b/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using Corgibytes.Freshli.Cli.Formatters; using Corgibytes.Freshli.Cli.OutputStrategies; @@ -9,11 +10,7 @@ public class ScanCommandOptions : CommandOptions public FormatType Format { get; set; } public IList Output { get; set; } = new List(); - public string Path { get ; set; } - - public ScanCommandOptions() - { - // Output = new List(); - } + public DirectoryInfo Path { get ; set; } + } } diff --git a/Corgibytes.Freshli.Cli/CommandRunners/ScanCommandRunner.cs b/Corgibytes.Freshli.Cli/CommandRunners/ScanCommandRunner.cs index a2f1b4aac..125c3250b 100644 --- a/Corgibytes.Freshli.Cli/CommandRunners/ScanCommandRunner.cs +++ b/Corgibytes.Freshli.Cli/CommandRunners/ScanCommandRunner.cs @@ -18,7 +18,7 @@ public ScanCommandRunner(IServiceProvider serviceProvider, Runner runner): base( public override int Run(ScanCommandOptions options) { - if (string.IsNullOrWhiteSpace(options.Path)) + if (string.IsNullOrWhiteSpace(options.Path?.FullName)) { throw new ArgumentNullException(nameof(options), CliOutput.ScanCommandRunner_Run_Path_should_not_be_null_or_empty); } @@ -26,7 +26,7 @@ public override int Run(ScanCommandOptions options) IOutputFormatter formatter = options.Format.ToFormatter(Services); IEnumerable outputStrategies = options.Output.ToOutputStrategies(Services); - IList results = Runner.Run(options.Path); + IList results = Runner.Run(options.Path.FullName); foreach (IOutputStrategy output in outputStrategies) { diff --git a/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs b/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs index 1dda3be6c..515499e14 100644 --- a/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs +++ b/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs @@ -3,6 +3,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.CommandLine.NamingConventionBinder; +using System.IO; using Corgibytes.Freshli.Cli.CommandOptions; using Corgibytes.Freshli.Cli.CommandRunners; using Corgibytes.Freshli.Cli.Formatters; @@ -36,7 +37,7 @@ public class ScanCommand : Command AddOption(formatOption); AddOption(outputOption); - Argument pathArgument = new("path", "Source code repository path") + Argument pathArgument = new("path", "Source code repository path") { Arity = ArgumentArity.ExactlyOne }; diff --git a/Corgibytes.Freshli.Cli/OutputStrategies/FileOutputStrategy.cs b/Corgibytes.Freshli.Cli/OutputStrategies/FileOutputStrategy.cs index 637a07ddb..5e1a66856 100644 --- a/Corgibytes.Freshli.Cli/OutputStrategies/FileOutputStrategy.cs +++ b/Corgibytes.Freshli.Cli/OutputStrategies/FileOutputStrategy.cs @@ -13,7 +13,7 @@ public class FileOutputStrategy : IOutputStrategy public virtual void Send(IList results, IOutputFormatter formatter, ScanCommandOptions options) { - string path = Path.Combine(options.Path ?? string.Empty, $"freshli-scan-{DateTime.Now:yyyyMMddTHHmmss}.{options.Format}"); + string path = Path.Combine(options.Path?.FullName ?? string.Empty, $"freshli-scan-{DateTime.Now:yyyyMMddTHHmmss}.{options.Format}"); StreamWriter file = File.CreateText(path); file.WriteLine(formatter.Format(results)); } From 395dda7699ae70edc5f6f05114fdaee12e8ca116 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Thu, 12 May 2022 10:42:12 -0500 Subject: [PATCH 08/38] feat: add 'cache' and 'cache prepare' commands Implements the 'cache' command, which has a 'prepare' subcommand. At present, this does nothing. --- .../CommandOptions/CacheCommandOptions.cs | 10 +++++ .../CommandRunners/CacheCommandRunner.cs | 38 +++++++++++++++++++ .../Commands/CacheCommand.cs | 36 ++++++++++++++++++ .../IoC/FreshliServiceBuilder.cs | 10 +++++ Corgibytes.Freshli.Cli/Program.cs | 3 +- 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 Corgibytes.Freshli.Cli/CommandOptions/CacheCommandOptions.cs create mode 100644 Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs create mode 100644 Corgibytes.Freshli.Cli/Commands/CacheCommand.cs diff --git a/Corgibytes.Freshli.Cli/CommandOptions/CacheCommandOptions.cs b/Corgibytes.Freshli.Cli/CommandOptions/CacheCommandOptions.cs new file mode 100644 index 000000000..c311b1e5f --- /dev/null +++ b/Corgibytes.Freshli.Cli/CommandOptions/CacheCommandOptions.cs @@ -0,0 +1,10 @@ +namespace Corgibytes.Freshli.Cli.CommandOptions +{ + public class CacheCommandOptions : CommandOptions + { + } + + public class CachePrepareCommandOptions : CommandOptions + { + } +} diff --git a/Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs b/Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs new file mode 100644 index 000000000..62292f5ee --- /dev/null +++ b/Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Corgibytes.Freshli.Cli.CommandOptions; +using Corgibytes.Freshli.Lib; + +namespace Corgibytes.Freshli.Cli.CommandRunners +{ + public class CacheCommandRunner : CommandRunner + { + public CacheCommandRunner(IServiceProvider serviceProvider, Runner runner) + : base(serviceProvider, runner) + { + + } + + public override int Run(CacheCommandOptions options) + { + return 0; + } + } + + public class CachePrepareCommandRunner : CommandRunner + { + public CachePrepareCommandRunner(IServiceProvider serviceProvider, Runner runner) + : base(serviceProvider, runner) + { + + } + + public override int Run(CachePrepareCommandOptions options) + { + // TODO: implementation here + Console.Out.WriteLine("YOU DID IT (Runner)!"); + return 0; + } + } +} + diff --git a/Corgibytes.Freshli.Cli/Commands/CacheCommand.cs b/Corgibytes.Freshli.Cli/Commands/CacheCommand.cs new file mode 100644 index 000000000..b71cd3919 --- /dev/null +++ b/Corgibytes.Freshli.Cli/Commands/CacheCommand.cs @@ -0,0 +1,36 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.NamingConventionBinder; +using Corgibytes.Freshli.Cli.CommandOptions; +using Corgibytes.Freshli.Cli.CommandRunners; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Corgibytes.Freshli.Cli.Commands +{ + public class CacheCommand : Command + { + public CacheCommand() : base("cache", "Manages the local cache database") + { + CachePrepareCommand prepare = new(); + AddCommand(prepare); + } + } + + public class CachePrepareCommand : Command + { + public CachePrepareCommand() : base("prepare", + "Ensures the cache directory exists and contains a valid cache database.") + { + Handler = CommandHandler.Create(Run); + } + + private void Run(IHost host, InvocationContext context, CachePrepareCommandOptions options) + { + using IServiceScope scope = host.Services.CreateScope(); + ICommandRunner runner = scope.ServiceProvider.GetRequiredService>(); + runner.Run(options); + } + } + +} diff --git a/Corgibytes.Freshli.Cli/IoC/FreshliServiceBuilder.cs b/Corgibytes.Freshli.Cli/IoC/FreshliServiceBuilder.cs index 6856f9c1c..4c485bcb6 100644 --- a/Corgibytes.Freshli.Cli/IoC/FreshliServiceBuilder.cs +++ b/Corgibytes.Freshli.Cli/IoC/FreshliServiceBuilder.cs @@ -22,6 +22,7 @@ public void Register() { RegisterBaseCommand(); RegisterScanCommand(); + RegisterCacheCommand(); } public void RegisterBaseCommand() @@ -39,5 +40,14 @@ public void RegisterScanCommand() Services.AddNamedScoped(OutputStrategyType.Console); Services.AddOptions().BindCommandLine(); } + + public void RegisterCacheCommand() + { + Services.AddScoped, CacheCommandRunner>(); + Services.AddOptions().BindCommandLine(); + + Services.AddScoped, CachePrepareCommandRunner>(); + Services.AddOptions().BindCommandLine(); + } } } diff --git a/Corgibytes.Freshli.Cli/Program.cs b/Corgibytes.Freshli.Cli/Program.cs index a360bba72..57acc1e20 100644 --- a/Corgibytes.Freshli.Cli/Program.cs +++ b/Corgibytes.Freshli.Cli/Program.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.CommandLine.Builder; using System.CommandLine.Hosting; using System.CommandLine.Invocation; @@ -33,7 +32,7 @@ static IHostBuilder CreateHostBuilder(string[] args) => public static CommandLineBuilder CreateCommandLineBuilder() { - var command = new MainCommand {new ScanCommand()}; + var command = new MainCommand {new ScanCommand(), new CacheCommand()}; CommandLineBuilder builder = new CommandLineBuilder(command) .UseHost(CreateHostBuilder) From 665542a92f7dc7cb547bb039b29f649b0afd2f33 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Thu, 12 May 2022 13:15:38 -0500 Subject: [PATCH 09/38] feat: add global --cache-dir option Adds a --cache-dir global option with a default value of $HOME/.freshli, for storing temporary files. --- .../CommandOptions/CommandOptions.cs | 9 +++++++-- .../CommandOptions/ScanCommandOptions.cs | 2 +- Corgibytes.Freshli.Cli/Commands/MainCommand.cs | 17 +++++++++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Corgibytes.Freshli.Cli/CommandOptions/CommandOptions.cs b/Corgibytes.Freshli.Cli/CommandOptions/CommandOptions.cs index 3ed0f4c93..9cf0661f8 100644 --- a/Corgibytes.Freshli.Cli/CommandOptions/CommandOptions.cs +++ b/Corgibytes.Freshli.Cli/CommandOptions/CommandOptions.cs @@ -1,4 +1,9 @@ -namespace Corgibytes.Freshli.Cli.CommandOptions +using System.IO; + +namespace Corgibytes.Freshli.Cli.CommandOptions { - public abstract class CommandOptions { } + public abstract class CommandOptions + { + public DirectoryInfo CacheDir { get ; set; } + } } diff --git a/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs b/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs index e7b602b7e..a1463c553 100644 --- a/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs +++ b/Corgibytes.Freshli.Cli/CommandOptions/ScanCommandOptions.cs @@ -11,6 +11,6 @@ public class ScanCommandOptions : CommandOptions public IList Output { get; set; } = new List(); public DirectoryInfo Path { get ; set; } - + } } diff --git a/Corgibytes.Freshli.Cli/Commands/MainCommand.cs b/Corgibytes.Freshli.Cli/Commands/MainCommand.cs index c4ba63719..80f71df42 100644 --- a/Corgibytes.Freshli.Cli/Commands/MainCommand.cs +++ b/Corgibytes.Freshli.Cli/Commands/MainCommand.cs @@ -1,9 +1,22 @@ -using System.CommandLine; +using System; +using System.CommandLine; +using System.IO; namespace Corgibytes.Freshli.Cli.Commands { public class MainCommand : RootCommand { - public MainCommand() : base("Root Command") { } + public MainCommand() : base("Root Command") + { + Option cacheDirOption = new( + new[] {"--cache-dir"}, + description: "The location for storing temporary files", + getDefaultValue: () => new DirectoryInfo(Environment.GetEnvironmentVariable("HOME") + "/.freshli")) + { + Arity = ArgumentArity.ExactlyOne + }; + + AddGlobalOption(cacheDirOption); + } } } From e6bdbafccbcf1977aa042543e376088f675a52a6 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Mon, 16 May 2022 10:03:18 -0500 Subject: [PATCH 10/38] bug: return code from ScanCommand.Run() The ScanCommandRunner.Run() function returns an integer return code (0 if successful), and the invocation of ScanCommand.Run() expects a return code, but ScanCommand.Run() incorrectly is 'void'. This changes the return of Run() so it no longer drops this return code. --- Corgibytes.Freshli.Cli/Commands/ScanCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs b/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs index 515499e14..88c294f32 100644 --- a/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs +++ b/Corgibytes.Freshli.Cli/Commands/ScanCommand.cs @@ -47,7 +47,7 @@ public class ScanCommand : Command Handler = CommandHandler.Create(Run); } - private void Run(IHost host, InvocationContext context, ScanCommandOptions options) + private int Run(IHost host, InvocationContext context, ScanCommandOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -57,7 +57,7 @@ private void Run(IHost host, InvocationContext context, ScanCommandOptions optio using IServiceScope scope = host.Services.CreateScope(); ICommandRunner runner = scope.ServiceProvider.GetRequiredService>(); - runner.Run(options); + return runner.Run(options); } } } From c2a063744225a7aebfcccdba9c37bad37ec4ad02 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Mon, 16 May 2022 17:14:03 -0500 Subject: [PATCH 11/38] feat: implement 'cache prepare' command Creates the database file at the user-specified (or default) location for the cache, and/or migrates the existing file. If the file is corrupt, the command fails with a non-zero exit code. --- .../CommandRunners/CacheCommandRunner.cs | 7 +- .../Commands/CacheCommand.cs | 4 +- .../Commands/MainCommand.cs | 6 +- .../Corgibytes.Freshli.Cli.csproj | 13 ++- Corgibytes.Freshli.Cli/Functionality/Cache.cs | 88 +++++++++++++++++++ .../20220516190023_InitialCreate.Designer.cs | 44 ++++++++++ .../20220516190023_InitialCreate.cs | 41 +++++++++ .../Migrations/CacheContextModelSnapshot.cs | 41 +++++++++ 8 files changed, 233 insertions(+), 11 deletions(-) create mode 100644 Corgibytes.Freshli.Cli/Functionality/Cache.cs create mode 100644 Corgibytes.Freshli.Cli/Migrations/20220516190023_InitialCreate.Designer.cs create mode 100644 Corgibytes.Freshli.Cli/Migrations/20220516190023_InitialCreate.cs create mode 100644 Corgibytes.Freshli.Cli/Migrations/CacheContextModelSnapshot.cs diff --git a/Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs b/Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs index 62292f5ee..36bf3649d 100644 --- a/Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs +++ b/Corgibytes.Freshli.Cli/CommandRunners/CacheCommandRunner.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; using Corgibytes.Freshli.Cli.CommandOptions; +using Corgibytes.Freshli.Cli.Functionality; using Corgibytes.Freshli.Lib; namespace Corgibytes.Freshli.Cli.CommandRunners @@ -29,9 +29,8 @@ public CachePrepareCommandRunner(IServiceProvider serviceProvider, Runner runner public override int Run(CachePrepareCommandOptions options) { - // TODO: implementation here - Console.Out.WriteLine("YOU DID IT (Runner)!"); - return 0; + bool success = Cache.Prepare(options.CacheDir); + return success ? 0 : 1; } } } diff --git a/Corgibytes.Freshli.Cli/Commands/CacheCommand.cs b/Corgibytes.Freshli.Cli/Commands/CacheCommand.cs index b71cd3919..ed2d67fc4 100644 --- a/Corgibytes.Freshli.Cli/Commands/CacheCommand.cs +++ b/Corgibytes.Freshli.Cli/Commands/CacheCommand.cs @@ -25,11 +25,11 @@ public CachePrepareCommand() : base("prepare", Handler = CommandHandler.Create(Run); } - private void Run(IHost host, InvocationContext context, CachePrepareCommandOptions options) + private int Run(IHost host, InvocationContext context, CachePrepareCommandOptions options) { using IServiceScope scope = host.Services.CreateScope(); ICommandRunner runner = scope.ServiceProvider.GetRequiredService>(); - runner.Run(options); + return runner.Run(options); } } diff --git a/Corgibytes.Freshli.Cli/Commands/MainCommand.cs b/Corgibytes.Freshli.Cli/Commands/MainCommand.cs index 80f71df42..086492cbc 100644 --- a/Corgibytes.Freshli.Cli/Commands/MainCommand.cs +++ b/Corgibytes.Freshli.Cli/Commands/MainCommand.cs @@ -1,6 +1,6 @@ -using System; -using System.CommandLine; +using System.CommandLine; using System.IO; +using Corgibytes.Freshli.Cli.Functionality; namespace Corgibytes.Freshli.Cli.Commands { @@ -11,7 +11,7 @@ public MainCommand() : base("Root Command") Option cacheDirOption = new( new[] {"--cache-dir"}, description: "The location for storing temporary files", - getDefaultValue: () => new DirectoryInfo(Environment.GetEnvironmentVariable("HOME") + "/.freshli")) + getDefaultValue: () => CacheContext.CacheDirDefault) { Arity = ArgumentArity.ExactlyOne }; diff --git a/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj b/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj index ac47a25a5..2e52d6fe2 100644 --- a/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj +++ b/Corgibytes.Freshli.Cli/Corgibytes.Freshli.Cli.csproj @@ -21,11 +21,16 @@ LICENSE + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - + + @@ -43,5 +48,9 @@ CliOutput.resx + + + + diff --git a/Corgibytes.Freshli.Cli/Functionality/Cache.cs b/Corgibytes.Freshli.Cli/Functionality/Cache.cs new file mode 100644 index 000000000..4078bd806 --- /dev/null +++ b/Corgibytes.Freshli.Cli/Functionality/Cache.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using ServiceStack; + +namespace Corgibytes.Freshli.Cli.Functionality +{ + [Index(nameof(Key), IsUnique = true)] + public class CachedProperty + { + public int Id { get; set; } + public string Key { get; set; } + public string Value { get; set; } + } + + public class CacheContext : DbContext + { + public static readonly string CacheDirEnvVariable = "FRESHLI_CACHE_DIR"; + public static readonly DirectoryInfo CacheDirDefault = new DirectoryInfo(Environment.GetEnvironmentVariable("HOME") + "/.freshli"); + private static readonly string cacheDbName = "freshli.db"; + public string DbPath { get; } + + public static string CacheDir + { + get + { + /* If the user passes --cache-dir, the environment variable is set. This design enables 'dotnet ef' + * to keep working, even after a call to `cache prepare --cache-dir [some directory]`. + * Otherwise, the default is used. */ + string cacheDir = Environment.GetEnvironmentVariable(CacheDirEnvVariable); + return cacheDir.IsNullOrEmpty() ? CacheDirDefault.ToString() : cacheDir; + } + } + + public DbSet CachedProperties { get; set; } + + public CacheContext() + { + DbPath = Path.Join(CacheDir, cacheDbName); + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options.UseSqlite($"Data Source={DbPath}"); + } + + public static class Cache + { + private static void MigrateIfPending(CacheContext context) + { + var pending = context.Database.GetPendingMigrations(); + if (pending.Any() == false) + { + return; + } + + context.Database.Migrate(); + } + public static bool Prepare(DirectoryInfo cacheDir) + { + // Create the directory if it doesn't already exist + if (cacheDir.Exists == false) + { + cacheDir.Create(); + } + + /* Store the expected cache directory in an environment variable so it can be used by the database model, + * even between calls in this terminal session. + */ + Environment.SetEnvironmentVariable(CacheContext.CacheDirEnvVariable, cacheDir.ToString()); + + Console.Out.WriteLine($"Preparing cache at {CacheContext.CacheDir}"); + + using var db = new CacheContext(); + try + { + MigrateIfPending(db); + } + catch (Microsoft.Data.Sqlite.SqliteException e) + { + Console.Out.WriteLine(e.Message); + return false; + } + + return true; + } + } +} diff --git a/Corgibytes.Freshli.Cli/Migrations/20220516190023_InitialCreate.Designer.cs b/Corgibytes.Freshli.Cli/Migrations/20220516190023_InitialCreate.Designer.cs new file mode 100644 index 000000000..ea0ecd50c --- /dev/null +++ b/Corgibytes.Freshli.Cli/Migrations/20220516190023_InitialCreate.Designer.cs @@ -0,0 +1,44 @@ +// +using Corgibytes.Freshli.Cli.Functionality; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Corgibytes.Freshli.Cli.Migrations +{ + [DbContext(typeof(CacheContext))] + [Migration("20220516190023_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.0-preview.4.22229.2"); + + modelBuilder.Entity("Corgibytes.Freshli.Cli.Functionality.CachedProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("CachedProperties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Corgibytes.Freshli.Cli/Migrations/20220516190023_InitialCreate.cs b/Corgibytes.Freshli.Cli/Migrations/20220516190023_InitialCreate.cs new file mode 100644 index 000000000..5b6c3d8da --- /dev/null +++ b/Corgibytes.Freshli.Cli/Migrations/20220516190023_InitialCreate.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Corgibytes.Freshli.Cli.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CachedProperties", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column(type: "TEXT", nullable: true), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CachedProperties", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_CachedProperties_Key", + table: "CachedProperties", + column: "Key", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CachedProperties"); + } + } +} diff --git a/Corgibytes.Freshli.Cli/Migrations/CacheContextModelSnapshot.cs b/Corgibytes.Freshli.Cli/Migrations/CacheContextModelSnapshot.cs new file mode 100644 index 000000000..a99d6c64f --- /dev/null +++ b/Corgibytes.Freshli.Cli/Migrations/CacheContextModelSnapshot.cs @@ -0,0 +1,41 @@ +// +using Corgibytes.Freshli.Cli.Functionality; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Corgibytes.Freshli.Cli.Migrations +{ + [DbContext(typeof(CacheContext))] + partial class CacheContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.0-preview.4.22229.2"); + + modelBuilder.Entity("Corgibytes.Freshli.Cli.Functionality.CachedProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("CachedProperties"); + }); +#pragma warning restore 612, 618 + } + } +} From 7e390a0880b6c285a67ae872939d47bedf81fcf5 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Tue, 17 May 2022 09:41:50 -0500 Subject: [PATCH 12/38] refactor test: make VerifyAlias test helper function generic Tests for other commands will need to use the logic in VerifyAlias. This moves VerifyAlias to a new TestHelpers class, and makes the function generic, so it can be used with any System.CommandLine.Command class. --- .../Commands/ScanCommandTest.cs | 13 ++----------- .../Common/TestHelpers.cs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 Corgibytes.Freshli.Cli.Test/Common/TestHelpers.cs diff --git a/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs b/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs index b1c5095a3..69034fcde 100644 --- a/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs +++ b/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs @@ -36,7 +36,7 @@ public void Verify_path_argument_configuration() [InlineData("-f")] public void Verify_format_option_configuration(string alias) { - VerifyAlias(alias, ArgumentArity.ExactlyOne, false); + TestHelpers.VerifyAlias(alias, ArgumentArity.ExactlyOne, false); } [Theory] @@ -44,7 +44,7 @@ public void Verify_format_option_configuration(string alias) [InlineData("-o")] public void Verify_output_options_configuration(string alias) { - VerifyAlias(alias, ArgumentArity.OneOrMore, true); + TestHelpers.VerifyAlias(alias, ArgumentArity.OneOrMore, true); } [Fact] @@ -66,14 +66,5 @@ await cmdBuilder.UseDefaults() _console.Out.ToString().Should().Contain("Command Execution Invocation Ended"); _console.Out.ToString().Should().NotContain("Exception has been thrown by the target of an invocation"); } - - private static void VerifyAlias(string alias, ArgumentArity arity, bool allowMultipleArgumentsPerToken) - { - ScanCommand scanCommand = new(); - Option option = scanCommand.Options.FirstOrDefault(x => x.Aliases.Contains(alias)); - option.Should().NotBeNull(); - option.AllowMultipleArgumentsPerToken.Should().Be(allowMultipleArgumentsPerToken); - option.Arity.Should().BeEquivalentTo(arity); - } } } diff --git a/Corgibytes.Freshli.Cli.Test/Common/TestHelpers.cs b/Corgibytes.Freshli.Cli.Test/Common/TestHelpers.cs new file mode 100644 index 000000000..e16dbee35 --- /dev/null +++ b/Corgibytes.Freshli.Cli.Test/Common/TestHelpers.cs @@ -0,0 +1,17 @@ +using System.CommandLine; +using System.Linq; +using FluentAssertions; + +namespace Corgibytes.Freshli.Cli.Test.Common; + +public class TestHelpers +{ + public static void VerifyAlias (string alias, ArgumentArity arity, bool allowMultipleArgumentsPerToken) where T: Command, new() + { + T command = new T(); + Option option = command.Options.FirstOrDefault(x => x.Aliases.Contains(alias)); + option.Should().NotBeNull(); + option.AllowMultipleArgumentsPerToken.Should().Be(allowMultipleArgumentsPerToken); + option.Arity.Should().BeEquivalentTo(arity); + } +} From 3b2acfe835ae1b3d29e36a9b99e06f9c20bb795d Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Tue, 17 May 2022 09:43:04 -0500 Subject: [PATCH 13/38] test: --cache-dir global option Tests for --cache-dir global option. This can only be tested on MainCommand directly, since the normal command building does not take place in the tests. --- .../Commands/MainCommandTest.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs diff --git a/Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs b/Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs new file mode 100644 index 000000000..77f1ab16b --- /dev/null +++ b/Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs @@ -0,0 +1,23 @@ +using System.CommandLine; +using System.CommandLine.IO; +using Corgibytes.Freshli.Cli.Commands; +using Corgibytes.Freshli.Cli.Test.Common; +using Xunit; +using Xunit.Abstractions; + +namespace Corgibytes.Freshli.Cli.Test.Commands +{ + public class MainCommandTest : FreshliTest + { + private readonly TestConsole _console = new(); + + public MainCommandTest(ITestOutputHelper output) : base(output) { } + + [Theory] + [InlineData("--cache-dir")] + public void Verify_cache_dir_option_configuration(string alias) + { + TestHelpers.VerifyAlias(alias, ArgumentArity.ExactlyOne, false); + } + } +} From ec853072ed614b7dd77ccb9b8f4f438e96003b09 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Tue, 17 May 2022 09:49:32 -0500 Subject: [PATCH 14/38] test: main command should not itself have a handler The main command should not be runnable directly. Instead, one of its subcommands must be run instead. This test confirms that as true. --- Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs b/Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs index 77f1ab16b..0eb0c9820 100644 --- a/Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs +++ b/Corgibytes.Freshli.Cli.Test/Commands/MainCommandTest.cs @@ -2,6 +2,7 @@ using System.CommandLine.IO; using Corgibytes.Freshli.Cli.Commands; using Corgibytes.Freshli.Cli.Test.Common; +using FluentAssertions; using Xunit; using Xunit.Abstractions; @@ -13,6 +14,13 @@ public class MainCommandTest : FreshliTest public MainCommandTest(ITestOutputHelper output) : base(output) { } + [Fact] + public void Verify_no_handler_configuration() + { + MainCommand mainCommand = new(); + mainCommand.Handler.Should().BeNull(); + } + [Theory] [InlineData("--cache-dir")] public void Verify_cache_dir_option_configuration(string alias) From 673b2ccfcd7ce46ff2ed102b3c52785f1f26489a Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Tue, 17 May 2022 09:50:28 -0500 Subject: [PATCH 15/38] test: 'cache' and 'cache prepare' commands Confirms the following: * 'cache' is not directly runnable (no handler) * 'cache prepare' is runnable (has handler) --- .../Commands/CacheCommandTest.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Corgibytes.Freshli.Cli.Test/Commands/CacheCommandTest.cs diff --git a/Corgibytes.Freshli.Cli.Test/Commands/CacheCommandTest.cs b/Corgibytes.Freshli.Cli.Test/Commands/CacheCommandTest.cs new file mode 100644 index 000000000..001c0c34a --- /dev/null +++ b/Corgibytes.Freshli.Cli.Test/Commands/CacheCommandTest.cs @@ -0,0 +1,33 @@ +using System.CommandLine.Builder; +using System.CommandLine.IO; +using System.CommandLine.Parsing; +using System.Threading.Tasks; +using Corgibytes.Freshli.Cli.Commands; +using Corgibytes.Freshli.Cli.Test.Common; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +namespace Corgibytes.Freshli.Cli.Test.Commands +{ + public class CacheCommandTest : FreshliTest + { + private readonly TestConsole _console = new(); + + public CacheCommandTest(ITestOutputHelper output) : base(output) { } + + [Fact] + public void Verify_no_cache_handler_configuration() + { + CacheCommand cacheCommand = new(); + cacheCommand.Handler.Should().BeNull(); + } + + [Fact] + public void Verify_prepare_handler_configuration() + { + CachePrepareCommand cachePrepareCommand = new(); + cachePrepareCommand.Handler.Should().NotBeNull(); + } + } +} From 2bbdc4f69798a55b1d9264de72509304db600cea Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Tue, 17 May 2022 11:51:54 -0500 Subject: [PATCH 16/38] test: simplify help test Cucumber now tests more specific statements within the --help output, rather than expecting a large exact block. --- features/help.feature | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/features/help.feature b/features/help.feature index c8164359a..1aa502522 100644 --- a/features/help.feature +++ b/features/help.feature @@ -1,18 +1,16 @@ Feature: Freshli.Cli - Scenario: Help - When I run `Corgibytes.Freshli.Cli -h` + Scenario: Help option + When I run `Corgibytes.Freshli.Cli --help` Then the output should contain: """ - Corgibytes.Freshli.Cli - Root Command - Usage: - Corgibytes.Freshli.Cli [options] [command] - - Options: - -?, -h, --help Show help and usage information - --version Show version information - - Commands: + Corgibytes.Freshli.Cli [command] [options] + """ + And the output should contain: + """ scan Scan command returns metrics results for given local repository path """ + And the output should contain: + """ + cache Manages the local cache database + """ From a5e2f936d43d4894842f9b70bd0ffe2a3a54ba85 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Tue, 17 May 2022 11:53:28 -0500 Subject: [PATCH 17/38] test: testing 'cache prepare' produces valid database Ensures that 'cache prepare' creates the expected default folder, and generates an SQLite database file that passes SQLite's "PRAGMA integrity_check". NOTE: It is not feasible to test for pending migrations with cucumber. --- .gitignore | 3 +++ Gemfile | 2 ++ features/cache.feature | 13 +++++++++++++ features/step_definitions/database.rb | 11 +++++++++++ features/support/aruba.rb | 9 +++++++++ 5 files changed, 38 insertions(+) create mode 100644 features/cache.feature create mode 100644 features/step_definitions/database.rb diff --git a/.gitignore b/.gitignore index 859944258..8f63a6548 100644 --- a/.gitignore +++ b/.gitignore @@ -456,3 +456,6 @@ Gemfile.lock bin/ binpublish/ exe/ + +# ignore cucumber/aruba temporary files +tmp/ diff --git a/Gemfile b/Gemfile index e4bc4e1b0..f4249468a 100644 --- a/Gemfile +++ b/Gemfile @@ -2,4 +2,6 @@ source "https://rubygems.org" +gem 'sqlite3' gem 'aruba', '~> 2.0.1' +gem 'rspec-expectations' diff --git a/features/cache.feature b/features/cache.feature new file mode 100644 index 000000000..d4dcdec37 --- /dev/null +++ b/features/cache.feature @@ -0,0 +1,13 @@ +Feature: Cache + Scenario: Prepare + Given a directory named "~/.freshli" does not exist + When I run `Corgibytes.Freshli.Cli cache prepare` + Then the directory named "~/.freshli" should exist + And a file named "~/.freshli/freshli.db" should exist + And we can open a SQLite connection to "~/.freshli/freshli.db" + + Scenario: Prepare with overridden cache-dir + Given a directory named "somewhere_else" does not exist + When I run `Corgibytes.Freshli.Cli cache prepare --cache-dir somewhere_else` + Then the directory named "somewhere_else" should exist + And a file named "somewhere_else/freshli.db" should exist diff --git a/features/step_definitions/database.rb b/features/step_definitions/database.rb new file mode 100644 index 000000000..5ca3d250d --- /dev/null +++ b/features/step_definitions/database.rb @@ -0,0 +1,11 @@ +require 'aruba/cucumber' +require 'sqlite3' + +Then('we can open a SQLite connection to {string}') do |database| + Aruba.configure do |config| + database["~"] = config.home_directory + db = SQLite3::Database.open database + r = db.execute("PRAGMA integrity_check") + expect(r[0]).to match(["ok"]) + end +end diff --git a/features/support/aruba.rb b/features/support/aruba.rb index fb0a661b9..6be6ca340 100644 --- a/features/support/aruba.rb +++ b/features/support/aruba.rb @@ -1 +1,10 @@ require 'aruba/cucumber' + +Aruba.configure do |config| + # Use aruba working directory + config.home_directory = File.join(config.root_directory, config.working_directory) +end + +Aruba.configure do |config| + puts %(The default value is "#{config.home_directory}") +end From 0cef7b21c3f40346cb54df5e3d93517f9de320ba Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Tue, 17 May 2022 14:27:44 -0400 Subject: [PATCH 18/38] Adds migrations folder to ignore list --- .codeclimate.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 027fb6492..7423c72dc 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,3 +1,4 @@ exclude_patterns: - "Corgibytes.Freshli.Cli/obj/**" - - "Corgibytes.Freshli.Cli.Test/obj/**" \ No newline at end of file + - "Corgibytes.Freshli.Cli.Test/obj/**" + - "Corgibytes.Freshli.Cli/Migrations/**" From 3def57edd9c1ff1621f4ae88f9b8d826d582ae88 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Tue, 17 May 2022 13:48:14 -0500 Subject: [PATCH 19/38] docs: update CONTRIBUTING.md to reflect new architecture --format and --output are no longer shared between all commands, so all mentions of that are removed. Shows the correct inheritance for Commands now that BaseCommand<> has been removed. Adds mention of --cache-dir global argument. Documents the new cache command. --- CONTRIBUTING.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c77afdb27..50724fb1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,10 +51,11 @@ This type of entity is where you configure the structure of your command. So far, the following commands have been implemented: * **scan** - Collects historical metrics about a project's dependencies - Implemented in _Commands/ScanCommand.cs_, _CommandOptions/ScanCommandOptions.cs_ and _CommandRunners/ScanCommandRunner.cs_ +* **cache** - Manages the cache databased used by the other commands - Implemented in _Commands/CacheCommand.cs_, _CommandOptions/CacheCommandOptions.cs_ and _CommandRunners/CacheCommandRunner.cs_ Follow below steps if you want to contribute with a new command: -1) Add a new class called _**YourNewCommandNameCommandOptions**_ into the **CommandOptions folder** and inherit from the base class **_CommandOptions_**. You do not need to implement anything to allow the **format** and **output** options. These are inherited from the base class. +1) Add a new class called _**YourNewCommandNameCommandOptions**_ into the **CommandOptions folder** and inherit from the base class _**CommandOptions**_. You do not need to implement anything to allow the **cache-dir** option, as this is inherited from the base class. Example: CustomCommandOptions @@ -68,12 +69,12 @@ Example: CustomCommandOptions } ``` -2) Add a new class called _**YourNewCommandNameCommand**_ into the **Commands folder** and inherit it from the base class _**BaseCommand**_. You do not need to implement anything to allow the **format** and **output** options. These are inherited from the base class. +2) Add a new class called _**YourNewCommandNameCommand**_ into the **Commands folder** and inherit it from the base class _**Command**_. You do not need to implement anything to allow the **cache-dir** option, as this is added globally. Example: CustomCommand ``` - public class CustomCommand : BaseCommand + public class CustomCommand : Command { public CustomCommand() : base("custom", "Custom command description") { @@ -125,8 +126,8 @@ Example: CustomCommandRunners Update the **FreshliServiceBuilder Register** method in order to add the invokation to this new method. -5) Go to Main and Add your new Command in the _**CreateCommandLineBuilder**_ method. This will allow the main program to identify you added -a new command. +5) Go to _Program.cs_ and add your new Command to the list at the top of the _**CreateCommandLineBuilder**_ method. This will allow the main program to identify + you added a new command. 6) Go to **Corgibytes.Freshli.Cli.Test** and add unit tests. @@ -134,6 +135,7 @@ a new command. #### Formatters +The **scan** command has a `--format` option, which allows specifying a formatter to use for the output of that command. A serialization formatter is responsible for encoding objects into a particular format. The formatted output will be sent to all the selected output strategies Available formatters are **json, yaml, and csv**. These formatters can be found under the _**Formatters**_ folder as follows. @@ -184,7 +186,8 @@ method and add a new registration line for your formatter as follows #### Output Strategies -An Output Strategy is reponsable for sending the serialized response of a command to a configured output. The formatted output will be sent to all the selected output strategies +The **scan** command has an `--output` option, which allows specifying one or more output strategies to use for the output of that command. +An Output Strategy is responsible for sending the serialized response of a command to a configured output. The formatted output will be sent to all the selected output strategies Available outputs are **console, file**. These formatters can be found under the _**OutputStrategies**_ folder as follows. * **ConsoleOutputStrategy** - Sends serialized data by a formatter to the standard output. From c30c59e262f649fc58790aec9a031301f8bc1e33 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Tue, 17 May 2022 13:54:05 -0500 Subject: [PATCH 20/38] docs: fix various typos and formatting issues Fixes misspelled words in README.md and CONTRIBUTING.md. Improves alignment of columns on Markdown representation of tables (thanks to JetBrains). Improves readability of command list in Program.cs, to make it easier for future developers to add their own. Reordered methods in Program.cs from outermost to innermost to make navigating the file a bit easier. --- CONTRIBUTING.md | 22 +++++++++++----------- Corgibytes.Freshli.Cli/Program.cs | 21 +++++++++++++-------- README.md | 20 ++++++++++---------- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 50724fb1f..65da03fdf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,14 +29,14 @@ This project also supports using ### Nuget Dependencies -Package | Version | Description ----------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- -`System.CommandLine` | [![Nuget](https://img.shields.io/nuget/v/System.CommandLine.svg)](https://nuget.org/packages/System.CommandLine) | Command line parser, model binding, invocation, shell completions -`System.CommandLine.Hosting` | [![Nuget](https://img.shields.io/nuget/v/System.CommandLine.Hosting.svg)](https://nuget.org/packages/System.CommandLine.Hosting) | support for using System.CommandLine with [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting/) -`Corgibytes.Freshli.Lib` | [![Nuget](https://img.shields.io/nuget/v/Corgibytes.Freshli.Lib.svg)](https://nuget.org/packages/Corgibytes.Freshli.Lib) | Core library for collecting historical metrics about a project's dependencies -`YamlDotNet` | [![Nuget](https://img.shields.io/nuget/v/YamlDotNet.svg)](https://nuget.org/packages/YamlDotNet) | A .NET library for YAML. YamlDotNet provides low level parsing and emitting of YAML as well as a high level object model similar to XmlDocument. -`Newtonsoft.Json` | [![Nuget](https://img.shields.io/nuget/v/Newtonsoft.Json.svg)](https://nuget.org/packages/Newtonsoft.Json) | Json.NET is a popular high-performance JSON framework for .NET -`NamedServices.Microsoft.Extensions.DependencyInjection` | [![Nuget](https://img.shields.io/nuget/v/NamedServices.Microsoft.Extensions.DependencyInjection.svg)](https://nuget.org/packages/NamedServices.Microsoft.Extensions.DependencyInjection) | Named Services for Microsoft.Extensions.DependencyInjection +| Package | Version | Description | +|----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| `System.CommandLine` | [![Nuget](https://img.shields.io/nuget/v/System.CommandLine.svg)](https://nuget.org/packages/System.CommandLine) | Command line parser, model binding, invocation, shell completions | +| `System.CommandLine.Hosting` | [![Nuget](https://img.shields.io/nuget/v/System.CommandLine.Hosting.svg)](https://nuget.org/packages/System.CommandLine.Hosting) | support for using System.CommandLine with [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting/) | +| `Corgibytes.Freshli.Lib` | [![Nuget](https://img.shields.io/nuget/v/Corgibytes.Freshli.Lib.svg)](https://nuget.org/packages/Corgibytes.Freshli.Lib) | Core library for collecting historical metrics about a project's dependencies | +| `YamlDotNet` | [![Nuget](https://img.shields.io/nuget/v/YamlDotNet.svg)](https://nuget.org/packages/YamlDotNet) | A .NET library for YAML. YamlDotNet provides low level parsing and emitting of YAML as well as a high level object model similar to XmlDocument. | +| `Newtonsoft.Json` | [![Nuget](https://img.shields.io/nuget/v/Newtonsoft.Json.svg)](https://nuget.org/packages/Newtonsoft.Json) | Json.NET is a popular high-performance JSON framework for .NET | +| `NamedServices.Microsoft.Extensions.DependencyInjection` | [![Nuget](https://img.shields.io/nuget/v/NamedServices.Microsoft.Extensions.DependencyInjection.svg)](https://nuget.org/packages/NamedServices.Microsoft.Extensions.DependencyInjection) | Named Services for Microsoft.Extensions.DependencyInjection | ### Main Entities @@ -124,7 +124,7 @@ Example: CustomCommandRunners } ``` -Update the **FreshliServiceBuilder Register** method in order to add the invokation to this new method. +Update the **FreshliServiceBuilder Register** method in order to add the invocation to this new method. 5) Go to _Program.cs_ and add your new Command to the list at the top of the _**CreateCommandLineBuilder**_ method. This will allow the main program to identify you added a new command. @@ -147,7 +147,7 @@ If you want to contribute with a formatter, you have to follow below instruction 1) Add the new format type into the _**FormatType**_ enum. Example: _**Custom**_ -2) Add a new class called _**YourNewFormatOutputFormatter**_ into the _**Formatters folder**_ and inherit it from OutputFormatter. Implement requred methods +2) Add a new class called _**YourNewFormatOutputFormatter**_ into the _**Formatters folder**_ and inherit it from OutputFormatter. Implement required methods Example: CustomOutputFormatter ``` @@ -175,7 +175,7 @@ method and add a new registration line for your formatter as follows ``` -6) In order to test your new formatter, build the solution and run a command (for example the scan command), and specofy your new format as input for the format option. Example: +6) In order to test your new formatter, build the solution and run a command (for example the scan command), and specify your new format as input for the format option. Example: ``` freshli scan repository-path -f custom diff --git a/Corgibytes.Freshli.Cli/Program.cs b/Corgibytes.Freshli.Cli/Program.cs index 57acc1e20..4a70b85ce 100644 --- a/Corgibytes.Freshli.Cli/Program.cs +++ b/Corgibytes.Freshli.Cli/Program.cs @@ -23,16 +23,14 @@ public static async Task Main(string[] args) .InvokeAsync(args); } - static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureServices((_, services) => - { - new FreshliServiceBuilder(services).Register(); - }); - public static CommandLineBuilder CreateCommandLineBuilder() { - var command = new MainCommand {new ScanCommand(), new CacheCommand()}; + var command = new MainCommand + { + // Add commands here! + new ScanCommand(), + new CacheCommand() + }; CommandLineBuilder builder = new CommandLineBuilder(command) .UseHost(CreateHostBuilder) @@ -46,6 +44,13 @@ public static CommandLineBuilder CreateCommandLineBuilder() return builder; } + static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + { + new FreshliServiceBuilder(services).Register(); + }); + public static async Task LogExecution(InvocationContext context, Func next) { string commandLine = context.ParseResult.ToString(); diff --git a/README.md b/README.md index 64447139e..29e446344 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ First you need .NET 6.0 runtime installed which you can find [here](https://dotn 1) Download it. 2) Extract the Zip file. -3) Copy the folder to an apporiate location. +3) Copy the folder to an appropriate location. 4) Open a terminal and run `Corgibytes.Freshli.Cli.exe`. To run Freshli: @@ -103,17 +103,17 @@ You can find old releases to download [here](https://github.com/corgibytes/fresh The dependency managers that Freshli supports are listed below along with the manifest files it can parse. The manifest file is the file that lists what dependencies are required by the project and has changed over time for some dependency managers, like NuGet. -| Dependency Manager | Language(s)/Framework(s) | Manifest Files Format | -|--------------------|-----------------------|----------| -| [Bundler](https://bundler.io/) | [Ruby](https://www.ruby-lang.org), [Ruby on Rails](https://rubyonrails.org/) | Gemfile.lock | -| [Carton](https://metacpan.org/pod/Carton) | [Perl](https://www.perl.org/) | cpanfile | -| [Composer](https://getcomposer.org/) | [PHP](https://www.php.net/) | composer.json, composer.lock | -| [Pip](https://pypi.org/project/pip/) | [Python](https://www.python.org/) | requirements.txt | -| [NuGet](https://www.nuget.org/) | [C#](https://docs.microsoft.com/en-us/dotnet/csharp/) | *.csproj | +| Dependency Manager | Language(s)/Framework(s) | Manifest Files Format | +|-------------------------------------------|------------------------------------------------------------------------------|------------------------------| +| [Bundler](https://bundler.io/) | [Ruby](https://www.ruby-lang.org), [Ruby on Rails](https://rubyonrails.org/) | Gemfile.lock | +| [Carton](https://metacpan.org/pod/Carton) | [Perl](https://www.perl.org/) | cpanfile | +| [Composer](https://getcomposer.org/) | [PHP](https://www.php.net/) | composer.json, composer.lock | +| [Pip](https://pypi.org/project/pip/) | [Python](https://www.python.org/) | requirements.txt | +| [NuGet](https://www.nuget.org/) | [C#](https://docs.microsoft.com/en-us/dotnet/csharp/) | *.csproj | Please let us know what other dependency managers and/or manifest files you would like use to support via the contact information in the [Contributing](#contributing) section. -## What Data Does Freahli Return? +## What Data Does Freshli Return? Freshli check your projects dependencies at on month intervals and returns a table with the following that looks like: @@ -166,7 +166,7 @@ When checking the libyear on May 1, 2019 Freshli will use v1.1.0 (Apr 3rd, 2019) 93 / 365 = 0.2548 ``` -If you have v1.0.1 instealled then your libyear when checking on May 1, 2019 is 68 days for a libyear of: +If you have v1.0.1 installed then your libyear when checking on May 1, 2019 is 68 days for a libyear of: ``` 68 / 365 = 0.1863 From 53bfbe4c7fc9e24de2b72b7d33114a05a5aa01e9 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Wed, 18 May 2022 08:50:43 -0500 Subject: [PATCH 21/38] build: track Gemfile.lock again --- .gitignore | 4 ---- Gemfile.lock | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index 8f63a6548..cc710054c 100644 --- a/.gitignore +++ b/.gitignore @@ -440,7 +440,6 @@ Freshli.sln.DotSettings.user *.log results/ - # Ignore test coverage. coverage.info Freshli.Cli.Test/coverage.info @@ -449,9 +448,6 @@ Freshli.Cli/nuget.config # Ignore nuget config as it contains passwords or personal access tokens. nuget.config -# ignore Ruby cruft -Gemfile.lock - # ignore generic build locations bin/ binpublish/ diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..cc4053b93 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,68 @@ +GEM + remote: https://rubygems.org/ + specs: + aruba (2.0.1) + bundler (>= 1.17, < 3.0) + childprocess (>= 2.0, < 5.0) + contracts (>= 0.16.0, < 0.18.0) + cucumber (>= 4.0, < 8.0) + rspec-expectations (~> 3.4) + thor (~> 1.0) + builder (3.2.4) + childprocess (4.1.0) + contracts (0.17) + cucumber (7.1.0) + builder (~> 3.2, >= 3.2.4) + cucumber-core (~> 10.1, >= 10.1.0) + cucumber-create-meta (~> 6.0, >= 6.0.1) + cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) + cucumber-gherkin (~> 22.0, >= 22.0.0) + cucumber-html-formatter (~> 17.0, >= 17.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-wire (~> 6.2, >= 6.2.0) + diff-lcs (~> 1.4, >= 1.4.4) + mime-types (~> 3.3, >= 3.3.1) + multi_test (~> 0.1, >= 0.1.2) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-core (10.1.1) + cucumber-gherkin (~> 22.0, >= 22.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-tag-expressions (~> 4.1, >= 4.1.0) + cucumber-create-meta (6.0.4) + cucumber-messages (~> 17.1, >= 17.1.1) + sys-uname (~> 1.2, >= 1.2.2) + cucumber-cucumber-expressions (14.0.0) + cucumber-gherkin (22.0.0) + cucumber-messages (~> 17.1, >= 17.1.1) + cucumber-html-formatter (17.0.0) + cucumber-messages (~> 17.1, >= 17.1.0) + cucumber-messages (17.1.1) + cucumber-tag-expressions (4.1.0) + cucumber-wire (6.2.1) + cucumber-core (~> 10.1, >= 10.1.0) + cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) + diff-lcs (1.5.0) + ffi (1.15.5) + mime-types (3.4.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2022.0105) + multi_test (0.1.2) + rspec-expectations (3.11.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.11.0) + rspec-support (3.11.0) + sqlite3 (1.4.2) + sys-uname (1.2.2) + ffi (~> 1.1) + thor (1.2.1) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + aruba (~> 2.0.1) + rspec-expectations + sqlite3 + +BUNDLED WITH + 2.3.5 From cd3f9e9fcbb6645a967a479b32726c04bb48361b Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Tue, 17 May 2022 16:13:28 -0400 Subject: [PATCH 22/38] Adds help for working with the devcontainer --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 29e446344..63d0c5bac 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,16 @@ From then on, you can run the Aruba tests with: dotnet build -o bin && bundle exec cucumber ``` +## Working with the DevContainer + +This project has uses DevContainer to assist with creating a full configured development environment. + +There are two paths to working with this DevContainer setup. + +1. [Install the `devcontainer` CLI](https://code.visualstudio.com/docs/remote/devcontainer-cli) and then run `devcontainer build` followed by `devcontainer open`. That will open Visual Studio Code running from inside of a container with everything needed to build the project. + +2. Run `docker` directly. Run `docker -t freshli-cli-dev .devcontainer/` to build the container. Then you'll be able to run `docker run --rm -it -v $PWD:/code -w /code freshli-cli-dev bash` to create a shell session inside of a running container with everything set up for you. (Note, you may need to run `bundle install` when you first start the container to install the ruby-based dependencies. This step is performed for you if you use the `devcontainer` CLI to open a Visual Studio Code instance.) + ## Contributing If you have any questions, notice a bug, or have a suggestion/enhancment please let us know by opening a [issue](https://github.com/corgibytes/freshli-cli/issues) or [pull request](https://github.com/corgibytes/freshli-cli/pulls). From 41634c8055026af12787330bd0bbf180b28c91b6 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Tue, 17 May 2022 15:11:30 -0400 Subject: [PATCH 23/38] Increments SDK version --- .devcontainer/Dockerfile | 4 ++-- .devcontainer/devcontainer.json | 4 ++-- global.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7e39476ef..5b3d5ff81 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,7 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.155.1/containers/dotnet/.devcontainer/base.Dockerfile -# [Choice] .NET version: 5.0, 3.1, 2.1 -ARG VARIANT="5.0" +# [Choice] .NET version: 6.0, 3.1, 2.1 +ARG VARIANT="6.0" FROM mcr.microsoft.com/vscode/devcontainers/dotnetcore:0-${VARIANT} # [Option] Install Node.js diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7ea5c00cc..4b59f1745 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,8 +5,8 @@ "build": { "dockerfile": "Dockerfile", "args": { - // Update 'VARIANT' to pick a .NET Core version: 2.1, 3.1, 5.0 - "VARIANT": "5.0", + // Update 'VARIANT' to pick a .NET Core version: 2.1, 3.1, 6.0 + "VARIANT": "6.0", // Options "INSTALL_NODE": "false", "NODE_VERSION": "lts/*", diff --git a/global.json b/global.json index 4db6a4866..97dd87392 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "5.0", + "version": "6.0", "rollForward": "latestMajor", "allowPrerelease": false } From ea0f906179db2300a1e488180ffdae5ef3e14865 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Tue, 17 May 2022 15:59:33 -0400 Subject: [PATCH 24/38] Refreshes devcontainer Adds in Ruby 3.1 using the same setup that's used to build the official Docker container. https://github.com/docker-library/ruby/blob/master/3.1/bullseye/Dockerfile --- .devcontainer/Dockerfile | 206 ++++++++++++++++-- .devcontainer/devcontainer.json | 29 +-- .devcontainer/library-scripts/azcli-debian.sh | 33 --- 3 files changed, 204 insertions(+), 64 deletions(-) delete mode 100644 .devcontainer/library-scripts/azcli-debian.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5b3d5ff81..1c93a5a3c 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,23 +1,201 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.155.1/containers/dotnet/.devcontainer/base.Dockerfile +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/dotnet/.devcontainer/base.Dockerfile -# [Choice] .NET version: 6.0, 3.1, 2.1 -ARG VARIANT="6.0" -FROM mcr.microsoft.com/vscode/devcontainers/dotnetcore:0-${VARIANT} +# [Choice] .NET version: 6.0, 5.0, 3.1, 6.0-bullseye, 5.0-bullseye, 3.1-bullseye, 6.0-focal, 5.0-focal, 3.1-focal +ARG VARIANT="6.0-bullseye" +FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} -# [Option] Install Node.js -ARG INSTALL_NODE="true" -ARG NODE_VERSION="lts/*" -RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi +# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi -# [Option] Install Azure CLI -ARG INSTALL_AZURE_CLI="false" -COPY library-scripts/azcli-debian.sh /tmp/library-scripts/ -RUN if [ "$INSTALL_AZURE_CLI" = "true" ]; then bash /tmp/library-scripts/azcli-debian.sh; fi \ - && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts +# Start buildpack-curl installation +# Copied from https://github.com/docker-library/buildpack-deps/blob/a017681ea2f5891786bfa151a0d38fbea051d4ba/debian/bullseye/curl/Dockerfile +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + netbase \ + wget \ + ; \ + rm -rf /var/lib/apt/lists/* + +RUN set -ex; \ + if ! command -v gpg > /dev/null; then \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + gnupg \ + dirmngr \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + fi + +# Start buildpack-scm installation +# Copied from https://github.com/docker-library/buildpack-deps/blob/a017681ea2f5891786bfa151a0d38fbea051d4ba/debian/bullseye/scm/Dockerfile +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + mercurial \ + openssh-client \ + subversion \ + \ + procps \ + && rm -rf /var/lib/apt/lists/* + +# Start buildpack installation +# Copied from https://github.com/docker-library/buildpack-deps/blob/a017681ea2f5891786bfa151a0d38fbea051d4ba/debian/bullseye/Dockerfile +RUN set -ex; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + autoconf \ + automake \ + bzip2 \ + dpkg-dev \ + file \ + g++ \ + gcc \ + imagemagick \ + libbz2-dev \ + libc6-dev \ + libcurl4-openssl-dev \ + libdb-dev \ + libevent-dev \ + libffi-dev \ + libgdbm-dev \ + libglib2.0-dev \ + libgmp-dev \ + libjpeg-dev \ + libkrb5-dev \ + liblzma-dev \ + libmagickcore-dev \ + libmagickwand-dev \ + libmaxminddb-dev \ + libncurses5-dev \ + libncursesw5-dev \ + libpng-dev \ + libpq-dev \ + libreadline-dev \ + libsqlite3-dev \ + libssl-dev \ + libtool \ + libwebp-dev \ + libxml2-dev \ + libxslt-dev \ + libyaml-dev \ + make \ + patch \ + unzip \ + xz-utils \ + zlib1g-dev \ + \ + # https://lists.debian.org/debian-devel-announce/2016/09/msg00000.html + $( \ + # if we use just "apt-cache show" here, it returns zero because "Can't select versions from package 'libmysqlclient-dev' as it is purely virtual", hence the pipe to grep + if apt-cache show 'default-libmysqlclient-dev' 2>/dev/null | grep -q '^Version:'; then \ + echo 'default-libmysqlclient-dev'; \ + else \ + echo 'libmysqlclient-dev'; \ + fi \ + ) \ + ; \ + rm -rf /var/lib/apt/lists/* + + + +# Start Ruby installation +# Copied from https://github.com/docker-library/ruby/blob/ac24ae0a894606d3ac91c28095db39e69771cac4/3.1/bullseye/Dockerfile + +# skip installing gem documentation +RUN set -eux; \ + mkdir -p /usr/local/etc; \ + { \ + echo 'install: --no-document'; \ + echo 'update: --no-document'; \ + } >> /usr/local/etc/gemrc + +ENV LANG C.UTF-8 +ENV RUBY_MAJOR 3.1 +ENV RUBY_VERSION 3.1.2 +ENV RUBY_DOWNLOAD_SHA256 ca10d017f8a1b6d247556622c841fc56b90c03b1803f87198da1e4fd3ec3bf2a + +# some of ruby's build scripts are written in ruby +# we purge system ruby later to make sure our final image uses what we just built +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + bison \ + dpkg-dev \ + libgdbm-dev \ + ruby \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + \ + wget -O ruby.tar.xz "https://cache.ruby-lang.org/pub/ruby/${RUBY_MAJOR%-rc}/ruby-$RUBY_VERSION.tar.xz"; \ + echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.xz" | sha256sum --check --strict; \ + \ + mkdir -p /usr/src/ruby; \ + tar -xJf ruby.tar.xz -C /usr/src/ruby --strip-components=1; \ + rm ruby.tar.xz; \ + \ + cd /usr/src/ruby; \ + \ + # hack in "ENABLE_PATH_CHECK" disabling to suppress: + # warning: Insecure world writable dir + { \ + echo '#define ENABLE_PATH_CHECK 0'; \ + echo; \ + cat file.c; \ + } > file.c.new; \ + mv file.c.new file.c; \ + \ + autoconf; \ + gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ + ./configure \ + --build="$gnuArch" \ + --disable-install-doc \ + --enable-shared \ + ; \ + make -j "$(nproc)"; \ + make install; \ + \ + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark > /dev/null; \ + find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec ldd '{}' ';' \ + | awk '/=>/ { print $(NF-1) }' \ + | sort -u \ + | grep -vE '^/usr/local/lib/' \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -r apt-mark manual \ + ; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + \ + cd /; \ + rm -r /usr/src/ruby; \ + # verify we have no "ruby" packages installed + if dpkg -l | grep -i ruby; then exit 1; fi; \ + [ "$(command -v ruby)" = '/usr/local/bin/ruby' ]; \ + # rough smoke test + ruby --version; \ + gem --version; \ + bundle --version + +# don't create ".bundle" in all our apps +ENV GEM_HOME /usr/local/bundle +ENV BUNDLE_SILENCE_ROOT_WARNING=1 \ + BUNDLE_APP_CONFIG="$GEM_HOME" +ENV PATH $GEM_HOME/bin:$PATH +# adjust permissions of a few directories for running "gem install" as an arbitrary user +RUN mkdir -p "$GEM_HOME" && chmod 777 "$GEM_HOME" + +# Install Aruba/Cucumber based acceptance testing tools +RUN gem install bundler # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends # [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4b59f1745..7a578623a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,31 +1,24 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.155.1/containers/dotnet +// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/dotnet { "name": "C# (.NET)", "build": { "dockerfile": "Dockerfile", "args": { - // Update 'VARIANT' to pick a .NET Core version: 2.1, 3.1, 6.0 - "VARIANT": "6.0", + // Update 'VARIANT' to pick a .NET Core version: 3.1, 5.0, 6.0 + // Append -bullseye or -focal to pin to an OS version. + "VARIANT": "6.0-bullseye", // Options - "INSTALL_NODE": "false", - "NODE_VERSION": "lts/*", - "INSTALL_AZURE_CLI": "false" + "NODE_VERSION": "lts/*" } }, // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash", - "dotnet-test-explorer.testArguments": "/p:CollectCoverage=true /p:CoverletOutputFormat=lcov", - "coverage-gutters.coverageFileNames": ["coverage.info"], - }, + "settings": {}, // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "ms-dotnettools.csharp", - "formulahendry.dotnet-test-explorer", - "ryanluker.vscode-coverage-gutters" + "ms-dotnettools.csharp" ], // Use 'forwardPorts' to make a list of ports inside the container available locally. @@ -55,8 +48,10 @@ // "mounts": [ "source=${env:HOME}${env:USERPROFILE}/.aspnet/https,target=/home/vscode/.aspnet/https,type=bind" ], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "dotnet restore", + "postCreateCommand": "bundle install && dotnet restore", - // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode", + "features": { + } } diff --git a/.devcontainer/library-scripts/azcli-debian.sh b/.devcontainer/library-scripts/azcli-debian.sh deleted file mode 100644 index b03dcb0f0..000000000 --- a/.devcontainer/library-scripts/azcli-debian.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- -# -# Docs: https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docs/azcli.md -# -# Syntax: ./azcli-debian.sh - -set -e - -if [ "$(id -u)" -ne 0 ]; then - echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' - exit 1 -fi - -export DEBIAN_FRONTEND=noninteractive - -# Install curl, apt-transport-https, lsb-release, or gpg if missing -if ! dpkg -s apt-transport-https curl ca-certificates lsb-release > /dev/null 2>&1 || ! type gpg > /dev/null 2>&1; then - if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then - apt-get update - fi - apt-get -y install --no-install-recommends apt-transport-https curl ca-certificates lsb-release gnupg2 -fi - -# Install the Azure CLI -echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/azure-cli.list -curl -sL https://packages.microsoft.com/keys/microsoft.asc | (OUT=$(apt-key add - 2>&1) || echo $OUT) -apt-get update -apt-get install -y azure-cli -echo "Done!" \ No newline at end of file From 937af35eb39cda7bbf12ebc68c841619680b3548 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 14:51:58 -0400 Subject: [PATCH 25/38] Reduces duplication within option/argument helper methods Also changes the name of the methods to make it more clear that a value is being retrieved, not an option/argument. --- .../CommandOptions/ScanCommandOptionsTest.cs | 10 +++--- Corgibytes.Freshli.Cli.Test/Extensions.cs | 35 ++++++++++++++----- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs b/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs index 10baf0928..16c09960e 100644 --- a/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs +++ b/Corgibytes.Freshli.Cli.Test/CommandOptions/ScanCommandOptionsTest.cs @@ -29,11 +29,11 @@ public void Send_Args_ReturnsScanOptions(string[] args, string expectedPath, For ParseResult result = parser.Parse(args); - DirectoryInfo path = result.GetArgumentByName("path"); - FormatType formatType = result.GetOptionByName("format"); - FormatType formatTypeFromAlias = result.GetOptionByAlias("-f"); - IEnumerable outputStrategyTypes = result.GetOptionByName>("output"); - IEnumerable outputStrategyTypesFromAlias = result.GetOptionByAlias>("-o"); + DirectoryInfo path = result.GetArgumentValueByName("path"); + FormatType formatType = result.GetOptionValueByName("format"); + FormatType formatTypeFromAlias = result.GetOptionValueByAlias("-f"); + IEnumerable outputStrategyTypes = result.GetOptionValueByName>("output"); + IEnumerable outputStrategyTypesFromAlias = result.GetOptionValueByAlias>("-o"); formatType.Should().Be(formatTypeFromAlias); diff --git a/Corgibytes.Freshli.Cli.Test/Extensions.cs b/Corgibytes.Freshli.Cli.Test/Extensions.cs index f740a38b6..ab9861f13 100644 --- a/Corgibytes.Freshli.Cli.Test/Extensions.cs +++ b/Corgibytes.Freshli.Cli.Test/Extensions.cs @@ -10,22 +10,39 @@ namespace Corgibytes.Freshli.Cli.Test { public static class ParseResultExtensions { - public static T GetArgumentByName(this ParseResult result, string name) + private static Option FindOption(this ParseResult result, Func finder) { - Argument arg = result.CommandResult.Command.Arguments.Single(x => x.Name.Equals(name)) as Argument; - return result.GetValueForArgument(arg); + return result.CommandResult.Command.Options.Single(finder) as Option; } - public static T GetOptionByName(this ParseResult result, string name) + private static T FindOptionAndGetValue(this ParseResult result, Func finder) { - Option option = result.CommandResult.Command.Options.Single(x => x.Name.Equals(name)) as Option; - return result.GetValueForOption(option); + return result.GetValueForOption(result.FindOption(finder)); } - public static T GetOptionByAlias(this ParseResult result, string alias) + private static Argument FindArgument(this ParseResult result, Func finder) { - Option option= result.CommandResult.Command.Options.Single(x => x.Aliases.Contains(alias)) as Option; - return result.GetValueForOption(option); + return result.CommandResult.Command.Arguments.Single(finder) as Argument; + } + + private static T FindArgumentAndGetValue(this ParseResult result, Func finder) + { + return result.GetValueForArgument(result.FindArgument(finder)); + } + + public static T GetArgumentValueByName(this ParseResult result, string name) + { + return result.FindArgumentAndGetValue(value => value.Name.Equals(name)); + } + + public static T GetOptionValueByName(this ParseResult result, string name) + { + return result.FindOptionAndGetValue(x => x.Name.Equals(name)); + } + + public static T GetOptionValueByAlias(this ParseResult result, string alias) + { + return result.FindOptionAndGetValue(x => x.Aliases.Contains(alias)); } } } From dd8ffddf8d385e1c5c8b12b0dbd4f871d4e1595e Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 14:52:09 -0400 Subject: [PATCH 26/38] Removes unused using statements --- Corgibytes.Freshli.Cli.Test/Extensions.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Corgibytes.Freshli.Cli.Test/Extensions.cs b/Corgibytes.Freshli.Cli.Test/Extensions.cs index ab9861f13..d882509b5 100644 --- a/Corgibytes.Freshli.Cli.Test/Extensions.cs +++ b/Corgibytes.Freshli.Cli.Test/Extensions.cs @@ -1,10 +1,7 @@ using System; -using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Parsing; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Corgibytes.Freshli.Cli.Test { From 33194996474a90bbf8637d1948db6a5b7ec9acdf Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 14:52:21 -0400 Subject: [PATCH 27/38] Normalizes file --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 97dd87392..b47b061d6 100644 --- a/global.json +++ b/global.json @@ -4,4 +4,4 @@ "rollForward": "latestMajor", "allowPrerelease": false } -} \ No newline at end of file +} From 287ea210c08332e400652f2fe27658a299734e54 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 15:06:46 -0400 Subject: [PATCH 28/38] Reduces duplication between test methods Switches to using `MethodData` and `TheoryData` to represent the set of data to test. This approach is used instead of the `[InlineData]` attribute because the `ArgumentArity` values are not technically constants, and only constant values can be parameters to attributes. --- .../Commands/ScanCommandTest.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs b/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs index 69034fcde..3e44b4452 100644 --- a/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs +++ b/Corgibytes.Freshli.Cli.Test/Commands/ScanCommandTest.cs @@ -9,6 +9,7 @@ using FluentAssertions; using Xunit; using Xunit.Abstractions; +using Xunit.DependencyInjection; namespace Corgibytes.Freshli.Cli.Test.Commands { @@ -32,19 +33,21 @@ public void Verify_path_argument_configuration() } [Theory] - [InlineData("--format")] - [InlineData("-f")] - public void Verify_format_option_configuration(string alias) + [MethodData(nameof(DataForVerifyOptionConfigurations))] + public void VerifyOptionConfigurations(string alias, ArgumentArity arity, bool allowsMultiples) { - TestHelpers.VerifyAlias(alias, ArgumentArity.ExactlyOne, false); + TestHelpers.VerifyAlias(alias, arity, allowsMultiples); } - [Theory] - [InlineData("--output")] - [InlineData("-o")] - public void Verify_output_options_configuration(string alias) + private static TheoryData DataForVerifyOptionConfigurations() { - TestHelpers.VerifyAlias(alias, ArgumentArity.OneOrMore, true); + return new TheoryData() + { + {"--format", ArgumentArity.ExactlyOne, false}, + {"-f", ArgumentArity.ExactlyOne, false}, + {"--output", ArgumentArity.OneOrMore, true}, + {"-o", ArgumentArity.OneOrMore, true} + }; } [Fact] From 74774412b3c6ff1f02289457a2e6248726d5b496 Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Wed, 18 May 2022 14:51:34 -0500 Subject: [PATCH 29/38] fix: eliminate use of environment variable for --cache-dir Environment variables on Linux do not persist between process executions, so this was not actually enabling the 'dotnet ef' tool to work with non-standard locations. Removing all use of the environment variable to simplify code. --- .../Commands/MainCommand.cs | 2 +- Corgibytes.Freshli.Cli/Functionality/Cache.cs | 29 +++++-------------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/Corgibytes.Freshli.Cli/Commands/MainCommand.cs b/Corgibytes.Freshli.Cli/Commands/MainCommand.cs index 086492cbc..07e2e0b39 100644 --- a/Corgibytes.Freshli.Cli/Commands/MainCommand.cs +++ b/Corgibytes.Freshli.Cli/Commands/MainCommand.cs @@ -11,7 +11,7 @@ public MainCommand() : base("Root Command") Option cacheDirOption = new( new[] {"--cache-dir"}, description: "The location for storing temporary files", - getDefaultValue: () => CacheContext.CacheDirDefault) + getDefaultValue: () => CacheContext.DefaultCacheDir) { Arity = ArgumentArity.ExactlyOne }; diff --git a/Corgibytes.Freshli.Cli/Functionality/Cache.cs b/Corgibytes.Freshli.Cli/Functionality/Cache.cs index 4078bd806..db823c620 100644 --- a/Corgibytes.Freshli.Cli/Functionality/Cache.cs +++ b/Corgibytes.Freshli.Cli/Functionality/Cache.cs @@ -16,28 +16,17 @@ public class CachedProperty public class CacheContext : DbContext { - public static readonly string CacheDirEnvVariable = "FRESHLI_CACHE_DIR"; - public static readonly DirectoryInfo CacheDirDefault = new DirectoryInfo(Environment.GetEnvironmentVariable("HOME") + "/.freshli"); + public static readonly DirectoryInfo DefaultCacheDir = new DirectoryInfo(Environment.GetEnvironmentVariable("HOME") + "/.freshli"); + public static DirectoryInfo CacheDir = DefaultCacheDir; + private static readonly string cacheDbName = "freshli.db"; public string DbPath { get; } - public static string CacheDir - { - get - { - /* If the user passes --cache-dir, the environment variable is set. This design enables 'dotnet ef' - * to keep working, even after a call to `cache prepare --cache-dir [some directory]`. - * Otherwise, the default is used. */ - string cacheDir = Environment.GetEnvironmentVariable(CacheDirEnvVariable); - return cacheDir.IsNullOrEmpty() ? CacheDirDefault.ToString() : cacheDir; - } - } - public DbSet CachedProperties { get; set; } public CacheContext() { - DbPath = Path.Join(CacheDir, cacheDbName); + DbPath = Path.Join(CacheDir.ToString(), cacheDbName); } protected override void OnConfiguring(DbContextOptionsBuilder options) @@ -58,19 +47,15 @@ private static void MigrateIfPending(CacheContext context) } public static bool Prepare(DirectoryInfo cacheDir) { + CacheContext.CacheDir = cacheDir; + Console.Out.WriteLine($"Preparing cache at {cacheDir}"); + // Create the directory if it doesn't already exist if (cacheDir.Exists == false) { cacheDir.Create(); } - /* Store the expected cache directory in an environment variable so it can be used by the database model, - * even between calls in this terminal session. - */ - Environment.SetEnvironmentVariable(CacheContext.CacheDirEnvVariable, cacheDir.ToString()); - - Console.Out.WriteLine($"Preparing cache at {CacheContext.CacheDir}"); - using var db = new CacheContext(); try { From 07de1d8339419226d3f161d8a3f2918271f9bf0e Mon Sep 17 00:00:00 2001 From: "Jason C. McDonald" Date: Wed, 18 May 2022 15:02:27 -0500 Subject: [PATCH 30/38] style: split classes out into dedicated files per PR request --- Corgibytes.Freshli.Cli/Functionality/Cache.cs | 28 ------------------- .../Functionality/CacheContext.cs | 27 ++++++++++++++++++ .../Functionality/CachedProperty.cs | 12 ++++++++ 3 files changed, 39 insertions(+), 28 deletions(-) create mode 100644 Corgibytes.Freshli.Cli/Functionality/CacheContext.cs create mode 100644 Corgibytes.Freshli.Cli/Functionality/CachedProperty.cs diff --git a/Corgibytes.Freshli.Cli/Functionality/Cache.cs b/Corgibytes.Freshli.Cli/Functionality/Cache.cs index db823c620..b225a6365 100644 --- a/Corgibytes.Freshli.Cli/Functionality/Cache.cs +++ b/Corgibytes.Freshli.Cli/Functionality/Cache.cs @@ -2,37 +2,9 @@ using System.IO; using System.Linq; using Microsoft.EntityFrameworkCore; -using ServiceStack; namespace Corgibytes.Freshli.Cli.Functionality { - [Index(nameof(Key), IsUnique = true)] - public class CachedProperty - { - public int Id { get; set; } - public string Key { get; set; } - public string Value { get; set; } - } - - public class CacheContext : DbContext - { - public static readonly DirectoryInfo DefaultCacheDir = new DirectoryInfo(Environment.GetEnvironmentVariable("HOME") + "/.freshli"); - public static DirectoryInfo CacheDir = DefaultCacheDir; - - private static readonly string cacheDbName = "freshli.db"; - public string DbPath { get; } - - public DbSet CachedProperties { get; set; } - - public CacheContext() - { - DbPath = Path.Join(CacheDir.ToString(), cacheDbName); - } - - protected override void OnConfiguring(DbContextOptionsBuilder options) - => options.UseSqlite($"Data Source={DbPath}"); - } - public static class Cache { private static void MigrateIfPending(CacheContext context) diff --git a/Corgibytes.Freshli.Cli/Functionality/CacheContext.cs b/Corgibytes.Freshli.Cli/Functionality/CacheContext.cs new file mode 100644 index 000000000..dc72743ea --- /dev/null +++ b/Corgibytes.Freshli.Cli/Functionality/CacheContext.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using Microsoft.EntityFrameworkCore; + +namespace Corgibytes.Freshli.Cli.Functionality +{ + public class CacheContext : DbContext + { + public static readonly DirectoryInfo DefaultCacheDir = + new DirectoryInfo(Environment.GetEnvironmentVariable("HOME") + "/.freshli"); + + public static DirectoryInfo CacheDir = DefaultCacheDir; + + private static readonly string cacheDbName = "freshli.db"; + public string DbPath { get; } + + public DbSet CachedProperties { get; set; } + + public CacheContext() + { + DbPath = Path.Join(CacheDir.ToString(), cacheDbName); + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options.UseSqlite($"Data Source={DbPath}"); + } +} diff --git a/Corgibytes.Freshli.Cli/Functionality/CachedProperty.cs b/Corgibytes.Freshli.Cli/Functionality/CachedProperty.cs new file mode 100644 index 000000000..02235481b --- /dev/null +++ b/Corgibytes.Freshli.Cli/Functionality/CachedProperty.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore; + +namespace Corgibytes.Freshli.Cli.Functionality +{ + [Index(nameof(Key), IsUnique = true)] + public class CachedProperty + { + public int Id { get; set; } + public string Key { get; set; } + public string Value { get; set; } + } +} From 484dc876cec4bff90250ee40c9a3a671e89dc9d9 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 16:11:33 -0400 Subject: [PATCH 31/38] Adds coverlet dotnet tool --- .config/dotnet-tools.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .config/dotnet-tools.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 000000000..74b90098c --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "coverlet.console": { + "version": "3.1.2", + "commands": [ + "coverlet" + ] + } + } +} From b9485cf79bec79b107d80a5e0d76f8bb38e8a2da Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 16:12:34 -0400 Subject: [PATCH 32/38] Ignores addition code coverage report files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index cc710054c..431d38c72 100644 --- a/.gitignore +++ b/.gitignore @@ -442,6 +442,8 @@ results/ # Ignore test coverage. coverage.info +coverage-acceptance.info +coverage.json Freshli.Cli.Test/coverage.info Freshli.Cli/nuget.config From c09b05c937751f2c73861105842b3c75e4316a2e Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 16:13:04 -0400 Subject: [PATCH 33/38] Attempts to add code coverage collection for Aruba --- .github/workflows/ci.yml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 694e1abcf..ee86ae458 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,9 @@ jobs: echo "NUGET_PACKAGE_PATH=${{ env.BUILD_ARTIFACTS_FOLDER }}/Corgibytes.Freshli.Cli.${{ steps.gitversion.outputs.NuGetVersionV2 }}.nupkg" >> $GITHUB_ENV - name: "[Test] - Test, Generate Code Coverage" + run: | + dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov + if: ${{ !env.ACT }} uses: paambaati/codeclimate-action@v2.7.5 env: @@ -115,15 +118,28 @@ jobs: ruby-version: 2.6 # Not needed with a .ruby-version file bundler-cache: true - - name: "[Test] - Behavior Acceptance Tests" + - name: "[Test] - Setup for Acceptance Test Coverage Collection" run: | - dotnet build -o bin && bundle exec cucumber + dotnet tool restore + + - name: "[Test] - Behavior Acceptance Tests with Coverage Collection" + run: | + dotnet build -o bin && dotnet coverlett ./bin --target "cucumber" --output ./coverage-acceptance.info --format lcov + + - name: "[Test] - Send Code Coverage Data to Code Climate" + uses: paambaati/codeclimate-action@v3.0.0 + env: + CC_TEST_REPORTER_ID: ${{ secrets.TEST_REPORTER_ID }} + with: + coverageLocations: | + ${{github.workspace}}/Corgibytes.Freshli.Cli.Test/coverage.info + ${{github.workspace}}/coverage-acceptance.info - name: "[Publish] - Publish win-x64, linux-x64 and osx-x64" run: | - dotnet publish -r win-x64 -c Release --self-contained false - dotnet publish -r linux-x64 -c Release --self-contained false - dotnet publish -r osx-x64 -c Release --self-contained false + dotnet publish -r win-x64 -c Release --self-contained false + dotnet publish -r linux-x64 -c Release --self-contained false + dotnet publish -r osx-x64 -c Release --self-contained false - name: "[Post Publish] - Zip win-x64 Release" uses: papeloto/action-zip@v1 From 071f6c28dbd0170b5e7a69ff40354a1515ec81d5 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 16:16:12 -0400 Subject: [PATCH 34/38] Removes extraneous configuration This was erroneously left over after the collect and submit steps were separated. --- .github/workflows/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee86ae458..dbb076d2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,15 +103,6 @@ jobs: run: | dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov - if: ${{ !env.ACT }} - uses: paambaati/codeclimate-action@v2.7.5 - env: - CC_TEST_REPORTER_ID: ${{ secrets.TEST_REPORTER_ID }} - with: - coverageCommand: dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov - coverageLocations: | - ${{github.workspace}}/Corgibytes.Freshli.Cli.Test/coverage.info:lcov - - name: "[Test] - Setup for Behavior Acceptance Tests" uses: ruby/setup-ruby@v1 with: From 9f1d82ee873d0a160719999808c18c61955ebb97 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 16:23:46 -0400 Subject: [PATCH 35/38] Bumps ruby version in CI to match the version in the DevContainer --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbb076d2a..41de58023 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,7 +106,7 @@ jobs: - name: "[Test] - Setup for Behavior Acceptance Tests" uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6 # Not needed with a .ruby-version file + ruby-version: 3.1 # Not needed with a .ruby-version file bundler-cache: true - name: "[Test] - Setup for Acceptance Test Coverage Collection" From e626d460a32b90ab9b56c2459650e8297ecab4a8 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 16:25:57 -0400 Subject: [PATCH 36/38] Fixes type "coverlett" -> "coverlet" --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41de58023..111b14442 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,7 +115,7 @@ jobs: - name: "[Test] - Behavior Acceptance Tests with Coverage Collection" run: | - dotnet build -o bin && dotnet coverlett ./bin --target "cucumber" --output ./coverage-acceptance.info --format lcov + dotnet build -o bin && dotnet coverlet ./bin --target "cucumber" --output ./coverage-acceptance.info --format lcov - name: "[Test] - Send Code Coverage Data to Code Climate" uses: paambaati/codeclimate-action@v3.0.0 From 849617b82b6dde0075eb2d5680787d94b2377402 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 16:29:10 -0400 Subject: [PATCH 37/38] Uses bundler to invoke cucumber executable --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 111b14442..26287ade2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,7 +115,7 @@ jobs: - name: "[Test] - Behavior Acceptance Tests with Coverage Collection" run: | - dotnet build -o bin && dotnet coverlet ./bin --target "cucumber" --output ./coverage-acceptance.info --format lcov + dotnet build -o bin && dotnet coverlet ./bin --target "bundle" --targetargs "exec cucumber" --output ./coverage-acceptance.info --format lcov - name: "[Test] - Send Code Coverage Data to Code Climate" uses: paambaati/codeclimate-action@v3.0.0 From 2d294b3aad953caf8faeed0f96b9064e5af5dfa2 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 18 May 2022 16:40:23 -0400 Subject: [PATCH 38/38] Adds docs for collecting coverage data --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e3f4c4308..25623af8f 100644 --- a/README.md +++ b/README.md @@ -188,9 +188,9 @@ Data (such as dates and numeric formatting) are NOT localized. Dates and numeric We are not sure how to handle documentation, such as this ReadMe, in different languages. If you have any suggestions or would like to help with translations please let us know using the contact information in the [Contributing](#contributing) section. -## Testing +## Acceptance Testing -You can test Freshli using Aruba and Cucumber, which is pre-configured in the repository. +In addition to running `dotnet test` to run the project's unit and integration tests, you run Freshli's acceptance test suite, built using Aruba and Cucumber, which is pre-configured in the repository. You will need Ruby installed on your system, and then run: @@ -205,6 +205,22 @@ From then on, you can run the Aruba tests with: dotnet build -o bin && bundle exec cucumber ``` +### Collecting Code Coverage for the Acceptance Tests + +Code coverage data can be collected for the acceptance tests. This activity is performed by the project's continuous integration environment where the collected data is sent to CodeClimate for further tracking. You can also run the code coverage collection locally. + +First you'll need to make sure that the correct version of the [Coverlet code coverage tool](https://github.com/coverlet-coverage/coverlet) is installed: + +```bash +dotnet tool restore +``` + +From then on, you can run the test suite and collect coverage with: + +```bash +dotnet coverlet bin/Corgibytes.Freshli.Cli --target "bundle" --targetargs "exec cucumber" --output +``` + ## Working with the DevContainer This project has uses DevContainer to assist with creating a full configured development environment.