{"id":2701,"date":"2023-07-17T15:37:48","date_gmt":"2023-07-17T20:37:48","guid":{"rendered":"https:\/\/badecho.com\/?p=2701"},"modified":"2024-12-08T00:42:36","modified_gmt":"2024-12-08T05:42:36","slug":"automated-versioning-with-github","status":"publish","type":"post","link":"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/","title":{"rendered":"Automated Versioning with GitHub"},"content":{"rendered":"\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AutomatedVersioning.png\" alt=\"Automated Versioning with GitHub\" class=\"wp-image-2702\" width=\"856\" height=\"448\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AutomatedVersioning.png 856w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AutomatedVersioning-300x157.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AutomatedVersioning-768x402.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AutomatedVersioning-480x251.png 480w\" sizes=\"(max-width: 856px) 100vw, 856px\" \/><\/figure><\/div>\n\n\n\n<p>Versioning your software helps to give it a history and a sense of progression. Having that automatically taken care of for you lift a rather tedious burden off your shoulders.<\/p>\n\n\n\n<p>I&#8217;ve previously written about my&nbsp;<a href=\"https:\/\/badecho.com\/index.php\/2022\/05\/06\/modern-net-versioning\/\" target=\"_blank\" rel=\"noreferrer noopener\">collected notes on modern .NET versioning practices<\/a>. Today, I&#8217;d like to showcase how I put all of it into effect at Bad Echo, resulting in a nicely version-automated working environment.<\/p>\n\n\n\n<h2 id=\"versioning-concepts\">Quick Rehash on Versioning Concepts<\/h2>\n\n\n\n<p>We intend to put&nbsp;<a href=\"https:\/\/semver.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">semantic versioning concepts<\/a>&nbsp;into practice with our software. As&nbsp;<a href=\"https:\/\/badecho.com\/index.php\/2022\/05\/06\/modern-net-versioning#stable-version-format\" target=\"_blank\" rel=\"noreferrer noopener\">mentioned in a previous article<\/a>, two versioning formats shall be employed: a stable and a developmental release format.<\/p>\n\n\n\n<h3>Stable Release Version Format<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>Major.Minor.Patch<\/code><\/pre>\n\n\n\n<p>The <em>major<\/em>, <em>minor<\/em>, and <em>patch<\/em> <em>version numbers<\/em> are simple numeric identifiers.<\/p>\n\n\n\n<p>As per the&nbsp;<a href=\"https:\/\/semver.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">semantic versioning principles<\/a>, the incrementation of a particular version number is dictated by the types of changes introduced into the new version.<\/p>\n\n\n\n<ul><li>Major: backward incompatible changes<\/li><li>Minor: backward compatible, new features<\/li><li>Patch: backward compatible bugfixes<\/li><\/ul>\n\n\n\n<h3>Prerelease Version Format<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>Major.Minor.Patch-PrereleaseId.Build&#91;+Metadata]<\/code><\/pre>\n\n\n\n<p>The&nbsp;<em>prerelease identifier<\/em>&nbsp;denotes the &#8220;degree&#8221; of stability and will typically be akin to&nbsp;<em>alpha<\/em>,&nbsp;<em>beta,&nbsp;<\/em>etc<em>.<\/em><\/p>\n\n\n\n<p>The&nbsp;<em>build number<\/em>&nbsp;is a counter incremented each time we generate a new build under a particular prerelease identifier.<\/p>\n\n\n\n<p>The&nbsp;<em>build metadata<\/em>&nbsp;is the short hash for the commit responsible for the build, and it allows us to quickly pull up a snapshot of the code as it was at the time of the build if we need to.<\/p>\n\n\n\n<p>The build number and build metadata do not appear in stable release packages for several reasons, the chief among them being that&nbsp;<a href=\"https:\/\/github.com\/NuGet\/Home\/wiki\/SemVer2-support-for-nuget.org-%28server-side%29#non-goals,\" target=\"_blank\" rel=\"noreferrer noopener\">NuGet and GitHub disapprove<\/a>. <\/p>\n\n\n\n<p>Disapproving powerful entities notwithstanding, having more than three numeric identifiers is too busy of a version for a public-facing package.<\/p>\n\n\n\n<p>That&#8217;s your quick rehash complete! Let&#8217;s now see how we make the above desired version formats a reality in our GitHub repos.<\/p>\n\n\n\n<h2>Build Customization Settings<\/h2>\n\n\n\n<p>For our versioning system to hook into our build process and control the version that gets baked into our assemblies, we need to provide it with some points of entry via custom MSBuild properties.<\/p>\n\n\n\n<p>The best way to do this is to author a&nbsp;<em>Directory.Build.props&nbsp;<\/em>file containing common version-related properties and place it at the root of each repo whose software we want versioned.<\/p>\n\n\n\n<h6 id=\"directory-build-props\">Directory.Build.props<\/h6>\n\n\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;Project&gt;\n\t&lt;!--Versioning.--&gt;\n\t&lt;PropertyGroup&gt;\n\t\t&lt;MajorVersion&gt;0&lt;\/MajorVersion&gt;\n\t\t&lt;MinorVersion&gt;1&lt;\/MinorVersion&gt;\n\t\t&lt;PatchVersion&gt;0&lt;\/PatchVersion&gt;\n\t\t&lt;VersionPrefix&gt;$(MajorVersion).$(MinorVersion).$(PatchVersion)&lt;\/VersionPrefix&gt;\n\t\t&lt;AssemblyVersion&gt;$(MajorVersion).0.0.0&lt;\/AssemblyVersion&gt;\n\t&lt;\/PropertyGroup&gt;\n\n\t&lt;Choose&gt;\n\t\t&lt;When Condition=&quot; '$(BuildMetadata)' != '' AND '$(PrereleaseId)' != '' AND '$(BuildNumber)' != ''&quot;&gt;\n\t\t\t&lt;PropertyGroup&gt;\n\t\t\t\t&lt;VersionSuffix&gt;$(PrereleaseId).$(BuildNumber)&lt;\/VersionSuffix&gt;\n\t\t\t\t&lt;InformationalVersion&gt;$(VersionPrefix)-$(VersionSuffix)+$(BuildMetadata)&lt;\/InformationalVersion&gt;\n\t\t\t\t&lt;FileVersion&gt;$(MajorVersion).$(MinorVersion).$(PatchVersion).$(BuildNumber)&lt;\/FileVersion&gt;\n\t\t\t&lt;\/PropertyGroup&gt;\n\t\t&lt;\/When&gt;\n\t\t&lt;Otherwise&gt;\n\t\t\t&lt;PropertyGroup&gt;\n\t\t\t\t&lt;FileVersion&gt;$(MajorVersion).$(MinorVersion).$(PatchVersion).0&lt;\/FileVersion&gt;\n\t\t\t&lt;\/PropertyGroup&gt;\n\t\t&lt;\/Otherwise&gt;\n\t&lt;\/Choose&gt;\n\n&lt;\/Project&gt;\n<\/pre>\n\n\n<p>The values set for each element here are immaterial, as values established by our build scripts will override them; what matters here is that the properties are defined.<\/p>\n\n\n\n<p>With this build configuration file in place, all code projects found under the root directory of our repo will automatically inherit these version properties and allow our process to version them.<\/p>\n\n\n\n<h2>Version File<\/h2>\n\n\n\n<p>We now need to create a record that acts as the source of truth for software version information in our repo. A simple JSON file with the name <em>version.json<\/em>, placed in the root directory of our repo, serves this purpose.<\/p>\n\n\n\n<h6>version.json<\/h6>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n  {\n  &quot;majorVersion&quot;: 1,\n  &quot;minorVersion&quot;: 0,\n  &quot;patchVersion&quot;: 12,\n  &quot;prereleaseId&quot;: &quot;alpha&quot; \n}\n<\/pre>\n\n\n<p>The version file is the source for all current versioning information and is referenced during compilation. All software projects in the repo will reflect this versioning information; if there is a need for a project to have a different version, consider placing it in its own repository.<\/p>\n\n\n\n<p>Every time we change one of the version numbers in this file, the build number will reset back to 0. We&#8217;ll explore how this happens in the next section covering our&nbsp;<em>build submodule<\/em>, which contains all common build-related scripts and assets.<\/p>\n\n\n\n<h2>Build Submodule<\/h2>\n\n\n\n<p>All Bad Echo software uses the same scripts to handle the building, versioning, and deployment of compiled assets. To avoid redundant code, I created a single repo to hold all build assets, which other repos can reference as a submodule.<\/p>\n\n\n\n<p>You can view the Bad Echo common build repository <a href=\"https:\/\/github.com\/BadEcho\/build\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>. Other repos reference it by adding it as a submodule via running the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git submodule add https:\/\/github.com\/BadEcho\/build.git<\/code><\/pre>\n\n\n\n<p>This will create a <em>build<\/em> folder in the root directory of the repo. The build workflows we&#8217;ll examine later will expect the build assets to be present at this location.<\/p>\n\n\n\n<p>The two assets we care most about in our build submodule are the&nbsp;<em>build script<\/em>&nbsp;and the&nbsp;<em>push script<\/em>.<\/p>\n\n\n\n<h3>Build Script<\/h3>\n\n\n\n<p>Our workflow uses the build script to compile, test, and package our software assets. It accepts several parameters:<\/p>\n\n\n\n<ul><li>Commit ID (optional)<\/li><li><em>Version distance<\/em> (only required if commit ID provided)<\/li><li>Value indicating if tests should be skipped<\/li><\/ul>\n\n\n\n<p>The commit ID is the short hash of the commit responsible for the build, and it is the value we use as the build metadata. We only provide it when we&#8217;re creating a prerelease build.<\/p>\n\n\n\n<p>The version distance is the number of commits made since we last updated our&nbsp;<em>version.json<\/em>&nbsp;file. It&#8217;s used as the build number and only provided when creating a prerelease build.<\/p>\n\n\n\n<h6>build.ps1<\/h6>\n\n\n<pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n# Builds the Bad Echo solution.\n\nparam (\n\t[string]$CommitId,\n\t[string]$VersionDistance,\n\t[switch]$SkipTests\n)\n\nfunction Execute([scriptblock]$command) {\n\t&amp; $command\n\tif ($lastexitcode -ne 0) {\n\t\tthrow (&quot;Build command errored with exit code: &quot; + $lastexitcode)\n\t}\n}\n\nfunction AppendCommand([string]$command, [string]$commandSuffix){\n\treturn [ScriptBlock]::Create($command + $commandSuffix)\n}\n\nNew-Item -ItemType Directory -Force .\\artifacts\n$artifacts = Resolve-Path .\\artifacts\\ | select -ExpandProperty Path\n\nif (Test-Path $artifacts) {\n\tRemove-Item $artifacts -Force -Recurse\n}\n\n$versionSettings = Get-Content version.json | ConvertFrom-Json\n$majorVersion = $versionSettings[0].majorVersion\n$minorVersion = $versionSettings[0].minorVersion\n$patchVersion = $versionSettings[0].patchVersion\n\n$buildCommand =  { &amp; dotnet build -c Release `\n\t\t\t\t\t-p:MajorVersion=$majorVersion -p:MinorVersion=$minorVersion `\n\t\t\t\t\t-p:PatchVersion=$patchVersion }\n$packCommand = { &amp; dotnet pack -c Release --no-build `\n\t\t\t\t\t-p:PackageOutputPath=$artifacts -p:MajorVersion=$majorVersion `\n\t\t\t\t\t-p:MinorVersion=$minorVersion -p:PatchVersion=$patchVersion }\n\nif($CommitId -and $VersionDistance) {\t\n\t$prereleaseId = $versionSettings[0].prereleaseId\n\t\t\n\t$versionCommand \n\t\t= &quot;-p:BuildMetadata=$CommitId -p:PrereleaseId=$prereleaseId -p:BuildNumber=$VersionDistance&quot;\n\n\t$buildCommand = AppendCommand($buildCommand.ToString(), $versionCommand)\n\t$packCommand = AppendCommand($packCommand.ToString(), $versionCommand)\n}\n\nExecute { &amp; dotnet clean -c Release }\nExecute $buildCommand \n\nif ($SkipTests -ne $true) {\n\tExecute { &amp; dotnet test -c Release --results-directory $artifacts --no-build -l trx `\n\t\t\t\t--verbosity=normal }\n}\n\nExecute $packCommand\n<\/pre>\n\n\n<p>The result of running this script will be <em>.nupkg<\/em> files for each software project, properly versioned at both the assembly and package levels.<\/p>\n\n\n\n<h3>Push Script<\/h3>\n\n\n\n<p>The next script we have is the push script, which is used by our workflow to deploy our assembled packages to the desired package repository.<\/p>\n\n\n\n<h6>push.ps1<\/h6>\n\n\n<pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n# Pushes Bad Echo packages to a package repository.\n\n$scriptName = $MyInvocation.MyCommand.Name\n$artifacts = &quot;.\\artifacts&quot;\n\nif ([string]::IsNullOrEmpty($Env:PKG_API_KEY)) {\n\tWrite-Host &quot;${scriptName}: PKG_API_KEY has not been set; no packages will be pushed.&quot;\n}\nelseif ([string]::IsNullOrEmpty($Env:PKG_URL)) {\n\tWrite-Host &quot;${scriptName}: PKG_URL has not been set; no packages will be pushed.&quot;\n}\nelse {\n\tGet-ChildItem $artifacts -Filter &quot;*.nupkg&quot; | ForEach-Object {\n\t\tWrite-Host &quot;$($scriptName): Pushing $($_.Name) to repository.&quot;\n\t\tdotnet nuget push $_ --source $Env:PKG_URL --api-key $Env:PKG_API_KEY\n\t\tif ($lastexitcode -ne 0) {\n\t\t\tthrow (&quot;Push command errored with exit code: &quot; + $lastexitcode)\n\t\t}\n\t}\n}\n<\/pre>\n\n\n<p>This is a very basic script, which mainly revolves around running a <code>dotnet nuget<\/code> command.<\/p>\n\n\n\n<p>The <code>$Env:PKG_URL<\/code> and  <code>$Env:PKG_API_KEY<\/code> environment variables are defined as secrets within your GitHub repo settings.<\/p>\n\n\n\n<p>The package repository URL isn&#8217;t hardcoded so that we can support multiple destinations; we only sometimes want to deploy to NuGet, depending on the circumstances.<\/p>\n\n\n\n<p>Why are the API key and package URL environment variables not command line parameters? I suppose the answer is that this script originated as one I used locally, and the environment variables seen here are defined as system environment variables on my machine.<\/p>\n\n\n\n<p>You may, of course, feel free to change the script to accept parameters instead &#8212; bear in mind the workflows (which we&#8217;ll be exploring in a bit) will need to be updated as well.<\/p>\n\n\n\n<p>Now that we have the build-time assets laid out lets look at the entities that make use of them: our workflows.<\/p>\n\n\n\n<h2 id=\"workflow-templates\">Workflow Templates<\/h2>\n\n\n\n<p><a href=\"https:\/\/docs.github.com\/en\/actions\/using-workflows\/about-workflows\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub Actions workflows<\/a> are automated processes that run one or more jobs in response to some repository-level event.<\/p>\n\n\n\n<p>We will need workflows added to every code repository which we desire to have automated versioning.<\/p>\n\n\n\n<p>To simplify this process, I felt it most helpful to create several&nbsp;<em>workflow templates<\/em>&nbsp;for Bad Echo software and then apply them to each repository I wanted them to run in.<\/p>\n\n\n\n<p>We&#8217;re now going to look at how you can create your own workflow templates, which you can apply to your repos to establish an automated versioning process.<\/p>\n\n\n\n<h3>Organizational Repository<\/h3>\n\n\n\n<p>Workflow templates need to be hosted from a special kind of GitHub repository. GitHub doesn&#8217;t give a nice, human-readable name for this repository type, so I refer to them as <em>organizational repositor<\/em>ies.<\/p>\n\n\n\n<p>To create an organization repository, create a new public repo on GitHub named <code>.github<\/code>. You don&#8217;t need an organization account to create one; a personal account will do just fine.<\/p>\n\n\n\n<p>Inside your new repo, create a directory named <em>workflow-templates<\/em>. This is where all of our workflow templates will live.<\/p>\n\n\n\n<p>Each workflow template requires the following:<\/p>\n\n\n\n<ul><li><em>workflow.properties.json<\/em>: Configuration for the workflow, containing settings such as display name, description, etc.<\/li><li><em>workflow.yml<\/em>: The base YAML of the workflow.<\/li><li><em>Icon.svg<\/em>: An icon for our workflow, perhaps optional, but why not give it some flair?<\/li><\/ul>\n\n\n\n<p>Now that we&#8217;ve covered these fundamentals let us look at the two workflow templates we&#8217;ll be defining for use in our repositories.<\/p>\n\n\n\n<h3>Push\/CI Workflow<\/h3>\n\n\n\n<p>The first workflow we&#8217;ll define will run every time a change is pushed to one of our repositories. It will compile the code, version it as a prerelease build, run tests, and publish the packages to a development\/unstable package repository feed (i.e., not NuGet).<\/p>\n\n\n\n<p>Using it will provide <em>continuous integration<\/em> to the repository it runs in.<\/p>\n\n\n\n<h6>push.yml<\/h6>\n\n\n<pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\n# On a push to source control, this compiles a release build for the repository's code, runs \n# tests, and then publishes new development builds to MyGet.\n\nname: CI on Push\n \non:\n  push:\n    branches:  \n      - master\n    paths-ignore:\n      - '.github\/workflows\/**'\n      - '!.github\/workflows\/push.yml'\n  pull_request:\n    branches:\n      - master\njobs:\n  build:  \n    runs-on: windows-2022\n    steps:\n    - name: Checkout\n      uses: actions\/checkout@v3\n      with:\n        submodules: recursive\n        fetch-depth: 0\n    - name: Setup .NET\n      uses: actions\/setup-dotnet@v3\n      with:\n        dotnet-version: 7.0.x      \n    - name: Build\n      shell: pwsh      \n      run: .\\build\\build.ps1 $(git rev-parse --short HEAD) $(git rev-list --count &quot;$(git log -1 --pretty=format:&quot;%H&quot; version.json)..HEAD&quot;)\n    - name: Push to MyGet\n      env:\n        PKG_URL: https:\/\/www.myget.org\/F\/bad-echo\/api\/v3\/index.json\n        PKG_API_KEY: ${{ secrets.MYGET_API_KEY }}\n      run: .\\build\\push.ps1\n      shell: pwsh\n    - name: Artifacts\n      if: always()\n      uses: actions\/upload-artifact@v2\n      with:\n        name: artifacts\n        path: artifacts\/**\/*\n<\/pre>\n\n\n<p>Let&#8217;s take a look at what this does. The majority of it is standard boilerplate: we checkout the latest version of our code and set up a .NET environment to build our code in.<\/p>\n\n\n\n<p>The main point of interest is the <strong>Build<\/strong> step, which is where we invoke our <em>build.ps1<\/em> script. Let us break down the parameters.<\/p>\n\n\n\n<p>The short commit ID parameter is provided with the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$(git rev-parse --short HEAD)<\/code><\/pre>\n\n\n\n<p>This <code>rev-parse<\/code> command simply returns the short hash of <code>HEAD<\/code>, the most recent commit to the repository. This is our build metadata.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$(git rev-list --count \"$(git log -1 --pretty=format:\"%H\" version.json)..HEAD\")<\/code><\/pre>\n\n\n\n<p>This <code>rev-list<\/code> command counts the number of commits there have been since <em>version.json<\/em> has been changed. This is our build number. <\/p>\n\n\n\n<p>We can use a <code>git rev-list -1 HEAD version.json<\/code> as opposed to a <code>git log -1 --pretty=format:\"%H\" version.json<\/code> for the argument to <code>--count<\/code>, but what we have works all the same.<\/p>\n\n\n\n<p>Following the&nbsp;<strong>Build<\/strong>&nbsp;step, we have the&nbsp;<strong>Push to MyGet<\/strong>&nbsp;step, which invokes our&nbsp;<em>push.ps1<\/em>&nbsp;script. The feed URL and API key are provided as environment variables &#8212; remember to change the URL being used here so you don&#8217;t attempt to publish to my feed!<\/p>\n\n\n\n<h6>push.properties.json<\/h6>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n{\n    &quot;name&quot;: &quot;CI on Push Workflow&quot;,\n    &quot;description&quot;: &quot;Bad Echo starter CI workflow.&quot;,\n    &quot;iconName&quot;: &quot;Icon&quot;,\n    &quot;filePatterns&quot;: [\n        &quot;version.json$&quot;,\n        &quot;build&quot;\n    ]\n}\n<\/pre>\n\n\n<p>This sets the name and description for our workflow; it also establishes some requirements via the <code>filePatterns<\/code> key that the repository must meet to use this workflow:<\/p>\n\n\n\n<ul><li>A <em>version.json<\/em> file must be present in the repository<\/li><li>A <em>build<\/em> directory must exist, which hopefully points to our build Git submodule.<\/li><\/ul>\n\n\n\n<p>Feel free to omit the <code>filePatterns<\/code> property entirely, however. My testing indicates that it has no effect, as I can apply this workflow to repositories even if they lack the&nbsp;<em>version.json<\/em>&nbsp;file, the&nbsp;<em>build<\/em>&nbsp;directory, or both.<\/p>\n\n\n\n<h3>Publish Workflow<\/h3>\n\n\n\n<p>The next workflow we&#8217;ll be defining will be responsible for creating new stable releases of our software. It will compile the code, version it as a release build, create a tag for the release, and publish the packages to an official NuGet package repository feed.<\/p>\n\n\n\n<p>Several essential differences will exist between our publish and push\/CI workflows. Most notably, the publish workflow will not run in response to some repository-level event, but rather from manual action.<\/p>\n\n\n\n<p>For a workflow to be run manually, GitHub requires us to configure the workflow to run on the <code>workflow_dispatch<\/code> event.<\/p>\n\n\n\n<p>Configuring the workflow to be manually triggerable will allow us to kick it off from our repository&#8217;s GitHub Actions page while also being able to provide it with some inputs that it can use to deliver better the experience we want.<\/p>\n\n\n\n<h6>publish.yml<\/h6>\n\n\n<pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\n# Publishes a new release of Bad Echo software to official NuGet feeds.\n# To configure this workflow, replace variables with their correct values in the &quot;env&quot; section below.\n\nname: Publish Release\n\non:\n  workflow_dispatch:\n    inputs:\n      echo-version:\n        description: Echo Version\n        required: true\njobs:\n  publish:\n    name: Publish\n    runs-on: windows-2022\n    env:\n      product-Name: Bad Echo Software # Replace with the software product being published. Appears in the release tag commit message.\n    steps:\n    - name: Checkout\n      uses: actions\/checkout@v3\n      with:\n        submodules: recursive\n        fetch-depth: 0      \n    - run: git config --global user.email &quot;chamber@badecho.com&quot;\n    - run: git config --global user.name &quot;Echo Chamber&quot;\n    - name: Create Echo Version\n      run: |\n        git tag -a ${{ github.event.inputs.echo-version }} HEAD -m &quot;${{ env.product-name }} ${{ github.event.inputs.echo-version }}&quot;\n        git push origin ${{ github.event.inputs.echo-version }}\n    - name: Setup .NET\n      uses: actions\/setup-dotnet@v3\n      with:\n        dotnet-version: 7.0.x      \n    - name: Build\n      shell: pwsh\n      run: .\\build\\build.ps1\n    - name: Push to NuGet\n      env:\n        PKG_URL: https:\/\/api.nuget.org\/v3\/index.json\n        PKG_API_KEY: ${{ secrets.NUGET_API_KEY }}\n      run: .\\build\\push.ps1\n      shell: pwsh\n    - name: Artifacts\n      if: always()\n      uses: actions\/upload-artifact@v2\n      with:\n        name: artifacts\n        path: artifacts\/**\/*\n\n<\/pre>\n\n\n<p>The workflow takes a single &#8220;version&#8221; string input from the user, which it uses as the <code>tagname<\/code> for the release tag that gets created. Additionally, a &#8220;product name&#8221; variable is defined, which we use as part of the tag&#8217;s message; however, this is meant to be configured inside the applied workflow itself and not provided when running the workflow.<\/p>\n\n\n\n<p>This input value does not affect the actual version that the software gets packaged up as (<em>version.json<\/em>&nbsp;is still king in this regard). It is merely the label we&#8217;ll tag onto this specific point in our repository&#8217;s history, indicating that we&#8217;ve made a release.<\/p>\n\n\n\n<p>I typically will provide version labels such as &#8220;v1.0.7&#8221;, &#8220;v1.0.8&#8221;, and so on.<\/p>\n\n\n\n<p>If we look at the&nbsp;<strong>Build<\/strong>&nbsp;step, we&#8217;ll see our&nbsp;<em>build.ps1<\/em>&nbsp;script is yet again being invoked; however, no arguments are provided. That&#8217;s because this is a stable release build, and only three version numbers will be present on the packaged output (the major, minor, and patch version numbers).<\/p>\n\n\n\n<p>Following the <strong>Build<\/strong> step, we have the <strong>Push to NuGet<\/strong> step. Once again, remember to change the feed URL to your own.<\/p>\n\n\n\n<h4>Increment the Version After a Release!<\/h4>\n\n\n\n<p>It should be noted that after this workflow runs, we will have a stable release build for our code whose version is of the format <code>Major.Minor.Patch<\/code> with values sourced from <em>version.json<\/em>. <\/p>\n\n\n\n<p>No further builds should be created using these version numbers following the release &#8212; either the major, minor, or patch number should be incremented in the <em>version.json<\/em> file before any other changes occur.<\/p>\n\n\n\n<p>It would be neat if the workflow automated this or if a safeguard could be put in to prevent additional builds using a released version number; unfortunately, it&#8217;s not something I&#8217;ve gotten around to yet.<\/p>\n\n\n\n<h6>publish.properties.json<\/h6>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n{\n    &quot;name&quot;: &quot;Publish Release Workflow&quot;,\n    &quot;description&quot;: &quot;Bad Echo starter publishing workflow.&quot;,\n    &quot;iconName&quot;: &quot;Icon&quot;,\n    &quot;filePatterns&quot;: [\n        &quot;version.json$&quot;,\n        &quot;build&quot;\n    ]\n}\n<\/pre>\n\n\n<p>This is congruous with our push\/CI workflow&#8217;s settings file, save for the different name and description values. Not much is happening here; we&#8217;re simply ensuring the workflow has an identifiable name.<\/p>\n\n\n\n<p>Once this is set up and saved to disk, please create a new commit containing these files and push it to our <code>.github<\/code> repository. We&#8217;re now ready to apply some automatic versioning to our GitHub repos.<\/p>\n\n\n\n<h2>Putting It All Together<\/h2>\n\n\n\n<p>Now that we have these workflow templates published to our organizational repo, we can easily apply them to all the code repos in which we want streamlined automatic versioning.<\/p>\n\n\n\n<h3>Configuring Secrets<\/h3>\n\n\n\n<p>Before we add our workflows, we&#8217;ll need to set up any repository secrets required by the workflows. Unfortunately, I could not discover a way to add account-wide secrets for a personal GitHub account; however, the option to add organization-wide secrets does exist if you have an organization account.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"alignleft size-full\"><img loading=\"lazy\" width=\"321\" height=\"131\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AddingSecrets.png\" alt=\"Click on &quot;Actions&quot; under &quot;Secrets and variables&quot; to add secrets required by our workflows.\" class=\"wp-image-2734\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AddingSecrets.png 321w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AddingSecrets-300x122.png 300w\" sizes=\"(max-width: 321px) 100vw, 321px\" \/><\/figure><\/div>\n\n\n\n<p>If you&#8217;re like me and rock a personal account, then for every repository you want to add the workflows to, you&#8217;ll need to click the&nbsp;<strong>Settings<\/strong>&nbsp;button on the repository&#8217;s page and browse the repository&#8217;s secrets.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Once there, you can create the secrets your workflows will need. The push\/CI workflow expects a <code>MYGET_API_KEY<\/code> secret, and the release workflow expects a <code>NUGET_API_KEY<\/code> secret.<\/p>\n\n\n\n<p>With our secrets defined, we can finally apply our workflow templates to our repositories.<\/p>\n\n\n\n<h3>Adding the Workflows<\/h3>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"alignright size-full\"><img loading=\"lazy\" width=\"109\" height=\"60\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/ActionsClick.png\" alt=\"Click on &quot;Actions&quot; to add our new workflows.\" class=\"wp-image-2723\"\/><\/figure><\/div>\n\n\n\n<p>First, you&#8217;ll want to browse to your repository on&nbsp;<a href=\"http:\/\/github.com\" target=\"_blank\" rel=\"noreferrer noopener\">github.com<\/a>&nbsp;and then click on the&nbsp;<strong>Actions<\/strong>&nbsp;button.&nbsp;<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"alignleft size-full\"><img loading=\"lazy\" width=\"316\" height=\"102\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/NewWorkflow.png\" alt=\"You'll need to his the &quot;New workflow&quot; button to add one of our new workflows if the repository already has had a workflow added to it.\" class=\"wp-image-2729\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/NewWorkflow.png 316w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/NewWorkflow-300x97.png 300w\" sizes=\"(max-width: 316px) 100vw, 316px\" \/><\/figure><\/div>\n\n\n\n<p>If you&#8217;ve previously added some actions to your repository, they will be listed here, and you will need to click the&nbsp;<strong>New workflow<\/strong>&nbsp;button.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Otherwise, you&#8217;ll be presented with a page that allows you to search for a new workflow to add. Positioned near the top of this page, you should see the organizational workflows you just published.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img loading=\"lazy\" width=\"598\" height=\"228\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/WorkflowTemplates.png\" alt=\"Our organization's workflow templates we just created.\" class=\"wp-image-2724\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/WorkflowTemplates.png 598w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/WorkflowTemplates-300x114.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/WorkflowTemplates-480x183.png 480w\" sizes=\"(max-width: 598px) 100vw, 598px\" \/><figcaption>Click &#8216;Configure&#8217; on each workflow to add it to our repository.<\/figcaption><\/figure><\/div>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"alignright size-full\"><img loading=\"lazy\" width=\"157\" height=\"55\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/CommitChanges.png\" alt=\"Click the &quot;Commit changes...&quot; button to finish adding the new workflow into your repository.\" class=\"wp-image-2732\"\/><\/figure><\/div>\n\n\n\n<p>Clicking on the <strong>Configure<\/strong> button will bring us to an editor with our workflow&#8217;s YAML loaded into it. You can make any changes you need here and then hit <strong>Commit changes&#8230;<\/strong> to finish adding the workflow to our repository.<\/p>\n\n\n\n<p>If we do this for both of our workflows, we will have a repository with a continuous integration process running every time we push changes to it and a manually triggered process that will publish stable new releases.<\/p>\n\n\n\n<p>Whenever we want to publish a new stable release, we simply browse to the workflow via the repository&#8217;s&nbsp;<strong>Action<\/strong>&nbsp;page and then click the&nbsp;<strong>Run workflow<\/strong>&nbsp;button.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" width=\"1024\" height=\"320\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/RunningPublishWorkflow-1024x320.png\" alt=\"Our Publish Release workflow can be manually triggered as pictured.\" class=\"wp-image-2726\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/RunningPublishWorkflow-1024x320.png 1024w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/RunningPublishWorkflow-300x94.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/RunningPublishWorkflow-768x240.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/RunningPublishWorkflow-480x150.png 480w, https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/RunningPublishWorkflow.png 1293w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption>Clicking on &#8216;Run workflow&#8217; allows us to specify the version and trigger a new manual run.<\/figcaption><\/figure><\/div>\n\n\n\n<p>This will compile a new release build, push it off to NuGet, and tag the current commit with the version label provided as the workflow&#8217;s input.<\/p>\n\n\n\n<p>The processes documented in this article are what I currently use over at Bad Echo, and they do their job well. If I make any significant changes or improvements to the system, I&#8217;ll be sure to write about it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Versioning your software helps to give it a history and a sense of progression. Having that automatically taken care of [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[10],"tags":[41,42,81,80],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\r\n<title>Automated Versioning with GitHub - omni&#039;s hackpad<\/title>\r\n<meta name=\"description\" content=\"Guides the reader through the creation of reusable workflows that, when applied to a GitHub repo, provide automatic versioning support.\" \/>\r\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\r\n<link rel=\"canonical\" href=\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/\" \/>\r\n<meta property=\"og:locale\" content=\"en_US\" \/>\r\n<meta property=\"og:type\" content=\"article\" \/>\r\n<meta property=\"og:title\" content=\"Automated Versioning with GitHub - omni&#039;s hackpad\" \/>\r\n<meta property=\"og:description\" content=\"Guides the reader through the creation of reusable workflows that, when applied to a GitHub repo, provide automatic versioning support.\" \/>\r\n<meta property=\"og:url\" content=\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/\" \/>\r\n<meta property=\"og:site_name\" content=\"omni&#039;s hackpad\" \/>\r\n<meta property=\"article:published_time\" content=\"2023-07-17T20:37:48+00:00\" \/>\r\n<meta property=\"article:modified_time\" content=\"2024-12-08T05:42:36+00:00\" \/>\r\n<meta property=\"og:image\" content=\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AutomatedVersioning.png\" \/>\r\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\r\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/badecho.com\/#website\",\"url\":\"https:\/\/badecho.com\/\",\"name\":\"omni&#039;s hackpad\",\"description\":\"Game Code Disassembly. Omnified Modification. Madness.\",\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/badecho.com\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/badecho.com\/wp-content\/uploads\/2023\/07\/AutomatedVersioning.png\",\"width\":856,\"height\":448,\"caption\":\"Automated Versioning with GitHub\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/#webpage\",\"url\":\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/\",\"name\":\"Automated Versioning with GitHub - omni&#039;s hackpad\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/#primaryimage\"},\"datePublished\":\"2023-07-17T20:37:48+00:00\",\"dateModified\":\"2024-12-08T05:42:36+00:00\",\"description\":\"Guides the reader through the creation of reusable workflows that, when applied to a GitHub repo, provide automatic versioning support.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/\"]}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/#webpage\"},\"author\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"headline\":\"Automated Versioning with GitHub\",\"datePublished\":\"2023-07-17T20:37:48+00:00\",\"dateModified\":\"2024-12-08T05:42:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/#webpage\"},\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"image\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/#primaryimage\"},\"keywords\":\".NET,C#,GitHub,PowerShell\",\"articleSection\":\"General Dev\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/badecho.com\/index.php\/2023\/07\/17\/automated-versioning-with-github\/#respond\"]}]},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\",\"name\":\"Matt Weber\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7e345ac2708b3a41c7bd70a4a0440d41?s=96&d=mm&r=g\",\"caption\":\"Matt Weber\"},\"logo\":{\"@id\":\"https:\/\/badecho.com\/#personlogo\"}}]}<\/script>\r\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2701"}],"collection":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/comments?post=2701"}],"version-history":[{"count":34,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2701\/revisions"}],"predecessor-version":[{"id":2972,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2701\/revisions\/2972"}],"wp:attachment":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/media?parent=2701"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/categories?post=2701"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/tags?post=2701"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}