Repository: scala-steward-org/scala-steward
Pull request #2927 (2023-01-16 19:44:32)

Use `30 days` instead of `@monthly` in repo config example
This changes the value of the `pullRequests.frequency` config in the example to slow down future updates in PR bodies from `@monthly` (which is an alias for `30 days`) to `30 days`. An advantage of this new value is that it is immediately clear how it needs to be adapted if you want to receive updates every 7, 14, 60, etc. days.

With `@monthly` you need to look at the documentation to learn what other special values exist and how to configure a custom frequency of every n days.


Patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/SelfCheckAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/application/SelfCheckAlg.scala
index 7d7b11a7..11e2050b 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/SelfCheckAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/SelfCheckAlg.scala
@@ -21,14 +21,12 @@ import cats.syntax.all._
 import org.scalasteward.core.edit.scalafix.ScalafixCli
 import org.scalasteward.core.edit.scalafix.ScalafixCli.scalafixBinary
 import org.scalasteward.core.git.GitAlg
-import org.scalasteward.core.io.FileAlg
 import org.scalasteward.core.scalafmt.{scalafmtBinary, ScalafmtAlg}
 import org.scalasteward.core.util.UrlChecker
 import org.scalasteward.core.util.logger.LoggerOps
 import org.typelevel.log4cats.Logger
 
 final class SelfCheckAlg[F[_]](config: Config)(implicit
-    fileAlg: FileAlg[F],
     gitAlg: GitAlg[F],
     logger: Logger[F],
     scalafixCli: ScalafixCli[F],
@@ -39,28 +37,12 @@ final class SelfCheckAlg[F[_]](config: Config)(implicit
   def checkAll: F[Unit] =
     for {
       _ <- logger.info("Run self checks")
-      _ <- checkWorkspaceDirectory
       _ <- checkGitBinary
       _ <- checkScalafixBinary
       _ <- checkScalafmtBinary
       _ <- checkUrlChecker
     } yield ()
 
-  private def checkWorkspaceDirectory: F[Unit] =
-    fileAlg
-      .isNonEmptyDirectory(config.workspace)
-      .ifM(
-        F.unit,
-        logger.warn(
-          s"""
-             |The workspace directory is empty: '${config.workspace}'
-             |This is expected if this is your first Scala Steward run.
-             |Make sure to preserve the workspace between runs for all features to work as expected.
-             |https://github.com/scala-steward-org/scala-steward/blob/main/docs/faq.md#why-doesnt-self-hosted-scala-steward-close-obsolete-prs
-             |""".stripMargin
-        )
-      )
-
   private def checkGitBinary: F[Unit] =
     logger.attemptWarn.log_(execFailedMessage("git")) {
       gitAlg.version.flatMap(output => logger.info(s"Using $output"))
diff --git a/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala
index b07da626..fe47f030 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala
@@ -34,8 +34,6 @@ trait FileAlg[F[_]] {
 
   def isDirectory(file: File): F[Boolean]
 
-  def isNonEmptyDirectory(dir: File): F[Boolean]
-
   def isRegularFile(file: File): F[Boolean]
 
   def removeTemporarily(file: File): Resource[F, Unit]
@@ -103,9 +101,6 @@ object FileAlg {
       override def isDirectory(file: File): F[Boolean] =
         F.blocking(file.isDirectory(File.LinkOptions.noFollow))
 
-      override def isNonEmptyDirectory(dir: File): F[Boolean] =
-        F.blocking(dir.isDirectory(File.LinkOptions.noFollow) && dir.nonEmpty)
-
       override def isRegularFile(file: File): F[Boolean] =
         F.blocking(file.isRegularFile(File.LinkOptions.noFollow))
 
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/GroupRepoConfig.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/GroupRepoConfig.scala
index 8050d6d5..97e7c690 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/GroupRepoConfig.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/GroupRepoConfig.scala
@@ -38,12 +38,12 @@ object GroupRepoConfig {
     val forUpdate: Update.Single => String = {
       case s: Update.ForArtifactId =>
         s"""{
-           |  pullRequests = { frequency = "@monthly" },
+           |  pullRequests = { frequency = "30 days" },
            |  dependency = { groupId = "${s.groupId}", artifactId = "${s.artifactId.name}" }
            |}""".stripMargin
       case g: Update.ForGroupId =>
         s"""{
-           |  pullRequests = { frequency = "@monthly" },
+           |  pullRequests = { frequency = "30 days" },
            |  dependency = { groupId = "${g.groupId}" }
            |}""".stripMargin
     }
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 56b81c91..93214432 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -4,7 +4,7 @@ addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.11")
 addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")
 addSbtPlugin("com.github.cb372" % "sbt-explicit-dependencies" % "0.2.16")
 addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.10.0")
-addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.12")
+addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.11")
 addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.9.0")
 addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.2")
 addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3")


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/application/StewardAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/application/StewardAlgTest.scala
index a1ab6a43..9062175a 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/application/StewardAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/application/StewardAlgTest.scala
@@ -8,6 +8,6 @@ import org.scalasteward.core.mock.MockState
 class StewardAlgTest extends CatsEffectSuite {
   test("runF") {
     val exitCode = stewardAlg.runF.runA(MockState.empty)
-    assertIO(exitCode, ExitCode.Success)
+    exitCode.map(assertEquals(_, ExitCode.Success))
   }
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/data/NewPullRequestDataTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/data/NewPullRequestDataTest.scala
index f72306d5..0a19c2c4 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/data/NewPullRequestDataTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/data/NewPullRequestDataTest.scala
@@ -30,7 +30,7 @@ class NewPullRequestDataTest extends FunSuite {
     val expected =
       raw"""|{
             |  "title" : "Update logback-classic to 1.2.3",
-            |  "body" : "Updates ch.qos.logback:logback-classic from 1.2.0 to 1.2.3.\n\n\nI'll automatically update this PR to resolve conflicts as long as you don't change it yourself.\n\nIf you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.\n\nConfigure Scala Steward for your repository with a [`.scala-steward.conf`](https://github.com/scala-steward-org/scala-steward/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.\n\nHave a fantastic day writing Scala!\n\n<details>\n<summary>Adjust future updates</summary>\n\nAdd this to your `.scala-steward.conf` file to ignore future updates of this dependency:\n```\nupdates.ignore = [ { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" } ]\n```\nOr, add this to slow down future updates of this dependency:\n```\ndependencyOverrides = [{\n  pullRequests = { frequency = \"@monthly\" },\n  dependency = { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" }\n}]\n```\n</details>\n\nlabels: library-update, early-semver-patch, semver-spec-patch, commit-count:0",
+            |  "body" : "Updates ch.qos.logback:logback-classic from 1.2.0 to 1.2.3.\n\n\nI'll automatically update this PR to resolve conflicts as long as you don't change it yourself.\n\nIf you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.\n\nConfigure Scala Steward for your repository with a [`.scala-steward.conf`](https://github.com/scala-steward-org/scala-steward/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.\n\nHave a fantastic day writing Scala!\n\n<details>\n<summary>Adjust future updates</summary>\n\nAdd this to your `.scala-steward.conf` file to ignore future updates of this dependency:\n```\nupdates.ignore = [ { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" } ]\n```\nOr, add this to slow down future updates of this dependency:\n```\ndependencyOverrides = [{\n  pullRequests = { frequency = \"30 days\" },\n  dependency = { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" }\n}]\n```\n</details>\n\nlabels: library-update, early-semver-patch, semver-spec-patch, commit-count:0",
             |  "head" : "scala-steward:update/logback-classic-1.2.3",
             |  "base" : "master",
             |  "labels" : [
@@ -61,7 +61,7 @@ class NewPullRequestDataTest extends FunSuite {
     val expected =
       raw"""|{
             |  "title" : "Update logback-classic to 1.2.3",
-            |  "body" : "Updates ch.qos.logback:logback-classic from 1.2.0 to 1.2.3.\n\n\nI'll automatically update this PR to resolve conflicts as long as you don't change it yourself.\n\nIf you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.\n\nConfigure Scala Steward for your repository with a [`.scala-steward.conf`](https://github.com/scala-steward-org/scala-steward/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.\n\nHave a fantastic day writing Scala!\n\n<details>\n<summary>Adjust future updates</summary>\n\nAdd this to your `.scala-steward.conf` file to ignore future updates of this dependency:\n```\nupdates.ignore = [ { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" } ]\n```\nOr, add this to slow down future updates of this dependency:\n```\ndependencyOverrides = [{\n  pullRequests = { frequency = \"@monthly\" },\n  dependency = { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" }\n}]\n```\n</details>\n<details>\n<summary>Note that the Scala Steward config file `.scala-steward.conf` wasn't parsed correctly</summary>\n\n```\nFailed to parse .scala-steward.conf\n```\n</details>\n\nlabels: library-update, early-semver-patch, semver-spec-patch, commit-count:0",
+            |  "body" : "Updates ch.qos.logback:logback-classic from 1.2.0 to 1.2.3.\n\n\nI'll automatically update this PR to resolve conflicts as long as you don't change it yourself.\n\nIf you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.\n\nConfigure Scala Steward for your repository with a [`.scala-steward.conf`](https://github.com/scala-steward-org/scala-steward/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.\n\nHave a fantastic day writing Scala!\n\n<details>\n<summary>Adjust future updates</summary>\n\nAdd this to your `.scala-steward.conf` file to ignore future updates of this dependency:\n```\nupdates.ignore = [ { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" } ]\n```\nOr, add this to slow down future updates of this dependency:\n```\ndependencyOverrides = [{\n  pullRequests = { frequency = \"30 days\" },\n  dependency = { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" }\n}]\n```\n</details>\n<details>\n<summary>Note that the Scala Steward config file `.scala-steward.conf` wasn't parsed correctly</summary>\n\n```\nFailed to parse .scala-steward.conf\n```\n</details>\n\nlabels: library-update, early-semver-patch, semver-spec-patch, commit-count:0",
             |  "head" : "scala-steward:update/logback-classic-1.2.3",
             |  "base" : "master",
             |  "labels" : [
@@ -384,11 +384,11 @@ class NewPullRequestDataTest extends FunSuite {
         |```
         |dependencyOverrides = [
         |  {
-        |    pullRequests = { frequency = "@monthly" },
+        |    pullRequests = { frequency = "30 days" },
         |    dependency = { groupId = "a", artifactId = "b" }
         |  },
         |  {
-        |    pullRequests = { frequency = "@monthly" },
+        |    pullRequests = { frequency = "30 days" },
         |    dependency = { groupId = "c", artifactId = "d" }
         |  }
         |]
@@ -442,11 +442,11 @@ class NewPullRequestDataTest extends FunSuite {
            |```
            |dependencyOverrides = [
            |  {
-           |    pullRequests = { frequency = \"@monthly\" },
+           |    pullRequests = { frequency = \"30 days\" },
            |    dependency = { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" }
            |  },
            |  {
-           |    pullRequests = { frequency = \"@monthly\" },
+           |    pullRequests = { frequency = \"30 days\" },
            |    dependency = { groupId = \"com.example\", artifactId = \"foo\" }
            |  }
            |]
diff --git a/modules/core/src/test/scala/org/scalasteward/core/io/FileAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/io/FileAlgTest.scala
index 497869f4..cbfe1998 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/io/FileAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/io/FileAlgTest.scala
@@ -2,7 +2,8 @@ package org.scalasteward.core.io
 
 import better.files.File
 import cats.effect.IO
-import munit.CatsEffectSuite
+import cats.effect.unsafe.implicits.global
+import munit.FunSuite
 import org.http4s.Uri
 import org.scalacheck.Arbitrary
 import org.scalasteward.core.TestInstances.ioLogger
@@ -12,65 +13,65 @@ import org.scalasteward.core.mock.MockContext.context.fileAlg
 import org.scalasteward.core.mock.MockState
 import org.scalasteward.core.mock.MockState.TraceEntry.Cmd
 
-class FileAlgTest extends CatsEffectSuite {
+class FileAlgTest extends FunSuite {
   test("createTemporarily") {
     val file = mockRoot / "test-scala-steward3.tmp"
     val content = Arbitrary.arbitrary[String].sample.getOrElse("")
 
-    val obtained = for {
+    val p = for {
       before <- ioFileAlg.readFile(file)
       during <- ioFileAlg.createTemporarily(file, content).surround(ioFileAlg.readFile(file))
       after <- ioFileAlg.readFile(file)
     } yield (before, during, after)
 
-    assertIO(obtained, (None, Some(content), None))
+    assertEquals(p.unsafeRunSync(), (None, Some(content), None))
   }
 
   test("writeFile *> readFile <* deleteForce") {
     val file = mockRoot / "test-scala-steward1.tmp"
     val content = Arbitrary.arbitrary[String].sample.getOrElse("")
-    val obtained = ioFileAlg.writeFile(file, content) *>
+    val read = ioFileAlg.writeFile(file, content) *>
       ioFileAlg.readFile(file).map(_.getOrElse("")) <*
       ioFileAlg.deleteForce(file)
-    assertIO(obtained, content)
+    assertEquals(read.unsafeRunSync(), content)
   }
 
   test("removeTemporarily") {
     val file = mockRoot / "test-scala-steward2.tmp"
     val content = Arbitrary.arbitrary[String].sample.getOrElse("")
 
-    val obtained = for {
+    val p = for {
       _ <- ioFileAlg.writeFile(file, content)
       before <- ioFileAlg.readFile(file)
       during <- ioFileAlg.removeTemporarily(file).surround(ioFileAlg.readFile(file))
       after <- ioFileAlg.readFile(file)
     } yield (before, during, after)
 
-    assertIO(obtained, (Some(content), None, Some(content)))
+    assertEquals(p.unsafeRunSync(), (Some(content), None, Some(content)))
   }
 
   test("removeTemporarily: nonexistent file") {
     val file = mockRoot / "does-not-exist.txt"
-    val obtained = ioFileAlg.removeTemporarily(file).surround(IO.pure(42))
-    assertIO(obtained, 42)
+    assertEquals(ioFileAlg.removeTemporarily(file).surround(IO.pure(42)).unsafeRunSync(), 42)
   }
 
   test("editFile: nonexistent file") {
-    val obtained = fileAlg
+    val state = fileAlg
       .editFile(mockRoot / "does-not-exist.txt", identity)
       .runS(MockState.empty)
+      .unsafeRunSync()
 
     val expected =
       MockState.empty.copy(trace = Vector(Cmd("read", s"$mockRoot/does-not-exist.txt")))
-    assertIO(obtained, expected)
+    assertEquals(state, expected)
   }
 
   test("editFile: existent file") {
     val file = mockRoot / "steward" / "test1.sbt"
-    val obtained = (for {
+    val state = (for {
       _ <- fileAlg.writeFile(file, "123")
       _ <- fileAlg.editFile(file, _.replace("2", "4"))
-    } yield ()).runS(MockState.empty)
+    } yield ()).runS(MockState.empty).unsafeRunSync()
 
     val expected = MockState.empty.copy(
       trace = Vector(
@@ -80,7 +81,7 @@ class FileAlgTest extends CatsEffectSuite {
       ),
       files = Map(file -> "143")
     )
-    assertIO(obtained, expected)
+    assertEquals(state, expected)
   }
 
   test("deleteForce removes dangling symlink in subdirectory") {
@@ -88,7 +89,7 @@ class FileAlgTest extends CatsEffectSuite {
     val sub = dir / "sub"
     val regular = dir / "regular"
     val symlink = sub / "symlink"
-    val obtained = for {
+    val p = for {
       _ <- IO(dir.delete(swallowIOExceptions = true))
       _ <- ioFileAlg.writeFile(regular, "I'm a regular file")
       _ <- IO(sub.createDirectory())
@@ -97,45 +98,29 @@ class FileAlgTest extends CatsEffectSuite {
       _ <- ioFileAlg.deleteForce(dir)
       symlinkExists <- IO(symlink.exists(File.LinkOptions.noFollow))
     } yield symlinkExists
-    assertIO(obtained, false)
+    assertEquals(p.unsafeRunSync(), false)
   }
 
   test("readUri: local file without scheme") {
     val file = mockRoot / "steward" / "readUri.txt"
     val content = "42"
-    val obtained = for {
+    val p = for {
       _ <- ioFileAlg.writeFile(file, content)
       read <- ioFileAlg.readUri(Uri.unsafeFromString(file.toString))
     } yield read
-    assertIO(obtained, content)
+    assertEquals(p.unsafeRunSync(), content)
   }
 
   test("isRegularFile") {
     val dir = mockRoot / "steward" / "regular"
     val file = dir / "file.txt"
-    val obtained = for {
+    val p = for {
       _ <- ioFileAlg.deleteForce(dir)
       r1 <- ioFileAlg.isRegularFile(file)
       _ <- ioFileAlg.writeFile(file, "content")
       r2 <- ioFileAlg.isRegularFile(file)
     } yield (r1, r2)
-    assertIO(obtained, (false, true))
-  }
-
-  test("isNonEmptyDirectory: empty") {
-    val dir = mockRoot / "workspace2"
-    val obtained = ioFileAlg.isNonEmptyDirectory(dir)
-    assertIO(obtained, false)
-  }
-
-  test("isNonEmptyDirectory: non empty") {
-    val dir = mockRoot / "workspace1"
-    val file = dir / "file.txt"
-    val obtained = for {
-      _ <- ioFileAlg.writeFile(file, "42")
-      read <- ioFileAlg.isNonEmptyDirectory(dir)
-    } yield read
-    assertIO(obtained, true)
+    assertEquals(p.unsafeRunSync(), (false, true))
   }
 }
 
diff --git a/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala b/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala
index f4428c89..091a6cc6 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala
@@ -18,12 +18,6 @@ class MockFileAlg extends FileAlg[MockEff] {
   override def isDirectory(file: File): MockEff[Boolean] =
     Kleisli(_.update(_.exec(List("test", "-d", file.pathAsString))) >> ioFileAlg.isDirectory(file))
 
-  override def isNonEmptyDirectory(dir: File): MockEff[Boolean] =
-    Kleisli(
-      _.update(_.exec(List("find", dir.pathAsString, "-type", "d", "-not", "-empty"))) >> ioFileAlg
-        .isNonEmptyDirectory(dir)
-    )
-
   override def isRegularFile(file: File): MockEff[Boolean] =
     Kleisli {
       _.update(_.exec(List("test", "-f", file.pathAsString))) >> ioFileAlg.isRegularFile(file)
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 c561d971..3980c1a5 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
@@ -59,7 +59,7 @@ class NurtureAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
              |Or, add this to slow down future updates of this dependency:
              |```
              |dependencyOverrides = [{
-             |  pullRequests = { frequency = "@monthly" },
+             |  pullRequests = { frequency = "30 days" },
              |  dependency = { groupId = "org.typelevel", artifactId = "cats-effect" }
              |}]
              |```
diff --git a/modules/core/src/test/scala/org/scalasteward/core/util/dateTimeTest.scala b/modules/core/src/test/scala/org/scalasteward/core/util/dateTimeTest.scala
index 29fb773b..1d9ced1c 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/util/dateTimeTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/util/dateTimeTest.scala
@@ -27,6 +27,11 @@ class dateTimeTest extends ScalaCheckSuite {
     assert(clue(parseFiniteDuration("Inf").isLeft))
   }
 
+  test("parseFiniteDuration: examples") {
+    assertEquals(parseFiniteDuration("30days"), Right(30.days))
+    assertEquals(parseFiniteDuration("30 days"), Right(30.days))
+  }
+
   test("showDuration: example 1") {
     val d = 2.days + 20.hours + 37.minutes + 3.seconds + 586.millis + 491.micros + 264.nanos
     assertEquals(showDuration(d), "2d 20h 37m")