├── .gitattributes
├── .gitignore
├── Build.ps1
├── LICENSE
├── README.md
├── appveyor.yml
├── dotnet-version-cli.sln
├── src
├── CsProj
│ ├── FileSystem
│ │ ├── DotNetFileSystemProvider.cs
│ │ └── IFileSystemProvider.cs
│ ├── ProjectFileDetector.cs
│ ├── ProjectFileParser.cs
│ ├── ProjectFileProperty.cs
│ └── ProjectFileVersionPatcher.cs
├── Model
│ ├── ProductOutputInfo.cs
│ └── VersionInfo.cs
├── OutputFormat.cs
├── ProductInfo.cs
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
├── Vcs
│ ├── Git
│ │ └── GitVcs.cs
│ ├── IVcs.cs
│ └── VcsParser.cs
├── VersionBump.cs
├── VersionCli.cs
├── VersionCliArgs.cs
├── Versioning
│ ├── SemVer.cs
│ └── SemVerBumper.cs
└── dotnet-version.csproj
└── test
├── CsProj
├── FileSystem
│ └── DotNetFileSystemProviderTests.cs
├── ProjectFileDetectorTest.cs
├── ProjectFileParserTest.cs
└── ProjectFileVersionPatcherTest.cs
├── GitVcsTest.cs
├── ProgramTest.cs
├── VersionCliTest.cs
├── Versioning
├── SemVerBumperTests.cs
└── SemVerTest.cs
└── dotnet-version-test.csproj
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | *.sln merge=binary
26 | *.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
65 | # Force bash scripts to always use lf line endings so that if a repo is accessed
66 | # in Unix via a file share from Windows, the scripts will work.
67 | *.sh text eol=lf
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .sonarqube/
2 | *.swp
3 | *.*~
4 | project.lock.json
5 | .DS_Store
6 | *.pyc
7 |
8 | # Visual Studio Code
9 | .vscode
10 |
11 | # User-specific files
12 | *.suo
13 | *.user
14 | *.userosscache
15 | *.sln.docstates
16 |
17 | # Build results
18 | [Dd]ebug/
19 | [Dd]ebugPublic/
20 | [Rr]elease/
21 | [Rr]eleases/
22 | x64/
23 | x86/
24 | build/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | msbuild.log
29 | msbuild.err
30 | msbuild.wrn
31 |
32 | # Visual Studio 2015
33 | .vs/
34 |
35 | # Rider
36 | .idea/
37 | .fake
38 | .ionide
39 |
--------------------------------------------------------------------------------
/Build.ps1:
--------------------------------------------------------------------------------
1 | # Taken from psake https://github.com/psake/psake
2 |
3 | <#
4 | .SYNOPSIS
5 | This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode
6 | to see if an error occcured. If an error is detected then an exception is thrown.
7 | This function allows you to run command-line programs without having to
8 | explicitly check the $lastexitcode variable.
9 | .EXAMPLE
10 | exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
11 | #>
12 | function Exec
13 | {
14 | [CmdletBinding()]
15 | param(
16 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
17 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd)
18 | )
19 | & $cmd
20 | if ($lastexitcode -ne 0) {
21 | throw ("Exec: " + $errorMessage)
22 | }
23 | }
24 |
25 | if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse }
26 |
27 | $Env:JAVA_HOME="C:\Program Files\Java\jdk17"
28 | $Env:PATH=-join($Env:JAVA_HOME, ";", $Env:PATH)
29 |
30 | $revision = @{ $true = $env:APPVEYOR_BUILD_NUMBER; $false = 1 }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
31 | $revision = "{0:D4}" -f [convert]::ToInt32($revision, 10)
32 |
33 | #
34 | # install Sonar Scanner (from SonarQube)
35 | #
36 | exec { & dotnet tool install --global dotnet-sonarscanner }
37 |
38 | exec { & dotnet restore }
39 |
40 | $sonarProjectKey = "skarpdev_dotnet-version-cli"
41 | $sonarHostUrl = "https://sonarcloud.io"
42 | $openCoveragePaths = "$Env:APPVEYOR_BUILD_FOLDER/test/coverage.*.opencover.xml"
43 | $trxCoveragePahts = "$Env:APPVEYOR_BUILD_FOLDER/test/TestResults/*.trx"
44 |
45 | # initialize Sonar Scanner
46 | # If the environment variable APPVEYOR_PULL_REQUEST_NUMBER is not present, then this is not a pull request
47 | if(-not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
48 | exec {
49 | & dotnet sonarscanner begin `
50 | /k:$sonarProjectKey `
51 | /o:skarp `
52 | /v:$revision `
53 | /d:sonar.host.url=$sonarHostUrl `
54 | /d:sonar.cs.opencover.reportsPaths=$openCoveragePaths `
55 | /d:sonar.cs.vstest.reportsPaths=$trxCoveragePahts `
56 | /d:sonar.coverage.exclusions="**Test*.cs" `
57 | /d:sonar.login="$Env:SONARCLOUD_TOKEN"
58 | }
59 | }
60 | elseif ($env:SONARCLOUD_TOKEN) {
61 | exec {
62 | & dotnet sonarscanner begin `
63 | /k:$sonarProjectKey `
64 | /o:skarp `
65 | /v:$revision `
66 | /d:sonar.host.url=$sonarHostUrl `
67 | /d:sonar.cs.opencover.reportsPaths=$openCoveragePaths `
68 | /d:sonar.cs.vstest.reportsPaths=$trxCoveragePahts `
69 | /d:sonar.coverage.exclusions="**Test*.cs" `
70 | /d:sonar.login="$env:SONARCLOUD_TOKEN" `
71 | /d:sonar.pullrequest.branch=$env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH `
72 | /d:sonar.pullrequest.base=$env:APPVEYOR_REPO_BRANCH `
73 | /d:sonar.pullrequest.key=$env:APPVEYOR_PULL_REQUEST_NUMBER
74 | }
75 | }
76 |
77 | exec { & dotnet build -c Release }
78 |
79 | exec { & dotnet test .\test\dotnet-version-test.csproj -c Release /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --logger trx }
80 |
81 | # trigger Sonar Scanner analysis
82 | if ($env:SONARCLOUD_TOKEN) {
83 | exec { & dotnet sonarscanner end /d:sonar.login="$env:SONARCLOUD_TOKEN" }
84 | }
85 | # pack up everything
86 | exec { & dotnet pack .\src\dotnet-version.csproj -c Release -o ..\artifacts --include-source }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 SKARP ApS
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://ci.appveyor.com/project/nover/dotnet-version-cli/branch/master)
2 | [![nuget version][nuget-image]][nuget-url]
3 | [![Sonar Quality][sonarqualitylogo]][sonarqubelink]
4 | [![Code coverage][sonarcoveragelogo]][sonarqubelink]
5 | [![Sonar vulnerabilities][sonarvulnerabilitieslogo]][sonarqubelink]
6 | [![Sonar bugs][sonarbugslogo]][sonarqubelink]
7 | [![Sonar code smells][sonarcodesmellslogo]][sonarqubelink]
8 |
9 | # dotnet-version-cli
10 |
11 | This repository contains the source code for an [npm/yarn version][1] inspired dotnet global tool for dotnet with full [SemVer 2.0][semver2] compatibility!
12 |
13 | This used to be a dotnet csproj installable `cli tool` - if you are not ready for the move to dotnet global tools, please take a look at the last [0.7.0 release that supports csproj installation](https://github.com/skarpdev/dotnet-version-cli/blob/v0.7.0/README.md).
14 |
15 | Once installed it provides a `dotnet version` command which allows you to easily bump `patch`, `minor` and `major` versions on your project. You can also release and manage pre-release
16 | vesions of your packages by using the `prepatch`, `preminor` and `premajor` commands. Once in pre-release mode you can use the `prerelease` option to update the pre-release number.
17 |
18 | Alternatively it allows you to call it with the specific version it should set in the target `csproj`.
19 |
20 | We do not aim to be 100% feature compatible with `npm version` but provide the bare minimum for working with version numbers on your libraries and applications.
21 |
22 | Effectively this means that issuing a `patch` command will
23 |
24 | - bump the patch version of your project with 1 - so 1.0.0 becomes 1.0.1
25 | - Create a commit with the message `v1.0.1`
26 | - Create a tag with the name `v1.0.1`
27 |
28 | Similarly for `minor` and `major`, but changing different parts of the version number.
29 |
30 | When working with pre-releases using the `prepatch`, `preminor` and `premajor` options additional build meta can be passed using the `--build-meta` switch and the default `next` prefix can be changed using `--prefix`.
31 |
32 | To control the output format the `--output-format` switch can be used - currently supported values are `json` and `text`. **Please beware** that output is only reformatted for success-cases, so if something is wrong you will get a non 0 exit code and text output!
33 | Changing output format works for both "version bumping" and the "show version" operations of the cli.
34 |
35 | The commit and tag can be disabled via the `--skip-vcs` option.
36 |
37 | A completely dry run where nothing will be changed but the new version number is output can be enabled with the `--dry-run` switch. Performing a dry run also implies `skip vcs`.
38 |
39 | If the current directory does not contain the `csproj` file to work on the `-f|--project-file` switch can be provided.
40 |
41 | ## Installing the cli tool
42 |
43 | To install the tool simply issue
44 |
45 | ```bash
46 | dotnet tool install -g dotnet-version-cli
47 | ```
48 |
49 | Now it should be available as
50 |
51 | ```bash
52 | dotnet version
53 | ```
54 |
55 | It can also be executed directly as `dotnet-version` - both should produce output similar to
56 |
57 | ```text
58 | $ dotnet version
59 | dotnet-version-cli
60 | Project version is:
61 | 1.3.0
62 | ```
63 |
64 | Using json output will produce
65 |
66 | ```bash
67 | $ dotnet version --output-format=json
68 | {"product":{"name":"dotnet-version-cli","version":"0.7.0.0"},"currentVersion":"1.3.0","projectFile":"C:\\your\\stuff\\project.csproj"}
69 | ```
70 |
71 | The `product` bit is information about the cli tool itself.
72 |
73 | ## Standard workflow
74 |
75 | You have just merged a PR with a bugfix onto master and you are ready to release a new version of your library / application. The workflow is then
76 |
77 | ```bash
78 | $ git pull
79 | $ dotnet version -f ./src/my.csproj patch
80 | $ git push && git push --tags
81 | ```
82 |
83 | ## Pre-release workflow
84 |
85 | As mentioned in the introduction the version tool allows working with pre-releases.
86 | Let's assume you have a library in version `1.2.4` and have made merges to master. You are not sure these changes work in the wild and therefore you require a
87 | pre-release. In the simpelest form you can
88 |
89 | ```bash
90 | $ dotnet version preminor
91 | ```
92 |
93 | To get a preminor out. This new version tag would become `1.2.5-next.0`.
94 | If additional changes are merged you can roll over the pre-release version number by
95 | ```bash
96 | $ dotnet version prerelease
97 | ```
98 | To make the release `1.2.5-next.1`.
99 | When ready you can snap out of pre-release mode and deploy the final minor version
100 | ```bash
101 | $ dotnet version minor
102 | ```
103 | Resulting in the version `1.2.5`.
104 |
105 | All other command line flags like `-f` apply, and you can also include `build meta` as per SemVer 2.0 spec, like so:
106 | ```bash
107 | dotnet version --build-meta `git rev-parse --short HEAD` preminor # or prerelease etc.
108 | ```
109 | To have a resulting version string like `1.2.5-next.1+abcedf`
110 |
111 | If the default `next` prefix is not desired it can easily be changed using the `--prefix` switch like so:
112 | ```bash
113 | dotnet version --prefix beta preminor # or prerelease etc.
114 | ```
115 |
116 | Resulting in `1.2.4-beta.0`.
117 |
118 | ## Possible CI workflow
119 |
120 | If you do not care that commits and tags are made with the current version of your library, but simply wish to bump the version of your software when building on master, the tool can be used as (powershell example):
121 |
122 | ```powershell
123 | dotnet version "1.0.$env:BUILD_ID"
124 | ```
125 |
126 | replacing `BUILD_ID` with whatever variable your build environment injects.
127 | The total count of commits in your git repo can also be used as a build number:
128 |
129 | ```powershell
130 | $revCount = & git rev-list HEAD --count | Out-String
131 | dotnet version "1.0.$revCount"
132 | ```
133 |
134 | ## Change commit message
135 |
136 | If you want to change defaults commit message, you can use the flag `-m` or `--message`.
137 | ```bash
138 | $ dotnet version minor -m "Commit message"
139 | ```
140 |
141 | There are variables availables to be set in the message
142 |
143 | `$projName` will be replaced for package title (or package id if its not defined)
144 |
145 | `$oldVer` will be replaced for old version of the package
146 |
147 | `$newVer` will be replaced for new version of the package
148 | ```bash
149 | $ dotnet version minor -m "$projName bumped from v$oldVer to v$newVer"
150 | # This will be replaced as
151 | # ProjectName bumped from v1.0.0 to v2.0.0
152 | ```
153 |
154 |
155 | ## Change tag message
156 |
157 | If you want to change defaults tag message, you can use the flag `-t` or `--tag`.
158 | ```bash
159 | $ dotnet version minor -t "Tag"
160 | ```
161 |
162 | There are variables availables to be set in the tag
163 |
164 | `$projName` will be replaced for package title (or package id if its not defined)
165 |
166 | `$oldVer` will be replaced for old version of the package
167 |
168 | `$newVer` will be replaced for new version of the package
169 | ```bash
170 | $ dotnet version minor -t "$projName bumped from v$oldVer to v$newVer"
171 | # This will be replaced as
172 | # ProjectName bumped from v1.0.0 to v2.0.0
173 | ```
174 |
175 | ## Common Version
176 | If you want to share a version across multiple csproj files, you can create a `.targets` file and [import](import) it in the csproj files:
177 | `Common.targets`:
178 | ```xml
179 |
180 |
181 | 2.0.0
182 |
183 |
184 | ```
185 |
186 | And in your `.csproj` files:
187 | ```xml
188 |
189 |
190 |
191 | ```
192 |
193 | You can then use `dotnet version` to change the version in `Common.targets`:
194 | ```powershell
195 | dotnet version -f Common.targets
196 | ```
197 |
198 | [1]: https://docs.npmjs.com/cli/version
199 | [nuget-image]: https://img.shields.io/nuget/v/dotnet-version-cli.svg
200 | [nuget-url]: https://www.nuget.org/packages/dotnet-version-cli
201 | [semver2]: https://semver.org/spec/v2.0.0.html
202 | [sonarqubelink]: https://sonarcloud.io/dashboard?id=skarpdev_dotnet-version-cli
203 | [sonarqualitylogo]: https://sonarcloud.io/api/project_badges/measure?project=skarpdev_dotnet-version-cli&metric=alert_status
204 | [sonarcoveragelogo]: https://sonarcloud.io/api/project_badges/measure?project=skarpdev_dotnet-version-cli&metric=coverage
205 | [sonarvulnerabilitieslogo]: https://sonarcloud.io/api/project_badges/measure?project=skarpdev_dotnet-version-cli&metric=vulnerabilities
206 | [sonarbugslogo]: https://sonarcloud.io/api/project_badges/measure?project=skarpdev_dotnet-version-cli&metric=bugs
207 | [sonarcodesmellslogo]: https://sonarcloud.io/api/project_badges/measure?project=skarpdev_dotnet-version-cli&metric=code_smells
208 | [import]: https://docs.microsoft.com/en-us/visualstudio/msbuild/import-element-msbuild?view=vs-2019
209 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | image: Visual Studio 2022
3 | environment:
4 | SONARCLOUD_TOKEN:
5 | secure: zP3yL8OgEY/gjfooti1esakQ0qHOwPl0GOoGugNS+61PxWOphHIndkOP/G3Glq96
6 | pull_requests:
7 | do_not_increment_build_number: true
8 | branches:
9 | only:
10 | - master
11 | nuget:
12 | disable_publish_on_pr: true
13 | build_script:
14 | - ps: .\Build.ps1
15 | test: off
16 | artifacts:
17 | - path: '**\*.nupkg'
18 | name: NuGet
19 | deploy:
20 | - provider: NuGet
21 | server: https://www.myget.org/F/skarp/api/v2/package
22 | api_key:
23 | secure: QUUDwCiAHecwSEHztB/ANurfoE3BMOpibwyPmr852U3a7VEamjUTfpzv86wVCjLD
24 | skip_symbols: true
25 | on:
26 | branch: master
27 | - provider: NuGet
28 | name: production
29 | api_key:
30 | secure: aYDDu8qdeToBDOLN8U4fL1aAuoO6oHeu33jtSv6Ph7oRzwHjANJUzjHw2T1adXhA
31 | on:
32 | appveyor_repo_tag: true
--------------------------------------------------------------------------------
/dotnet-version-cli.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.6.33723.286
4 | MinimumVisualStudioVersion = 15.0.26124.0
5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-version", "src\dotnet-version.csproj", "{1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}"
6 | EndProject
7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-version-test", "test\dotnet-version-test.csproj", "{FB420ACF-9E12-42B6-B724-1EEE9CBF251E}"
8 | EndProject
9 | Global
10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
11 | Debug|Any CPU = Debug|Any CPU
12 | Debug|x64 = Debug|x64
13 | Debug|x86 = Debug|x86
14 | Release|Any CPU = Release|Any CPU
15 | Release|x64 = Release|x64
16 | Release|x86 = Release|x86
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Debug|x64.ActiveCfg = Debug|Any CPU
22 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Debug|x64.Build.0 = Debug|Any CPU
23 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Debug|x86.ActiveCfg = Debug|Any CPU
24 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Debug|x86.Build.0 = Debug|Any CPU
25 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Release|x64.ActiveCfg = Release|Any CPU
28 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Release|x64.Build.0 = Release|Any CPU
29 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Release|x86.ActiveCfg = Release|Any CPU
30 | {1AE7AFF7-E333-4205-AA1B-B8A8A79B4A87}.Release|x86.Build.0 = Release|Any CPU
31 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Debug|x64.ActiveCfg = Debug|Any CPU
34 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Debug|x64.Build.0 = Debug|Any CPU
35 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Debug|x86.ActiveCfg = Debug|Any CPU
36 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Debug|x86.Build.0 = Debug|Any CPU
37 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Release|x64.ActiveCfg = Release|Any CPU
40 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Release|x64.Build.0 = Release|Any CPU
41 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Release|x86.ActiveCfg = Release|Any CPU
42 | {FB420ACF-9E12-42B6-B724-1EEE9CBF251E}.Release|x86.Build.0 = Release|Any CPU
43 | EndGlobalSection
44 | GlobalSection(SolutionProperties) = preSolution
45 | HideSolutionNode = FALSE
46 | EndGlobalSection
47 | EndGlobal
48 |
--------------------------------------------------------------------------------
/src/CsProj/FileSystem/DotNetFileSystemProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 |
4 | namespace Skarp.Version.Cli.CsProj.FileSystem
5 | {
6 | public class DotNetFileSystemProvider : IFileSystemProvider
7 | {
8 | ///
9 | /// List the files of the given path
10 | ///
11 | ///
12 | ///
13 | public IEnumerable List(string path)
14 | {
15 | return Directory.EnumerateFiles(path);
16 | }
17 |
18 | ///
19 | /// Determines whether the given path is actually a csproj or targets file
20 | ///
21 | ///
22 | ///
23 | public bool IsCsProjectFile(string path)
24 | {
25 | return File.Exists(path) && (path.EndsWith(".csproj") || path.EndsWith(".targets"));
26 | }
27 |
28 | ///
29 | /// Gets the current working directory of the running application
30 | ///
31 | ///
32 | public string Cwd()
33 | {
34 | return Directory.GetCurrentDirectory();
35 | }
36 |
37 | ///
38 | /// Load content from the given file
39 | ///
40 | ///
41 | ///
42 | public string LoadContent(string filePath)
43 | {
44 | return File.ReadAllText(filePath);
45 | }
46 |
47 | ///
48 | /// Write all text content to the given filepath
49 | ///
50 | ///
51 | ///
52 | public void WriteAllContent(string filePath, string data)
53 | {
54 | File.WriteAllText(filePath, data);
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/src/CsProj/FileSystem/IFileSystemProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Skarp.Version.Cli.CsProj.FileSystem
4 | {
5 | public interface IFileSystemProvider
6 | {
7 | ///
8 | /// List the items in the given path
9 | ///
10 | ///
11 | ///
12 | IEnumerable List(string path);
13 |
14 | ///
15 | /// Determines whether the given path is actually a csproj file or a path
16 | ///
17 | ///
18 | ///
19 | bool IsCsProjectFile(string path);
20 |
21 | ///
22 | /// Get the current working directory
23 | ///
24 | ///
25 | string Cwd();
26 |
27 | ///
28 | /// Loads all the content from the given file path as a string
29 | ///
30 | ///
31 | string LoadContent(string filePath);
32 |
33 | ///
34 | /// Writes all the content to the given file as a strings
35 | ///
36 | ///
37 | ///
38 | ///
39 | void WriteAllContent(string filePath, string data);
40 | }
41 | }
--------------------------------------------------------------------------------
/src/CsProj/ProjectFileDetector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Skarp.Version.Cli.CsProj.FileSystem;
6 |
7 | namespace Skarp.Version.Cli.CsProj
8 | {
9 | public class ProjectFileDetector
10 | {
11 | private readonly IFileSystemProvider _fileSystem;
12 | public ProjectFileDetector(
13 | IFileSystemProvider fileSystem)
14 | {
15 | _fileSystem = fileSystem;
16 | }
17 |
18 | ///
19 | /// Method tries to find the nearest cs project file and loads up the
20 | /// xml and returns it
21 | ///
22 | ///
23 | /// If the given bootstrap path is empty, it will try to detect the nearest (current dir)
24 | /// cs project file. If bootstrapPath is given, and is a csproj file it will load this
25 | /// if bootstrap path is a folder, it will try to search for a csproj file there.
26 | ///
27 | ///
28 | ///
29 | public virtual string FindAndLoadCsProj(string bootstrapPath)
30 | {
31 | var path = bootstrapPath;
32 | string csProjFile;
33 |
34 | if (string.IsNullOrEmpty(bootstrapPath))
35 | {
36 | path = _fileSystem.Cwd();
37 | }
38 | if (_fileSystem.IsCsProjectFile(path))
39 | {
40 | csProjFile = path;
41 | }
42 | else
43 | {
44 | var files = _fileSystem.List(path);
45 | var csProjFiles = files.Where(q => q.EndsWith(".csproj"));
46 | var projFiles = csProjFiles as IList ?? csProjFiles.ToList();
47 | if (projFiles.Count == 0)
48 | {
49 | throw new OperationCanceledException("No csproj file could be found in path - ensure that you are running `dotnet version` next to the project file, or use -f to specify a target csproj file");
50 | }
51 |
52 | if (projFiles.Count > 1)
53 | {
54 | var sb = new StringBuilder();
55 | sb.AppendLine("Multiple csproj files found - aborting:");
56 | foreach (var project in projFiles)
57 | {
58 | sb.AppendLine($"\t{project}");
59 | }
60 |
61 | throw new OperationCanceledException(sb.ToString());
62 | }
63 |
64 | csProjFile = projFiles.Single();
65 | }
66 | ResolvedCsProjFile = csProjFile;
67 |
68 | var xml = _fileSystem.LoadContent(csProjFile);
69 | return xml;
70 | }
71 |
72 | public virtual string ResolvedCsProjFile { get; private set; }
73 | }
74 | }
--------------------------------------------------------------------------------
/src/CsProj/ProjectFileParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Xml.Linq;
5 |
6 | namespace Skarp.Version.Cli.CsProj
7 | {
8 | public class ProjectFileParser
9 | {
10 | public virtual string PackageName { get; private set; }
11 |
12 | public virtual string PackageVersion { get; private set; }
13 |
14 | public virtual string Version { get; private set; }
15 |
16 | public virtual string VersionPrefix { get; private set; }
17 |
18 | public virtual string VersionSuffix { get; private set; }
19 |
20 | public virtual ProjectFileProperty DesiredVersionSource { get; private set; }
21 |
22 | public ProjectFileProperty VersionSource
23 | {
24 | get
25 | {
26 | if (DesiredVersionSource == ProjectFileProperty.Version)
27 | {
28 | return string.IsNullOrWhiteSpace(Version)
29 | ? ProjectFileProperty.VersionPrefix
30 | : ProjectFileProperty.Version;
31 | }
32 |
33 | return ProjectFileProperty.PackageVersion;
34 | }
35 | }
36 |
37 | private IEnumerable _propertyGroup { get; set; }
38 |
39 | protected virtual void Load(string xmlDocument, ProjectFileProperty property)
40 | {
41 | LoadPropertyGroup(xmlDocument);
42 |
43 | XElement propertyElement = LoadProperty(property);
44 |
45 | switch (property)
46 | {
47 | case ProjectFileProperty.Version:
48 | Version = propertyElement?.Value ?? string.Empty;
49 | break;
50 | case ProjectFileProperty.PackageVersion:
51 | PackageVersion = propertyElement?.Value ?? string.Empty;
52 | break;
53 | case ProjectFileProperty.Title:
54 | var defaultPropertyElement = LoadProperty(ProjectFileProperty.PackageId);
55 | PackageName = propertyElement?.Value ?? defaultPropertyElement?.Value ?? string.Empty;
56 | break;
57 | case ProjectFileProperty.VersionPrefix:
58 | VersionPrefix = propertyElement?.Value ?? string.Empty;
59 | break;
60 | case ProjectFileProperty.VersionSuffix:
61 | VersionSuffix = propertyElement?.Value ?? string.Empty;
62 | break;
63 | }
64 | }
65 |
66 |
67 | public virtual void Load(string xmlDocument, ProjectFileProperty versionSource, params ProjectFileProperty[] properties)
68 | {
69 | DesiredVersionSource = versionSource;
70 |
71 | if (properties == null || !properties.Any())
72 | {
73 | properties = new[]
74 | {
75 | ProjectFileProperty.Title,
76 | ProjectFileProperty.Version,
77 | ProjectFileProperty.PackageId,
78 | ProjectFileProperty.PackageVersion,
79 | ProjectFileProperty.VersionPrefix,
80 | ProjectFileProperty.VersionSuffix
81 | };
82 | }
83 |
84 | // Try to load xmlDocument even if there is no properties to be loaded
85 | // in order to verify if project file is well formed
86 | LoadPropertyGroup(xmlDocument);
87 |
88 | foreach (var property in properties)
89 | {
90 | Load(xmlDocument, property);
91 | }
92 | }
93 |
94 | public string GetHumanReadableVersionFromSource()
95 | {
96 | return VersionSource switch
97 | {
98 | ProjectFileProperty.Version => Version,
99 | ProjectFileProperty.VersionPrefix => $"{VersionPrefix}-{VersionSuffix}",
100 | ProjectFileProperty.PackageVersion => PackageVersion,
101 | _ => throw new ArgumentOutOfRangeException($"Unknown version source {VersionSource}")
102 | };
103 | }
104 |
105 | private XElement LoadProperty(ProjectFileProperty property)
106 | {
107 | XElement propertyElement = (
108 | from prop in _propertyGroup.Elements()
109 | where prop.Name == property.ToString("g")
110 | select prop
111 | ).FirstOrDefault();
112 | return propertyElement;
113 | }
114 |
115 | private void LoadPropertyGroup(string xmlDocument)
116 | {
117 | // Check if it has been already loaded
118 | if (_propertyGroup != null) return;
119 |
120 | var xml = XDocument.Parse(xmlDocument, LoadOptions.PreserveWhitespace);
121 |
122 | // Project should be root of the document
123 | var project = xml.Elements("Project");
124 | var xProject = project as IList ?? project.ToList();
125 | if (!xProject.Any())
126 | {
127 | throw new ArgumentException(
128 | "The provided csproj file seems malformed - no in the root",
129 | paramName: nameof(xmlDocument)
130 | );
131 | }
132 |
133 | _propertyGroup = xProject.Elements("PropertyGroup");
134 | }
135 | }
136 | }
--------------------------------------------------------------------------------
/src/CsProj/ProjectFileProperty.cs:
--------------------------------------------------------------------------------
1 | namespace Skarp.Version.Cli.CsProj
2 | {
3 | public enum ProjectFileProperty
4 | {
5 | Version,
6 | PackageVersion,
7 | PackageId,
8 | Title,
9 | VersionPrefix,
10 | VersionSuffix
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/CsProj/ProjectFileVersionPatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Xml.Linq;
4 | using Skarp.Version.Cli.CsProj.FileSystem;
5 |
6 | namespace Skarp.Version.Cli.CsProj
7 | {
8 | public class ProjectFileVersionPatcher
9 | {
10 | private readonly IFileSystemProvider _fileSystem;
11 | private XDocument _doc;
12 |
13 | public ProjectFileVersionPatcher(IFileSystemProvider fileSystem)
14 | {
15 | _fileSystem = fileSystem;
16 | }
17 |
18 | public virtual void Load(string xmlDocument)
19 | {
20 | _doc = XDocument.Parse(xmlDocument, LoadOptions.PreserveWhitespace);
21 | }
22 |
23 | ///
24 | /// Replace the existing version number in the csproj xml with the new version
25 | ///
26 | /// The new version number to persist in the csproj file
27 | ///
28 | public virtual void PatchField(string newValue, ProjectFileProperty versionField)
29 | {
30 | PatchGenericField(versionField.ToString(), newValue);
31 | }
32 |
33 | ///
34 | /// Helper method for patching up a generic XML field in the loaded XML
35 | ///
36 | /// The name to find and update or add it to the tree
37 | /// New value
38 | ///
39 | private void PatchGenericField(string elementName, string newVal)
40 | {
41 | if (_doc == null)
42 | {
43 | throw new InvalidOperationException("Please call Load(string xml) before invoking patch operations");
44 | }
45 |
46 | // If the element is not present, add it to the XML document (csproj file
47 | if (!ContainsElement(elementName))
48 | {
49 | AddMissingElementToCsProj(elementName, newVal);
50 | }
51 |
52 | var elm = _doc.Descendants(elementName).First();
53 | elm.Value = newVal;
54 | }
55 |
56 | private bool ContainsElement(string elementName)
57 | {
58 | var nodes = _doc.Descendants(elementName);
59 | return nodes.Any();
60 | }
61 |
62 | private void AddMissingElementToCsProj(string elementName, string value)
63 | {
64 | // try to locate the PropertyGroup where the element belongs
65 | var node = _doc.Descendants("TargetFramework").FirstOrDefault();
66 | if (node == null)
67 | {
68 | node = _doc.Descendants("TargetFrameworks").FirstOrDefault();
69 |
70 | if (node == null)
71 | {
72 | throw new ArgumentException(
73 | $"Given XML does not contain {elementName} and cannot locate existing PropertyGroup to add it to - is this a valid csproj file?");
74 | }
75 | }
76 |
77 | var propertyGroup = node.Parent;
78 | propertyGroup.Add(new XElement(elementName, value));
79 | }
80 |
81 | ///
82 | /// Save the csproj changes to disk
83 | ///
84 | /// The csproj xml content
85 | /// The path of the csproj to write to
86 | public virtual void Flush(string filePath)
87 | {
88 | _fileSystem.WriteAllContent(filePath, ToXmlString());
89 | }
90 |
91 | ///
92 | /// Get the underlying csproj XML back from the patcher as a string
93 | ///
94 | ///
95 | public virtual string ToXmlString()
96 | {
97 | return _doc.ToString();
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/src/Model/ProductOutputInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Skarp.Version.Cli.Model
2 | {
3 | public class ProductOutputInfo
4 | {
5 | public string Name { get; set; }
6 | public string Version { get; set; }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/Model/VersionInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Skarp.Version.Cli.Model
2 | {
3 | public class VersionInfo
4 | {
5 | public ProductOutputInfo Product { get; set; }
6 | public string OldVersion { get; set; }
7 | public string NewVersion { get; set; }
8 | public string ProjectFile { get; set; }
9 | public string VersionStrategy { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/src/OutputFormat.cs:
--------------------------------------------------------------------------------
1 | namespace Skarp.Version.Cli
2 | {
3 | public enum OutputFormat
4 | {
5 | ///
6 | /// Regular text output
7 | ///
8 | Text,
9 |
10 | ///
11 | /// json output
12 | ///
13 | Json,
14 |
15 | ///
16 | /// Bare output without extraneous information
17 | ///
18 | Bare
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/ProductInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace Skarp.Version.Cli
4 | {
5 | public static class ProductInfo
6 | {
7 | ///
8 | /// The name of the product
9 | ///
10 | public const string Name = "dotnet-version-cli";
11 |
12 | ///
13 | /// The version of the running product
14 | ///
15 | public static readonly string Version = Assembly.GetEntryAssembly().GetName().Version.ToString();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Microsoft.Extensions.CommandLineUtils;
5 | using Skarp.Version.Cli.CsProj;
6 | using Skarp.Version.Cli.CsProj.FileSystem;
7 | using Skarp.Version.Cli.Vcs;
8 | using Skarp.Version.Cli.Vcs.Git;
9 | using Skarp.Version.Cli.Versioning;
10 |
11 | namespace Skarp.Version.Cli
12 | {
13 | static class Program
14 | {
15 | private static VersionCli _cli;
16 |
17 | static int Main(string[] args)
18 | {
19 | SetUpDependencies();
20 |
21 | var commandLineApplication = new CommandLineApplication(throwOnUnexpectedArg: false)
22 | {
23 | Name = "dotnet version",
24 | ExtendedHelpText =
25 | $"{Environment.NewLine}Available commands after [options] to control the version bump are: {Environment.NewLine}\tmajor | minor | patch | premajor | preminor | prepatch | prerelease | "
26 | };
27 |
28 | commandLineApplication.HelpOption("-? | -h | --help");
29 | var outputFormatOption = commandLineApplication.Option(
30 | "-o | --output-format ",
31 | "Change output format, allowed values: json, text - default value is text",
32 | CommandOptionType.SingleValue);
33 |
34 | var skipVcsOption = commandLineApplication.Option(
35 | "-s | --skip-vcs", "Disable version control system changes - default is to tag and commit new version",
36 | CommandOptionType.NoValue);
37 |
38 | var doDryRun = commandLineApplication.Option(
39 | "-d | --dry-run",
40 | "Disable all changes to disk and vcs. Use to see what the changes would have been but without changing the csproj file nor committing or tagging.",
41 | CommandOptionType.NoValue);
42 |
43 | var csProjectFileOption = commandLineApplication.Option(
44 | "-f | --project-file ",
45 | "The project file to work on. Defaults to auto-locating in current directory",
46 | CommandOptionType.SingleValue);
47 |
48 | var buildMetaOption = commandLineApplication.Option(
49 | "-b | --build-meta ",
50 | "Additional build metadata to add to a `premajor`, `preminor` or `prepatch` version bump",
51 | CommandOptionType.SingleValue);
52 |
53 | var prefixOption = commandLineApplication.Option(
54 | "-p | --prefix ",
55 | "Override the default next prefix/label for a `premajor`, `preminor` or `prepatch` version bump",
56 | CommandOptionType.SingleValue);
57 |
58 | var commitMessage = commandLineApplication.Option(
59 | "-m | --message ",
60 | "Set commit's message - default is 'v'. Available variables: $projName, $oldVer, $newVer",
61 | CommandOptionType.SingleValue);
62 |
63 | var vcsTag = commandLineApplication.Option(
64 | "-t | --tag ",
65 | "Set tag's name - default is 'v'. Available variables: $projName, $oldVer, $newVer",
66 | CommandOptionType.SingleValue);
67 |
68 | var projectFilePropertyName = commandLineApplication.Option(
69 | "-v | --version-property-name ",
70 | "Specify which tag from to use as the version tag. Default is Version. Available values: Version, PackageVersion.",
71 | CommandOptionType.SingleValue);
72 |
73 | commandLineApplication.OnExecute(() =>
74 | {
75 | try
76 | {
77 | var outputFormat = OutputFormat.Text;
78 | if (outputFormatOption.HasValue())
79 | {
80 | outputFormat =
81 | (OutputFormat) Enum.Parse(typeof(OutputFormat), outputFormatOption.Value(), true);
82 | }
83 |
84 | if (outputFormat == OutputFormat.Text)
85 | {
86 | Console.WriteLine($"{ProductInfo.Name} version {ProductInfo.Version}");
87 | }
88 |
89 | var doVcs = !skipVcsOption.HasValue();
90 | var dryRunEnabled = doDryRun.HasValue();
91 |
92 | if (commandLineApplication.RemainingArguments.Count == 0)
93 | {
94 | _cli.DumpVersion(new VersionCliArgs
95 | {
96 | OutputFormat = outputFormat,
97 | CsProjFilePath = csProjectFileOption.Value(),
98 | ProjectFilePropertyName = Enum.Parse(projectFilePropertyName.Value() ?? "Version", ignoreCase: true),
99 | });
100 |
101 | return 0;
102 | }
103 |
104 | var cliArgs = GetVersionBumpFromRemainingArgs(
105 | commandLineApplication.RemainingArguments,
106 | outputFormat,
107 | doVcs,
108 | dryRunEnabled,
109 | csProjectFileOption.Value(),
110 | buildMetaOption.Value(),
111 | prefixOption.Value(),
112 | commitMessage.Value(),
113 | vcsTag.Value(),
114 | projectFilePropertyName.Value()
115 | );
116 | _cli.Execute(cliArgs);
117 |
118 | return 0;
119 | }
120 | catch (ArgumentException ex)
121 | {
122 | Console.Error.WriteLine($"ERR {ex.Message}");
123 |
124 | commandLineApplication.ShowHelp();
125 | return 1;
126 | }
127 |
128 | catch (OperationCanceledException oce)
129 | {
130 | Console.Error.WriteLine($"ERR {oce.Message}");
131 |
132 | commandLineApplication.ShowHelp();
133 | return 1;
134 | }
135 | catch (Exception e)
136 | {
137 | Console.Error.WriteLine("ERR Something went completely haywire, developer zen:");
138 | Console.Error.WriteLine($"\t{e.Message} STACK: {Environment.NewLine}{e.StackTrace}");
139 | return 1;
140 | }
141 | });
142 | return commandLineApplication.Execute(args);
143 | }
144 |
145 | internal static VersionCliArgs GetVersionBumpFromRemainingArgs(
146 | List remainingArguments,
147 | OutputFormat outputFormat,
148 | bool doVcs,
149 | bool dryRunEnabled,
150 | string userSpecifiedCsProjFilePath,
151 | string userSpecifiedBuildMeta,
152 | string preReleasePrefix,
153 | string commitMessage,
154 | string vcsTag,
155 | string projectFilePropertyName
156 | )
157 | {
158 | if (remainingArguments == null || !remainingArguments.Any())
159 | {
160 | var msgEx =
161 | "No version bump specified, please specify one of:\n\tmajor | minor | patch | premajor | preminor | prepatch | prerelease | ";
162 | // ReSharper disable once NotResolvedInText
163 | throw new ArgumentException(msgEx);
164 | }
165 |
166 | var args = new VersionCliArgs
167 | {
168 | OutputFormat = outputFormat,
169 | DoVcs = doVcs,
170 | DryRun = dryRunEnabled,
171 | BuildMeta = userSpecifiedBuildMeta,
172 | PreReleasePrefix = preReleasePrefix,
173 | CommitMessage = commitMessage,
174 | VersionControlTag = vcsTag
175 | };
176 |
177 | var bump = VersionBump.Patch;
178 |
179 | foreach (var arg in remainingArguments)
180 | {
181 | if (Enum.TryParse(arg, true, out bump)) break;
182 |
183 | var ver = SemVer.FromString(arg);
184 | args.SpecificVersionToApply = ver.ToSemVerVersionString(null);
185 | bump = VersionBump.Specific;
186 | }
187 |
188 | args.VersionBump = bump;
189 | args.CsProjFilePath = userSpecifiedCsProjFilePath;
190 |
191 | if (!string.IsNullOrEmpty(projectFilePropertyName))
192 | {
193 | args.ProjectFilePropertyName = Enum.Parse(projectFilePropertyName, ignoreCase: true);
194 | }
195 |
196 | return args;
197 | }
198 |
199 | private static void SetUpDependencies()
200 | {
201 | var dotNetFileSystemProvider = new DotNetFileSystemProvider();
202 | _cli = new VersionCli(
203 | new GitVcs(),
204 | new ProjectFileDetector(
205 | dotNetFileSystemProvider
206 | ),
207 | new ProjectFileParser(),
208 | new VcsParser(),
209 | new ProjectFileVersionPatcher(dotNetFileSystemProvider),
210 | new SemVerBumper()
211 | );
212 | }
213 | }
214 | }
--------------------------------------------------------------------------------
/src/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("dotnet-version-test")]
--------------------------------------------------------------------------------
/src/Vcs/Git/GitVcs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace Skarp.Version.Cli.Vcs.Git
5 | {
6 | public class GitVcs : IVcs
7 | {
8 | ///
9 | /// Creates a new commit with the given message
10 | ///
11 | /// Path to the cs project file that was version updated
12 | /// The message to include in the commit
13 | public void Commit(string csProjFilePath, string message)
14 | {
15 | if(!LaunchGitWithArgs($"add \"{csProjFilePath}\""))
16 | {
17 | throw new OperationCanceledException($"Unable to add cs proj file {csProjFilePath} to git index");
18 | }
19 |
20 | if(!LaunchGitWithArgs($"commit -m \"{message}\""))
21 | {
22 | throw new OperationCanceledException("Unable to commit");
23 | }
24 | }
25 |
26 | ///
27 | /// Determines whether the current repository is clean.
28 | ///
29 | ///
30 | public bool IsRepositoryClean()
31 | {
32 | return LaunchGitWithArgs("diff-index --quiet HEAD --");
33 | }
34 |
35 | ///
36 | /// Determines whether git is present in PATH on the current computer
37 | ///
38 | ///
39 | public bool IsVcsToolPresent()
40 | {
41 | // launching `git --help` returns exit code 0 where as `git` returns 1 as git wants a cmd line argument
42 | return LaunchGitWithArgs("--help");
43 | }
44 |
45 | ///
46 | /// Creates a new tag
47 | ///
48 | /// Name of the tag
49 | public void Tag(string tagName)
50 | {
51 | if(!LaunchGitWithArgs($"tag -a {tagName} -m {tagName}"))
52 | {
53 | throw new OperationCanceledException("Unable to create tag");
54 | }
55 | }
56 |
57 | private static bool LaunchGitWithArgs(string args, int waitForExitTimeMs = 1000, int exitCode = 0)
58 | {
59 | try
60 | {
61 | var startInfo = CreateGitShellStartInfo(args);
62 | var proc = Process.Start(startInfo);
63 | proc.WaitForExit(waitForExitTimeMs);
64 |
65 | return proc.ExitCode == exitCode;
66 | }
67 | catch (Exception ex)
68 | {
69 | Console.Error.WriteLine(ex.Message);
70 | return false;
71 | }
72 | }
73 |
74 | private static ProcessStartInfo CreateGitShellStartInfo(string args)
75 | {
76 | return new ProcessStartInfo("git")
77 | {
78 | Arguments = args,
79 | RedirectStandardError = true,
80 | RedirectStandardInput = true,
81 | RedirectStandardOutput = true,
82 | };
83 | }
84 |
85 | public string ToolName()
86 | {
87 | return "git";
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/src/Vcs/IVcs.cs:
--------------------------------------------------------------------------------
1 | namespace Skarp.Version.Cli.Vcs
2 | {
3 | ///
4 | /// Version Control System abstraction interface
5 | ///
6 | public interface IVcs
7 | {
8 | ///
9 | /// When implemented by a concrete class it returns the name of the VCS tool
10 | ///
11 | ///
12 | string ToolName();
13 |
14 | ///
15 | /// When implemented by a concrete class it determines whether the necessary tools
16 | /// are available in the current CLI contenxt - i.e check that `git` command can be found
17 | /// and executed
18 | ///
19 | /// true if the tool exists, false otherwise
20 | bool IsVcsToolPresent();
21 |
22 | ///
23 | /// When implemented by a concrete class it returns true if the
24 | /// current HEAD of the local repository is clean - i.e no pending changes
25 | ///
26 | ///
27 | bool IsRepositoryClean();
28 |
29 | ///
30 | /// When implemented by a concrete class it allows to create a commit with the
31 | /// changed version in the project file
32 | ///
33 | /// Path to the cs project file
34 | /// The message to create the commit message with
35 | void Commit(string csProjFilePath, string message);
36 |
37 | ///
38 | /// When implemented by a concrete class it will tag the latest commit with the
39 | /// given tag name
40 | ///
41 | /// The name of the tag to create - i.e v1.0.2
42 | void Tag(string tagName);
43 | }
44 | }
--------------------------------------------------------------------------------
/src/Vcs/VcsParser.cs:
--------------------------------------------------------------------------------
1 | using Skarp.Version.Cli.CsProj;
2 | using Skarp.Version.Cli.Model;
3 |
4 | namespace Skarp.Version.Cli.Vcs
5 | {
6 | public class VcsParser
7 | {
8 | public string Commit(VersionInfo verInfo, ProjectFileParser fileParser, string argMessage)
9 | {
10 | if (string.IsNullOrEmpty(argMessage)) return $"v{verInfo.NewVersion}";
11 |
12 | return ReplaceVariables(verInfo, fileParser, argMessage);
13 | }
14 |
15 | public string Tag(VersionInfo verInfo, ProjectFileParser fileParser, string argTag)
16 | {
17 | if (string.IsNullOrEmpty(argTag)) return $"v{verInfo.NewVersion}";
18 |
19 | return ReplaceVariables(verInfo, fileParser, argTag);
20 | }
21 |
22 | private string ReplaceVariables(VersionInfo verInfo, ProjectFileParser fileParser, string dest)
23 | {
24 | return dest
25 | .Replace("$projName", fileParser.PackageName)
26 | .Replace("$oldVer", verInfo.OldVersion)
27 | .Replace("$newVer", verInfo.NewVersion);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/VersionBump.cs:
--------------------------------------------------------------------------------
1 | namespace Skarp.Version.Cli
2 | {
3 | ///
4 | /// Enumerates the possible version bumps
5 | ///
6 | public enum VersionBump
7 | {
8 | // Not supplied or parsing error or something - we can't bump `unknown`
9 | Unknown,
10 |
11 | Major,
12 |
13 | Minor,
14 |
15 | Patch,
16 |
17 | PreMajor,
18 |
19 | PreMinor,
20 |
21 | PrePatch,
22 |
23 | ///
24 | /// Increment the PreRelease indetifier (if it is numeric and rolled by this tool)
25 | ///
26 | PreRelease,
27 |
28 | ///
29 | /// Apply a specific, given, version to the project file
30 | ///
31 | Specific,
32 |
33 | ///
34 | /// Do not apply any changes.
35 | ///
36 | None
37 | }
38 | }
--------------------------------------------------------------------------------
/src/VersionCli.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Serialization;
4 | using Skarp.Version.Cli.CsProj;
5 | using Skarp.Version.Cli.Model;
6 | using Skarp.Version.Cli.Vcs;
7 | using Skarp.Version.Cli.Versioning;
8 |
9 | namespace Skarp.Version.Cli
10 | {
11 | public class VersionCli
12 | {
13 | private readonly IVcs _vcsTool;
14 | private readonly ProjectFileDetector _fileDetector;
15 | private readonly ProjectFileParser _fileParser;
16 | private readonly VcsParser _vcsParser;
17 | private readonly ProjectFileVersionPatcher _fileVersionPatcher;
18 | private readonly SemVerBumper _bumper;
19 |
20 | public VersionCli(
21 | IVcs vcsClient,
22 | ProjectFileDetector fileDetector,
23 | ProjectFileParser fileParser,
24 | VcsParser vcsParser,
25 | ProjectFileVersionPatcher fileVersionPatcher,
26 | SemVerBumper bumper
27 | )
28 | {
29 | _vcsTool = vcsClient;
30 | _fileDetector = fileDetector;
31 | _fileParser = fileParser;
32 | _vcsParser = vcsParser;
33 | _fileVersionPatcher = fileVersionPatcher;
34 | _bumper = bumper;
35 | }
36 |
37 | public VersionInfo Execute(VersionCliArgs args)
38 | {
39 | if (!args.DryRun && args.DoVcs && !_vcsTool.IsVcsToolPresent())
40 | {
41 | throw new OperationCanceledException(
42 | $"Unable to find the vcs tool {_vcsTool.ToolName()} in your path");
43 | }
44 |
45 | if (!args.DryRun && args.DoVcs && !_vcsTool.IsRepositoryClean())
46 | {
47 | throw new OperationCanceledException(
48 | "You currently have uncomitted changes in your repository, please commit these and try again");
49 | }
50 |
51 | var csProjXml = _fileDetector.FindAndLoadCsProj(args.CsProjFilePath);
52 | _fileParser.Load(
53 | csProjXml,
54 | args.ProjectFilePropertyName,
55 | ProjectFileProperty.Version, ProjectFileProperty.PackageVersion, ProjectFileProperty.VersionSuffix,
56 | ProjectFileProperty.VersionPrefix
57 | );
58 |
59 | var currentSemVer = GetCurrentSemVerFromSource();
60 |
61 | var bumpedSemVer = _bumper.Bump(
62 | currentSemVer,
63 | args.VersionBump,
64 | args.SpecificVersionToApply,
65 | args.BuildMeta,
66 | args.PreReleasePrefix
67 | );
68 |
69 | var theOutput = new VersionInfo
70 | {
71 | Product = new ProductOutputInfo
72 | {
73 | Name = ProductInfo.Name,
74 | Version = ProductInfo.Version
75 | },
76 | OldVersion = currentSemVer.ToSemVerVersionString(_fileParser),
77 | NewVersion = bumpedSemVer.ToSemVerVersionString(_fileParser),
78 | ProjectFile = _fileDetector.ResolvedCsProjFile,
79 | VersionStrategy = args.VersionBump.ToString().ToLowerInvariant()
80 | };
81 |
82 | if (!args.DryRun) // if we are not in dry run mode, then we should go ahead
83 | {
84 | _fileVersionPatcher.Load(csProjXml);
85 |
86 | _fileVersionPatcher.PatchField(
87 | bumpedSemVer.ToSemVerVersionString(_fileParser),
88 | _fileParser.VersionSource
89 | );
90 |
91 | _fileVersionPatcher.Flush(
92 | _fileDetector.ResolvedCsProjFile
93 | );
94 |
95 | if (args.DoVcs)
96 | {
97 | _fileParser.Load(csProjXml, ProjectFileProperty.Title);
98 | // Run git commands
99 | _vcsTool.Commit(_fileDetector.ResolvedCsProjFile,
100 | _vcsParser.Commit(theOutput, _fileParser, args.CommitMessage));
101 | _vcsTool.Tag(_vcsParser.Tag(theOutput, _fileParser, args.VersionControlTag));
102 | }
103 | }
104 |
105 | if (args.OutputFormat == OutputFormat.Json)
106 | {
107 | WriteJsonToStdout(theOutput);
108 | }
109 | else if (args.OutputFormat == OutputFormat.Bare)
110 | {
111 | Console.WriteLine(bumpedSemVer.ToSemVerVersionString(_fileParser));
112 | }
113 | else
114 | {
115 | Console.WriteLine(
116 | $"Bumped {_fileDetector.ResolvedCsProjFile} to version {bumpedSemVer.ToSemVerVersionString(_fileParser)}");
117 | }
118 |
119 | return theOutput;
120 | }
121 |
122 | private SemVer GetCurrentSemVerFromSource()
123 | {
124 | return _fileParser.VersionSource switch
125 | {
126 | ProjectFileProperty.Version => SemVer.FromString(string.IsNullOrWhiteSpace(_fileParser.Version) ? "0.0.0" : _fileParser.Version),
127 | ProjectFileProperty.PackageVersion => SemVer.FromString(string.IsNullOrWhiteSpace(_fileParser.PackageVersion) ? "0.0.0" : _fileParser.PackageVersion),
128 | _ => SemVer.FromString(string.IsNullOrWhiteSpace(_fileParser.VersionPrefix) ? "0.0.0" : _fileParser.VersionPrefix)
129 | };
130 | }
131 |
132 | public void DumpVersion(VersionCliArgs args)
133 | {
134 | var csProjXml = _fileDetector.FindAndLoadCsProj(args.CsProjFilePath);
135 | _fileParser.Load(csProjXml, args.ProjectFilePropertyName);
136 |
137 | switch (args.OutputFormat)
138 | {
139 | case OutputFormat.Json:
140 | var theOutput = new
141 | {
142 | Product = new
143 | {
144 | Name = ProductInfo.Name,
145 | Version = ProductInfo.Version
146 | },
147 | CurrentVersion = _fileParser.GetHumanReadableVersionFromSource(),
148 | ProjectFile = _fileDetector.ResolvedCsProjFile,
149 | };
150 | WriteJsonToStdout(theOutput);
151 | break;
152 | case OutputFormat.Bare:
153 | Console.WriteLine(_fileParser.GetHumanReadableVersionFromSource());
154 | break;
155 | case OutputFormat.Text:
156 | default:
157 | Console.WriteLine("Project version is: {0}\t{1}", Environment.NewLine,
158 | _fileParser.GetHumanReadableVersionFromSource());
159 | break;
160 | }
161 | }
162 |
163 | private static void WriteJsonToStdout(object theOutput)
164 | {
165 | Console.WriteLine(
166 | JsonConvert.SerializeObject(
167 | theOutput, new JsonSerializerSettings
168 | {
169 | ContractResolver = new CamelCasePropertyNamesContractResolver()
170 | }));
171 | }
172 | }
173 | }
--------------------------------------------------------------------------------
/src/VersionCliArgs.cs:
--------------------------------------------------------------------------------
1 | using Skarp.Version.Cli.CsProj;
2 |
3 | namespace Skarp.Version.Cli
4 | {
5 | public class VersionCliArgs
6 | {
7 | public VersionBump VersionBump { get; set; }
8 |
9 | public string SpecificVersionToApply { get; set; }
10 |
11 | public string CsProjFilePath { get; set; }
12 |
13 | public OutputFormat OutputFormat { get; set; }
14 |
15 | ///
16 | /// Whether or not to do version control changes like
17 | /// commit and tag.
18 | ///
19 | public bool DoVcs { get; set; }
20 |
21 | ///
22 | /// Whether dry run is enabled and thus all mutations should be disabled
23 | ///
24 | public bool DryRun { get; set; }
25 |
26 | ///
27 | /// Build meta for a pre-release tag passed via CLI arguments
28 | ///
29 | public string BuildMeta { get; set; }
30 |
31 | ///
32 | /// Override for the default `next` pre-release prefix/label
33 | ///
34 | public string PreReleasePrefix { get; set; }
35 |
36 | ///
37 | /// Set commit's message
38 | ///
39 | public string CommitMessage { get; set; }
40 |
41 | ///
42 | /// Override for the default `v` vcs tag
43 | ///
44 | public string VersionControlTag { get; set; }
45 |
46 | ///
47 | /// Specify the Version-Tag that should be targeted. Default is Version.
48 | ///
49 | public ProjectFileProperty ProjectFilePropertyName { get; set; } = ProjectFileProperty.Version;
50 | }
51 | }
--------------------------------------------------------------------------------
/src/Versioning/SemVer.cs:
--------------------------------------------------------------------------------
1 | using Skarp.Version.Cli.CsProj;
2 | using System;
3 | using System.Text;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace Skarp.Version.Cli.Versioning
7 | {
8 | public class SemVer
9 | {
10 | // this lovely little regex comes from the SemVer spec:
11 | // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
12 | private static readonly Regex VersionPartRegex = new Regex(
13 | @"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$",
14 | RegexOptions.Compiled,
15 | TimeSpan.FromSeconds(2)
16 | );
17 |
18 | public bool IsPreRelease => !string.IsNullOrWhiteSpace(PreRelease);
19 |
20 | ///
21 | /// Serialize the parsed version information into a SemVer version string including pre and build meta
22 | ///
23 | ///
24 | public string ToSemVerVersionString(ProjectFileParser projectFileParser)
25 | {
26 | var sb = new StringBuilder();
27 | sb.Append($"{Major}.{Minor}.{Patch}");
28 |
29 | if (!string.IsNullOrWhiteSpace(PreRelease))
30 | {
31 | sb.AppendFormat("-{0}", PreRelease);
32 | if (!string.IsNullOrWhiteSpace(BuildMeta))
33 | {
34 | sb.AppendFormat("+{0}", BuildMeta);
35 | }
36 | }
37 |
38 | if (projectFileParser != null
39 | && projectFileParser.VersionSource == ProjectFileProperty.VersionPrefix
40 | && !string.IsNullOrWhiteSpace(projectFileParser.VersionSuffix))
41 | {
42 | sb.AppendFormat("-{0}", projectFileParser.VersionSuffix);
43 | }
44 |
45 | return sb.ToString();
46 | }
47 |
48 | ///
49 | /// Create a new instance of a SemVer based off the version string
50 | ///
51 | /// The version string to parse into a SemVer instance
52 | ///
53 | public static SemVer FromString(string versionString)
54 | {
55 | var matches = VersionPartRegex.Match(versionString);
56 | if (!matches.Success)
57 | {
58 | throw new ArgumentException($"Invalid SemVer version string: {versionString}", nameof(versionString));
59 | }
60 |
61 | // Groups [0] is the full string , then we have the version parts after that
62 | return new SemVer
63 | {
64 | Major = Convert.ToInt32(matches.Groups[1].Value),
65 | Minor = Convert.ToInt32(matches.Groups[2].Value),
66 | Patch = Convert.ToInt32(matches.Groups[3].Value),
67 | PreRelease = matches.Groups[4].Value,
68 | BuildMeta = matches.Groups[5].Value
69 | };
70 | }
71 |
72 | public object Clone()
73 | {
74 | return this.MemberwiseClone();
75 | }
76 |
77 | ///
78 | /// The parsed major version
79 | ///
80 | ///
81 | public int Major { get; set; }
82 |
83 | ///
84 | /// The parsed minor version
85 | ///
86 | ///
87 | public int Minor { get; set; }
88 |
89 | ///
90 | /// The parsed patch version
91 | ///
92 | ///
93 | public int Patch { get; set; }
94 |
95 | ///
96 | /// Pre-release semver 2 information (the stuff added with a dash after version)
97 | ///
98 | public string PreRelease { get; set; }
99 |
100 | ///
101 | /// Build mtadata semver 2 information (the stuff added with a + sign after PreRelease info)
102 | ///
103 | public string BuildMeta { get; set; }
104 |
105 | }
106 | }
--------------------------------------------------------------------------------
/src/Versioning/SemVerBumper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Skarp.Version.Cli.Versioning
4 | {
5 | public class SemVerBumper
6 | {
7 | ///
8 | /// Bump the currently parsed version information with the specified
9 | ///
10 | ///
11 | /// The bump to apply to the version
12 | /// The specific version to apply if bump is Specific
13 | /// Additional build metadata to add to the final version string
14 | /// Override of default `next` pre-release prefix/label
15 | public SemVer Bump(
16 | SemVer currentVersion,
17 | VersionBump bump,
18 | string specificVersionToApply = "",
19 | string buildMeta = "",
20 | string preReleasePrefix = ""
21 | )
22 | {
23 | var newVersion = (SemVer)currentVersion.Clone();
24 | newVersion.BuildMeta = buildMeta;
25 |
26 | switch (bump)
27 | {
28 | case VersionBump.Major:
29 | {
30 | HandleMajorBump(newVersion);
31 | break;
32 | }
33 | case VersionBump.PreMajor:
34 | {
35 | HandlePreMajorBump(newVersion, preReleasePrefix);
36 | break;
37 | }
38 | case VersionBump.Minor:
39 | {
40 | HandleMinorBump(newVersion);
41 | break;
42 | }
43 | case VersionBump.PreMinor:
44 | {
45 | HandlePreMinorBump(newVersion, preReleasePrefix);
46 | break;
47 | }
48 | case VersionBump.Patch:
49 | {
50 | HandlePatchBump(newVersion);
51 | break;
52 | }
53 | case VersionBump.PrePatch:
54 | {
55 | HandlePrePatchBump(newVersion, preReleasePrefix);
56 | break;
57 | }
58 | case VersionBump.PreRelease:
59 | {
60 | HandlePreReleaseBump(newVersion);
61 | break;
62 | }
63 | case VersionBump.Specific:
64 | {
65 | HandleSpecificVersion(specificVersionToApply, newVersion);
66 | break;
67 | }
68 | case VersionBump.None:
69 | //Do nothing;
70 | break;
71 | default:
72 | {
73 | throw new ArgumentOutOfRangeException(nameof(bump), $"VersionBump : {bump} not supported");
74 | }
75 | }
76 |
77 | return newVersion;
78 | }
79 |
80 | private static void HandleSpecificVersion(string specificVersionToApply, SemVer newVersion)
81 | {
82 | if (string.IsNullOrEmpty(specificVersionToApply))
83 | {
84 | throw new ArgumentException($"When bump is specific, specificVersionToApply must be provided");
85 | }
86 |
87 | var specific = SemVer.FromString(specificVersionToApply);
88 | newVersion.Major = specific.Major;
89 | newVersion.Minor = specific.Minor;
90 | newVersion.Patch = specific.Patch;
91 | newVersion.PreRelease = specific.PreRelease;
92 | newVersion.BuildMeta = specific.BuildMeta;
93 | }
94 |
95 | private static void HandlePreReleaseBump(SemVer newVersion)
96 | {
97 | if (!newVersion.IsPreRelease)
98 | {
99 | throw new InvalidOperationException(
100 | "Cannot Prerelease bump when not already a prerelease. Please use prepatch, preminor or premajor to prepare");
101 | }
102 |
103 | string preReleaseLabel = "next";
104 |
105 | if (!int.TryParse(newVersion.PreRelease, out var preReleaseNumber))
106 | {
107 | // it was not just a number, let's try to split it (pre-release might look like `next.42`)
108 | var preReleaseSplit = newVersion.PreRelease.Split(".");
109 | if (preReleaseSplit.Length != 2)
110 | {
111 | throw new ArgumentException(
112 | $"Pre-release part invalid. Must be either numeric or `label.number`. Got {newVersion.PreRelease}");
113 | }
114 |
115 | if (!int.TryParse(preReleaseSplit[1], out preReleaseNumber))
116 | {
117 | throw new ArgumentException(
118 | "Second part of pre-release is not numeric, cannot apply automatic prerelease roll. Should follow pattern `label.number`");
119 | }
120 |
121 | preReleaseLabel = preReleaseSplit[0];
122 | }
123 |
124 | // increment the pre-release number
125 | preReleaseNumber += 1;
126 | newVersion.PreRelease = $"{preReleaseLabel}.{preReleaseNumber}";
127 | }
128 |
129 | private static void HandlePrePatchBump(SemVer newVersion, string preReleasePrefix)
130 | {
131 | if (string.IsNullOrWhiteSpace(preReleasePrefix))
132 | {
133 | preReleasePrefix = "next";
134 | }
135 | newVersion.Patch += 1;
136 | newVersion.PreRelease = $"{preReleasePrefix}.0";
137 | }
138 |
139 | private void HandlePatchBump(SemVer newVersion)
140 | {
141 | if (!newVersion.IsPreRelease)
142 | {
143 | newVersion.Patch += 1;
144 | }
145 | else
146 | {
147 | newVersion.PreRelease = string.Empty;
148 | newVersion.BuildMeta = string.Empty;
149 | }
150 | }
151 |
152 | private void HandlePreMinorBump(SemVer newVersion, string preReleasePrefix)
153 | {
154 | if (string.IsNullOrWhiteSpace(preReleasePrefix))
155 | {
156 | preReleasePrefix = "next";
157 | }
158 |
159 | newVersion.Minor += 1;
160 | newVersion.Patch = 0;
161 | newVersion.PreRelease = $"{preReleasePrefix}.0";
162 | }
163 |
164 | private void HandleMinorBump(SemVer newVersion)
165 | {
166 | if (newVersion.IsPreRelease)
167 | {
168 | newVersion.PreRelease = string.Empty;
169 | newVersion.BuildMeta = string.Empty;
170 | }
171 | else
172 | {
173 | newVersion.Minor += 1;
174 | newVersion.Patch = 0;
175 | }
176 | }
177 |
178 | private void HandlePreMajorBump(SemVer newVersion, string preReleasePrefix)
179 | {
180 | if (string.IsNullOrWhiteSpace(preReleasePrefix))
181 | {
182 | preReleasePrefix = "next";
183 | }
184 |
185 | newVersion.Major += 1;
186 | newVersion.Minor = 0;
187 | newVersion.Patch = 0;
188 | newVersion.PreRelease = $"{preReleasePrefix}.0";
189 | }
190 |
191 | private void HandleMajorBump(SemVer newVersion)
192 | {
193 | if (newVersion.IsPreRelease)
194 | {
195 | newVersion.PreRelease = string.Empty;
196 | newVersion.BuildMeta = string.Empty;
197 | }
198 | else
199 | {
200 | newVersion.Major += 1;
201 | newVersion.Minor = 0;
202 | newVersion.Patch = 0;
203 | }
204 | }
205 | }
206 | }
--------------------------------------------------------------------------------
/src/dotnet-version.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net6.0;net7.0;net8.0
5 | true
6 | dotnet-version
7 | true
8 | Skarp.Version.Cli
9 | 4.0.0
10 | dotnet-version-cli
11 | nover
12 | A dotnet core global tool for changing your csproj version and automatically comitting and tagging - npm version style.
13 | core;version;npm version;version patch;
14 | https://github.com/skarpdev/dotnet-version-cli/
15 | https://raw.githubusercontent.com/skarpdev/dotnet-version-cli/master/LICENSE
16 | false
17 | git
18 | https://github.com/skarpdev/dotnet-version-cli/
19 | dotnet-version-cli
20 | SKARP ApS
21 | 1ae7aff7-e333-4205-aa1b-b8a8a79b4a87
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/CsProj/FileSystem/DotNetFileSystemProviderTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using Skarp.Version.Cli.CsProj.FileSystem;
5 | using Xunit;
6 |
7 | namespace Skarp.Version.Cli.Test.CsProj.FileSystem
8 | {
9 | public class DotNetFileSystemProviderTests
10 | {
11 | private readonly DotNetFileSystemProvider _provider;
12 |
13 | public DotNetFileSystemProviderTests()
14 | {
15 | _provider = new DotNetFileSystemProvider();
16 | }
17 |
18 | [Fact]
19 | public void List_works()
20 | {
21 | // List files in the current directory running from (the build output folder)
22 | var files = _provider.List("./").ToList();
23 |
24 | Assert.NotEmpty(files);
25 | Assert.Contains("./dotnet-version.dll", files);
26 | }
27 |
28 | [Theory]
29 | [InlineData("./dotnet-version.dll", false)]
30 | [InlineData("../../../dotnet-version-test.csproj", true)]
31 | public void IsCsProjectFile_works(string path, bool isCsProj)
32 | {
33 | Assert.Equal(_provider.IsCsProjectFile(path), isCsProj);
34 | }
35 |
36 | [Fact(Skip = "Not working properly in CI")]
37 | public void Cwd_works()
38 | {
39 | var cwd = _provider.Cwd();
40 | Assert.Contains($"Release{Path.DirectorySeparatorChar}net", cwd);
41 | }
42 |
43 | [Fact]
44 | public void LoadAllContent_works()
45 | {
46 | var content = _provider.LoadContent("../../../dotnet-version-test.csproj");
47 | Assert.Contains("fb420acf-9e12-42b6-b724-1eee9cbf251e", content);
48 | }
49 |
50 | [Fact]
51 | public void WriteAllContent_works()
52 | {
53 | var path = "./test-file.txt";
54 | var content = "this is content";
55 | _provider.WriteAllContent(path, content);
56 |
57 | var loadedContent = File.ReadAllText(path);
58 |
59 | Assert.Equal(content, loadedContent);
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/test/CsProj/ProjectFileDetectorTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using FakeItEasy;
4 | using Skarp.Version.Cli.CsProj;
5 | using Skarp.Version.Cli.CsProj.FileSystem;
6 | using Xunit;
7 |
8 | namespace Skarp.Version.Cli.Test.CsProj
9 | {
10 | public class ProjectFileDetectorTest
11 | {
12 | private static string _projectXml =
13 | "" +
14 | "" +
15 | "netstandard1.6" +
16 | "Unit.For.The.Win" +
17 | "Unit.Testing.Library" +
18 | "1.0.0" +
19 | "" +
20 | "";
21 |
22 | [Fact]
23 | public void CanDetectCsProjFileWithGivenBootstrapFolder()
24 | {
25 | const string rootPath = "/unit-test";
26 | var theCsProjFile = $"{rootPath}/test.csproj";
27 |
28 | var fakeFileSystem = A.Fake(opts => opts.Strict());
29 |
30 | A.CallTo(() => fakeFileSystem.List(A._)).Returns(
31 | new List{
32 | theCsProjFile,
33 | $"{rootPath}/Test.cs",
34 | }
35 | );
36 | A.CallTo(() =>
37 | fakeFileSystem.IsCsProjectFile(
38 | A.That.Matches(str => str == $"{rootPath}")))
39 | .Returns(false);
40 | A.CallTo(() =>
41 | fakeFileSystem.LoadContent(A.That.Matches(str => str == theCsProjFile))
42 | ).Returns(_projectXml);
43 |
44 | var detect = new ProjectFileDetector(fakeFileSystem);
45 | var xml = detect.FindAndLoadCsProj(rootPath);
46 |
47 | Assert.Equal(_projectXml, xml);
48 | Assert.Equal(theCsProjFile, detect.ResolvedCsProjFile);
49 | }
50 |
51 | [Fact]
52 | public void AbortsWhenMoreThanOneCsprojFile()
53 | {
54 | const string rootPath = "/unit-test";
55 |
56 | var fakeFileSystem = A.Fake(opts => opts.Strict());
57 | A.CallTo(() => fakeFileSystem.List(A._)).Returns(
58 | new List{
59 | $"{rootPath}/test.csproj",
60 | $"{rootPath}/other.csproj",
61 | $"{rootPath}/Test.cs",
62 | }
63 | );
64 | A.CallTo(() =>
65 | fakeFileSystem.IsCsProjectFile(
66 | A.That.Matches(str => str == $"{rootPath}")))
67 | .Returns(false);
68 |
69 |
70 | var detect = new ProjectFileDetector(fakeFileSystem);
71 | Assert.Throws(() => detect.FindAndLoadCsProj(rootPath));
72 | }
73 |
74 | [Fact]
75 | public void Aborts_when_no_csproj_file()
76 | {
77 | const string rootPath = "/unit-test";
78 |
79 | var fakeFileSystem = A.Fake(opts => opts.Strict());
80 | A.CallTo(() => fakeFileSystem.List(A._)).Returns(
81 | new List{
82 | $"{rootPath}/Test.cs",
83 | }
84 | );
85 | A.CallTo(() =>
86 | fakeFileSystem.IsCsProjectFile(
87 | A.That.Matches(str => str == $"{rootPath}")))
88 | .Returns(false);
89 |
90 |
91 | var detect = new ProjectFileDetector(fakeFileSystem);
92 | Assert.Throws(() => detect.FindAndLoadCsProj(rootPath));
93 | }
94 |
95 | [Fact]
96 | public void CanDetectCsProjFileWithGivenBootstrapCsProj()
97 | {
98 | const string rootPath = "/unit-test";
99 | var theCsProjFile = $"{rootPath}/test.csproj";
100 |
101 | var fakeFileSystem = A.Fake(opts => opts.Strict());
102 | A.CallTo(() => fakeFileSystem.List(A._)).Returns(
103 | new List{
104 | theCsProjFile,
105 | $"{rootPath}/Test.cs",
106 | }
107 | );
108 | A.CallTo(() =>
109 | fakeFileSystem.IsCsProjectFile(
110 | A.That.Matches(str => str == theCsProjFile)))
111 | .Returns(true);
112 | A.CallTo(() =>
113 | fakeFileSystem.LoadContent(A.That.Matches(str => str == theCsProjFile))
114 | ).Returns(_projectXml);
115 |
116 | var detect = new ProjectFileDetector(fakeFileSystem);
117 | var xml = detect.FindAndLoadCsProj(theCsProjFile);
118 |
119 | Assert.Equal(_projectXml, xml);
120 | Assert.Equal(theCsProjFile, detect.ResolvedCsProjFile);
121 | }
122 |
123 | [Fact]
124 | public void CanDetectProjectFileWithEmptyBootstrapPath()
125 | {
126 | const string rootPath = "/unit-test";
127 | var theCsProjFile = $"{rootPath}/test.csproj";
128 |
129 | var fakeFileSystem = A.Fake(opts => opts.Strict());
130 | A.CallTo(() => fakeFileSystem.List(A._)).Returns(
131 | new List{
132 | theCsProjFile,
133 | $"{rootPath}/Test.cs",
134 | }
135 | );
136 | A.CallTo(() =>
137 | fakeFileSystem.Cwd()
138 | ).Returns(rootPath);
139 | A.CallTo(() =>
140 | fakeFileSystem.IsCsProjectFile(
141 | A.That.Matches(str => str == $"{rootPath}")))
142 | .Returns(false);
143 | A.CallTo(() =>
144 | fakeFileSystem.LoadContent(A.That.Matches(str => str == theCsProjFile))
145 | ).Returns(_projectXml);
146 |
147 | var detect = new ProjectFileDetector(fakeFileSystem);
148 | var xml = detect.FindAndLoadCsProj("");
149 |
150 | Assert.Equal(_projectXml, xml);
151 | Assert.Equal(theCsProjFile, detect.ResolvedCsProjFile);
152 | }
153 |
154 | }
155 | }
--------------------------------------------------------------------------------
/test/CsProj/ProjectFileParserTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Skarp.Version.Cli.CsProj;
3 | using Xunit;
4 |
5 | namespace Skarp.Version.Cli.Test.CsProj
6 | {
7 | public class ProjectFileParserTest
8 | {
9 | private readonly ProjectFileParser parser;
10 |
11 | public ProjectFileParserTest()
12 | {
13 | parser = new ProjectFileParser();
14 | }
15 |
16 | [Fact]
17 | public void CanParseWellFormedProjectFilesWithVersionTag()
18 | {
19 | const string csProjXml = "" +
20 | "" +
21 | "netstandard1.6" +
22 | "Unit.For.The.Win" +
23 | "Unit.Testing.Library" +
24 | "1.0.0" +
25 | "1.0.0-1+master" +
26 | "" +
27 | "";
28 |
29 | parser.Load(csProjXml, ProjectFileProperty.Version, ProjectFileProperty.Version, ProjectFileProperty.PackageVersion);
30 | Assert.Equal("1.0.0", parser.Version);
31 | Assert.Equal("1.0.0-1+master", parser.PackageVersion);
32 | }
33 |
34 | [Fact]
35 | public void CanParse_when_version_and_package_version_missing()
36 | {
37 | const string csProjXml = "" +
38 | "" +
39 | "netstandard1.6" +
40 | "Unit.For.The.Win" +
41 | "Unit.Testing.Library" +
42 | "" +
43 | "";
44 |
45 | parser.Load(csProjXml, ProjectFileProperty.Version, ProjectFileProperty.PackageVersion, ProjectFileProperty.Version);
46 | Assert.Empty(parser.Version);
47 | Assert.Empty(parser.PackageVersion);
48 | }
49 |
50 | [Fact]
51 | public void BailsOnMalformedProjectFile()
52 | {
53 | const string csProjXml = "" +
54 | "" +
55 | "netstandard1.6" +
56 | "Unit.For.The.Win" +
57 | "Unit.Testing.Library" +
58 | "" +
59 | "";
60 |
61 | var ex = Assert.Throws(() =>
62 | parser.Load(csProjXml, ProjectFileProperty.Version)
63 | );
64 |
65 | Assert.Contains($"The provided csproj file seems malformed - no in the root", ex.Message);
66 | Assert.Equal("xmlDocument", ex.ParamName);
67 | }
68 |
69 | [Fact]
70 | public void Works_when_no_packageId_or_title()
71 | {
72 | const string csProjXml = "" +
73 | "" +
74 | "netstandard1.6" +
75 | "Unit.For.The.Win" +
76 | "" +
77 | "";
78 |
79 | parser.Load(csProjXml, ProjectFileProperty.Version);
80 | Assert.Empty(parser.PackageName);
81 | }
82 |
83 | [Fact]
84 | public void CanParse_when_versionprefix_is_set()
85 | {
86 | const string csProjXml = "" +
87 | "" +
88 | "netstandard1.6" +
89 | "Unit.For.The.Win" +
90 | "1.0.0" +
91 | "" +
92 | "";
93 |
94 | parser.Load(csProjXml, ProjectFileProperty.Version);
95 | Assert.Empty(parser.PackageName);
96 | Assert.Equal("1.0.0", parser.VersionPrefix);
97 | Assert.Empty(parser.VersionSuffix);
98 | Assert.Empty(parser.Version);
99 | }
100 |
101 |
102 | [Fact]
103 | public void CanParse_when_versionprefix_and_versionsuffix_is_set()
104 | {
105 | const string csProjXml = "" +
106 | "" +
107 | "netstandard1.6" +
108 | "Unit.For.The.Win" +
109 | "1.0.0" +
110 | "SNAPSHOT" +
111 | "" +
112 | "";
113 |
114 | parser.Load(csProjXml, ProjectFileProperty.Version);
115 | Assert.Empty(parser.PackageName);
116 | Assert.Equal("1.0.0", parser.VersionPrefix);
117 | Assert.Equal("SNAPSHOT", parser.VersionSuffix);
118 | Assert.Empty(parser.Version);
119 | }
120 |
121 | [Fact]
122 | public void Can_get_human_readable_version_from_version()
123 | {
124 | const string csProjXml = "" +
125 | "" +
126 | "netstandard1.6" +
127 | "Unit.For.The.Win" +
128 | "Unit.Testing.Library" +
129 | "1.0.0" +
130 | "1.0.0-1+master" +
131 | "" +
132 | "";
133 |
134 | parser.Load(csProjXml, ProjectFileProperty.Version);
135 | Assert.Equal("1.0.0", parser.GetHumanReadableVersionFromSource());
136 | }
137 |
138 | [Fact]
139 | public void Can_get_human_readable_version_from_packageversion()
140 | {
141 | const string csProjXml = "" +
142 | "" +
143 | "netstandard1.6" +
144 | "Unit.For.The.Win" +
145 | "Unit.Testing.Library" +
146 | "1.0.0" +
147 | "1.0.0-1+master" +
148 | "" +
149 | "";
150 |
151 | parser.Load(csProjXml, ProjectFileProperty.PackageVersion);
152 | Assert.Equal("1.0.0-1+master", parser.GetHumanReadableVersionFromSource());
153 | }
154 |
155 | [Fact]
156 | public void Can_get_human_readable_version_from_versionprefix()
157 | {
158 | const string csProjXml = "" +
159 | "" +
160 | "netstandard1.6" +
161 | "Unit.For.The.Win" +
162 | "Unit.Testing.Library" +
163 | "1.0.0" +
164 | "master" +
165 | "" +
166 | "";
167 |
168 | parser.Load(csProjXml, ProjectFileProperty.Version, ProjectFileProperty.Version, ProjectFileProperty.VersionPrefix,
169 | ProjectFileProperty.VersionSuffix, ProjectFileProperty.PackageVersion);
170 | Assert.Equal("1.0.0-master", parser.GetHumanReadableVersionFromSource());
171 | }
172 | }
173 | }
--------------------------------------------------------------------------------
/test/CsProj/ProjectFileVersionPatcherTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FakeItEasy;
3 | using Skarp.Version.Cli.CsProj;
4 | using Skarp.Version.Cli.CsProj.FileSystem;
5 | using Xunit;
6 |
7 | namespace Skarp.Version.Cli.Test.CsProj
8 | {
9 | public class ProjectFileVersionPatcherTest
10 | {
11 | private static string _projectXml =
12 | "" +
13 | "" +
14 | "netstandard1.6" +
15 | "Unit.For.The.Win" +
16 | "Unit.Testing.Library" +
17 | "1.0.0" +
18 | "1.0.0" +
19 | "" +
20 | "";
21 |
22 | private readonly ProjectFileVersionPatcher _patcher;
23 | private readonly IFileSystemProvider _fileSystem;
24 |
25 | public ProjectFileVersionPatcherTest()
26 | {
27 | _fileSystem = A.Fake();
28 | _patcher = new ProjectFileVersionPatcher(_fileSystem);
29 | }
30 |
31 | [Fact]
32 | public void Throws_when_load_not_called()
33 | {
34 | var ex = Record.Exception((() => _patcher.PatchField("2.0.0", ProjectFileProperty.Version)));
35 |
36 | Assert.IsAssignableFrom(ex);
37 | }
38 |
39 | [Fact]
40 | public void CanPatchVersionOnWellFormedXml()
41 | {
42 | _patcher.Load(_projectXml);
43 | _patcher.PatchField("1.1.0-0", ProjectFileProperty.Version);
44 |
45 | var newXml = _patcher.ToXmlString();
46 | Assert.NotEqual(_projectXml, newXml);
47 | Assert.Contains("1.1.0-0", newXml);
48 | }
49 |
50 | [Fact]
51 | public void CanPatchWhenVersionIsMissing()
52 | {
53 | var xml =
54 | "" +
55 | "" +
56 | "netstandard1.6" +
57 | "Unit.For.The.Win" +
58 | "Unit.Testing.Library" +
59 | "" +
60 | "";
61 |
62 | _patcher.Load(xml);
63 | _patcher.PatchField("2.0.0", ProjectFileProperty.Version);
64 | var newXml = _patcher.ToXmlString();
65 | Assert.Contains("2.0.0", newXml);
66 | }
67 |
68 | [Fact]
69 | public void PreservesWhiteSpaceWhilePatching()
70 | {
71 | var xml =
72 | "" +
73 | "" +
74 | "1.0.0" +
75 | "" +
76 | $"{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}" +
77 | "";
78 |
79 | _patcher.Load(xml);
80 | _patcher.PatchField("2.0.0", ProjectFileProperty.Version);
81 | var newXml = _patcher.ToXmlString();
82 | Assert.Contains($"{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}",
83 | newXml);
84 | }
85 |
86 | [Fact]
87 | public void HandlesMissingVersionWhenTargetFrameworksField()
88 | {
89 | var xml =
90 | "" +
91 | "" +
92 | "netstandard1.6;dotnet462" +
93 | "" +
94 | "";
95 |
96 | _patcher.Load(xml);
97 | _patcher.PatchField("2.0.0", ProjectFileProperty.Version);
98 | var newXml = _patcher.ToXmlString();
99 | Assert.Contains("2.0.0", newXml);
100 | }
101 |
102 | [Fact]
103 | public void BailsWhenUnableToLocatePropertyGroup()
104 | {
105 | var xml =
106 | "" +
107 | "";
108 |
109 | _patcher.Load(xml);
110 | var ex = Record.Exception(() => _patcher.PatchField("2.0.0", ProjectFileProperty.Version));
111 |
112 | var aex = Assert.IsAssignableFrom(ex);
113 |
114 | Assert.Equal(
115 | "Given XML does not contain Version and cannot locate existing PropertyGroup to add it to - is this a valid csproj file?",
116 | aex.Message
117 | );
118 | }
119 |
120 | [Fact]
121 | public void Flush_calls_filesystem()
122 | {
123 | _patcher.Load(_projectXml);
124 |
125 | var thePath = "/some/path.txt";
126 | _patcher.Flush(thePath);
127 |
128 | A.CallTo(() => _fileSystem.WriteAllContent(thePath, A._)).MustHaveHappenedOnceExactly();
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/test/GitVcsTest.cs:
--------------------------------------------------------------------------------
1 | using Skarp.Version.Cli.Vcs.Git;
2 | using Xunit;
3 |
4 | namespace Skarp.Version.Cli.Test
5 | {
6 | public class GitVcsTest
7 | {
8 | private readonly GitVcs _vcs;
9 |
10 | public GitVcsTest()
11 | {
12 | _vcs = new GitVcs();
13 | }
14 |
15 | [Fact(
16 | Skip = "Dont run on build servers"
17 | )]
18 | public void DetectingGitOnMachineWorks()
19 | {
20 | Assert.True(_vcs.IsVcsToolPresent());
21 | }
22 |
23 | [Fact(
24 | Skip = "Dont run on build servers"
25 | )]
26 | public void IsRepositoryCleanWorks()
27 | {
28 | Assert.True(_vcs.IsRepositoryClean());
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/test/ProgramTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Skarp.Version.Cli.CsProj;
4 | using Xunit;
5 |
6 | namespace Skarp.Version.Cli.Test
7 | {
8 | public class ProgramTest
9 | {
10 | [Theory]
11 | [InlineData("major", VersionBump.Major)]
12 | [InlineData("premajor", VersionBump.PreMajor)]
13 | [InlineData("minor", VersionBump.Minor)]
14 | [InlineData("preminor", VersionBump.PreMinor)]
15 | [InlineData("patch", VersionBump.Patch)]
16 | [InlineData("prepatch", VersionBump.PrePatch)]
17 | [InlineData("1.0.1", VersionBump.Specific)]
18 | [InlineData("1.0.1-0", VersionBump.Specific)]
19 | [InlineData("1.0.1-0+master", VersionBump.Specific)]
20 | [InlineData("1.0.1-alpha.43+4432fsd", VersionBump.Specific)]
21 | public void GetVersionBumpFromRemainingArgsWork(string strVersionBump, VersionBump expectedBump)
22 | {
23 | var args = Program.GetVersionBumpFromRemainingArgs(
24 | new List() {strVersionBump},
25 | OutputFormat.Text,
26 | true,
27 | true,
28 | string.Empty,
29 | string.Empty,
30 | string.Empty,
31 | string.Empty,
32 | string.Empty,
33 | string.Empty
34 | );
35 | Assert.Equal(expectedBump, args.VersionBump);
36 | if (expectedBump == VersionBump.Specific)
37 | {
38 | Assert.Equal(strVersionBump, args.SpecificVersionToApply);
39 | }
40 | }
41 |
42 | [Fact]
43 | public void Get_version_bump_throws_on_missing_value()
44 | {
45 | var ex = Assert.Throws(() =>
46 | Program.GetVersionBumpFromRemainingArgs(
47 | new List(),
48 | OutputFormat.Text,
49 | true,
50 | true,
51 | string.Empty,
52 | string.Empty,
53 | string.Empty,
54 | string.Empty,
55 | string.Empty,
56 | string.Empty
57 | )
58 | );
59 | Assert.Contains(
60 | $"No version bump specified, please specify one of:\n\tmajor | minor | patch | premajor | preminor | prepatch | prerelease | ",
61 | ex.Message);
62 | }
63 |
64 | [Fact]
65 | public void Get_version_bump_throws_on_invalid_value()
66 | {
67 | const string invalidVersion = "invalid-version";
68 |
69 | var ex = Assert.Throws(() =>
70 | Program.GetVersionBumpFromRemainingArgs(
71 | new List {invalidVersion},
72 | OutputFormat.Text,
73 | true,
74 | true,
75 | string.Empty,
76 | string.Empty,
77 | string.Empty,
78 | string.Empty,
79 | string.Empty,
80 | string.Empty
81 | )
82 | );
83 | Assert.Contains($"Invalid SemVer version string: {invalidVersion}",
84 | ex.Message);
85 | Assert.Equal("versionString", ex.ParamName);
86 | }
87 |
88 | [Fact]
89 | public void DefaultsToReadingVersionStringFromVersionProperty()
90 | {
91 | var args = Program.GetVersionBumpFromRemainingArgs(
92 | new List() {"patch"},
93 | OutputFormat.Text,
94 | true,
95 | true,
96 | string.Empty,
97 | string.Empty,
98 | string.Empty,
99 | string.Empty,
100 | string.Empty,
101 | string.Empty
102 | );
103 |
104 | Assert.Equal(ProjectFileProperty.Version, args.ProjectFilePropertyName);
105 | }
106 |
107 | [Theory]
108 | [InlineData(null, ProjectFileProperty.Version)]
109 | [InlineData("", ProjectFileProperty.Version)]
110 | [InlineData("verSION", ProjectFileProperty.Version)]
111 | [InlineData("packageversion", ProjectFileProperty.PackageVersion)]
112 | public void CanOverrideTheVersionPropertyName(string input, ProjectFileProperty expected)
113 | {
114 | var args = Program.GetVersionBumpFromRemainingArgs(
115 | new List() {"patch"},
116 | OutputFormat.Text,
117 | true,
118 | true,
119 | string.Empty,
120 | string.Empty,
121 | string.Empty,
122 | string.Empty,
123 | string.Empty,
124 | input
125 | );
126 |
127 | Assert.Equal(expected, args.ProjectFilePropertyName);
128 | }
129 | }
130 | }
--------------------------------------------------------------------------------
/test/VersionCliTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FakeItEasy;
3 | using Skarp.Version.Cli.CsProj;
4 | using Skarp.Version.Cli.Vcs;
5 | using Skarp.Version.Cli.Versioning;
6 | using Xunit;
7 |
8 | namespace Skarp.Version.Cli.Test
9 | {
10 | public class VersionCliTest
11 | {
12 | private IVcs _vcsTool;
13 | private ProjectFileDetector _fileDetector;
14 | private ProjectFileParser _fileParser;
15 | private VcsParser _vcsParser;
16 | private ProjectFileVersionPatcher _filePatcher;
17 | private VersionCli _cli;
18 |
19 | public VersionCliTest()
20 | {
21 | _vcsTool = A.Fake(opts => opts.Strict());
22 | A.CallTo(() => _vcsTool.ToolName()).Returns("_FAKE_");
23 |
24 | _fileDetector = A.Fake();
25 | _fileParser = A.Fake();
26 | _vcsParser = A.Fake();
27 | _filePatcher = A.Fake();
28 |
29 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
30 | const string csProjFilePath = "/unit-test/test.csproj";
31 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
32 |
33 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
34 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
35 | A.CallTo(() => _fileParser.VersionPrefix).Returns("2.0.1");
36 | A.CallTo(() => _fileParser.VersionSuffix).Returns("DEVELOPMENT");
37 | A.CallTo(() => _fileParser.DesiredVersionSource).Returns(ProjectFileProperty.Version);
38 |
39 | _cli = new VersionCli(
40 | _vcsTool,
41 | _fileDetector,
42 | _fileParser,
43 | _vcsParser,
44 | _filePatcher,
45 | new SemVerBumper()
46 | );
47 | }
48 |
49 | [Fact]
50 | public void VersionCli_Bump_VersionPrefix()
51 | {
52 | A.CallTo(() => _fileParser.Version).Returns(null);
53 | A.CallTo(() => _fileParser.VersionPrefix).Returns("2.0.1");
54 | A.CallTo(() => _fileParser.VersionSuffix).Returns("DEVELOPMENT");
55 |
56 | var output = _cli.Execute(new VersionCliArgs
57 | { OutputFormat = OutputFormat.Bare, VersionBump = VersionBump.None, DryRun = true });
58 |
59 | Assert.Equal("2.0.1-DEVELOPMENT", output.OldVersion);
60 | }
61 |
62 | [Fact]
63 | public void VersionCli_throws_when_vcs_tool_is_not_present_and_doVcs_is_true()
64 | {
65 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(false);
66 |
67 | var ex = Assert.Throws(() =>
68 | _cli.Execute(new VersionCliArgs { VersionBump = VersionBump.Major, DoVcs = true }));
69 | Assert.Equal("Unable to find the vcs tool _FAKE_ in your path", ex.Message);
70 | }
71 |
72 | [Fact]
73 | public void VersionCli_doesNotThrow_when_vcs_tool_is_not_present_if_doVcs_is_false()
74 | {
75 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(false);
76 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
77 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
78 |
79 | _cli.Execute(new VersionCliArgs { VersionBump = VersionBump.Major, DoVcs = false });
80 | }
81 |
82 | [Fact]
83 | public void VersionCli_throws_when_repo_is_not_clean_and_doVcs_is_true()
84 | {
85 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
86 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(false);
87 |
88 | var ex = Assert.Throws(() =>
89 | _cli.Execute(new VersionCliArgs { VersionBump = VersionBump.Major, DoVcs = true }));
90 | Assert.Equal("You currently have uncomitted changes in your repository, please commit these and try again",
91 | ex.Message);
92 | }
93 |
94 | [Fact]
95 | public void VersionCli_doesNotThrow_when_repo_is_not_clean_if_doVcs_is_false()
96 | {
97 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
98 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(false);
99 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
100 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
101 |
102 | _cli.Execute(new VersionCliArgs { VersionBump = VersionBump.Major, DoVcs = false });
103 | }
104 |
105 | [Fact]
106 | public void VersionCli_can_bump_versions()
107 | {
108 | // Configure
109 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
110 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
111 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
112 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
113 |
114 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
115 | const string csProjFilePath = "/unit-test/test.csproj";
116 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
117 |
118 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
119 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
120 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
121 |
122 | // Act
123 | _cli.Execute(new VersionCliArgs { VersionBump = VersionBump.Major, DoVcs = true, DryRun = false });
124 |
125 | // Verify
126 | A.CallTo(() => _filePatcher.PatchField(
127 | A.That.Matches(newVer => newVer == "2.0.0"),
128 | ProjectFileProperty.Version
129 | ))
130 | .MustHaveHappened(Repeated.Exactly.Once);
131 |
132 | A.CallTo(() => _filePatcher.Flush(
133 | A.That.Matches(path => path == csProjFilePath)))
134 | .MustHaveHappened(Repeated.Exactly.Once);
135 | A.CallTo(() => _vcsTool.Commit(
136 | A.That.Matches(path => path == csProjFilePath),
137 | A.That.Matches(msg => msg == "v2.0.0")))
138 | .MustHaveHappened(Repeated.Exactly.Once);
139 | A.CallTo(() => _vcsTool.Tag(
140 | A.That.Matches(tag => tag == "v2.0.0")))
141 | .MustHaveHappened(Repeated.Exactly.Once);
142 | }
143 |
144 | [Fact]
145 | public void VersionCli_can_bump_pre_release_versions()
146 | {
147 | // Configure
148 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
149 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
150 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
151 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
152 |
153 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
154 | const string csProjFilePath = "/unit-test/test.csproj";
155 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
156 |
157 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
158 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
159 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
160 |
161 | // Act
162 | _cli.Execute(new VersionCliArgs { VersionBump = VersionBump.PreMajor, DoVcs = true, DryRun = false });
163 |
164 | // Verify
165 | A.CallTo(() => _filePatcher.PatchField(
166 | A.That.Matches(newVer => newVer == "2.0.0-next.0"),
167 | ProjectFileProperty.Version
168 | ))
169 | .MustHaveHappened(Repeated.Exactly.Once);
170 |
171 | A.CallTo(() => _filePatcher.Flush(
172 | A.That.Matches(path => path == csProjFilePath)))
173 | .MustHaveHappened(Repeated.Exactly.Once);
174 | A.CallTo(() => _vcsTool.Commit(
175 | A.That.Matches(path => path == csProjFilePath),
176 | A.That.Matches(msg => msg == "v2.0.0-next.0")))
177 | .MustHaveHappened(Repeated.Exactly.Once);
178 | A.CallTo(() => _vcsTool.Tag(
179 | A.That.Matches(tag => tag == "v2.0.0-next.0")))
180 | .MustHaveHappened(Repeated.Exactly.Once);
181 | }
182 |
183 | [Fact]
184 | public void VersionCli_can_bump_pre_release_with_custom_prefix()
185 | {
186 | // Configure
187 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
188 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
189 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
190 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
191 |
192 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
193 | const string csProjFilePath = "/unit-test/test.csproj";
194 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
195 |
196 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
197 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
198 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
199 |
200 | // Act
201 | _cli.Execute(new VersionCliArgs
202 | { VersionBump = VersionBump.PreMajor, DoVcs = true, DryRun = false, PreReleasePrefix = "beta" });
203 |
204 | // Verify
205 | A.CallTo(() => _filePatcher.PatchField(
206 | "2.0.0-beta.0",
207 | ProjectFileProperty.Version
208 | ))
209 | .MustHaveHappened(Repeated.Exactly.Once);
210 |
211 | A.CallTo(() => _filePatcher.Flush(
212 | csProjFilePath))
213 | .MustHaveHappened(Repeated.Exactly.Once);
214 | A.CallTo(() => _vcsTool.Commit(
215 | csProjFilePath,
216 | "v2.0.0-beta.0"))
217 | .MustHaveHappened(Repeated.Exactly.Once);
218 | A.CallTo(() => _vcsTool.Tag(
219 | "v2.0.0-beta.0"))
220 | .MustHaveHappened(Repeated.Exactly.Once);
221 | }
222 |
223 | [Fact]
224 | public void VersionCli_can_bump_pre_release_with_build_meta_versions()
225 | {
226 | // Configure
227 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
228 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
229 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
230 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
231 |
232 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
233 | const string csProjFilePath = "/unit-test/test.csproj";
234 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
235 |
236 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
237 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
238 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
239 |
240 | // Act
241 | _cli.Execute(new VersionCliArgs
242 | { VersionBump = VersionBump.PreMajor, DoVcs = true, DryRun = false, BuildMeta = "master" });
243 |
244 | // Verify
245 | A.CallTo(() => _filePatcher.PatchField(
246 | A.That.Matches(newVer => newVer == "2.0.0-next.0+master"),
247 | ProjectFileProperty.Version
248 | ))
249 | .MustHaveHappened(Repeated.Exactly.Once);
250 |
251 | A.CallTo(() => _filePatcher.Flush(
252 | A.That.Matches(path => path == csProjFilePath)))
253 | .MustHaveHappened(Repeated.Exactly.Once);
254 | A.CallTo(() => _vcsTool.Commit(
255 | A.That.Matches(path => path == csProjFilePath),
256 | A.That.Matches(msg => msg == "v2.0.0-next.0+master")))
257 | .MustHaveHappened(Repeated.Exactly.Once);
258 | A.CallTo(() => _vcsTool.Tag(
259 | A.That.Matches(tag => tag == "v2.0.0-next.0+master")))
260 | .MustHaveHappened(Repeated.Exactly.Once);
261 | }
262 |
263 | [Fact]
264 | public void VersionCli_can_bump_versions_can_skip_vcs()
265 | {
266 | // Configure
267 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
268 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
269 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
270 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
271 |
272 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
273 | const string csProjFilePath = "/unit-test/test.csproj";
274 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
275 |
276 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
277 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
278 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
279 |
280 | // Act
281 | _cli.Execute(new VersionCliArgs { VersionBump = VersionBump.Major, DoVcs = false, DryRun = false });
282 |
283 | // Verify
284 | A.CallTo(() => _filePatcher.PatchField(
285 | A.That.Matches(newVer => newVer == "2.0.0"),
286 | ProjectFileProperty.Version
287 | ))
288 | .MustHaveHappened(Repeated.Exactly.Once);
289 | A.CallTo(() => _filePatcher.Flush(
290 | A.That.Matches(path => path == csProjFilePath)))
291 | .MustHaveHappened(Repeated.Exactly.Once);
292 | A.CallTo(() => _vcsTool.Commit(A._, A._)).MustNotHaveHappened();
293 | A.CallTo(() => _vcsTool.Tag(A._)).MustNotHaveHappened();
294 | }
295 |
296 | [Fact]
297 | public void VersionCli_can_bump_versions_can_dry_run()
298 | {
299 | // Configure
300 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
301 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
302 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
303 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
304 |
305 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
306 | const string csProjFilePath = "/unit-test/test.csproj";
307 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
308 |
309 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
310 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
311 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
312 |
313 | // Act
314 | var info = _cli.Execute(new VersionCliArgs
315 | { VersionBump = VersionBump.Major, DoVcs = true, DryRun = true });
316 |
317 | Assert.NotEqual(info.OldVersion, info.NewVersion);
318 | Assert.Equal("2.0.0", info.NewVersion);
319 |
320 | // Verify
321 | A.CallTo(() => _filePatcher.PatchField(
322 | A.That.Matches(newVer => newVer == "2.0.0"),
323 | ProjectFileProperty.Version
324 | ))
325 | .MustNotHaveHappened();
326 |
327 | A.CallTo(() => _filePatcher.Flush(
328 | A.That.Matches(path => path == csProjFilePath)))
329 | .MustNotHaveHappened();
330 | A.CallTo(() => _vcsTool.Commit(A._, A._)).MustNotHaveHappened();
331 | A.CallTo(() => _vcsTool.Tag(A._)).MustNotHaveHappened();
332 | }
333 |
334 | [Fact]
335 | public void VersionCli_can_set_vcs_commit_message()
336 | {
337 | // Configure
338 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
339 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
340 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
341 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
342 |
343 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
344 | const string csProjFilePath = "/unit-test/test.csproj";
345 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
346 |
347 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
348 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
349 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
350 |
351 | // Act
352 | _cli.Execute(new VersionCliArgs
353 | { VersionBump = VersionBump.Major, DoVcs = true, DryRun = false, CommitMessage = "commit message" });
354 |
355 | // Verify
356 | A.CallTo(() => _filePatcher.PatchField(
357 | A.That.Matches(newVer => newVer == "2.0.0"),
358 | ProjectFileProperty.Version
359 | ))
360 | .MustHaveHappened(Repeated.Exactly.Once);
361 |
362 | A.CallTo(() => _filePatcher.Flush(
363 | A.That.Matches(path => path == csProjFilePath)))
364 | .MustHaveHappened(Repeated.Exactly.Once);
365 | A.CallTo(() => _vcsTool.Commit(
366 | A.That.Matches(path => path == csProjFilePath),
367 | A.That.Matches(msg => msg == "commit message")))
368 | .MustHaveHappened(Repeated.Exactly.Once);
369 | A.CallTo(() => _vcsTool.Tag(
370 | A.That.Matches(tag => tag == "v2.0.0")))
371 | .MustHaveHappened(Repeated.Exactly.Once);
372 | }
373 |
374 | [Fact]
375 | public void VersionCli_can_set_vcs_commit_message_with_variables()
376 | {
377 | // Configure
378 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
379 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
380 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
381 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
382 |
383 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
384 | const string csProjFilePath = "/unit-test/test.csproj";
385 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
386 |
387 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
388 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
389 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
390 | A.CallTo(() => _fileParser.PackageName).Returns("unit-test");
391 |
392 | // Act
393 | _cli.Execute(new VersionCliArgs
394 | {
395 | VersionBump = VersionBump.Major, DoVcs = true, DryRun = false,
396 | CommitMessage = "bump from v$oldVer to v$newVer at $projName"
397 | });
398 |
399 | // Verify
400 | A.CallTo(() => _filePatcher.PatchField(
401 | A.That.Matches(newVer => newVer == "2.0.0"),
402 | ProjectFileProperty.Version
403 | ))
404 | .MustHaveHappened(Repeated.Exactly.Once);
405 |
406 | A.CallTo(() => _filePatcher.Flush(
407 | A.That.Matches(path => path == csProjFilePath)))
408 | .MustHaveHappened(Repeated.Exactly.Once);
409 | A.CallTo(() => _vcsTool.Commit(
410 | A.That.Matches(path => path == csProjFilePath),
411 | A.That.Matches(msg => msg == "bump from v1.2.1 to v2.0.0 at unit-test")))
412 | .MustHaveHappened(Repeated.Exactly.Once);
413 | A.CallTo(() => _vcsTool.Tag(
414 | A.That.Matches(tag => tag == "v2.0.0")))
415 | .MustHaveHappened(Repeated.Exactly.Once);
416 | }
417 |
418 | [Fact]
419 | public void VersionCli_can_set_vcs_tag()
420 | {
421 | // Configure
422 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
423 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
424 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
425 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
426 |
427 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
428 | const string csProjFilePath = "/unit-test/test.csproj";
429 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
430 |
431 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
432 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
433 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
434 |
435 | // Act
436 | _cli.Execute(new VersionCliArgs
437 | { VersionBump = VersionBump.Major, DoVcs = true, DryRun = false, VersionControlTag = "vcs tag" });
438 |
439 | // Verify
440 | A.CallTo(() => _filePatcher.PatchField(
441 | A.That.Matches(newVer => newVer == "2.0.0"),
442 | ProjectFileProperty.Version
443 | ))
444 | .MustHaveHappened(Repeated.Exactly.Once);
445 |
446 | A.CallTo(() => _filePatcher.Flush(
447 | A.That.Matches(path => path == csProjFilePath)))
448 | .MustHaveHappened(Repeated.Exactly.Once);
449 | A.CallTo(() => _vcsTool.Commit(
450 | A.That.Matches(path => path == csProjFilePath),
451 | A.That.Matches(msg => msg == "v2.0.0")))
452 | .MustHaveHappened(Repeated.Exactly.Once);
453 | A.CallTo(() => _vcsTool.Tag(
454 | A.That.Matches(tag => tag == "vcs tag")))
455 | .MustHaveHappened(Repeated.Exactly.Once);
456 | }
457 |
458 | [Fact]
459 | public void VersionCli_can_set_vcs_tag_with_variables()
460 | {
461 | // Configure
462 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
463 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
464 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
465 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
466 |
467 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
468 | const string csProjFilePath = "/unit-test/test.csproj";
469 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
470 |
471 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
472 | A.CallTo(() => _fileParser.Version).Returns("1.2.1");
473 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.2.1");
474 | A.CallTo(() => _fileParser.PackageName).Returns("unit-test");
475 |
476 | // Act
477 | _cli.Execute(new VersionCliArgs
478 | {
479 | VersionBump = VersionBump.Major, DoVcs = true, DryRun = false,
480 | VersionControlTag = "bump from v$oldVer to v$newVer at $projName"
481 | });
482 |
483 | // Verify
484 | A.CallTo(() => _filePatcher.PatchField(
485 | A.That.Matches(newVer => newVer == "2.0.0"),
486 | ProjectFileProperty.Version
487 | ))
488 | .MustHaveHappened(Repeated.Exactly.Once);
489 |
490 | A.CallTo(() => _filePatcher.Flush(
491 | A.That.Matches(path => path == csProjFilePath)))
492 | .MustHaveHappened(Repeated.Exactly.Once);
493 | A.CallTo(() => _vcsTool.Commit(
494 | A.That.Matches(path => path == csProjFilePath),
495 | A.That.Matches(msg => msg == "v2.0.0")))
496 | .MustHaveHappened(Repeated.Exactly.Once);
497 | A.CallTo(() => _vcsTool.Tag(
498 | A.That.Matches(tag => tag == "bump from v1.2.1 to v2.0.0 at unit-test")))
499 | .MustHaveHappened(Repeated.Exactly.Once);
500 | }
501 |
502 | [Fact]
503 | public void VersionCli_can_read_version_from_version_field()
504 | {
505 | // Configure
506 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
507 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
508 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
509 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
510 |
511 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
512 | const string csProjFilePath = "/unit-test/test.csproj";
513 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
514 |
515 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
516 | A.CallTo(() => _fileParser.Version).Returns("2.0.0");
517 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.0.0");
518 |
519 | // Act
520 | var output = _cli.Execute(new VersionCliArgs
521 | {
522 | ProjectFilePropertyName = ProjectFileProperty.Version,
523 | VersionBump = VersionBump.None,
524 | OutputFormat = OutputFormat.Bare,
525 | DoVcs = true,
526 | DryRun = false
527 | });
528 |
529 | Assert.Equal("2.0.0", output.OldVersion);
530 | }
531 |
532 | [Fact]
533 | public void VersionCli_can_read_version_from_package_version_field()
534 | {
535 | // Configure
536 | A.CallTo(() => _vcsTool.IsRepositoryClean()).Returns(true);
537 | A.CallTo(() => _vcsTool.IsVcsToolPresent()).Returns(true);
538 | A.CallTo(() => _vcsTool.Commit(A._, A._)).DoesNothing();
539 | A.CallTo(() => _vcsTool.Tag(A._)).DoesNothing();
540 |
541 | A.CallTo(() => _fileDetector.FindAndLoadCsProj(A._)).Returns("");
542 | const string csProjFilePath = "/unit-test/test.csproj";
543 | A.CallTo(() => _fileDetector.ResolvedCsProjFile).Returns(csProjFilePath);
544 |
545 | A.CallTo(() => _fileParser.Load(A._, A._)).DoesNothing();
546 | A.CallTo(() => _fileParser.Version).Returns("2.0.0");
547 | A.CallTo(() => _fileParser.PackageVersion).Returns("1.0.0");
548 | A.CallTo(() => _fileParser.DesiredVersionSource).Returns(ProjectFileProperty.PackageVersion);
549 |
550 | // Act
551 | var output = _cli.Execute(new VersionCliArgs
552 | {
553 | ProjectFilePropertyName = ProjectFileProperty.PackageVersion,
554 | VersionBump = VersionBump.None,
555 | OutputFormat = OutputFormat.Bare,
556 | DoVcs = true,
557 | DryRun = false
558 | });
559 |
560 | Assert.Equal("1.0.0", output.OldVersion);
561 | }
562 | }
563 | }
--------------------------------------------------------------------------------
/test/Versioning/SemVerBumperTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Skarp.Version.Cli.Versioning;
3 | using Xunit;
4 |
5 | namespace Skarp.Version.Cli.Test.Versioning
6 | {
7 | public class SemVerBumperTests
8 | {
9 | private readonly SemVerBumper _bumper;
10 |
11 | public SemVerBumperTests()
12 | {
13 | _bumper = new SemVerBumper();
14 | }
15 |
16 | [Theory]
17 | [InlineData("1.1.0", VersionBump.Major, 2, 0, 0, "", "")]
18 | [InlineData("1.1.0", VersionBump.PreMajor, 2, 0, 0, "next.0", "")]
19 | [InlineData("4.1.3", VersionBump.Minor, 4, 2, 0, "", "")]
20 | [InlineData("4.1.3", VersionBump.PreMinor, 4, 2, 0, "next.0", "")]
21 | [InlineData("2.1.0", VersionBump.Patch, 2, 1, 1, "", "")]
22 | [InlineData("2.1.0", VersionBump.PrePatch, 2, 1, 1, "next.0", "")]
23 | [InlineData("3.2.1", VersionBump.Specific, 3, 2, 1, "", "")]
24 | [InlineData("3.2.1-0+master", VersionBump.Specific, 3, 2, 1, "0", "master")]
25 | [InlineData("6.0.2-1-g609a472", VersionBump.Specific, 6, 0, 2, "1-g609a472", "")]
26 | public void CanBumpVersions(
27 | string version,
28 | VersionBump bump,
29 | int expectedMajor,
30 | int expectedMinor,
31 | int expectedPatch,
32 | string expectedPreRelease,
33 | string expectedBuildMeta
34 | )
35 | {
36 | var semver = _bumper.Bump(
37 | SemVer.FromString(version),
38 | bump,
39 | version
40 | );
41 |
42 | Assert.Equal(expectedMajor, semver.Major);
43 | Assert.Equal(expectedMinor, semver.Minor);
44 | Assert.Equal(expectedPatch, semver.Patch);
45 | Assert.Equal(expectedPreRelease, semver.PreRelease);
46 | Assert.Equal(expectedBuildMeta, semver.BuildMeta);
47 | }
48 |
49 | [Theory]
50 | [InlineData("1.0.0", VersionBump.Major, "2.0.0")]
51 | [InlineData("1.0.0", VersionBump.PreMajor, "2.0.0-next.0")]
52 | [InlineData("4.1.3", VersionBump.Minor, "4.2.0")]
53 | [InlineData("4.1.3", VersionBump.PreMinor, "4.2.0-next.0")]
54 | [InlineData("2.1.0", VersionBump.Patch, "2.1.1")]
55 | [InlineData("2.1.0", VersionBump.PrePatch, "2.1.1-next.0")]
56 | // snap out of pre-release mode
57 | [InlineData("2.0.0-next.2", VersionBump.Major, "2.0.0")]
58 | [InlineData("1.1.1-42", VersionBump.Patch, "1.1.1")]
59 | [InlineData("1.1.1-42+master", VersionBump.Patch, "1.1.1")]
60 | [InlineData("4.1.3-next.1", VersionBump.Minor, "4.1.3")]
61 |
62 | // increment prerelease number
63 | [InlineData("1.1.1-42", VersionBump.PreRelease, "1.1.1-next.43")]
64 | [InlineData("1.1.1-next.42", VersionBump.PreRelease, "1.1.1-next.43")]
65 | public void CanBumpAndSerializeStringVersion(string version, VersionBump bump, string expectedVersion)
66 | {
67 | var semver = _bumper.Bump(SemVer.FromString(version), bump);
68 | Assert.Equal(expectedVersion, semver.ToSemVerVersionString(null));
69 | }
70 |
71 | [Theory]
72 | [InlineData("1.0.0", VersionBump.PreMajor, "2.0.0-alpha.0", "alpha")]
73 | [InlineData("1.0.0", VersionBump.PreMinor, "1.1.0-beta.0", "beta")]
74 | [InlineData("1.0.0", VersionBump.PrePatch, "1.0.1-pre.0", "pre")]
75 | public void Respects_custom_pre_release_prefix(
76 | string version,
77 | VersionBump bump,
78 | string expectedVersion,
79 | string prefix
80 | )
81 | {
82 | var semver = _bumper.Bump(SemVer.FromString(version), bump, preReleasePrefix: prefix);
83 | Assert.Equal(expectedVersion, semver.ToSemVerVersionString(null));
84 | }
85 |
86 | [Fact]
87 | public void Bails_when_bump_is_not_supported()
88 | {
89 | var ex = Record.Exception(() => _bumper.Bump(SemVer.FromString("1.0.0"), VersionBump.Unknown));
90 | var aex = Assert.IsAssignableFrom(ex);
91 | Assert.Contains(
92 | "VersionBump : Unknown not supported",
93 | aex.Message
94 | );
95 | }
96 |
97 | [Fact]
98 | public void Bails_when_specific_version_empty()
99 | {
100 | var ex = Record.Exception(() => _bumper.Bump(SemVer.FromString("1.0.0"), VersionBump.Specific, ""));
101 | Assert.IsAssignableFrom(ex);
102 | }
103 |
104 | [Fact]
105 | public void Complains_about_prerelease_bump_if_not_already_pre()
106 | {
107 | var semver = SemVer.FromString("2.0.0");
108 | var ex = Record.Exception((() => _bumper.Bump(semver, VersionBump.PreRelease)));
109 |
110 | var iex = Assert.IsAssignableFrom(ex);
111 | Assert.Contains("Cannot Prerelease bump when not", iex.Message);
112 | }
113 |
114 | [Theory]
115 | [InlineData("1.0.0-alpha-1")]
116 | [InlineData("1.0.0-alpha-notANumber")]
117 | [InlineData("1.0.0-alpha.notANumber")]
118 | public void Bails_when_prerelease_label_is_messy(string version)
119 | {
120 | var semver = SemVer.FromString(version);
121 |
122 | var ex = Record.Exception((() => _bumper.Bump(semver, VersionBump.PreRelease)));
123 | var aex = Assert.IsAssignableFrom(ex);
124 | Assert.Contains("`label.number`", aex.Message);
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/test/Versioning/SemVerTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Skarp.Version.Cli.Versioning;
3 | using Xunit;
4 |
5 | namespace Skarp.Version.Cli.Test.Versioning
6 | {
7 | public class SemVerTest
8 | {
9 | [Theory]
10 | // valid cases - Semver 2.0 is crazy!
11 | [InlineData("1.0.0", 1, 0, 0, "", "", true)]
12 | [InlineData("0.10.0", 0, 10, 0, "", "", true)]
13 | [InlineData("1.12.99", 1, 12, 99, "", "", true)]
14 | [InlineData("4.99.43245", 4, 99, 43245, "", "", true)]
15 | [InlineData("4.1.3", 4, 1, 3, "", "", true)]
16 | [InlineData("42.4.554", 42, 4, 554, "", "", true)]
17 | [InlineData("3.1.554-alpha.33", 3, 1, 554, "alpha.33", "", true)]
18 | [InlineData("3.1.554-alpha.33+master", 3, 1, 554, "alpha.33", "master", true)]
19 | public void CanParseValidSemVers(
20 | string version,
21 | int expectedMajor,
22 | int expectedMinor,
23 | int expectedPatch,
24 | string expectedPreReleaseBuildInfo,
25 | string expectedBuildMeta,
26 | bool isValid
27 | )
28 | {
29 | if (!isValid)
30 | {
31 | var ex = Record.Exception(() => SemVer.FromString(version));
32 | Assert.IsAssignableFrom(ex);
33 |
34 | return;
35 | }
36 |
37 | var semver = SemVer.FromString(version);
38 |
39 | Assert.Equal(expectedMajor, semver.Major);
40 | Assert.Equal(expectedMinor, semver.Minor);
41 | Assert.Equal(expectedPatch, semver.Patch);
42 | Assert.Equal(expectedPreReleaseBuildInfo, semver.PreRelease);
43 | Assert.Equal(expectedBuildMeta, semver.BuildMeta);
44 |
45 | if (!string.IsNullOrWhiteSpace(semver.PreRelease))
46 | {
47 | Assert.True(semver.IsPreRelease);
48 | }
49 | }
50 |
51 | [Theory]
52 | [InlineData("is-this-a-version")]
53 | [InlineData("1,0")]
54 | [InlineData("2")]
55 | [InlineData("2.0")]
56 | [InlineData("2.0.1.2.3.4")]
57 | public void BailsOnInvalidSemVers(string version)
58 | {
59 | var ex = Assert.Throws(() => SemVer.FromString(version));
60 | Assert.Equal("versionString", ex.ParamName);
61 | Assert.Contains($"Invalid SemVer version string: {version}", ex.Message);
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/test/dotnet-version-test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0;net7.0;net8.0
4 | Skarp.Version.Cli.Test
5 | fb420acf-9e12-42b6-b724-1eee9cbf251e
6 |
7 |
8 |
9 | runtime; build; native; contentfiles; analyzers; buildtransitive
10 | all
11 |
12 |
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 | all
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------