We use C# 9 and .Net 5.x (soon to upgrade to C# 10 + .Net 6.x).
The application is split up into a Console App per source technology (e.g. github, ado, bitbucket, etc) + Class Library for shared code.
When the CLI is executed the flow of execution is:
-
Program.cs - sets up some plumbing/infrastructure. It uses System.CommandLine library to parse the command-line args and invoke the appropriate command class. It also sets up dependency injection.
-
Commands - For every command the CLI supports there is a XXXCommand class. This class is responsible for defining the various options/args supported by that command, and most of the implementation logic.
-
AdoApi/GithubApi - (and eventually classes for Bitbucket/GitLab/etc) is a bunch of small functions that wrap ADO/GitHub API's (one function per API). They encapsulate the details about the endpoint URL, the request payload, and how to parse the response.
-
AdoClient/GithubClient - A wrapper over HttpClient that implements some helper logic that is not specific to any single API. E.g. paging through results, setting authorization headers, handling throttling/retries, etc.
All tests must run and pass on every PR.
For unit testing we use XUnit.Net and Moq.
Pretty much all code should be covered by unit tests. Any new code should come with appropriate unit tests in the same PR.
In addition there are a small number of End-to-End integration tests (still a work in progress), that will run actual migrations against ADO/GitHub.
WARNING: Running the integration tests locally will crash any CI integration test actions that are in progress and result in unexpected behavior, so wait until any in progress actions finish first.
- First make sure your environment variables are set:
export ADO_PAT=<pat_found_in_1password> export GH_PAT=<pat_found_in_1password>
- Build the binary with publish.ps1 (needs PowerShell) or copy the build command from publish.ps1:
pwsh
> ./publish.ps1 > exit
- Install the gh gei extension using the new binary:
mkdir gh-gei # Copy the binary from wherever it's located, for ex: cp ./dist/linux-x64/gei-linux-amd64 ./gh-gei/gh-gei cd gh-gei gh extension install . cd ..
- Run the integration tests, either use the Test Runner in Visual Studio, or run the command:
dotnet test src/OctoshiftCLI.IntegrationTests/OctoshiftCLI.IntegrationTests.csproj
Running e2e tests locally isn't supported yet, to run these you can create a draft PR.
There are 3 types of static analysis that are enforced on every PR:
-
Roslyn analyzers - These enforce various coding standards and design patterns. If you use Visual Studio this will automatically run on build and generate warnings. On PR those warnings will be treated as errors. Note: There are some roslyn rules that run in Visual Studio but not in the PR build (only because we haven't figured out how to run them from Actions yet). Please also ensure these are all passing. If a rule is determined to be causing more pain than value they can be turned off in src/.editorconfig (Note: any changes to this file will get extra scrutiny). Our approach has been to start by turning on more rules than we probably should, and selectively turn them off as we encounter ones that aren't providing much value.
-
CodeQL - This is run on every PR and checks for any potential security or quality issues in the code. As with the Roslyn analyzers we have defaulted to turning on most rules, and will turn some off over time if needed. CodeQL rules can be turned off in .github/codeql/csharp-custom-queries.qls. It should possible to run CodeQL locally by following this guide. In the future we will setup a Codespaces container that makes this easier to get going. You can also create a draft PR to run a CodeQL scan.
-
dotnet format - This is a style/formatting check. To ensure we follow consistent code formatting you should run
dotnet format
locally before creating a PR. If the PR build fails on dotnet format it's almost certainly because you didn't run dotnet format before committing.
Only repo maintainers can publish a release. The process is kicked off by pushing a tag in the format v0.7
. We follow Semantic Versioning when deciding on the next version number.
- Check
RELEASENOTES.md
to see if there is anything to release. - Switch to the
main
branch andpull
the latest. - optional List the tags e.g.
git tag
- Tag the version e.g.
git tag v5.0
- Push the tags up e.g.
git push --tags
- This will trigger an Actions workflow that results in a new release being published, once the build is done it will wait for approval(from maintainers) in order to
pushlish
the binaries. The workflow does the following steps:- Validates that the SHA referenced by the tag is in the history of the
main
branch - Runs
publish.ps1
to build self-contained binaries for each platform. This script also embeds the version number (extracted from the tag) into each binary. - Creates a release in this repo with the self-contained binaries, uses the contents of
RELEASENOTES.md
as the release description. - Moves the contents of
[RELEASENOTES.md](http://RELEASENOTES.md)
to a version specific file under the name of the createdtag
in the releasenotes folder, then empties outRELEASENOTES.md
and commits the 2 files.
- Validates that the SHA referenced by the tag is in the history of the
You can try developing this repo using GitHub codespaces, so all the dependencies are installed for you!
Check out publish.ps1
to see how binaries are built for this repo for various distributions.
Build with:
dotnet build src/OctoshiftCLI.sln
Alternatively, you can use for gei
dotnet watch build --project src/gei/gei.csproj
to run builds automatically.
- If you're doing this, you can run the binaries with
./src/gei/bin/Debug/net6.0/gei
If you aren't using watch, run after building with:
dotnet run --project src/gei/gei.csproj
Run tests with
dotnet test src/OctoshiftCLI.Tests/OctoshiftCLI.Tests.csproj
Format your files locally with:
dotnet format src/OctoshiftCLI.sln
When you add a new parameter, the name of the argument in the invoke function has to match the name of the parameter.
If somebody who is not a maintainer on this repo submits a PR the CI build will fail because of permissions/secrets issues. We can probably improve this in the future, but for now a workaround is for somebody who is a maintainer to do this:
git fetch origin pull/263/head:pr263
git checkout -b dylan-smith/external-pr-263 pr263
git push -u origin dylan-smith/external-pr-263
where 263 is the PR number. After running these commands create a draft PR with the new branch (dylan-smith/external-pr-263
in this example). The new draft PR will run CI successfully, and the success (or failure) will show up on both the new draft PR and the original PR from the external contributor.