|
| 1 | +#!/usr/bin/env -S scala-cli shebang |
| 2 | + |
| 3 | +//> using scala 3.3.6 |
| 4 | + |
| 5 | +//> using dep "com.madgag.play-git-hub::core:10.0.0-PREVIEW.examine-default-branch.2025-10-01T1252.5ed70273" |
| 6 | + |
| 7 | +import cats.* |
| 8 | +import cats.data.* |
| 9 | +import cats.effect.unsafe.implicits.global |
| 10 | +import cats.effect.{ExitCode, IO, IOApp} |
| 11 | +import cats.implicits.* |
| 12 | +import cats.syntax.all.* |
| 13 | +import com.madgag.github.apps.GitHubAppAuth |
| 14 | +import com.madgag.scalagithub |
| 15 | +import com.madgag.scalagithub.GitHub |
| 16 | +import com.madgag.scalagithub.model.{Comment, PullRequest, RepoId} |
| 17 | +import play.api.libs.json.Json.toJson |
| 18 | +import play.api.libs.json.{Json, OWrites} |
| 19 | +import sttp.model.* |
| 20 | + |
| 21 | +import java.nio.file.{Files, Path} |
| 22 | +import scala.collection.immutable.SortedSet |
| 23 | +import scala.concurrent.ExecutionContext.Implicits.global |
| 24 | + |
| 25 | +val gitHubAppAuth: GitHubAppAuth = GitHubAppAuth.fromConfigMap(sys.env, "TARGETED_RELEASES") |
| 26 | + |
| 27 | +case class Config( |
| 28 | + prId: PullRequest.Id, |
| 29 | + updates: NonEmptySet[Artifact] |
| 30 | +) { |
| 31 | + val groupIds: NonEmptySet[String] = updates.map(_.groupId) |
| 32 | + val versions: NonEmptySet[String] = updates.map(_.version) |
| 33 | + |
| 34 | + val commitsMessage: String = "Update ${artifactName} from ${currentVersion} to ${nextVersion}" |
| 35 | + |
| 36 | + val updatesJson: String = toJson(updates.toSortedSet.toSeq).toString |
| 37 | + |
| 38 | + val text = |
| 39 | + s""" |
| 40 | + |updates.allow = $updatesJson |
| 41 | + | |
| 42 | + |updates.allowPreReleases = $updatesJson |
| 43 | + | |
| 44 | + |pullRequests.draft = true |
| 45 | + |commits.message = "$commitsMessage" |
| 46 | + |pullRequests.grouping = [{ |
| 47 | + | name = "${prId.slug}", |
| 48 | + | title = "Update `${prId.repo.name}` to ${versions.toSortedSet.mkString(", ")}", |
| 49 | + | filter = [{"group" = "*"}] |
| 50 | + |}] |
| 51 | + |""".stripMargin |
| 52 | +} |
| 53 | + |
| 54 | +case class Artifact(groupId: String, artifactId: String, version: String) { |
| 55 | + val artifactIdWithoutScalaVersion: String = artifactId.split('_').dropRight(1).mkString("_") |
| 56 | + |
| 57 | + lazy val withoutScalaVersion: Artifact = copy(artifactId = artifactIdWithoutScalaVersion) |
| 58 | +} |
| 59 | + |
| 60 | +object Artifact { |
| 61 | + // c96c2303d065496792e7edb379b0e671f57edbb13cecb6b2d94deb22d1a504d4 ./com/gu/panda-hmac-core_3/11.0.0/panda-hmac-core_3-11.0.0.pom |
| 62 | + def fromSha256PomLine(pomLine: String): Option[Artifact] = { |
| 63 | + val segments = pomLine.split(' ').last.split('/').drop(1).dropRight(1).reverse.toList |
| 64 | + segments match { |
| 65 | + case version :: artifactId :: groupSegments => |
| 66 | + Some(Artifact(groupSegments.reverse.mkString("."), artifactId, version)) |
| 67 | + case _ => None |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + given Order[Artifact] = Order.by(Tuple.fromProductTyped) |
| 72 | + |
| 73 | + given OWrites[Artifact] = Json.writes |
| 74 | +} |
| 75 | + |
| 76 | +object HelloWorld extends IOApp { |
| 77 | + |
| 78 | + def run(args: List[String]) = (for { |
| 79 | + installationAccess <- gitHubAppAuth.accessSoleInstallation() |
| 80 | + _ <- IO.println(s"Hello, ${installationAccess.installedOnAccount.atLogin}!") |
| 81 | + prId = PullRequest.Id.from(Uri.unsafeParse(args(0))) |
| 82 | + outputFile = Path.of(args(1)) |
| 83 | + given GitHub = installationAccess.accountAccess().gitHub |
| 84 | + pr <- summon[GitHub].getPullRequest(prId).map(_.result) |
| 85 | + configOpt <- scalaStewardConfigForMostRecentPreviewReleaseOf(pr).value |
| 86 | + _ <- IO(Files.writeString(outputFile, configOpt.map(_.text).mkString)) |
| 87 | + } yield println(s"Wrote config to ${outputFile.toAbsolutePath}")).as(ExitCode.Success) |
| 88 | + |
| 89 | + def scalaStewardConfigForMostRecentPreviewReleaseOf(pr: PullRequest)(using GitHub): OptionT[IO, Config] = |
| 90 | + findLatestPreviewVersionIn(pr).flatMap(version => scalaStewardConfigFor(pr.prId, version)) |
| 91 | + |
| 92 | + def findLatestPreviewVersionIn(pr: PullRequest)(using GitHub): OptionT[IO,String] = |
| 93 | + OptionT(pr.comments2.list().filter(_.user.login == "gu-scala-library-release[bot]").map(findPreviewVersionIn).unNone.compile.last) |
| 94 | + |
| 95 | + def findPreviewVersionIn(comment: Comment): Option[String] = comment.body.linesIterator.find(_.contains("-PREVIEW")) |
| 96 | + |
| 97 | + def tagMessageFor(repoId: RepoId, version: String)(using g: GitHub): IO[String] = for { |
| 98 | + repo <- g.getRepo(repoId).map(_.result) |
| 99 | + ref <- repo.refs.get(s"tags/v$version").map(_.result) |
| 100 | + tag <- repo.tags.get(ref.objectId.name).map(_.result) |
| 101 | + } yield tag.message |
| 102 | + |
| 103 | + def artifactsFrom(tagMessage: String): Seq[Artifact] = |
| 104 | + tagMessage.linesIterator.filter(_.endsWith(".pom")).flatMap(Artifact.fromSha256PomLine).toSeq |
| 105 | + |
| 106 | + def scalaStewardConfigFor(prId: PullRequest.Id, version: String)(using GitHub): OptionT[IO, Config] = OptionT(for { |
| 107 | + tagMessage <- tagMessageFor(prId.repo, version) |
| 108 | + } yield for { |
| 109 | + artifacts <- NonEmptySet.fromSet(SortedSet.from(artifactsFrom(tagMessage))) |
| 110 | + } yield Config(prId, artifacts.map(_.withoutScalaVersion))) |
| 111 | +} |
0 commit comments