Repository: scala-steward-org/scala-steward (scala)

Issue:
Allow to add custom labels on PR
It would be nice to be able to add custom labels that are added to each PR

Ex:

```hocon
pullRequests.customLabels = [ "dependencies", "scala" ]
```

This could allow to distinguish a PR coming from scala-steward or to combine scala-steward and other tools like dependabot (for github actions, or other repo technology for example).  By pooling the labels "dependencies" and "scala" as in the example, we harmonize the labels, which allows to create filters on dependencies in repo/projects, being agnostic of the dependency automation tool. 

To my knowledge this is currently not possible.  

Note that if I am helped to get a good approach for the implementation, I can provide a PR.

Pull request description:
Close https://github.com/scala-steward-org/scala-steward/issues/3048

Patch:
diff --git a/docs/repo-specific-configuration.md b/docs/repo-specific-configuration.md
index d480fdf038..f29ed69066 100644
--- a/docs/repo-specific-configuration.md
+++ b/docs/repo-specific-configuration.md
@@ -73,11 +73,17 @@ pullRequests.grouping = [
   { name = "all", "title" = "Dependency updates", "filter" = [{"group" = "*"}] }
 ]
 
-# pullRequests.includeMatchedLabels allows to control which labels are added to PRs
+# pullRequests.includeMatchedLabels allows to control which autogenerated labels are added to PRs
+# This setting has no effect on custom labels (see below).
 # via a regex check each label is checked against.
 # Defaults to no regex (all labels are added) which is equivalent to ".*".
 pullRequests.includeMatchedLabels = "(.*semver.*)|(commit-count:n:.*)"
 
+# pullRequests.customLabels allows to add custom labels to PRs.
+# This is useful if you want to use the labels for automation (project board for example).
+# Defaults to no labels (no labels are added).
+pullRequests.customLabels = [ "dependencies", "scala" ]
+
 # Only these dependencies which match the given patterns are updated.
 #
 # Each pattern must have `groupId`, and may have `artifactId` and `version`.
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/data/NewPullRequestData.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/data/NewPullRequestData.scala
index 1245d29913..b6615e5a94 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/data/NewPullRequestData.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/data/NewPullRequestData.scala
@@ -317,9 +317,13 @@ object NewPullRequestData {
     val scalafixLabel = edits.collectFirst { case _: ScalafixEdit => "scalafix-migrations" }
     val oldVersionLabel = Option.when(filesWithOldVersion.nonEmpty)("old-version-remains")
 
-    updateTypeLabels(update) ++
-      semverLabels ++ List(scalafixLabel, oldVersionLabel).flatten ++
+    List.concat(
+      updateTypeLabels(update),
+      semverLabels,
+      scalafixLabel,
+      oldVersionLabel,
       List(commitCountLabel)
+    )
   }
 
   def filterLabels(labels: List[String], includeMatchedLabels: Option[Regex]): List[String] =
diff --git a/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
index ff9aacc59b..10301e07d1 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
@@ -241,7 +241,7 @@ final class NurtureAlg[F[_]](config: ForgeCfg)(implicit
         artifactIdToUpdateInfoUrls = artifactIdToUpdateInfoUrls.toMap,
         filesWithOldVersion = filesWithOldVersion,
         addLabels = config.addLabels,
-        labels = labels
+        labels = data.repoData.config.pullRequests.customLabels ++ labels
       )
     } yield requestData
 
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestsConfig.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestsConfig.scala
index a6764c2865..39fef7b22c 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestsConfig.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestsConfig.scala
@@ -21,13 +21,15 @@ import cats.{Eq, Monoid}
 import io.circe.Codec
 import io.circe.generic.extras.Configuration
 import io.circe.generic.extras.semiauto.deriveConfiguredCodec
+
 import java.util.regex.PatternSyntaxException
 import scala.util.matching.Regex
 
 final case class PullRequestsConfig(
     frequency: Option[PullRequestFrequency] = None,
     grouping: List[PullRequestGroup] = Nil,
-    includeMatchedLabels: Option[Regex] = None
+    includeMatchedLabels: Option[Regex] = None,
+    customLabels: List[String] = Nil
 ) {
   def frequencyOrDefault: PullRequestFrequency =
     frequency.getOrElse(PullRequestsConfig.defaultFrequency)
@@ -57,7 +59,8 @@ object PullRequestsConfig {
         PullRequestsConfig(
           frequency = x.frequency.orElse(y.frequency),
           grouping = x.grouping |+| y.grouping,
-          includeMatchedLabels = x.includeMatchedLabels.orElse(y.includeMatchedLabels)
+          includeMatchedLabels = x.includeMatchedLabels.orElse(y.includeMatchedLabels),
+          customLabels = x.customLabels |+| y.customLabels
         )
     )
 }


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/nurture/NurtureAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/nurture/NurtureAlgTest.scala
index 5bc62da151..c053214332 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/nurture/NurtureAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/nurture/NurtureAlgTest.scala
@@ -11,7 +11,7 @@ import org.scalasteward.core.forge.data.NewPullRequestData
 import org.scalasteward.core.git.{Branch, Commit}
 import org.scalasteward.core.mock.MockContext.context
 import org.scalasteward.core.mock.{MockConfig, MockEff, MockState}
-import org.scalasteward.core.repoconfig.RepoConfig
+import org.scalasteward.core.repoconfig.{PullRequestsConfig, RepoConfig}
 
 class NurtureAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
   test("preparePullRequest") {
@@ -123,4 +123,54 @@ class NurtureAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
       )
     }
   }
+
+  test(
+    "preparePullRequest should set custom labels if PullRequestsConfig.customLabels is provided"
+  ) {
+    def nextToLast(L: Array[String]) = L(L.size - 2)
+
+    val nurtureAlg = context.nurtureAlg
+    val dependency = "org.typelevel".g % ("cats-effect", "cats-effect_2.13").a % "3.3.0"
+    val customLabels = List("custom-label-1", "custom-label-2")
+    val repoData =
+      RepoData(
+        repo = Repo("scala-steward-org", "scala-steward"),
+        cache = dummyRepoCache,
+        config = RepoConfig(
+          pullRequests = PullRequestsConfig(
+            customLabels = customLabels
+          )
+        )
+      )
+    val update = (dependency %> "3.4.0").single
+    val baseBranch = Branch("main")
+    val updateBranch = Branch("update/cats-effect-3.4.0")
+    val updateData =
+      UpdateData(repoData, repoData.repo, update, baseBranch, dummySha1, updateBranch)
+    val edits = List(UpdateEdit(update, Commit(dummySha1)))
+    val state = MockState.empty.copy(clientResponses = HttpApp {
+      case HEAD -> Root / "typelevel" / "cats-effect"                                 => Ok()
+      case HEAD -> Root / "typelevel" / "cats-effect" / "releases" / "tag" / "v3.4.0" => Ok()
+      case HEAD -> Root / "typelevel" / "cats-effect" / "compare" / "v3.3.0...v3.4.0" => Ok()
+      case _                                                                          => NotFound()
+    })
+    nurtureAlg.preparePullRequest(updateData, edits).runA(state).map { obtained =>
+      assertEquals(
+        obtained.labels,
+        customLabels ++ List(
+          "library-update",
+          "early-semver-minor",
+          "semver-spec-minor",
+          "commit-count:1"
+        )
+      )
+
+      val nextToLastLine = nextToLast(obtained.body.split("\n"))
+
+      assertEquals(
+        nextToLastLine,
+        "labels: custom-label-1, custom-label-2, library-update, early-semver-minor, semver-spec-minor, commit-count:1"
+      )
+    }
+  }
 }