From f6a992f32106f311121a9f75a2027691a098d747 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 05:44:15 +0000 Subject: [PATCH 01/28] Bump the nuget-deps group with 1 update Bumps ptr727.LanguageTags from 1.1.14 to 1.2.1 --- updated-dependencies: - dependency-name: ptr727.LanguageTags dependency-version: 1.2.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 79800b59..0424ef74 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -51,7 +51,7 @@ Version="10.0.102" PrivateAssets="all" /> - + From 4ff2fe5f9d1e86fcc61018476175f5ccf97bd8db Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 1 Feb 2026 13:54:24 -0500 Subject: [PATCH 02/28] Update ptr727.LanguageTags package to version 1.2.3 and clean up project file (#630) --- PlexCleaner/PlexCleaner.csproj | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 0424ef74..2fe6e054 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -46,12 +46,8 @@ - - + + @@ -63,19 +59,4 @@ - - - - From 4a182e99788614b624faf5dc06d52a9864be644a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:57:44 +0000 Subject: [PATCH 03/28] Bump the nuget-deps group with 1 update Bumps csharpier from 1.2.5 to 1.2.6 --- updated-dependencies: - dependency-name: csharpier dependency-version: 1.2.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 3245c67f..2f1a9109 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "csharpier": { - "version": "1.2.5", + "version": "1.2.6", "commands": [ "csharpier" ], From 83e8cfa2fe76e0ae25e0922dc53a9e91d8a5f73f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:44:52 +0000 Subject: [PATCH 04/28] Bump the nuget-deps group with 3 updates Bumps Microsoft.SourceLink.GitHub from 10.0.102 to 10.0.103 Bumps Serilog from 4.3.0 to 4.3.1 Bumps System.CommandLine from 2.0.2 to 2.0.3 --- updated-dependencies: - dependency-name: Microsoft.SourceLink.GitHub dependency-version: 10.0.103 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps - dependency-name: Serilog dependency-version: 4.3.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps - dependency-name: Serilog dependency-version: 4.3.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps - dependency-name: System.CommandLine dependency-version: 2.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 6 +++--- Sandbox/Sandbox.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 2fe6e054..e33f3c3a 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -46,14 +46,14 @@ - + - - + + diff --git a/Sandbox/Sandbox.csproj b/Sandbox/Sandbox.csproj index ba45deed..e661ff8b 100644 --- a/Sandbox/Sandbox.csproj +++ b/Sandbox/Sandbox.csproj @@ -5,7 +5,7 @@ false - + From 6b64f181e0325bd188c57cfb60ea8cd9977c40fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:43:52 +0000 Subject: [PATCH 05/28] Bump the nuget-deps group with 1 update Bumps ptr727.LanguageTags from 1.2.3 to 1.2.7 --- updated-dependencies: - dependency-name: ptr727.LanguageTags dependency-version: 1.2.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index e33f3c3a..0d9cb19a 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -47,7 +47,7 @@ - + From 9d248855e0db8999d3cdfeeb147ead94b7f6b435 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 05:39:02 -0500 Subject: [PATCH 06/28] Bump the nuget-deps group with 4 updates (#640) Bumps AwesomeAssertions from 9.3.0 to 9.4.0 Bumps coverlet.collector from 6.0.4 to 8.0.0 Bumps dotnet-outdated-tool from 4.6.9 to 4.7.0 Bumps ptr727.LanguageTags from 1.2.7 to 1.2.11 --- updated-dependencies: - dependency-name: AwesomeAssertions dependency-version: 9.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-deps - dependency-name: coverlet.collector dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: nuget-deps - dependency-name: dotnet-outdated-tool dependency-version: 4.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-deps - dependency-name: ptr727.LanguageTags dependency-version: 1.2.11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .config/dotnet-tools.json | 2 +- PlexCleaner/PlexCleaner.csproj | 2 +- PlexCleanerTests/PlexCleanerTests.csproj | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 2f1a9109..ef131a29 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -17,7 +17,7 @@ "rollForward": false }, "dotnet-outdated-tool": { - "version": "4.6.9", + "version": "4.7.0", "commands": [ "dotnet-outdated" ], diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 0d9cb19a..0eaf5a2b 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -47,7 +47,7 @@ - + diff --git a/PlexCleanerTests/PlexCleanerTests.csproj b/PlexCleanerTests/PlexCleanerTests.csproj index 3811a18e..36c3d6f0 100644 --- a/PlexCleanerTests/PlexCleanerTests.csproj +++ b/PlexCleanerTests/PlexCleanerTests.csproj @@ -3,14 +3,14 @@ net10.0 - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From c6bfec03235afc6fab47404959ba568d6f0e9de7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 05:44:29 +0000 Subject: [PATCH 07/28] Bump the nuget-deps group with 2 updates Bumps Microsoft.NET.Test.Sdk from 18.0.1 to 18.3.0 Bumps ptr727.LanguageTags from 1.2.11 to 1.2.13 --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-deps - dependency-name: ptr727.LanguageTags dependency-version: 1.2.13 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- PlexCleanerTests/PlexCleanerTests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 0eaf5a2b..d6197134 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -47,7 +47,7 @@ - + diff --git a/PlexCleanerTests/PlexCleanerTests.csproj b/PlexCleanerTests/PlexCleanerTests.csproj index 36c3d6f0..4c39434a 100644 --- a/PlexCleanerTests/PlexCleanerTests.csproj +++ b/PlexCleanerTests/PlexCleanerTests.csproj @@ -4,7 +4,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive From 95fecf9e5f7e863ab3ad280e844205cc288e8fd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 05:44:01 +0000 Subject: [PATCH 08/28] Bump the nuget-deps group with 1 update Bumps dotnet-outdated-tool from 4.7.0 to 4.7.1 --- updated-dependencies: - dependency-name: dotnet-outdated-tool dependency-version: 4.7.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index ef131a29..61d39e0d 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -17,7 +17,7 @@ "rollForward": false }, "dotnet-outdated-tool": { - "version": "4.7.0", + "version": "4.7.1", "commands": [ "dotnet-outdated" ], From 834f1e55f0f090ea55fc9417689433fc72811677 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:33:26 -0800 Subject: [PATCH 09/28] Bump the actions-deps group with 2 updates (#644) Bumps the actions-deps group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/upload-artifact` from 6 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) Updates `actions/download-artifact` from 7 to 8 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Pieter Viljoen --- .github/workflows/BuildDockerPush.yml | 4 ++-- .github/workflows/BuildGitHubRelease.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/BuildDockerPush.yml b/.github/workflows/BuildDockerPush.yml index 8718a4b8..0a810d90 100644 --- a/.github/workflows/BuildDockerPush.yml +++ b/.github/workflows/BuildDockerPush.yml @@ -57,7 +57,7 @@ jobs: run: cat ${{ runner.temp }}/versions/${{ matrix.file }} - name: Upload version artifacts - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: versions-${{ matrix.file }} path: ${{ runner.temp }}/versions/${{ matrix.file }} @@ -74,7 +74,7 @@ jobs: uses: actions/checkout@v6 - name: Download version artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: versions-* merge-multiple: true diff --git a/.github/workflows/BuildGitHubRelease.yml b/.github/workflows/BuildGitHubRelease.yml index 32b5cdbb..44a677ed 100644 --- a/.github/workflows/BuildGitHubRelease.yml +++ b/.github/workflows/BuildGitHubRelease.yml @@ -55,7 +55,7 @@ jobs: -property:PackageVersion=${{ needs.get-version.outputs.SemVer2 }} - name: Upload build artifacts - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: publish-${{ matrix.runtime }} path: ${{ runner.temp }}/publish @@ -68,7 +68,7 @@ jobs: steps: - name: Download build artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: publish-* merge-multiple: true From 21c2b7319f19d9070f1ac3b34fd0000073174bc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 05:44:04 +0000 Subject: [PATCH 10/28] Bump the nuget-deps group with 1 update Bumps ptr727.LanguageTags from 1.2.13 to 1.2.17 --- updated-dependencies: - dependency-name: ptr727.LanguageTags dependency-version: 1.2.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index d6197134..7e31cd0c 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -47,7 +47,7 @@ - + From d0add2d8b375a1d0fabedaff671e4e555264c347 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 05:42:46 +0000 Subject: [PATCH 11/28] Bump the nuget-deps group with 2 updates Bumps Microsoft.SourceLink.GitHub from 10.0.103 to 10.0.200 Bumps System.CommandLine from 2.0.3 to 2.0.4 --- updated-dependencies: - dependency-name: Microsoft.SourceLink.GitHub dependency-version: 10.0.200 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps - dependency-name: System.CommandLine dependency-version: 2.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 7e31cd0c..a8c10af7 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -46,14 +46,14 @@ - + - + From 2b155b9f0949d72cb842285268d1bc0c10219154 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 05:43:57 +0000 Subject: [PATCH 12/28] Bump the nuget-deps group with 1 update Bumps husky from 0.8.0 to 0.9.0 --- updated-dependencies: - dependency-name: husky dependency-version: 0.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 61d39e0d..0e79fc3c 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -10,7 +10,7 @@ "rollForward": false }, "husky": { - "version": "0.8.0", + "version": "0.9.0", "commands": [ "husky" ], From 1754d7cb7e6319064c6479294d80cb9fbfa38051 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 05:44:17 +0000 Subject: [PATCH 13/28] Bump the nuget-deps group with 2 updates Bumps Microsoft.SourceLink.GitHub from 10.0.200 to 10.0.201 Bumps System.CommandLine from 2.0.4 to 2.0.5 --- updated-dependencies: - dependency-name: Microsoft.SourceLink.GitHub dependency-version: 10.0.201 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps - dependency-name: System.CommandLine dependency-version: 2.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index a8c10af7..bbdb03e7 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -46,14 +46,14 @@ - + - + From 5725175cdd5567b5bbde4e0d41bc061ca4ac7a4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 05:47:36 +0000 Subject: [PATCH 14/28] Bump the nuget-deps group with 1 update Bumps husky from 0.9.0 to 0.9.1 --- updated-dependencies: - dependency-name: husky dependency-version: 0.9.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 0e79fc3c..2b10359b 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -10,7 +10,7 @@ "rollForward": false }, "husky": { - "version": "0.9.0", + "version": "0.9.1", "commands": [ "husky" ], From 304d9a810811a8894bc32702a153d9623ec3e2ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:44:06 +0000 Subject: [PATCH 15/28] Bump the nuget-deps group with 1 update Bumps coverlet.collector from 8.0.0 to 8.0.1 --- updated-dependencies: - dependency-name: coverlet.collector dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleanerTests/PlexCleanerTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleanerTests/PlexCleanerTests.csproj b/PlexCleanerTests/PlexCleanerTests.csproj index 4c39434a..a34d7a21 100644 --- a/PlexCleanerTests/PlexCleanerTests.csproj +++ b/PlexCleanerTests/PlexCleanerTests.csproj @@ -10,7 +10,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 70d2d18e5695a9300b62e6b1e5d3f6ec67bfa922 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 05:47:38 +0000 Subject: [PATCH 16/28] Bump the nuget-deps group with 1 update Bumps CliWrap from 3.10.0 to 3.10.1 --- updated-dependencies: - dependency-name: CliWrap dependency-version: 3.10.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index bbdb03e7..9e3939ff 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -44,7 +44,7 @@ - + From 554cf319bc9c3e5da4e11c97826c6334c838e893 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:46:53 +0000 Subject: [PATCH 17/28] Bump the nuget-deps group with 1 update Bumps ptr727.LanguageTags from 1.2.17 to 1.2.23 --- updated-dependencies: - dependency-name: ptr727.LanguageTags dependency-version: 1.2.23 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 9e3939ff..76d9e6ad 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -47,7 +47,7 @@ - + From a0dd21f7878bbb1f36c09b25d32c47f41376f7fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 07:23:37 -0700 Subject: [PATCH 18/28] Bump the actions-deps group across 1 directory with 4 updates (#652) Bumps the actions-deps group with 4 updates in the / directory: [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action), [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [docker/login-action](https://github.com/docker/login-action) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/setup-qemu-action` from 3 to 4 - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4) Updates `docker/setup-buildx-action` from 3 to 4 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4) Updates `docker/login-action` from 3 to 4 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3...v4) Updates `docker/build-push-action` from 6 to 7 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6...v7) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps - dependency-name: docker/login-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps - dependency-name: docker/build-push-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/BuildDockerTask.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/BuildDockerTask.yml b/.github/workflows/BuildDockerTask.yml index 010515ef..dae2f7c0 100644 --- a/.github/workflows/BuildDockerTask.yml +++ b/.github/workflows/BuildDockerTask.yml @@ -35,24 +35,24 @@ jobs: uses: actions/checkout@v6 - name: Setup QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 with: platforms: linux/amd64,linux/arm64 - name: Setup Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 with: platforms: linux/amd64,linux/arm64 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: docker.io username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Docker build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . push: ${{ inputs.push }} From 9b54526093cbb37e034ca1e45499c6b40de10aa6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 05:44:29 +0000 Subject: [PATCH 19/28] Bump the nuget-deps group with 1 update Bumps Microsoft.NET.Test.Sdk from 18.3.0 to 18.4.0 --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleanerTests/PlexCleanerTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleanerTests/PlexCleanerTests.csproj b/PlexCleanerTests/PlexCleanerTests.csproj index a34d7a21..eae9a7e1 100644 --- a/PlexCleanerTests/PlexCleanerTests.csproj +++ b/PlexCleanerTests/PlexCleanerTests.csproj @@ -4,7 +4,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive From 05312a95b63c79c19eda5e4a733068572a88e89e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 05:44:09 +0000 Subject: [PATCH 20/28] Bump the nuget-deps group with 1 update Bumps InsaneGenius.Utilities from 3.4.1 to 3.4.3 --- updated-dependencies: - dependency-name: InsaneGenius.Utilities dependency-version: 3.4.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 76d9e6ad..4351d0d7 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -45,7 +45,7 @@ - + From f9037b9270a0aa1eb489b5d218ccc104ffe42735 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 05:51:00 +0000 Subject: [PATCH 21/28] Bump the nuget-deps group with 1 update Bumps ptr727.LanguageTags from 1.2.23 to 1.2.25 --- updated-dependencies: - dependency-name: ptr727.LanguageTags dependency-version: 1.2.25 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 4351d0d7..f4de0320 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -47,7 +47,7 @@ - + From b51a68cfb9afe385a47e69877cb19c09be11e80f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 05:42:33 +0000 Subject: [PATCH 22/28] Bump the nuget-deps group with 1 update Bumps ptr727.LanguageTags from 1.2.25 to 1.2.26 --- updated-dependencies: - dependency-name: ptr727.LanguageTags dependency-version: 1.2.26 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index f4de0320..143c6679 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -47,7 +47,7 @@ - + From a49759fe24bdb8213845465c206c8b0106eb8c58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 05:44:20 +0000 Subject: [PATCH 23/28] Bump the nuget-deps group with 2 updates Bumps Microsoft.SourceLink.GitHub from 10.0.201 to 10.0.202 Bumps System.CommandLine from 2.0.5 to 2.0.6 --- updated-dependencies: - dependency-name: Microsoft.SourceLink.GitHub dependency-version: 10.0.202 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps - dependency-name: System.CommandLine dependency-version: 2.0.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] --- PlexCleaner/PlexCleaner.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 143c6679..3a61e169 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -46,14 +46,14 @@ - + - + From 868173ff3ea07ea4be9d71a3a80c9f580de80dbd Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 15 Apr 2026 19:03:26 -0700 Subject: [PATCH 24/28] chore: Consolidate project structure and update build configuration (#682) * chore: Consolidate project structure and update build configuration - Added CODESTYLE.md for code style and formatting rules. - Introduced Directory.Build.props and Directory.Packages.props for centralized project settings and package versions. - Refactored Docker build scripts and Dockerfile for improved clarity and functionality. - Removed obsolete UnitTest.sh script and integrated testing directly into Build.sh. - Updated HISTORY.md to reflect structural changes and new version 3.16. - Updated LICENSE to reflect copyright years. - Adjusted PlexCleaner and PlexCleanerTests project files to align with new build settings. - Refactored test classes to remove unnecessary fixture dependencies. - Updated README.md to reflect the latest version and changes. - Updated version.json to version 3.16. Signed-off-by: Pieter Viljoen * chore: Update .dockerignore to include .artifacts directory and modify .editorconfig for CA5392 severity description Signed-off-by: Pieter Viljoen * fix: Correct BUILD_ASSEMBLY_VERSION to use AssemblyVersion instead of AssemblyFileVersion Signed-off-by: Pieter Viljoen --------- Signed-off-by: Pieter Viljoen --- .dockerignore | 64 ++- .editorconfig | 36 +- .github/copilot-instructions.md | 24 +- .github/dependabot.yml | 66 +-- .github/workflows/BuildGitHubRelease.yml | 86 --- .github/workflows/DependabotAutoMerge.yml | 32 -- .github/workflows/TestBuildPr.yml | 19 - .github/workflows/TestBuildTask.yml | 33 -- .github/workflows/TestDockerPr.yml | 20 - .github/workflows/TestDockerTask.yml | 14 - .github/workflows/build-datebadge-task.yml | 27 + ...ldDockerTask.yml => build-docker-task.yml} | 37 +- .github/workflows/build-executable-task.yml | 78 +++ .github/workflows/build-release-task.yml | 63 +++ ...etVersionTask.yml => get-version-task.yml} | 82 +-- .github/workflows/merge-bot-pull-request.yml | 41 ++ ...ml => publish-periodic-docker-release.yml} | 218 ++++---- .github/workflows/publish-release.yml | 24 + .github/workflows/test-pull-request.yml | 36 ++ .github/workflows/test-release-task.yml | 40 ++ .gitignore | 2 + .vscode/launch.json | 104 ++-- .vscode/tasks.json | 493 ++++++++---------- AGENTS.md | 64 +++ CODESTYLE.md | 314 +++++++++++ Directory.Build.props | 14 + Directory.Packages.props | 21 + Docker/Build.sh | 12 +- .../{Ubuntu.Rolling.Dockerfile => Dockerfile} | 18 +- Docker/UnitTest.sh | 10 - HISTORY.md | 8 + LICENSE | 2 +- PlexCleaner.code-workspace | 13 +- PlexCleaner.slnx | 30 +- PlexCleaner/Bitrate.cs | 2 - PlexCleaner/BitrateInfo.cs | 2 - PlexCleaner/CommandLineOptions.cs | 3 - PlexCleaner/ConfigFileJsonSchema.cs | 2 - PlexCleaner/Convert.cs | 2 - PlexCleaner/ConvertOptions.cs | 1 - PlexCleaner/Extensions.cs | 1 - PlexCleaner/FfMpegBuilder.cs | 1 - PlexCleaner/FfMpegIdetInfo.cs | 1 - PlexCleaner/FfMpegTool.cs | 4 - PlexCleaner/FfMpegToolJsonSchema.cs | 1 - PlexCleaner/FfProbeBuilder.cs | 1 - PlexCleaner/FfProbeTool.cs | 6 - PlexCleaner/HandBrakeBuilder.cs | 1 - PlexCleaner/HandBrakeTool.cs | 2 - PlexCleaner/JsonSerialization.cs | 1 - PlexCleaner/KeepAwake.cs | 1 - PlexCleaner/Language.cs | 3 - PlexCleaner/MediaInfoBuilder.cs | 1 - PlexCleaner/MediaInfoTool.cs | 2 - PlexCleaner/MediaInfoToolJsonSchema.cs | 2 - PlexCleaner/MediaInfoToolXmlSchema.cs | 2 - PlexCleaner/MediaInfoXmlParser.cs | 4 - PlexCleaner/MediaProps.cs | 4 - PlexCleaner/MediaTool.cs | 4 - PlexCleaner/MediaToolInfo.cs | 1 - PlexCleaner/MkvMergeBuilder.cs | 2 - PlexCleaner/MkvMergeTool.cs | 2 - PlexCleaner/MkvPropEditBuilder.cs | 2 - PlexCleaner/MkvPropEditTool.cs | 2 - PlexCleaner/MkvToolJsonSchema.cs | 1 - PlexCleaner/Monitor.cs | 5 - PlexCleaner/PlexCleaner.csproj | 30 +- PlexCleaner/Process.cs | 5 - PlexCleaner/ProcessDriver.cs | 5 - PlexCleaner/ProcessFile.cs | 4 - PlexCleaner/ProcessOptions.cs | 3 - PlexCleaner/ProcessResultJsonSchema.cs | 2 - PlexCleaner/Program.cs | 7 - PlexCleaner/SelectMediaProps.cs | 3 - PlexCleaner/SevenZipBuilder.cs | 1 - PlexCleaner/SevenZipTool.cs | 2 - PlexCleaner/SidecarFile.cs | 2 - PlexCleaner/SidecarFileJsonSchema.cs | 2 - PlexCleaner/SubtitleProps.cs | 1 - PlexCleaner/TagMapSet.cs | 3 - PlexCleaner/ToolInfoJsonSchema.cs | 3 - PlexCleaner/Tools.cs | 5 - PlexCleaner/TrackProps.cs | 3 - PlexCleaner/VerifyOptions.cs | 1 - PlexCleaner/VideoProps.cs | 3 - PlexCleanerTests/CommandLineTests.cs | 4 +- PlexCleanerTests/FfMpegIdetInfoSerializer.cs | 5 +- PlexCleanerTests/FfMpegIdetParsingTests.cs | 5 +- PlexCleanerTests/FileNameEscapingTests.cs | 4 +- PlexCleanerTests/PlexCleanerFixture.cs | 20 +- PlexCleanerTests/PlexCleanerTests.csproj | 15 +- PlexCleanerTests/VersionParsingTests.cs | 4 +- PlexCleanerTests/WildcardTests.cs | 4 +- README.md | 18 +- Sandbox/Program.cs | 12 +- Sandbox/Sandbox.csproj | 3 +- Sandbox/TestSomething.cs | 2 - version.json | 2 +- 98 files changed, 1369 insertions(+), 1028 deletions(-) delete mode 100644 .github/workflows/BuildGitHubRelease.yml delete mode 100644 .github/workflows/DependabotAutoMerge.yml delete mode 100644 .github/workflows/TestBuildPr.yml delete mode 100644 .github/workflows/TestBuildTask.yml delete mode 100644 .github/workflows/TestDockerPr.yml delete mode 100644 .github/workflows/TestDockerTask.yml create mode 100644 .github/workflows/build-datebadge-task.yml rename .github/workflows/{BuildDockerTask.yml => build-docker-task.yml} (58%) create mode 100644 .github/workflows/build-executable-task.yml create mode 100644 .github/workflows/build-release-task.yml rename .github/workflows/{GetVersionTask.yml => get-version-task.yml} (81%) create mode 100644 .github/workflows/merge-bot-pull-request.yml rename .github/workflows/{BuildDockerPush.yml => publish-periodic-docker-release.yml} (69%) create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/test-pull-request.yml create mode 100644 .github/workflows/test-release-task.yml create mode 100644 AGENTS.md create mode 100644 CODESTYLE.md create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props rename Docker/{Ubuntu.Rolling.Dockerfile => Dockerfile} (88%) delete mode 100644 Docker/UnitTest.sh diff --git a/.dockerignore b/.dockerignore index a95d5c6f..44ceb9da 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,27 +1,37 @@ -**/.mount -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/bin -**/charts -**/docker-compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md -auth.conf \ No newline at end of file +# .NET build artifacts +**/bin/ +**/obj/ +**/out/ +.artifacts/ +**/.artifacts/ + +# Visual Studio / VS Code +.vs/ +.vscode/ +*.user +*.suo +*.userosscache +*.sln.docstates + +# Test results +TestResults/ +**/TestResults/ + +# NuGet packages +*.nupkg +*.snupkg +packages/ + +# Git +.git/ +.gitignore +.gitattributes + +# CI/CD +.github/ +.codex + +# Other +*.log +*.tmp +*.cache diff --git a/.editorconfig b/.editorconfig index 9719e019..c218e00a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ # https://github.com/dotnet/runtime/blob/main/.editorconfig # https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-format -# dotnet format style --verify-no-changes --severity=info --verbosity=detailed --exclude-diagnostics=IDE0055 +# dotnet format style --verify-no-changes --severity=info --verbosity=detailed # Root config root = true @@ -22,7 +22,7 @@ insert_final_newline = true trim_trailing_whitespace = true # Markdown files -[*.{md}] +[*.md] end_of_line = crlf trim_trailing_whitespace = false @@ -39,7 +39,6 @@ indent_size = 2 # Json files [*.json] end_of_line = crlf -indent_size = 4 # Linux scripts [*.sh] @@ -54,6 +53,29 @@ end_of_line = crlf end_of_line = crlf dotnet_diagnostic.IDE0055.severity = none dotnet_analyzer_diagnostic.severity = suggestion + +# Public API surface rules: not applicable, this is a console app not a reusable library +dotnet_diagnostic.CA1002.severity = none # Do not expose List +dotnet_diagnostic.CA1024.severity = none # Use properties where appropriate +dotnet_diagnostic.CA1034.severity = none # Nested types should not be visible +dotnet_diagnostic.CA1052.severity = none # Static holder types (builder pattern uses partial classes) +dotnet_diagnostic.CA1055.severity = none # URI return values should not be strings +dotnet_diagnostic.CA1056.severity = none # URI properties should not be strings +dotnet_diagnostic.CA1062.severity = none # Validate parameters of public methods +dotnet_diagnostic.CA1515.severity = none # Consider making public types internal +dotnet_diagnostic.CA1724.severity = none # Type names should not match namespaces +dotnet_diagnostic.CA2227.severity = none # Collection properties should be read only + +# Console app context: no localization, no SynchronizationContext, intentional case usage +dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters +dotnet_diagnostic.CA1307.severity = none # Specify StringComparison for clarity +dotnet_diagnostic.CA1308.severity = none # Normalize strings to uppercase (code intentionally lowercases) +dotnet_diagnostic.CA2007.severity = none # ConfigureAwait (console app has no SynchronizationContext) +dotnet_diagnostic.CA2234.severity = none # Pass System.Uri instead of string + +# False positives or inapplicable in this codebase +dotnet_diagnostic.CA1508.severity = none # Dead code (false positive with ??= pattern) +dotnet_diagnostic.CA5392.severity = none # DefaultDllImportSearchPaths (limited intentional P/Invoke, risk accepted) csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true @@ -73,7 +95,7 @@ csharp_prefer_simple_using_statement = true csharp_prefer_static_anonymous_function = true csharp_prefer_static_local_function = true csharp_prefer_system_threading_lock = true -csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_preferred_modifier_order = public,private,protected,internal,file,static,abstract,sealed,virtual,override,readonly,unsafe,volatile,async,extern,new,partial:warning csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false csharp_space_after_cast = false @@ -204,3 +226,9 @@ dotnet_style_qualification_for_method = false dotnet_style_qualification_for_property = false dotnet_style_readonly_field = true dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# ReSharper settings +resharper_csharp_trailing_comma_in_multiline_lists = true +resharper_csharp_var_for_built_in_types = false +resharper_csharp_var_when_type_is_apparent = false +resharper_csharp_var_when_type_is_not_apparent = false diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5433ae5e..30935153 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -215,6 +215,13 @@ Parser design patterns: - Logging: Serilog with thread IDs (`Log.Information/Warning/Error`) - Exception handling: Currently uses broad `catch(Exception)` - TODO to specialize - Global usings: `GlobalUsing.cs` defines project-wide type aliases (`ConfigFileJsonSchema`, `SidecarFileJsonSchema`) +- `Directory.Build.props`: Common MSBuild properties (`TargetFramework`, `Nullable`, `ImplicitUsings`, + `AnalysisLevel`, etc.) shared across all projects live here at the solution root. Do not duplicate + these in individual `.csproj` files -- only add a property to a `.csproj` when it is project-specific + or overrides the shared default. +- `Directory.Packages.props`: All NuGet package versions are centralised here via `PackageVersion` items. + `PackageReference` elements in `.csproj` files must not include a `Version` attribute. Asset metadata + (`PrivateAssets`, `IncludeAssets`) stays in the `.csproj` `PackageReference` element. ### Naming and Structure @@ -281,15 +288,15 @@ dotnet husky run ### GitHub Actions -- **BuildGitHubRelease.yml**: Multi-runtime matrix build (win, linux, osx × x64/arm/arm64) -- **BuildDockerPush.yml**: Multi-arch Docker builds (linux/amd64, linux/arm64) -- **TestBuildPr.yml** / **TestDockerPr.yml**: PR validation +- **publish-release.yml**: Multi-runtime matrix build (win, linux, osx x x64/arm/arm64) +- **publish-periodic-docker-release.yml**: Multi-arch Docker builds (linux/amd64, linux/arm64) +- **test-pull-request.yml**: PR validation - Version info: `version.json` with Nerdbank.GitVersioning format - Branches: `main` (stable releases), `develop` (pre-releases) ### Docker -- Multi-stage builds in `Docker/Ubuntu.Rolling.Dockerfile` +- Multi-stage builds in `Docker/Dockerfile` - Base image: `ubuntu:rolling` only (no longer publishing Alpine or Debian variants) - Supported architectures: `linux/amd64`, `linux/arm64` (no longer supporting `linux/arm/v7`) - Tool installation: Ubuntu package manager (apt) @@ -440,3 +447,12 @@ Check states with `HasFlag()`, combine with `|=` - **KeepAwake.cs**: System sleep prevention - **PlexCleaner.defaults.json**: Canonical configuration reference - **.editorconfig** / **.csharpier.json**: Code style definitions + +## Git and Commit Rules + +**These rules are absolute — no exceptions:** + +- **Never make git commits.** All commits must be cryptographically signed (SSH/GPG). AI coding agents cannot produce signed commits. Stage changes with `git add` and leave `git commit` to the developer, who must run it in their own environment where signing keys are available. +- **Never force push.** Do not run `git push --force` or `git push --force-with-lease`. Force pushing rewrites shared branch history and is blocked by branch protection rules. +- **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction. +- **Staging is the limit.** Prepare changes and stage files; the developer handles all commits and pushes. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fbc6fce9..5d47fa7e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,43 +1,23 @@ -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file -version: 2 -updates: - - # main -- package-ecosystem: "nuget" - target-branch: "main" - directory: "/" - schedule: - interval: "daily" - groups: - nuget-deps: - patterns: - - "*" -- package-ecosystem: "github-actions" - target-branch: "main" - directory: "/" - schedule: - interval: "daily" - groups: - actions-deps: - patterns: - - "*" - - # develop -- package-ecosystem: "nuget" - target-branch: "develop" - directory: "/" - schedule: - interval: "daily" - groups: - nuget-deps: - patterns: - - "*" -- package-ecosystem: "github-actions" - target-branch: "develop" - directory: "/" - schedule: - interval: "daily" - groups: - actions-deps: - patterns: - - "*" +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +version: 2 +updates: + + # main +- package-ecosystem: "nuget" + target-branch: "main" + directory: "/" + schedule: + interval: "daily" + groups: + nuget-deps: + patterns: + - "*" +- package-ecosystem: "github-actions" + target-branch: "main" + directory: "/" + schedule: + interval: "daily" + groups: + actions-deps: + patterns: + - "*" diff --git a/.github/workflows/BuildGitHubRelease.yml b/.github/workflows/BuildGitHubRelease.yml deleted file mode 100644 index 44a677ed..00000000 --- a/.github/workflows/BuildGitHubRelease.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Build and publish release - -on: - push: - branches: [ main, develop ] - paths-ignore: - - 'Docker/**' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - test-build: - name: Run build tests - uses: ./.github/workflows/TestBuildTask.yml - secrets: inherit - - get-version: - name: Get version information - uses: ./.github/workflows/GetVersionTask.yml - secrets: inherit - - build-matrix: - name: Build matrix - runs-on: ubuntu-latest - needs: [test-build, get-version] - strategy: - matrix: - runtime: [ win-x64, linux-x64, linux-musl-x64, linux-arm, linux-arm64, osx-x64, osx-arm64 ] - - steps: - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: "10" - - - name: Checkout code - uses: actions/checkout@v6 - - - name: Build project - run: >- - dotnet publish ./PlexCleaner/PlexCleaner.csproj - --runtime ${{ matrix.runtime }} - --output ${{ runner.temp }}/publish/${{ matrix.runtime }} - --configuration ${{ endsWith(github.ref, 'refs/heads/main') && 'Release' || 'Debug' }} - -property:PublishAot=false - -property:Version=${{ needs.get-version.outputs.AssemblyVersion }} - -property:FileVersion=${{ needs.get-version.outputs.AssemblyFileVersion }} - -property:AssemblyVersion=${{ needs.get-version.outputs.AssemblyVersion }} - -property:InformationalVersion=${{ needs.get-version.outputs.AssemblyInformationalVersion }} - -property:PackageVersion=${{ needs.get-version.outputs.SemVer2 }} - - - name: Upload build artifacts - uses: actions/upload-artifact@v7 - with: - name: publish-${{ matrix.runtime }} - path: ${{ runner.temp }}/publish - - github-release: - name: Create GitHub release - runs-on: ubuntu-latest - needs: [ build-matrix, get-version ] - - steps: - - - name: Download build artifacts - uses: actions/download-artifact@v8 - with: - pattern: publish-* - merge-multiple: true - path: ${{ runner.temp }}/publish - - - name: Zip build output - run: 7z a -t7z ${{ runner.temp }}/publish/PlexCleaner.7z ${{ runner.temp }}/publish/* - - - name: Create GitHub release - uses: softprops/action-gh-release@v2 - with: - generate_release_notes: true - tag_name: ${{ needs.get-version.outputs.SemVer2 }} - prerelease: ${{ !endsWith(github.ref, 'refs/heads/main') }} - files: ${{ runner.temp }}/publish/PlexCleaner.7z diff --git a/.github/workflows/DependabotAutoMerge.yml b/.github/workflows/DependabotAutoMerge.yml deleted file mode 100644 index 49368e22..00000000 --- a/.github/workflows/DependabotAutoMerge.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Dependabot auto-merge - -on: - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - dependabot: - name: Merge dependabot PRs - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - if: github.actor == 'dependabot[bot]' - - steps: - - - name: Get dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v2 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Merge dependabot PR - if: steps.metadata.outputs.update-type != 'version-update:semver-major' - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/TestBuildPr.yml b/.github/workflows/TestBuildPr.yml deleted file mode 100644 index ee9d5edf..00000000 --- a/.github/workflows/TestBuildPr.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Test build PRs - -on: - pull_request: - branches: [ main, develop ] - paths-ignore: - - 'Docker/**' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - test-build: - name: Run build tests - uses: ./.github/workflows/TestBuildTask.yml - secrets: inherit diff --git a/.github/workflows/TestBuildTask.yml b/.github/workflows/TestBuildTask.yml deleted file mode 100644 index fec28633..00000000 --- a/.github/workflows/TestBuildTask.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Test build task - -on: - workflow_call: - workflow_dispatch: - -jobs: - - test-build: - name: Run build tests - runs-on: ubuntu-latest - - steps: - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: "10" - - - name: Checkout code - uses: actions/checkout@v6 - - - name: Check code style - run: | - dotnet tool restore - dotnet husky install - dotnet husky run - - - name: Run unit tests - run: dotnet test - - - name: Build solution - run: dotnet build diff --git a/.github/workflows/TestDockerPr.yml b/.github/workflows/TestDockerPr.yml deleted file mode 100644 index 10ccb298..00000000 --- a/.github/workflows/TestDockerPr.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Test docker PRs - -on: - pull_request: - branches: [ main, develop ] - paths: - - 'Docker/**' - - '.github/workflows/TestDocker.yml' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - test-docker: - name: Test docker images - uses: ./.github/workflows/TestDockerTask.yml - secrets: inherit diff --git a/.github/workflows/TestDockerTask.yml b/.github/workflows/TestDockerTask.yml deleted file mode 100644 index 8bc33545..00000000 --- a/.github/workflows/TestDockerTask.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Test docker task - -on: - workflow_call: - workflow_dispatch: - -jobs: - - build-docker: - name: Build docker images - uses: ./.github/workflows/BuildDockerTask.yml - secrets: inherit - with: - push: false diff --git a/.github/workflows/build-datebadge-task.yml b/.github/workflows/build-datebadge-task.yml new file mode 100644 index 00000000..fc3355a6 --- /dev/null +++ b/.github/workflows/build-datebadge-task.yml @@ -0,0 +1,27 @@ +name: Build BYOB date badge task + +on: + workflow_call: + +jobs: + + date-badge: + name: Build BYOB date badge job + runs-on: ubuntu-latest + + steps: + + - name: Get current date step + id: date + run: echo "date=$(date)" >> $GITHUB_OUTPUT + + - name: Build BYOB date badge step + if: ${{ github.ref_name == 'main' }} + uses: RubbaBoy/BYOB@v1 + with: + name: lastbuild + label: "Last Build" + icon: "github" + status: ${{ steps.date.outputs.date }} + color: "blue" + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/BuildDockerTask.yml b/.github/workflows/build-docker-task.yml similarity index 58% rename from .github/workflows/BuildDockerTask.yml rename to .github/workflows/build-docker-task.yml index dae2f7c0..7ee42721 100644 --- a/.github/workflows/BuildDockerTask.yml +++ b/.github/workflows/build-docker-task.yml @@ -1,57 +1,58 @@ -name: Build docker image task +name: Build Docker image task on: workflow_call: inputs: + # Input to control whether to push the Docker image to Docker Hub push: required: false type: boolean default: false - workflow_dispatch: jobs: get-version: - name: Get version information - uses: ./.github/workflows/GetVersionTask.yml + name: Get version information job + uses: ./.github/workflows/get-version-task.yml secrets: inherit - build-matrix: - name: Build docker matrix + build-docker: + name: Build Docker image job runs-on: ubuntu-latest needs: [get-version] strategy: matrix: include: - - file: ./Docker/Ubuntu.Rolling.Dockerfile + - file: ./Docker/Dockerfile platforms: linux/amd64,linux/arm64 tags: | - docker.io/ptr727/plexcleaner:${{ endsWith(github.ref, 'refs/heads/main') && 'latest' || 'develop' }} + docker.io/ptr727/plexcleaner:${{ github.ref_name == 'main' && 'latest' || 'develop' }} docker.io/ptr727/plexcleaner:${{ needs.get-version.outputs.SemVer2 }} steps: - - name: Checkout + - name: Checkout step uses: actions/checkout@v6 - - name: Setup QEMU + - name: Setup QEMU step uses: docker/setup-qemu-action@v4 with: platforms: linux/amd64,linux/arm64 - - name: Setup Buildx + - name: Setup Buildx step uses: docker/setup-buildx-action@v4 with: platforms: linux/amd64,linux/arm64 - - name: Login to Docker Hub - uses: docker/login-action@v4 + # Always login to Docker Hub, not just on push, to benefit from + # higher rate limits with a Docker subscription for pulls and cache + - name: Login to Docker Hub step + uses: docker/login-action@v3 with: - registry: docker.io username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - name: Docker build and push + - name: Docker build and push step uses: docker/build-push-action@v7 with: context: . @@ -59,11 +60,13 @@ jobs: file: ${{ matrix.file }} tags: ${{ matrix.tags }} platforms: ${{ matrix.platforms }} + cache-from: type=registry,ref=docker.io/ptr727/plexcleaner:buildcache + cache-to: type=registry,ref=docker.io/ptr727/plexcleaner:buildcache,mode=max build-args: | LABEL_VERSION=${{ needs.get-version.outputs.SemVer2 }} - BUILD_CONFIGURATION=${{ endsWith(github.ref, 'refs/heads/main') && 'Release' || 'Debug' }} + BUILD_CONFIGURATION=${{ github.ref_name == 'main' && 'Release' || 'Debug' }} BUILD_VERSION=${{ needs.get-version.outputs.AssemblyVersion }} BUILD_FILE_VERSION=${{ needs.get-version.outputs.AssemblyFileVersion }} - BUILD_ASSEMBLY_VERSION=${{ needs.get-version.outputs.AssemblyFileVersion }} + BUILD_ASSEMBLY_VERSION=${{ needs.get-version.outputs.AssemblyVersion }} BUILD_INFORMATION_VERSION=${{ needs.get-version.outputs.AssemblyInformationalVersion }} BUILD_PACKAGE_VERSION=${{ needs.get-version.outputs.SemVer2 }} diff --git a/.github/workflows/build-executable-task.yml b/.github/workflows/build-executable-task.yml new file mode 100644 index 00000000..06679136 --- /dev/null +++ b/.github/workflows/build-executable-task.yml @@ -0,0 +1,78 @@ +name: Build executable task + +on: + workflow_call: + outputs: + # Output of the uploaded artifact id + artifact-id: + value: ${{ jobs.upload-build-artifacts.outputs.artifact-id }} + +jobs: + + get-version: + name: Get version information job + uses: ./.github/workflows/get-version-task.yml + secrets: inherit + + build-executable-matrix: + name: Build executable project matrix job + runs-on: ubuntu-latest + needs: [get-version] + strategy: + matrix: + runtime: [ win-x64, linux-x64, linux-musl-x64, linux-arm, linux-arm64, osx-x64, osx-arm64 ] + + steps: + + - name: Setup .NET SDK step + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Checkout code step + uses: actions/checkout@v6 + + - name: Build executable project step + run: | + dotnet publish ./PlexCleaner/PlexCleaner.csproj \ + --runtime ${{ matrix.runtime }} \ + -property:PublishDir=${{ runner.temp }}/publish/${{ matrix.runtime }}/ \ + --configuration ${{ github.ref_name == 'main' && 'Release' || 'Debug' }} \ + -property:PublishAot=false \ + -property:Version=${{ needs.get-version.outputs.AssemblyVersion }} \ + -property:FileVersion=${{ needs.get-version.outputs.AssemblyFileVersion }} \ + -property:AssemblyVersion=${{ needs.get-version.outputs.AssemblyVersion }} \ + -property:InformationalVersion=${{ needs.get-version.outputs.AssemblyInformationalVersion }} \ + -property:PackageVersion=${{ needs.get-version.outputs.SemVer2 }} + + - name: Upload matrix build artifacts step + uses: actions/upload-artifact@v7 + with: + name: publish-${{ matrix.runtime }} + path: ${{ runner.temp }}/publish + + upload-build-artifacts: + name: Upload matrix build artifacts job + outputs: + artifact-id: ${{ steps.artifact-upload-step.outputs.artifact-id }} + runs-on: ubuntu-latest + needs: [ build-executable-matrix ] + + steps: + + - name: Download matrix build artifacts step + uses: actions/download-artifact@v8 + with: + pattern: publish-* + merge-multiple: true + path: ${{ runner.temp }}/publish + + - name: Zip build output step + run: 7z a -t7z ${{ runner.temp }}/PlexCleaner.7z ${{ runner.temp }}/publish/* + + - name: Upload build artifacts step + id: artifact-upload-step + uses: actions/upload-artifact@v7 + with: + name: executable-build + path: ${{ runner.temp }}/PlexCleaner.7z diff --git a/.github/workflows/build-release-task.yml b/.github/workflows/build-release-task.yml new file mode 100644 index 00000000..f3bf9ecd --- /dev/null +++ b/.github/workflows/build-release-task.yml @@ -0,0 +1,63 @@ +name: Build project release task + +on: + workflow_call: + inputs: + # Input to control whether to create a GitHub release + github: + required: false + type: boolean + default: false + # Input to control whether to push the docker image to Docker Hub + dockerhub: + required: false + type: boolean + default: false + +jobs: + + get-version: + name: Get version information job + uses: ./.github/workflows/get-version-task.yml + secrets: inherit + + build-executable: + name: Build executable job + uses: ./.github/workflows/build-executable-task.yml + secrets: inherit + + build-docker: + name: Build Docker job + uses: ./.github/workflows/build-docker-task.yml + secrets: inherit + with: + # Conditional push to Docker Hub + push: ${{ inputs.dockerhub }} + + github-release: + name: Publish GitHub release job + if: ${{ inputs.github }} + runs-on: ubuntu-latest + needs: [get-version, build-executable, build-docker] + + steps: + + - name: Checkout code step + uses: actions/checkout@v6 + + - name: Download executable build artifacts step + uses: actions/download-artifact@v8 + with: + artifact-ids: ${{ needs.build-executable.outputs.artifact-id }} + path: ./Publish + + - name: Create GitHub release step + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + tag_name: ${{ needs.get-version.outputs.SemVer2 }} + prerelease: ${{ github.ref_name != 'main' }} + files: | + LICENSE + README.md + ./Publish/* diff --git a/.github/workflows/GetVersionTask.yml b/.github/workflows/get-version-task.yml similarity index 81% rename from .github/workflows/GetVersionTask.yml rename to .github/workflows/get-version-task.yml index 55b4ffdd..2c52cb21 100644 --- a/.github/workflows/GetVersionTask.yml +++ b/.github/workflows/get-version-task.yml @@ -1,41 +1,41 @@ -name: Get version information task - -on: - workflow_call: - outputs: - SemVer2: - value: ${{ jobs.get-version.outputs.SemVer2 }} - AssemblyVersion: - value: ${{ jobs.get-version.outputs.AssemblyVersion }} - AssemblyFileVersion: - value: ${{ jobs.get-version.outputs.AssemblyFileVersion }} - AssemblyInformationalVersion: - value: ${{ jobs.get-version.outputs.AssemblyInformationalVersion }} - workflow_dispatch: - -jobs: - - get-version: - name: Get version information - runs-on: ubuntu-latest - outputs: - SemVer2: ${{ steps.nbgv.outputs.SemVer2 }} - AssemblyVersion: ${{ steps.nbgv.outputs.AssemblyVersion }} - AssemblyFileVersion: ${{ steps.nbgv.outputs.AssemblyFileVersion }} - AssemblyInformationalVersion: ${{ steps.nbgv.outputs.AssemblyInformationalVersion }} - - steps: - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: "10" - - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Run Nerdbank.GitVersioning tool - id: nbgv - uses: dotnet/nbgv@master +name: Get version information task + +on: + workflow_call: + outputs: + # Version information outputs + SemVer2: + value: ${{ jobs.get-version.outputs.SemVer2 }} + AssemblyVersion: + value: ${{ jobs.get-version.outputs.AssemblyVersion }} + AssemblyFileVersion: + value: ${{ jobs.get-version.outputs.AssemblyFileVersion }} + AssemblyInformationalVersion: + value: ${{ jobs.get-version.outputs.AssemblyInformationalVersion }} + +jobs: + + get-version: + name: Get version information job + runs-on: ubuntu-latest + outputs: + SemVer2: ${{ steps.nbgv.outputs.SemVer2 }} + AssemblyVersion: ${{ steps.nbgv.outputs.AssemblyVersion }} + AssemblyFileVersion: ${{ steps.nbgv.outputs.AssemblyFileVersion }} + AssemblyInformationalVersion: ${{ steps.nbgv.outputs.AssemblyInformationalVersion }} + + steps: + + - name: Setup .NET SDK step + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Checkout code step + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Run Nerdbank.GitVersioning tool step + id: nbgv + uses: dotnet/nbgv@master diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml new file mode 100644 index 00000000..d45b39e1 --- /dev/null +++ b/.github/workflows/merge-bot-pull-request.yml @@ -0,0 +1,41 @@ +name: Merge bot pull request action + +on: + pull_request: + types: [opened, reopened, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + merge-dependabot: + name: Merge dependabot pull request job + runs-on: ubuntu-latest + # To prevent abuse, the PR must come from Dependabot and the PR must originate from this repository. + if: >- + github.actor == 'dependabot[bot]' && + github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: write + pull-requests: write + + steps: + + - name: Get dependabot metadata step + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + # Merge any non-NuGet update, e.g. GitHub Actions often updates v1 to v2. + # Merge NuGet only for non-major updates, e.g. major updates may build but break functionality. + - name: Merge pull request step + if: >- + (steps.metadata.outputs.package-ecosystem != 'nuget') || + (steps.metadata.outputs.update-type != 'version-update:semver-major') + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/BuildDockerPush.yml b/.github/workflows/publish-periodic-docker-release.yml similarity index 69% rename from .github/workflows/BuildDockerPush.yml rename to .github/workflows/publish-periodic-docker-release.yml index 0a810d90..f09ca5f2 100644 --- a/.github/workflows/BuildDockerPush.yml +++ b/.github/workflows/publish-periodic-docker-release.yml @@ -1,115 +1,103 @@ -name: Build and push docker images - -on: - push: - branches: [ main, develop ] - workflow_dispatch: - schedule: - - cron: '0 2 * * MON' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - build-docker: - name: Build docker images - uses: ./.github/workflows/BuildDockerTask.yml - secrets: inherit - with: - push: true - - tool-version-matrix: - name: Get tool versions - runs-on: ubuntu-latest - needs: build-docker - strategy: - matrix: - include: - - tag: ${{ endsWith(github.ref, 'refs/heads/main') && 'latest' || 'develop' }} - file: latest.ver - - steps: - - - name: Get image size - run: | - mkdir -p ${{ runner.temp }}/versions - touch ${{ runner.temp }}/versions/${{ matrix.file }} - echo Image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} >> ${{ runner.temp }}/versions/${{ matrix.file }} - echo Size: $(docker manifest inspect -v docker.io/ptr727/plexcleaner:${{ matrix.tag }} | jq '.[] | select(.Descriptor.platform.architecture=="amd64") | [.OCIManifest.layers[].size] | add' | numfmt --to=iec) >> ${{ runner.temp }}/versions/${{ matrix.file }} - - - name: Write tool versions to file - uses: addnab/docker-run-action@v3 - with: - image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} - options: --volume ${{ runner.temp }}/versions:/versions - run: | - echo OS: $(. /etc/os-release; echo $PRETTY_NAME) >> /versions/${{ matrix.file }} - echo dotNET: $(dotnet --info) >> /versions/${{ matrix.file }} - echo PlexCleaner: $(/PlexCleaner/PlexCleaner --version) >> /versions/${{ matrix.file }} - echo HandBrakeCLI: $(HandBrakeCLI --version) >> /versions/${{ matrix.file }} - echo MediaInfo: $(mediainfo --version) >> /versions/${{ matrix.file }} - echo MkvMerge: $(mkvmerge --version) >> /versions/${{ matrix.file }} - echo FfMpeg: $(ffmpeg -version) >> /versions/${{ matrix.file }} - - - name: Print versions - run: cat ${{ runner.temp }}/versions/${{ matrix.file }} - - - name: Upload version artifacts - uses: actions/upload-artifact@v7 - with: - name: versions-${{ matrix.file }} - path: ${{ runner.temp }}/versions/${{ matrix.file }} - - update-readme: - name: Create Docker README.md - runs-on: ubuntu-latest - needs: tool-version-matrix - if: ${{ endsWith(github.ref, 'refs/heads/main') }} - - steps: - - - name: Checkout - uses: actions/checkout@v6 - - - name: Download version artifacts - uses: actions/download-artifact@v8 - with: - pattern: versions-* - merge-multiple: true - path: ${{ runner.temp }}/versions - - - name: Create README.md from README.m4 - run: m4 --include=${{ runner.temp }}/versions ./Docker/README.m4 > ${{ runner.temp }}/README.md - - - name: Update Docker Hub README.md - uses: peter-evans/dockerhub-description@v5 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_PASSWORD }} - repository: ptr727/plexcleaner - short-description: ${{ github.event.repository.description }} - readme-filepath: ${{ runner.temp }}/README.md - - date-badge: - name: Create date badge - runs-on: ubuntu-latest - needs: build-docker - if: ${{ endsWith(github.ref, 'refs/heads/main') }} - - steps: - - - id: date - run: | - echo "date=$(date)" >> $GITHUB_OUTPUT - - - name: Build date badge - uses: RubbaBoy/BYOB@v1 - with: - name: lastbuild - label: "Last Build" - icon: "github" - status: ${{ steps.date.outputs.date }} - color: "blue" - github_token: ${{ secrets.GITHUB_TOKEN }} +name: Publish weekly Docker image to Docker Hub action + +on: + push: + branches: [ main, develop ] + workflow_dispatch: + schedule: + # Run weekly on Mondays at 02:00 UTC + - cron: '0 2 * * MON' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + build-docker: + name: Build Docker image job + uses: ./.github/workflows/build-docker-task.yml + secrets: inherit + with: + # Push to registry + push: true + + tool-version-matrix: + name: Get tool versions + runs-on: ubuntu-latest + needs: build-docker + strategy: + matrix: + include: + - tag: ${{ github.ref_name == 'main' && 'latest' || 'develop' }} + file: latest.ver + + steps: + + - name: Get image size step + run: | + mkdir -p ${{ runner.temp }}/versions + touch ${{ runner.temp }}/versions/${{ matrix.file }} + echo Image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} >> ${{ runner.temp }}/versions/${{ matrix.file }} + echo Size: $(docker manifest inspect -v docker.io/ptr727/plexcleaner:${{ matrix.tag }} | jq '.[] | select(.Descriptor.platform.architecture=="amd64") | [.OCIManifest.layers[].size] | add' | numfmt --to=iec) >> ${{ runner.temp }}/versions/${{ matrix.file }} + + - name: Write tool versions to file step + uses: addnab/docker-run-action@v3 + with: + image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} + options: --volume ${{ runner.temp }}/versions:/versions + run: | + echo OS: $(. /etc/os-release; echo $PRETTY_NAME) >> /versions/${{ matrix.file }} + echo dotNET: $(dotnet --info) >> /versions/${{ matrix.file }} + echo PlexCleaner: $(/PlexCleaner/PlexCleaner --version) >> /versions/${{ matrix.file }} + echo HandBrakeCLI: $(HandBrakeCLI --version) >> /versions/${{ matrix.file }} + echo MediaInfo: $(mediainfo --version) >> /versions/${{ matrix.file }} + echo MkvMerge: $(mkvmerge --version) >> /versions/${{ matrix.file }} + echo FfMpeg: $(ffmpeg -version) >> /versions/${{ matrix.file }} + + - name: Print versions step + run: cat ${{ runner.temp }}/versions/${{ matrix.file }} + + - name: Upload version artifacts step + uses: actions/upload-artifact@v7 + with: + name: versions-${{ matrix.file }} + path: ${{ runner.temp }}/versions/${{ matrix.file }} + + update-readme: + name: Create Docker README.md job + runs-on: ubuntu-latest + needs: tool-version-matrix + if: ${{ github.ref_name == 'main' }} + + steps: + + - name: Checkout step + uses: actions/checkout@v6 + + - name: Download version artifacts step + uses: actions/download-artifact@v8 + with: + pattern: versions-* + merge-multiple: true + path: ${{ runner.temp }}/versions + + - name: Create README.md from README.m4 step + run: m4 --include=${{ runner.temp }}/versions ./Docker/README.m4 > ${{ runner.temp }}/README.md + + - name: Update Docker Hub README.md step + uses: peter-evans/dockerhub-description@v5 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + repository: ptr727/plexcleaner + short-description: ${{ github.event.repository.description }} + readme-filepath: ${{ runner.temp }}/README.md + + date-badge: + name: Create BYOB date badge job + needs: [build-docker] + uses: ./.github/workflows/build-datebadge-task.yml + secrets: inherit + permissions: + contents: write diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 00000000..e9390888 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,24 @@ +name: Publish project release action + +on: + push: + branches: [ main, develop ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + create-release: + name: Publish project release job + uses: ./.github/workflows/build-release-task.yml + secrets: inherit + permissions: + contents: write + with: + # Push to GitHub; Docker image is built (validates Dockerfile) but not pushed + # Docker Hub publishing is handled by publish-periodic-docker-release.yml + github: true + dockerhub: false diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml new file mode 100644 index 00000000..7322ef08 --- /dev/null +++ b/.github/workflows/test-pull-request.yml @@ -0,0 +1,36 @@ +name: Test pull request action + +on: + pull_request: + branches: [ main, develop ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + test-release: + name: Test release job + uses: ./.github/workflows/test-release-task.yml + secrets: inherit + + # TODO: Workaround for GitHub Actions not supporting status checks on conditional jobs + # https://github.com/orgs/community/discussions/12395#discussioncomment-12970019 + check-workflow-status: + name: Check pull request workflow status + runs-on: ubuntu-latest + needs: + [ test-release ] + if: always() + steps: + - name: Check workflow results + run: | + exit_on_result() { + if [[ "$2" == "failure" || "$2" == "cancelled" ]]; then + echo "Job '$1' failed or was cancelled." + exit 1 + fi + } + exit_on_result "test-release" "${{ needs.test-release.result }}" diff --git a/.github/workflows/test-release-task.yml b/.github/workflows/test-release-task.yml new file mode 100644 index 00000000..4340986f --- /dev/null +++ b/.github/workflows/test-release-task.yml @@ -0,0 +1,40 @@ +name: Test release task + +on: + workflow_call: + workflow_dispatch: + +jobs: + + unit-test: + name: Run unit tests job + runs-on: ubuntu-latest + + steps: + + - name: Setup .NET SDK step + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Checkout code step + uses: actions/checkout@v6 + + - name: Check code style step + run: | + dotnet tool restore + dotnet husky install + dotnet husky run + + - name: Run unit tests step + run: dotnet test + + build-release: + name: Build release without publishing job + needs: [unit-test] + uses: ./.github/workflows/build-release-task.yml + secrets: inherit + with: + # Do not publish + github: false + dockerhub: false diff --git a/.gitignore b/.gitignore index 8ae884af..f326ab60 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ .idea .vs +.codex .DS_Store +*.log *.user diff --git a/.vscode/launch.json b/.vscode/launch.json index 079badb8..ead5d35b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,11 +11,11 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "--help", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -24,12 +24,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--help", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -38,11 +38,11 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "--version", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -51,12 +51,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "getversioninfo", "--settingsfile=PlexCleaner.json", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -65,12 +65,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "defaultsettings", "--settingsfile=PlexCleaner.defaults.json", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -79,7 +79,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--logfile=PlexCleaner_Single.log", @@ -87,7 +87,7 @@ "--resultsfile=Results_Single.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "stopAtEntry": false, "enableStepFiltering": false, "justMyCode": false @@ -97,7 +97,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--logfile=PlexCleaner_Single_Quickscan.log", @@ -106,7 +106,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -117,7 +117,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--logfile=PlexCleaner_Single_Quickscan.log", @@ -127,7 +127,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -138,7 +138,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--logfile=PlexCleaner_Parallel_TestSnippets_Quickscan.log", @@ -150,7 +150,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -161,7 +161,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "monitor", "--logfile=PlexCleaner.log", @@ -169,7 +169,7 @@ "--preprocess", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -180,7 +180,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "remux", "--logfile=PlexCleaner.log", @@ -188,7 +188,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -199,7 +199,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "reencode", "--logfile=PlexCleaner.log", @@ -207,7 +207,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -218,7 +218,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "deinterlace", "--logfile=PlexCleaner.log", @@ -226,7 +226,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -237,7 +237,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "removeclosedcaptions", "--logfile=PlexCleaner.log", @@ -245,7 +245,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -256,14 +256,14 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "removesubtitles", "--logfile=PlexCleaner.log", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -274,7 +274,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "verify", "--logfile=PlexCleaner.log", @@ -282,7 +282,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -293,13 +293,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "gettagmap", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -310,13 +310,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "getmediainfo", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -327,14 +327,14 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "testmediainfo", "--logfile=PlexCleaner.log", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -345,13 +345,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "gettoolinfo", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -362,12 +362,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "checkfornewtools", "--settingsfile=PlexCleaner.json", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -378,13 +378,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "createsidecar", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -395,13 +395,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "updatesidecar", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -412,13 +412,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "getsidecarinfo", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -429,12 +429,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "createschema", "--schemafile=PlexCleaner.schema.json" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -445,9 +445,9 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/Sandbox/bin/Debug/net10.0/Sandbox.dll", + "program": "${workspaceFolder}/.artifacts/bin/Sandbox/debug/Sandbox.dll", "args": [], - "cwd": "${workspaceFolder}/Sandbox/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/Sandbox/debug", "stopAtEntry": false, "enableStepFiltering": false, "justMyCode": false diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 957454dc..1cc35902 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,280 +1,213 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": ".Net Build", - "type": "process", - "command": "dotnet", - "args": [ - "build", - "${workspaceFolder}", - "--verbosity=diagnostic" - ], - "group": "build", - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": ".Net Format", - "type": "process", - "command": "dotnet", - "args": [ - "format", - "style", - "--verify-no-changes", - "--severity=info", - "--verbosity=detailed" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - }, - "dependsOn": [ - "CSharpier Format", - ".Net Build" - ] - }, - { - "label": "CSharpier Format", - "type": "process", - "command": "dotnet", - "args": [ - "csharpier", - "format", - "--log-level=debug", - "." - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": ".Net Tool Update", - "type": "process", - "command": "dotnet", - "args": [ - "tool", - "update", - "--all" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Husky.Net Run", - "type": "process", - "command": "dotnet", - "args": [ - "husky", - "run" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Build Ubuntu.Rolling Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "buildx", - "build", - "--platform=linux/amd64,linux/arm64", - "--file=./Docker/Ubuntu.Rolling.Dockerfile", - "${workspaceFolder}" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Build Ubuntu.Devel Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "buildx", - "build", - "--platform=linux/amd64,linux/arm64", - "--file=./Docker/Ubuntu.Devel.Dockerfile", - "${workspaceFolder}" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Build all Dockerfiles", - "dependsOrder": "parallel", - "dependsOn": [ - "Build Ubuntu.Rolling Dockerfile", - ], - "problemMatcher": [], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Load Ubuntu.Rolling Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "buildx", - "build", - "--load", - "--platform=linux/amd64", - "--tag=plexcleaner:ubuntu", - "--file=./Docker/Ubuntu.Rolling.Dockerfile", - "${workspaceFolder}" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Load Ubuntu.Devel Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "buildx", - "build", - "--load", - "--platform=linux/amd64", - "--tag=plexcleaner:ubuntu-devel", - "--file=./Docker/Ubuntu.Devel.Dockerfile", - "${workspaceFolder}" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Load all Dockerfiles", - "dependsOrder": "parallel", - "dependsOn": [ - "Load Ubuntu.Rolling Dockerfile", - ], - "problemMatcher": [], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Test Ubuntu.Rolling Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "run", - "-it", - "--rm", - "--name=PlexCleaner-Test-Ubuntu.Rolling", - "plexcleaner:ubuntu", - "/Test/Test.sh" - ], - "dependsOrder": "sequence", - "dependsOn": [ - "Load Ubuntu.Rolling Dockerfile" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Test Ubuntu.Devel Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "run", - "-it", - "--rm", - "--name=PlexCleaner-Test-Ubuntu.Devel", - "plexcleaner:ubuntu-devel", - "/Test/Test.sh" - ], - "dependsOrder": "sequence", - "dependsOn": [ - "Load Ubuntu.Devel Dockerfile" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Test all Dockerfiles", - "dependsOrder": "parallel", - "dependsOn": [ - "Test Ubuntu.Rolling Dockerfile", - ], - "problemMatcher": [], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": ".NET Format", - "type": "process", - "command": "dotnet", - "args": [ - "format", - "--verify-no-changes", - "--severity=info", - "--verbosity=detailed" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": true, - "panel": "dedicated" - } - }, - ] -} +{ + "version": "2.0.0", + "tasks": [ + { + "label": ".Net Build", + "type": "process", + "command": "dotnet", + "args": [ + "build", + "${workspaceFolder}", + "--verbosity=diagnostic" + ], + "group": "build", + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": ".Net Format", + "type": "process", + "command": "dotnet", + "args": [ + "format", + "style", + "--verify-no-changes", + "--severity=info", + "--verbosity=detailed" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + }, + "dependsOn": [ + "CSharpier Format", + ".Net Build" + ] + }, + { + "label": "CSharpier Format", + "type": "process", + "command": "dotnet", + "args": [ + "csharpier", + "format", + "--log-level=debug", + "." + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": ".Net Tool Update", + "type": "process", + "command": "dotnet", + "args": [ + "tool", + "update", + "--all" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Husky.Net Run", + "type": "process", + "command": "dotnet", + "args": [ + "husky", + "run" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": ".Net Outdated Upgrade", + "type": "process", + "command": "dotnet", + "args": [ + "outdated", + "--upgrade:prompt" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Build Dockerfile", + "type": "shell", + "command": "docker", + "args": [ + "buildx", + "build", + "--platform=linux/amd64,linux/arm64", + "--file=./Docker/Dockerfile", + "${workspaceFolder}" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Build all Dockerfiles", + "dependsOrder": "parallel", + "dependsOn": [ + "Build Dockerfile" + ], + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Load Dockerfile", + "type": "shell", + "command": "docker", + "args": [ + "buildx", + "build", + "--load", + "--platform=linux/amd64", + "--tag=plexcleaner:ubuntu", + "--file=./Docker/Dockerfile", + "${workspaceFolder}" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Load all Dockerfiles", + "dependsOrder": "parallel", + "dependsOn": [ + "Load Dockerfile" + ], + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Test Dockerfile", + "type": "shell", + "command": "docker", + "args": [ + "run", + "-it", + "--rm", + "--name=PlexCleaner-Test", + "plexcleaner:ubuntu", + "/Test/Test.sh" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "Load Dockerfile" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Test all Dockerfiles", + "dependsOrder": "parallel", + "dependsOn": [ + "Test Dockerfile" + ], + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + } + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..99126a5c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,64 @@ +# Instructions for AI Coding Agents + +**PlexCleaner** is a C# .NET utility that optimizes media files for Direct Play in Plex, Emby, Jellyfin, etc. + +For comprehensive coding standards and detailed conventions, refer to [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) and [`CODESTYLE.md`](./CODESTYLE.md). + +## Git and Commit Rules + +**These rules are absolute — no exceptions:** + +- **Never make git commits.** AI coding agents cannot produce cryptographically signed commits. All commits must be signed (SSH/GPG) and must be made by the developer. Stage changes with `git add` and leave the commit to the developer. +- **Never force push.** Do not run `git push --force` or `git push --force-with-lease` under any circumstances. Force pushing rewrites shared history and can cause data loss. +- **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction. +- **Staging is the limit.** Prepare and stage file changes; the developer runs `git commit` in their own environment where signing keys are available. + +## Key Requirements for All Projects Derived from This Template + +### Build & Quality Standards + +- **Zero Warnings Policy**: All builds must complete without errors or warnings + - Use `CSharpier Format`, `.Net Format`, and `Husky.Net Run` tasks + +- **Code Analysis**: Enable all .NET analyzers + - `true` + - `latest-all` + +### Project Configuration + +- Common MSBuild properties (`TargetFramework`, `Nullable`, `ImplicitUsings`, `AnalysisLevel`, etc.) + live in `Directory.Build.props` at the solution root. Do not duplicate these in individual `.csproj` + files — only add a property to a `.csproj` when it is project-specific or overrides the shared default. +- All NuGet package versions are centralised in `Directory.Packages.props`. `PackageReference` elements + in `.csproj` files must not include a `Version` attribute. Asset metadata (`PrivateAssets`, + `IncludeAssets`) stays in the `.csproj` `PackageReference` element. + +### Development Environment + +- Target latest .NET SDK (currently .NET 10 with C# 14) +- Support Visual Studio Code (`.code-workspace`) and Visual Studio Community (`.slnx`) +- Support Linux, Windows, and macOS with correct line endings and permissions +- Use `.editorconfig` for style enforcement + +### Project Structure + +- **PlexCleaner**: CLI application +- **PlexCleanerTests**: Unit tests using xUnit and AwesomeAssertions +- **Sandbox**: Sandbox/testing utility project +- **Docker**: Multi-platform Linux containers + +### Testing + +- Use xUnit v3 and AwesomeAssertions +- Organize tests logically in separate files +- Follow Arrange-Act-Assert pattern +- Test naming: `MethodName_Scenario_ExpectedBehavior()` + +## Authoritative References + +For detailed specifications, see: + +- [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - Complete coding conventions and style guide +- [`CODESTYLE.md`](./CODESTYLE.md) - Code style and formatting rules +- [`.editorconfig`](./.editorconfig) - Automated style enforcement +- Project task definitions - `CSharpier Format`, `.Net Build`, `.Net Format`, `.Net Outdated Upgrade`, `Husky.Net Run` diff --git a/CODESTYLE.md b/CODESTYLE.md new file mode 100644 index 00000000..96cd1da8 --- /dev/null +++ b/CODESTYLE.md @@ -0,0 +1,314 @@ +# Code Style and Formatting Rules + +## Build Requirements + +### Zero Warnings Policy + +**CRITICAL**: All builds must complete without warnings. The project enforces this through: + +1. **VS Code tasks** + - `CSharpier Format` → `.Net Build` → `.Net Format` + - `.Net Format` must pass with `--verify-no-changes` before commit + - Command: `dotnet format style --verify-no-changes --severity=info --verbosity=detailed` + +2. **Analyzer configuration** + - `latest-all` + - `true` + - Analyzer severity is `suggestion`, but all warnings must be addressed + +3. **Husky.Net pre-commit hooks** + - Automated checks run before commits + +### Build Tasks + +Available VS Code tasks (use via `run_task` tool): + +- `.Net Build`: Build with diagnostic verbosity +- `.Net Format`: Verify formatting and style (must pass) +- `CSharpier Format`: Auto-format code with CSharpier +- `.Net Tool Update`: Update dotnet tools +- `.Net Outdated Upgrade`: Upgrade outdated NuGet dependencies (interactive prompt) +- `Husky.Net Run`: Run pre-commit hooks manually + +## Tooling and Editor + +### Code Formatting and Tooling + +1. **CSharpier**: Primary code formatter + - Run before committing: `dotnet csharpier format --log-level=debug .` + +2. **dotnet format**: Style verification + - Verify no changes: `dotnet format style --verify-no-changes --severity=info --verbosity=detailed` + +3. **Husky.Net**: Git hooks for automated checks + - Installed as a local dotnet tool (via `dotnet tool restore`) + - Install Git hooks locally with `dotnet husky install` + - Pre-commit hooks run formatting and style checks + +4. **Other tools** + - `dotnet-outdated-tool`: Dependency update checks + - Nerdbank.GitVersioning: Version management + +### Editor Baseline + +1. **Required VS Code extensions**: CSharpier, markdownlint, CSpell +2. **VS Code settings**: Use the workspace settings without overrides + +### Markdown Files + +1. **Linting**: All `.md` files must be linted with the VS Code `markdownlint` extension (local only; no CI) +2. **Zero warnings**: Markdown linting must be error and warning free + +### Spelling + +1. **CSpell**: All spelling checks must be error free using the CSpell VS Code integration +2. **Accepted spellings**: Words must be correctly spelled in US or UK English +3. **Allowed exceptions**: Project-specific terms must be added to the workspace CSpell config + +## Coding Standards and Conventions + +Note: Code snippets are illustrative examples only. Replace namespaces/types to match your project. + +### C# Language Features + +1. **File-scoped namespaces** + + ```csharp + namespace PlexCleaner; + ``` + +2. **Nullable reference types**: Enabled (`enable`) + - Use nullable annotations appropriately + - Use `required` for mandatory properties + +3. **Modern C# features**: Prefer modern language constructs + - Primary constructors when appropriate + - Top-level statements for console apps + - Pattern matching over traditional checks + - Collection expressions when types loosely match + - Extension methods using `extension()` syntax + - Implicit object creation when type is apparent + - Range and index operators + +4. **Expression-bodied members**: Use for applicable members + - Methods, properties, accessors, operators, lambdas, local functions + +5. **`var` keyword**: Do NOT use `var` (always use explicit types) + + ```csharp + // Correct + int count = 42; + string name = "test"; + + // Incorrect + var count = 42; + var name = "test"; + ``` + +### Naming Conventions + +1. **Private fields**: underscore prefix with camelCase + + ```csharp + private readonly HttpClient _httpClient; + private int _counter; + ``` + +2. **Static fields**: `s_` prefix with camelCase + + ```csharp + private static int s_instanceCount; + ``` + +3. **Constants**: PascalCase + + ```csharp + private const int MaxRetries = 3; + ``` + +### Code Structure + +1. **Global usings**: Use `GlobalUsings.cs` for common namespaces + + ```csharp + global using System; + global using System.Net.Http; + global using System.Threading.Tasks; + global using Serilog; + ``` + +2. **Usings placement**: Outside namespace, sorted with `System` directives first + + ```csharp + using System.CommandLine; + using System.Runtime.CompilerServices; + using PlexCleaner; + + namespace PlexCleaner; + ``` + +3. **Braces**: Allman style + + ```csharp + public void Method() + { + if (condition) + { + // code + } + } + ``` + +4. **Indentation** + - C# files: 4 spaces + - XML/csproj files: 2 spaces + - YAML files: 2 spaces + - JSON files: 4 spaces + +5. **Line endings** + - C#, XML, YAML, JSON, Windows scripts: CRLF + - Linux scripts (`.sh`): LF + +6. **`#region`**: Do not use regions. Prefer logical file/folder/namespace organization. +7. **Member ordering (StyleCop SA1201)**: const → static readonly → static fields → instance readonly fields → instance fields → constructors → public (events → properties → indexers → methods → operators) → non-public in same order → nested types + +### Comments and Documentation + +1. **XML documentation** + - `true` + - Missing XML comments for public APIs are suppressed (`.editorconfig`) + - Must document all public surfaces. + - Single-line summaries, additional details in remarks, document input parameters, returns values, exceptions, and add crefs + + ```csharp + /// + /// Example of a single line summary. + /// + /// + /// Additional important details about usage. + /// Multiple lines if needed. + /// + /// + /// The quote category to request + /// + /// + /// A that can be used to cancel the request. + /// + /// + /// A containing the quote text. + /// + /// + /// Thrown when is not a supported value. + /// + public async Task GetQuoteOfTheDayAsync(string category, CancellationToken cancellationToken) {} + ``` + +2. **Code analysis suppressions** + - Do not use `#pragma` sections to disable analyzers + - For one-off cases, use suppression attributes with justifications + - For project-wide suppressions, add rules to `.editorconfig` + + ```csharp + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1034:Nested types should not be visible", + Justification = "https://github.com/dotnet/sdk/issues/51681" + )] + ``` + +### Error Handling and Logging + +1. **Serilog logging**: Use structured logging + + ```csharp + logger.Error(exception, "{Function}", function); + ``` + +2. **Library log configuration**: Libraries must expose logging configuration + - Provide options or settings to supply an `ILoggerFactory` and/or `ILogger` + - Offer a global fallback logger for static usage when needed + +3. **CallerMemberName**: Use for automatic function name tracking + + ```csharp + public bool LogAndPropagate( + Exception exception, + [CallerMemberName] string function = "unknown" + ) + ``` + +4. **Logger extensions**: Use `Extensions.cs` for logger and other extension methods + + ```csharp + extension(ILogger logger) + { + public bool LogAndPropagate(Exception exception, ...) { } + } + ``` + +5. **Exceptions**: Do not swallow exceptions; log and rethrow or translate to a domain-specific exception + +### Code Patterns + +1. **Guard clauses**: Prefer early returns for validation and error handling +2. **Async all the way**: Avoid blocking calls (`.Result`, `.Wait()`); use `async`/`await` +3. **Cancellation tokens**: Accept `CancellationToken` as the last parameter and pass it through +4. **ConfigureAwait**: In library code, use `ConfigureAwait(false)` unless context is required + - Do not call `ConfigureAwait(false)` in xUnit tests (see xUnit1030) +5. **Disposables**: Use `await using` for async disposables; prefer `using` declarations +6. **LINQ vs loops**: Use LINQ for clarity, loops for hot paths or allocations +7. **HTTP**: Reuse `HttpClient` via factory; avoid per-request instantiation +8. **Collections**: Prefer `IReadOnlyList`/`IReadOnlyCollection` for public APIs +9. **Immutability**: Prefer immutable records; use init-only setters when records are not suitable; prefer immutable or frozen collections for read-only data +10. **Exceptions as control flow**: Avoid using exceptions for expected flow +11. **Sealing classes**: Seal classes that are not designed for inheritance +12. **Read-only data**: Use immutable or frozen collections for read-only data sets +13. **Lazy initialization**: Use `Lazy` for static, thread-safe instantiation (e.g., logger factory, HTTP factory) + +### Testing Conventions + +1. **Framework**: xUnit with AwesomeAssertions + + ```csharp + [Fact] + public void MethodName_Scenario_ExpectedBehavior() + { + // Arrange + int expected = 42; + + // Act + int actual = GetValue(); + + // Assert + actual.Should().Be(expected); + } + ``` + +2. **Organization**: Arrange-Act-Assert pattern +3. **Naming**: Descriptive names with underscores +4. **Theory tests**: Use `[Theory]` with `[InlineData]` + +## Project Configuration + +1. **Target framework**: .NET 10.0 (`net10.0`) + +2. **AOT compatibility** + - `true` + - `true` + +3. **Assembly information** + - Use semantic versioning + - Include SourceLink: `true` + - Embed untracked sources: `true` + +4. **Internal visibility**: Use `InternalsVisibleTo` for test access + + ```xml + + + + ``` + +## Best Practices + +1. **Code reviews**: All changes go through pull requests diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..da26fe38 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,14 @@ + + + net10.0 + enable + enable + latest-all + All + true + true + $(MSBuildThisFileDirectory).artifacts + false + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..712c48f3 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Docker/Build.sh b/Docker/Build.sh index 1d43903e..e3d50453 100644 --- a/Docker/Build.sh +++ b/Docker/Build.sh @@ -6,10 +6,16 @@ set -x # Exit on error set -e +# Build the solution +dotnet build ./PlexCleaner/PlexCleaner.csproj + +# Test the solution +dotnet test ./PlexCleanerTests/PlexCleanerTests.csproj + # Build release and debug builds dotnet publish ./PlexCleaner/PlexCleaner.csproj \ --arch $TARGETARCH \ - --output ./Build/Release \ + -property:PublishDir=$(pwd)/Build/Release/ \ --configuration release \ -property:PublishAot=false \ -property:Version=$BUILD_VERSION \ @@ -20,7 +26,7 @@ dotnet publish ./PlexCleaner/PlexCleaner.csproj \ dotnet publish ./PlexCleaner/PlexCleaner.csproj \ --arch $TARGETARCH \ - --output ./Build/Debug \ + -property:PublishDir=$(pwd)/Build/Debug/ \ --configuration debug \ -property:PublishAot=false \ -property:Version=$BUILD_VERSION \ @@ -29,7 +35,7 @@ dotnet publish ./PlexCleaner/PlexCleaner.csproj \ -property:InformationalVersion=$BUILD_INFORMATION_VERSION \ -property:PackageVersion=$BUILD_PACKAGE_VERSION -# Copy build output +# Copy configured build target as default output mkdir -p ./Publish/PlexCleaner/Debug mkdir -p ./Publish/PlexCleaner/Release if [ "$BUILD_CONFIGURATION" = "Debug" ] || [ "$BUILD_CONFIGURATION" = "debug" ] diff --git a/Docker/Ubuntu.Rolling.Dockerfile b/Docker/Dockerfile similarity index 88% rename from Docker/Ubuntu.Rolling.Dockerfile rename to Docker/Dockerfile index ebfb559a..0a36c7bb 100644 --- a/Docker/Ubuntu.Rolling.Dockerfile +++ b/Docker/Dockerfile @@ -15,10 +15,13 @@ # Build Dockerfile # docker buildx create --name "plexcleaner" --use -# docker buildx build --platform linux/amd64,linux/arm64 --file ./Docker/Ubuntu.Rolling.Dockerfile . +# docker buildx build --platform linux/amd64,linux/arm64 --file ./Docker/Dockerfile . + +# Build and log output +# docker buildx build --no-cache --progress=plain --platform linux/amd64 --file ./Docker/Dockerfile . 2>&1 | tee build.log # Test linux/amd64 target -# docker buildx build --load --platform linux/amd64 --tag plexcleaner:ubuntu --file ./Docker/Ubuntu.Rolling.Dockerfile . +# docker buildx build --load --platform linux/amd64 --tag plexcleaner:ubuntu --file ./Docker/Dockerfile . # docker run -it --rm --name PlexCleaner-Test plexcleaner:ubuntu /bin/bash @@ -53,16 +56,15 @@ RUN apt update \ # https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu-install RUN apt install -y --install-suggests dotnet-sdk-10.0 -# Copy source and unit tests +# Copy source COPY ./Samples/. ./Samples/. COPY ./PlexCleanerTests/. ./PlexCleanerTests/. COPY ./PlexCleaner/. ./PlexCleaner/. +COPY ./.editorconfig ./ +COPY ./Directory.Build.props ./ +COPY ./Directory.Packages.props ./ -# Unit Test -COPY --chmod=ug=rwx,o=rx ./Docker/UnitTest.sh ./ -RUN ./UnitTest.sh - -# Build +# Build project COPY --chmod=ug=rwx,o=rx ./Docker/Build.sh ./ RUN ./Build.sh diff --git a/Docker/UnitTest.sh b/Docker/UnitTest.sh deleted file mode 100644 index 8748d2aa..00000000 --- a/Docker/UnitTest.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -# Echo commands -set -x - -# Exit on error -set -e - -# Test -dotnet test ./PlexCleanerTests/PlexCleanerTests.csproj diff --git a/HISTORY.md b/HISTORY.md index 560ffb91..f3c8aa3c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,14 @@ Utility to optimize media files for Direct Play in Plex, Emby, Jellyfin, etc. ## Release History +- Version 3.16: + - Structural changes only, no functional changes. + - Consolidated project structure, build configuration, CI/CD workflows, and Docker configuration across projects. + - Project changes: Added `Directory.Build.props` and `Directory.Packages.props`, refactored `.csproj` files, added `CODESTYLE.md` and `AGENTS.md`. + - Build changes: Enabled `TreatWarningsAsErrors`, `AnalysisLevel=latest-all`, centralized NuGet package versions. + - CI/CD changes: Renamed workflows to kebab-case, restructured into reusable task workflows, consolidated Docker test workflows. + - Docker changes: Merged build and test scripts, renamed Dockerfile. + - Editor changes: Updated `.editorconfig`, `.dockerignore`, VS Code tasks and workspace settings. - Version 3.15: - This is primarily a code refactoring release. - Updated from .NET 9 to .NET 10. diff --git a/LICENSE b/LICENSE index 1f66aab9..fccbd913 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Pieter Viljoen +Copyright (c) 2020-2026 Pieter Viljoen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PlexCleaner.code-workspace b/PlexCleaner.code-workspace index 8eb52a44..6879b8e4 100644 --- a/PlexCleaner.code-workspace +++ b/PlexCleaner.code-workspace @@ -249,26 +249,27 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "csharpier.csharpier-vscode" }, - "[json][jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, "diffEditor.ignoreTrimWhitespace": false, "editor.renderWhitespace": "boundary", "dotnet.formatting.organizeImportsOnFormat": true, "csharp.debug.symbolOptions.searchNuGetOrgSymbolServer": true, "csharp.debug.symbolOptions.searchMicrosoftSymbolServer": true, "files.encoding": "utf8", + "files.trimTrailingWhitespaceInRegexAndStrings": false, + "git.alwaysSignOff": true, "markdown.extension.toc.levels": "2..3" }, "extensions": { "recommendations": [ + "csharpier.csharpier-vscode", "davidanson.vscode-markdownlint", + "editorconfig.editorconfig", + "github.vscode-github-actions", "gruntfuggly.todo-tree", + "ms-azuretools.vscode-docker", "ms-dotnettools.csdevkit", "streetsidesoftware.code-spell-checker", - "editorconfig.editorconfig", - "csharpier.csharpier-vscode", - "github.vscode-github-actions" + "yzhang.markdown-all-in-one" ] } } diff --git a/PlexCleaner.slnx b/PlexCleaner.slnx index 8ab77210..87e6fd95 100644 --- a/PlexCleaner.slnx +++ b/PlexCleaner.slnx @@ -1,14 +1,15 @@ - - - - - - - - - - + + + + + + + + + + + @@ -21,8 +22,7 @@ - - + @@ -50,11 +50,15 @@ - + + + + + diff --git a/PlexCleaner/Bitrate.cs b/PlexCleaner/Bitrate.cs index 985254b0..6f5f6748 100644 --- a/PlexCleaner/Bitrate.cs +++ b/PlexCleaner/Bitrate.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using InsaneGenius.Utilities; using Serilog; diff --git a/PlexCleaner/BitrateInfo.cs b/PlexCleaner/BitrateInfo.cs index 8b4010c0..bb035b39 100644 --- a/PlexCleaner/BitrateInfo.cs +++ b/PlexCleaner/BitrateInfo.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; namespace PlexCleaner; diff --git a/PlexCleaner/CommandLineOptions.cs b/PlexCleaner/CommandLineOptions.cs index 9c8ddc32..f6d65406 100644 --- a/PlexCleaner/CommandLineOptions.cs +++ b/PlexCleaner/CommandLineOptions.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Invocation; using System.CommandLine.Parsing; -using System.Linq; namespace PlexCleaner; diff --git a/PlexCleaner/ConfigFileJsonSchema.cs b/PlexCleaner/ConfigFileJsonSchema.cs index b32406bd..54d50d08 100644 --- a/PlexCleaner/ConfigFileJsonSchema.cs +++ b/PlexCleaner/ConfigFileJsonSchema.cs @@ -7,8 +7,6 @@ // Update the Upgrade() method to handle upgrading from the previous version // Update GlobalUsing.cs global using statements to the latest version -using System; -using System.IO; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Schema; diff --git a/PlexCleaner/Convert.cs b/PlexCleaner/Convert.cs index 70b7a3b2..069c8f96 100644 --- a/PlexCleaner/Convert.cs +++ b/PlexCleaner/Convert.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ConvertOptions.cs b/PlexCleaner/ConvertOptions.cs index 1788d63f..b2e26089 100644 --- a/PlexCleaner/ConvertOptions.cs +++ b/PlexCleaner/ConvertOptions.cs @@ -1,4 +1,3 @@ -using System; using System.Text.Json.Serialization; using Serilog; diff --git a/PlexCleaner/Extensions.cs b/PlexCleaner/Extensions.cs index 44fa5a87..bb4e1c79 100644 --- a/PlexCleaner/Extensions.cs +++ b/PlexCleaner/Extensions.cs @@ -1,4 +1,3 @@ -using System; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/FfMpegBuilder.cs b/PlexCleaner/FfMpegBuilder.cs index b95deed0..89aa80eb 100644 --- a/PlexCleaner/FfMpegBuilder.cs +++ b/PlexCleaner/FfMpegBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/FfMpegIdetInfo.cs b/PlexCleaner/FfMpegIdetInfo.cs index 280deac6..33f455ec 100644 --- a/PlexCleaner/FfMpegIdetInfo.cs +++ b/PlexCleaner/FfMpegIdetInfo.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; diff --git a/PlexCleaner/FfMpegTool.cs b/PlexCleaner/FfMpegTool.cs index 989f8eba..4245d307 100644 --- a/PlexCleaner/FfMpegTool.cs +++ b/PlexCleaner/FfMpegTool.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; using System.Text; using System.Text.RegularExpressions; using CliWrap; diff --git a/PlexCleaner/FfMpegToolJsonSchema.cs b/PlexCleaner/FfMpegToolJsonSchema.cs index 95e494f9..1aeb8903 100644 --- a/PlexCleaner/FfMpegToolJsonSchema.cs +++ b/PlexCleaner/FfMpegToolJsonSchema.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/FfProbeBuilder.cs b/PlexCleaner/FfProbeBuilder.cs index ae8077e5..b33ee588 100644 --- a/PlexCleaner/FfProbeBuilder.cs +++ b/PlexCleaner/FfProbeBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/FfProbeTool.cs b/PlexCleaner/FfProbeTool.cs index b8d7e76b..3e90f354 100644 --- a/PlexCleaner/FfProbeTool.cs +++ b/PlexCleaner/FfProbeTool.cs @@ -1,14 +1,8 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Stream; -using System.Threading; -using System.Threading.Tasks; using CliWrap; using CliWrap.Buffered; using Serilog; diff --git a/PlexCleaner/HandBrakeBuilder.cs b/PlexCleaner/HandBrakeBuilder.cs index 7230fda4..30612adc 100644 --- a/PlexCleaner/HandBrakeBuilder.cs +++ b/PlexCleaner/HandBrakeBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/HandBrakeTool.cs b/PlexCleaner/HandBrakeTool.cs index ae62e4f8..cf6c2172 100644 --- a/PlexCleaner/HandBrakeTool.cs +++ b/PlexCleaner/HandBrakeTool.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; using System.Text.RegularExpressions; using CliWrap; using CliWrap.Buffered; diff --git a/PlexCleaner/JsonSerialization.cs b/PlexCleaner/JsonSerialization.cs index 8b6ae494..fd483509 100644 --- a/PlexCleaner/JsonSerialization.cs +++ b/PlexCleaner/JsonSerialization.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/KeepAwake.cs b/PlexCleaner/KeepAwake.cs index 6b4d1d6c..2f98ab73 100644 --- a/PlexCleaner/KeepAwake.cs +++ b/PlexCleaner/KeepAwake.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.InteropServices; using System.Timers; diff --git a/PlexCleaner/Language.cs b/PlexCleaner/Language.cs index 6ce4c42a..0c6989d8 100644 --- a/PlexCleaner/Language.cs +++ b/PlexCleaner/Language.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using ptr727.LanguageTags; namespace PlexCleaner; diff --git a/PlexCleaner/MediaInfoBuilder.cs b/PlexCleaner/MediaInfoBuilder.cs index 8b00b698..083b38d6 100644 --- a/PlexCleaner/MediaInfoBuilder.cs +++ b/PlexCleaner/MediaInfoBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/MediaInfoTool.cs b/PlexCleaner/MediaInfoTool.cs index fb02e3f9..85a2d8c2 100644 --- a/PlexCleaner/MediaInfoTool.cs +++ b/PlexCleaner/MediaInfoTool.cs @@ -1,7 +1,5 @@ -using System; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Text.RegularExpressions; using CliWrap; using CliWrap.Buffered; diff --git a/PlexCleaner/MediaInfoToolJsonSchema.cs b/PlexCleaner/MediaInfoToolJsonSchema.cs index cd7eb780..87ea29c9 100644 --- a/PlexCleaner/MediaInfoToolJsonSchema.cs +++ b/PlexCleaner/MediaInfoToolJsonSchema.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/MediaInfoToolXmlSchema.cs b/PlexCleaner/MediaInfoToolXmlSchema.cs index 65c6bea5..34f1df2b 100644 --- a/PlexCleaner/MediaInfoToolXmlSchema.cs +++ b/PlexCleaner/MediaInfoToolXmlSchema.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Xml.Serialization; // https://github.com/MediaArea/MediaAreaXml/blob/master/mediainfo.xsd diff --git a/PlexCleaner/MediaInfoXmlParser.cs b/PlexCleaner/MediaInfoXmlParser.cs index 28da558c..6b64c2b6 100644 --- a/PlexCleaner/MediaInfoXmlParser.cs +++ b/PlexCleaner/MediaInfoXmlParser.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text; using System.Text.Json; using System.Xml; diff --git a/PlexCleaner/MediaProps.cs b/PlexCleaner/MediaProps.cs index d2efc004..5c691e4e 100644 --- a/PlexCleaner/MediaProps.cs +++ b/PlexCleaner/MediaProps.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/MediaTool.cs b/PlexCleaner/MediaTool.cs index 94b0a1ce..0a25410c 100644 --- a/PlexCleaner/MediaTool.cs +++ b/PlexCleaner/MediaTool.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.IO; using System.Runtime.InteropServices; using System.Text; -using System.Threading; using CliWrap; using CliWrap.Buffered; using Serilog; diff --git a/PlexCleaner/MediaToolInfo.cs b/PlexCleaner/MediaToolInfo.cs index 0dfbe21c..9dfe4abc 100644 --- a/PlexCleaner/MediaToolInfo.cs +++ b/PlexCleaner/MediaToolInfo.cs @@ -1,4 +1,3 @@ -using System; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/MkvMergeBuilder.cs b/PlexCleaner/MkvMergeBuilder.cs index 322667ed..a532f5ef 100644 --- a/PlexCleaner/MkvMergeBuilder.cs +++ b/PlexCleaner/MkvMergeBuilder.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.Linq; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/MkvMergeTool.cs b/PlexCleaner/MkvMergeTool.cs index 01e835d9..1b2021fd 100644 --- a/PlexCleaner/MkvMergeTool.cs +++ b/PlexCleaner/MkvMergeTool.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; using System.Text.RegularExpressions; using CliWrap; using CliWrap.Buffered; diff --git a/PlexCleaner/MkvPropEditBuilder.cs b/PlexCleaner/MkvPropEditBuilder.cs index 4efa4660..1205ed60 100644 --- a/PlexCleaner/MkvPropEditBuilder.cs +++ b/PlexCleaner/MkvPropEditBuilder.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/MkvPropEditTool.cs b/PlexCleaner/MkvPropEditTool.cs index f3613eac..612ea560 100644 --- a/PlexCleaner/MkvPropEditTool.cs +++ b/PlexCleaner/MkvPropEditTool.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.Linq; using CliWrap; using CliWrap.Buffered; diff --git a/PlexCleaner/MkvToolJsonSchema.cs b/PlexCleaner/MkvToolJsonSchema.cs index 67ca1803..d880cb90 100644 --- a/PlexCleaner/MkvToolJsonSchema.cs +++ b/PlexCleaner/MkvToolJsonSchema.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/Monitor.cs b/PlexCleaner/Monitor.cs index 96500cd1..49d12609 100644 --- a/PlexCleaner/Monitor.cs +++ b/PlexCleaner/Monitor.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 3a61e169..d6dfe983 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -1,7 +1,6 @@ - + True - latest PlexCleaner.ico PlexCleaner 1.1.1.0 @@ -11,13 +10,9 @@ Utility to optimize media files for Direct Play in Plex, Emby, Jellyfin, etc. Linux true - true - true - true 1.1.1.0 true en - enable Exe ptr727.PlexCleaner MIT @@ -30,7 +25,6 @@ PlexCleaner PlexCleaner.Program snupkg - net10.0 1.1.1.0-prerelease @@ -44,17 +38,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/PlexCleaner/Process.cs b/PlexCleaner/Process.cs index ffe5ac58..bde7d927 100644 --- a/PlexCleaner/Process.cs +++ b/PlexCleaner/Process.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ProcessDriver.cs b/PlexCleaner/ProcessDriver.cs index 01702b81..a38253b7 100644 --- a/PlexCleaner/ProcessDriver.cs +++ b/PlexCleaner/ProcessDriver.cs @@ -1,10 +1,5 @@ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ProcessFile.cs b/PlexCleaner/ProcessFile.cs index 5ca65245..a619ef04 100644 --- a/PlexCleaner/ProcessFile.cs +++ b/PlexCleaner/ProcessFile.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ProcessOptions.cs b/PlexCleaner/ProcessOptions.cs index 3ff07c9b..a141c9ee 100644 --- a/PlexCleaner/ProcessOptions.cs +++ b/PlexCleaner/ProcessOptions.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using Serilog; diff --git a/PlexCleaner/ProcessResultJsonSchema.cs b/PlexCleaner/ProcessResultJsonSchema.cs index 57591306..b7a58c4d 100644 --- a/PlexCleaner/ProcessResultJsonSchema.cs +++ b/PlexCleaner/ProcessResultJsonSchema.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/Program.cs b/PlexCleaner/Program.cs index 6e9145bf..9b34f094 100644 --- a/PlexCleaner/Program.cs +++ b/PlexCleaner/Program.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; -using System.Net.Http; using System.Net.Http.Headers; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using InsaneGenius.Utilities; using Serilog; using Serilog.Debugging; diff --git a/PlexCleaner/SelectMediaProps.cs b/PlexCleaner/SelectMediaProps.cs index 545ca87c..b3d56b94 100644 --- a/PlexCleaner/SelectMediaProps.cs +++ b/PlexCleaner/SelectMediaProps.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace PlexCleaner; diff --git a/PlexCleaner/SevenZipBuilder.cs b/PlexCleaner/SevenZipBuilder.cs index 6e273a3c..98d69e89 100644 --- a/PlexCleaner/SevenZipBuilder.cs +++ b/PlexCleaner/SevenZipBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/SevenZipTool.cs b/PlexCleaner/SevenZipTool.cs index 2ef2653e..76c08146 100644 --- a/PlexCleaner/SevenZipTool.cs +++ b/PlexCleaner/SevenZipTool.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using CliWrap; diff --git a/PlexCleaner/SidecarFile.cs b/PlexCleaner/SidecarFile.cs index 78df1aa2..44b0f28f 100644 --- a/PlexCleaner/SidecarFile.cs +++ b/PlexCleaner/SidecarFile.cs @@ -1,7 +1,5 @@ -using System; using System.Buffers; using System.Diagnostics; -using System.IO; using System.Security.Cryptography; using InsaneGenius.Utilities; using Serilog; diff --git a/PlexCleaner/SidecarFileJsonSchema.cs b/PlexCleaner/SidecarFileJsonSchema.cs index 1855edb5..22aaae7b 100644 --- a/PlexCleaner/SidecarFileJsonSchema.cs +++ b/PlexCleaner/SidecarFileJsonSchema.cs @@ -1,7 +1,5 @@ // See ConfigFileJsonSchema.cs for schema update steps -using System; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using InsaneGenius.Utilities; diff --git a/PlexCleaner/SubtitleProps.cs b/PlexCleaner/SubtitleProps.cs index bdcd61e0..9f0bbbe5 100644 --- a/PlexCleaner/SubtitleProps.cs +++ b/PlexCleaner/SubtitleProps.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using Serilog; diff --git a/PlexCleaner/TagMapSet.cs b/PlexCleaner/TagMapSet.cs index e966b57e..f300a643 100644 --- a/PlexCleaner/TagMapSet.cs +++ b/PlexCleaner/TagMapSet.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ToolInfoJsonSchema.cs b/PlexCleaner/ToolInfoJsonSchema.cs index fbfaf694..902420a4 100644 --- a/PlexCleaner/ToolInfoJsonSchema.cs +++ b/PlexCleaner/ToolInfoJsonSchema.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using Serilog; diff --git a/PlexCleaner/Tools.cs b/PlexCleaner/Tools.cs index 7ef087b8..4e6f7202 100644 --- a/PlexCleaner/Tools.cs +++ b/PlexCleaner/Tools.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Net.Http; using System.Runtime.InteropServices; -using System.Threading.Tasks; using InsaneGenius.Utilities; using Serilog; diff --git a/PlexCleaner/TrackProps.cs b/PlexCleaner/TrackProps.cs index 6839fe8b..544a8be9 100644 --- a/PlexCleaner/TrackProps.cs +++ b/PlexCleaner/TrackProps.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.Linq; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/VerifyOptions.cs b/PlexCleaner/VerifyOptions.cs index 0b898a8b..128d5582 100644 --- a/PlexCleaner/VerifyOptions.cs +++ b/PlexCleaner/VerifyOptions.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; diff --git a/PlexCleaner/VideoProps.cs b/PlexCleaner/VideoProps.cs index 3eddde5a..47bc98d4 100644 --- a/PlexCleaner/VideoProps.cs +++ b/PlexCleaner/VideoProps.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Serilog; // TODO: Find a better way to create profile levels diff --git a/PlexCleanerTests/CommandLineTests.cs b/PlexCleanerTests/CommandLineTests.cs index 4a215a0e..ea13e91b 100644 --- a/PlexCleanerTests/CommandLineTests.cs +++ b/PlexCleanerTests/CommandLineTests.cs @@ -4,10 +4,8 @@ namespace PlexCleanerTests; -public class CommandLineTests(PlexCleanerFixture fixture) +public class CommandLineTests { - private readonly PlexCleanerFixture _fixture = fixture; - [Theory] [InlineData( "removeclosedcaptions", diff --git a/PlexCleanerTests/FfMpegIdetInfoSerializer.cs b/PlexCleanerTests/FfMpegIdetInfoSerializer.cs index 2fa8da30..a2d32b05 100644 --- a/PlexCleanerTests/FfMpegIdetInfoSerializer.cs +++ b/PlexCleanerTests/FfMpegIdetInfoSerializer.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using PlexCleaner; @@ -10,8 +9,8 @@ public class FfMpegIdetInfoSerializer : IXunitSerializer { public bool IsSerializable( Type type, - object value, - [NotNullWhen(false)] out string failureReason + object? value, + [NotNullWhen(false)] out string? failureReason ) { if (type == typeof(FfMpegIdetInfo) && value is FfMpegIdetInfo) diff --git a/PlexCleanerTests/FfMpegIdetParsingTests.cs b/PlexCleanerTests/FfMpegIdetParsingTests.cs index a0418d19..7bc35c64 100644 --- a/PlexCleanerTests/FfMpegIdetParsingTests.cs +++ b/PlexCleanerTests/FfMpegIdetParsingTests.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using AwesomeAssertions; @@ -11,10 +10,8 @@ namespace PlexCleanerTests; -public class FfMpegIdetParsingTests(PlexCleanerFixture fixture) +public class FfMpegIdetParsingTests { - private readonly PlexCleanerFixture _fixture = fixture; - public static TheoryData Data => new() { diff --git a/PlexCleanerTests/FileNameEscapingTests.cs b/PlexCleanerTests/FileNameEscapingTests.cs index d93daf09..26c3eee3 100644 --- a/PlexCleanerTests/FileNameEscapingTests.cs +++ b/PlexCleanerTests/FileNameEscapingTests.cs @@ -3,10 +3,8 @@ namespace PlexCleanerTests; -public class FileNameEscapingTests(PlexCleanerFixture fixture) +public class FileNameEscapingTests { - private readonly PlexCleanerFixture _fixture = fixture; - [Theory] [InlineData(@"\", @"/")] [InlineData(@":", @"\\:")] diff --git a/PlexCleanerTests/PlexCleanerFixture.cs b/PlexCleanerTests/PlexCleanerFixture.cs index 21de8476..ac55a57f 100644 --- a/PlexCleanerTests/PlexCleanerFixture.cs +++ b/PlexCleanerTests/PlexCleanerFixture.cs @@ -1,7 +1,5 @@ -using System; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Reflection; using InsaneGenius.Utilities; using PlexCleaner; @@ -18,7 +16,7 @@ namespace PlexCleanerTests; // One instance for all tests in the assembly -public class PlexCleanerFixture : IDisposable +public sealed class PlexCleanerFixture : IDisposable { internal static string GetSamplesAbsoluteDirectory() { @@ -26,9 +24,9 @@ internal static string GetSamplesAbsoluteDirectory() const string samplesDirectory = "../../../../Samples/PlexCleaner"; // Get absolute path - Assembly entryAssembly = Assembly.GetEntryAssembly(); + Assembly entryAssembly = Assembly.GetEntryAssembly()!; Debug.Assert(entryAssembly != null); - string assemblyDirectory = Path.GetDirectoryName(entryAssembly.Location); + string assemblyDirectory = Path.GetDirectoryName(entryAssembly.Location)!; Debug.Assert(!string.IsNullOrEmpty(assemblyDirectory)); return Path.GetFullPath(Path.Combine(assemblyDirectory, samplesDirectory)); } @@ -74,6 +72,16 @@ public string GetSampleFilePath(string fileName) => } // One instance per test and copy of samples in temp directory +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1063:Implement IDisposable Correctly", + Justification = "Test fixture does not need the full IDisposable pattern" +)] +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1031:Do not catch general exception types", + Justification = "Test fixture cleanup must not throw" +)] public class SamplesFixture : IDisposable { public SamplesFixture() @@ -118,7 +126,7 @@ string sourceFilePath in Directory.EnumerateFiles( { string relativePath = Path.GetRelativePath(sourceSamplesDirectory, sourceFilePath); string destFilePath = Path.Combine(GetSamplesDirectory, relativePath); - string destDirPath = Path.GetDirectoryName(destFilePath); + string destDirPath = Path.GetDirectoryName(destFilePath)!; if (!string.IsNullOrEmpty(destDirPath) && !Directory.Exists(destDirPath)) { diff --git a/PlexCleanerTests/PlexCleanerTests.csproj b/PlexCleanerTests/PlexCleanerTests.csproj index eae9a7e1..a1a3cfcc 100644 --- a/PlexCleanerTests/PlexCleanerTests.csproj +++ b/PlexCleanerTests/PlexCleanerTests.csproj @@ -1,16 +1,17 @@ - net10.0 + true - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/PlexCleanerTests/VersionParsingTests.cs b/PlexCleanerTests/VersionParsingTests.cs index 7400cceb..4dcbc482 100644 --- a/PlexCleanerTests/VersionParsingTests.cs +++ b/PlexCleanerTests/VersionParsingTests.cs @@ -4,10 +4,8 @@ namespace PlexCleanerTests; -public class VersionParsingTests(PlexCleanerFixture fixture) +public class VersionParsingTests { - private readonly PlexCleanerFixture _fixture = fixture; - [Theory] [InlineData( "ffmpeg version 4.3.1-2020-11-19-full_build-www.gyan.dev Copyright (c) 2000-2020 the FFmpeg developers", diff --git a/PlexCleanerTests/WildcardTests.cs b/PlexCleanerTests/WildcardTests.cs index 5adc0256..86f5becb 100644 --- a/PlexCleanerTests/WildcardTests.cs +++ b/PlexCleanerTests/WildcardTests.cs @@ -3,10 +3,8 @@ namespace PlexCleanerTests; -public class WildcardTests(PlexCleanerFixture fixture) +public class WildcardTests { - private readonly PlexCleanerFixture _fixture = fixture; - [Theory] [InlineData("*.fuse_hidden*", "Foo.fuse_hidden1234", true)] [InlineData("*.fuse_hidden*", "fuse_hidden.foo", false)] diff --git a/README.md b/README.md index 0d65b4ad..4d13a6d5 100644 --- a/README.md +++ b/README.md @@ -24,20 +24,12 @@ Utility to optimize media files for Direct Play in Plex, Emby, Jellyfin, etc. ### Release Notes -**Version: 3.15**: +**Version: 3.16**: **Summary:** -- Updated from .NET 9 to .NET 10. -- Refactored code to support Nullable types and Native AOT. -- Changed MediaInfo output from XML to JSON for AOT compatibility. -- Documentation structure update. - -> **⚠️ Docker Breaking Changes:** -> -> - Only `ubuntu:rolling` images are published (Alpine and Debian discontinued). -> - Only `linux/amd64` and `linux/arm64` architectures supported (`linux/arm/v7` discontinued). -> - Update compose files: Use `docker.io/ptr727/plexcleaner:latest` (Based on `ubuntu:rolling`). +- Structural changes only, no functional changes. +- Consolidated project structure, build configuration, CI/CD workflows, and Docker configuration across projects. See [Release History](./HISTORY.md) for complete release notes and older versions. @@ -998,7 +990,7 @@ Licensed under the [MIT License][license-link]\ [docker-develop-version-shield]: https://img.shields.io/docker/v/ptr727/plexcleaner/develop?label=Docker%20Develop&logo=docker&color=orange [docker-latest-version-shield]: https://img.shields.io/docker/v/ptr727/plexcleaner/latest?label=Docker%20Latest&logo=docker [docker-link]: https://hub.docker.com/r/ptr727/plexcleaner -[docker-status-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/PlexCleaner/BuildDockerPush.yml?logo=github&label=Docker%20Build +[docker-status-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/PlexCleaner/publish-periodic-docker-release.yml?logo=github&label=Docker%20Build [github-link]: https://github.com/ptr727/PlexCleaner [plexcleaner-hub-link]: https://hub.docker.com/r/ptr727/plexcleaner [issues-link]: https://github.com/ptr727/PlexCleaner/issues @@ -1008,7 +1000,7 @@ Licensed under the [MIT License][license-link]\ [license-link]: ./LICENSE [license-shield]: https://img.shields.io/github/license/ptr727/PlexCleaner?label=License [pre-release-version-shield]: https://img.shields.io/github/v/release/ptr727/PlexCleaner?include_prereleases&label=GitHub%20Pre-Release&logo=github -[release-status-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/PlexCleaner/BuildGitHubRelease.yml?logo=github&label=Releases%20Build +[release-status-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/PlexCleaner/publish-release.yml?logo=github&label=Releases%20Build [release-version-shield]: https://img.shields.io/github/v/release/ptr727/PlexCleaner?logo=github&label=GitHub%20Release [releases-link]: https://github.com/ptr727/PlexCleaner/releases [ubuntu-hub-link]: https://hub.docker.com/_/ubuntu diff --git a/Sandbox/Program.cs b/Sandbox/Program.cs index 7f81e2b5..31946031 100644 --- a/Sandbox/Program.cs +++ b/Sandbox/Program.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading.Tasks; using InsaneGenius.Utilities; using PlexCleaner; using Serilog; @@ -56,11 +52,11 @@ public static async Task Main(string[] args) LogOptions.Logger = Log.Logger; // Get settings - Dictionary settings = null; + Dictionary? settings = null; if (GetSettingsFilePath(JsonConfigFile) is { } settingsPath) { await using FileStream jsonStream = File.OpenRead(settingsPath); - settings = JsonSerializer.Deserialize( + settings = await JsonSerializer.DeserializeAsync( jsonStream, ConfigJsonContext.Default.DictionaryStringJsonElement ); @@ -68,7 +64,7 @@ public static async Task Main(string[] args) } // Derive from Program and implement Sandbox() - TestSomething program = new(settings); + TestSomething program = new(settings ?? []); int ret = await program.Sandbox(args); // Done @@ -93,7 +89,7 @@ public static void SetRuntimeOptions() : 1; } - public static string GetSettingsFilePath(string fileName) + public static string? GetSettingsFilePath(string fileName) { // Load settings file from current working directory string settingsPath = Path.GetFullPath(fileName); diff --git a/Sandbox/Sandbox.csproj b/Sandbox/Sandbox.csproj index e661ff8b..e103c99e 100644 --- a/Sandbox/Sandbox.csproj +++ b/Sandbox/Sandbox.csproj @@ -1,11 +1,10 @@ Exe - net10.0 false - + diff --git a/Sandbox/TestSomething.cs b/Sandbox/TestSomething.cs index 736e70b0..a9da6f72 100644 --- a/Sandbox/TestSomething.cs +++ b/Sandbox/TestSomething.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; using System.Text.Json; -using System.Threading.Tasks; namespace Sandbox; diff --git a/version.json b/version.json index 286b9f36..a79bea72 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "3.15", + "version": "3.16", "publicReleaseRefSpec": [ "^refs/heads/main$" ], From 7072c1a5b903c247434af8166fae5f794afdccfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 02:05:21 +0000 Subject: [PATCH 25/28] Bump the actions-deps group across 1 directory with 3 updates (#683) Bumps the actions-deps group with 3 updates in the / directory: [docker/login-action](https://github.com/docker/login-action), [softprops/action-gh-release](https://github.com/softprops/action-gh-release) and [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata). Updates `docker/login-action` from 3 to 4 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3...v4) Updates `softprops/action-gh-release` from 2 to 3 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3) Updates `dependabot/fetch-metadata` from 2 to 3 - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps - dependency-name: softprops/action-gh-release dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps - dependency-name: dependabot/fetch-metadata dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-docker-task.yml | 2 +- .github/workflows/build-release-task.yml | 2 +- .github/workflows/merge-bot-pull-request.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-docker-task.yml b/.github/workflows/build-docker-task.yml index 7ee42721..cc9e7b68 100644 --- a/.github/workflows/build-docker-task.yml +++ b/.github/workflows/build-docker-task.yml @@ -47,7 +47,7 @@ jobs: # Always login to Docker Hub, not just on push, to benefit from # higher rate limits with a Docker subscription for pulls and cache - name: Login to Docker Hub step - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} diff --git a/.github/workflows/build-release-task.yml b/.github/workflows/build-release-task.yml index f3bf9ecd..6ae18c07 100644 --- a/.github/workflows/build-release-task.yml +++ b/.github/workflows/build-release-task.yml @@ -52,7 +52,7 @@ jobs: path: ./Publish - name: Create GitHub release step - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: generate_release_notes: true tag_name: ${{ needs.get-version.outputs.SemVer2 }} diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index d45b39e1..a5656d24 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -25,7 +25,7 @@ jobs: - name: Get dependabot metadata step id: metadata - uses: dependabot/fetch-metadata@v2 + uses: dependabot/fetch-metadata@v3 with: github-token: "${{ secrets.GITHUB_TOKEN }}" From 793112d66a4d5d80cd6429c415b14740a994ba5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 05:44:15 +0000 Subject: [PATCH 26/28] Bump the nuget-deps group with 1 update (#684) Bumps ptr727.LanguageTags from 1.2.26 to 1.2.29 --- updated-dependencies: - dependency-name: ptr727.LanguageTags dependency-version: 1.2.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 712c48f3..da326ad4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ - + From 8afb8776f863bfd31cca4a7284d37bb92b8b6597 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 10:59:13 -0700 Subject: [PATCH 27/28] Bump the nuget-deps group with 4 updates (#690) Bumps coverlet.collector from 8.0.1 to 10.0.0 Bumps Microsoft.NET.Test.Sdk from 18.4.0 to 18.5.1 Bumps Microsoft.SourceLink.GitHub from 10.0.202 to 10.0.203 Bumps System.CommandLine from 2.0.6 to 2.0.7 --- updated-dependencies: - dependency-name: coverlet.collector dependency-version: 10.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: nuget-deps - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.5.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-deps - dependency-name: Microsoft.SourceLink.GitHub dependency-version: 10.0.203 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps - dependency-name: System.CommandLine dependency-version: 2.0.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index da326ad4..c8dc0620 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,17 +2,17 @@ - + - - + + - + From 478e0a7542a4b03c7ea5d18757057a68de82010a Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Thu, 7 May 2026 13:38:49 -0700 Subject: [PATCH 28/28] Align dependabot, merge-bot, release flow, and SHA-pin all actions (#692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Align dependabot, merge-bot, and release flow for both branches - dependabot.yml: also target develop so both auto-publishing branches stay on the same baseline. - merge-bot-pull-request.yml: branch-aware merge method (squash for develop, merge for main) and App-token auth so post-merge pushes trigger publish-release and publish-periodic-docker-release on develop instead of being skipped by GitHub's recursion guard. - build-release-task.yml: set target_commitish=github.sha so release tags land on the triggering commit, not the repo default branch; fail_on_unmatched_files; pin softprops/action-gh-release to v3.0.0. - AGENTS.md: document the feature->develop->main flow, develop->main promotion via merge commit, dual-publish release flow, dependabot rationale, and merge-bot design. - copilot-instructions.md: terse summary section pointing at AGENTS.md. Co-Authored-By: Claude Opus 4.7 (1M context) * Pin GitHub Actions to commit SHAs across all workflows Replace floating major-version refs (@v5/@v6/@v7/@v8/@v3/@v4/@v1) and the @master ref on dotnet/nbgv with full commit SHAs plus version comments, matching the pattern already used for create-github-app-token, fetch-metadata, and softprops/action-gh-release. Pins make supply-chain attacks via tag mutation impossible and let dependabot bump them mechanically. Also drops the verbose target_commitish rationale comment from build-release-task.yml; the field is self-explanatory in this repo's context. Co-Authored-By: Claude Opus 4.7 (1M context) * Document GitHub Actions SHA-pinning policy The previous commit pinned every third-party action to a commit SHA but didn't capture the rule itself. Add a "GitHub Actions pinning" section to AGENTS.md explaining what to pin, why floating tags are unsafe, and how to resolve a SHA when adding a new action. Add a one-line summary to copilot-instructions.md so AI agents don't reintroduce floating refs in suggestions. Co-Authored-By: Claude Opus 4.7 (1M context) * Re-indent publish-periodic-docker-release.yml step lists Every other workflow file in the repo uses the over-indented YAML style where list items under `steps:` are at parent_indent+2 (e.g., `steps:` at column 4, `- name:` at column 6). This file alone used the compact style with both at the same column. Both are valid YAML and GitHub Actions accepts either, but consistency matters more than syntactic flexibility. Bring this file in line with the other six. Co-Authored-By: Claude Opus 4.7 (1M context) * Document Dependabot secret duplication and tag-comment convention Two clarifications surfaced by Copilot review on PR #692: 1. The merge-bot uses an App token minted from CODEGEN_APP_* secrets. For Dependabot-authored pull_request events, GitHub only exposes secrets from the Dependabot namespace (Settings → Secrets → Dependabot), not the regular Actions namespace. The secrets must exist in both, or the App-token step gets empty inputs at runtime. This is non-obvious and worth calling out explicitly so future maintainers don't strip the Dependabot duplicate as redundant. 2. The SHA-pinning comment convention is "match the upstream release tag" — usually # vX.Y.Z, but # v3 (or # master) when upstream only publishes major-only / branch tags. Don't fabricate a semver suffix; use what gh api repos///releases/latest returns as tag_name. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .github/copilot-instructions.md | 12 +++ .github/dependabot.yml | 56 +++++++---- .github/workflows/build-datebadge-task.yml | 2 +- .github/workflows/build-docker-task.yml | 10 +- .github/workflows/build-executable-task.yml | 10 +- .github/workflows/build-release-task.yml | 10 +- .github/workflows/get-version-task.yml | 6 +- .github/workflows/merge-bot-pull-request.yml | 51 ++++++++-- .../publish-periodic-docker-release.yml | 98 +++++++++---------- .github/workflows/test-release-task.yml | 4 +- AGENTS.md | 54 ++++++++++ 11 files changed, 217 insertions(+), 96 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 30935153..b6863ac2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -13,6 +13,18 @@ PlexCleaner is a .NET 10.0 CLI utility that optimizes media files for Direct Pla The tool orchestrates external media processing tools (FFmpeg, HandBrake, MkvToolNix, MediaInfo, 7-Zip) via CLI wrappers. +## Branching, Releases, and Bot Behavior + +For full rationale see [`AGENTS.md`](../AGENTS.md). Quick rules: + +- `feature → develop → main`. PRs only. +- Develop accepts **squash merges only**; main accepts **merge commits only**. Don't suggest rebase-merge — it's disabled at the repo level. +- Both branches **auto-publish on push**: develop produces NBGV prereleases (`X.Y.Z-g{sha}`) tagged `develop` on Docker Hub; main produces stable releases (`X.Y.Z`) tagged `latest`. +- Dependabot targets **both** `main` and `develop` with the same ecosystems; major NuGet bumps gate on human review, everything else auto-merges via App-token-driven merge-bot. +- Every third-party GitHub Action is pinned to a full commit SHA with a `# vX.Y.Z` comment. Don't introduce `@v6` / `@main` / `@master` floating refs. +- Don't recommend `git push --force` or `--force-with-lease`; both rulesets enforce `non_fast_forward`. +- `version.json`'s `publicReleaseRefSpec` is `^refs/heads/main$` — bumping the base `version` field is the only manual versioning action. + ## Documentation User-facing documentation is organized as follows: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5d47fa7e..152efecb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,21 +3,41 @@ version: 2 updates: # main -- package-ecosystem: "nuget" - target-branch: "main" - directory: "/" - schedule: - interval: "daily" - groups: - nuget-deps: - patterns: - - "*" -- package-ecosystem: "github-actions" - target-branch: "main" - directory: "/" - schedule: - interval: "daily" - groups: - actions-deps: - patterns: - - "*" + - package-ecosystem: "nuget" + target-branch: "main" + directory: "/" + schedule: + interval: "daily" + groups: + nuget-deps: + patterns: + - "*" + - package-ecosystem: "github-actions" + target-branch: "main" + directory: "/" + schedule: + interval: "daily" + groups: + actions-deps: + patterns: + - "*" + + # develop + - package-ecosystem: "nuget" + target-branch: "develop" + directory: "/" + schedule: + interval: "daily" + groups: + nuget-deps: + patterns: + - "*" + - package-ecosystem: "github-actions" + target-branch: "develop" + directory: "/" + schedule: + interval: "daily" + groups: + actions-deps: + patterns: + - "*" diff --git a/.github/workflows/build-datebadge-task.yml b/.github/workflows/build-datebadge-task.yml index fc3355a6..e6cb9dfc 100644 --- a/.github/workflows/build-datebadge-task.yml +++ b/.github/workflows/build-datebadge-task.yml @@ -17,7 +17,7 @@ jobs: - name: Build BYOB date badge step if: ${{ github.ref_name == 'main' }} - uses: RubbaBoy/BYOB@v1 + uses: RubbaBoy/BYOB@24f464284c1fd32028524b59607d417a2e36fee7 # v1.3.0 with: name: lastbuild label: "Last Build" diff --git a/.github/workflows/build-docker-task.yml b/.github/workflows/build-docker-task.yml index cc9e7b68..922af0fe 100644 --- a/.github/workflows/build-docker-task.yml +++ b/.github/workflows/build-docker-task.yml @@ -32,28 +32,28 @@ jobs: steps: - name: Checkout step - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup QEMU step - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 with: platforms: linux/amd64,linux/arm64 - name: Setup Buildx step - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 with: platforms: linux/amd64,linux/arm64 # Always login to Docker Hub, not just on push, to benefit from # higher rate limits with a Docker subscription for pulls and cache - name: Login to Docker Hub step - uses: docker/login-action@v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Docker build and push step - uses: docker/build-push-action@v7 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: context: . push: ${{ inputs.push }} diff --git a/.github/workflows/build-executable-task.yml b/.github/workflows/build-executable-task.yml index 06679136..d85fd93d 100644 --- a/.github/workflows/build-executable-task.yml +++ b/.github/workflows/build-executable-task.yml @@ -25,12 +25,12 @@ jobs: steps: - name: Setup .NET SDK step - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: dotnet-version: 10.x - name: Checkout code step - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build executable project step run: | @@ -46,7 +46,7 @@ jobs: -property:PackageVersion=${{ needs.get-version.outputs.SemVer2 }} - name: Upload matrix build artifacts step - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: publish-${{ matrix.runtime }} path: ${{ runner.temp }}/publish @@ -61,7 +61,7 @@ jobs: steps: - name: Download matrix build artifacts step - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: publish-* merge-multiple: true @@ -72,7 +72,7 @@ jobs: - name: Upload build artifacts step id: artifact-upload-step - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: executable-build path: ${{ runner.temp }}/PlexCleaner.7z diff --git a/.github/workflows/build-release-task.yml b/.github/workflows/build-release-task.yml index 6ae18c07..e49a381d 100644 --- a/.github/workflows/build-release-task.yml +++ b/.github/workflows/build-release-task.yml @@ -43,20 +43,22 @@ jobs: steps: - name: Checkout code step - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download executable build artifacts step - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: artifact-ids: ${{ needs.build-executable.outputs.artifact-id }} path: ./Publish - name: Create GitHub release step - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: - generate_release_notes: true tag_name: ${{ needs.get-version.outputs.SemVer2 }} + target_commitish: ${{ github.sha }} prerelease: ${{ github.ref_name != 'main' }} + generate_release_notes: true + fail_on_unmatched_files: true files: | LICENSE README.md diff --git a/.github/workflows/get-version-task.yml b/.github/workflows/get-version-task.yml index 2c52cb21..c8b522cb 100644 --- a/.github/workflows/get-version-task.yml +++ b/.github/workflows/get-version-task.yml @@ -27,15 +27,15 @@ jobs: steps: - name: Setup .NET SDK step - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: dotnet-version: 10.x - name: Checkout code step - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Run Nerdbank.GitVersioning tool step id: nbgv - uses: dotnet/nbgv@master + uses: dotnet/nbgv@3cf2d96c2aa00675081b59f401356ac1fb81092f # v0.5.1 diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index a5656d24..d2f3f73f 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -1,6 +1,6 @@ name: Merge bot pull request action -on: +"on": pull_request: types: [opened, reopened, synchronize] @@ -13,9 +13,12 @@ jobs: merge-dependabot: name: Merge dependabot pull request job runs-on: ubuntu-latest - # To prevent abuse, the PR must come from Dependabot and the PR must originate from this repository. + # Restrict to dependabot PRs that originate from this repository, not a + # fork. Check the PR author rather than the event actor so maintainer + # repair commits on Dependabot branches can still auto-merge after CI + # passes. if: >- - github.actor == 'dependabot[bot]' && + github.event.pull_request.user.login == 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository permissions: contents: write @@ -23,19 +26,49 @@ jobs: steps: + - name: Generate GitHub App token step + # Use an App token (not GITHUB_TOKEN) so the resulting merge push is + # committed by the App and fires downstream workflows on develop/main. + # Pushes from GITHUB_TOKEN are blocked from triggering further workflow + # runs by GitHub's recursion guard, which would silently skip + # publish-release.yml and publish-periodic-docker-release.yml on the + # merge commit and prevent develop's auto-prerelease/Docker rebuild. + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }} + private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} + - name: Get dependabot metadata step id: metadata - uses: dependabot/fetch-metadata@v3 + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - # Merge any non-NuGet update, e.g. GitHub Actions often updates v1 to v2. - # Merge NuGet only for non-major updates, e.g. major updates may build but break functionality. + # Skip semver-major NuGet bumps: they often build cleanly but break + # runtime behavior, so they should land via human review. GitHub Actions + # majors auto-merge because the workflow execution itself validates them. + # + # Merge method must match the base branch's ruleset: + # develop -> squash only (linear history) + # main -> merge commits only (preserves develop ancestry) + # A mismatch fails enablePullRequestAutoMerge with + # "Merge method ... is not allowed on this repository". - name: Merge pull request step if: >- (steps.metadata.outputs.package-ecosystem != 'nuget') || (steps.metadata.outputs.update-type != 'version-update:semver-major') - run: gh pr merge --auto --squash "$PR_URL" + run: | + set -euo pipefail + case "${{ github.event.pull_request.base.ref }}" in + develop) method=--squash ;; + main) method=--merge ;; + *) + echo "::error::Unsupported base branch: ${{ github.event.pull_request.base.ref }}" + exit 1 + ;; + esac + gh pr merge --auto "$method" "$PR_URL" env: - PR_URL: ${{github.event.pull_request.html_url}} - GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/publish-periodic-docker-release.yml b/.github/workflows/publish-periodic-docker-release.yml index f09ca5f2..5651069d 100644 --- a/.github/workflows/publish-periodic-docker-release.yml +++ b/.github/workflows/publish-periodic-docker-release.yml @@ -34,35 +34,35 @@ jobs: steps: - - name: Get image size step - run: | - mkdir -p ${{ runner.temp }}/versions - touch ${{ runner.temp }}/versions/${{ matrix.file }} - echo Image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} >> ${{ runner.temp }}/versions/${{ matrix.file }} - echo Size: $(docker manifest inspect -v docker.io/ptr727/plexcleaner:${{ matrix.tag }} | jq '.[] | select(.Descriptor.platform.architecture=="amd64") | [.OCIManifest.layers[].size] | add' | numfmt --to=iec) >> ${{ runner.temp }}/versions/${{ matrix.file }} - - - name: Write tool versions to file step - uses: addnab/docker-run-action@v3 - with: - image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} - options: --volume ${{ runner.temp }}/versions:/versions + - name: Get image size step run: | - echo OS: $(. /etc/os-release; echo $PRETTY_NAME) >> /versions/${{ matrix.file }} - echo dotNET: $(dotnet --info) >> /versions/${{ matrix.file }} - echo PlexCleaner: $(/PlexCleaner/PlexCleaner --version) >> /versions/${{ matrix.file }} - echo HandBrakeCLI: $(HandBrakeCLI --version) >> /versions/${{ matrix.file }} - echo MediaInfo: $(mediainfo --version) >> /versions/${{ matrix.file }} - echo MkvMerge: $(mkvmerge --version) >> /versions/${{ matrix.file }} - echo FfMpeg: $(ffmpeg -version) >> /versions/${{ matrix.file }} - - - name: Print versions step - run: cat ${{ runner.temp }}/versions/${{ matrix.file }} - - - name: Upload version artifacts step - uses: actions/upload-artifact@v7 - with: - name: versions-${{ matrix.file }} - path: ${{ runner.temp }}/versions/${{ matrix.file }} + mkdir -p ${{ runner.temp }}/versions + touch ${{ runner.temp }}/versions/${{ matrix.file }} + echo Image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} >> ${{ runner.temp }}/versions/${{ matrix.file }} + echo Size: $(docker manifest inspect -v docker.io/ptr727/plexcleaner:${{ matrix.tag }} | jq '.[] | select(.Descriptor.platform.architecture=="amd64") | [.OCIManifest.layers[].size] | add' | numfmt --to=iec) >> ${{ runner.temp }}/versions/${{ matrix.file }} + + - name: Write tool versions to file step + uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 + with: + image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} + options: --volume ${{ runner.temp }}/versions:/versions + run: | + echo OS: $(. /etc/os-release; echo $PRETTY_NAME) >> /versions/${{ matrix.file }} + echo dotNET: $(dotnet --info) >> /versions/${{ matrix.file }} + echo PlexCleaner: $(/PlexCleaner/PlexCleaner --version) >> /versions/${{ matrix.file }} + echo HandBrakeCLI: $(HandBrakeCLI --version) >> /versions/${{ matrix.file }} + echo MediaInfo: $(mediainfo --version) >> /versions/${{ matrix.file }} + echo MkvMerge: $(mkvmerge --version) >> /versions/${{ matrix.file }} + echo FfMpeg: $(ffmpeg -version) >> /versions/${{ matrix.file }} + + - name: Print versions step + run: cat ${{ runner.temp }}/versions/${{ matrix.file }} + + - name: Upload version artifacts step + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: versions-${{ matrix.file }} + path: ${{ runner.temp }}/versions/${{ matrix.file }} update-readme: name: Create Docker README.md job @@ -72,27 +72,27 @@ jobs: steps: - - name: Checkout step - uses: actions/checkout@v6 - - - name: Download version artifacts step - uses: actions/download-artifact@v8 - with: - pattern: versions-* - merge-multiple: true - path: ${{ runner.temp }}/versions - - - name: Create README.md from README.m4 step - run: m4 --include=${{ runner.temp }}/versions ./Docker/README.m4 > ${{ runner.temp }}/README.md - - - name: Update Docker Hub README.md step - uses: peter-evans/dockerhub-description@v5 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_PASSWORD }} - repository: ptr727/plexcleaner - short-description: ${{ github.event.repository.description }} - readme-filepath: ${{ runner.temp }}/README.md + - name: Checkout step + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download version artifacts step + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: versions-* + merge-multiple: true + path: ${{ runner.temp }}/versions + + - name: Create README.md from README.m4 step + run: m4 --include=${{ runner.temp }}/versions ./Docker/README.m4 > ${{ runner.temp }}/README.md + + - name: Update Docker Hub README.md step + uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + repository: ptr727/plexcleaner + short-description: ${{ github.event.repository.description }} + readme-filepath: ${{ runner.temp }}/README.md date-badge: name: Create BYOB date badge job diff --git a/.github/workflows/test-release-task.yml b/.github/workflows/test-release-task.yml index 4340986f..6b5cfae3 100644 --- a/.github/workflows/test-release-task.yml +++ b/.github/workflows/test-release-task.yml @@ -13,12 +13,12 @@ jobs: steps: - name: Setup .NET SDK step - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: dotnet-version: 10.x - name: Checkout code step - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check code style step run: | diff --git a/AGENTS.md b/AGENTS.md index 99126a5c..815bd456 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,60 @@ For comprehensive coding standards and detailed conventions, refer to [`.github/ - **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction. - **Staging is the limit.** Prepare and stage file changes; the developer runs `git commit` in their own environment where signing keys are available. +## Branches and merging + +- Pipeline is `feature → develop → main`. Both branches are protected by branch rulesets; everything lands via PR. +- **Feature → develop PRs squash-merge** (single commit on develop, PR title becomes the commit message; never rebase-merge). +- **Develop → main PRs merge-commit** (one merge commit on main per release, develop's tip becomes a second parent and stays in main's ancestry — see [Develop → Main Promotion](#develop--main-promotion)). +- Open feature PRs against `develop`. `develop → main` is how stable releases are cut. + +Repo settings reflect this: `allow_merge_commit=true`, `allow_squash_merge=true`, `allow_rebase_merge=false`, `allow_auto_merge=true`. The `develop` ruleset enforces `allowed_merge_methods=["squash"]` and `required_linear_history`. The `main` ruleset enforces `allowed_merge_methods=["merge"]` and intentionally omits linear-history (the develop → main merge commit is non-linear by design). + +## Develop → Main Promotion + +Use the **"Create a merge commit"** option on develop → main PRs. Repo rulesets are split: PRs into `develop` are squash-only (linear history); PRs into `main` are merge-commit only. Clicking "Create a merge commit" on a develop → main PR produces a merge commit on main whose second parent is develop's tip — so develop becomes a real ancestor of main, and the *next* develop → main PR has a clean merge base (no recurring conflicts, no behind-base churn). + +Under any squash-only setup this would be a recurring pain point: each develop → main squash drops develop's ancestry and forces a per-cycle admin-bypass merge commit on develop to resync. With merge-commit on main, that resync is unnecessary — main's history shows one merge commit per release (a feature, not a defect: each promotion is visible as a single auditable node), and develop stays linear. + +## Release flow + +PlexCleaner is a "pull" project: consumers (`docker pull ptr727/plexcleaner:latest`, `docker pull ptr727/plexcleaner:develop`, GitHub Releases) track both branches. **Both `main` and `develop` auto-publish on every push** — there is no manual `workflow_dispatch` gate. + +[publish-release.yml](.github/workflows/publish-release.yml) drives both prereleases and stable releases off the same [build-release-task.yml](.github/workflows/build-release-task.yml). It triggers on `push: [main, develop]`: + +- **Push to `develop`** — automatic prerelease. Merging any PR into `develop` (feature, bug fix, dependabot) calls [get-version-task.yml](.github/workflows/get-version-task.yml) for an NBGV-computed version like `3.16.42-g1a2b3c4` (because develop does not match `publicReleaseRefSpec` in [version.json](version.json)) and creates a GitHub Release with `prerelease: true`. The Docker image is tagged `develop` by [publish-periodic-docker-release.yml](.github/workflows/publish-periodic-docker-release.yml). +- **Push to `main`** — automatic stable release. NBGV produces a clean version like `3.16.42` and creates a GitHub Release with `prerelease: false`. The Docker image is tagged `latest`. + +Branch-aware logic lives in three places: + +- [build-release-task.yml](.github/workflows/build-release-task.yml) — `prerelease: ${{ github.ref_name != 'main' }}` and `target_commitish: ${{ github.sha }}` (the latter is critical: without it, softprops creates the tag against the repo's default branch, mis-tagging develop builds onto main's tip). +- [publish-periodic-docker-release.yml](.github/workflows/publish-periodic-docker-release.yml) — `tag: ${{ github.ref_name == 'main' && 'latest' || 'develop' }}`. + +Bot-merged PRs (Dependabot) trigger the publish workflows automatically because the merge-bot uses an App token — see the merge-bot section below. + +## Dependabot + +[.github/dependabot.yml](.github/dependabot.yml) targets **both `main` and `develop`** with two ecosystems each (`nuget`, `github-actions`), grouped per ecosystem, daily. The duplication is intentional: because both branches auto-publish, develop must not drift from main's dependency baseline. A NuGet major bump landing on develop should land on main on the next promotion cycle, not weeks later. + +Major NuGet bumps are not auto-merged by [merge-bot-pull-request.yml](.github/workflows/merge-bot-pull-request.yml) — they require human review. Major GitHub Actions bumps are auto-merged because the workflow execution itself is the validation surface. + +## GitHub Actions pinning + +Every third-party action in `.github/workflows/*.yml` is pinned to a full commit SHA with a trailing comment matching the upstream release tag, e.g. `uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2`. The comment is whatever tag the action's repo actually publishes — typically `# vX.Y.Z`, but use `# v3` if upstream only publishes major-only tags (e.g. `addnab/docker-run-action`) and `# master` if the action ships only a moving branch (rare). Floating refs without a SHA (`@v6`, `@main`, `@master`) are never used. Local reusable workflows (`./.github/workflows/*.yml`) are referenced by path and don't need pinning. + +**Why:** Floating tags can be silently re-pointed by the action's owner (or by a compromised account) to malicious code; a SHA pin is immutable. Matching the comment to upstream's actual release tag (rather than fabricating one) lets dependabot rewrite both the SHA and the comment together when bumping. + +When adding a new `uses:` line, resolve the latest release's commit SHA (`gh api repos///releases/latest`) and copy its `tag_name` into the comment verbatim. Don't ship a floating tag and "pin it later". + +## Merge bot + +[merge-bot-pull-request.yml](.github/workflows/merge-bot-pull-request.yml) auto-merges Dependabot PRs. Two key design choices: + +- **Branch-aware merge method**: the script picks `--squash` for PRs targeting develop and `--merge` for PRs targeting main, matching each ruleset's `allowed_merge_methods`. An unknown base branch is a hard error. +- **App token, not GITHUB_TOKEN**: the merge step uses a token minted by `actions/create-github-app-token` from `CODEGEN_APP_CLIENT_ID` / `CODEGEN_APP_PRIVATE_KEY` secrets. Pushes authored by `GITHUB_TOKEN` are blocked from triggering downstream workflows by GitHub's recursion guard; without the App token, a Dependabot merge to develop would silently skip `publish-release.yml` and `publish-periodic-docker-release.yml`, leaving the develop Docker tag and prerelease stale. + +The App secrets (`CODEGEN_APP_CLIENT_ID`, `CODEGEN_APP_PRIVATE_KEY`) must exist in **both** secret namespaces: Settings → Secrets and variables → **Actions**, and Settings → Secrets and variables → **Dependabot**. Since Sept 2021, GitHub injects only the Dependabot-namespace secrets when a Dependabot-authored `pull_request` event fires; the regular Actions namespace is not visible to that run. Without the Dependabot duplicate the App-token step gets empty inputs and merge-bot silently fails to auto-merge. (The trigger remains `pull_request`, not `pull_request_target` — the merge-bot doesn't check out PR code, but `pull_request` plus duplicated secrets is the simpler, less-permissive setup.) + ## Key Requirements for All Projects Derived from This Template ### Build & Quality Standards