├── .github
└── FUNDING.yml
├── .gitignore
├── CHANGELOG.md
├── README.md
├── demodata
├── 0.1.2.3.dll
├── 0.1.2.3.exe
├── 0.1.2.4.exe
├── 0.1.2.5.exe
├── SettingsExample.json
├── example1.zip
└── example2.zip
├── img
├── build_secrets.png
├── build_smart-command-line-arguments.png
└── flow.png
└── src
├── .idea
└── .idea.GitHubReleaser
│ └── .idea
│ ├── .gitignore
│ ├── encodings.xml
│ ├── indexLayout.xml
│ └── vcs.xml
├── GitHubReleaser.sln
└── GitHubReleaser
├── GitHubReleaser.args.json
├── GitHubReleaser.csproj
├── Model
├── ChangelogManager.cs
├── CommandLineParameters.cs
├── ErrorHandler.cs
├── IssueWithLabel.cs
├── ReleaseManager.cs
├── Releaser.cs
└── ReleaserSettings.cs
├── Program.cs
└── Properties
└── launchSettings.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [Suplanus]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 | /src/GitHubReleaser/Model/Secrets.cs
352 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [1.0.1](https://github.com/Suplanus/GitHubReleaser/releases/tag/1.0.1.103)
2 |
3 | `Build: 103 | Date (UTC): 2020-11-17 10:29`
4 |
5 | #### Bugs:
6 | - [Timeout on large assets](https://github.com/Suplanus/GitHubReleaser/issues/12)
7 |
8 | ## [1.0.0](https://github.com/Suplanus/GitHubReleaser/releases/tag/1.0.0.0)
9 |
10 | `Build: 0 | Date (UTC): 2020-11-06 11:24`
11 |
12 | #### Enhancements:
13 | - [Changelog: Create](https://github.com/Suplanus/GitHubReleaser/issues/10)
14 | - [Documentation](https://github.com/Suplanus/GitHubReleaser/issues/11)
15 | - [Release: Create](https://github.com/Suplanus/GitHubReleaser/issues/8)
16 | - [Release: Update](https://github.com/Suplanus/GitHubReleaser/issues/9)
17 |
18 | ## [0.1.2](https://github.com/Suplanus/GitHubReleaser/releases/tag/0.1.2.5)
19 |
20 | `Build: 5 | Date (UTC): 2020-11-06 09:33`
21 |
22 | #### Bugs:
23 | - [Test1](https://github.com/Suplanus/GitHubReleaser/issues/2)
24 |
25 | #### Enhancements:
26 | - [Test2](https://github.com/Suplanus/GitHubReleaser/issues/3)
27 |
28 | ## [0.1.2](https://github.com/Suplanus/GitHubReleaser/releases/tag/0.1.2.4)
29 |
30 | `Build: 4 | Date (UTC): 2020-11-05 13:27 | Pre-release`
31 |
32 |
33 | #### Bug:
34 | - [Test1](https://github.com/Suplanus/GitHubReleaser/issues/2)
35 |
36 | ## [0.1.2](https://github.com/Suplanus/GitHubReleaser/releases/tag/0.1.2.3)
37 |
38 | `Build: 3 | Date (UTC): 2020-11-05 13:27 | Pre-release`
39 |
40 |
41 | #### Bug:
42 | - [Test1](https://github.com/Suplanus/GitHubReleaser/issues/2)
43 | - [Test4](https://github.com/Suplanus/GitHubReleaser/issues/5)
44 |
45 | #### Enhancement:
46 | - [Test2](https://github.com/Suplanus/GitHubReleaser/issues/3)
47 |
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GitHubReleaser
2 | Simple way to automatically create GitHub releases with changelog and attachments out of issues & milestones.
3 |
4 | 
5 |
6 | ## Functions
7 | Creates a release with the version of a DLL or EXE file, e.g. `1.2.3.4`. The last version part is the build number.
8 |
9 | GitHubReleaser searches for a milestone without the build number e.g. `1.2.3`. All issues in the milestone would be printed in the release changelog. You can also define which label should be in or filter some single issues out.
10 |
11 | You can also attach files to the release e.g. ZIP-File with your binaries.
12 | If the release is a pre-release, all closed issues of the milestone are collected since last release (date). If the release is __NOT__ a pre-release, all issues of the milestone are collected (summary).
13 |
14 | You can also update a release (changelog & attachments).
15 |
16 | ## Usage
17 | Get a API-Token for your GitHub-User [here](https://github.com/settings/tokens). We need the scope `repo`.
18 |
19 | Execute GitHubRelease via command line arguments:
20 | ```shell
21 | GitHubReleaser.exe /github-repo:"MyUsername/MyRepo" /github-token:"5578d3a8e0ed1c524e3c8c832e1533d79b16ad3c" /file-for-version:"C:\test\MyDllOrExe.dll" /pre-release:"false" /issue-labels:"bug;Bug" "feature;Feature" /issue-filter-label:"not-in-changelog" /release-attachments:"C:\test\MyDllOrExe1.zip" "C:\test\MyDllOrExe2.zip" /create-changelog-file:"false"
22 | ```
23 |
24 | Or open config file with GitHubRelease:
25 | ```shell
26 | GitHubReleaser.exe "C:\test\MySettings.json"
27 | ```
28 |
29 | Combining config file with commandline arguments is not supported yet.
30 |
31 | ## Parameter
32 |
33 | | Name | Example | Required |
34 | | --------------------------- | ----------------------------------------------------- | -------- |
35 | | `github-repo` | _MyUsername/MyRepo_ | ✅ |
36 | | `github-token` | _5578d3a8e0ed1c524e3c8c832e1533d79b16ad3c_ | ✅ |
37 | | `file-for-version` | "C:\test\MyDllOrExe.dll" | ✅ |
38 | | `pre-release` | _false_ | |
39 | | `issue-labels` | _"bug;Bug" "feature;Feature"_ | |
40 | | `issue-filter-label` | not-in-changelog | |
41 | | `release-attachments` | _"C:\test\MyDllOrExe1.zip" "C:\test\MyDllOrExe2.zip"_ | |
42 | | `update-only` | _false_ | |
43 | | `create-changelog-file` | _false_ | |
44 | | `draft` | _false_ | |
45 | | `delete-files-after-upload` | _false_ | |
46 |
47 |
48 | ## Config file example
49 |
50 | ```json
51 | {
52 | "IsChangelogFileCreationEnabled": false,
53 | "IsUpdateOnly": true,
54 | "ReleaseAttachments": [ "C:\\test\\MyDllOrExe1.zip", "C:\\test\\MyDllOrExe2.zip" ],
55 | "IssueFilterLabel": "not-in-changelog",
56 | "IssueLabels": {
57 | "bug": "Bug",
58 | "feature": "Feature"
59 | },
60 | "IsPreRelease": false,
61 | "IsDraft": false,
62 | "DeleteFilesAfterUpload": false,
63 | "FileForVersion": "C:\\test\\MyDllOrExe.dll",
64 | "GitHubToken": "_5578d3a8e0ed1c524e3c8c832e1533d79b16ad3c_",
65 | "GitHubRepo": "MyUsername/MyRepo"
66 | }
67 | ```
68 |
69 | ## Build
70 | For debugging you can add a Secrets.cs to the folder Model:
71 | 
72 |
73 | For testing we use [Smart Command Line Arguments](https://marketplace.visualstudio.com/items?itemName=MBulli.SmartCommandlineArguments) Visual Studio extension.
74 | With a test milestone and some test issues you can simple run throw the steps:
75 |
76 | 
77 |
78 | ## Contributing
79 | Feel free to make a pull-request 🦄
80 | Or simple make an issue for a bug report or feature request 💖
81 |
82 | Thanks to [Daniel Papp](https://github.com/DanielPa) for being a first class contributer.
83 |
--------------------------------------------------------------------------------
/demodata/0.1.2.3.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/demodata/0.1.2.3.dll
--------------------------------------------------------------------------------
/demodata/0.1.2.3.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/demodata/0.1.2.3.exe
--------------------------------------------------------------------------------
/demodata/0.1.2.4.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/demodata/0.1.2.4.exe
--------------------------------------------------------------------------------
/demodata/0.1.2.5.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/demodata/0.1.2.5.exe
--------------------------------------------------------------------------------
/demodata/SettingsExample.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsChangelogFileCreationEnabled": false,
3 | "IsUpdateOnly": true,
4 | "ReleaseAttachments": [ ".\\..\\..\\..\\..\\..\\demodata\\example1.zip" ],
5 | "IssueFilterLabel": "not-in-changelog",
6 | "IssueLabels": {
7 | "bug": "Bug",
8 | "feature": "Feature",
9 | "enhancement": "Enhancements"
10 | },
11 | "IsPreRelease": false,
12 | "IsDraft": false,
13 | "DeleteFilesAfterUpload": false,
14 | "FileForVersion": ".\\..\\..\\..\\..\\..\\demodata\\0.1.2.3.dll",
15 | "GitHubToken": "Fill out Secrets.cs",
16 | "GitHubRepo": "Suplanus/GitHubReleaser"
17 | }
--------------------------------------------------------------------------------
/demodata/example1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/demodata/example1.zip
--------------------------------------------------------------------------------
/demodata/example2.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/demodata/example2.zip
--------------------------------------------------------------------------------
/img/build_secrets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/img/build_secrets.png
--------------------------------------------------------------------------------
/img/build_smart-command-line-arguments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/img/build_smart-command-line-arguments.png
--------------------------------------------------------------------------------
/img/flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Suplanus/GitHubReleaser/077fd3c09b2b65a659255b9062239dd39102a286/img/flow.png
--------------------------------------------------------------------------------
/src/.idea/.idea.GitHubReleaser/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Rider ignored files
5 | /contentModel.xml
6 | /projectSettingsUpdater.xml
7 | /.idea.GitHubReleaser.iml
8 | /modules.xml
9 | # Editor-based HTTP Client requests
10 | /httpRequests/
11 | # Datasource local storage ignored files
12 | /dataSources/
13 | /dataSources.local.xml
14 |
--------------------------------------------------------------------------------
/src/.idea/.idea.GitHubReleaser/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/.idea/.idea.GitHubReleaser/.idea/indexLayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/.idea/.idea.GitHubReleaser/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/GitHubReleaser.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30611.23
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHubReleaser", "GitHubReleaser\GitHubReleaser.csproj", "{EEF80A13-60C4-481F-A254-D281EE5450BE}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Release|x64 = Release|x64
11 | Debug|x64 = Debug|x64
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {EEF80A13-60C4-481F-A254-D281EE5450BE}.Release|x64.ActiveCfg = Release|x64
15 | {EEF80A13-60C4-481F-A254-D281EE5450BE}.Release|x64.Build.0 = Release|x64
16 | {EEF80A13-60C4-481F-A254-D281EE5450BE}.Debug|x64.ActiveCfg = Debug|x64
17 | {EEF80A13-60C4-481F-A254-D281EE5450BE}.Debug|x64.Build.0 = Debug|x64
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {9A21E1C6-E8D4-4FFF-B2C2-3CD296F3BC70}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/src/GitHubReleaser/GitHubReleaser.args.json:
--------------------------------------------------------------------------------
1 | {
2 | "FileVersion": 2,
3 | "Id": "eef80a13-60c4-481f-a254-d281ee5450be",
4 | "Items": [
5 | {
6 | "Id": "f59968cc-e42c-44ac-ba50-06769cca33a0",
7 | "Command": "Test",
8 | "Items": [
9 | {
10 | "Id": "31ff52ad-ffd5-44a3-bd74-affe07f7df21",
11 | "Command": "Common",
12 | "Items": [
13 | {
14 | "Id": "10188794-9d4e-4fbe-b450-49dc500bab4b",
15 | "Command": "/github-repo:\"Suplanus/GitHubReleaser\""
16 | },
17 | {
18 | "Id": "66d2ca42-1828-4f28-a3a5-01717a71f436",
19 | "Command": "/github-token:\"Fill out Secrets.cs\""
20 | },
21 | {
22 | "Id": "adc3a3e9-741f-4a70-8268-48e3eac12f65",
23 | "Command": "/create-changelog-file:\"true\""
24 | },
25 | {
26 | "Id": "ccf9141f-2477-48ea-9f21-57b780e9babe",
27 | "Command": "/issue-filter-label:\"not-in-changelog\""
28 | },
29 | {
30 | "Id": "5c7ffbf0-c886-4e6a-9997-170790271a1b",
31 | "Command": "/issue-labels:\"bug;Bugs\" \"enhancement;Enhancements\""
32 | },
33 | {
34 | "Id": "f34a0e39-bbfd-4636-911e-c57a51506cc6",
35 | "Command": "/draft:\"false\""
36 | },
37 | {
38 | "Id": "ad7d8700-6446-4735-af4f-20705bcdbe3d",
39 | "Command": "/delete-files-after-upload:\"false\""
40 | }
41 | ]
42 | },
43 | {
44 | "Id": "9a8f3dab-5f70-4361-8631-3a5751845afd",
45 | "Command": "Release",
46 | "Items": [
47 | {
48 | "Id": "d6139be2-da9d-4baf-bfab-ad56fe2d9533",
49 | "Command": "/file-for-version:\".\\..\\..\\..\\..\\..\\demodata\\0.1.2.3.dll\""
50 | },
51 | {
52 | "Id": "a6fcaa26-a9e0-4629-9812-7b7aa44041c7",
53 | "Command": "/pre-release:\"false\""
54 | },
55 | {
56 | "Id": "86f9ac55-e067-416a-844c-857969853fbe",
57 | "Command": "Create",
58 | "Items": [
59 | {
60 | "Id": "7a496341-222c-4da0-b4b6-edfd908ec81f",
61 | "Command": "/release-attachments:\".\\..\\..\\..\\..\\..\\demodata\\example1.zip\""
62 | },
63 | {
64 | "Id": "05406f61-0a97-42c4-ad35-bc342df4f2b9",
65 | "Command": "/update-only:\"false\""
66 | }
67 | ]
68 | },
69 | {
70 | "Id": "fca0f85a-d1b2-403a-ac08-444261179b31",
71 | "Command": "Update",
72 | "Items": [
73 | {
74 | "Id": "e904e603-9318-4aad-8dfa-dbda2f60afdd",
75 | "Command": "/release-attachments:\".\\..\\..\\..\\..\\..\\demodata\\example2.zip\""
76 | },
77 | {
78 | "Id": "d10c75fb-30d4-45a5-87d0-28c0da2f8b4b",
79 | "Command": "/update-only:\"true\""
80 | }
81 | ]
82 | }
83 | ]
84 | },
85 | {
86 | "Id": "4f4afcad-bd02-4584-bf6f-f398005d8282",
87 | "Command": "PreRelease",
88 | "Items": [
89 | {
90 | "Id": "88bf43d5-e633-4a01-8785-5eadfdc97aea",
91 | "Command": "1",
92 | "Items": [
93 | {
94 | "Id": "0e5b5810-decd-4cc7-876c-780e3dc5973a",
95 | "Command": "/file-for-version:\".\\..\\..\\..\\..\\..\\demodata\\0.1.2.3.exe\""
96 | },
97 | {
98 | "Id": "5057ef3c-edb6-4454-8cbf-e2e99898e2a0",
99 | "Command": "/pre-release:\"true\""
100 | }
101 | ]
102 | },
103 | {
104 | "Id": "6472bd7a-9a1e-4692-ab93-2c27b18cc584",
105 | "Command": "2",
106 | "Items": [
107 | {
108 | "Id": "b2ced1e1-fbeb-45e5-bdb6-3ab6287b9d5d",
109 | "Command": "/file-for-version:\".\\..\\..\\..\\..\\..\\demodata\\0.1.2.4.exe\""
110 | },
111 | {
112 | "Id": "ec3bc229-0738-48a4-a7df-d358224bf5ff",
113 | "Command": "/pre-release:\"true\""
114 | }
115 | ]
116 | },
117 | {
118 | "Id": "6cfcff55-0322-4dd3-9e06-7abf3baec0d4",
119 | "Command": "3",
120 | "Items": [
121 | {
122 | "Id": "58eba2a1-dc0f-4c89-9e48-bc465449fc64",
123 | "Command": "/file-for-version:\".\\..\\..\\..\\..\\..\\demodata\\0.1.2.5.exe\""
124 | },
125 | {
126 | "Id": "66a1dc02-054b-422e-8cbf-2f6e1654cfe0",
127 | "Command": "/pre-release:\"false\""
128 | },
129 | {
130 | "Id": "e013a393-a7c9-4882-9036-7c83ba8e6d84",
131 | "Command": "/release-attachments:\".\\..\\..\\..\\..\\..\\demodata\\example1.zip\""
132 | }
133 | ]
134 | }
135 | ]
136 | },
137 | {
138 | "Id": "2f84915c-8cb9-4de9-8d2a-1b3502e2024f",
139 | "Command": "Changelog",
140 | "Items": [
141 | {
142 | "Id": "9a87c227-bd7e-40eb-9081-30d6b754d497",
143 | "Command": "/update-only:\"true\""
144 | },
145 | {
146 | "Id": "f50d038a-5a96-4637-9b31-5127d79dae18",
147 | "Command": "/file-for-version:\".\\..\\..\\..\\..\\..\\demodata\\0.1.2.5.exe\""
148 | },
149 | {
150 | "Id": "ec9b9e01-d810-4860-acc7-2a2c606224ba",
151 | "Command": "/pre-release:\"false\""
152 | }
153 | ]
154 | }
155 | ]
156 | },
157 | {
158 | "Id": "777ef22e-58f8-4b1b-9fc4-e85ae62deac6",
159 | "Command": "Release",
160 | "Items": [
161 | {
162 | "Id": "e06c0b9c-4451-4022-a717-1e7b6278db7e",
163 | "Command": "Common",
164 | "Items": [
165 | {
166 | "Id": "875363c7-d16e-4b34-9e36-77f31167d2ee",
167 | "Command": "/file-for-version:\".\\GitHubReleaser.exe\""
168 | },
169 | {
170 | "Id": "7dff0d8f-2904-4956-859b-730dd2f7f0c8",
171 | "Command": "/release-attachments:\".\\..\\..\\Release.zip\""
172 | },
173 | {
174 | "Id": "7308522a-66f7-4abd-a134-13ecf87bd086",
175 | "Command": "/github-repo:\"Suplanus/GitHubReleaser\""
176 | },
177 | {
178 | "Id": "eccd00ee-3262-41f6-8099-c17f5c1f5115",
179 | "Command": "/create-changelog-file:\"true\""
180 | },
181 | {
182 | "Id": "2fc6263b-fab5-4614-b79e-6787c6be8a5e",
183 | "Command": "/issue-filter-label:\"not-in-changelog\""
184 | },
185 | {
186 | "Id": "a9bc5139-2487-4f21-bf3b-42e386268760",
187 | "Command": "/issue-labels:\"bug;Bugs\" \"enhancement;Enhancements\""
188 | },
189 | {
190 | "Id": "41ddadf6-d226-4a7d-9a73-e2202732797b",
191 | "Command": "/draft:\"false\""
192 | },
193 | {
194 | "Id": "5882777c-7fd8-4267-b2b5-1b257235993f",
195 | "Command": "/delete-files-after-upload:\"false\""
196 | }
197 | ]
198 | },
199 | {
200 | "Id": "c07f08b7-0f2e-45cb-911a-8a9fcb5b0f80",
201 | "Command": "Release",
202 | "Items": [
203 | {
204 | "Id": "ef0b156b-9571-480c-abdf-2f257633df5c",
205 | "Command": "/file-for-version:\".\\..\\..\\..\\..\\..\\demodata\\0.1.2.3.dll\""
206 | },
207 | {
208 | "Id": "fa9bed05-5e42-4ed1-8913-328c3cf78525",
209 | "Command": "/pre-release:\"false\""
210 | },
211 | {
212 | "Id": "6c863e67-e608-4911-8af4-3505403daed5",
213 | "Command": "Create",
214 | "Items": [
215 | {
216 | "Id": "7e96d8ef-a588-4f65-81da-8913c361a9ff",
217 | "Command": "/update-only:\"false\""
218 | }
219 | ]
220 | },
221 | {
222 | "Id": "ac3e1ddc-3d45-4a2e-ac3c-0c0131ff23a2",
223 | "Command": "Update",
224 | "Items": [
225 | {
226 | "Id": "1babe658-503d-403e-9b04-ce8e6d1ccaa9",
227 | "Command": "/update-only:\"true\""
228 | }
229 | ]
230 | }
231 | ]
232 | }
233 | ]
234 | }
235 | ]
236 | }
--------------------------------------------------------------------------------
/src/GitHubReleaser/GitHubReleaser.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | true
5 | 1.0.0.0
6 | latest
7 | 1.0.4.105
8 | 1.0.4.105
9 | net7.0
10 | Release;Debug
11 | x64
12 |
13 |
14 | True
15 | True
16 | True
17 | True
18 | True
19 | None.None.None.Increment
20 | False
21 | SettingsVersion
22 | None
23 | None.None.None.Increment
24 | None.None.None.Increment
25 | AssemblyVersionAttribute
26 |
27 |
28 | bin\Debug\
29 |
30 |
31 | bin\Release\
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/GitHubReleaser/Model/ChangelogManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Octokit;
7 | using Serilog;
8 |
9 | namespace GitHubReleaser.Model
10 | {
11 | internal class ChangelogManager
12 | {
13 | private readonly Releaser _releaser;
14 |
15 | private static readonly string Alert = "| :warning: **Pre-release**|" +
16 | Environment.NewLine +
17 | "| --- |";
18 |
19 | public ChangelogManager(Releaser releaser)
20 | {
21 | _releaser = releaser;
22 | }
23 |
24 | public async Task Set(Release release)
25 | {
26 | ReleaseUpdate updateRelease = release.ToUpdate();
27 | updateRelease.Body = await GetReleaseChangelog();
28 | await _releaser.Client.Repository.Release.Edit(_releaser.Account, _releaser.Repo, release.Id, updateRelease);
29 | }
30 |
31 | private async Task GetReleaseChangelog()
32 | {
33 | DateTimeOffset? lastReleaseCreatedDate = null;
34 | if (_releaser.Settings.IsPreRelease)
35 | {
36 | var releases = await _releaser.Client.Repository.Release.GetAll(_releaser.Account, _releaser.Repo);
37 | Release lastRelease = releases.OrderBy(obj => obj.CreatedAt.DateTime).LastOrDefault();
38 | if (lastRelease != null)
39 | {
40 | lastReleaseCreatedDate = lastRelease.PublishedAt;
41 | }
42 | }
43 |
44 | var repository = await _releaser.Client.Repository.Get(_releaser.Account, _releaser.Repo);
45 | var allIssues = await _releaser.Client.Issue.GetAllForRepository(repository.Id,
46 | new RepositoryIssueRequest
47 | { State = ItemStateFilter.Closed });
48 | var issuesWithLabel = new List();
49 | foreach (var issue in allIssues)
50 | {
51 | if (issue.Milestone == null)
52 | {
53 | continue;
54 | }
55 |
56 | if (!issue.Milestone.Title.Equals(_releaser.VersionMilestone))
57 | {
58 | continue;
59 | }
60 |
61 | if (_releaser.Settings.IssueFilterLabel != null)
62 | {
63 | if (issue.Labels.Any(obj => obj.Name.ToLower().Equals(_releaser.Settings.IssueFilterLabel.ToLower())))
64 | {
65 | continue;
66 | }
67 | }
68 |
69 | if (lastReleaseCreatedDate != null)
70 | {
71 | if (issue.ClosedAt <= lastReleaseCreatedDate)
72 | {
73 | continue;
74 | }
75 | }
76 |
77 | // Filter by issue label
78 | if (_releaser.Settings.IssueLabels != null &&
79 | _releaser.Settings.IssueLabels.Any())
80 | {
81 | foreach (var label in issue.Labels)
82 | {
83 | _releaser.Settings.IssueLabels.TryGetValue(label.Name, out var labelHeader);
84 | if (labelHeader != null)
85 | {
86 | issuesWithLabel.Add(new IssueWithLabel(labelHeader, issue));
87 | break;
88 | }
89 | }
90 | }
91 | else
92 | {
93 | issuesWithLabel.Add(new IssueWithLabel(null, issue));
94 | }
95 | }
96 |
97 | // Build changelog text
98 | var issueGroups = issuesWithLabel.GroupBy(obj => obj.Label).OrderBy(obj => obj.Key);
99 | var changelog = GetChangelogFromIssues(issueGroups);
100 |
101 | if (_releaser.Settings.IsPreRelease)
102 | {
103 | changelog = Alert + Environment.NewLine + changelog;
104 | }
105 |
106 | return changelog;
107 | }
108 |
109 | private string GetChangelogFromIssues(IEnumerable> issueGroups)
110 | {
111 | Log.Information("Issues:");
112 | var sb = new StringBuilder();
113 | foreach (var issueGroup in issueGroups)
114 | {
115 | sb.AppendLine();
116 | sb.AppendLine($"#### {issueGroup.Key}:");
117 | Console.WriteLine("\t" + issueGroup.Key);
118 | foreach (IssueWithLabel issueWithLabel in issueGroup.OrderBy(obj => obj.Issue.Title))
119 | {
120 | sb.AppendLine($"- [{issueWithLabel.Issue.Title}]({issueWithLabel.Issue.HtmlUrl})");
121 | Console.WriteLine("\t\t" + issueWithLabel.Issue.Title);
122 | }
123 | }
124 | string changelog = sb.ToString().Trim();
125 | return changelog;
126 | }
127 |
128 | internal async Task Set()
129 | {
130 | Log.Information("Create Changelog...");
131 |
132 | StringBuilder sb = new StringBuilder();
133 |
134 | var releases = await _releaser.Client.Repository.Release.GetAll(_releaser.Account, _releaser.Repo);
135 | foreach (var release in releases.OrderByDescending(obj => obj.CreatedAt.Date))
136 | {
137 | var version = new Version(release.Name);
138 | var versionToDisplay = $"{version.Major}.{version.Minor}.{version.Build}";
139 | var dateTime = release.CreatedAt.DateTime.ToUniversalTime();
140 | dateTime = dateTime.AddDays(1); // Don't know why this is needed
141 | var dateTimeToDisplay = dateTime.ToString("yyyy-MM-dd HH:mm");
142 |
143 | sb.AppendLine($"## [{versionToDisplay}]({release.HtmlUrl})");
144 | sb.AppendLine();
145 |
146 | if (release.Prerelease)
147 | {
148 | sb.AppendLine($"`Build: {version.Revision} | Date (UTC): {dateTimeToDisplay} | Pre-release`");
149 | }
150 | else
151 | {
152 | sb.AppendLine($"`Build: {version.Revision} | Date (UTC): {dateTimeToDisplay}`");
153 | }
154 |
155 | sb.AppendLine();
156 |
157 | string changeLog = release.Body.Trim();
158 | changeLog = changeLog.Replace(Alert, ""); // Remove alert
159 | var changeLogLines = changeLog.Split('\n');
160 | foreach (var line in changeLogLines)
161 | {
162 | if (!line.StartsWith("|")) // Ignore Tables
163 | {
164 | sb.AppendLine(line);
165 | }
166 | }
167 | sb.AppendLine();
168 | }
169 |
170 | // Commit Changelog
171 | var changelog = sb.ToString();
172 | IReadOnlyList contents = await _releaser.Client
173 | .Repository
174 | .Content
175 | .GetAllContents(_releaser.Account, _releaser.Repo);
176 | string path = "CHANGELOG.md";
177 | RepositoryContent content = contents.FirstOrDefault(obj => obj.Path.Equals(path));
178 | if (content == null)
179 | {
180 | ErrorHandler.Log("Changelog.md not found");
181 | }
182 |
183 | await _releaser.Client.Repository.Content.CreateFile(
184 | _releaser.Account,
185 | _releaser.Repo,
186 | path,
187 | new UpdateFileRequest("Changelog",
188 | changelog, content.Sha));
189 |
190 | Console.WriteLine(changelog);
191 | }
192 | }
193 | }
--------------------------------------------------------------------------------
/src/GitHubReleaser/Model/CommandLineParameters.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text.Json;
6 | using Fclp;
7 | using Serilog;
8 |
9 | namespace GitHubReleaser.Model
10 | {
11 | internal class CommandLineParameters : ReleaserSettings
12 | {
13 | public ICommandLineParserResult Result { get; set; }
14 |
15 | public List IssueLabelsWithHeader { get; set; }
16 |
17 | public static CommandLineParameters FromArguments(string[] args)
18 | {
19 | var parser = new FluentCommandLineParser();
20 | parser.Setup(arg => arg.GitHubRepo)
21 | .As("github-repo")
22 | .Required();
23 |
24 | parser.Setup(arg => arg.GitHubToken)
25 | .As("github-token")
26 | .Required();
27 |
28 | parser.Setup(arg => arg.FileForVersion)
29 | .As("file-for-version")
30 | .Required();
31 |
32 | parser.Setup(arg => arg.IsPreRelease)
33 | .As("pre-release");
34 |
35 | parser.Setup(arg => arg.IssueLabelsWithHeader)
36 | .As("issue-labels");
37 |
38 | parser.Setup(arg => arg.IssueFilterLabel)
39 | .As("issue-filter-label");
40 |
41 | parser.Setup(arg => arg.ReleaseAttachments)
42 | .As("release-attachments");
43 |
44 | parser.Setup(arg => arg.IsUpdateOnly)
45 | .As("update-only");
46 |
47 | parser.Setup(arg => arg.IsChangelogFileCreationEnabled)
48 | .As("create-changelog-file");
49 |
50 | parser.Setup(arg => arg.IsDraft)
51 | .As("draft");
52 |
53 | parser.Setup(arg => arg.DeleteFilesAfterUpload)
54 | .As("delete-files-after-upload");
55 |
56 | var result = parser.Parse(args);
57 |
58 | var commandLineArguments = parser.Object;
59 |
60 | // Manual map
61 | if (parser.Object.IssueLabelsWithHeader != null)
62 | {
63 | parser.Object.IssueLabels = new Dictionary();
64 | foreach (var issueLabelWithHeader in parser.Object.IssueLabelsWithHeader)
65 | {
66 | var split = issueLabelWithHeader.Split(';');
67 | parser.Object.IssueLabels.Add(split.First(), split.Last());
68 | }
69 | }
70 |
71 | commandLineArguments.Result = result;
72 |
73 | return commandLineArguments;
74 | }
75 |
76 | public static CommandLineParameters FromFile(string[] args)
77 | {
78 | if (args.Length != 1)
79 | {
80 | return null;
81 | }
82 |
83 | var configFile = args[0];
84 |
85 | if (!File.Exists(configFile))
86 | {
87 | Log.Error($"File does not exits: {configFile}");
88 | throw new FileNotFoundException("The file name passed with the argument 'config-file' does not exist",
89 | configFile);
90 | }
91 |
92 | var commandLineParameters = new CommandLineParameters();
93 | ReleaserSettings settings;
94 | var extension = Path.GetExtension(configFile);
95 | switch (extension)
96 | {
97 | case ".json":
98 | settings = JsonDeserialize(configFile);
99 | break;
100 | case ".xml":
101 | settings = XmlDeserialize(configFile);
102 | break;
103 | default:
104 | Log.Error($"Unknown file type {Path.GetExtension(configFile)}");
105 | throw new ArgumentException("The file name passed with the argument has an unknown file extension.",
106 | "config-file");
107 | }
108 | commandLineParameters.MapProperties(settings);
109 |
110 | var parser = new FluentCommandLineParser();
111 | parser.Setup(arg => arg.ConfigFile)
112 | .As("config-file"); //dummy
113 | commandLineParameters.Result = parser.Parse(args);
114 | commandLineParameters.ConfigFile = configFile;
115 | return commandLineParameters;
116 | }
117 |
118 | private static ReleaserSettings JsonDeserialize(string configFile)
119 | {
120 | try
121 | {
122 | var jsonString = File.ReadAllText(configFile);
123 | var settings = JsonSerializer.Deserialize(jsonString);
124 | return settings;
125 | }
126 | catch (Exception e)
127 | {
128 | Log.Error(e.Message);
129 | throw;
130 | }
131 | }
132 |
133 | private void MapProperties(ReleaserSettings settings)
134 | {
135 | IsChangelogFileCreationEnabled = settings.IsChangelogFileCreationEnabled;
136 | IsUpdateOnly = settings.IsUpdateOnly;
137 | ReleaseAttachments = settings.ReleaseAttachments;
138 | IssueFilterLabel = settings.IssueFilterLabel;
139 | IssueLabels = settings.IssueLabels;
140 | IsPreRelease = settings.IsPreRelease;
141 | IsDraft = settings.IsDraft;
142 | DeleteFilesAfterUpload = settings.DeleteFilesAfterUpload;
143 | FileForVersion = settings.FileForVersion;
144 | GitHubToken = Environment.ExpandEnvironmentVariables(settings.GitHubToken);
145 | GitHubRepo = settings.GitHubRepo;
146 | }
147 |
148 | private static ReleaserSettings XmlDeserialize(string configFile)
149 | {
150 | throw new NotImplementedException();
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/GitHubReleaser/Model/ErrorHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace GitHubReleaser.Model
4 | {
5 | internal class ErrorHandler
6 | {
7 | public static void Log(string errorMessage)
8 | {
9 | Serilog.Log.Error(errorMessage);
10 | throw new Exception(errorMessage);
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/GitHubReleaser/Model/IssueWithLabel.cs:
--------------------------------------------------------------------------------
1 | using Octokit;
2 |
3 | namespace GitHubReleaser.Model
4 | {
5 | internal class IssueWithLabel
6 | {
7 | public string Label { get; }
8 | public Issue Issue { get; }
9 |
10 | public IssueWithLabel(string label, Issue issue)
11 | {
12 | Label = label;
13 | Issue = issue;
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/GitHubReleaser/Model/ReleaseManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Octokit;
7 | using Serilog;
8 |
9 | namespace GitHubReleaser.Model
10 | {
11 | internal class ReleaseManager
12 | {
13 | private readonly Releaser _releaser;
14 |
15 | public ReleaseManager(Releaser releaser)
16 | {
17 | _releaser = releaser;
18 | }
19 |
20 | public async Task UpdateRelease()
21 | {
22 | IReadOnlyList releases = await _releaser.Client.Repository.Release.GetAll(_releaser.Account, _releaser.Repo); // Get() throw exception if not found
23 | Release release = releases.FirstOrDefault(obj => obj.Name.Equals(_releaser.VersionFull));
24 | if (release == null)
25 | {
26 | ErrorHandler.Log("Release to update not found");
27 | }
28 |
29 | ReleaseUpdate updateRelease = release.ToUpdate();
30 | updateRelease.Draft = _releaser.Settings.IsDraft;
31 | var result = await _releaser.Client.Repository.Release.Edit(_releaser.Account, _releaser.Repo, release.Id, updateRelease);
32 |
33 | // Attachments
34 | if (result.Assets.Any())
35 | {
36 | foreach (var asset in result.Assets)
37 | {
38 | await _releaser.Client.Repository.Release.DeleteAsset(_releaser.Account, _releaser.Repo, asset.Id);
39 | }
40 | }
41 | await UploadAttachments(result);
42 | return result;
43 | }
44 |
45 | public async Task CreateRelease()
46 | {
47 | // Check existing
48 | IReadOnlyList releases = await _releaser.Client.Repository.Release.GetAll(_releaser.Account, _releaser.Repo); // Get() throw exception if not found
49 | var release = releases.FirstOrDefault(obj => obj.Name.Equals(_releaser.VersionFull));
50 | if (release != null)
51 | {
52 | ErrorHandler.Log("Release already extists. Please use update.");
53 | }
54 |
55 | // Create
56 | Log.Information("Create release...");
57 | NewRelease newRelease = new NewRelease(_releaser.VersionFull);
58 | newRelease.Name = _releaser.VersionFull;
59 | newRelease.Prerelease = _releaser.Settings.IsPreRelease;
60 | newRelease.Draft = _releaser.Settings.IsDraft;
61 |
62 | release = await _releaser.Client.Repository.Release.Create(_releaser.Account, _releaser.Repo, newRelease);
63 |
64 | // Upload: Attachments
65 | await UploadAttachments(release);
66 | return release;
67 | }
68 |
69 | private async Task UploadAttachments(Release release)
70 | {
71 | if (_releaser.Settings.ReleaseAttachments == null ||
72 | !_releaser.Settings.ReleaseAttachments.Any())
73 | {
74 | return;
75 | }
76 |
77 | Log.Information("Upload attachments...");
78 | bool deleteFilesAfterUpload = _releaser.Settings.DeleteFilesAfterUpload;
79 | try
80 | {
81 | for (var index = 0; index < _releaser.Settings.ReleaseAttachments.Count; index++)
82 | {
83 | Log.Information(index + 1 + " / " + _releaser.Settings.ReleaseAttachments.Count);
84 |
85 | var setupFile = _releaser.Settings.ReleaseAttachments[index];
86 | using var archiveContents = File.OpenRead(setupFile);
87 | string assetFilename = Path.GetFileName(setupFile);
88 | var assetUpload = new ReleaseAssetUpload
89 | {
90 | FileName = assetFilename,
91 | ContentType = "application/x-msdownload",
92 | RawData = archiveContents,
93 | Timeout = TimeSpan.FromHours(1) // Needed because there is a global timeout
94 | };
95 | await _releaser.Client.Repository.Release.UploadAsset(release, assetUpload);
96 | }
97 | }
98 | catch (Exception exception)
99 | {
100 | Log.Error(exception.ToString());
101 | deleteFilesAfterUpload = false;
102 | }
103 |
104 | if (deleteFilesAfterUpload)
105 | {
106 | foreach (var attachment in _releaser.Settings.ReleaseAttachments)
107 | {
108 | File.Delete(attachment);
109 | }
110 | }
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/src/GitHubReleaser/Model/Releaser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Threading.Tasks;
6 | using Octokit;
7 |
8 | namespace GitHubReleaser.Model
9 | {
10 | internal class Releaser
11 | {
12 | public ReleaserSettings Settings { get; set; }
13 | public GitHubClient Client { get; }
14 | public string Account { get; }
15 | public string Repo { get; }
16 | public string VersionMilestone { get; }
17 | public string VersionFull { get; }
18 |
19 | public Releaser(ReleaserSettings releaserSettings)
20 | {
21 | Settings = releaserSettings;
22 | var fileVersion = FileVersionInfo.GetVersionInfo(Settings.FileForVersion);
23 | var version = new Version(fileVersion.FileVersion);
24 | VersionMilestone = version.ToString(3);
25 | VersionFull = version.ToString();
26 |
27 | var split = Settings.GitHubRepo.Split('/');
28 | Account = split.First();
29 | Repo = split.Last();
30 |
31 | // GitHub
32 | ServicePointManager.SecurityProtocol =
33 | SecurityProtocolType.Tls12; // needed https://github.com/octokit/octokit.net/issues/1756
34 | var connection = new Connection(new ProductHeaderValue(Repo));
35 | Client = new GitHubClient(connection);
36 | var tokenAuth = new Credentials(Settings.GitHubToken);
37 | Client.Credentials = tokenAuth;
38 | }
39 |
40 | public async Task ExecuteAsync()
41 | {
42 | // Release
43 | ReleaseManager releaseManager = new ReleaseManager(this);
44 | Release release;
45 | if (Settings.IsUpdateOnly)
46 | {
47 | release = await releaseManager.UpdateRelease();
48 | }
49 | else
50 | {
51 | release = await releaseManager.CreateRelease();
52 | }
53 |
54 | // Changelog
55 | ChangelogManager changelogManager = new ChangelogManager(this);
56 | await changelogManager.Set(release);
57 | if (Settings.IsChangelogFileCreationEnabled)
58 | {
59 | await changelogManager.Set();
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/GitHubReleaser/Model/ReleaserSettings.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace GitHubReleaser.Model
4 | {
5 | internal class ReleaserSettings
6 | {
7 | public string ConfigFile { get; set; }
8 |
9 | public bool IsChangelogFileCreationEnabled { get; set; }
10 |
11 | public bool IsUpdateOnly { get; set; }
12 |
13 | public List ReleaseAttachments { get; set; }
14 |
15 | public string IssueFilterLabel { get; set; }
16 |
17 | public Dictionary IssueLabels { get; set; }
18 |
19 | public bool IsPreRelease { get; set; }
20 |
21 | public bool IsDraft { get; set; }
22 |
23 | public bool DeleteFilesAfterUpload { get; set; }
24 |
25 | public string FileForVersion { get; set; }
26 |
27 | public string GitHubToken { get; set; }
28 |
29 | public string GitHubRepo { get; set; }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/GitHubReleaser/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using GitHubReleaser.Model;
7 | using Serilog;
8 | using Serilog.Sinks.SystemConsole.Themes;
9 |
10 | namespace GitHubReleaser
11 | {
12 | class Program
13 | {
14 | static async Task Main(string[] args)
15 | {
16 | // Setup
17 | var argumentString = string.Join(" ", args.Skip(1));
18 | Console.Title = $@"{nameof(GitHubReleaser)} {argumentString}";
19 |
20 | Log.Logger = new LoggerConfiguration()
21 | .WriteTo.Console(theme: AnsiConsoleTheme.Code)
22 | .CreateLogger();
23 |
24 | // CommandLineParameters
25 | var commandLine = GetCommandline(args);
26 |
27 | // Execute
28 | try
29 | {
30 | Releaser releaser = new Releaser(commandLine);
31 | await releaser.ExecuteAsync();
32 | Log.Information("All looks good, have fun with your release!");
33 | return 0;
34 | }
35 | catch(Exception ex)
36 | {
37 | #if DEBUG
38 | throw;
39 | #endif
40 | Log.Error(ex, "Error during release process");
41 | return 100;
42 | }
43 | }
44 |
45 | private static CommandLineParameters GetCommandline(string[] args)
46 | {
47 | CommandLineParameters commandLineParameters = CommandLineParameters.FromFile(args);
48 |
49 | if (commandLineParameters == null)
50 | {
51 | commandLineParameters = CommandLineParameters.FromArguments(args);
52 | }
53 |
54 | // Checks
55 | if (commandLineParameters.Result.HasErrors)
56 | {
57 | ErrorHandler.Log($"Error in parameters: {commandLineParameters.Result.ErrorText}");
58 | }
59 |
60 | commandLineParameters.FileForVersion = Path.GetFullPath(commandLineParameters.FileForVersion);
61 | if (!File.Exists(commandLineParameters.FileForVersion))
62 | {
63 | ErrorHandler.Log($"File not exists: {commandLineParameters.FileForVersion}");
64 | }
65 |
66 | var extension = Path.GetExtension(commandLineParameters.FileForVersion)?.ToLower();
67 | if (extension != ".dll" &&
68 | extension != ".exe")
69 | {
70 | ErrorHandler.Log($"File type not supported: {extension}");
71 | }
72 |
73 | if (commandLineParameters.ReleaseAttachments != null)
74 | {
75 | for (var index = 0; index < commandLineParameters.ReleaseAttachments.Count; index++)
76 | {
77 | commandLineParameters.ReleaseAttachments[index] =
78 | Path.GetFullPath(commandLineParameters.ReleaseAttachments[index]);
79 | var attachment = commandLineParameters.ReleaseAttachments[index];
80 | if (!File.Exists(attachment))
81 | {
82 | ErrorHandler.Log($"Attachment file not found: {attachment}");
83 | }
84 | }
85 | }
86 |
87 | LogParameter(nameof(commandLineParameters.GitHubRepo), commandLineParameters.GitHubRepo);
88 | LogParameter(nameof(commandLineParameters.GitHubToken), commandLineParameters.GitHubToken);
89 | LogParameter(nameof(commandLineParameters.FileForVersion), commandLineParameters.FileForVersion);
90 | LogParameter(nameof(commandLineParameters.IsChangelogFileCreationEnabled),
91 | commandLineParameters.IsChangelogFileCreationEnabled);
92 | LogParameter(nameof(commandLineParameters.IsPreRelease), commandLineParameters.IsPreRelease);
93 | LogParameter(nameof(commandLineParameters.IsUpdateOnly), commandLineParameters.IsUpdateOnly);
94 | LogParameter(nameof(commandLineParameters.IssueFilterLabel), commandLineParameters.IssueFilterLabel);
95 | LogParameter(nameof(commandLineParameters.IssueLabels), commandLineParameters.IssueLabels);
96 | LogParameter(nameof(commandLineParameters.ReleaseAttachments), commandLineParameters.ReleaseAttachments);
97 |
98 | return commandLineParameters;
99 | }
100 |
101 | private static void LogParameter(string name, object value)
102 | {
103 | if (!string.IsNullOrWhiteSpace(value?.ToString()))
104 | {
105 | switch (value)
106 | {
107 | case List list:
108 | {
109 | string listValue = string.Empty;
110 | foreach (var item in list)
111 | {
112 | listValue = listValue + Environment.NewLine + "\t\t" + item;
113 | }
114 | Log.Information($"{name}: {listValue}");
115 | break;
116 | }
117 | case Dictionary list:
118 | {
119 | string listValue = string.Empty;
120 | foreach (var item in list)
121 | {
122 | listValue = listValue + Environment.NewLine + "\t\t" + item.Key + " -> " + item.Value;
123 | }
124 | Log.Information($"{name}: {listValue}");
125 | break;
126 | }
127 | default:
128 | Log.Information($"{name}: {value}");
129 | break;
130 | }
131 | }
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/src/GitHubReleaser/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "GitHubReleaser": {
4 | "commandName": "Project",
5 | "commandLineArgs": "/github-repo:\"Suplanus/GitHubReleaser\" /github-token:\"Fill out Secrets.cs\" /create-changelog-file:\"true\" /issue-filter-label:\"not-in-changelog\" /issue-labels:\"bug;Bugs\" \"enhancement;Enhancements\" /draft:\"false\" /delete-files-after-upload:\"false\" /file-for-version:\".\\GitHubReleaser.exe\" /release-attachments:\".\\..\\..\\Release.zip\" /github-repo:\"Suplanus/GitHubReleaser\" /create-changelog-file:\"true\" /issue-filter-label:\"not-in-changelog\" /issue-labels:\"bug;Bugs\" \"enhancement;Enhancements\" /draft:\"false\" /delete-files-after-upload:\"false\" /update-only:\"false\""
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------