diff --git a/docs/help.md b/docs/help.md index 2dc529eb70..4930fb2646 100644 --- a/docs/help.md +++ b/docs/help.md @@ -4,7 +4,7 @@ All command line arguments for the `scala-steward` application. ``` Usage: - scala-steward --workspace --repos-file [--repos-file ]... [--git-author-name ] --git-author-email [--git-author-signing-key ] --git-ask-pass [--sign-commits] [--forge-type ] [--forge-api-host ] --forge-login [--do-not-fork] [--add-labels] [--ignore-opts-files] [--env-var ]... [--process-timeout ] [--whitelist ]... [--read-only ]... [--enable-sandbox | --disable-sandbox] [--max-buffer-size ] [--repo-config ]... [--disable-default-repo-config] [--scalafix-migrations ]... [--disable-default-scalafix-migrations] [--artifact-migrations ]... [--disable-default-artifact-migrations] [--cache-ttl ] [--bitbucket-use-default-reviewers] [--bitbucket-server-use-default-reviewers] [--gitlab-merge-when-pipeline-succeeds] [--gitlab-required-reviewers ] [--gitlab-remove-source-branch] [--azure-repos-organization ] [--github-app-id --github-app-key-file ] [--url-checker-test-url ]... [--default-maven-repo ] [--refresh-backoff-period ] + scala-steward --workspace --repos-file [--repos-file ]... [--git-author-name ] --git-author-email [--git-author-signing-key ] --git-ask-pass [--sign-commits] [--forge-type ] [--forge-api-host ] --forge-login [--do-not-fork] [--add-labels] [--ignore-opts-files] [--env-var ]... [--process-timeout ] [--whitelist ]... [--read-only ]... [--enable-sandbox | --disable-sandbox] [--max-buffer-size ] [--repo-config ]... [--disable-default-repo-config] [--scalafix-migrations ]... [--disable-default-scalafix-migrations] [--artifact-migrations ]... [--disable-default-artifact-migrations] [--cache-ttl ] [--bitbucket-use-default-reviewers] [--bitbucket-server-use-default-reviewers] [--gitlab-merge-when-pipeline-succeeds] [--gitlab-required-reviewers ] [--gitlab-remove-source-branch] [--azure-repos-organization ] [--github-app-id --github-app-key-file ] [--url-checker-test-url ]... [--default-maven-repo ] [--refresh-backoff-period ] [--exit-code-success-if-any-repo-succeeds] scala-steward validate-repo-config @@ -94,6 +94,8 @@ Options and flags: default: https://repo1.maven.org/maven2/ --refresh-backoff-period Period of time a failed build won't be triggered again; default: 0days + --exit-code-success-if-any-repo-succeeds + Whether the Scala Steward process should exit with success (exit code 0) if any repo succeeds; default: false Subcommands: validate-repo-config diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala index dfbebffea9..87a9057534 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala @@ -18,12 +18,17 @@ package org.scalasteward.core.application import better.files.File import cats.data.Validated +import cats.effect.ExitCode import cats.syntax.all._ import com.monovore.decline.Opts.{flag, option, options} import com.monovore.decline._ import org.http4s.Uri import org.http4s.syntax.literals._ import org.scalasteward.core.application.Config._ +import org.scalasteward.core.application.ExitCodePolicy.{ + SuccessIfAnyRepoSucceeds, + SuccessOnlyIfAllReposSucceed +} import org.scalasteward.core.data.Resolver import org.scalasteward.core.forge.ForgeType import org.scalasteward.core.forge.ForgeType.{AzureRepos, GitHub} @@ -31,6 +36,7 @@ import org.scalasteward.core.forge.github.GitHubApp import org.scalasteward.core.git.Author import org.scalasteward.core.util.Nel import org.scalasteward.core.util.dateTime.renderFiniteDuration + import scala.concurrent.duration._ object Cli { @@ -337,6 +343,13 @@ object Cli { .withDefault(default) } + private val exitCodePolicy: Opts[ExitCodePolicy] = flag( + "exit-code-success-if-any-repo-succeeds", + s"Whether the Scala Steward process should exit with success (exit code ${ExitCode.Success.code}) if any repo succeeds; default: false" + ).orFalse.map { ifAnyRepoSucceeds => + if (ifAnyRepoSucceeds) SuccessIfAnyRepoSucceeds else SuccessOnlyIfAllReposSucceed + } + private val regular: Opts[Usage] = ( workspace, reposFiles, @@ -355,7 +368,8 @@ object Cli { gitHubApp, urlCheckerTestUrls, defaultMavenRepo, - refreshBackoffPeriod + refreshBackoffPeriod, + exitCodePolicy ).mapN(Config.apply).map(Usage.Regular.apply) private val validateRepoConfig: Opts[Usage] = diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Config.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Config.scala index 3385af7203..11d666716f 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/application/Config.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/application/Config.scala @@ -63,7 +63,8 @@ final case class Config( githubApp: Option[GitHubApp], urlCheckerTestUrls: Nel[Uri], defaultResolver: Resolver, - refreshBackoffPeriod: FiniteDuration + refreshBackoffPeriod: FiniteDuration, + exitCodePolicy: ExitCodePolicy ) { def forgeSpecificCfg: ForgeSpecificCfg = forgeCfg.tpe match { diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/ExitCodePolicy.scala b/modules/core/src/main/scala/org/scalasteward/core/application/ExitCodePolicy.scala new file mode 100644 index 0000000000..9edcf1a7f2 --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/application/ExitCodePolicy.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2018-2023 Scala Steward contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.scalasteward.core.application + +import cats.effect.ExitCode + +trait ExitCodePolicy { + def exitCodeFor(runResults: RunResults): ExitCode +} + +object ExitCodePolicy { + def successIf(isSuccess: RunResults => Boolean): ExitCodePolicy = + (runResults: RunResults) => if (isSuccess(runResults)) ExitCode.Success else ExitCode.Error + + val SuccessIfAnyRepoSucceeds: ExitCodePolicy = successIf(_.successRepos.nonEmpty) + + val SuccessOnlyIfAllReposSucceed: ExitCodePolicy = successIf(_.reposWithFailures.nonEmpty) +} diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/RunResults.scala b/modules/core/src/main/scala/org/scalasteward/core/application/RunResults.scala index 9b3af428be..eb6c7bcb55 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/application/RunResults.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/application/RunResults.scala @@ -16,15 +16,12 @@ package org.scalasteward.core.application -import cats.effect.ExitCode import cats.syntax.all._ import org.scalasteward.core.data.Repo case class RunResults(results: List[Either[(Repo, Throwable), Repo]]) { val (reposWithFailures, successRepos) = results.separate - val exitCode: ExitCode = if (successRepos.nonEmpty) ExitCode.Success else ExitCode.Error - val markdownSummary: String = { val failuresSummaryOpt = Option.when(reposWithFailures.nonEmpty) { (Seq(s"# Job failed for ${reposWithFailures.size} out of ${results.size} repos") ++ (for { diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala index ac7c91c30d..afbe9633d9 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala @@ -98,7 +98,7 @@ final class StewardAlg[F[_]](config: Config)(implicit for { summaryFile <- workspaceAlg.runSummaryFile _ <- fileAlg.writeFile(summaryFile, runResults.markdownSummary) - } yield runResults.exitCode + } yield config.exitCodePolicy.exitCodeFor(runResults) } } yield exitCode } diff --git a/modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala b/modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala index ffbe23d46f..b2e3ebf2e7 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala @@ -6,9 +6,14 @@ import munit.FunSuite import org.http4s.syntax.literals._ import org.scalasteward.core.application.Cli.ParseResult._ import org.scalasteward.core.application.Cli.{EnvVar, Usage} +import org.scalasteward.core.application.ExitCodePolicy.{ + SuccessIfAnyRepoSucceeds, + SuccessOnlyIfAllReposSucceed +} import org.scalasteward.core.forge.ForgeType import org.scalasteward.core.forge.github.GitHubApp import org.scalasteward.core.util.Nel + import scala.concurrent.duration._ class CliTest extends FunSuite { @@ -207,6 +212,19 @@ class CliTest extends FunSuite { assert(!obtained.forgeCfg.addLabels) } + test("parseArgs: exit code policy: --exit-code-success-if-any-repo-succeeds") { + val params = minimumRequiredParams ++ List( + List("--exit-code-success-if-any-repo-succeeds") + ) + val Success(Usage.Regular(obtained)) = Cli.parseArgs(params.flatten) + assert(obtained.exitCodePolicy == SuccessIfAnyRepoSucceeds) + } + + test("parseArgs: exit code policy: default") { + val Success(Usage.Regular(obtained)) = Cli.parseArgs(minimumRequiredParams.flatten) + assert(obtained.exitCodePolicy == SuccessOnlyIfAllReposSucceed) + } + test("parseArgs: validate pull request labeling enabled") { val params = minimumRequiredParams ++ List( List("--forge-type", "bitbucket"),