From 32cdf61622aa778264bcb63a1633eefb20ff209c Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Wed, 15 Nov 2023 17:41:04 +0100 Subject: [PATCH] Start lila-fishnet v2 - Remove all the code except models - Clean sbt settings - Add new dependencies --- .scalafmt.conf | 53 ++++++++- app/AppLoader.scala | 46 -------- app/JsonApi.scala | 87 --------------- app/Lila.scala | 70 +----------- app/MoveDb.scala | 138 +++--------------------- app/Work.scala | 18 ++-- app/controllers/FishnetController.scala | 62 ----------- app/model.scala | 6 +- build.sbt | 105 +++++++----------- project/Dependencies.scala | 54 ++++++++++ project/plugins.sbt | 10 +- 11 files changed, 172 insertions(+), 477 deletions(-) delete mode 100644 app/AppLoader.scala delete mode 100644 app/JsonApi.scala delete mode 100644 app/controllers/FishnetController.scala create mode 100644 project/Dependencies.scala diff --git a/.scalafmt.conf b/.scalafmt.conf index ea5cb92..df2e271 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,6 +1,49 @@ -version = "2.6.3" -align.preset = more +version = 3.7.15 +runner.dialect = scala3 maxColumn = 110 -spaces.inImportCurlyBraces = true -rewrite.rules = [SortImports, RedundantParens, SortModifiers] -rewrite.redundantBraces.stringInterpolation = true + +newlines.source = keep + +trailingCommas = "multiple" + +rewrite { + rules = [Imports] + imports.sort = ascii +} + +rewrite.scala3 { + convertToNewSyntax = yes + removeOptionalBraces = yes +} + +align { + allowOverflow = true + preset = more + openParenCallSite = false + stripMargin = true +} + +continuationIndent { + callSite = 2 + defnSite = 4 +} + +docstrings { + style = Asterisk + oneline = keep + wrap = no +} + +spaces { + beforeContextBoundColon = Never + inImportCurlyBraces = true +} + +fileOverride { + "glob:**/build.sbt" { + runner.dialect = scala213 + } + "glob:**/project/**" { + runner.dialect = scala213 + } +} diff --git a/app/AppLoader.scala b/app/AppLoader.scala deleted file mode 100644 index 5f401b0..0000000 --- a/app/AppLoader.scala +++ /dev/null @@ -1,46 +0,0 @@ -package lila.app - -import play.api._ -import play.api.routing.{ Router, SimpleRouter } -import play.api.routing.sird._ -import akka.actor.ActorSystem - -final class AppLoader extends ApplicationLoader { - def load(ctx: ApplicationLoader.Context): Application = new LilaComponents(ctx).application -} - -final class LilaComponents(ctx: ApplicationLoader.Context) extends BuiltInComponentsFromContext(ctx) { - - LoggerConfigurator(ctx.environment.classLoader).foreach { - _.configure(ctx.environment, ctx.initialConfiguration, Map.empty) - } - - println { - val java = System.getProperty("java.version") - val mem = Runtime.getRuntime().maxMemory() / 1024 / 1024 - s"lila-fishnet ${ctx.environment.mode} / java ${java}, memory: ${mem}MB" - } - - override val httpFilters = Seq.empty - - import _root_.controllers._ - - implicit def system: ActorSystem = actorSystem - - lazy val moveDb = new lila.fishnet.MoveDb - lazy val redis = new lila.fishnet.Lila(moveDb, configuration) - lazy val controller = new FishnetController(configuration, redis, moveDb, controllerComponents) - - // eagerly wire up all controllers - val router: Router = new SimpleRouter { - def routes: Router.Routes = { - case POST(p"/fishnet/acquire") => controller.acquire - case POST(p"/fishnet/move/$workId") => controller.move(workId) - } - } - - if (configuration.get[Boolean]("kamon.enabled")) { - println("Kamon is enabled") - kamon.Kamon.loadModules() - } -} diff --git a/app/JsonApi.scala b/app/JsonApi.scala deleted file mode 100644 index 4b42936..0000000 --- a/app/JsonApi.scala +++ /dev/null @@ -1,87 +0,0 @@ -package lila.fishnet - -import play.api.libs.json._ - -import chess.format.{ FEN, Uci } -import chess.variant.Variant -import lila.fishnet.{ Work => W } - -object JsonApi { - - sealed trait Request { - val fishnet: Request.Fishnet - def clientKey = fishnet.apikey - } - - object Request { - - sealed trait Result - - case class Fishnet(apikey: ClientKey) - - case class Acquire(fishnet: Fishnet) extends Request - - case class PostMove(fishnet: Fishnet, move: MoveResult) extends Request with Result - - case class MoveResult(bestmove: String) { - def uci: Option[Uci] = Uci(bestmove) - } - } - - case class Game( - game_id: String, - position: FEN, - variant: Variant, - moves: String - ) - - def fromGame(g: W.Game) = - Game( - game_id = g.id, - position = g.initialFen | g.variant.initialFen, - variant = g.variant, - moves = g.moves - ) - - sealed trait Work { - val id: String - val game: Game - } - case class Move( - id: String, - level: Int, - game: Game, - clock: Option[Work.Clock] - ) extends Work - - def moveFromWork(m: Work.Move) = Move(m.id.value, m.level, fromGame(m.game), m.clock) - - object readers { - implicit val ClientKeyReads: Reads[ClientKey] = Reads.of[String].map(new ClientKey(_)) - implicit val FishnetReads: Reads[Request.Fishnet] = Json.reads[Request.Fishnet] - implicit val AcquireReads: Reads[Request.Acquire] = Json.reads[Request.Acquire] - implicit val MoveResultReads: Reads[Request.MoveResult] = Json.reads[Request.MoveResult] - implicit val PostMoveReads: Reads[Request.PostMove] = Json.reads[Request.PostMove] - } - - object writers { - implicit val VariantWrites: Writes[Variant] = Writes[Variant] { v => JsString(v.key) } - implicit val FENWrites: Writes[FEN] = Writes[FEN] { fen => JsString(fen.value) } - implicit val GameWrites: Writes[Game] = Json.writes[Game] - implicit val ClockWrites: Writes[Work.Clock] = Json.writes[Work.Clock] - implicit val WorkIdWrites: Writes[Work.Id] = Writes[Work.Id] { id => JsString(id.value) } - implicit val WorkWrites: OWrites[Work] = OWrites[Work] { work => - (work match { - case m: Move => - Json.obj( - "work" -> Json.obj( - "type" -> "move", - "id" -> m.id, - "level" -> m.level, - "clock" -> m.clock - ) - ) - }) ++ Json.toJson(work.game).as[JsObject] - } - } -} diff --git a/app/Lila.scala b/app/Lila.scala index a3e8cdf..f8f8f64 100644 --- a/app/Lila.scala +++ b/app/Lila.scala @@ -1,76 +1,18 @@ package lila.fishnet -import chess.format.{ FEN, Uci } -import io.lettuce.core._ -import io.lettuce.core.pubsub._ +import chess.format.{ Fen, Uci } import org.joda.time.DateTime -import play.api.Configuration -import play.api.Logger -final class Lila( - moveDb: MoveDb, - config: Configuration -) { +object Lila: - private val logger = Logger(getClass) - private val redis = RedisClient create config.get[String]("redis.uri") - - def pubsub(chanIn: String, chanOut: String): Lila.Move => Unit = { - - val connIn = redis.connectPubSub() - val connOut = redis.connectPubSub() - - def send(move: Lila.Move): Unit = connIn.async.publish(chanIn, move.write) - - connOut.addListener(new RedisPubSubAdapter[String, String] { - override def message(chan: String, msg: String): Unit = - Lila.readMoveReq(msg) match { - case None => logger warn s"LilaOut invalid move $msg" - case Some(req) => moveDb add req - } - }) - - connOut.async.subscribe(chanOut) thenRun { () => connIn.async.publish(chanIn, "start") } - - send - } -} - -object Lila { - - case class Move(game: Work.Game, uci: Uci) { + case class Move(game: Work.Game, uci: Uci): def sign = game.moves.takeRight(20).replace(" ", "") def write = s"${game.id} $sign ${uci.uci}" - } - def readMoveReq(msg: String): Option[Work.Move] = - msg.split(";", 6) match { - case Array(gameId, levelS, clockS, variantS, initialFenS, moves) => - for { - level <- levelS.toIntOption - variant = chess.variant.Variant.orDefault(variantS) - initialFen = if (initialFenS.isEmpty) None else Some(FEN(initialFenS)) - clock = readClock(clockS) - } yield Work.Move( - _id = Work.makeId, - game = Work.Game( - id = gameId, - initialFen = initialFen, - variant = variant, - moves = moves - ), - level = level, - clock = clock, - tries = 0, - lastTryByKey = None, - acquired = None, - createdAt = DateTime.now - ) - case _ => None - } + def readMoveReq(msg: String): Option[Work.Move] = ??? def readClock(s: String) = - s split ' ' match { + s split ' ' match case Array(ws, bs, incs) => for { wtime <- ws.toIntOption @@ -78,5 +20,3 @@ object Lila { inc <- incs.toIntOption } yield Work.Clock(wtime, btime, inc) case _ => None - } -} diff --git a/app/MoveDb.scala b/app/MoveDb.scala index d474cc0..c241181 100644 --- a/app/MoveDb.scala +++ b/app/MoveDb.scala @@ -1,130 +1,18 @@ package lila.fishnet -import akka.actor._ -import akka.pattern.ask -import java.util.concurrent.TimeUnit import kamon.Kamon import org.joda.time.DateTime -import play.api.Logger -import scala.concurrent.duration._ -import scala.concurrent.{ ExecutionContext, Future } - -final class MoveDb(implicit system: ActorSystem, ec: ExecutionContext) { - - import MoveDb._ - import Work.Move - - implicit private val timeout = new akka.util.Timeout(2.seconds) - - def add(move: Move) = actor ! Add(move) - - def acquire(clientKey: ClientKey): Future[Option[Move]] = { - actor ? Acquire(clientKey) mapTo manifest[Option[Move]] - } - - def postResult( - workId: Work.Id, - data: JsonApi.Request.PostMove - ): Future[Option[Lila.Move]] = { - actor ? PostResult(workId, data) mapTo manifest[Option[Lila.Move]] - } - - private val actor = system.actorOf(Props(new Actor { - - val coll = scala.collection.mutable.Map.empty[Work.Id, Move] - - val maxSize = 300 - - def receive = { - - case Add(move) if !coll.exists(_._2 similar move) => coll += (move.id -> move) - - case Add(move) => - clearIfFull() - coll += (move.id -> move) - - case Acquire(clientKey) => - sender() ! coll.values - .foldLeft[Option[Move]](None) { - case (found, m) if m.nonAcquired => - Some { - found.fold(m) { a => - if (m.canAcquire(clientKey) && m.createdAt.isBefore(a.createdAt)) m else a - } - } - case (found, _) => found - } - .map { m => - val move = m assignTo clientKey - coll += (move.id -> move) - move - } - - case PostResult(workId, data) => - coll get workId match { - case None => - monitor.notFound(workId, data.clientKey) - sender() ! None - case Some(move) if move isAcquiredBy data.clientKey => - data.move.uci match { - case Some(uci) => - sender() ! Some(Lila.Move(move.game, uci)) - coll -= move.id - monitor.success(move) - case _ => - sender() ! None - updateOrGiveUp(move.invalid) - monitor.failure(move, data.clientKey, new Exception("Missing move")) - } - case Some(move) => - sender() ! None - monitor.notAcquired(move, data.clientKey) - } - - case Clean => - val since = DateTime.now minusSeconds 3 - val timedOut = coll.values.filter(_ acquiredBefore since) - if (timedOut.nonEmpty) logger.debug(s"cleaning ${timedOut.size} of ${coll.size} moves") - timedOut.foreach { m => - logger.info(s"Timeout move $m") - updateOrGiveUp(m.timeout) - } - monitor.dbSize.update(coll.size.toDouble) - monitor.dbQueued.update(coll.count(_._2.nonAcquired).toDouble) - monitor.dbAcquired.update(coll.count(_._2.isAcquired).toDouble) - } - - def updateOrGiveUp(move: Move) = - if (move.isOutOfTries) { - logger.warn(s"Give up on move $move") - coll -= move.id - } else coll += (move.id -> move) - - def clearIfFull() = - if (coll.size > maxSize) { - logger.warn(s"MoveDB collection is full! maxSize=$maxSize. Dropping all now!") - coll.clear() - } - })) - - system.scheduler.scheduleWithFixedDelay(5.seconds, 3.seconds) { () => - actor ? Clean mapTo manifest[Iterable[Move]] map { moves => - moves foreach { move => logger.info(s"Timeout move $move") } - } - } - - private val logger = Logger(getClass) - private val monitor = new Monitor(logger) -} +import java.util.concurrent.TimeUnit +import scala.concurrent.duration.* -object MoveDb { +object MoveDb: private case class Add(move: Work.Move) private case class Acquire(clientKey: ClientKey) - private case class PostResult(workId: Work.Id, data: JsonApi.Request.PostMove) + // private case class PostResult(workId: Work.Id, data: JsonApi.Request.PostMove) private object Clean - final private class Monitor(logger: Logger) { + final private class Monitor: val dbSize = Kamon.gauge("db.size").withoutTags() val dbQueued = Kamon.gauge("db.queued").withoutTags() @@ -132,27 +20,25 @@ object MoveDb { val lvl8AcquiredTimeRequest = Kamon.timer("move.acquired.lvl8").withoutTags() val lvl1FullTimeRequest = Kamon.timer("move.full.lvl1").withoutTags() - def success(work: Work.Move) = { + def success(work: Work.Move) = val now = System.currentTimeMillis if (work.level == 8) work.acquiredAt foreach { acquiredAt => lvl8AcquiredTimeRequest.record(now - acquiredAt.getMillis, TimeUnit.MILLISECONDS) } if (work.level == 1) lvl1FullTimeRequest.record(now - work.createdAt.getMillis, TimeUnit.MILLISECONDS) - } def failure(work: Work, clientKey: ClientKey, e: Exception) = { - logger.warn(s"Received invalid move ${work.id} for ${work.game.id} by $clientKey", e) + // logger.warn(s"Received invalid move ${work.id} for ${work.game.id} by $clientKey", e) } def notFound(id: Work.Id, clientKey: ClientKey) = { - logger.info(s"Received unknown work $id by $clientKey") + // logger.info(s"Received unknown work $id by $clientKey") } def notAcquired(work: Work, clientKey: ClientKey) = { - logger.info( - s"Received unacquired move ${work.id} for ${work.game.id} by $clientKey. Work current tries: ${work.tries} acquired: ${work.acquired}" - ) + // logger.info( + // s"Received unacquired move ${work.id} for ${work.game.id} by $clientKey. Work current tries: ${work.tries} acquired: ${work.acquired}" + // ) } - } -} + diff --git a/app/Work.scala b/app/Work.scala index 935bf2a..2eded09 100644 --- a/app/Work.scala +++ b/app/Work.scala @@ -2,9 +2,9 @@ package lila.fishnet import org.joda.time.DateTime import chess.variant.Variant -import chess.format.FEN +import chess.format.Fen -sealed trait Work { +sealed trait Work: def _id: Work.Id def game: Work.Game def tries: Int @@ -21,26 +21,24 @@ sealed trait Work { def nonAcquired = !isAcquired def canAcquire(clientKey: ClientKey) = lastTryByKey.fold(true)(clientKey.!=) - def acquiredBefore(date: DateTime) = acquiredAt.fold(false)(_ isBefore date) -} + def acquiredBefore(date: DateTime) = acquiredAt.fold(false)(_ `isBefore` date) -object Work { +object Work: case class Id(value: String) extends AnyVal with StringValue case class Acquired( clientKey: ClientKey, date: DateTime - ) { + ): def ageInMillis = System.currentTimeMillis - date.getMillis override def toString = s"by $clientKey at $date" - } case class Game( id: String, // can be a study chapter ID, if studyId is set - initialFen: Option[FEN], + initialFen: Option[Fen.Epd], variant: Variant, moves: String ) @@ -56,7 +54,7 @@ object Work { lastTryByKey: Option[ClientKey], acquired: Option[Acquired], createdAt: DateTime - ) extends Work { + ) extends Work: def assignTo(clientKey: ClientKey) = copy( @@ -79,7 +77,5 @@ object Work { override def toString = s"id:$id game:${game.id} variant:${game.variant.key} level:$level tries:$tries created:$createdAt acquired:$acquired" - } def makeId = Id(scala.util.Random.alphanumeric.take(8).mkString) -} diff --git a/app/controllers/FishnetController.scala b/app/controllers/FishnetController.scala deleted file mode 100644 index 7e54f59..0000000 --- a/app/controllers/FishnetController.scala +++ /dev/null @@ -1,62 +0,0 @@ -package controllers - -import play.api.Configuration -import play.api.libs.json._ -import play.api.mvc._ -import scala.concurrent.{ ExecutionContext, Future } - -import lila.fishnet._ - -class FishnetController( - config: Configuration, - lila: Lila, - moveDb: MoveDb, - val controllerComponents: ControllerComponents -)(implicit ec: ExecutionContext) - extends BaseController { - - val logger = play.api.Logger(getClass) - - val version = System.getProperty("java.version") - val memory = Runtime.getRuntime().maxMemory() / 1024 / 1024 - val useKamon = config.get[String]("kamon.influxdb.hostname").nonEmpty - - logger.info(s"lila-fishnet netty kamon=$useKamon") - logger.info(s"Java version: $version, memory: ${memory}MB") - - if (useKamon) kamon.Kamon.loadModules() - - import JsonApi.readers._ - import JsonApi.writers._ - - val sendMove = lila.pubsub("fishnet-in", "fishnet-out") - - def acquire = ClientAction[JsonApi.Request.Acquire](doAcquire) - - def move(workId: String) = - ClientAction[JsonApi.Request.PostMove] { data => - moveDb.postResult(Work.Id(workId), data) flatMap { move => - move foreach sendMove - doAcquire(data) - } - } - - private def doAcquire(req: JsonApi.Request): Future[Option[JsonApi.Work]] = - moveDb.acquire(req.clientKey) map { _ map JsonApi.moveFromWork } - - private def ClientAction[A <: JsonApi.Request]( - f: A => Future[Option[JsonApi.Work]] - )(implicit reads: Reads[A]) = - Action.async(parse.tolerantJson) { req => - req.body - .validate[A] - .fold( - err => Future successful BadRequest(JsError toJson err), - data => - f(data).map { - case Some(work) => Accepted(Json toJson work) - case None => NoContent - } - ) - } -} diff --git a/app/model.scala b/app/model.scala index 550ff4c..f577dfb 100644 --- a/app/model.scala +++ b/app/model.scala @@ -1,13 +1,11 @@ package lila.fishnet -trait StringValue extends Any { +trait StringValue extends Any: def value: String override def toString = value -} -trait IntValue extends Any { +trait IntValue extends Any: def value: Int override def toString = value.toString -} case class IpAddress(value: String) extends AnyVal with StringValue diff --git a/build.sbt b/build.sbt index 5868cbb..1388afc 100644 --- a/build.sbt +++ b/build.sbt @@ -1,67 +1,42 @@ -name := "lila-fishnet" - -version := "2.0" - -maintainer := "lichess.org" - -lazy val root = Project("lila-fishnet", file(".")) - .enablePlugins(PlayScala, PlayNettyServer) - -scalaVersion := "2.13.12" -Compile / resourceDirectory := baseDirectory.value / "conf" - -val kamonVersion = "2.5.11" - -libraryDependencies += "io.lettuce" % "lettuce-core" % "6.2.6.RELEASE" -libraryDependencies += "io.netty" % "netty-transport-native-epoll" % "4.1.101.Final" classifier "linux-x86_64" -libraryDependencies += "joda-time" % "joda-time" % "2.12.5" - -libraryDependencies += "org.lichess" %% "scalachess" % "10.6.3" -libraryDependencies += "io.kamon" %% "kamon-core" % kamonVersion -libraryDependencies += "io.kamon" %% "kamon-influxdb" % kamonVersion -libraryDependencies += "io.kamon" %% "kamon-system-metrics" % kamonVersion - -resolvers += "lila-maven" at "https://raw.githubusercontent.com/ornicar/lila-maven/master" - -scalacOptions ++= Seq( - "-explaintypes", - "-feature", - "-language:higherKinds", - "-language:implicitConversions", - "-language:postfixOps", - "-Ymacro-annotations", - // Warnings as errors! - // "-Xfatal-warnings", - // Linting options - "-unchecked", - "-Xcheckinit", - "-Xlint:adapted-args", - "-Xlint:constant", - "-Xlint:delayedinit-select", - "-Xlint:deprecation", - "-Xlint:inaccessible", - "-Xlint:infer-any", - "-Xlint:missing-interpolator", - "-Xlint:nullary-unit", - "-Xlint:option-implicit", - "-Xlint:package-object-classes", - "-Xlint:poly-implicit-overload", - "-Xlint:private-shadow", - "-Xlint:stars-align", - "-Xlint:type-parameter-shadow", - "-Wdead-code", - "-Wextra-implicit", - "-Wnumeric-widen", - "-Wunused:imports", - "-Wunused:locals", - "-Wunused:patvars", - "-Wunused:privates", - "-Wunused:implicits", - "-Wunused:params" - /* "-Wvalue-discard" */ +import Dependencies._ + +inThisBuild( + Seq( + scalaVersion := "3.3.1", + versionScheme := Some("early-semver"), + name := "lila-fishnet", + maintainer := "lichess.org", + version := "2.0", + run / fork := true, + ) ) -javaOptions ++= Seq("-Xms64m", "-Xmx128m") - -Compile / doc / sources := Seq.empty -Compile / packageDoc / publishArtifact := false +lazy val app = project + .settings( + scalacOptions -= "-Xfatal-warnings", + scalacOptions ++= Seq("-source:future", "-rewrite", "-indent", "-explain", "-Wunused:all"), + resolvers ++= Seq(Dependencies.lilaMaven), + libraryDependencies ++= Seq( + catsCore, + catsEffect, + chess, + circeCore, + circeGeneric, + circeParser, + cirisCore, + cirisHtt4s, + fs2, + jodaTime, + kamonCore, + kamonInflux, + kamonSystemMetrics, + http4sClient, + log4Cats, + weaver, + weaverScalaCheck, + ), + ) + +lazy val root = project + .in(file(".")) + .aggregate(app) diff --git a/project/Dependencies.scala b/project/Dependencies.scala new file mode 100644 index 0000000..dded4a0 --- /dev/null +++ b/project/Dependencies.scala @@ -0,0 +1,54 @@ +import sbt._ + +object Dependencies { + + val lilaMaven = "lila-maven" at "https://raw.githubusercontent.com/lichess-org/lila-maven/master" + + object V { + val fs2 = "3.9.3" + val circe = "0.14.6" + val http4s = "0.23.23" + val ciris = "3.4.0" + val kamon = "2.5.11" + } + + def http4s(artifact: String) = "org.http4s" %% s"http4s-$artifact" % V.http4s + def circe(artifact: String) = "io.circe" %% s"circe-$artifact" % V.circe + + val chess = "org.lichess" %% "scalachess" % "15.6.11" + + val catsCore = "org.typelevel" %% "cats-core" % "2.10.0" + + val catsEffect = "org.typelevel" %% "cats-effect" % "3.5.0" + + val fs2 = "co.fs2" %% "fs2-core" % V.fs2 + val fs2IO = "co.fs2" %% "fs2-io" % V.fs2 + + val circeCore = circe("core") + val circeParser = circe("parser") + val circeGeneric = circe("generic") + + val cirisCore = "is.cir" %% "ciris" % V.ciris + val cirisHtt4s = "is.cir" %% "ciris-http4s" % V.ciris + val cirisRefined = "is.cir" %% "ciris-refined" % V.ciris + + val jodaTime = "joda-time" % "joda-time" % "2.12.5" + + val kamonCore = "io.kamon" %% "kamon-core" % V.kamon + val kamonInflux = "io.kamon" %% "kamon-influxdb" % V.kamon + val kamonSystemMetrics = "io.kamon" %% "kamon-system-metrics" % V.kamon + + val http4sDsl = http4s("dsl") + val http4sServer = http4s("ember-server") + val http4sClient = http4s("ember-client") + val http4sCirce = http4s("circe") + + val log4Cats = "org.typelevel" %% "log4cats-slf4j" % "2.6.0" + val logback = "ch.qos.logback" % "logback-classic" % "1.4.11" + + val log4CatsNoop = "org.typelevel" %% "log4cats-noop" % "2.6.0" % Test + val testContainers = "com.dimafeng" %% "testcontainers-scala-redis" % "0.41.0" % Test + val weaver = "com.disneystreaming" %% "weaver-cats" % "0.8.3" % Test + val weaverScalaCheck = "com.disneystreaming" %% "weaver-scalacheck" % "0.8.3" % Test + +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 404a606..07654b1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,4 @@ -resolvers += Resolver.url( - "lila-maven-sbt", - url("https://raw.githubusercontent.com/ornicar/lila-maven/master") -)(Resolver.ivyStylePatterns) -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.18-lila_1.21") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") +addSbtPlugin("org.typelevel" % "sbt-tpolecat" % "0.5.0") +addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.19.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") +addSbtPlugin("nl.gn0s1s" % "sbt-dotenv" % "3.0.0")