├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── dotnet.yml │ └── merge.yml ├── .gitignore ├── DotNet.Property.sln ├── LICENSE ├── README.md ├── coverlet.runsettings ├── logo.png ├── src ├── Directory.Build.props └── DotNet.Property │ ├── DotNet.Property.csproj │ ├── Extensions.cs │ ├── Program.cs │ ├── ProjectUpdater.cs │ └── Properties │ └── launchSettings.json └── test └── DotNet.Property.Tests ├── DotNet.Property.Tests.csproj ├── ProjectUpdaterTest.cs └── samples ├── NestedGroup.xml └── SampleLibrary.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | # All Files 6 | [*] 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | # XML Configuration Files 14 | [*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct,refactorlog,runsettings}] 15 | indent_size = 2 16 | 17 | # JSON Files 18 | [*.{json,json5,webmanifest}] 19 | indent_size = 2 20 | 21 | # Project Files 22 | [*.{csproj,sqlproj}] 23 | indent_size = 2 24 | 25 | # YAML Files 26 | [*.{yml,yaml}] 27 | indent_size = 2 28 | 29 | # Markdown Files 30 | [*.md] 31 | trim_trailing_whitespace = false 32 | 33 | # Web Files 34 | [*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,pcss,svg,vue}] 35 | indent_size = 2 36 | 37 | # Batch Files 38 | [*.{cmd,bat}] 39 | end_of_line = crlf 40 | 41 | # Bash Files 42 | [*.sh] 43 | end_of_line = lf 44 | 45 | [*.{cs,vb}] 46 | dotnet_sort_system_directives_first = true 47 | dotnet_separate_import_directive_groups = true 48 | dotnet_style_namespace_match_folder = true 49 | 50 | [*.cs] 51 | csharp_using_directive_placement = outside_namespace 52 | csharp_style_namespace_declarations = file_scoped:warning 53 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: loresoft 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "01:00" 8 | timezone: "America/Chicago" 9 | open-pull-requests-limit: 10 10 | 11 | - package-ecosystem: nuget 12 | directory: "/" 13 | schedule: 14 | interval: daily 15 | time: "02:00" 16 | timezone: "America/Chicago" 17 | open-pull-requests-limit: 10 18 | groups: 19 | Azure: 20 | patterns: 21 | - "Azure.*" 22 | - "Microsoft.Azure.*" 23 | - "Microsoft.Extensions.Azure" 24 | AspNetCoreHealthChecks: 25 | patterns: 26 | - "AspNetCore.HealthChecks.*" 27 | AspNetCore: 28 | patterns: 29 | - "Microsoft.AspNetCore.*" 30 | - "Microsoft.Extensions.Features" 31 | MicrosoftExtensions: 32 | patterns: 33 | - "Microsoft.Extensions.*" 34 | EntityFrameworkCore: 35 | patterns: 36 | - "Microsoft.EntityFrameworkCore.*" 37 | OpenTelemetry: 38 | patterns: 39 | - "OpenTelemetry.*" 40 | Serilog: 41 | patterns: 42 | - "Serilog" 43 | - "Serilog.*" 44 | Hangfire: 45 | patterns: 46 | - "Hangfire" 47 | - "Hangfire.*" 48 | Testcontainers: 49 | patterns: 50 | - "Testcontainers.*" 51 | xUnit: 52 | patterns: 53 | - "xunit" 54 | - "xunit.assert" 55 | - "xunit.core" 56 | - "xunit.extensibility.*" 57 | - "xunit.runner.*" 58 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | env: 4 | DOTNET_NOLOGO: true 5 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 6 | DOTNET_ENVIRONMENT: github 7 | ASPNETCORE_ENVIRONMENT: github 8 | BUILD_PATH: "${{github.workspace}}/artifacts" 9 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 10 | 11 | on: 12 | push: 13 | branches: 14 | - master 15 | - develop 16 | tags: 17 | - "v*" 18 | pull_request: 19 | branches: 20 | - master 21 | - develop 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 0 32 | 33 | - name: Install .NET 34 | uses: actions/setup-dotnet@v4 35 | with: 36 | dotnet-version: 9.x 37 | 38 | - name: Restore Dependencies 39 | run: dotnet restore 40 | 41 | - name: Build Solution 42 | run: dotnet build --no-restore --configuration Release 43 | 44 | - name: Run Test 45 | run: dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --settings coverlet.runsettings 46 | 47 | - name: Report Coverage 48 | if: success() 49 | uses: coverallsapp/github-action@v2 50 | with: 51 | file: "${{github.workspace}}/test/*/TestResults/*/coverage.info" 52 | format: lcov 53 | 54 | - name: Create Packages 55 | if: success() && github.event_name != 'pull_request' 56 | run: dotnet pack --configuration Release --no-build --output "${{env.BUILD_PATH}}" 57 | 58 | - name: Upload Packages 59 | if: success() && github.event_name != 'pull_request' 60 | uses: actions/upload-artifact@v4 61 | with: 62 | name: packages 63 | path: "${{env.BUILD_PATH}}" 64 | 65 | deploy: 66 | runs-on: ubuntu-latest 67 | needs: build 68 | if: success() && github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) 69 | 70 | steps: 71 | - name: Download Artifact 72 | uses: actions/download-artifact@v4 73 | with: 74 | name: packages 75 | 76 | - name: Publish Packages GitHub 77 | run: | 78 | for package in $(find -name "*.nupkg"); do 79 | echo "${0##*/}": Pushing $package... 80 | dotnet nuget push $package --source https://nuget.pkg.github.com/loresoft/index.json --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate 81 | done 82 | 83 | - name: Publish Packages feedz 84 | run: | 85 | for package in $(find -name "*.nupkg"); do 86 | echo "${0##*/}": Pushing $package... 87 | dotnet nuget push $package --source https://f.feedz.io/loresoft/open/nuget/index.json --api-key ${{ secrets.FEEDDZ_KEY }} --skip-duplicate 88 | done 89 | 90 | - name: Publish Packages Nuget 91 | if: startsWith(github.ref, 'refs/tags/v') 92 | run: | 93 | for package in $(find -name "*.nupkg"); do 94 | echo "${0##*/}": Pushing $package... 95 | dotnet nuget push $package --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_KEY }} --skip-duplicate 96 | done 97 | -------------------------------------------------------------------------------- /.github/workflows/merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot Auto-Merge 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: github.actor == 'dependabot[bot]' 12 | 13 | steps: 14 | - name: Dependabot Metadata 15 | id: metadata 16 | uses: dependabot/fetch-metadata@v2 17 | with: 18 | github-token: "${{ secrets.GITHUB_TOKEN }}" 19 | 20 | - name: Dependabot Auto-Merge PRs 21 | run: gh pr merge --auto --merge "$PR_URL" 22 | env: 23 | PR_URL: ${{github.event.pull_request.html_url}} 24 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | 238 | # Custom 239 | /artifacts 240 | /Tools 241 | /*.txt 242 | /xunit.xml 243 | *.GhostDoc.xml 244 | *.csv 245 | coverage.xml 246 | -------------------------------------------------------------------------------- /DotNet.Property.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33829.357 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNet.Property", "src\DotNet.Property\DotNet.Property.csproj", "{4BD565C9-B0BA-4558-A40D-67CE8975CFFB}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNet.Property.Tests", "test\DotNet.Property.Tests\DotNet.Property.Tests.csproj", "{F3F32D10-ED34-4430-9E02-2C499BDB17CF}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{F6E81BF8-B782-4E0F-98D5-E811BAC65682}" 11 | ProjectSection(SolutionItems) = preProject 12 | coverlet.runsettings = coverlet.runsettings 13 | src\Directory.Build.props = src\Directory.Build.props 14 | .github\workflows\dotnet.yml = .github\workflows\dotnet.yml 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {4BD565C9-B0BA-4558-A40D-67CE8975CFFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {4BD565C9-B0BA-4558-A40D-67CE8975CFFB}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {4BD565C9-B0BA-4558-A40D-67CE8975CFFB}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {4BD565C9-B0BA-4558-A40D-67CE8975CFFB}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {F3F32D10-ED34-4430-9E02-2C499BDB17CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {F3F32D10-ED34-4430-9E02-2C499BDB17CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {F3F32D10-ED34-4430-9E02-2C499BDB17CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {F3F32D10-ED34-4430-9E02-2C499BDB17CF}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {7CDCEC6E-6473-47BB-9AFA-841595736521} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 LoreSoft 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 | # DotNet.Property 2 | 3 | .NET Core command-line (CLI) tool to update project properties and version numbers on build. 4 | 5 | [![Build Project](https://github.com/loresoft/DotNet.Property/actions/workflows/dotnet.yml/badge.svg)](https://github.com/loresoft/DotNet.Property/actions/workflows/dotnet.yml) 6 | 7 | [![Coverage Status](https://coveralls.io/repos/github/loresoft/DotNet.Property/badge.svg)](https://coveralls.io/github/loresoft/DotNet.Property) 8 | 9 | [![NuGet Version](https://img.shields.io/nuget/v/dotnet-property.svg?style=flat-square)](https://www.nuget.org/packages/dotnet-property/) 10 | 11 | ## Usage 12 | 13 | Install the global tool 14 | 15 | dotnet tool install -g dotnet-property 16 | 17 | Update project property 18 | 19 | dotnet property 20 | 21 | | Argument | Description | 22 | | ------------- | ------------- | 23 | | `projectGlob` | Path glob expression of projects to update. Must be first argument. | 24 | | `property:value` | Project property name value pair. Can have multiple properties to update. | 25 | 26 | ### Examples 27 | 28 | Set the version number in a shared props file 29 | 30 | dotnet property "**/version.props" Version:"1.0.0.3" 31 | 32 | Update project version and copyright properties 33 | 34 | dotnet property "**/Project.csproj" Version:"1.0.0.3" Copyright:"Copyright 2018 LoreSoft" 35 | -------------------------------------------------------------------------------- /coverlet.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | lcov 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loresoft/DotNet.Property/9af34b351eaf6542dee25489403c50214f8955bc/logo.png -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dotnet-property 5 | dotnet-property 6 | .NET Core command-line (CLI) tool to update project properties and version numbers on build. 7 | Copyright © $([System.DateTime]::Now.ToString(yyyy)) LoreSoft 8 | LoreSoft 9 | en-US 10 | true 11 | dotnet; cli; build; version; properties 12 | https://github.com/loresoft/DotNet.Property 13 | MIT 14 | logo.png 15 | README.md 16 | git 17 | https://github.com/loresoft/DotNet.Property 18 | true 19 | 20 | 21 | 22 | embedded 23 | true 24 | false 25 | 26 | 27 | 28 | true 29 | 30 | 31 | 32 | en-US 33 | latest 34 | enable 35 | 1591 36 | 37 | 38 | 39 | v 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | true 49 | \ 50 | false 51 | 52 | 53 | true 54 | \ 55 | false 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/DotNet.Property/DotNet.Property.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | dotnet-property 7 | DotNet.Property 8 | dotnet-property 9 | dotnet-property 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/DotNet.Property/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace DotNet.Property; 4 | 5 | /// 6 | /// Extension methods 7 | /// 8 | public static class Extensions 9 | { 10 | /// 11 | /// Gets the or create an . 12 | /// 13 | /// The container. 14 | /// The name of the element. 15 | /// 16 | /// or is 17 | public static XElement GetOrCreateElement(this XContainer container, string name) 18 | { 19 | ArgumentNullException.ThrowIfNull(container); 20 | ArgumentNullException.ThrowIfNull(name); 21 | 22 | var element = container.Element(name); 23 | if (element != null) 24 | return element; 25 | 26 | element = new XElement(name); 27 | container.Add(element); 28 | 29 | return element; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/DotNet.Property/Program.cs: -------------------------------------------------------------------------------- 1 | namespace DotNet.Property; 2 | 3 | public static class Program 4 | { 5 | static int Main(string[] args) 6 | { 7 | if (args.Length < 2) 8 | { 9 | OutputVersion(); 10 | OutputUsage(); 11 | 12 | return 1; 13 | } 14 | 15 | try 16 | { 17 | var updater = new ProjectUpdater(); 18 | updater.Update(args, Environment.CurrentDirectory); 19 | } 20 | catch (Exception ex) 21 | { 22 | Console.Error.WriteLine(ex.ToString()); 23 | return 1; 24 | } 25 | 26 | return 0; 27 | } 28 | 29 | private static void OutputVersion() 30 | { 31 | Console.WriteLine(".NET Core command-line (CLI) tool to update project properties"); 32 | } 33 | 34 | private static void OutputUsage() 35 | { 36 | Console.WriteLine(); 37 | Console.WriteLine("Usage: dotnet property :"); 38 | Console.WriteLine(); 39 | Console.WriteLine("Example:"); 40 | Console.WriteLine(" dotnet property \"**/version.props\" Version:\"1.0.0.3\" Copyright:\"Copyright 2018 LoreSoft\""); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/DotNet.Property/ProjectUpdater.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | using System.Xml.Linq; 3 | 4 | using Microsoft.Extensions.FileSystemGlobbing; 5 | using Microsoft.Extensions.FileSystemGlobbing.Abstractions; 6 | 7 | namespace DotNet.Property; 8 | 9 | /// 10 | /// Update .net core project properties 11 | /// 12 | public class ProjectUpdater 13 | { 14 | 15 | /// 16 | /// Gets or sets the log writer delegate. 17 | /// 18 | /// 19 | /// The log writer delegate. 20 | /// 21 | public Action Logger { get; set; } = Console.WriteLine; 22 | 23 | /// 24 | /// Gets or sets the properties to update. 25 | /// 26 | /// 27 | /// The properties to update. 28 | /// 29 | public Dictionary Properties { get; set; } = []; 30 | 31 | /// 32 | /// Gets or sets the projects to update as a file glob expression. 33 | /// 34 | /// 35 | /// The projects to update as a file glob expression. 36 | /// 37 | public string Projects { get; set; } = "**/*.props"; 38 | 39 | 40 | /// 41 | /// Updates .net core projects using the specified command line arguments. The first argument is the project list glob expression. 42 | /// 43 | /// The arguments used to update project with. 44 | /// The root directory to search for projects. 45 | public void Update(string[] args, string rootDirectory = null) 46 | { 47 | Projects = args[0]; 48 | Properties = ParseArguments(args, 1); 49 | 50 | WriteLog($"Matching projects: {Projects}"); 51 | var matcher = new Matcher(StringComparison.OrdinalIgnoreCase); 52 | matcher.AddInclude(Projects); 53 | 54 | if (string.IsNullOrEmpty(rootDirectory)) 55 | rootDirectory = Environment.CurrentDirectory; 56 | 57 | var currentDirectory = new DirectoryInfo(rootDirectory); 58 | var startingDirectory = new DirectoryInfoWrapper(currentDirectory); 59 | var matchingResult = matcher.Execute(startingDirectory); 60 | 61 | if (!matchingResult.HasMatches) 62 | { 63 | WriteLog($"Error: No Projects found: {Projects}"); 64 | return; 65 | } 66 | 67 | foreach (var fileMatch in matchingResult.Files) 68 | { 69 | var filePath = Path.GetFullPath(fileMatch.Path); 70 | try 71 | { 72 | UpdateProject(filePath); 73 | } 74 | catch (Exception ex) 75 | { 76 | Console.Error.WriteLine($"Error occurred processing {filePath} : {ex.Message}"); 77 | } 78 | } 79 | } 80 | 81 | 82 | /// 83 | /// Updates the project at the specified . 84 | /// 85 | /// The file path of the project to update. 86 | /// is 87 | /// is not found. 88 | public void UpdateProject(string filePath) 89 | { 90 | ArgumentNullException.ThrowIfNull(filePath); 91 | 92 | if (!File.Exists(filePath)) 93 | throw new ArgumentException($"File not found: {filePath}", nameof(filePath)); 94 | 95 | WriteLog($"Updating Project: {filePath}"); 96 | 97 | XDocument document; 98 | using (var readStream = File.OpenRead(filePath)) 99 | { 100 | document = XDocument.Load(readStream); 101 | } 102 | 103 | UpdateProject(document); 104 | 105 | // save document 106 | var settings = new XmlWriterSettings 107 | { 108 | OmitXmlDeclaration = true, 109 | Indent = true 110 | }; 111 | 112 | using var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write); 113 | using var writer = XmlWriter.Create(stream, settings); 114 | document.Save(writer); 115 | } 116 | 117 | /// 118 | /// Updates the project as an . 119 | /// 120 | /// The project document. 121 | /// is 122 | /// Missing root Project node. 123 | public void UpdateProject(XDocument document) 124 | { 125 | ArgumentNullException.ThrowIfNull(document); 126 | 127 | if (Properties == null || Properties.Count == 0) 128 | return; 129 | 130 | var projectElement = document.Element("Project"); 131 | if (projectElement == null) 132 | throw new InvalidOperationException("Could not find root Project node. Make sure file has a root Project node without a namespace."); 133 | 134 | foreach (var p in Properties) 135 | { 136 | WriteLog($" Set Property '{p.Key}':'{p.Value}'"); 137 | 138 | // find last group with element and no condition 139 | var projectGroup = projectElement 140 | .Elements("PropertyGroup") 141 | .LastOrDefault(e => 142 | e.Elements(p.Key).Any() && 143 | (e.HasAttributes == false || e.Attributes().All(a => a.Name != "Condition")) 144 | ); 145 | 146 | // use last group without condition 147 | if (projectGroup == null) 148 | projectGroup = projectElement 149 | .Elements("PropertyGroup") 150 | .LastOrDefault(e => e.HasAttributes == false || e.Attributes().All(a => a.Name != "Condition")); 151 | 152 | // create new if not found 153 | if (projectGroup == null) 154 | projectGroup = projectElement 155 | .GetOrCreateElement("PropertyGroup"); 156 | 157 | projectGroup 158 | .GetOrCreateElement(p.Key) 159 | .SetValue(p.Value); 160 | } 161 | } 162 | 163 | 164 | /// 165 | /// Parses the arguments into a dictionary. 166 | /// 167 | /// The arguments. 168 | /// The start index. 169 | /// 170 | /// is 171 | public static Dictionary ParseArguments(string[] args, int startIndex = 0) 172 | { 173 | if (args == null) 174 | throw new ArgumentNullException(nameof(args)); 175 | 176 | var arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); 177 | 178 | // skip first 179 | for (int i = startIndex; i < args.Length; i++) 180 | { 181 | int keyStartIndex = 0; 182 | 183 | if (args[i].StartsWith("--")) 184 | { 185 | keyStartIndex = 2; 186 | } 187 | else if (args[i].StartsWith('-')) 188 | { 189 | keyStartIndex = 1; 190 | } 191 | else if (args[i].StartsWith('/')) 192 | { 193 | keyStartIndex = 1; 194 | } 195 | 196 | int separator = args[i].IndexOfAny([':', '=']); 197 | 198 | if (separator < 0) 199 | { 200 | continue; // an invalid format 201 | } 202 | 203 | var key = args[i][keyStartIndex..separator]; 204 | var value = args[i][(separator + 1)..]; 205 | 206 | arguments[key] = value; 207 | } 208 | 209 | return arguments; 210 | } 211 | 212 | 213 | private void WriteLog(string s) 214 | { 215 | if (Logger == null || string.IsNullOrEmpty(s)) 216 | return; 217 | 218 | Logger.Invoke(s); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/DotNet.Property/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DotNet.Property": { 4 | "commandName": "Project", 5 | "commandLineArgs": "\"**/version.props\" Version:\"1.0.0.3\" AssemblyVersion:\"1.0.0.0\" FileVersion:\"1.0.0.3\" InformationalVersion:\"1.0.0.3\" Copyright:\"Copyright 2018 LoreSoft\"" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /test/DotNet.Property.Tests/DotNet.Property.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | 7 | 8 | 9 | 10 | all 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Always 29 | 30 | 31 | Always 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/DotNet.Property.Tests/ProjectUpdaterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Xml.Linq; 5 | using FluentAssertions; 6 | using Xunit; 7 | using Xunit.Abstractions; 8 | 9 | namespace DotNet.Property.Tests; 10 | 11 | public class ProjectUpdaterTest 12 | { 13 | public ProjectUpdaterTest(ITestOutputHelper output) 14 | { 15 | Output = output; 16 | } 17 | 18 | public ITestOutputHelper Output { get; } 19 | 20 | [Fact] 21 | public void ParseArguments() 22 | { 23 | var args = ProjectUpdater.ParseArguments(new[] 24 | { 25 | "Version:1.0.0.3", 26 | "Copyright:Copyright 2018 LoreSoft" 27 | }, 0); 28 | 29 | args.Should().NotBeNull(); 30 | args.Count.Should().Be(2); 31 | args["Version"].Should().Be("1.0.0.3"); 32 | args["Copyright"].Should().Be("Copyright 2018 LoreSoft"); 33 | } 34 | 35 | [Fact] 36 | public void ParseArgumentsEquals() 37 | { 38 | var args = ProjectUpdater.ParseArguments(new[] 39 | { 40 | "Version=1.0.0.3", 41 | "Copyright=Copyright 2018 LoreSoft" 42 | }, 0); 43 | 44 | args.Should().NotBeNull(); 45 | args.Count.Should().Be(2); 46 | args["Version"].Should().Be("1.0.0.3"); 47 | args["Copyright"].Should().Be("Copyright 2018 LoreSoft"); 48 | } 49 | 50 | [Fact] 51 | public void ParseArgumentsUrl() 52 | { 53 | var args = ProjectUpdater.ParseArguments(new[] 54 | { 55 | "Version=1.0.0.3", 56 | "PackageProjectUrl=https://test.com", 57 | "SourceProjectUrl=https://other.com" 58 | }, 0); 59 | 60 | args.Should().NotBeNull(); 61 | args.Count.Should().Be(3); 62 | args["Version"].Should().Be("1.0.0.3"); 63 | args["PackageProjectUrl"].Should().Be("https://test.com"); 64 | args["SourceProjectUrl"].Should().Be("https://other.com"); 65 | } 66 | 67 | [Fact] 68 | public void UpdateProject() 69 | { 70 | var properties = new Dictionary 71 | { 72 | { "Version", "2.0.0.0" }, 73 | { "Copyright", "Copyright 2018 LoreSoft" } 74 | }; 75 | 76 | var xml = "1.0.0.0"; 77 | var document = XDocument.Parse(xml); 78 | 79 | 80 | var updater = new ProjectUpdater(); 81 | updater.Properties = properties; 82 | updater.Logger = Output.WriteLine; 83 | 84 | updater.UpdateProject(document); 85 | 86 | var result = document.ToString(SaveOptions.DisableFormatting); 87 | 88 | var expected = "2.0.0.0Copyright 2018 LoreSoft"; 89 | result.Should().Be(expected); 90 | } 91 | 92 | [Fact] 93 | public void UpdateProjectMissingGroup() 94 | { 95 | var properties = new Dictionary 96 | { 97 | { "Version", "2.0.0.0" }, 98 | { "Copyright", "Copyright 2018 LoreSoft" } 99 | }; 100 | 101 | var xml = ""; 102 | var document = XDocument.Parse(xml); 103 | 104 | 105 | var updater = new ProjectUpdater(); 106 | updater.Properties = properties; 107 | updater.Logger = Output.WriteLine; 108 | 109 | updater.UpdateProject(document); 110 | 111 | var result = document.ToString(SaveOptions.DisableFormatting); 112 | 113 | var expected = "2.0.0.0Copyright 2018 LoreSoft"; 114 | result.Should().Be(expected); 115 | } 116 | 117 | [Fact] 118 | public void UpdateProjectComplex() 119 | { 120 | var properties = new Dictionary 121 | { 122 | { "Version", "2.0.0.0" }, 123 | { "Description", "Nested Version" } 124 | }; 125 | 126 | var xml = "net45;netstandard2.0Testdotnet-propertyTest (.NET Core 2.0) "; 127 | var document = XDocument.Parse(xml); 128 | 129 | 130 | var updater = new ProjectUpdater(); 131 | updater.Properties = properties; 132 | updater.Logger = Output.WriteLine; 133 | 134 | updater.UpdateProject(document); 135 | 136 | var result = document.ToString(SaveOptions.DisableFormatting); 137 | 138 | var expected = "net45;netstandard2.0Nested Versiondotnet-property2.0.0.0Test (.NET Core 2.0) "; 139 | result.Should().Be(expected); 140 | } 141 | 142 | 143 | [Fact] 144 | public void UpdateProjectSampleLibrary() 145 | { 146 | var properties = new Dictionary 147 | { 148 | { "Version", "2.0.0.0" }, 149 | { "Copyright", "Copyright 2018 LoreSoft" } 150 | }; 151 | 152 | var projectPath = Path.Combine(Environment.CurrentDirectory, "samples", "SampleLibrary.xml"); 153 | 154 | var updater = new ProjectUpdater(); 155 | updater.Properties = properties; 156 | updater.Logger = Output.WriteLine; 157 | 158 | updater.UpdateProject(projectPath); 159 | } 160 | 161 | 162 | [Fact] 163 | public void UpdateProjectNestedGroup() 164 | { 165 | var properties = new Dictionary 166 | { 167 | { "Version", "2.0.0.0" }, 168 | { "Description", "Nested Version" } 169 | }; 170 | 171 | var projectPath = Path.Combine(Environment.CurrentDirectory, "samples", "NestedGroup.xml"); 172 | 173 | var updater = new ProjectUpdater(); 174 | updater.Properties = properties; 175 | updater.Logger = Output.WriteLine; 176 | 177 | updater.UpdateProject(projectPath); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /test/DotNet.Property.Tests/samples/NestedGroup.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | net45;netstandard2.0 4 | 5 | 6 | Test 7 | 8 | 9 | dotnet-property 10 | 11 | 12 | Test (.NET Core 2.0) 13 | 14 | -------------------------------------------------------------------------------- /test/DotNet.Property.Tests/samples/SampleLibrary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | --------------------------------------------------------------------------------